public inbox for linux-bluetooth@vger.kernel.org
 help / color / mirror / Atom feed
* [PATCH v3 0/2] gatt-client:Implement error handling for DB_OUT_OF_SYNC in GATT caching.
@ 2026-01-21  8:38 Mengshi Wu
  2026-01-21  8:38 ` [PATCH v3 1/2] shared/att: Implement ATT error retry mechanism with callback support Mengshi Wu
  2026-01-21  8:38 ` [PATCH v3 2/2] gatt-client: Add DB_OUT_OF_SYNC error handling with retry mechanism Mengshi Wu
  0 siblings, 2 replies; 9+ messages in thread
From: Mengshi Wu @ 2026-01-21  8:38 UTC (permalink / raw)
  To: luiz.dentz
  Cc: linux-bluetooth, shuai.zhang, cheng.jiang, chezhou, wei.deng,
	yiboz, Mengshi Wu

shared/att: Implement ATT error retry mechanism with callback support
gatt-client: Add DB_OUT_OF_SYNC error handling with retry mechanism

Here are the btmon logs showing the automatic recovery of a failed
ATT request caused by a database out‑of‑sync error, when recovery
is possible:

bluetoothd[650]: < ACL Data TX: Handle 1 flags 0x00 dlen 7       #1 [hci1] 3.4439
      ATT: Read Request (0x0a) len 2
        Handle: 0x000b
> HCI Event: Number of Completed Packets (0x13) plen 5           #2 [hci1] 3.4588                              
        Num handles: 1
        Handle: 1
        Count: 1
> ACL Data RX: Handle 1 flags 0x02 dlen 6                        #3 [hci1] 3.4948
      ATT: Read Response (0x0b) len 1
bluetoothd[650]: < ACL Data TX: Handle 1 flags 0x00 dlen 7       #4 [hci1] 23.7473
      ATT: Read Request (0x0a) len 2
        Handle: 0x000b
> HCI Event: Number of Completed Packets (0x13) plen 5           #5 [hci1] 23.7722
        Num handles: 1
        Handle: 1
        Count: 1
> ACL Data RX: Handle 1 flags 0x02 dlen 9                        #6 [hci1] 23.8068
      ATT: Error Response (0x01) len 4
        Read Request (0x0a)
        Handle: 0x000b
        Error: Database Out of Sync (0x12)
bluetoothd[650]: < ACL Data TX: Handle 1 flags 0x00 dlen 11      #7 [hci1] 23.8093
      ATT: Read By Type Request (0x08) len 6
        Handle range: 0x0001-0xffff
        Attribute type: Database Hash (0x2b2a)
> HCI Event: Number of Completed Packets (0x13) plen 5           #8 [hci1] 23.8297
        Num handles: 1
        Handle: 1
        Count: 1
> ACL Data RX: Handle 1 flags 0x02 dlen 24                       #9 [hci1] 23.8668
      ATT: Read By Type Response (0x09) len 19
        Attribute data length: 18
        Attribute data list: 1 entry
        Handle: 0x000d
        Value: a01f96d239c187f8ba218f084501dad9
bluetoothd[650]: < ACL Data TX: Handle 1 flags 0x00 dlen 11      #10 [hci1] 23.8693
      ATT: Read By Type Request (0x08) len 6
        Handle range: 0x000e-0xffff
        Attribute type: Database Hash (0x2b2a)
> HCI Event: Number of Completed Packets (0x13) plen 5           #11 [hci1] 23.8914
        Num handles: 1
        Handle: 1
        Count: 1
> ACL Data RX: Handle 1 flags 0x02 dlen 9                        #12 [hci1] 23.9268
      ATT: Error Response (0x01) len 4
        Read By Type Request (0x08)
        Handle: 0x000e
        Error: Attribute Not Found (0x0a)
bluetoothd[650]: < ACL Data TX: Handle 1 flags 0x00 dlen 7       #13 [hci1] 23.9293
      ATT: Read Request (0x0a) len 2
        Handle: 0x000b
> HCI Event: Number of Completed Packets (0x13) plen 5           #14 [hci1] 23.9497
        Num handles: 1
        Handle: 1
        Count: 1
> ACL Data RX: Handle 1 flags 0x02 dlen 6                        #15 [hci1] 23.9908
      ATT: Read Response (0x0b) len 1


Here are the btmon logs showing the automatic rediscovery triggered by
a database out‑of‑sync error:

bluetoothd[650]: < ACL Data TX: Handle 1 flags 0x00 dlen 7       #22 [hci1] 114.579 784
      ATT: Read Request (0x0a) len 2
        Handle: 0x000b
> HCI Event: Number of Completed Packets (0x13) plen 5           #23 [hci1] 114.610 893
        Num handles: 1
        Handle: 1
        Count: 1
> ACL Data RX: Handle 1 flags 0x02 dlen 9                        #24 [hci1] 114.646 637
      ATT: Error Response (0x01) len 4
        Read Request (0x0a)
        Handle: 0x000b
        Error: Database Out of Sync (0x12)
bluetoothd[650]: < ACL Data TX: Handle 1 flags 0x00 dlen 11      #25 [hci1] 114.647 233
      ATT: Read By Type Request (0x08) len 6
        Handle range: 0x0001-0xffff
        Attribute type: Database Hash (0x2b2a)
> HCI Event: Number of Completed Packets (0x13) plen 5           #26 [hci1] 114.670 946
        Num handles: 1
        Handle: 1
        Count: 1
> ACL Data RX: Handle 1 flags 0x02 dlen 24                       #27 [hci1] 114.706 865
      ATT: Read By Type Response (0x09) len 19
        Attribute data length: 18
        Attribute data list: 1 entry
        Handle: 0x000d
        Value: 9eada072929f475ffa51d09c55f5e178
bluetoothd[650]: < ACL Data TX: Handle 1 flags 0x00 dlen 11      #28 [hci1] 114.709 230
      ATT: Read By Type Request (0x08) len 6
        Handle range: 0x000e-0xffff
        Attribute type: Database Hash (0x2b2a)
> HCI Event: Number of Completed Packets (0x13) plen 5           #29 [hci1] 114.730 974
        Num handles: 1
        Handle: 1
        Count: 1
> ACL Data RX: Handle 1 flags 0x02 dlen 9                        #30 [hci1] 114.766 849
      ATT: Error Response (0x01) len 4
        Read By Type Request (0x08)
        Handle: 0x000e
        Error: Attribute Not Found (0x0a)
bluetoothd[650]: < ACL Data TX: Handle 1 flags 0x00 dlen 11      #31 [hci1] 114.769 215
      ATT: Read By Group Type Request (0x10) len 6
        Handle range: 0x0001-0xffff
        Attribute group type: Primary Service (0x2800)
> HCI Event: Number of Completed Packets (0x13) plen 5           #32 [hci1] 114.791 091
        Num handles: 1
        Handle: 1
        Count: 1
> ACL Data RX: Handle 1 flags 0x02 dlen 24                       #33 [hci1] 114.826 851
      ATT: Read By Group Type Response (0x11) len 19
        Attribute data length: 6
        Attribute group list: 3 entries
        Handle range: 0x0001-0x0005
        UUID: Generic Access Profile (0x1800)
        Handle range: 0x0006-0x000f
        UUID: Generic Attribute Profile (0x1801)
        Handle range: 0x0010-0x0012
        UUID: Device Information (0x180a)
bluetoothd[650]: < ACL Data TX: Handle 1 flags 0x00 dlen 11     #34 [hci1] 114.829 308
      ATT: Read By Group Type Request (0x10) len 6
        Handle range: 0x0013-0xffff
        Attribute group type: Primary Service (0x2800)
> HCI Event: Number of Completed Packets (0x13) plen 5          #35 [hci1] 114.850 958
        Num handles: 1
        Handle: 1
        Count: 1
> ACL Data RX: Handle 1 flags 0x02 dlen 26                      #36 [hci1] 114.886 942
      ATT: Read By Group Type Response (0x11) len 21
        Attribute data length: 20
        Attribute group list: 1 entry
        Handle range: 0x0027-0x002a
        UUID: Vendor specific (12345678-1234-5678-1234-56789abcdef0)
bluetoothd[650]: < ACL Data TX: Handle 1 flags 0x00 dlen 11     #37 [hci1] 114.887 480
      ATT: Read By Group Type Request (0x10) len 6
        Handle range: 0x002b-0xffff
        Attribute group type: Primary Service (0x2800)
> HCI Event: Number of Completed Packets (0x13) plen 5          #38 [hci1] 114.910 960
        Num handles: 1
        Handle: 1
        Count: 1
> ACL Data RX: Handle 1 flags 0x02 dlen 9                       #39 [hci1] 114.946 605
      ATT: Error Response (0x01) len 4
        Read By Group Type Request (0x10)
        Handle: 0x002b
        Error: Attribute Not Found (0x0a)
bluetoothd[650]: < ACL Data TX: Handle 1 flags 0x00 dlen 11     #40 [hci1] 114.949 145
      ATT: Read By Group Type Request (0x10) len 6
        Handle range: 0x0001-0xffff
        Attribute group type: Secondary Service (0x2801)
> HCI Event: Number of Completed Packets (0x13) plen 5          #41 [hci1] 114.970 937
        Num handles: 1
        Handle: 1
        Count: 1
> ACL Data RX: Handle 1 flags 0x02 dlen 9                       #42 [hci1] 115.006 864
      ATT: Error Response (0x01) len 4
        Read By Group Type Request (0x10)
        Handle: 0x0001
        Error: Attribute Not Found (0x0a)
bluetoothd[650]: < ACL Data TX: Handle 1 flags 0x00 dlen 11    #43 [hci1] 115.007 315
      ATT: Read By Type Request (0x08) len 6
        Handle range: 0x0013-0x002a
        Attribute type: Include (0x2802)
> HCI Event: Number of Completed Packets (0x13) plen 5         #44 [hci1] 115.031 117
        Num handles: 1
        Handle: 1
        Count: 1
> ACL Data RX: Handle 1 flags 0x02 dlen 9                      #45 [hci1] 115.066 838
      ATT: Error Response (0x01) len 4
        Read By Type Request (0x08)
        Handle: 0x0013
        Error: Attribute Not Found (0x0a)
bluetoothd[650]: < ACL Data TX: Handle 1 flags 0x00 dlen 11    #46 [hci1] 115.067 316
      ATT: Read By Type Request (0x08) len 6
        Handle range: 0x0013-0x002a
        Attribute type: Characteristic (0x2803)
> HCI Event: Number of Completed Packets (0x13) plen 5         #47 [hci1] 115.092 808
        Num handles: 1
        Handle: 1
        Count: 1
> ACL Data RX: Handle 1 flags 0x02 dlen 27                     #48 [hci1] 115.126 872
      ATT: Read By Type Response (0x09) len 22
        Attribute data length: 21
        Attribute data list: 1 entry
        Handle: 0x0028
        Value: 1a2900f1debc9a785634127856341278563412
bluetoothd[650]: < ACL Data TX: Handle 1 flags 0x00 dlen 11    #49 [hci1] 115.127 341
      ATT: Read By Type Request (0x08) len 6
        Handle range: 0x0029-0x002a
        Attribute type: Characteristic (0x2803)
> HCI Event: Number of Completed Packets (0x13) plen 5         #50 [hci1] 115.150 892
        Num handles: 1
        Handle: 1
        Count: 1
> ACL Data RX: Handle 1 flags 0x02 dlen 9                      #51 [hci1] 115.186 863
      ATT: Error Response (0x01) len 4
        Read By Type Request (0x08)
        Handle: 0x0029
        Error: Attribute Not Found (0x0a)
bluetoothd[650]: < ACL Data TX: Handle 1 flags 0x00 dlen 11    #52 [hci1] 115.238 433
      ATT: Read By Type Request (0x08) len 6
        Handle range: 0x0001-0xffff
        Attribute type: Database Hash (0x2b2a)
> HCI Event: Number of Completed Packets (0x13) plen 5         #53 [hci1] 115.270 923
        Num handles: 1
        Handle: 1
        Count: 1
> ACL Data RX: Handle 1 flags 0x02 dlen 24                     #54 [hci1] 115.310 644
      ATT: Read By Type Response (0x09) len 19
        Attribute data length: 18
        Attribute data list: 1 entry
        Handle: 0x000d
        Value: 9eada072929f475ffa51d09c55f5e178
bluetoothd[650]: < ACL Data TX: Handle 1 flags 0x00 dlen 11    #55 [hci1] 115.311 066
      ATT: Read By Type Request (0x08) len 6
        Handle range: 0x000e-0xffff
        Attribute type: Database Hash (0x2b2a)
> HCI Event: Number of Completed Packets (0x13) plen 5         #56 [hci1] 115.332 821
        Num handles: 1
        Handle: 1
        Count: 1
> ACL Data RX: Handle 1 flags 0x02 dlen 9                      #57 [hci1] 115.366 878
      ATT: Error Response (0x01) len 4
        Read By Type Request (0x08)
        Handle: 0x000e
        Error: Attribute Not Found (0x0a)
bluetoothd[650]: < ACL Data TX: Handle 1 flags 0x00 dlen 9    #58 [hci1] 115.367 532
      ATT: Write Request (0x12) len 4
        Handle: 0x0009
          Data: 0200
> HCI Event: Number of Completed Packets (0x13) plen 5        #59 [hci1] 115.392 826
        Num handles: 1
        Handle: 1
        Count: 1
> ACL Data RX: Handle 1 flags 0x02 dlen 5                     #60 [hci1] 115.426 845
      ATT: Write Response (0x13) len 0
> HCI Event: LE Meta Event (0x3e) plen 6                      #61 [hci1] 123.431 741
      LE PHY Update Complete (0x0c)
        Status: Success (0x00)
        Handle: 1
        TX PHY: LE 1M (0x01)
        RX PHY: LE 2M (0x02)


Changes from v2:
 - Detects DB_OUT_OF_SYNC errors during GATT operations
 - Extracts affected handles from the original request PDU
 - Checks if Service Changed indications overlap with those handles
 - Verifies database consistency using Database Hash characteristic
 - Automatically retries the original request if DB is consistent
 - Automatically retries the original request if handle is not affected
 - Link to v2
   https://lore.kernel.org/all/20260105103828.105346-1-mengshi.wu@oss.qualcomm.com/

Changes from v1:
 - Implement automatic recovery when ATT_ECODE_DB_OUT_OF_SYNC error is
   received from the remote device.
 - Link to v1
   https://lore.kernel.org/all/20251208101915.247459-1-mengshi.wu@oss.qualcomm.com/

Mengshi Wu (2):
  shared/att: Implement ATT error retry mechanism with callback support
  gatt-client: Add DB_OUT_OF_SYNC error handling with retry mechanism

 src/shared/att.c          | 182 ++++++++++++++++++++++++++++++++++++--
 src/shared/att.h          |  16 ++++
 src/shared/gatt-client.c  | 168 +++++++++++++++++++++++++++++++++++
 src/shared/gatt-helpers.c |  16 ++++
 src/shared/gatt-helpers.h |   3 +
 5 files changed, 378 insertions(+), 7 deletions(-)

-- 
2.34.1


^ permalink raw reply	[flat|nested] 9+ messages in thread
* [PATCH v2 1/1] gatt-client:Implement error handling for DB_OUT_OF_SYNC in GATT caching.
@ 2026-01-05 10:38 Mengshi Wu
  2026-01-05 10:54 ` bluez.test.bot
  0 siblings, 1 reply; 9+ messages in thread
From: Mengshi Wu @ 2026-01-05 10:38 UTC (permalink / raw)
  To: luiz.dentz
  Cc: linux-bluetooth, shuai.zhang, cheng.jiang, chezhou, wei.deng,
	yiboz, Mengshi Wu

Implement automatic recovery when ATT_ECODE_DB_OUT_OF_SYNC error is
received from the remote device. The recovery mechanism:

- Detects DB_OUT_OF_SYNC errors during GATT operations
- Extracts affected handles from the original request PDU
- Checks if Service Changed indications overlap with those handles
- Verifies database consistency using Database Hash characteristic
- Automatically retries the original request if DB is consistent
- Automatically retries the original request if handle is not affected

This may prevent some application-level failures when the GATT database
changes on the remote device.

Signed-off-by: Mengshi Wu <mengshi.wu@oss.qualcomm.com>
---
 src/shared/gatt-client.c  | 376 +++++++++++++++++++++++++++++++++++++-
 src/shared/gatt-helpers.c |  16 ++
 src/shared/gatt-helpers.h |   3 +
 3 files changed, 392 insertions(+), 3 deletions(-)

diff --git a/src/shared/gatt-client.c b/src/shared/gatt-client.c
index f8ebab3fa..22b6c5d1d 100644
--- a/src/shared/gatt-client.c
+++ b/src/shared/gatt-client.c
@@ -41,6 +41,9 @@
 	gatt_log(_client, "[%p] %s:%s() " _format, _client, __FILE__, \
 		__func__, ## arg)
 
+#define DB_OUT_OF_SYNC_HOLD_ON true
+#define DB_OUT_OF_SYNC_GO_ON false
+
 struct ready_cb {
 	bt_gatt_client_callback_t callback;
 	bt_gatt_client_destroy_func_t destroy;
@@ -114,6 +117,9 @@ struct bt_gatt_client {
 
 	struct bt_gatt_request *discovery_req;
 	unsigned int mtu_req_id;
+
+	/* Pending DB out of sync handling */
+	struct db_out_of_sync_data *pending_db_sync;
 };
 
 struct request {
@@ -126,8 +132,31 @@ struct request {
 	unsigned int att_id;
 	void *data;
 	void (*destroy)(void *);
+
+	/* For DB_OUT_OF_SYNC recovery capability */
+	uint8_t *sent_pdu;
+	uint16_t sent_pdu_len;
+	uint8_t sent_opcode;
+};
+
+struct db_out_of_sync_data {
+	struct bt_gatt_client *client;
+	struct request *original_req;
+	uint8_t opcode;
+	uint8_t *pdu;
+	uint16_t pdu_len;
+	bt_att_response_func_t att_callback;
+	uint16_t *handles;
+	uint8_t num_handles;
+	bool handle_overlaps;
+	bool svc_changed_arrived;
+	/* Store original error PDU */
+	struct bt_att_pdu_error_rsp error_pdu;
 };
 
+static void db_out_of_sync_data_free(struct db_out_of_sync_data *data);
+static void call_original_callback_with_error(struct db_out_of_sync_data *data);
+
 static struct request *request_ref(struct request *req)
 {
 	__sync_fetch_and_add(&req->ref_count, 1);
@@ -210,6 +239,7 @@ static void request_unref(void *data)
 			notify_client_idle(client);
 	}
 
+	free(req->sent_pdu);
 	free(req);
 }
 
@@ -1968,11 +1998,272 @@ fail:
 		"Failed to initiate service discovery after Service Changed");
 }
 
+static void db_out_of_sync_recover(struct bt_gatt_client *client)
+{
+	struct db_out_of_sync_data *pending = client->pending_db_sync;
+	unsigned int new_att_id = 0;
+
+	assert(pending);
+
+	new_att_id = bt_att_send(client->att, pending->opcode, pending->pdu,
+				 pending->pdu_len, pending->att_callback,
+				 request_ref(pending->original_req),
+				 request_unref);
+	if (new_att_id)
+		pending->original_req->att_id = new_att_id;
+	else
+		call_original_callback_with_error(pending);
+	client->pending_db_sync = NULL;
+	db_out_of_sync_data_free(pending);
+}
+
+static void db_hash_check_read_cb(bool success, uint8_t att_ecode,
+				  struct bt_gatt_result *result,
+				  void *user_data)
+{
+	struct bt_gatt_client *client = user_data;
+	struct db_out_of_sync_data *pending = client->pending_db_sync;
+	const uint8_t *local_hash = NULL, *remote_hash;
+	struct gatt_db_attribute *hash_attr = NULL;
+	struct service_changed_op *op;
+	struct bt_gatt_iter iter;
+	bt_uuid_t uuid;
+	uint16_t handle, len;
+
+	assert(pending);
+
+	if (pending->svc_changed_arrived) {
+		if (!pending->handle_overlaps) {
+			/* No overlap - resend original request */
+			DBG(client, "Service changed range doesn't overlap");
+			db_out_of_sync_recover(client);
+		}
+
+		return;
+	}
+
+	/* If read failed, fall back to full re-discovery */
+	if (!success)
+		goto trigger_rediscovery;
+
+	if (!result || !bt_gatt_iter_init(&iter, result))
+		goto trigger_rediscovery;
+
+	if (!bt_gatt_iter_next_read_by_type(&iter, &handle,
+					&len, &remote_hash))
+		goto trigger_rediscovery;
+
+	if (len != 16)
+		goto trigger_rediscovery;
+
+	bt_uuid16_create(&uuid, GATT_CHARAC_DB_HASH);
+	gatt_db_find_by_type(client->db, 0x0001, 0xffff, &uuid,
+			     get_first_attribute, &hash_attr);
+
+	if (hash_attr) {
+		gatt_db_attribute_read(hash_attr, 0, BT_ATT_OP_READ_REQ, NULL,
+				       db_hash_read_value_cb, &local_hash);
+	}
+
+	/* If hashes match, no need to trigger re-discovery */
+	if (local_hash && !memcmp(local_hash, remote_hash, 16)) {
+		db_out_of_sync_recover(client);
+		return;
+	}
+
+	DBG(client, "DB Hash mismatch: triggering re-discovery");
+
+trigger_rediscovery:
+	call_original_callback_with_error(pending);
+	client->pending_db_sync = NULL;
+	db_out_of_sync_data_free(pending);
+
+	process_service_changed(client, 0x0001, 0xffff);
+}
+
+static void db_out_of_sync_data_free(struct db_out_of_sync_data *data)
+{
+	if (!data)
+		return;
+
+	if (data->original_req)
+		request_unref(data->original_req);
+
+	free(data->pdu);
+	free(data->handles);
+	free(data);
+}
+
+static bool check_handle_overlap(uint16_t *handles, uint8_t num_handles,
+				 uint16_t start, uint16_t end)
+{
+	uint8_t i;
+
+	if (!handles)
+		return true;
+
+	for (i = 0; i < num_handles; i++) {
+		if (handles[i] >= start && handles[i] <= end)
+			return true;
+	}
+
+	return false;
+}
+
+static uint8_t extract_handles_from_pdu(uint8_t opcode, const uint8_t *pdu,
+					uint16_t pdu_len, uint16_t **handles)
+{
+	uint8_t num_handles = 0;
+	uint16_t *handle_array;
+	uint16_t i;
+
+	switch (opcode) {
+	case BT_ATT_OP_READ_REQ:
+	case BT_ATT_OP_READ_BLOB_REQ:
+	case BT_ATT_OP_WRITE_REQ:
+	case BT_ATT_OP_WRITE_CMD:
+	case BT_ATT_OP_PREP_WRITE_REQ:
+		/* Single handle at offset 0 */
+		num_handles = 1;
+		handle_array = malloc(sizeof(uint16_t));
+		if (handle_array)
+			handle_array[0] = get_le16(pdu);
+		break;
+
+	case BT_ATT_OP_READ_MULT_REQ:
+	case BT_ATT_OP_READ_MULT_VL_REQ:
+		/* Multiple handles, 2 bytes each */
+		num_handles = pdu_len / 2;
+		handle_array = malloc(num_handles * sizeof(uint16_t));
+		if (handle_array) {
+			for (i = 0; i < num_handles; i++)
+				handle_array[i] = get_le16(pdu + (i * 2));
+		}
+		break;
+
+	default:
+		return 0;
+	}
+
+	if (!handle_array)
+		return 0;
+
+	*handles = handle_array;
+	return num_handles;
+}
+
+static void call_original_callback_with_error(struct db_out_of_sync_data *data)
+{
+	struct request *req = data->original_req;
+
+	if (!req || !data->att_callback)
+		return;
+
+	data->att_callback(BT_ATT_OP_ERROR_RSP, &(data->error_pdu),
+			   sizeof(struct bt_att_pdu_error_rsp), req);
+}
+
+static bool process_db_out_of_sync(struct bt_gatt_client *client,
+				   uint8_t att_ecode, const void *error_pdu,
+				   struct request *req,
+				   bt_att_response_func_t callback)
+{
+	struct db_out_of_sync_data *pending;
+	struct service_changed_op *op;
+	struct bt_gatt_request *gatt_req_op = client->discovery_req;
+	const struct bt_att_pdu_error_rsp *epdu = error_pdu;
+	bt_uuid_t uuid;
+	unsigned int new_att_id = 0;
+
+	if (att_ecode != BT_ATT_ERROR_DB_OUT_OF_SYNC)
+		return DB_OUT_OF_SYNC_GO_ON;
+
+	/* Check if we already have a pending operation */
+	if (client->pending_db_sync)
+		return DB_OUT_OF_SYNC_GO_ON;
+
+	/* Only handle if we have the necessary request info */
+	if (!req || !req->sent_pdu || !callback)
+		goto trigger_rediscovery;
+
+	/* Create pending structure */
+	pending = new0(struct db_out_of_sync_data, 1);
+	if (!pending)
+		goto trigger_rediscovery;
+
+	pending->client = client;
+	pending->original_req = request_ref(req);
+	pending->att_callback = callback;
+	pending->opcode = req->sent_opcode;
+	pending->pdu_len = req->sent_pdu_len;
+
+	/* Copy PDU */
+	pending->pdu = malloc(pending->pdu_len);
+	if (!pending->pdu) {
+		db_out_of_sync_data_free(pending);
+		goto trigger_rediscovery;
+	}
+	memcpy(pending->pdu, req->sent_pdu, pending->pdu_len);
+
+	/* Store original error PDU */
+	memcpy(&(pending->error_pdu), error_pdu,
+	       sizeof(struct bt_att_pdu_error_rsp));
+
+	/* Extract handles from PDU */
+	pending->num_handles =
+		extract_handles_from_pdu(pending->opcode, pending->pdu,
+					 pending->pdu_len, &pending->handles);
+	if (!pending->num_handles) {
+		db_out_of_sync_data_free(pending);
+		goto trigger_rediscovery;
+	}
+
+	/* Store pending operation */
+	client->pending_db_sync = pending;
+
+	/* Initiate hash read */
+	bt_uuid16_create(&uuid, GATT_CHARAC_DB_HASH);
+
+	if (bt_gatt_read_by_type(client->att, 0x0001, 0xffff, &uuid,
+				 db_hash_check_read_cb, client, NULL)) {
+		return DB_OUT_OF_SYNC_HOLD_ON;
+	}
+
+	client->pending_db_sync = NULL;
+	db_out_of_sync_data_free(pending);
+
+trigger_rediscovery:
+
+	if (client->in_svc_chngd) {
+		if (client->discovery_req && req && req->sent_pdu && callback &&
+		    (epdu->handle != 0) && gatt_req_op &&
+		    (bt_gatt_request_get_start_handle(gatt_req_op) >
+			     epdu->handle ||
+		     bt_gatt_request_get_end_handle(gatt_req_op) <
+			     epdu->handle)) {
+			new_att_id = bt_att_send(client->att, req->sent_opcode,
+						 req->sent_pdu,
+						 req->sent_pdu_len, callback,
+						 request_ref(req),
+						 request_unref);
+			if (new_att_id) {
+				req->att_id = new_att_id;
+				return DB_OUT_OF_SYNC_HOLD_ON;
+			}
+		}
+		return DB_OUT_OF_SYNC_GO_ON;
+	}
+
+	process_service_changed(client, 0x0001, 0xffff);
+	return DB_OUT_OF_SYNC_GO_ON;
+}
+
 static void service_changed_cb(uint16_t value_handle, const uint8_t *value,
 					uint16_t length, void *user_data)
 {
 	struct bt_gatt_client *client = user_data;
 	struct service_changed_op *op;
+	struct db_out_of_sync_data *pending;
 	uint16_t start, end;
 
 	if (length != 4)
@@ -1990,6 +2281,14 @@ static void service_changed_cb(uint16_t value_handle, const uint8_t *value,
 	DBG(client, "Service Changed received - start: 0x%04x end: 0x%04x",
 			start, end);
 
+	/* Check if there's a pending DB out of sync operation */
+	pending = client->pending_db_sync;
+	if (pending) {
+		pending->svc_changed_arrived = true;
+		pending->handle_overlaps = check_handle_overlap(pending->handles,
+					pending->num_handles, start, end);
+	}
+
 	if (!client->in_svc_chngd) {
 		process_service_changed(client, start, end);
 		return;
@@ -2332,6 +2631,10 @@ static void att_disconnect_cb(int err, void *user_data)
 
 	client->disc_id = 0;
 
+	/* Cleanup pending DB out of sync operation */
+	db_out_of_sync_data_free(client->pending_db_sync);
+	client->pending_db_sync = NULL;
+
 	bt_att_unref(client->att);
 	client->att = NULL;
 
@@ -2712,10 +3015,15 @@ static void read_multiple_cb(uint8_t opcode, const void *pdu, uint16_t length,
 			(!pdu && length)) {
 		success = false;
 
-		if (opcode == BT_ATT_OP_ERROR_RSP)
+		if (opcode == BT_ATT_OP_ERROR_RSP) {
 			att_ecode = process_error(pdu, length);
-		else
+			if (process_db_out_of_sync(req->client, att_ecode,
+						   pdu, req,
+						   read_multiple_cb))
+				return;
+		} else {
 			att_ecode = 0;
+		}
 
 		pdu = NULL;
 		length = 0;
@@ -2799,6 +3107,13 @@ unsigned int bt_gatt_client_read_multiple(struct bt_gatt_client *client,
 		BT_GATT_CHRC_CLI_FEAT_EATT ? BT_ATT_OP_READ_MULT_VL_REQ :
 		BT_ATT_OP_READ_MULT_REQ;
 
+	/* Store PDU for potential resend on DB_OUT_OF_SYNC */
+	req->sent_opcode = opcode;
+	req->sent_pdu_len = num_handles * 2;
+	req->sent_pdu = malloc(req->sent_pdu_len);
+	if (req->sent_pdu)
+		memcpy(req->sent_pdu, pdu, req->sent_pdu_len);
+
 	req->att_id = bt_att_send(client->att, opcode, pdu, num_handles * 2,
 							read_multiple_cb, req,
 							request_unref);
@@ -2867,6 +3182,10 @@ static void read_long_cb(uint8_t opcode, const void *pdu,
 	if (opcode == BT_ATT_OP_ERROR_RSP) {
 		success = false;
 		att_ecode = process_error(pdu, length);
+		if (process_db_out_of_sync(req->client, att_ecode,
+					   pdu, req,
+					   read_long_cb))
+			return;
 		goto done;
 	}
 
@@ -2975,6 +3294,13 @@ unsigned int bt_gatt_client_read_long_value(struct bt_gatt_client *client,
 		att_op = BT_ATT_OP_READ_REQ;
 	}
 
+	/* Store PDU for potential resend on DB_OUT_OF_SYNC */
+	req->sent_opcode = att_op;
+	req->sent_pdu_len = pdu_len;
+	req->sent_pdu = malloc(req->sent_pdu_len);
+	if (req->sent_pdu)
+		memcpy(req->sent_pdu, pdu, req->sent_pdu_len);
+
 	req->att_id = bt_att_send(client->att, att_op, pdu, pdu_len,
 					read_long_cb, req, request_unref);
 
@@ -3053,6 +3379,9 @@ static void write_cb(uint8_t opcode, const void *pdu, uint16_t length,
 	if (opcode == BT_ATT_OP_ERROR_RSP) {
 		success = false;
 		att_ecode = process_error(pdu, length);
+		if (process_db_out_of_sync(req->client, att_ecode,
+					   pdu, req, write_cb))
+			return;
 		goto done;
 	}
 
@@ -3096,6 +3425,13 @@ unsigned int bt_gatt_client_write_value(struct bt_gatt_client *client,
 	put_le16(value_handle, pdu);
 	memcpy(pdu + 2, value, length);
 
+	/* Store PDU for potential resend on DB_OUT_OF_SYNC */
+	req->sent_opcode = BT_ATT_OP_WRITE_REQ;
+	req->sent_pdu_len = 2 + length;
+	req->sent_pdu = malloc(req->sent_pdu_len);
+	if (req->sent_pdu)
+		memcpy(req->sent_pdu, pdu, req->sent_pdu_len);
+
 	req->att_id = bt_att_send(client->att, BT_ATT_OP_WRITE_REQ,
 							pdu, 2 + length,
 							write_cb, req,
@@ -3216,6 +3552,10 @@ static void execute_write_cb(uint8_t opcode, const void *pdu, uint16_t length,
 	if (opcode == BT_ATT_OP_ERROR_RSP) {
 		success = false;
 		att_ecode = process_error(pdu, length);
+		if (process_db_out_of_sync(req->client, att_ecode,
+					   pdu, req,
+					   execute_write_cb))
+			return;
 	} else if (opcode != BT_ATT_OP_EXEC_WRITE_RSP || pdu || length)
 		success = false;
 
@@ -3281,6 +3621,10 @@ static void prepare_write_cb(uint8_t opcode, const void *pdu, uint16_t length,
 	if (opcode == BT_ATT_OP_ERROR_RSP) {
 		success = false;
 		att_ecode = process_error(pdu, length);
+		if (process_db_out_of_sync(req->client, att_ecode,
+					   pdu, req,
+					   prepare_write_cb))
+			return;
 		goto done;
 	}
 
@@ -3401,11 +3745,15 @@ unsigned int bt_gatt_client_write_long_value(struct bt_gatt_client *client,
 	put_le16(offset, pdu + 2);
 	memcpy(pdu + 4, op->value, op->cur_length);
 
+	/* Store PDU for potential resend on DB_OUT_OF_SYNC */
+	req->sent_opcode = BT_ATT_OP_PREP_WRITE_REQ;
+	req->sent_pdu_len = op->cur_length + 4;
+	req->sent_pdu = pdu;
+
 	req->att_id = bt_att_send(client->att, BT_ATT_OP_PREP_WRITE_REQ,
 							pdu, op->cur_length + 4,
 							prepare_write_cb, req,
 							request_unref);
-	free(pdu);
 
 	if (!req->att_id) {
 		op->destroy = NULL;
@@ -3450,6 +3798,10 @@ static void prep_write_cb(uint8_t opcode, const void *pdu, uint16_t length,
 		success = false;
 		reliable_error = false;
 		att_ecode = process_error(pdu, length);
+		if (process_db_out_of_sync(req->client, att_ecode,
+					   pdu, req,
+					   prep_write_cb))
+			return;
 		goto done;
 	}
 
@@ -3566,6 +3918,13 @@ unsigned int bt_gatt_client_prepare_write(struct bt_gatt_client *client,
 	memcpy(op->pdu, pdu, length);
 	op->pdu_len = length;
 
+	/* Store PDU for potential resend on DB_OUT_OF_SYNC */
+	req->sent_opcode = BT_ATT_OP_PREP_WRITE_REQ;
+	req->sent_pdu_len = length;
+	req->sent_pdu = malloc(req->sent_pdu_len);
+	if (req->sent_pdu)
+		memcpy(req->sent_pdu, pdu, req->sent_pdu_len);
+
 	/*
 	 * Now we are ready to send command
 	 * Note that request_unref will be done on write execute
@@ -3600,6 +3959,10 @@ static void exec_write_cb(uint8_t opcode, const void *pdu, uint16_t length,
 	if (opcode == BT_ATT_OP_ERROR_RSP) {
 		success = false;
 		att_ecode = process_error(pdu, length);
+		if (process_db_out_of_sync(req->client, att_ecode,
+					   pdu, req,
+					   exec_write_cb))
+			return;
 		goto done;
 	}
 
@@ -3659,6 +4022,13 @@ unsigned int bt_gatt_client_write_execute(struct bt_gatt_client *client,
 	req->data = op;
 	req->destroy = destroy_write_op;
 
+	/* Store PDU for potential resend on DB_OUT_OF_SYNC */
+	req->sent_opcode = BT_ATT_OP_EXEC_WRITE_REQ;
+	req->sent_pdu_len = sizeof(pdu);
+	req->sent_pdu = malloc(req->sent_pdu_len);
+	if (req->sent_pdu)
+		memcpy(req->sent_pdu, &pdu, req->sent_pdu_len);
+
 	req->att_id = bt_att_send(client->att, BT_ATT_OP_EXEC_WRITE_REQ, &pdu,
 						sizeof(pdu), exec_write_cb,
 						req, request_unref);
diff --git a/src/shared/gatt-helpers.c b/src/shared/gatt-helpers.c
index c1cbbdc91..e3a6548c4 100644
--- a/src/shared/gatt-helpers.c
+++ b/src/shared/gatt-helpers.c
@@ -790,6 +790,22 @@ done:
 	discovery_op_complete(op, success, att_ecode);
 }
 
+uint16_t bt_gatt_request_get_start_handle(struct bt_gatt_request *request)
+{
+	if (!request)
+		return 0;
+
+	return request->start_handle;
+}
+
+uint16_t bt_gatt_request_get_end_handle(struct bt_gatt_request *request)
+{
+	if (!request)
+		return 0;
+
+	return request->end_handle;
+}
+
 static struct bt_gatt_request *discover_services(struct bt_att *att,
 					bt_uuid_t *uuid,
 					uint16_t start, uint16_t end,
diff --git a/src/shared/gatt-helpers.h b/src/shared/gatt-helpers.h
index 7623862e9..7a51ec619 100644
--- a/src/shared/gatt-helpers.h
+++ b/src/shared/gatt-helpers.h
@@ -101,3 +101,6 @@ bool bt_gatt_read_by_type(struct bt_att *att, uint16_t start, uint16_t end,
 					bt_gatt_request_callback_t callback,
 					void *user_data,
 					bt_gatt_destroy_func_t destroy);
+
+uint16_t bt_gatt_request_get_end_handle(struct bt_gatt_request *request);
+uint16_t bt_gatt_request_get_start_handle(struct bt_gatt_request *request);
-- 
2.34.1


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

end of thread, other threads:[~2026-02-02  2:46 UTC | newest]

Thread overview: 9+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-01-21  8:38 [PATCH v3 0/2] gatt-client:Implement error handling for DB_OUT_OF_SYNC in GATT caching Mengshi Wu
2026-01-21  8:38 ` [PATCH v3 1/2] shared/att: Implement ATT error retry mechanism with callback support Mengshi Wu
2026-01-21  8:59   ` gatt-client:Implement error handling for DB_OUT_OF_SYNC in GATT caching bluez.test.bot
2026-01-21 18:15   ` [PATCH v3 1/2] shared/att: Implement ATT error retry mechanism with callback support Luiz Augusto von Dentz
2026-01-26 10:44     ` Mengshi Wu
2026-01-27 15:35       ` Luiz Augusto von Dentz
2026-02-02  2:46         ` Mengshi Wu
2026-01-21  8:38 ` [PATCH v3 2/2] gatt-client: Add DB_OUT_OF_SYNC error handling with retry mechanism Mengshi Wu
  -- strict thread matches above, loose matches on Subject: below --
2026-01-05 10:38 [PATCH v2 1/1] gatt-client:Implement error handling for DB_OUT_OF_SYNC in GATT caching Mengshi Wu
2026-01-05 10:54 ` bluez.test.bot

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