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
next 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