public inbox for linux-bluetooth@vger.kernel.org
 help / color / mirror / Atom feed
From: "Frédéric Danis" <frederic.danis@collabora.com>
To: linux-bluetooth@vger.kernel.org
Subject: [PATCH BlueZ] client/btpclient: Add GATT read and write value supports
Date: Fri,  3 Apr 2026 16:36:31 +0200	[thread overview]
Message-ID: <20260403143631.545312-1-frederic.danis@collabora.com> (raw)

This stores the characteristics proxy and handle so we can call
org.bluez.GattCharacteristic1 ReadValue and WriteValue methods with
the "handle provided by PTS - 1".

This will allow to pass BAP/CL/CGGIT/CHA/BV-02-C and
BAP/UCL/CGGIT/CHA/BV-03-C.
---
 client/btpclient/gatt.c | 285 ++++++++++++++++++++++++++++++++++++++++
 src/shared/btp.h        |  35 +++++
 2 files changed, 320 insertions(+)

diff --git a/client/btpclient/gatt.c b/client/btpclient/gatt.c
index c6bb254b4..4a28a7b27 100644
--- a/client/btpclient/gatt.c
+++ b/client/btpclient/gatt.c
@@ -53,7 +53,10 @@ static void btp_gatt_read_commands(uint8_t index, const void *param,
 	}
 
 	commands |= (1 << BTP_OP_GATT_READ_SUPPORTED_COMMANDS);
+	commands |= (1 << BTP_OP_GATT_READ);
 	commands |= (1 << BTP_OP_GATT_READ_UUID);
+	commands |= (1 << BTP_OP_GATT_WRITE_WITHOUT_RSP);
+	commands |= (1 << BTP_OP_GATT_WRITE);
 
 	commands = L_CPU_TO_LE16(commands);
 
@@ -68,6 +71,13 @@ static bool match_attribute_uuid(const void *attr, const void *uuid)
 	return !bt_uuid_cmp(&attribute->uuid, uuid);
 }
 
+static bool match_attribute_handle(const void *attr, const void *handle)
+{
+	const struct gatt_attribute *attribute = attr;
+
+	return attribute->handle == L_PTR_TO_UINT(handle);
+}
+
 static void gatt_read_setup(struct l_dbus_message *message,
 							void *user_data)
 {
@@ -82,6 +92,93 @@ static void gatt_read_setup(struct l_dbus_message *message,
 	l_dbus_message_builder_destroy(builder);
 }
 
+static void gatt_read_reply(struct l_dbus_proxy *proxy,
+						struct l_dbus_message *result,
+						void *user_data)
+{
+	struct btp_adapter *adapter = user_data;
+	struct btp_gatt_read_rp *rp;
+	struct l_dbus_message_iter iter;
+	uint8_t *data;
+	uint32_t n;
+
+	if (l_dbus_message_is_error(result)) {
+		const char *name, *desc;
+
+		l_dbus_message_get_error(result, &name, &desc);
+		l_error("Failed to read value (%s), %s", name, desc);
+
+		btp_send_error(btp, BTP_GATT_SERVICE, adapter->index,
+							BTP_ERROR_FAIL);
+		return;
+	}
+
+	if (!l_dbus_message_get_arguments(result, "ay", &iter))
+		goto failed;
+
+	if (!l_dbus_message_iter_get_fixed_array(&iter, &data, &n)) {
+		l_debug("Cannot read value");
+		goto failed;
+	}
+
+	rp = malloc(sizeof(struct btp_gatt_read_rp) + n);
+	rp->response = 0;
+	rp->len = n;
+	memcpy(rp->data, data, n);
+
+	btp_send(btp, BTP_GATT_SERVICE, BTP_OP_GATT_READ, adapter->index,
+				sizeof(struct btp_gatt_read_rp) + n, rp);
+
+	free(rp);
+
+	return;
+
+failed:
+	btp_send_error(btp, BTP_GATT_SERVICE, adapter->index, BTP_ERROR_FAIL);
+}
+
+static void btp_gatt_read(uint8_t index, const void *param,
+					uint16_t length, void *user_data)
+{
+	struct btp_adapter *adapter = find_adapter_by_index(index);
+	struct btp_device *device;
+	const struct btp_gatt_read_cp *cp = param;
+	uint8_t status = BTP_ERROR_FAIL;
+	bool prop;
+	struct gatt_attribute *characteristic;
+
+	if (!adapter) {
+		status = BTP_ERROR_INVALID_INDEX;
+		goto failed;
+	}
+
+	/* Adapter needs to be powered to be able to read Handle */
+	if (!l_dbus_proxy_get_property(adapter->proxy, "Powered", "b",
+					&prop) || !prop) {
+		goto failed;
+	}
+
+	device = find_device_by_address(adapter, &cp->address,
+							cp->address_type);
+	if (!device)
+		goto failed;
+
+	characteristic = l_queue_find(device->characteristics,
+					match_attribute_handle,
+					L_UINT_TO_PTR(cp->handle - 1));
+	if (!characteristic)
+		goto failed;
+
+	l_dbus_proxy_method_call(characteristic->proxy, "ReadValue",
+					gatt_read_setup, gatt_read_reply,
+					adapter, NULL);
+
+	return;
+
+failed:
+	btp_send_error(btp, BTP_GATT_SERVICE, index, status);
+}
+
 static void gatt_read_uuid_reply(struct l_dbus_proxy *proxy,
 						struct l_dbus_message *result,
 						void *user_data)
@@ -187,6 +284,185 @@ failed:
 	btp_send_error(btp, BTP_GATT_SERVICE, index, status);
 }
 
+struct gatt_write_req {
+	struct btp_adapter *adapter;
+	const struct btp_gatt_write_cp *cp;
+	const char *type;
+};
+
+static void gatt_write_setup(struct l_dbus_message *message,
+							void *user_data)
+{
+	struct gatt_write_req *req = user_data;
+	struct l_dbus_message_builder *builder;
+	uint16_t i;
+
+	builder = l_dbus_message_builder_new(message);
+	l_dbus_message_builder_enter_array(builder, "y");
+	for (i = 0; i < req->cp->len; i++)
+		l_dbus_message_builder_append_basic(builder, 'y',
+						&(req->cp->data[i]));
+	l_dbus_message_builder_leave_array(builder);
+	l_dbus_message_builder_enter_array(builder, "{sv}");
+	l_dbus_message_builder_enter_dict(builder, "sv");
+	if (req->type) {
+		l_dbus_message_builder_append_basic(builder, 's', "type");
+		l_dbus_message_builder_enter_variant(builder, "s");
+		l_dbus_message_builder_append_basic(builder, 's', req->type);
+		l_dbus_message_builder_leave_variant(builder);
+	}
+	l_dbus_message_builder_leave_dict(builder);
+	l_dbus_message_builder_leave_array(builder);
+	l_dbus_message_builder_finalize(builder);
+	l_dbus_message_builder_destroy(builder);
+}
+
+static void gatt_write_without_reply(struct l_dbus_proxy *proxy,
+						struct l_dbus_message *result,
+						void *user_data)
+{
+	struct gatt_write_req *req = user_data;
+	struct btp_adapter *adapter = req->adapter;
+
+	if (l_dbus_message_is_error(result)) {
+		const char *name, *desc;
+
+		l_dbus_message_get_error(result, &name, &desc);
+		l_error("Failed to write value (%s), %s", name, desc);
+
+		btp_send_error(btp, BTP_GATT_SERVICE, adapter->index,
+							BTP_ERROR_FAIL);
+		return;
+	}
+
+	btp_send(btp, BTP_GATT_SERVICE, BTP_OP_GATT_WRITE_WITHOUT_RSP,
+				adapter->index, 0, NULL);
+}
+
+static void btp_gatt_write_without_rsp(uint8_t index, const void *param,
+					uint16_t length, void *user_data)
+{
+	struct btp_adapter *adapter = find_adapter_by_index(index);
+	struct btp_device *device;
+	const struct btp_gatt_write_cp *cp = param;
+	uint8_t status = BTP_ERROR_FAIL;
+	bool prop;
+	struct gatt_attribute *characteristic;
+	struct gatt_write_req *req;
+
+	if (!adapter) {
+		status = BTP_ERROR_INVALID_INDEX;
+		goto failed;
+	}
+
+	/* Adapter needs to be powered to be able to write to Handle */
+	if (!l_dbus_proxy_get_property(adapter->proxy, "Powered", "b",
+					&prop) || !prop) {
+		goto failed;
+	}
+
+	device = find_device_by_address(adapter, &cp->address,
+							cp->address_type);
+	if (!device)
+		goto failed;
+
+	characteristic = l_queue_find(device->characteristics,
+					match_attribute_handle,
+					L_UINT_TO_PTR(cp->handle - 1));
+	if (!characteristic)
+		goto failed;
+
+	req = l_new(struct gatt_write_req, 1);
+	req->adapter = adapter;
+	req->cp = cp;
+	req->type = "command";
+
+	l_dbus_proxy_method_call(characteristic->proxy, "WriteValue",
+					gatt_write_setup,
+					gatt_write_without_reply,
+					req, NULL);
+
+	return;
+
+failed:
+	btp_send_error(btp, BTP_GATT_SERVICE, index, status);
+}
+
+static void gatt_write_reply(struct l_dbus_proxy *proxy,
+						struct l_dbus_message *result,
+						void *user_data)
+{
+	struct gatt_write_req *req = user_data;
+	struct btp_adapter *adapter = req->adapter;
+	struct btp_gatt_write_rp *rp;
+
+	if (l_dbus_message_is_error(result)) {
+		const char *name, *desc;
+
+		l_dbus_message_get_error(result, &name, &desc);
+		l_error("Failed to write value (%s), %s", name, desc);
+
+		btp_send_error(btp, BTP_GATT_SERVICE, adapter->index,
+							BTP_ERROR_FAIL);
+		return;
+	}
+
+	rp = l_new(struct btp_gatt_write_rp, 1);
+	rp->att_response = 0;
+
+	btp_send(btp, BTP_GATT_SERVICE, BTP_OP_GATT_WRITE, adapter->index,
+				sizeof(struct btp_gatt_write_rp), rp);
+
+	free(rp);
+}
+
+static void btp_gatt_write(uint8_t index, const void *param,
+					uint16_t length, void *user_data)
+{
+	struct btp_adapter *adapter = find_adapter_by_index(index);
+	struct btp_device *device;
+	const struct btp_gatt_write_cp *cp = param;
+	uint8_t status = BTP_ERROR_FAIL;
+	bool prop;
+	struct gatt_attribute *characteristic;
+	struct gatt_write_req *req;
+
+	if (!adapter) {
+		status = BTP_ERROR_INVALID_INDEX;
+		goto failed;
+	}
+
+	/* Adapter needs to be powered to be able to write to Handle */
+	if (!l_dbus_proxy_get_property(adapter->proxy, "Powered", "b",
+					&prop) || !prop) {
+		goto failed;
+	}
+
+	device = find_device_by_address(adapter, &cp->address,
+							cp->address_type);
+	if (!device)
+		goto failed;
+
+	characteristic = l_queue_find(device->characteristics,
+					match_attribute_handle,
+					L_UINT_TO_PTR(cp->handle - 1));
+	if (!characteristic)
+		goto failed;
+
+	req = l_new(struct gatt_write_req, 1);
+	req->adapter = adapter;
+	req->cp = cp;
+
+	l_dbus_proxy_method_call(characteristic->proxy, "WriteValue",
+					gatt_write_setup, gatt_write_reply,
+					req, NULL);
+
+	return;
+
+failed:
+	btp_send_error(btp, BTP_GATT_SERVICE, index, status);
+}
+
 bool gatt_register_service(struct btp *btp_, struct l_dbus *dbus_,
 					struct l_dbus_client *client)
 {
@@ -195,9 +471,18 @@ bool gatt_register_service(struct btp *btp_, struct l_dbus *dbus_,
 	btp_register(btp, BTP_GATT_SERVICE, BTP_OP_GATT_READ_SUPPORTED_COMMANDS,
 					btp_gatt_read_commands, NULL, NULL);
 
+	btp_register(btp, BTP_GATT_SERVICE, BTP_OP_GATT_READ,
+					btp_gatt_read, NULL, NULL);
+
 	btp_register(btp, BTP_GATT_SERVICE, BTP_OP_GATT_READ_UUID,
 					btp_gatt_read_uuid, NULL, NULL);
 
+	btp_register(btp, BTP_GATT_SERVICE, BTP_OP_GATT_WRITE_WITHOUT_RSP,
+					btp_gatt_write_without_rsp, NULL, NULL);
+
+	btp_register(btp, BTP_GATT_SERVICE, BTP_OP_GATT_WRITE,
+					btp_gatt_write, NULL, NULL);
+
 	gatt_service_registered = true;
 
 	return true;
diff --git a/src/shared/btp.h b/src/shared/btp.h
index bdb5ad33e..8b5078f82 100644
--- a/src/shared/btp.h
+++ b/src/shared/btp.h
@@ -293,6 +293,19 @@ struct btp_gatt_char_value {
 
 #define BTP_OP_GATT_READ_SUPPORTED_COMMANDS	0x01
 
+#define BTP_OP_GATT_READ			0x11
+struct btp_gatt_read_cp {
+	uint8_t address_type;
+	bdaddr_t address;
+	uint16_t handle;
+} __packed;
+
+struct btp_gatt_read_rp {
+	uint8_t response;
+	uint16_t len;
+	uint8_t data[];
+} __packed;
+
 #define BTP_OP_GATT_READ_UUID			0x12
 struct btp_gatt_read_uuid_cp {
 	uint8_t address_type;
@@ -309,6 +322,28 @@ struct btp_gatt_read_uuid_rp {
 	struct btp_gatt_char_value values[];
 } __packed;
 
+#define BTP_OP_GATT_WRITE_WITHOUT_RSP		0x15
+struct btp_gatt_write_without_rsp_cp {
+	uint8_t address_type;
+	bdaddr_t address;
+	uint16_t handle;
+	uint16_t len;
+	uint8_t data[];
+} __packed;
+
+#define BTP_OP_GATT_WRITE			0x17
+struct btp_gatt_write_cp {
+	uint8_t address_type;
+	bdaddr_t address;
+	uint16_t handle;
+	uint16_t len;
+	uint8_t data[];
+} __packed;
+
+struct btp_gatt_write_rp {
+	uint8_t att_response;
+} __packed;
+
 struct btp;
 
 typedef void (*btp_destroy_func_t)(void *user_data);
-- 
2.43.0


             reply	other threads:[~2026-04-03 14:36 UTC|newest]

Thread overview: 2+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-04-03 14:36 Frédéric Danis [this message]
2026-04-03 15:46 ` [BlueZ] client/btpclient: Add GATT read and write value supports bluez.test.bot

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20260403143631.545312-1-frederic.danis@collabora.com \
    --to=frederic.danis@collabora.com \
    --cc=linux-bluetooth@vger.kernel.org \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox