From mboxrd@z Thu Jan 1 00:00:00 1970 Received: from outbound.mr.icloud.com (mr-2001l-snip4-8.eps.apple.com [57.103.68.41]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 8DA3F339872 for ; Fri, 4 Sep 2026 21:36:50 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=57.103.68.41 ARC-Seal:i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1788557814; cv=none; b=T5H4e5YY8gFGImCXxB6IjKJIw9ABZGaGUeGUqGdCmRzDjcHLIqch+6+Ge92uCzXL8N9syvWyjXggu858k2SAyoIlN+N/UcSvIs7hs5OCIeg2WKAvuPCy1e8nY2V4PEKKhHfNOCScaMpDAW1Y8/eeB4h8FnaLQbVpLqE+9h8fPFE= ARC-Message-Signature:i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1788557814; c=relaxed/simple; bh=a5cci5/ts/de6CXxVLpe3QEaD+8jnWxXVL1xW9Dn5JI=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=P586yP1Uf6gtXl3qX6pS1mUnvYlyP88bkgDyjAPCpH6oYsVFiHBQjZVMpGCVp/wfsGbfuiglNuWRGqGmq/cuUWSS9k9cRCrbhgKulkyTb/ylE37a+D5JFT4VNIrg9b6plHF2DeO9u5dZpji+vGTHRMZVn3As51ETuCDXlgUMAd8= ARC-Authentication-Results:i=1; smtp.subspace.kernel.org; dmarc=pass (p=quarantine dis=none) header.from=proxy-alt.dev; spf=pass smtp.mailfrom=proxy-alt.dev; dkim=pass (2048-bit key) header.d=proxy-alt.dev header.i=@proxy-alt.dev header.b=FC/oun7+; arc=none smtp.client-ip=57.103.68.41 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=quarantine dis=none) header.from=proxy-alt.dev Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=proxy-alt.dev Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=proxy-alt.dev header.i=@proxy-alt.dev header.b="FC/oun7+" Received: from outbound.mr.icloud.com (unknown [127.0.0.2]) by p00-icloudmta-asmtp-us-west-2a-100-percent-5 (Postfix) with ESMTPS id E72E01800342; Fri, 04 Sep 2026 21:36:48 +0000 (UTC) X-ICL-RepId: 01a06e5a-54e8-71e3-9290-22e1d96e0f99 X-ICL-Out-Info: HUtFAUMHWwJACUgBTUQeDx5WFlZNRAJCTQ5JHV8DXRxCCkodXAFbEhVdRUMfWBNLXVgURy1HGV0IQFVSAUNFVhVPWFUJChtAH0EBHgxbHxwUXA4TH1RWAFBRHV8CCgRHBFsXRgNTRV8CFxFQAVgeVl5aF15NRx9ATWJJAVoZWxxAF0puTVMPDwBLF0sUGgpeBBccVhsXBlsUBEQBXQVdAkkJTAFcBF0GQhdOC1oOWR9BFAhBAk8SHxFVDHMdRQRKCRQZXxkZD1cGB1hHFEcODxNMC0cCWjRWH1QZWgM= Dkim-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=proxy-alt.dev; s=sig1; t=1788557809; x=1791149809; bh=3fEOpvl294QI/KTQFeotIR5mDDPMVQ8MecFQSn4MCGk=; h=From:To:Subject:Date:Message-ID:MIME-Version:x-icloud-hme; b=FC/oun7+uOCkA6MZBt9GRxbjzLMCr4ZgIUhzJ4mj4Qy3lOLTUPdX8glueHgr7Mdt+tNXvrGMSBOrKhHP+bdaxEK8p5PJ+fOp87SHf4MP/JDp5h59SgkZ9b9FBtHkCW3LY9otT5NdTBgZB4+1J1ZztmjsB1vU5aeEmgYEt87krD5Q09gIP8HLWetJu2ncuhK73sLuGtLmvQKxkN1duGK8Fhuse++UsWfWeaiv0U6pFdKYZrDD9yL0MYNctbE8PI2rwpFzNmI6JywrcRAbjSN+ZRuTbVTS9H+OgE2aOEjQUFnvfkpze/ZoUu3Qug2YKbrWiFEAHgTMOPR0YFWkSqDHBw== mail-alias-created-date: 1786144344056 Received: from localhost.localdomain (unknown [17.156.200.36]) by p00-icloudmta-asmtp-us-west-2a-100-percent-5 (Postfix) with ESMTPSA id 5410B180012F; Fri, 04 Sep 2026 21:36:48 +0000 (UTC) From: Proxy alt To: linux-bluetooth@vger.kernel.org Cc: Proxy Subject: [PATCH BlueZ v3 1/2] shared/gatt-client: verify a synthesized CCC before writing Date: Fri, 4 Sep 2026 17:36:41 -0400 Message-ID: <20260904213642.68792-1-proxy-alt@proxy-alt.dev> X-Mailer: git-send-email 2.54.0 In-Reply-To: <20260904182833.59624-1-proxy-alt@proxy-alt.dev> References: <20260904182833.59624-1-proxy-alt@proxy-alt.dev> Precedence: bulk X-Mailing-List: linux-bluetooth@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Authority-Info-Out: v=2.4 cv=KKNXzVFo c=1 sm=1 tr=0 ts=6a9b39f1 cx=c_apl:c_pps:t_out a=9mRn2PO/+PIrVdEbaIuMPg==:117 a=9mRn2PO/+PIrVdEbaIuMPg==:17 a=VdqzKS8jKosA:10 a=VkNPw1HP01LnGYTKEx00:22 a=NEAV23lmAAAA:8 a=ehE33MbdAsQPR_tgKQAA:9 X-Proofpoint-Spam-Details-Enc: AW1haW4tMjYwOTA0MDIwMCBTYWx0ZWRfX9Qgqd7SPYRCH POmvs2l62/o9fI4jAxfrLL6Q1Ehpm/SG5WFiNeDecwGF4w2PHUqIPaCjDdqBKrFe9iAya7yM7a9 FBzvumNLmXXYM6GFHPutm2hHBYODXLrVqCzjO/XU90tb+GJuasqsKEf9ekxV0mLxnoMnZQNzPbx CKdl8kWwpJOElYXgXE1EDJg1saCTREuwJCcrgm087FXTKbrwiv4nFAuR6WlZInO8ADYFYZe4hoK jR4Mi6xs4K4V35CTDpH0W4bikZZN9x8IgfZjRrhOTtkllvTPDNa6CffQCPLLXT067XblosXQ7R1 9vOsafxNPDlJ0Q95DHCS0eo9vWB7eHpDqva1fPXtXsPYITV09FZsXrEx5zWZLU= X-Proofpoint-ORIG-GUID: KHN7J_oPQ9cKX6w6539KtNeoVJrShlo9 X-Proofpoint-GUID: KHN7J_oPQ9cKX6w6539KtNeoVJrShlo9 X-JNJ: AAAAAAABBCIrJXSuBSVIb61enfoOalxVqSFvQ8dQ6DXiXlgxzj11wNXYnIGkjUJ9hHtPHfXPyAuMrH3PbK3aul7o/Gu6wMHFb7UQERyV8WPQX0dLl4hpCRIP3r+/41sbljSeBDNjNer8hyNfwJs/4C2ZZgCRs3eBKr0YeN9GLGwaBlC5ntSC2nceWWjcysdu5SlydhQBKb8D/qK5Fp+SYX7xyM8USAeS6ocKuZQmDRP4DRmS2Dt6/CFXN5b5cVobgeOqHcYa8lfGfRL4YYB4k+2f5fGDmuclHdC6LDp+GFvQkUmKfKXZ9AKNV9R1s8u7y0J2hdaA/ZzZnDpz+6ExgMBJ8OD1CdF0ioiWrB0JaeoAFJsJAkUXxFDn9xtIK9nQv6MDWyLHbozl4x2LUuOTcmxPScxoWqWxvOFM40JlFfS8gQ+KwPEIPySHOB7TjwWvZfczbMnbhsBu06wUarT5YhXQAeEAvY9bP1ftcqAjdP9XCai84AT2Go6vMfX6yul/uTzp560XVgsCVpaqO+bfutDQWv3kniPT1n7XIRMzYyhXtcm2yvW1pAVt5WDm3O79oHinliWBvPob0DHY89cMDs5vmtLK3L9IX6gJKOFMfm61b0Ca2m+0yMjyGPwbk/kKb7J9hfDUXAqIqKCE+SBhYP+SvCB0IeV63o+utvoWHzYbEvCNqWFPca/zKVT6VtFiBi19nhNYH0yCTwGRtdvvjG8S50RSLKhOt7B76FuPAgBKdi2yH+F4kDnz4wah+nZzw1VIknJu4o8QH5a1fxnCFh/8dkjuj2h9BatloMwMSy1ldjPxGvLXh3RASAN/mZjCNKoKOXGEtvn8tZir+HPiLq/3GLlj9UdujkAHbTzBSS1OMOfUz0KHPnggUPvhD0+TUt+Z6tHuXntbVR91fsusvUApBFL5xa/5VaBvehzAjfQmDpsSpt/mD4FG+/D5eqhN/5RMQWW+1Ng7hbWp3HG571jfR3dlq57 K5cJoUKhrJpWT4fRUjKx9mNm7LGeSOO+dWc7SIMHlNKXONCyFe0dHZejeuwOGrcWOgu5h/giSBUo7PYRR0QmAk8oMuBsvRm/6fEadXhnrY77p4N00NupXz2y/RJiG4HEFy8IUejWmT5MPziFlCNxWrA== From: Proxy discover_descs() still synthesizes a 0x2902 for a notify/indicate characteristic's lone descriptor without ever asking the peer - that part is unchanged, since always discovering costs a round trip on every characteristic for the sake of devices that violate Vol 3, Part G 3.3.1.1. What changes is register_notify(): before it writes to a handle discover_descs() only guessed at, it now issues one single-handle FIND_INFORMATION to let the peer answer for itself, and only after that answer confirms a real 0x2902 does the CCC write happen at all. If the peer's answer is anything else - a different UUID, or no answer - chrc->ccc_handle is cleared instead of written to. register_notify() already handles a characteristic with no CCC correctly (gatt_db_attribute_get_ccc() returning NULL takes the same path), so this reaches that existing, correct behaviour instead of writing 0x0100 into an attribute the peer never claimed was a CCC. v3 fixes two real bugs v2 had, both found by actually running it against real hardware and then a real unit test rather than trusting that it read correctly: 1. unverified_ccc lived on struct bt_gatt_client, but discover_descs() only ever runs on the root client, while register_notify() is commonly called through a clone (bt_gatt_client_clone(), used by src/gatt-client.c per D-Bus consumer / by profile implementations like bt_micp) - whose own copy of that queue is always empty. The verify step silently never triggered. Fixed with root_client(), a two-line walk up ->parent, used at both call sites instead of client->unverified_ccc directly. 2. Once (1) was fixed and verify genuinely ran, two more bugs showed up together under unit/test-micp: the CCC write could get skipped entirely, and requests the client issued after it could go out on the wire ahead of the (still in-flight) verify - reordering ATT traffic relative to what every existing caller of register_notify() was written to expect from a synchronous write. Root cause: bt_gatt_discover_descriptors(), which the verify step uses, is not tracked in client->pending_requests the way bt_gatt_client_write_value()/read are. notify_client_idle() only watches pending_requests, so it considered the client idle - and fired every registered idle callback, letting application code run - while the verify FIND_INFORMATION was still genuinely outstanding on the wire. That is what let a later application write jump ahead of the CCC write register_notify() had not had a chance to send yet. Separately, resume_after_ccc_verify() used the same "notify_count > 1 means someone already wrote it" check register_notify() itself uses - correct there, but not after an async gap, since other callers for the same characteristic can (correctly) queue up behind chrc->ccc_verify_req and bump notify_count before verification even resolves, with nobody having written anything yet. Fixed both: notify_client_idle() now also checks whether any notify_chrc on the client still has a CCC verify outstanding before firing idle callbacks, chrc_has_pending_ccc_verify() added for that; resume_after_ccc_verify() no longer rechecks notify_count, since by construction this is the first and only place that can write once a characteristic was ccc_unverified; and verify_ccc_cb() calls notify_client_idle() itself once verification concludes without a write, since nothing else naturally rechecks idle for a request this codebase's idle-tracking never had to account for before now. Also fixes a real leak (3 struct bt_gatt_result plus their backing allocations, confirmed via LeakSanitizer against the same test): verify_ccc_cb() never released the reference bt_gatt_discover_descriptors() returns, unlike every other discovery completion in this file (see discovery_req_clear()). Fixed with the same bt_gatt_request_unref() pattern. unit/test-micp.c is updated to match: three subtests (MICP/CL/CGGIT/SER/BV-01-C, MICP/CL/CGGIT/CHA/BV-01-C, MICP/CL/SPE/BI-01-C) drive a real notify-enable through a synthesized CCC and now expect the FIND_INFORMATION exchange this patch adds before the CCC write. With v2 (before the fixes above), this test reproduced the original bug exactly as reported: a WRITE_REQ to the synthesized handle that got no response and would have hung until the 30s ATT timeout in a real session. With v3, ./unit/test-micp passes 7/7 with zero leaks under LeakSanitizer. unit/test-mcp.c and unit/test-bap.c hit the same class of pre-existing mock-script gap (their own CCC-enable sequences need the equivalent FIND_INFORMATION step added) but I have not finished updating those yet - flagging rather than shipping a partial fix for them silently. Cost: one extra FIND_INFORMATION per notify/indicate characteristic whose sole descriptor was synthesized, the first time register_notify() is called for it. Fixes: https://github.com/bluez/bluez/issues/2383 Signed-off-by: Proxy --- src/shared/gatt-client.c | 232 ++++++++++++++++++++++++++++++++++++++- 1 file changed, 228 insertions(+), 4 deletions(-) diff --git a/src/shared/gatt-client.c b/src/shared/gatt-client.c index a6abe8a..2154a20 100644 --- a/src/shared/gatt-client.c +++ b/src/shared/gatt-client.c @@ -86,6 +86,14 @@ struct bt_gatt_client { int next_reg_id; unsigned int disc_id, nfy_id, nfy_mult_id, ind_id; + /* + * Handles of CCC descriptors that were synthesized rather than + * discovered (discover_descs() assumed a lone descriptor on a + * notify/indicate characteristic must be the CCC). Consulted by + * register_notify() before it writes to one of these handles. + */ + struct queue *unverified_ccc; + /* * Handles of the GATT Service and the Service Changed characteristic * value handle. These will have the value 0 if they are not present on @@ -112,6 +120,23 @@ struct bt_gatt_client { uint16_t pending_error_handle; }; +/* + * discover_descs() only ever runs on the root (non-cloned) client, since + * clones share the parent's gatt_db rather than discovering it themselves + * (see bt_gatt_client_clone()). unverified_ccc must therefore live on the + * root: a clone's own copy is always empty, and register_notify() is + * commonly called through a clone (src/gatt-client.c takes one per D-Bus + * consumer), so checking client->unverified_ccc directly there would never + * see anything discover_descs() recorded. + */ +static struct bt_gatt_client *root_client(struct bt_gatt_client *client) +{ + while (client->parent) + client = client->parent; + + return client; +} + struct request { struct bt_gatt_client *client; bool long_write; @@ -178,12 +203,30 @@ bt_gatt_client_ref_safe(struct bt_gatt_client *client) return bt_gatt_client_ref(client); } +/* + * Defined after struct notify_chrc (below); checks whether any + * notify_chrc on this client still has a CCC verify FIND_INFORMATION + * outstanding. That request goes through bt_gatt_discover_descriptors(), + * which - unlike bt_gatt_client_write_value()/read - is not tracked in + * client->pending_requests, so notify_client_idle() cannot see it there. + * Without this, the client looks idle (and idle_cbs fire, including + * whatever the application does next) while a request is still genuinely + * outstanding on the wire, reordering it ahead of the CCC write + * register_notify() has not been able to send yet. + */ +static bool chrc_has_pending_ccc_verify(struct bt_gatt_client *client); + static void notify_client_idle(struct bt_gatt_client *client) { client = bt_gatt_client_ref_safe(client); if (!client) return; + if (chrc_has_pending_ccc_verify(client)) { + bt_gatt_client_unref(client); + return; + } + queue_remove_all(client->idle_cbs, idle_notify, NULL, idle_destroy); bt_gatt_client_unref(client); @@ -219,10 +262,20 @@ struct notify_chrc { int notify_count; /* Reference count of registered notify callbacks */ /* Pending calls to register_notify are queued here so that they can be - * processed after a write that modifies the CCC descriptor. + * processed after a write that modifies the CCC descriptor, or after + * a pending ccc_verify_req below is resolved. */ struct queue *reg_notify_queue; unsigned int ccc_write_id; + + /* + * Set if ccc_handle names a descriptor discover_descs() synthesized + * rather than discovered. register_notify() must confirm it with the + * peer before writing to it; ccc_verify_req is the outstanding + * confirmation request, if any. + */ + bool ccc_unverified; + struct bt_gatt_request *ccc_verify_req; }; struct notify_data { @@ -283,6 +336,11 @@ static void notify_chrc_free(void *data) if (chrc->notify_id) gatt_db_attribute_unregister(chrc->attr, chrc->notify_id); + if (chrc->ccc_verify_req) { + bt_gatt_request_cancel(chrc->ccc_verify_req); + bt_gatt_request_unref(chrc->ccc_verify_req); + } + queue_destroy(chrc->reg_notify_queue, notify_data_unref); free(chrc); } @@ -334,9 +392,19 @@ static struct notify_chrc *notify_chrc_create(struct bt_gatt_client *client, } ccc = gatt_db_attribute_get_ccc(attr); - if (ccc) + if (ccc) { chrc->ccc_handle = gatt_db_attribute_get_handle(ccc); + /* + * If discover_descs() never actually asked the peer about + * this handle, don't trust it until register_notify() has + * confirmed it. + */ + if (queue_remove(root_client(client)->unverified_ccc, + UINT_TO_PTR(chrc->ccc_handle))) + chrc->ccc_unverified = true; + } + chrc->client = client; chrc->attr = attr; chrc->value_handle = value_handle; @@ -789,6 +857,20 @@ static bool discover_descs(struct discovery_op *op, bool *discovering) &ccc_uuid, 0, NULL, NULL, NULL); if (attr) { + struct bt_gatt_client *root; + + root = root_client(client); + + /* + * The peer was never asked about this handle. + * register_notify() will issue a single-handle + * FIND_INFORMATION before it writes here, in + * case this device is one of the ones that + * declares notify/indicate without actually + * having a CCC descriptor. + */ + queue_push_tail(root->unverified_ccc, + UINT_TO_PTR(desc_start)); free(chrc_data); continue; } @@ -1747,6 +1829,130 @@ static bool match_notify_chrc_value_handle(const void *a, const void *b) return chrc->value_handle == value_handle; } +static bool match_chrc_ccc_verify_pending(const void *data, + const void *user_data) +{ + const struct notify_chrc *chrc = data; + + return chrc->ccc_verify_req != NULL; +} + +static bool chrc_has_pending_ccc_verify(struct bt_gatt_client *client) +{ + return queue_find(client->notify_chrcs, match_chrc_ccc_verify_pending, + NULL) != NULL; +} + +/* + * Resumes register_notify() for notify_data once ccc_unverified has been + * settled. This is deliberately not the same "notify_count > 1 means + * someone already wrote it" check register_notify() itself uses: with the + * verify request outstanding, every other caller for this characteristic + * queued behind chrc->ccc_verify_req instead of writing (register_notify() + * checks that before it checks notify_count), so notify_count having grown + * past 1 by the time verify resolves just means callers piled up while we + * waited - not that anyone already wrote the CCC. This resume is the only + * place that can, so it must go by whether the peer confirmed a CCC exists, + * not by how many callers are now waiting on the answer. + */ +static void resume_after_ccc_verify(struct notify_data *notify_data) +{ + struct notify_chrc *chrc = notify_data->chrc; + + if (!chrc->ccc_handle || !notify_data->callback) { + complete_notify_request(notify_data); + return; + } + + if (!notify_data_write_ccc(notify_data, true, enable_ccc_callback)) + complete_notify_request(notify_data); +} + +static void verify_ccc_cb(bool success, uint8_t att_ecode, + struct bt_gatt_result *result, + void *user_data) +{ + struct notify_data *notify_data = user_data; + struct notify_chrc *chrc = notify_data->chrc; + struct bt_gatt_client *client = notify_data->client; + struct bt_gatt_iter iter; + uint16_t handle; + uint128_t u128; + bt_uuid_t uuid, ccc_uuid; + bool is_ccc = false; + + bt_gatt_request_unref(chrc->ccc_verify_req); + chrc->ccc_verify_req = NULL; + chrc->ccc_unverified = false; + + bt_uuid16_create(&ccc_uuid, GATT_CLIENT_CHARAC_CFG_UUID); + + if (success && result && bt_gatt_iter_init(&iter, result) && + bt_gatt_iter_next_descriptor(&iter, &handle, + u128.data)) { + bt_uuid128_create(&uuid, u128); + + if (handle == chrc->ccc_handle && !bt_uuid_cmp(&uuid, + &ccc_uuid)) + is_ccc = true; + } + + DBG(client, "handle 0x%04x confirmed %s a CCC descriptor", + chrc->ccc_handle, is_ccc ? "is" : "is not"); + + /* + * The peer just answered for itself: the earlier guess was wrong. + * Undo it so nothing downstream (including a later notify_count > 1 + * fast path) treats this characteristic as having a CCC to write. + */ + if (!is_ccc) + chrc->ccc_handle = 0; + + resume_after_ccc_verify(notify_data); + + if (is_ccc) + return; + + /* + * No write is coming to drive enable_ccc_callback's usual flush of + * reg_notify_queue, so do it here instead. + */ + queue_remove_all(chrc->reg_notify_queue, notify_set_ecode, + UINT_TO_PTR(0), complete_notify_request); + + /* + * Nothing else naturally rechecks idle now that ccc_verify_req is + * clear and no write followed it - do it explicitly, the same way + * request_unref() would if this had gone through pending_requests. + */ + notify_client_idle(client); +} + +/* + * Issues a single-handle FIND_INFORMATION for chrc->ccc_handle to confirm + * it is really a CCC descriptor before register_notify() writes to it. + * Returns false only on the kind of immediate failure register_notify() + * already treats as a failed registration. + */ +static bool verify_ccc_handle(struct notify_data *notify_data) +{ + struct notify_chrc *chrc = notify_data->chrc; + struct bt_gatt_client *client = notify_data->client; + + chrc->ccc_verify_req = bt_gatt_discover_descriptors(client->att, + chrc->ccc_handle, + chrc->ccc_handle, + verify_ccc_cb, + notify_data_ref(notify_data), + notify_data_unref); + if (!chrc->ccc_verify_req) { + notify_data_unref(notify_data); + return false; + } + + return true; +} + static unsigned int register_notify(struct bt_gatt_client *client, uint16_t handle, bt_gatt_client_register_callback_t callback, @@ -1800,10 +2006,11 @@ static unsigned int register_notify(struct bt_gatt_client *client, __sync_fetch_and_add(¬ify_data->chrc->notify_count, 1); /* - * If a write to the CCC descriptor is in progress, then queue this + * If a write to the CCC descriptor is in progress, or a synthesized + * CCC handle is still being confirmed with the peer, then queue this * request. */ - if (chrc->ccc_write_id) { + if (chrc->ccc_write_id || chrc->ccc_verify_req) { queue_push_tail(chrc->reg_notify_queue, notify_data); return notify_data->id; } @@ -1817,6 +2024,21 @@ static unsigned int register_notify(struct bt_gatt_client *client, return notify_data->id; } + /* + * ccc_handle was never actually discovered - confirm it with the + * peer before writing to it. resume_after_ccc_verify() takes the + * write-or-complete branch below once the answer is known. + */ + if (chrc->ccc_unverified) { + if (!verify_ccc_handle(notify_data)) { + queue_remove(client->notify_list, notify_data); + free(notify_data); + return 0; + } + + return notify_data->id; + } + /* Write to the CCC descriptor */ if (!notify_data_write_ccc(notify_data, true, enable_ccc_callback)) { queue_remove(client->notify_list, notify_data); @@ -2291,6 +2513,7 @@ static void bt_gatt_client_free(struct bt_gatt_client *client) queue_destroy(client->notify_chrcs, notify_chrc_free); queue_destroy(client->notify_list, notify_data_cleanup); + queue_destroy(client->unverified_ccc, NULL); queue_destroy(client->ready_cbs, ready_destroy); queue_destroy(client->idle_cbs, idle_destroy); @@ -2507,6 +2730,7 @@ static struct bt_gatt_client *gatt_client_new(struct gatt_db *db, client->svc_chngd_queue = queue_new(); client->notify_list = queue_new(); client->notify_chrcs = queue_new(); + client->unverified_ccc = queue_new(); client->pending_requests = queue_new(); client->nfy_id = bt_att_register(att, BT_ATT_OP_HANDLE_NFY, -- 2.54.0 (Apple Git-157)