All of lore.kernel.org
 help / color / mirror / Atom feed
* [PATCH v2 0/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:38 ` [PATCH v2 1/1] " Mengshi Wu
  0 siblings, 1 reply; 6+ 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


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 (1):
  gatt-client:Implement error handling for DB_OUT_OF_SYNC in GATT
    caching.

 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(-)

-- 
2.34.1


^ permalink raw reply	[flat|nested] 6+ messages in thread
* [PATCH v3 1/2] shared/att: Implement ATT error retry mechanism with callback support
@ 2026-01-21  8:38 Mengshi Wu
  2026-01-21  8:59 ` gatt-client:Implement error handling for DB_OUT_OF_SYNC in GATT caching bluez.test.bot
  0 siblings, 1 reply; 6+ 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

Add a retry mechanism for ATT operations that allows upper layers to
decide whether to retry failed requests. This includes:

- Add retry callback registration (bt_att_set_retry_cb) to allow
  applications to handle retry decisions
- Implement pending_retry state tracking in att_send_op to store
  operations awaiting retry approval
- Add bt_att_retry_request() and bt_att_cancel_retry() functions to
  approve or reject retry attempts
- Store error PDUs during retry_pending state for callback inspection
- Modify handle_error_rsp() to return retry decision codes instead of
  boolean values
- Add BT_ATT_RETRY_* constants for retry decision handling
- Update GATT helpers to support retry callbacks for operations like
  discovery and read/write requests

This enables more robust error handling by allowing the application
layer to implement custom retry logic based on ATT error codes.

Signed-off-by: Mengshi Wu <mengshi.wu@oss.qualcomm.com>
---
 src/shared/att.c | 182 +++++++++++++++++++++++++++++++++++++++++++++--
 src/shared/att.h |  16 +++++
 2 files changed, 191 insertions(+), 7 deletions(-)

diff --git a/src/shared/att.c b/src/shared/att.c
index 77ca4aa24..4ae97530a 100644
--- a/src/shared/att.c
+++ b/src/shared/att.c
@@ -47,6 +47,7 @@ struct bt_att_chan {
 
 	struct att_send_op *pending_req;
 	struct att_send_op *pending_ind;
+	struct att_send_op *pending_retry;
 	bool writer_active;
 
 	bool in_req;			/* There's a pending incoming request */
@@ -78,6 +79,10 @@ struct bt_att {
 	bt_att_destroy_func_t timeout_destroy;
 	void *timeout_data;
 
+	bt_att_retry_func_t retry_callback;
+	bt_att_destroy_func_t retry_destroy;
+	void *retry_data;
+
 	uint8_t debug_level;
 	bt_att_debug_func_t debug_callback;
 	bt_att_destroy_func_t debug_destroy;
@@ -194,6 +199,9 @@ struct att_send_op {
 	void *pdu;
 	uint16_t len;
 	bool retry;
+	bool retry_pending;  /* Waiting for approval to retry */
+	uint8_t *error_pdu;  /* Stored error PDU for retry_pending */
+	uint16_t error_pdu_len;
 	bt_att_response_func_t callback;
 	bt_att_destroy_func_t destroy;
 	void *user_data;
@@ -210,6 +218,7 @@ static void destroy_att_send_op(void *data)
 		op->destroy(op->user_data);
 
 	free(op->pdu);
+	free(op->error_pdu);
 	free(op);
 }
 
@@ -644,6 +653,9 @@ static void bt_att_chan_free(void *data)
 	if (chan->pending_ind)
 		destroy_att_send_op(chan->pending_ind);
 
+	if (chan->pending_retry)
+		destroy_att_send_op(chan->pending_retry);
+
 	queue_destroy(chan->queue, destroy_att_send_op);
 
 	io_destroy(chan->io);
@@ -682,6 +694,11 @@ static bool disconnect_cb(struct io *io, void *user_data)
 		chan->pending_ind = NULL;
 	}
 
+	if (chan->pending_retry) {
+		disc_att_send_op(chan->pending_retry);
+		chan->pending_retry = NULL;
+	}
+
 	bt_att_chan_free(chan);
 
 	/* Don't run disconnect callback if there are channels left */
@@ -777,16 +794,17 @@ static bool change_security(struct bt_att_chan *chan, uint8_t ecode)
 	return bt_att_chan_set_security(chan, security);
 }
 
-static bool handle_error_rsp(struct bt_att_chan *chan, uint8_t *pdu,
+static int handle_error_rsp(struct bt_att_chan *chan, uint8_t *pdu,
 					ssize_t pdu_len, uint8_t *opcode)
 {
 	struct bt_att *att = chan->att;
 	const struct bt_att_pdu_error_rsp *rsp;
 	struct att_send_op *op = chan->pending_req;
+	int should_retry = BT_ATT_RETRY_NO;
 
 	if (pdu_len != sizeof(*rsp)) {
 		*opcode = 0;
-		return false;
+		return should_retry;
 	}
 
 	rsp = (void *) pdu;
@@ -797,11 +815,43 @@ static bool handle_error_rsp(struct bt_att_chan *chan, uint8_t *pdu,
 	 * the security again.
 	 */
 	if (op->retry)
-		return false;
+		return should_retry;
 
 	/* Attempt to change security */
-	if (!change_security(chan, rsp->ecode))
-		return false;
+	if (change_security(chan, rsp->ecode)) {
+		should_retry = BT_ATT_RETRY_YES;
+	} else if (att->retry_callback) {
+		should_retry = att->retry_callback(op->opcode, rsp->ecode,
+						   op->pdu + 1, op->len - 1,
+						   op->id, att->retry_data);
+
+		/* Check if callback wants to defer the retry decision */
+		if (should_retry == BT_ATT_RETRY_PENDING) {
+			op->retry_pending = true;
+
+			/* Store error PDU for later use */
+			op->error_pdu = malloc(pdu_len);
+			if (op->error_pdu) {
+				memcpy(op->error_pdu, pdu, pdu_len);
+				op->error_pdu_len = pdu_len;
+			}
+
+			/* Remove timeout since we're waiting for approval */
+			if (op->timeout_id) {
+				timeout_remove(op->timeout_id);
+				op->timeout_id = 0;
+			}
+
+			/* Move from pending_req to pending_retry */
+			chan->pending_retry = op;
+
+			DBG(att, "(chan %p) Retry pending for operation %p",
+			    chan, op);
+		}
+	}
+
+	if (should_retry != BT_ATT_RETRY_YES)
+		return should_retry;
 
 	/* Remove timeout_id if outstanding */
 	if (op->timeout_id) {
@@ -815,7 +865,8 @@ static bool handle_error_rsp(struct bt_att_chan *chan, uint8_t *pdu,
 	op->retry = true;
 
 	/* Push operation back to channel queue */
-	return queue_push_head(chan->queue, op);
+	return queue_push_head(chan->queue, op) ?
+		BT_ATT_RETRY_YES : BT_ATT_RETRY_NO;
 }
 
 static void handle_rsp(struct bt_att_chan *chan, uint8_t opcode, uint8_t *pdu,
@@ -845,9 +896,15 @@ static void handle_rsp(struct bt_att_chan *chan, uint8_t opcode, uint8_t *pdu,
 	 */
 	if (opcode == BT_ATT_OP_ERROR_RSP) {
 		/* Return if error response cause a retry */
-		if (handle_error_rsp(chan, pdu, pdu_len, &req_opcode)) {
+		switch (handle_error_rsp(chan, pdu, pdu_len, &req_opcode)) {
+		case BT_ATT_RETRY_PENDING:
+			/* Operation moved to pending_retry, clear pending_req */
+			chan->pending_req = NULL;
+		case BT_ATT_RETRY_YES:
 			wakeup_chan_writer(chan, NULL);
 			return;
+		default:
+			break;
 		}
 	} else if (!(req_opcode = get_req_opcode(opcode)))
 		goto fail;
@@ -1142,6 +1199,9 @@ static void bt_att_free(struct bt_att *att)
 	if (att->timeout_destroy)
 		att->timeout_destroy(att->timeout_data);
 
+	if (att->retry_destroy)
+		att->retry_destroy(att->retry_data);
+
 	if (att->debug_destroy)
 		att->debug_destroy(att->debug_data);
 
@@ -1473,6 +1533,23 @@ bool bt_att_set_timeout_cb(struct bt_att *att, bt_att_timeout_func_t callback,
 	return true;
 }
 
+bool bt_att_set_retry_cb(struct bt_att *att, bt_att_retry_func_t callback,
+						void *user_data,
+						bt_att_destroy_func_t destroy)
+{
+	if (!att)
+		return false;
+
+	if (att->retry_destroy)
+		att->retry_destroy(att->retry_data);
+
+	att->retry_callback = callback;
+	att->retry_destroy = destroy;
+	att->retry_data = user_data;
+
+	return true;
+}
+
 unsigned int bt_att_register_disconnect(struct bt_att *att,
 					bt_att_disconnect_func_t callback,
 					void *user_data,
@@ -2051,6 +2128,97 @@ bool bt_att_has_crypto(struct bt_att *att)
 	return att->crypto ? true : false;
 }
 
+bool bt_att_retry_request(struct bt_att *att, unsigned int id)
+{
+	const struct queue_entry *entry;
+	struct bt_att_chan *chan = NULL;
+	struct att_send_op *op;
+
+	if (!att || !id)
+		return false;
+
+	/* Find the channel with the pending retry operation */
+	for (entry = queue_get_entries(att->chans); entry;
+						entry = entry->next) {
+		struct bt_att_chan *c = entry->data;
+
+		if (c->pending_retry && c->pending_retry->id == id &&
+		    c->pending_retry->retry_pending) {
+			chan = c;
+			op = c->pending_retry;
+			break;
+		}
+	}
+
+	if (!chan || !op)
+		return false;
+
+	DBG(att, "(chan %p) Approving retry for operation %p", chan, op);
+
+	/* Clear pending retry state and mark for retry */
+	op->retry_pending = false;
+	op->retry = true;
+	chan->pending_retry = NULL;
+
+	/* Free stored error PDU as we're retrying */
+	free(op->error_pdu);
+	op->error_pdu = NULL;
+	op->error_pdu_len = 0;
+
+	/* Push operation back to channel queue for retry */
+	if (!queue_push_head(chan->queue, op))
+		return false;
+
+	/* Wake up writer to send the retry */
+	wakeup_chan_writer(chan, NULL);
+
+	return true;
+}
+
+bool bt_att_cancel_retry(struct bt_att *att, unsigned int id)
+{
+	const struct queue_entry *entry;
+	struct bt_att_chan *chan = NULL;
+	struct att_send_op *op;
+
+	if (!att || !id)
+		return false;
+
+	/* Find the channel with the pending retry operation */
+	for (entry = queue_get_entries(att->chans); entry;
+						entry = entry->next) {
+		struct bt_att_chan *c = entry->data;
+
+		if (c->pending_retry && c->pending_retry->id == id &&
+		    c->pending_retry->retry_pending) {
+			chan = c;
+			op = c->pending_retry;
+			break;
+		}
+	}
+
+	if (!chan || !op)
+		return false;
+
+	DBG(att, "(chan %p) Canceling retry for operation %p", chan, op);
+
+	/* Clear pending retry state */
+	op->retry_pending = false;
+	chan->pending_retry = NULL;
+
+	/* Call the callback with stored error PDU to notify upper layer */
+	if (op->callback)
+		op->callback(BT_ATT_OP_ERROR_RSP, op->error_pdu,
+			     op->error_pdu_len, op->user_data);
+
+	destroy_att_send_op(op);
+
+	/* Wake up writer in case there are other operations */
+	wakeup_chan_writer(chan, NULL);
+
+	return true;
+}
+
 bool bt_att_set_retry(struct bt_att *att, unsigned int id, bool retry)
 {
 	struct att_send_op *op;
diff --git a/src/shared/att.h b/src/shared/att.h
index 53a3f7a2a..6dc9944bb 100644
--- a/src/shared/att.h
+++ b/src/shared/att.h
@@ -46,6 +46,15 @@ typedef void (*bt_att_disconnect_func_t)(int err, void *user_data);
 typedef void (*bt_att_exchange_func_t)(uint16_t mtu, void *user_data);
 typedef bool (*bt_att_counter_func_t)(uint32_t *sign_cnt, void *user_data);
 
+/* Return values for bt_att_retry_func_t */
+#define BT_ATT_RETRY_NO	0	/* Don't retry */
+#define BT_ATT_RETRY_YES	1	/* Retry immediately */
+#define BT_ATT_RETRY_PENDING	2	/* Defer retry decision */
+
+typedef int (*bt_att_retry_func_t)(uint8_t opcode, uint8_t error_code,
+					const void *pdu, uint16_t length,
+					unsigned int att_id, void *user_data);
+
 bool bt_att_set_debug(struct bt_att *att, uint8_t level,
 			bt_att_debug_func_t callback, void *user_data,
 			bt_att_destroy_func_t destroy);
@@ -58,6 +67,13 @@ bool bt_att_set_timeout_cb(struct bt_att *att, bt_att_timeout_func_t callback,
 						void *user_data,
 						bt_att_destroy_func_t destroy);
 
+bool bt_att_set_retry_cb(struct bt_att *att, bt_att_retry_func_t callback,
+						void *user_data,
+						bt_att_destroy_func_t destroy);
+
+bool bt_att_retry_request(struct bt_att *att, unsigned int id);
+bool bt_att_cancel_retry(struct bt_att *att, unsigned int id);
+
 unsigned int bt_att_send(struct bt_att *att, uint8_t opcode,
 					const void *pdu, uint16_t length,
 					bt_att_response_func_t callback,
-- 
2.34.1


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

end of thread, other threads:[~2026-01-21  8:59 UTC | newest]

Thread overview: 6+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-01-05 10:38 [PATCH v2 0/1] gatt-client:Implement error handling for DB_OUT_OF_SYNC in GATT caching Mengshi Wu
2026-01-05 10:38 ` [PATCH v2 1/1] " Mengshi Wu
2026-01-05 10:54   ` bluez.test.bot
2026-01-05 19:21   ` [PATCH v2 1/1] " Luiz Augusto von Dentz
2026-01-06  6:00     ` Mengshi Wu
  -- strict thread matches above, loose matches on Subject: below --
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

This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.