* [PATCH 01/11] shared/gatt-client: Allow registering notify with explicit CCC value
2026-10-01 13:18 [PATCH 00/11] Ranging Profile and Ranging Service PTS fixes Cheng Jiang
@ 2026-10-01 13:18 ` Cheng Jiang
2026-10-01 16:03 ` Ranging Profile and Ranging Service PTS fixes bluez.test.bot
2026-10-01 13:18 ` [PATCH 02/11] shared/gatt-db: Add gatt_db_attribute_indicate() Cheng Jiang
` (9 subsequent siblings)
10 siblings, 1 reply; 15+ messages in thread
From: Cheng Jiang @ 2026-10-01 13:18 UTC (permalink / raw)
To: linux-bluetooth
Cc: jinwang.li, wei.deng, xiuzhuo.shang, shuai.zhang, Cheng Jiang
bt_gatt_client_register_notify() picks the CCC value to write based on
the characteristic properties, preferring notifications over indications
when both are supported. A client that specifically needs one of the two
- for example the Ranging Service, which distinguishes notification and
indication transports - has no way to request it.
Add bt_gatt_client_register_notify_with_ccc() which takes the desired
CCC value (0x0001 for notifications, 0x0002 for indications) and writes
it verbatim. register_notify() gains a ccc_value argument and rejects a
value the characteristic does not support. The existing entry point
keeps its behaviour by passing 0, which restores the property-based
selection.
Assisted-by: Claude:claude-opus-4.8
---
src/shared/gatt-client.c | 41 +++++++++++++++++++++++++++++++++++-----
src/shared/gatt-client.h | 7 +++++++
2 files changed, 43 insertions(+), 5 deletions(-)
diff --git a/src/shared/gatt-client.c b/src/shared/gatt-client.c
index 92ad7c39c..1cf361f50 100644
--- a/src/shared/gatt-client.c
+++ b/src/shared/gatt-client.c
@@ -232,6 +232,7 @@ struct notify_data {
uint8_t att_ecode;
int ref_count;
struct notify_chrc *chrc;
+ uint16_t ccc_value;
bt_gatt_client_register_callback_t callback;
bt_gatt_client_notify_callback_t notify;
void *user_data;
@@ -1670,10 +1671,9 @@ static bool notify_data_write_ccc(struct notify_data *notify_data, bool enable,
assert(notify_data->chrc->ccc_handle);
if (enable) {
- /* Try to enable notifications or indications based on
- * whatever the characteristic supports.
- */
- if (properties & BT_GATT_CHRC_PROP_NOTIFY)
+ if (notify_data->ccc_value)
+ value = cpu_to_le16(notify_data->ccc_value);
+ else if (properties & BT_GATT_CHRC_PROP_NOTIFY)
value = cpu_to_le16(0x0001);
else if (properties & BT_GATT_CHRC_PROP_INDICATE)
value = cpu_to_le16(0x0002);
@@ -1749,6 +1749,7 @@ static bool match_notify_chrc_value_handle(const void *a, const void *b)
static unsigned int register_notify(struct bt_gatt_client *client,
uint16_t handle,
+ uint16_t ccc_value,
bt_gatt_client_register_callback_t callback,
bt_gatt_client_notify_callback_t notify,
void *user_data,
@@ -1778,10 +1779,20 @@ static unsigned int register_notify(struct bt_gatt_client *client,
if (chrc->notify_count == INT_MAX)
return 0;
+ if (ccc_value == 0x0001 && !(chrc->properties & BT_GATT_CHRC_PROP_NOTIFY))
+ return 0;
+
+ if (ccc_value == 0x0002 && !(chrc->properties & BT_GATT_CHRC_PROP_INDICATE))
+ return 0;
+
+ if (ccc_value != 0 && ccc_value != 0x0001 && ccc_value != 0x0002)
+ return 0;
+
notify_data = new0(struct notify_data, 1);
notify_data->client = client;
notify_data->ref_count = 1;
notify_data->chrc = chrc;
+ notify_data->ccc_value = ccc_value;
notify_data->callback = callback;
notify_data->notify = notify;
notify_data->user_data = user_data;
@@ -1871,6 +1882,7 @@ static bool register_service_changed(struct bt_gatt_client *client)
*/
client->svc_chngd_ind_id = register_notify(client,
gatt_db_attribute_get_handle(attr),
+ 0x0002,
service_changed_register_cb,
service_changed_cb,
client, NULL);
@@ -3834,10 +3846,29 @@ unsigned int bt_gatt_client_register_notify(struct bt_gatt_client *client,
if (client->in_svc_chngd)
return 0;
- return register_notify(client, chrc_value_handle, callback, notify,
+ return register_notify(client, chrc_value_handle, 0, callback, notify,
user_data, destroy);
}
+unsigned int bt_gatt_client_register_notify_with_ccc(
+ struct bt_gatt_client *client,
+ uint16_t chrc_value_handle, uint16_t ccc_value,
+ bt_gatt_client_register_callback_t callback,
+ bt_gatt_client_notify_callback_t notify,
+ void *user_data,
+ bt_gatt_client_destroy_func_t destroy)
+{
+ if (!client || !client->db || !chrc_value_handle ||
+ (!callback && !notify) || !ccc_value)
+ return 0;
+
+ if (client->in_svc_chngd)
+ return 0;
+
+ return register_notify(client, chrc_value_handle, ccc_value, callback,
+ notify, user_data, destroy);
+}
+
bool bt_gatt_client_unregister_notify(struct bt_gatt_client *client,
unsigned int id)
{
diff --git a/src/shared/gatt-client.h b/src/shared/gatt-client.h
index 63cf99500..1b19c885b 100644
--- a/src/shared/gatt-client.h
+++ b/src/shared/gatt-client.h
@@ -122,6 +122,13 @@ unsigned int bt_gatt_client_register_notify(struct bt_gatt_client *client,
bt_gatt_client_notify_callback_t notify,
void *user_data,
bt_gatt_client_destroy_func_t destroy);
+unsigned int bt_gatt_client_register_notify_with_ccc(
+ struct bt_gatt_client *client,
+ uint16_t chrc_value_handle, uint16_t ccc_value,
+ bt_gatt_client_register_callback_t callback,
+ bt_gatt_client_notify_callback_t notify,
+ void *user_data,
+ bt_gatt_client_destroy_func_t destroy);
bool bt_gatt_client_unregister_notify(struct bt_gatt_client *client,
unsigned int id);
--
2.43.0
^ permalink raw reply related [flat|nested] 15+ messages in thread* RE: Ranging Profile and Ranging Service PTS fixes
2026-10-01 13:18 ` [PATCH 01/11] shared/gatt-client: Allow registering notify with explicit CCC value Cheng Jiang
@ 2026-10-01 16:03 ` bluez.test.bot
0 siblings, 0 replies; 15+ messages in thread
From: bluez.test.bot @ 2026-10-01 16:03 UTC (permalink / raw)
To: linux-bluetooth, cheng.jiang
[-- Attachment #1: Type: text/plain, Size: 26188 bytes --]
This is automated email and please do not reply to this email!
Dear submitter,
Thank you for submitting the patches to the linux bluetooth mailing list.
This is a CI test results with your patch series:
PW Link:https://patchwork.kernel.org/series/1177680/
---Test result---
Test Summary:
CheckPatch FAIL 5.81 seconds
GitLint PASS 3.49 seconds
BuildEll PASS 19.30 seconds
BluezMake FAIL 22.19 seconds
MakeCheck FAIL 49.12 seconds
MakeDistcheck PASS 140.88 seconds
CheckValgrind FAIL 18.15 seconds
CheckSmatch FAIL 27.94 seconds
bluezmakeextell FAIL 14.71 seconds
TestFunctional ERROR 23.97 seconds
IncrementalBuild FAIL 578.13 seconds
ScanBuild FAIL 43.48 seconds
Details
##############################
Test: CheckPatch - FAIL
Desc: Run checkpatch.pl script
Output:
[01/11] shared/gatt-client: Allow registering notify with explicit CCC value
WARNING:LONG_LINE: line length of 82 exceeds 80 columns
#170: FILE: src/shared/gatt-client.c:1782:
+ if (ccc_value == 0x0001 && !(chrc->properties & BT_GATT_CHRC_PROP_NOTIFY))
WARNING:LONG_LINE: line length of 84 exceeds 80 columns
#173: FILE: src/shared/gatt-client.c:1785:
+ if (ccc_value == 0x0002 && !(chrc->properties & BT_GATT_CHRC_PROP_INDICATE))
/home/runner/work/bluez/bluez/src/patch/14861158.patch total: 0 errors, 2 warnings, 97 lines checked
NOTE: For some of the reported defects, checkpatch may be able to
mechanically convert to the typical style using --fix or --fix-inplace.
/home/runner/work/bluez/bluez/src/patch/14861158.patch has style problems, please review.
NOTE: Ignored message types: COMMIT_MESSAGE COMPLEX_MACRO CONST_STRUCT FILE_PATH_CHANGES MISSING_SIGN_OFF PREFER_PACKED SPDX_LICENSE_TAG SPLIT_STRING SSCANF_TO_KSTRTO
NOTE: If any of the errors are false positives, please report
them to the maintainer, see CHECKPATCH in MAINTAINERS.
[03/11] shared/rap: Honor CCCD transport for RAS on-demand ranging data
WARNING:COMMIT_LOG_LONG_LINE: Prefer a maximum 75 chars per line (possible unwrapped commit description?)
#136:
and the next segment is queued from the confirmation callback; notifications
WARNING:LONG_LINE_COMMENT: line length of 81 exceeds 80 columns
#192: FILE: src/shared/rap.c:395:
+ * transport (notification vs indication) the client actually enabled. */
WARNING:BLOCK_COMMENT_STYLE: Block comments use a trailing */ on a separate line
#192: FILE: src/shared/rap.c:395:
+ * transport (notification vs indication) the client actually enabled. */
WARNING:LONG_LINE: line length of 81 exceeds 80 columns
#248: FILE: src/shared/rap.c:1248:
+ ras_cp_send_response_code(rap, ras, RAS_CP_RSP_CODE_SERVER_BUSY);
WARNING:BLOCK_COMMENT_STYLE: Block comments use a trailing */ on a separate line
#302: FILE: src/shared/rap.c:1403:
+ * Notification). */
WARNING:LONG_LINE: line length of 84 exceeds 80 columns
#655: FILE: src/shared/rap.c:2086:
+ rap->cache.counter);
WARNING:LONG_LINE: line length of 81 exceeds 80 columns
#664: FILE: src/shared/rap.c:2095:
+ ras_cp_send_response_code(rap, ras, RAS_CP_RSP_CODE_SERVER_BUSY);
WARNING:LONG_LINE: line length of 81 exceeds 80 columns
#694: FILE: src/shared/rap.c:2203:
+ if (ras_data_ccc_use_indication(ras->realtime_ccc_value))
WARNING:LONG_LINE: line length of 81 exceeds 80 columns
#709: FILE: src/shared/rap.c:2215:
+ if (ras_data_ccc_use_indication(ras->ondemand_ccc_value))
WARNING:LONG_LINE: line length of 83 exceeds 80 columns
#799: FILE: src/shared/rap.c:4184:
+ DBG(rap, "Ignoring RFU RAS CP response code 0x%02x", code);
/home/runner/work/bluez/bluez/src/patch/14861164.patch total: 0 errors, 10 warnings, 623 lines checked
NOTE: For some of the reported defects, checkpatch may be able to
mechanically convert to the typical style using --fix or --fix-inplace.
/home/runner/work/bluez/bluez/src/patch/14861164.patch has style problems, please review.
NOTE: Ignored message types: COMMIT_MESSAGE COMPLEX_MACRO CONST_STRUCT FILE_PATH_CHANGES MISSING_SIGN_OFF PREFER_PACKED SPDX_LICENSE_TAG SPLIT_STRING SSCANF_TO_KSTRTO
NOTE: If any of the errors are false positives, please report
them to the maintainer, see CHECKPATCH in MAINTAINERS.
[06/11] shared/rap: Add real-time ranging data delivery on the responder
WARNING:LONG_LINE: line length of 81 exceeds 80 columns
#228: FILE: src/shared/rap.c:2204:
+ gatt_db_attribute_get_value(ras->realtime_chrc));
WARNING:LONG_LINE: line length of 83 exceeds 80 columns
#235: FILE: src/shared/rap.c:2211:
+ id = bt_att_send(bt_rap_get_att(rap), BT_ATT_OP_HANDLE_IND,
WARNING:LONG_LINE: line length of 84 exceeds 80 columns
#236: FILE: src/shared/rap.c:2212:
+ pdu, 2 + segment->len, realtime_ind_conf_cb,
WARNING:LONG_LINE_COMMENT: line length of 87 exceeds 80 columns
#286: FILE: src/shared/rap.c:2289:
+ /* Only the final completed procedure segment terminates the stream. */
WARNING:LONG_LINE_STRING: line length of 82 exceeds 80 columns
#342: FILE: src/shared/rap.c:2323:
+ DBG(rap, "Failed to queue real-time RAS segment");
WARNING:LONG_LINE_COMMENT: line length of 91 exceeds 80 columns
#364: FILE: src/shared/rap.c:2709:
+ /* A new real-time procedure supersedes queued old ranging data. */
/home/runner/work/bluez/bluez/src/patch/14861163.patch total: 0 errors, 6 warnings, 238 lines checked
NOTE: For some of the reported defects, checkpatch may be able to
mechanically convert to the typical style using --fix or --fix-inplace.
/home/runner/work/bluez/bluez/src/patch/14861163.patch has style problems, please review.
NOTE: Ignored message types: COMMIT_MESSAGE COMPLEX_MACRO CONST_STRUCT FILE_PATH_CHANGES MISSING_SIGN_OFF PREFER_PACKED SPDX_LICENSE_TAG SPLIT_STRING SSCANF_TO_KSTRTO
NOTE: If any of the errors are false positives, please report
them to the maintainer, see CHECKPATCH in MAINTAINERS.
[07/11] shared/rap: Add requester ranging data subscription API
WARNING:LONG_LINE: line length of 81 exceeds 80 columns
#201: FILE: src/shared/rap.c:3433:
+ notify->id = bt_gatt_client_register_notify_with_ccc(rap->client,
WARNING:LONG_LINE: line length of 87 exceeds 80 columns
#206: FILE: src/shared/rap.c:3438:
+ value_handle, ras_register, rap_notify, notify,
WARNING:LONG_LINE: line length of 96 exceeds 80 columns
#324: FILE: src/shared/rap.c:4425:
+ uint16_t ccc_value, rap_notify_t func, const char *name,
WARNING:BLOCK_COMMENT_STYLE: Block comments use a trailing */ on a separate line
#357: FILE: src/shared/rap.c:4465:
+ * A Ready notification, not its current value, starts a retrieval. */
WARNING:LONG_LINE: line length of 82 exceeds 80 columns
#373: FILE: src/shared/rap.c:4481:
+ !gatt_db_attribute_get_char_data(ras->realtime_chrc, NULL,
WARNING:LONG_LINE: line length of 89 exceeds 80 columns
#374: FILE: src/shared/rap.c:4482:
+ &value_handle, NULL, NULL, NULL))
WARNING:LONG_LINE: line length of 83 exceeds 80 columns
#377: FILE: src/shared/rap.c:4485:
+ rap->realtime_notify_id = bt_rap_register_notify(rap, value_handle,
WARNING:LONG_LINE_COMMENT: line length of 82 exceeds 80 columns
#389: FILE: src/shared/rap.c:4497:
+ /* Data is segmented and can be large; keep it notification-based.
WARNING:LONG_LINE_COMMENT: line length of 85 exceeds 80 columns
#390: FILE: src/shared/rap.c:4498:
+ * The control/status characteristics use the requested transport. */
WARNING:BLOCK_COMMENT_STYLE: Block comments use a trailing */ on a separate line
#390: FILE: src/shared/rap.c:4498:
+ * The control/status characteristics use the requested transport. */
WARNING:LONG_LINE: line length of 86 exceeds 80 columns
#393: FILE: src/shared/rap.c:4501:
+ ras_segment_notify_cb, "on-demand", &rap->ondemand_notify_id);
WARNING:LONG_LINE: line length of 92 exceeds 80 columns
#396: FILE: src/shared/rap.c:4504:
+ ras_cp_response_notify_cb, "RAS Control Point", &rap->cp_notify_id);
WARNING:LONG_LINE: line length of 95 exceeds 80 columns
#399: FILE: src/shared/rap.c:4507:
+ ras_data_ready_notify_cb, "Ranging Data Ready", &rap->ready_notify_id);
WARNING:LONG_LINE_STRING: line length of 83 exceeds 80 columns
#402: FILE: src/shared/rap.c:4510:
+ ras_data_overwritten_notify_cb, "Ranging Data Overwritten",
WARNING:LONG_LINE: line length of 81 exceeds 80 columns
#406: FILE: src/shared/rap.c:4514:
+ &value_handle, NULL, NULL, NULL))
WARNING:LONG_LINE: line length of 87 exceeds 80 columns
#407: FILE: src/shared/rap.c:4515:
+ rap_read_value(rap, value_handle, ras_data_ready_read_complete,
WARNING:BLOCK_COMMENT_STYLE: Block comments use a trailing */ on a separate line
#525: FILE: src/shared/rap.h:319:
+ * called after remote RAS discovery and Features read complete. */
WARNING:BLOCK_COMMENT_STYLE: Block comments use a trailing */ on a separate line
#531: FILE: src/shared/rap.h:325:
+ * disables the characteristic CCCD when this is its final subscription. */
/home/runner/work/bluez/bluez/src/patch/14861166.patch total: 0 errors, 18 warnings, 372 lines checked
NOTE: For some of the reported defects, checkpatch may be able to
mechanically convert to the typical style using --fix or --fix-inplace.
/home/runner/work/bluez/bluez/src/patch/14861166.patch has style problems, please review.
NOTE: Ignored message types: COMMIT_MESSAGE COMPLEX_MACRO CONST_STRUCT FILE_PATH_CHANGES MISSING_SIGN_OFF PREFER_PACKED SPDX_LICENSE_TAG SPLIT_STRING SSCANF_TO_KSTRTO
NOTE: If any of the errors are false positives, please report
them to the maintainer, see CHECKPATCH in MAINTAINERS.
[08/11] profiles/ranging: Expose requester ranging data mode over D-Bus
WARNING:LONG_LINE_COMMENT: line length of 81 exceeds 80 columns
#134: FILE: profiles/ranging/rap.c:881:
+ /* The Requester owns the Real-time CCCD subscription. Unregister it when
WARNING:BLOCK_COMMENT_STYLE: Block comments use a trailing */ on a separate line
#135: FILE: profiles/ranging/rap.c:882:
+ * the procedure ends so the peer receives the required CCCD disable. */
/home/runner/work/bluez/bluez/src/patch/14861162.patch total: 0 errors, 2 warnings, 52 lines checked
NOTE: For some of the reported defects, checkpatch may be able to
mechanically convert to the typical style using --fix or --fix-inplace.
/home/runner/work/bluez/bluez/src/patch/14861162.patch has style problems, please review.
NOTE: Ignored message types: COMMIT_MESSAGE COMPLEX_MACRO CONST_STRUCT FILE_PATH_CHANGES MISSING_SIGN_OFF PREFER_PACKED SPDX_LICENSE_TAG SPLIT_STRING SSCANF_TO_KSTRTO
NOTE: If any of the errors are false positives, please report
them to the maintainer, see CHECKPATCH in MAINTAINERS.
[09/11] client: Add Channel Sounding ranging data mode control
WARNING:LONG_LINE: line length of 82 exceeds 80 columns
#151: FILE: client/cs.c:1450:
+ bt_shell_printf("SetRangingDataMode failed: %s\n", error.message);
WARNING:LONG_LINE: line length of 89 exceeds 80 columns
#189: FILE: client/cs.c:1488:
+ bt_shell_printf("Invalid Ranging Data transport: %s\n", argv[3]);
WARNING:LONG_LINE_STRING: line length of 83 exceeds 80 columns
#202: FILE: client/cs.c:1501:
+ bt_shell_printf("No ChannelSounding1 interface for that device\n");
WARNING:LONG_LINE: line length of 81 exceeds 80 columns
#207: FILE: client/cs.c:1506:
+ ranging_data_mode_setup, ranging_data_mode_reply,
WARNING:LONG_LINE_STRING: line length of 91 exceeds 80 columns
#219: FILE: client/cs.c:1627:
+ { "ranging-data-mode", "<dev_addr> <disabled/realtime/ondemand> [notify/indicate]",
WARNING:LONG_LINE: line length of 85 exceeds 80 columns
#221: FILE: client/cs.c:1629:
+ "Set RAP Requester Ranging Data subscription mode" },
/home/runner/work/bluez/bluez/src/patch/14861165.patch total: 0 errors, 6 warnings, 94 lines checked
NOTE: For some of the reported defects, checkpatch may be able to
mechanically convert to the typical style using --fix or --fix-inplace.
/home/runner/work/bluez/bluez/src/patch/14861165.patch has style problems, please review.
NOTE: Ignored message types: COMMIT_MESSAGE COMPLEX_MACRO CONST_STRUCT FILE_PATH_CHANGES MISSING_SIGN_OFF PREFER_PACKED SPDX_LICENSE_TAG SPLIT_STRING SSCANF_TO_KSTRTO
NOTE: If any of the errors are false positives, please report
them to the maintainer, see CHECKPATCH in MAINTAINERS.
##############################
Test: BluezMake - FAIL
Desc: Build BlueZ
Output:
src/shared/rap.c:1256:13: error: redundant redeclaration of ‘ras_ondemand_cache_clear’ [-Werror=redundant-decls]
1256 | static void ras_ondemand_cache_clear(struct bt_rap *rap);
| ^~~~~~~~~~~~~~~~~~~~~~~~
src/shared/rap.c:414:13: note: previous declaration of ‘ras_ondemand_cache_clear’ with type ‘void(struct bt_rap *)’
414 | static void ras_ondemand_cache_clear(struct bt_rap *rap);
| ^~~~~~~~~~~~~~~~~~~~~~~~
src/shared/rap.c: In function ‘send_ras_segment_data’:
src/shared/rap.c:2264:21: error: variable ‘ras’ set but not used [-Werror=unused-but-set-variable]
2264 | struct ras *ras;
| ^~~
cc1: all warnings being treated as errors
make[1]: *** [Makefile:7947: src/shared/libshared_mainloop_la-rap.lo] Error 1
make[1]: *** Waiting for unfinished jobs....
make: *** [Makefile:4241: all] Error 2
##############################
Test: MakeCheck - FAIL
Desc: Run Bluez Make Check
Output:
src/shared/rap.c:1256:13: error: redundant redeclaration of ‘ras_ondemand_cache_clear’ [-Werror=redundant-decls]
1256 | static void ras_ondemand_cache_clear(struct bt_rap *rap);
| ^~~~~~~~~~~~~~~~~~~~~~~~
src/shared/rap.c:414:13: note: previous declaration of ‘ras_ondemand_cache_clear’ with type ‘void(struct bt_rap *)’
414 | static void ras_ondemand_cache_clear(struct bt_rap *rap);
| ^~~~~~~~~~~~~~~~~~~~~~~~
src/shared/rap.c: In function ‘send_ras_segment_data’:
src/shared/rap.c:2264:21: error: variable ‘ras’ set but not used [-Werror=unused-but-set-variable]
2264 | struct ras *ras;
| ^~~
cc1: all warnings being treated as errors
make[1]: *** [Makefile:7660: src/shared/libshared_glib_la-rap.lo] Error 1
make: *** [Makefile:10979: check] Error 2
##############################
Test: CheckValgrind - FAIL
Desc: Run Bluez Make Check with Valgrind
Output:
src/shared/rap.c:1256:13: error: redundant redeclaration of ‘ras_ondemand_cache_clear’ [-Werror=redundant-decls]
1256 | static void ras_ondemand_cache_clear(struct bt_rap *rap);
| ^~~~~~~~~~~~~~~~~~~~~~~~
src/shared/rap.c:414:13: note: previous declaration of ‘ras_ondemand_cache_clear’ with type ‘void(struct bt_rap *)’
414 | static void ras_ondemand_cache_clear(struct bt_rap *rap);
| ^~~~~~~~~~~~~~~~~~~~~~~~
src/shared/rap.c: In function ‘send_ras_segment_data’:
src/shared/rap.c:2264:21: error: variable ‘ras’ set but not used [-Werror=unused-but-set-variable]
2264 | struct ras *ras;
| ^~~
cc1: all warnings being treated as errors
make[1]: *** [Makefile:7947: src/shared/libshared_mainloop_la-rap.lo] Error 1
make[1]: *** Waiting for unfinished jobs....
make: *** [Makefile:10979: check] Error 2
##############################
Test: CheckSmatch - FAIL
Desc: Run smatch tool with source
Output:
src/shared/crypto.c:271:21: warning: Variable length array is used.
src/shared/crypto.c:272:23: warning: Variable length array is used.
src/shared/gatt-helpers.c:764:31: warning: Variable length array is used.
src/shared/gatt-helpers.c:842:31: warning: Variable length array is used.
src/shared/gatt-helpers.c:1335:31: warning: Variable length array is used.
src/shared/gatt-helpers.c:1366:23: warning: Variable length array is used.
src/shared/bap.c:318:25: warning: array of flexible structures
src/shared/bap.c: note: in included file:
./src/shared/ascs.h:88:25: warning: array of flexible structures
src/shared/shell.c: note: in included file (through /usr/include/x86_64-linux-gnu//sys/ioctl.h, /usr/include/x86_64-linux-gnu//sys/epoll.h, src/shared/mainloop.h):
/usr/include/x86_64-linux-gnu//sys/ttydefaults.h:55:9: warning: preprocessor token CTRL redefined
src/shared/shell.c: note: in included file (through /usr/include/readline/keymaps.h, /usr/include/readline/readline.h):
/usr/include/readline/chardefs.h:51:9: this was the original definition
src/shared/rap.c:1256:13: error: redundant redeclaration of ‘ras_ondemand_cache_clear’ [-Werror=redundant-decls]
1256 | static void ras_ondemand_cache_clear(struct bt_rap *rap);
| ^~~~~~~~~~~~~~~~~~~~~~~~
src/shared/rap.c:414:13: note: previous declaration of ‘ras_ondemand_cache_clear’ with type ‘void(struct bt_rap *)’
414 | static void ras_ondemand_cache_clear(struct bt_rap *rap);
| ^~~~~~~~~~~~~~~~~~~~~~~~
src/shared/rap.c: In function ‘send_ras_segment_data’:
src/shared/rap.c:2264:21: error: variable ‘ras’ set but not used [-Werror=unused-but-set-variable]
2264 | struct ras *ras;
| ^~~
src/shared/crypto.c:271:21: warning: Variable length array is used.
src/shared/crypto.c:272:23: warning: Variable length array is used.
cc1: all warnings being treated as errors
make[1]: *** [Makefile:7947: src/shared/libshared_mainloop_la-rap.lo] Error 1
make[1]: *** Waiting for unfinished jobs....
make: *** [Makefile:4241: all] Error 2
##############################
Test: bluezmakeextell - FAIL
Desc: Build Bluez with External ELL
Output:
src/shared/rap.c:1256:13: error: redundant redeclaration of ‘ras_ondemand_cache_clear’ [-Werror=redundant-decls]
1256 | static void ras_ondemand_cache_clear(struct bt_rap *rap);
| ^~~~~~~~~~~~~~~~~~~~~~~~
src/shared/rap.c:414:13: note: previous declaration of ‘ras_ondemand_cache_clear’ with type ‘void(struct bt_rap *)’
414 | static void ras_ondemand_cache_clear(struct bt_rap *rap);
| ^~~~~~~~~~~~~~~~~~~~~~~~
src/shared/rap.c: In function ‘send_ras_segment_data’:
src/shared/rap.c:2264:21: error: variable ‘ras’ set but not used [-Werror=unused-but-set-variable]
2264 | struct ras *ras;
| ^~~
cc1: all warnings being treated as errors
make[1]: *** [Makefile:7947: src/shared/libshared_mainloop_la-rap.lo] Error 1
make[1]: *** Waiting for unfinished jobs....
make: *** [Makefile:4241: all] Error 2
##############################
Test: TestFunctional - ERROR
Desc: Run test-functional
Output:
Failed to build BlueZ:
Failed to build BlueZ
##############################
Test: IncrementalBuild - FAIL
Desc: Incremental build with the patches in the series
Output:
src/shared/rap.c:1212:13: error: redundant redeclaration of ‘ras_ondemand_cache_clear’ [-Werror=redundant-decls]
1212 | static void ras_ondemand_cache_clear(struct bt_rap *rap);
| ^~~~~~~~~~~~~~~~~~~~~~~~
src/shared/rap.c:412:13: note: previous declaration of ‘ras_ondemand_cache_clear’ with type ‘void(struct bt_rap *)’
412 | static void ras_ondemand_cache_clear(struct bt_rap *rap);
| ^~~~~~~~~~~~~~~~~~~~~~~~
src/shared/rap.c:1212:13: error: redundant redeclaration of ‘ras_ondemand_cache_clear’ [-Werror=redundant-decls]
1212 | static void ras_ondemand_cache_clear(struct bt_rap *rap);
| ^~~~~~~~~~~~~~~~~~~~~~~~
src/shared/rap.c:412:13: note: previous declaration of ‘ras_ondemand_cache_clear’ with type ‘void(struct bt_rap *)’
412 | static void ras_ondemand_cache_clear(struct bt_rap *rap);
| ^~~~~~~~~~~~~~~~~~~~~~~~
src/shared/rap.c:1212:13: error: redundant redeclaration of ‘ras_ondemand_cache_clear’ [-Werror=redundant-decls]
1212 | static void ras_ondemand_cache_clear(struct bt_rap *rap);
| ^~~~~~~~~~~~~~~~~~~~~~~~
src/shared/rap.c:412:13: note: previous declaration of ‘ras_ondemand_cache_clear’ with type ‘void(struct bt_rap *)’
412 | static void ras_ondemand_cache_clear(struct bt_rap *rap);
| ^~~~~~~~~~~~~~~~~~~~~~~~
cc1: all warnings being treated as errors
make[1]: *** [Makefile:7660: src/shared/libshared_glib_la-rap.lo] Error 1
make[1]: *** Waiting for unfinished jobs....
cc1: all warnings being treated as errors
make[1]: *** [Makefile:7947: src/shared/libshared_mainloop_la-rap.lo] Error 1
cc1: all warnings being treated as errors
make[1]: *** [Makefile:7387: src/shared/libshared_ell_la-rap.lo] Error 1
make: *** [Makefile:4241: all] Error 2
[03/11] shared/rap: Honor CCCD transport for RAS on-demand ranging data
src/shared/rap.c:1212:13: error: redundant redeclaration of ‘ras_ondemand_cache_clear’ [-Werror=redundant-decls]
1212 | static void ras_ondemand_cache_clear(struct bt_rap *rap);
| ^~~~~~~~~~~~~~~~~~~~~~~~
src/shared/rap.c:412:13: note: previous declaration of ‘ras_ondemand_cache_clear’ with type ‘void(struct bt_rap *)’
412 | static void ras_ondemand_cache_clear(struct bt_rap *rap);
| ^~~~~~~~~~~~~~~~~~~~~~~~
src/shared/rap.c:1212:13: error: redundant redeclaration of ‘ras_ondemand_cache_clear’ [-Werror=redundant-decls]
1212 | static void ras_ondemand_cache_clear(struct bt_rap *rap);
| ^~~~~~~~~~~~~~~~~~~~~~~~
src/shared/rap.c:412:13: note: previous declaration of ‘ras_ondemand_cache_clear’ with type ‘void(struct bt_rap *)’
412 | static void ras_ondemand_cache_clear(struct bt_rap *rap);
| ^~~~~~~~~~~~~~~~~~~~~~~~
src/shared/rap.c:1212:13: error: redundant redeclaration of ‘ras_ondemand_cache_clear’ [-Werror=redundant-decls]
1212 | static void ras_ondemand_cache_clear(struct bt_rap *rap);
| ^~~~~~~~~~~~~~~~~~~~~~~~
src/shared/rap.c:412:13: note: previous declaration of ‘ras_ondemand_cache_clear’ with type ‘void(struct bt_rap *)’
412 | static void ras_ondemand_cache_clear(struct bt_rap *rap);
| ^~~~~~~~~~~~~~~~~~~~~~~~
cc1: all warnings being treated as errors
make[1]: *** [Makefile:7660: src/shared/libshared_glib_la-rap.lo] Error 1
make[1]: *** Waiting for unfinished jobs....
cc1: all warnings being treated as errors
make[1]: *** [Makefile:7947: src/shared/libshared_mainloop_la-rap.lo] Error 1
cc1: all warnings being treated as errors
make[1]: *** [Makefile:7387: src/shared/libshared_ell_la-rap.lo] Error 1
make: *** [Makefile:4241: all] Error 2
##############################
Test: ScanBuild - FAIL
Desc: Run Scan Build
Output:
src/shared/gatt-client.c:458:21: warning: Use of memory after it is freed [unix.Malloc]
458 | gatt_db_unregister(op->client->db, op->db_id);
| ^~~~~~~~~~
src/shared/gatt-client.c:703:2: warning: Use of memory after it is freed [unix.Malloc]
703 | discovery_op_complete(op, false, att_ecode);
| ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
src/shared/gatt-client.c:1109:2: warning: Use of memory after it is freed [unix.Malloc]
1109 | discovery_op_complete(op, success, att_ecode);
| ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
src/shared/gatt-client.c:1303:2: warning: Use of memory after it is freed [unix.Malloc]
1303 | discovery_op_complete(op, success, att_ecode);
| ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
src/shared/gatt-client.c:1368:2: warning: Use of memory after it is freed [unix.Malloc]
1368 | discovery_op_complete(op, success, att_ecode);
| ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
src/shared/gatt-client.c:1643:6: warning: Use of memory after it is freed [unix.Malloc]
1643 | if (read_db_hash(op)) {
| ^~~~~~~~~~~~~~~~
src/shared/gatt-client.c:1648:2: warning: Use of memory after it is freed [unix.Malloc]
1648 | discover_all(op);
| ^~~~~~~~~~~~~~~~
src/shared/gatt-client.c:1703:56: warning: Use of memory after it is freed [unix.Malloc]
1703 | notify_data->chrc->ccc_write_id = notify_data->att_id = att_id;
| ~~~~~~~~~~~~~~~~~~~ ^
src/shared/gatt-client.c:2168:6: warning: Use of memory after it is freed [unix.Malloc]
2168 | if (read_db_hash(op)) {
| ^~~~~~~~~~~~~~~~
src/shared/gatt-client.c:2176:8: warning: Use of memory after it is freed [unix.Malloc]
2176 | discovery_op_ref(op),
| ^~~~~~~~~~~~~~~~~~~~
src/shared/gatt-client.c:3362:2: warning: Use of memory after it is freed [unix.Malloc]
3362 | complete_write_long_op(req, success, 0, false);
| ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
src/shared/gatt-client.c:3384:2: warning: Use of memory after it is freed [unix.Malloc]
3384 | request_unref(req);
| ^~~~~~~~~~~~~~~~~~
12 warnings generated.
src/shared/rap.c:1212:13: error: redundant redeclaration of ‘ras_ondemand_cache_clear’ [-Werror=redundant-decls]
1212 | static void ras_ondemand_cache_clear(struct bt_rap *rap);
| ^~~~~~~~~~~~~~~~~~~~~~~~
src/shared/rap.c:412:13: note: previous declaration of ‘ras_ondemand_cache_clear’ with type ‘void(struct bt_rap *)’
412 | static void ras_ondemand_cache_clear(struct bt_rap *rap);
| ^~~~~~~~~~~~~~~~~~~~~~~~
cc1: all warnings being treated as errors
make[1]: *** [Makefile:7947: src/shared/libshared_mainloop_la-rap.lo] Error 1
make[1]: *** Waiting for unfinished jobs....
src/shared/bap.c:1543:8: warning: Use of memory after it is freed [unix.Malloc]
1543 | bap = bt_bap_ref_safe(bap);
| ^~~~~~~~~~~~~~~~~~~~
src/shared/bap.c:2359:20: warning: Use of memory after it is freed [unix.Malloc]
2359 | return queue_find(stream->bap->streams, NULL, stream);
| ^~~~~~~~~~~~~~~~~~~~
2 warnings generated.
make: *** [Makefile:4241: all] Error 2
https://github.com/bluez/bluez/pull/2617
---
Regards,
Linux Bluetooth
^ permalink raw reply [flat|nested] 15+ messages in thread
* [PATCH 02/11] shared/gatt-db: Add gatt_db_attribute_indicate()
2026-10-01 13:18 [PATCH 00/11] Ranging Profile and Ranging Service PTS fixes Cheng Jiang
2026-10-01 13:18 ` [PATCH 01/11] shared/gatt-client: Allow registering notify with explicit CCC value Cheng Jiang
@ 2026-10-01 13:18 ` Cheng Jiang
2026-10-01 15:48 ` Luiz Augusto von Dentz
2026-10-01 13:18 ` [PATCH 03/11] shared/rap: Honor CCCD transport for RAS on-demand ranging data Cheng Jiang
` (8 subsequent siblings)
10 siblings, 1 reply; 15+ messages in thread
From: Cheng Jiang @ 2026-10-01 13:18 UTC (permalink / raw)
To: linux-bluetooth
Cc: jinwang.li, wei.deng, xiuzhuo.shang, shuai.zhang, Cheng Jiang
The notify callback leaves the choice between a notification and an
indication entirely to the CCC value written by the client, preferring
notifications whenever the client enabled them. Some services need the
indication transport for a given characteristic regardless: the Ranging
Service must indicate Ranging Data Ready and Ranging Data Overwritten
even when the client subscribed to both transports (RAS 3.4.2, 3.5.2).
Add gatt_db_attribute_indicate() alongside gatt_db_attribute_notify() and
let the callback query which of the two it was invoked from through
gatt_db_attribute_indicate_only(). Both entry points share a helper, so
the CCC lookup stays in one place, and neither gatt_db_notify_t nor the
existing gatt_db_attribute_notify() callers change.
gatt-database honors the request by skipping the notification branch and
sending an indication instead, provided the client enabled indications.
Assisted-by: Claude:claude-opus-4.8
---
src/gatt-database.c | 7 ++++++-
src/shared/gatt-db.c | 35 +++++++++++++++++++++++++++++++++--
src/shared/gatt-db.h | 17 +++++++++++++++++
unit/test-rap.c | 13 +++++++++----
4 files changed, 65 insertions(+), 7 deletions(-)
diff --git a/src/gatt-database.c b/src/gatt-database.c
index 18b7aa667..800f76032 100644
--- a/src/gatt-database.c
+++ b/src/gatt-database.c
@@ -168,6 +168,7 @@ struct notify {
uint16_t len;
bt_gatt_server_conf_func_t conf;
void *user_data;
+ bool indicate_only;
};
#define CLI_FEAT_SIZE 1
@@ -1446,7 +1447,7 @@ static void send_notification_to_device(void *data, void *user_data)
* TODO: If the device is not connected but bonded, send the
* notification/indication when it becomes connected.
*/
- if (ccc->value & 0x0001) {
+ if (!notify->indicate_only && (ccc->value & 0x0001)) {
DBG("GATT server sending notification");
bt_gatt_server_send_notification(server,
notify->handle, notify->value,
@@ -1455,6 +1456,9 @@ static void send_notification_to_device(void *data, void *user_data)
return;
}
+ if (!(ccc->value & 0x0002))
+ return;
+
DBG("GATT server sending indication");
bt_gatt_server_send_indication(server, notify->handle, notify->value,
notify->len, notify->conf,
@@ -1486,6 +1490,7 @@ static void gatt_notify_cb(struct gatt_db_attribute *attrib,
notify.ccc_handle = gatt_db_attribute_get_handle(ccc);
notify.value = (void *) value;
notify.len = len;
+ notify.indicate_only = gatt_db_attribute_indicate_only(attrib);
if (attrib == database->svc_chngd)
notify.conf = service_changed_conf;
diff --git a/src/shared/gatt-db.c b/src/shared/gatt-db.c
index 751d4c3da..1f6e9ae14 100644
--- a/src/shared/gatt-db.c
+++ b/src/shared/gatt-db.c
@@ -123,6 +123,11 @@ struct gatt_db_attribute {
unsigned int next_notify_id;
struct queue *notify_list;
+
+ /* Set only while notify_func is being called on behalf of
+ * gatt_db_attribute_indicate().
+ */
+ bool indicate_only;
};
struct gatt_db_service {
@@ -2440,9 +2445,9 @@ gatt_db_attribute_get_ccc(struct gatt_db_attribute *attrib)
return ccc;
}
-bool gatt_db_attribute_notify(struct gatt_db_attribute *attrib,
+static bool attribute_notify(struct gatt_db_attribute *attrib,
const uint8_t *value, size_t len,
- struct bt_att *att)
+ struct bt_att *att, bool indicate_only)
{
struct gatt_db_attribute *ccc;
struct gatt_db *db;
@@ -2468,11 +2473,37 @@ bool gatt_db_attribute_notify(struct gatt_db_attribute *attrib,
else
notify_user_data = ccc->user_data;
+ attrib->indicate_only = indicate_only;
+
attrib->notify_func(attrib, ccc, value, len, att, notify_user_data);
+ attrib->indicate_only = false;
+
return true;
}
+bool gatt_db_attribute_notify(struct gatt_db_attribute *attrib,
+ const uint8_t *value, size_t len,
+ struct bt_att *att)
+{
+ return attribute_notify(attrib, value, len, att, false);
+}
+
+bool gatt_db_attribute_indicate(struct gatt_db_attribute *attrib,
+ const uint8_t *value, size_t len,
+ struct bt_att *att)
+{
+ return attribute_notify(attrib, value, len, att, true);
+}
+
+bool gatt_db_attribute_indicate_only(const struct gatt_db_attribute *attrib)
+{
+ if (!attrib)
+ return false;
+
+ return attrib->indicate_only;
+}
+
bool gatt_db_attribute_reset(struct gatt_db_attribute *attrib)
{
if (!attrib)
diff --git a/src/shared/gatt-db.h b/src/shared/gatt-db.h
index da0600c01..63d7f3464 100644
--- a/src/shared/gatt-db.h
+++ b/src/shared/gatt-db.h
@@ -300,6 +300,23 @@ bool gatt_db_attribute_notify(struct gatt_db_attribute *attrib,
const uint8_t *value, size_t len,
struct bt_att *att);
+/* Same as gatt_db_attribute_notify() but requests an indication even when the
+ * client has also enabled notifications. Some profiles mandate the indication
+ * transport for specific characteristics (e.g. the RAS Ranging Data Ready and
+ * Ranging Data Overwritten characteristics), which gatt_db_attribute_notify()
+ * cannot express since it prefers notifications whenever the client enabled
+ * them.
+ */
+bool gatt_db_attribute_indicate(struct gatt_db_attribute *attrib,
+ const uint8_t *value, size_t len,
+ struct bt_att *att);
+
+/* Valid only while a gatt_db_notify_t callback is running: tells whether it was
+ * triggered by gatt_db_attribute_indicate() rather than
+ * gatt_db_attribute_notify().
+ */
+bool gatt_db_attribute_indicate_only(const struct gatt_db_attribute *attrib);
+
bool gatt_db_attribute_reset(struct gatt_db_attribute *attrib);
void *gatt_db_attribute_get_user_data(struct gatt_db_attribute *attrib);
diff --git a/unit/test-rap.c b/unit/test-rap.c
index d7da2ff45..5c6033a4e 100644
--- a/unit/test-rap.c
+++ b/unit/test-rap.c
@@ -167,10 +167,15 @@ static void gatt_notify_cb(struct gatt_db_attribute *attrib,
printf("%s: notify.value:%d notify->len:%d\n", __func__,
(int)*(notify.value), notify.len);
- if (!bt_gatt_server_send_notification(data->server,
- notify.handle, notify.value,
- notify.len, false))
- printf("%s: Failed to send notification\n", __func__);
+ if (gatt_db_attribute_indicate_only(attrib)) {
+ if (!bt_gatt_server_send_indication(data->server, notify.handle,
+ notify.value, notify.len, NULL, NULL, NULL) &&
+ tester_use_debug())
+ tester_debug("Failed to send indication");
+ } else if (!bt_gatt_server_send_notification(data->server,
+ notify.handle, notify.value, notify.len, false) &&
+ tester_use_debug())
+ tester_debug("Failed to send notification");
}
static void gatt_ccc_write_cb(struct gatt_db_attribute *attrib,
--
2.43.0
^ permalink raw reply related [flat|nested] 15+ messages in thread* Re: [PATCH 02/11] shared/gatt-db: Add gatt_db_attribute_indicate()
2026-10-01 13:18 ` [PATCH 02/11] shared/gatt-db: Add gatt_db_attribute_indicate() Cheng Jiang
@ 2026-10-01 15:48 ` Luiz Augusto von Dentz
2026-10-09 8:45 ` Cheng Jiang
0 siblings, 1 reply; 15+ messages in thread
From: Luiz Augusto von Dentz @ 2026-10-01 15:48 UTC (permalink / raw)
To: Cheng Jiang
Cc: linux-bluetooth, jinwang.li, wei.deng, xiuzhuo.shang, shuai.zhang
Hi,
On Thu, Oct 1, 2026 at 9:48 AM Cheng Jiang <cheng.jiang@oss.qualcomm.com> wrote:
>
> The notify callback leaves the choice between a notification and an
> indication entirely to the CCC value written by the client, preferring
> notifications whenever the client enabled them. Some services need the
> indication transport for a given characteristic regardless: the Ranging
> Service must indicate Ranging Data Ready and Ranging Data Overwritten
> even when the client subscribed to both transports (RAS 3.4.2, 3.5.2).
>
> Add gatt_db_attribute_indicate() alongside gatt_db_attribute_notify() and
> let the callback query which of the two it was invoked from through
> gatt_db_attribute_indicate_only(). Both entry points share a helper, so
> the CCC lookup stays in one place, and neither gatt_db_notify_t nor the
> existing gatt_db_attribute_notify() callers change.
>
> gatt-database honors the request by skipping the notification branch and
> sending an indication instead, provided the client enabled indications.
>
> Assisted-by: Claude:claude-opus-4.8
> ---
> src/gatt-database.c | 7 ++++++-
> src/shared/gatt-db.c | 35 +++++++++++++++++++++++++++++++++--
> src/shared/gatt-db.h | 17 +++++++++++++++++
> unit/test-rap.c | 13 +++++++++----
> 4 files changed, 65 insertions(+), 7 deletions(-)
>
> diff --git a/src/gatt-database.c b/src/gatt-database.c
> index 18b7aa667..800f76032 100644
> --- a/src/gatt-database.c
> +++ b/src/gatt-database.c
> @@ -168,6 +168,7 @@ struct notify {
> uint16_t len;
> bt_gatt_server_conf_func_t conf;
> void *user_data;
> + bool indicate_only;
> };
>
> #define CLI_FEAT_SIZE 1
> @@ -1446,7 +1447,7 @@ static void send_notification_to_device(void *data, void *user_data)
> * TODO: If the device is not connected but bonded, send the
> * notification/indication when it becomes connected.
> */
> - if (ccc->value & 0x0001) {
> + if (!notify->indicate_only && (ccc->value & 0x0001)) {
> DBG("GATT server sending notification");
> bt_gatt_server_send_notification(server,
> notify->handle, notify->value,
> @@ -1455,6 +1456,9 @@ static void send_notification_to_device(void *data, void *user_data)
> return;
> }
>
> + if (!(ccc->value & 0x0002))
> + return;
> +
> DBG("GATT server sending indication");
> bt_gatt_server_send_indication(server, notify->handle, notify->value,
> notify->len, notify->conf,
> @@ -1486,6 +1490,7 @@ static void gatt_notify_cb(struct gatt_db_attribute *attrib,
> notify.ccc_handle = gatt_db_attribute_get_handle(ccc);
> notify.value = (void *) value;
> notify.len = len;
> + notify.indicate_only = gatt_db_attribute_indicate_only(attrib);
>
> if (attrib == database->svc_chngd)
> notify.conf = service_changed_conf;
> diff --git a/src/shared/gatt-db.c b/src/shared/gatt-db.c
> index 751d4c3da..1f6e9ae14 100644
> --- a/src/shared/gatt-db.c
> +++ b/src/shared/gatt-db.c
> @@ -123,6 +123,11 @@ struct gatt_db_attribute {
>
> unsigned int next_notify_id;
> struct queue *notify_list;
> +
> + /* Set only while notify_func is being called on behalf of
> + * gatt_db_attribute_indicate().
> + */
> + bool indicate_only;
> };
>
> struct gatt_db_service {
> @@ -2440,9 +2445,9 @@ gatt_db_attribute_get_ccc(struct gatt_db_attribute *attrib)
> return ccc;
> }
>
> -bool gatt_db_attribute_notify(struct gatt_db_attribute *attrib,
> +static bool attribute_notify(struct gatt_db_attribute *attrib,
> const uint8_t *value, size_t len,
> - struct bt_att *att)
> + struct bt_att *att, bool indicate_only)
> {
> struct gatt_db_attribute *ccc;
> struct gatt_db *db;
> @@ -2468,11 +2473,37 @@ bool gatt_db_attribute_notify(struct gatt_db_attribute *attrib,
> else
> notify_user_data = ccc->user_data;
>
> + attrib->indicate_only = indicate_only;
> +
> attrib->notify_func(attrib, ccc, value, len, att, notify_user_data);
>
> + attrib->indicate_only = false;
> +
> return true;
> }
>
> +bool gatt_db_attribute_notify(struct gatt_db_attribute *attrib,
> + const uint8_t *value, size_t len,
> + struct bt_att *att)
> +{
> + return attribute_notify(attrib, value, len, att, false);
> +}
> +
> +bool gatt_db_attribute_indicate(struct gatt_db_attribute *attrib,
> + const uint8_t *value, size_t len,
> + struct bt_att *att)
> +{
> + return attribute_notify(attrib, value, len, att, true);
> +}
> +
> +bool gatt_db_attribute_indicate_only(const struct gatt_db_attribute *attrib)
> +{
> + if (!attrib)
> + return false;
> +
> + return attrib->indicate_only;
> +}
> +
> bool gatt_db_attribute_reset(struct gatt_db_attribute *attrib)
> {
> if (!attrib)
> diff --git a/src/shared/gatt-db.h b/src/shared/gatt-db.h
> index da0600c01..63d7f3464 100644
> --- a/src/shared/gatt-db.h
> +++ b/src/shared/gatt-db.h
> @@ -300,6 +300,23 @@ bool gatt_db_attribute_notify(struct gatt_db_attribute *attrib,
> const uint8_t *value, size_t len,
> struct bt_att *att);
>
> +/* Same as gatt_db_attribute_notify() but requests an indication even when the
> + * client has also enabled notifications. Some profiles mandate the indication
> + * transport for specific characteristics (e.g. the RAS Ranging Data Ready and
> + * Ranging Data Overwritten characteristics), which gatt_db_attribute_notify()
> + * cannot express since it prefers notifications whenever the client enabled
> + * them.
> + */
> +bool gatt_db_attribute_indicate(struct gatt_db_attribute *attrib,
> + const uint8_t *value, size_t len,
> + struct bt_att *att);
> +
> +/* Valid only while a gatt_db_notify_t callback is running: tells whether it was
> + * triggered by gatt_db_attribute_indicate() rather than
> + * gatt_db_attribute_notify().
> + */
> +bool gatt_db_attribute_indicate_only(const struct gatt_db_attribute *attrib);
> +
> bool gatt_db_attribute_reset(struct gatt_db_attribute *attrib);
>
> void *gatt_db_attribute_get_user_data(struct gatt_db_attribute *attrib);
> diff --git a/unit/test-rap.c b/unit/test-rap.c
> index d7da2ff45..5c6033a4e 100644
> --- a/unit/test-rap.c
> +++ b/unit/test-rap.c
> @@ -167,10 +167,15 @@ static void gatt_notify_cb(struct gatt_db_attribute *attrib,
>
> printf("%s: notify.value:%d notify->len:%d\n", __func__,
> (int)*(notify.value), notify.len);
> - if (!bt_gatt_server_send_notification(data->server,
> - notify.handle, notify.value,
> - notify.len, false))
> - printf("%s: Failed to send notification\n", __func__);
> + if (gatt_db_attribute_indicate_only(attrib)) {
> + if (!bt_gatt_server_send_indication(data->server, notify.handle,
> + notify.value, notify.len, NULL, NULL, NULL) &&
> + tester_use_debug())
> + tester_debug("Failed to send indication");
> + } else if (!bt_gatt_server_send_notification(data->server,
> + notify.handle, notify.value, notify.len, false) &&
> + tester_use_debug())
> + tester_debug("Failed to send notification");
> }
Don't mix changes from src/shared and unit/, send them in separate
patches, now about the gatt_db_attribute_indicate_only, introducing an
API just for unit testing sounds suboptional and I wonder if it could
be avoided some way.
> static void gatt_ccc_write_cb(struct gatt_db_attribute *attrib,
> --
> 2.43.0
>
>
--
Luiz Augusto von Dentz
^ permalink raw reply [flat|nested] 15+ messages in thread* Re: [PATCH 02/11] shared/gatt-db: Add gatt_db_attribute_indicate()
2026-10-01 15:48 ` Luiz Augusto von Dentz
@ 2026-10-09 8:45 ` Cheng Jiang
0 siblings, 0 replies; 15+ messages in thread
From: Cheng Jiang @ 2026-10-09 8:45 UTC (permalink / raw)
To: Luiz Augusto von Dentz
Cc: linux-bluetooth, jinwang.li, wei.deng, xiuzhuo.shang, shuai.zhang
Hi Luiz,
On 10/1/2026 11:48 PM, Luiz Augusto von Dentz wrote:
> Hi,
>
> On Thu, Oct 1, 2026 at 9:48 AM Cheng Jiang <cheng.jiang@oss.qualcomm.com> wrote:
>>
>> The notify callback leaves the choice between a notification and an
>> indication entirely to the CCC value written by the client, preferring
>> notifications whenever the client enabled them. Some services need the
>> indication transport for a given characteristic regardless: the Ranging
>> Service must indicate Ranging Data Ready and Ranging Data Overwritten
>> even when the client subscribed to both transports (RAS 3.4.2, 3.5.2).
>>
>> Add gatt_db_attribute_indicate() alongside gatt_db_attribute_notify() and
>> let the callback query which of the two it was invoked from through
>> gatt_db_attribute_indicate_only(). Both entry points share a helper, so
>> the CCC lookup stays in one place, and neither gatt_db_notify_t nor the
>> existing gatt_db_attribute_notify() callers change.
>>
>> gatt-database honors the request by skipping the notification branch and
>> sending an indication instead, provided the client enabled indications.
>>
>> Assisted-by: Claude:claude-opus-4.8
>> ---
>> src/gatt-database.c | 7 ++++++-
>> src/shared/gatt-db.c | 35 +++++++++++++++++++++++++++++++++--
>> src/shared/gatt-db.h | 17 +++++++++++++++++
>> unit/test-rap.c | 13 +++++++++----
>> 4 files changed, 65 insertions(+), 7 deletions(-)
>>
>> diff --git a/src/gatt-database.c b/src/gatt-database.c
>> index 18b7aa667..800f76032 100644
>> --- a/src/gatt-database.c
>> +++ b/src/gatt-database.c
>> @@ -168,6 +168,7 @@ struct notify {
>> uint16_t len;
>> bt_gatt_server_conf_func_t conf;
>> void *user_data;
>> + bool indicate_only;
>> };
>>
>> #define CLI_FEAT_SIZE 1
>> @@ -1446,7 +1447,7 @@ static void send_notification_to_device(void *data, void *user_data)
>> * TODO: If the device is not connected but bonded, send the
>> * notification/indication when it becomes connected.
>> */
>> - if (ccc->value & 0x0001) {
>> + if (!notify->indicate_only && (ccc->value & 0x0001)) {
>> DBG("GATT server sending notification");
>> bt_gatt_server_send_notification(server,
>> notify->handle, notify->value,
>> @@ -1455,6 +1456,9 @@ static void send_notification_to_device(void *data, void *user_data)
>> return;
>> }
>>
>> + if (!(ccc->value & 0x0002))
>> + return;
>> +
>> DBG("GATT server sending indication");
>> bt_gatt_server_send_indication(server, notify->handle, notify->value,
>> notify->len, notify->conf,
>> @@ -1486,6 +1490,7 @@ static void gatt_notify_cb(struct gatt_db_attribute *attrib,
>> notify.ccc_handle = gatt_db_attribute_get_handle(ccc);
>> notify.value = (void *) value;
>> notify.len = len;
>> + notify.indicate_only = gatt_db_attribute_indicate_only(attrib);
>>
>> if (attrib == database->svc_chngd)
>> notify.conf = service_changed_conf;
>> diff --git a/src/shared/gatt-db.c b/src/shared/gatt-db.c
>> index 751d4c3da..1f6e9ae14 100644
>> --- a/src/shared/gatt-db.c
>> +++ b/src/shared/gatt-db.c
>> @@ -123,6 +123,11 @@ struct gatt_db_attribute {
>>
>> unsigned int next_notify_id;
>> struct queue *notify_list;
>> +
>> + /* Set only while notify_func is being called on behalf of
>> + * gatt_db_attribute_indicate().
>> + */
>> + bool indicate_only;
>> };
>>
>> struct gatt_db_service {
>> @@ -2440,9 +2445,9 @@ gatt_db_attribute_get_ccc(struct gatt_db_attribute *attrib)
>> return ccc;
>> }
>>
>> -bool gatt_db_attribute_notify(struct gatt_db_attribute *attrib,
>> +static bool attribute_notify(struct gatt_db_attribute *attrib,
>> const uint8_t *value, size_t len,
>> - struct bt_att *att)
>> + struct bt_att *att, bool indicate_only)
>> {
>> struct gatt_db_attribute *ccc;
>> struct gatt_db *db;
>> @@ -2468,11 +2473,37 @@ bool gatt_db_attribute_notify(struct gatt_db_attribute *attrib,
>> else
>> notify_user_data = ccc->user_data;
>>
>> + attrib->indicate_only = indicate_only;
>> +
>> attrib->notify_func(attrib, ccc, value, len, att, notify_user_data);
>>
>> + attrib->indicate_only = false;
>> +
>> return true;
>> }
>>
>> +bool gatt_db_attribute_notify(struct gatt_db_attribute *attrib,
>> + const uint8_t *value, size_t len,
>> + struct bt_att *att)
>> +{
>> + return attribute_notify(attrib, value, len, att, false);
>> +}
>> +
>> +bool gatt_db_attribute_indicate(struct gatt_db_attribute *attrib,
>> + const uint8_t *value, size_t len,
>> + struct bt_att *att)
>> +{
>> + return attribute_notify(attrib, value, len, att, true);
>> +}
>> +
>> +bool gatt_db_attribute_indicate_only(const struct gatt_db_attribute *attrib)
>> +{
>> + if (!attrib)
>> + return false;
>> +
>> + return attrib->indicate_only;
>> +}
>> +
>> bool gatt_db_attribute_reset(struct gatt_db_attribute *attrib)
>> {
>> if (!attrib)
>> diff --git a/src/shared/gatt-db.h b/src/shared/gatt-db.h
>> index da0600c01..63d7f3464 100644
>> --- a/src/shared/gatt-db.h
>> +++ b/src/shared/gatt-db.h
>> @@ -300,6 +300,23 @@ bool gatt_db_attribute_notify(struct gatt_db_attribute *attrib,
>> const uint8_t *value, size_t len,
>> struct bt_att *att);
>>
>> +/* Same as gatt_db_attribute_notify() but requests an indication even when the
>> + * client has also enabled notifications. Some profiles mandate the indication
>> + * transport for specific characteristics (e.g. the RAS Ranging Data Ready and
>> + * Ranging Data Overwritten characteristics), which gatt_db_attribute_notify()
>> + * cannot express since it prefers notifications whenever the client enabled
>> + * them.
>> + */
>> +bool gatt_db_attribute_indicate(struct gatt_db_attribute *attrib,
>> + const uint8_t *value, size_t len,
>> + struct bt_att *att);
>> +
>> +/* Valid only while a gatt_db_notify_t callback is running: tells whether it was
>> + * triggered by gatt_db_attribute_indicate() rather than
>> + * gatt_db_attribute_notify().
>> + */
>> +bool gatt_db_attribute_indicate_only(const struct gatt_db_attribute *attrib);
>> +
>> bool gatt_db_attribute_reset(struct gatt_db_attribute *attrib);
>>
>> void *gatt_db_attribute_get_user_data(struct gatt_db_attribute *attrib);
>> diff --git a/unit/test-rap.c b/unit/test-rap.c
>> index d7da2ff45..5c6033a4e 100644
>> --- a/unit/test-rap.c
>> +++ b/unit/test-rap.c
>> @@ -167,10 +167,15 @@ static void gatt_notify_cb(struct gatt_db_attribute *attrib,
>>
>> printf("%s: notify.value:%d notify->len:%d\n", __func__,
>> (int)*(notify.value), notify.len);
>> - if (!bt_gatt_server_send_notification(data->server,
>> - notify.handle, notify.value,
>> - notify.len, false))
>> - printf("%s: Failed to send notification\n", __func__);
>> + if (gatt_db_attribute_indicate_only(attrib)) {
>> + if (!bt_gatt_server_send_indication(data->server, notify.handle,
>> + notify.value, notify.len, NULL, NULL, NULL) &&
>> + tester_use_debug())
>> + tester_debug("Failed to send indication");
>> + } else if (!bt_gatt_server_send_notification(data->server,
>> + notify.handle, notify.value, notify.len, false) &&
>> + tester_use_debug())
>> + tester_debug("Failed to send notification");
>> }
>
> Don't mix changes from src/shared and unit/, send them in separate
> patches, now about the gatt_db_attribute_indicate_only, introducing an
> API just for unit testing sounds suboptional and I wonder if it could
> be avoided some way.
Sure, I will update in v2 to address the two issues. Thank you for the
comment.
>> static void gatt_ccc_write_cb(struct gatt_db_attribute *attrib,
>> --
>> 2.43.0
>>
>>
>
>
^ permalink raw reply [flat|nested] 15+ messages in thread
* [PATCH 03/11] shared/rap: Honor CCCD transport for RAS on-demand ranging data
2026-10-01 13:18 [PATCH 00/11] Ranging Profile and Ranging Service PTS fixes Cheng Jiang
2026-10-01 13:18 ` [PATCH 01/11] shared/gatt-client: Allow registering notify with explicit CCC value Cheng Jiang
2026-10-01 13:18 ` [PATCH 02/11] shared/gatt-db: Add gatt_db_attribute_indicate() Cheng Jiang
@ 2026-10-01 13:18 ` Cheng Jiang
2026-10-01 13:18 ` [PATCH 04/11] shared/rap: Add RAS Control Point Abort Operation Cheng Jiang
` (7 subsequent siblings)
10 siblings, 0 replies; 15+ messages in thread
From: Cheng Jiang @ 2026-10-01 13:18 UTC (permalink / raw)
To: linux-bluetooth
Cc: jinwang.li, wei.deng, xiuzhuo.shang, shuai.zhang, Cheng Jiang
Complete the RAS responder handling of On-demand Ranging Data so the
transport used for each notified characteristic follows the peer's CCCD
subscription. The On-demand Ranging Data, RAS Control Point, Ranging
Data Ready and Ranging Data Overwritten characteristics are delivered
as notifications or indications according to how the client configured
their CCCDs. Ready and Overwritten gain a CCC write callback so their
subscribed transport is tracked, and a Ranging Counter each so a read
returns the value last notified.
When a client enables both transports (0x0003) the spec does not pick the
same one everywhere, so the two rules are kept in separate helpers:
Ranging Data Ready and Ranging Data Overwritten are indicated per RAS
3.4.2 and 3.5.2, while On-demand and Real-time Ranging Data are notified
per RAS 3.2.2 and 3.2.3.1. The RAS Control Point is indicate-only.
Ranging data is segmented across ATT PDUs, and it was queued a whole
transfer at a time. Notifications land on the att write queue, which
pick_next_send_op() drains ahead of the request queue and with no flow
control of its own, so doing that starves incoming ATT operations until the
last segment has been written: a Control Point write arriving mid-transfer
is only answered once the transfer ends.
Send one segment at a time instead. Indications go out with bt_att_send()
and the next segment is queued from the confirmation callback; notifications
have no confirmation to gate on, so the next one is queued from a short
timeout. Either way a single segment is outstanding, so an Abort Operation
stops the transfer rather than letting a queued one drain out.
The notification delay cannot be zero: a zero timeout yields the event loop
without waiting, so a Control Point write is only seen once it has already
arrived, and a short transfer can finish before an Abort crosses the air
interface. Against RAS/SR/SPE/BI-05-C, which writes an invalid Abort
Operation mid-transfer, 0 ms fails and 5 ms passes, so use that.
Assisted-by: Claude:claude-opus-4.8
---
src/shared/rap.c | 386 +++++++++++++++++++++++++++++++++++++++++------
1 file changed, 336 insertions(+), 50 deletions(-)
diff --git a/src/shared/rap.c b/src/shared/rap.c
index e55bbe088..a794de044 100644
--- a/src/shared/rap.c
+++ b/src/shared/rap.c
@@ -33,6 +33,11 @@
* ras_ondemand_client permanently stuck waiting.
*/
#define RAS_CP_RESPONSE_TIMEOUT (10 * 1000)
+#define RAS_SEGMENT_TIMEOUT (1 * 1000)
+/* Delay between On-demand ranging-data notification segments, see
+ * send_ondemand_segment_data_cb().
+ */
+#define RAS_SEGMENT_NFY_PACE_MS 5
#define DBG(_rap, fmt, ...) \
rap_debug(_rap, "%s:%s() " fmt, __FILE__, __func__, ##__VA_ARGS__)
@@ -337,10 +342,12 @@ enum ras_cp_response_code {
*/
struct ras_ondemand_cache {
bool valid;
+ bool sending;
uint16_t counter;
struct iovec data;
uint16_t send_index;
struct segmentation_header seg_hdr;
+ unsigned int send_id;
};
/* Initiator role: which RAS Control Point request the client is
@@ -378,10 +385,16 @@ struct ras {
struct gatt_db_attribute *ready_ccc;
struct gatt_db_attribute *overwritten_chrc;
struct gatt_db_attribute *overwritten_ccc;
+ uint16_t ready_counter;
+ uint16_t overwritten_counter;
/* CCC state tracking for mutual exclusivity */
uint16_t realtime_ccc_value;
uint16_t ondemand_ccc_value;
+ /* CCC state for Ready/Overwritten so notifications are sent with the
+ * transport (notification vs indication) the client actually enabled. */
+ uint16_t ready_ccc_value;
+ uint16_t overwritten_ccc_value;
/* Initiator role: pending RAS Control Point request state */
struct ras_ondemand_client client;
@@ -396,6 +409,9 @@ static inline bool ras_is_ondemand_mode(const struct ras *ras)
* defined alongside the other RAS Control Point client helpers.
*/
static void ras_ondemand_client_reset(struct ras *ras);
+static void ras_ondemand_cache_clear(struct bt_rap *rap);
+static bool ras_cp_write_op(struct bt_rap *rap, struct ras *ras,
+ uint8_t opcode, uint16_t counter);
struct bt_rap_db {
struct gatt_db *db;
@@ -942,8 +958,7 @@ void bt_rap_detach(struct bt_rap *rap)
* connection so a reconnect (by this or another central) cannot
* be served stale ranging data via Get_Ranging_Data.
*/
- free(rap->cache.data.iov_base);
- memset(&rap->cache, 0, sizeof(rap->cache));
+ ras_ondemand_cache_clear(rap);
if (rap->rrapdb && rap->rrapdb->ras) {
struct ras *ras = rap->rrapdb->ras;
@@ -1180,12 +1195,7 @@ static void ras_features_read_cb(struct gatt_db_attribute *attrib,
void *user_data)
{
struct ras *ras = user_data;
- /*
- * Feature mask: bits 0-2 set:
- * - Real-time ranging
- * - Retrieve stored results
- * - Abort operation
- */
+ /* Real-time ranging and Abort Operation are supported. */
uint8_t value[4] = { 0x01, 0x00, 0x00, 0x00 };
if (ras)
@@ -1203,9 +1213,7 @@ static void ras_ondemand_cache_clear(struct bt_rap *rap);
/*
* RAS Control Point handler, dispatches Get_Ranging_Data and
- * ACK_Ranging_Data. The remaining, optional
- * op codes (Retrieve_Lost_Ranging_Data_Segments, Abort_Operation,
- * Set_Filter) are not implemented.
+ * ACK_Ranging_Data. The remaining optional op codes are not implemented.
*/
static void ras_control_point_write_cb(struct gatt_db_attribute *attrib,
unsigned int id, uint16_t offset,
@@ -1236,6 +1244,13 @@ static void ras_control_point_write_cb(struct gatt_db_attribute *attrib,
return;
}
+ if (rap->cache.sending) {
+ ras_cp_send_response_code(rap, ras, RAS_CP_RSP_CODE_SERVER_BUSY);
+ goto done;
+ }
+
+ DBG(rap, "CP write: opcode=0x%02x len=%zu", value[0], len);
+
switch (value[0]) {
case RAS_CP_OP_GET_RANGING_DATA:
if (len != 3) {
@@ -1279,6 +1294,7 @@ static void ras_control_point_write_cb(struct gatt_db_attribute *attrib,
break;
}
+done:
gatt_db_attribute_write_result(attrib, id, 0);
}
@@ -1289,13 +1305,12 @@ static void ras_data_ready_read_cb(struct gatt_db_attribute *attrib,
void *user_data)
{
struct ras *ras = user_data;
- uint16_t counter = 0;
uint8_t value[2];
if (ras)
bt_rap_get_session(att, ras->rapdb->db);
- put_le16(counter, value);
+ put_le16(ras ? ras->ready_counter : 0, value);
gatt_db_attribute_read_result(attrib, id, 0, value, sizeof(value));
}
@@ -1306,11 +1321,12 @@ static void ras_data_overwritten_read_cb(struct gatt_db_attribute *attrib,
void *user_data)
{
struct ras *ras = user_data;
- uint8_t value[2] = { 0x00, 0x00 };
+ uint8_t value[2];
if (ras)
bt_rap_get_session(att, ras->rapdb->db);
+ put_le16(ras ? ras->overwritten_counter : 0, value);
gatt_db_attribute_read_result(attrib, id, 0, value, sizeof(value));
}
@@ -1380,6 +1396,75 @@ static void ras_ranging_data_ccc_write_cb(struct gatt_db_attribute *attrib,
gatt_db_attribute_write_result(attrib, id, 0);
}
+/* Track the Ready / Overwritten CCC value so their notifications are sent with
+ * the transport the client subscribed to (notification vs indication). Both
+ * characteristics support NOTIFY and INDICATE; a headless RAS server must honor
+ * whichever the client enabled (e.g. RCO/BV-05-C uses Indication, BV-06-C uses
+ * Notification). */
+static void ras_ready_overwritten_ccc_write_cb(struct gatt_db_attribute *attrib,
+ unsigned int id, uint16_t offset,
+ const uint8_t *value, size_t len,
+ uint8_t opcode, struct bt_att *att,
+ void *user_data)
+{
+ struct ras *ras = user_data;
+ uint16_t ccc_value;
+
+ if (!ras) {
+ gatt_db_attribute_write_result(attrib, id,
+ BT_ATT_ERROR_UNLIKELY);
+ return;
+ }
+
+ if (offset) {
+ gatt_db_attribute_write_result(attrib, id,
+ BT_ATT_ERROR_INVALID_OFFSET);
+ return;
+ }
+
+ if (len != 2) {
+ gatt_db_attribute_write_result(attrib, id,
+ BT_ATT_ERROR_INVALID_ATTRIBUTE_VALUE_LEN);
+ return;
+ }
+
+ ccc_value = get_le16(value);
+
+ if (ccc_value != 0x0000 && ccc_value != 0x0001 &&
+ ccc_value != 0x0002 && ccc_value != 0x0003) {
+ gatt_db_attribute_write_result(attrib, id,
+ BT_ERROR_WRITE_REQUEST_REJECTED);
+ return;
+ }
+
+ if (attrib == ras->ready_ccc)
+ ras->ready_ccc_value = ccc_value;
+ else if (attrib == ras->overwritten_ccc)
+ ras->overwritten_ccc_value = ccc_value;
+
+ gatt_db_attribute_write_result(attrib, id, 0);
+}
+
+/* Pick the notify transport for Ranging Data Ready / Ranging Data Overwritten
+ * from a tracked CCC value. RAS 3.4.2 and 3.5.2 require indications when both
+ * transports are enabled (0x0003), so notification is used only when it is the
+ * sole enabled transport. The On-demand and Real-time Ranging Data
+ * characteristics take the opposite rule and must not use this helper.
+ */
+static bool ras_ccc_use_indication(uint16_t ccc_value)
+{
+ return !(ccc_value & 0x0001) || (ccc_value & 0x0002);
+}
+
+/* Pick the notify transport for On-demand / Real-time Ranging Data. RAS 3.2.2
+ * and 3.2.3.1 require notifications when both transports are enabled (0x0003),
+ * so indication is used only when it is the sole enabled transport.
+ */
+static bool ras_data_ccc_use_indication(uint16_t ccc_value)
+{
+ return !(ccc_value & 0x0001);
+}
+
/* Service registration - store attribute pointers */
static struct ras *register_ras_service(struct gatt_db *db)
{
@@ -1470,8 +1555,10 @@ static struct ras *register_ras_service(struct gatt_db *db)
ras_data_ready_read_cb, NULL,
ras);
- ras->ready_ccc = gatt_db_service_add_ccc(ras->svc,
- BT_ATT_PERM_READ | BT_ATT_PERM_WRITE);
+ ras->ready_ccc = gatt_db_service_add_ccc_custom(ras->svc,
+ BT_ATT_PERM_READ | BT_ATT_PERM_WRITE,
+ ras_ready_overwritten_ccc_write_cb,
+ ras);
/* RAS Data Overwritten */
bt_uuid16_create(&uuid, RAS_DATA_OVERWRITTEN_UUID);
@@ -1485,8 +1572,10 @@ static struct ras *register_ras_service(struct gatt_db *db)
ras_data_overwritten_read_cb,
NULL, ras);
- ras->overwritten_ccc = gatt_db_service_add_ccc(ras->svc,
- BT_ATT_PERM_READ | BT_ATT_PERM_WRITE);
+ ras->overwritten_ccc = gatt_db_service_add_ccc_custom(ras->svc,
+ BT_ATT_PERM_READ | BT_ATT_PERM_WRITE,
+ ras_ready_overwritten_ccc_write_cb,
+ ras);
/* Activate the service */
gatt_db_service_set_active(ras->svc, true);
@@ -1702,12 +1791,17 @@ static void ras_ondemand_cache_clear(struct bt_rap *rap)
if (!rap)
return;
+ if (rap->cache.send_id)
+ timeout_remove(rap->cache.send_id);
+
free(rap->cache.data.iov_base);
rap->cache.data.iov_base = NULL;
rap->cache.data.iov_len = 0;
rap->cache.valid = false;
rap->cache.counter = 0;
rap->cache.send_index = 0;
+ rap->cache.send_id = 0;
+ rap->cache.sending = false;
}
static void ras_ondemand_cache_store(struct bt_rap *rap,
@@ -1735,9 +1829,14 @@ static void ras_send_ranging_data_ready(struct bt_rap *rap, struct ras *ras,
if (!rap || !ras || !ras->ready_chrc)
return;
+ ras->ready_counter = counter;
put_le16(counter, value);
- gatt_db_attribute_notify(ras->ready_chrc, value, sizeof(value),
- bt_rap_get_att(rap));
+ if (ras_ccc_use_indication(ras->ready_ccc_value))
+ gatt_db_attribute_indicate(ras->ready_chrc, value,
+ sizeof(value), bt_rap_get_att(rap));
+ else
+ gatt_db_attribute_notify(ras->ready_chrc, value,
+ sizeof(value), bt_rap_get_att(rap));
}
static void ras_send_ranging_data_overwritten(struct bt_rap *rap,
@@ -1748,9 +1847,14 @@ static void ras_send_ranging_data_overwritten(struct bt_rap *rap,
if (!rap || !ras || !ras->overwritten_chrc)
return;
+ ras->overwritten_counter = counter;
put_le16(counter, value);
- gatt_db_attribute_notify(ras->overwritten_chrc, value, sizeof(value),
- bt_rap_get_att(rap));
+ if (ras_ccc_use_indication(ras->overwritten_ccc_value))
+ gatt_db_attribute_indicate(ras->overwritten_chrc, value,
+ sizeof(value), bt_rap_get_att(rap));
+ else
+ gatt_db_attribute_notify(ras->overwritten_chrc, value,
+ sizeof(value), bt_rap_get_att(rap));
}
static void ras_cp_send_response_code(struct bt_rap *rap, struct ras *ras,
@@ -1761,8 +1865,9 @@ static void ras_cp_send_response_code(struct bt_rap *rap, struct ras *ras,
if (!rap || !ras || !ras->cp_chrc)
return;
- gatt_db_attribute_notify(ras->cp_chrc, value, sizeof(value),
- bt_rap_get_att(rap));
+ DBG(rap, "CP send Response Code: rsp=0x%02x", response_code);
+ gatt_db_attribute_indicate(ras->cp_chrc, value, sizeof(value),
+ bt_rap_get_att(rap));
}
static void ras_cp_send_complete_ranging_data(struct bt_rap *rap,
@@ -1776,18 +1881,51 @@ static void ras_cp_send_complete_ranging_data(struct bt_rap *rap,
value[0] = RAS_CP_RSP_COMPLETE_RANGING_DATA;
put_le16(counter, value + 1);
- gatt_db_attribute_notify(ras->cp_chrc, value, sizeof(value),
- bt_rap_get_att(rap));
+ gatt_db_attribute_indicate(ras->cp_chrc, value, sizeof(value),
+ bt_rap_get_att(rap));
}
-static void send_ondemand_segment_data(struct bt_rap *rap, struct ras *ras)
+static bool send_ondemand_segment_data_cb(void *user_data);
+
+/*
+ * Confirmation callback for an on-demand ranging-data Indication segment.
+ * Invoked (opcode == BT_ATT_OP_HANDLE_CONF) when the client confirms the
+ * segment. Only then do we send the next segment (confirm-driven flow control),
+ * so at most one indication is in-flight and an Abort (which clears
+ * cache.sending) stops the transfer immediately.
+ */
+static void ondemand_ind_conf_cb(uint8_t opcode, const void *pdu,
+ uint16_t length, void *user_data)
{
+ struct bt_rap *rap = user_data;
+ struct ras *ras = rap && rap->lrapdb ? rap->lrapdb->ras : NULL;
+
+ if (!rap || !ras || !rap->cache.valid || !rap->cache.sending)
+ return;
+
+ /* All data sent and confirmed: finish with Complete Ranging Data. */
+ if (rap->cache.send_index >= rap->cache.data.iov_len) {
+ rap->cache.sending = false;
+ ras_cp_send_complete_ranging_data(rap, ras, rap->cache.counter);
+ return;
+ }
+
+ /* Otherwise send the next segment. */
+ send_ondemand_segment_data_cb(rap);
+}
+
+static bool send_ondemand_segment_data_cb(void *user_data)
+{
+ struct bt_rap *rap = user_data;
+ struct ras *ras = rap && rap->lrapdb ? rap->lrapdb->ras : NULL;
uint16_t value_max;
const uint16_t header_len = ras_segment_header_size;
uint16_t raw_payload_size;
- if (!rap || !ras || !rap->cache.valid)
- return;
+ if (!rap || !ras || !rap->cache.valid || !rap->cache.sending)
+ return false;
+
+ rap->cache.send_id = 0;
value_max = ras_att_value_payload_max(rap);
@@ -1801,17 +1939,13 @@ static void send_ondemand_segment_data(struct bt_rap *rap, struct ras *ras)
*/
ras_cp_send_response_code(rap, ras,
RAS_CP_RSP_CODE_SERVER_BUSY);
- return;
+ rap->cache.sending = false;
+ return false;
}
raw_payload_size = (uint16_t)(value_max - header_len);
- rap->cache.send_index = 0;
- rap->cache.seg_hdr.first_segment = 1;
- rap->cache.seg_hdr.last_segment = 0;
- rap->cache.seg_hdr.rolling_segment_counter = 0;
-
- while (true) {
+ {
size_t total_len = rap->cache.data.iov_len;
size_t index = rap->cache.send_index;
size_t unsent_data_size = total_len - index;
@@ -1833,7 +1967,8 @@ static void send_ondemand_segment_data(struct bt_rap *rap, struct ras *ras)
DBG(rap, "OOM (%u)", seg_len);
ras_cp_send_response_code(rap, ras,
RAS_CP_RSP_CODE_SERVER_BUSY);
- return;
+ rap->cache.sending = false;
+ return false;
}
wr += (uint16_t)
@@ -1844,6 +1979,88 @@ static void send_ondemand_segment_data(struct bt_rap *rap, struct ras *ras)
copy_size);
wr += copy_size;
+ /*
+ * On-demand transfer flow control by transport:
+ *
+ * - Indication: send one segment as an ATT Handle Value
+ * Indication via bt_att_send() and advance/schedule the NEXT
+ * segment only from its confirmation callback
+ * (ondemand_ind_conf_cb). This keeps at most one segment
+ * in-flight, so an Abort Operation that clears cache.sending
+ * actually stops the transfer instead of a large backlog
+ * draining out of the att indication queue.
+ * - Notification: fire-and-forget, so there is no confirmation
+ * to gate on. Notifications go to the att write queue, which
+ * pick_next_send_op() drains ahead of the request queue and
+ * without flow control of its own, so queueing the whole
+ * transfer at once would starve incoming ATT operations until
+ * the last segment is written. Queue the next segment from
+ * the next event loop iteration instead, which keeps one
+ * segment outstanding.
+ *
+ * The delay has to be non-zero for a client to be able to
+ * abort a transfer it no longer wants. A zero timeout yields
+ * the event loop but does not wait, since poll() returns at
+ * once when nothing is pending on the socket, so a Control
+ * Point write is only seen here once it has already arrived,
+ * and the transfer can be over by then.
+ *
+ * What the peer gets is the transfer time, segments times this
+ * delay, and it has to cover a connection event for an Abort
+ * Operation to reach us at all. A short transfer is therefore
+ * the constraining case, not a long one, and the rate at which
+ * the controller reports ranging data does not help: the
+ * segments of one subevent still go out back to back.
+ * RAS/SR/SPE/BI-05-C, which writes an invalid Abort Operation
+ * mid-transfer and expects Invalid Parameter rather than
+ * Complete Ranging Data, fails at 0 ms and passes at 5 ms.
+ *
+ * The delay also bounds a transfer, so keep it short: at a
+ * 247 byte MTU a 13 KB procedure is 57 segments.
+ */
+ if (ras_data_ccc_use_indication(ras->ondemand_ccc_value)) {
+ uint16_t vhandle =
+ gatt_db_attribute_get_handle(
+ gatt_db_attribute_get_value(
+ ras->ondemand_chrc));
+ uint8_t *pdu = malloc(2 + wr);
+ unsigned int id = 0;
+
+ if (pdu) {
+ put_le16(vhandle, pdu);
+ memcpy(pdu + 2, seg, wr);
+ id = bt_att_send(bt_rap_get_att(rap),
+ BT_ATT_OP_HANDLE_IND,
+ pdu, 2 + wr,
+ ondemand_ind_conf_cb,
+ rap, NULL);
+ free(pdu);
+ }
+
+ free(seg);
+
+ if (!id) {
+ DBG(rap, "Failed to send on-demand indication");
+ ras_cp_send_response_code(rap, ras,
+ RAS_CP_RSP_CODE_SERVER_BUSY);
+ rap->cache.sending = false;
+ return false;
+ }
+
+ /* Advance now; last-segment completion and scheduling
+ * of the next segment happen in the confirmation cb.
+ */
+ rap->cache.send_index += copy_size;
+ rap->cache.seg_hdr.first_segment = 0;
+ rap->cache.seg_hdr.rolling_segment_counter =
+ (uint8_t)((rap->cache.seg_hdr
+ .rolling_segment_counter + 1) & 0x3F);
+ return false;
+ }
+
+ /* Reached whenever the client enabled notifications, so
+ * gatt_db_attribute_notify() picks the notification transport.
+ */
ok = gatt_db_attribute_notify(ras->ondemand_chrc, seg, wr,
bt_rap_get_att(rap));
@@ -1853,7 +2070,8 @@ static void send_ondemand_segment_data(struct bt_rap *rap, struct ras *ras)
DBG(rap, "Failed to send on-demand RAS notification");
ras_cp_send_response_code(rap, ras,
RAS_CP_RSP_CODE_SERVER_BUSY);
- return;
+ rap->cache.sending = false;
+ return false;
}
rap->cache.send_index += copy_size;
@@ -1863,11 +2081,36 @@ static void send_ondemand_segment_data(struct bt_rap *rap, struct ras *ras)
.rolling_segment_counter + 1) & 0x3F);
if (rap->cache.seg_hdr.last_segment) {
+ rap->cache.sending = false;
ras_cp_send_complete_ranging_data(rap, ras,
- rap->cache.counter);
- return;
+ rap->cache.counter);
+ return false;
}
}
+
+ rap->cache.send_id = timeout_add(RAS_SEGMENT_NFY_PACE_MS,
+ send_ondemand_segment_data_cb,
+ rap, NULL);
+ if (!rap->cache.send_id) {
+ ras_cp_send_response_code(rap, ras, RAS_CP_RSP_CODE_SERVER_BUSY);
+ rap->cache.sending = false;
+ }
+
+ return false;
+}
+
+static void send_ondemand_segment_data(struct bt_rap *rap, struct ras *ras)
+{
+ if (!rap || !ras || !rap->cache.valid || rap->cache.sending)
+ return;
+
+ rap->cache.sending = true;
+ rap->cache.send_index = 0;
+ rap->cache.seg_hdr.first_segment = 1;
+ rap->cache.seg_hdr.last_segment = 0;
+ rap->cache.seg_hdr.rolling_segment_counter = 0;
+
+ send_ondemand_segment_data_cb(rap);
}
static void send_ras_segment_data(struct bt_rap *rap,
@@ -1956,14 +2199,28 @@ static void send_ras_segment_data(struct bt_rap *rap,
wr += copy_size;
/* Try sending to real-time characteristic */
- if (ras->realtime_chrc)
- ok = gatt_db_attribute_notify(ras->realtime_chrc, seg,
- wr, bt_rap_get_att(rap));
+ if (ras->realtime_chrc) {
+ if (ras_data_ccc_use_indication(ras->realtime_ccc_value))
+ ok = gatt_db_attribute_indicate(
+ ras->realtime_chrc, seg, wr,
+ bt_rap_get_att(rap));
+ else
+ ok = gatt_db_attribute_notify(
+ ras->realtime_chrc, seg, wr,
+ bt_rap_get_att(rap));
+ }
/* Try sending to on-demand characteristic */
- if (ras->ondemand_chrc && ok)
- ok = gatt_db_attribute_notify(ras->ondemand_chrc, seg,
- wr, bt_rap_get_att(rap));
+ if (ras->ondemand_chrc && ok) {
+ if (ras_data_ccc_use_indication(ras->ondemand_ccc_value))
+ ok = gatt_db_attribute_indicate(
+ ras->ondemand_chrc, seg, wr,
+ bt_rap_get_att(rap));
+ else
+ ok = gatt_db_attribute_notify(
+ ras->ondemand_chrc, seg, wr,
+ bt_rap_get_att(rap));
+ }
free(seg);
@@ -3087,7 +3344,7 @@ static bool ras_cp_timeout_cb(void *user_data)
if (ras) {
DBG(rap, "RAS CP response timed out (pending_op=%d "
- "counter=%u) - resetting", ras->client.pending_op,
+ "counter=%u)", ras->client.pending_op,
ras->client.pending_counter);
/* The glib source is about to be destroyed on return; avoid
@@ -3113,11 +3370,24 @@ static void ras_cp_arm_timeout(struct bt_rap *rap, struct ras *ras)
ras_cp_timeout_cb, rap, NULL);
}
+static void ras_cp_arm_segment_timeout(struct bt_rap *rap, struct ras *ras)
+{
+ if (!ras)
+ return;
+
+ if (ras->client.cp_timeout_id)
+ timeout_remove(ras->client.cp_timeout_id);
+
+ ras->client.cp_timeout_id = timeout_add(RAS_SEGMENT_TIMEOUT,
+ ras_cp_timeout_cb, rap, NULL);
+}
+
static bool ras_cp_write_op(struct bt_rap *rap, struct ras *ras,
uint8_t opcode, uint16_t counter)
{
uint8_t value[3];
uint16_t value_handle;
+ uint16_t length = 1;
if (!rap || !ras || !ras->cp_chrc || !rap->client)
return false;
@@ -3128,10 +3398,11 @@ static bool ras_cp_write_op(struct bt_rap *rap, struct ras *ras,
value[0] = opcode;
put_le16(counter, value + 1);
+ length = sizeof(value);
return bt_gatt_client_write_without_response(rap->client,
value_handle, false,
- value, sizeof(value));
+ value, length);
}
static bool ras_cp_send_get_ranging_data(struct bt_rap *rap, struct ras *ras,
@@ -3776,11 +4047,15 @@ static void ras_segment_notify_cb(struct bt_rap *rap, uint16_t value_handle,
void *user_data)
{
const char *label = user_data ? user_data : "real-time";
+ struct ras *ras = rap_get_ras(rap);
DBG(rap, "Received %s notification: handle=0x%04x len=%u",
label, value_handle, length);
ras_reassemble_segment_notify(rap, value, length);
+
+ if (ras && ras->client.pending_op == RAS_CP_PENDING_GET)
+ ras_cp_arm_segment_timeout(rap, ras);
}
/* Initiator role: reacts to a Ranging Data Ready notification/indication
@@ -3896,11 +4171,22 @@ static void ras_cp_response_notify_cb(struct bt_rap *rap,
DBG(rap, "RAS CP response code=0x%02x (pending_op=%d "
"counter=%u)", code, ras->client.pending_op,
ras->client.pending_counter);
- ras_ondemand_client_reset(ras);
+ switch (code) {
+ case RAS_CP_RSP_CODE_SUCCESS:
+ case RAS_CP_RSP_CODE_OPCODE_NOT_SUPPORTED:
+ case RAS_CP_RSP_CODE_INVALID_PARAMETER:
+ case RAS_CP_RSP_CODE_PROCEDURE_NOT_COMPLETED:
+ case RAS_CP_RSP_CODE_SERVER_BUSY:
+ case RAS_CP_RSP_CODE_NO_RECORDS_FOUND:
+ ras_ondemand_client_reset(ras);
+ break;
+ default:
+ DBG(rap, "Ignoring RFU RAS CP response code 0x%02x", code);
+ break;
+ }
break;
default:
DBG(rap, "Unknown RAS CP response opcode 0x%02x", value[0]);
- ras_ondemand_client_reset(ras);
break;
}
}
--
2.43.0
^ permalink raw reply related [flat|nested] 15+ messages in thread* [PATCH 04/11] shared/rap: Add RAS Control Point Abort Operation
2026-10-01 13:18 [PATCH 00/11] Ranging Profile and Ranging Service PTS fixes Cheng Jiang
` (2 preceding siblings ...)
2026-10-01 13:18 ` [PATCH 03/11] shared/rap: Honor CCCD transport for RAS on-demand ranging data Cheng Jiang
@ 2026-10-01 13:18 ` Cheng Jiang
2026-10-01 13:18 ` [PATCH 05/11] shared/rap: Shorten the requester Control Point response timeout Cheng Jiang
` (6 subsequent siblings)
10 siblings, 0 replies; 15+ messages in thread
From: Cheng Jiang @ 2026-10-01 13:18 UTC (permalink / raw)
To: linux-bluetooth
Cc: jinwang.li, wei.deng, xiuzhuo.shang, shuai.zhang, Cheng Jiang
Implement the Abort_Operation procedure (Op Code 0x03) so a client can
cancel an On-demand Ranging Data transfer that is already in progress,
and advertise it through bit 2 of the RAS Features characteristic.
Abort clears the sending state and removes the pending segment timer.
Since the transfer keeps at most one segment outstanding, dropping that
timer stops the transfer instead of letting a queued backlog drain out.
The Control Point busy check lets Abort through while every other Op Code
is answered with Server Busy, so an abort is accepted mid-transfer.
On the requester side, an Abort is written when a Get_Ranging_Data
request times out and the peer advertises Abort support, so the responder
does not keep sending data for a request the requester gave up on.
The unit test Features value changes to 0x05 accordingly.
Assisted-by: Claude:claude-opus-4.8
---
src/shared/rap.c | 35 ++++++++++++++++++++++++++++++-----
unit/test-rap.c | 6 +++---
2 files changed, 33 insertions(+), 8 deletions(-)
diff --git a/src/shared/rap.c b/src/shared/rap.c
index a794de044..b45bfc9e6 100644
--- a/src/shared/rap.c
+++ b/src/shared/rap.c
@@ -312,8 +312,8 @@ struct cstracker {
enum ras_cp_opcode {
RAS_CP_OP_GET_RANGING_DATA = 0x00,
RAS_CP_OP_ACK_RANGING_DATA = 0x01,
+ RAS_CP_OP_ABORT_OPERATION = 0x03,
/* 0x02 Retrieve_Lost_Ranging_Data_Segments,
- * 0x03 Abort_Operation,
* 0x04 Set_Filter - optional, not implemented
*/
};
@@ -366,6 +366,7 @@ struct ras_ondemand_client {
* peer never sends a Control Point response.
*/
unsigned int cp_timeout_id;
+ bool abort_operation;
};
/* Ranging Service context */
@@ -1196,7 +1197,7 @@ static void ras_features_read_cb(struct gatt_db_attribute *attrib,
{
struct ras *ras = user_data;
/* Real-time ranging and Abort Operation are supported. */
- uint8_t value[4] = { 0x01, 0x00, 0x00, 0x00 };
+ uint8_t value[4] = { 0x05, 0x00, 0x00, 0x00 };
if (ras)
bt_rap_get_session(att, ras->rapdb->db);
@@ -1244,7 +1245,7 @@ static void ras_control_point_write_cb(struct gatt_db_attribute *attrib,
return;
}
- if (rap->cache.sending) {
+ if (rap->cache.sending && value[0] != RAS_CP_OP_ABORT_OPERATION) {
ras_cp_send_response_code(rap, ras, RAS_CP_RSP_CODE_SERVER_BUSY);
goto done;
}
@@ -1269,6 +1270,22 @@ static void ras_control_point_write_cb(struct gatt_db_attribute *attrib,
send_ondemand_segment_data(rap, ras);
break;
+ case RAS_CP_OP_ABORT_OPERATION:
+ if (len != 1) {
+ ras_cp_send_response_code(rap, ras,
+ RAS_CP_RSP_CODE_INVALID_PARAMETER);
+ break;
+ }
+
+ if (rap->cache.sending) {
+ if (rap->cache.send_id)
+ timeout_remove(rap->cache.send_id);
+ rap->cache.send_id = 0;
+ rap->cache.sending = false;
+ }
+
+ ras_cp_send_response_code(rap, ras, RAS_CP_RSP_CODE_SUCCESS);
+ break;
case RAS_CP_OP_ACK_RANGING_DATA:
if (len != 3) {
ras_cp_send_response_code(rap, ras,
@@ -3352,6 +3369,10 @@ static bool ras_cp_timeout_cb(void *user_data)
* on it.
*/
ras->client.cp_timeout_id = 0;
+ if (ras->client.pending_op == RAS_CP_PENDING_GET &&
+ ras->client.abort_operation)
+ ras_cp_write_op(rap, ras, RAS_CP_OP_ABORT_OPERATION,
+ ras->client.pending_counter);
ras_ondemand_client_reset(ras);
}
@@ -3397,8 +3418,10 @@ static bool ras_cp_write_op(struct bt_rap *rap, struct ras *ras,
return false;
value[0] = opcode;
- put_le16(counter, value + 1);
- length = sizeof(value);
+ if (opcode != RAS_CP_OP_ABORT_OPERATION) {
+ put_le16(counter, value + 1);
+ length = sizeof(value);
+ }
return bt_gatt_client_write_without_response(rap->client,
value_handle, false,
@@ -4265,6 +4288,8 @@ static void read_ras_features(struct bt_rap *rap, bool success,
supports_realtime = (features & 0x01) != 0;
retrieve_lost = (features & 0x02) != 0;
abort_operation = (features & 0x04) != 0;
+ if (ras)
+ ras->client.abort_operation = abort_operation;
DBG(rap, "RAS Features - Real-time: %s, Retrieve Lost: %s, Abort: %s",
supports_realtime ? "Yes" : "No",
diff --git a/unit/test-rap.c b/unit/test-rap.c
index 5c6033a4e..349d06840 100644
--- a/unit/test-rap.c
+++ b/unit/test-rap.c
@@ -802,11 +802,11 @@ const struct test_config_rap cfg_cggit_discovery = {
* Handle: 0x0003 (RAS Features value handle)
*
* ATT: Read Response (0x0b) len 5
- * Value: 0x01 0x00 0x00 0x00
+ * Value: 0x05 0x00 0x00 0x00
* Feature bits:
* Bit 0: Real-time ranging (1 = supported)
* Bit 1: Retrieve stored results (0 = not supported)
- * Bit 2: Abort operation (0 = not supported)
+ * Bit 2: Abort operation (1 = supported)
*
* Note: The RAS Features characteristic is registered with
* BT_ATT_PERM_READ | BT_ATT_PERM_READ_ENCRYPT. Since the test sets
@@ -817,7 +817,7 @@ const struct test_config_rap cfg_cggit_discovery = {
#define ATT_READ_RAS_FEATURES \
IOV_DATA(0x0a, 0x03, 0x00), \
- IOV_DATA(0x0b, 0x01, 0x00, 0x00, 0x00)
+ IOV_DATA(0x0b, 0x05, 0x00, 0x00, 0x00)
#define RAS_SR_RCO_BV_01_C \
ATT_EXCHANGE_MTU, \
--
2.43.0
^ permalink raw reply related [flat|nested] 15+ messages in thread* [PATCH 05/11] shared/rap: Shorten the requester Control Point response timeout
2026-10-01 13:18 [PATCH 00/11] Ranging Profile and Ranging Service PTS fixes Cheng Jiang
` (3 preceding siblings ...)
2026-10-01 13:18 ` [PATCH 04/11] shared/rap: Add RAS Control Point Abort Operation Cheng Jiang
@ 2026-10-01 13:18 ` Cheng Jiang
2026-10-01 13:18 ` [PATCH 06/11] shared/rap: Add real-time ranging data delivery on the responder Cheng Jiang
` (5 subsequent siblings)
10 siblings, 0 replies; 15+ messages in thread
From: Cheng Jiang @ 2026-10-01 13:18 UTC (permalink / raw)
To: linux-bluetooth
Cc: jinwang.li, wei.deng, xiuzhuo.shang, shuai.zhang, Cheng Jiang
RAP 4.5.4.1 gives the Requester two deadlines once it has written Get
Ranging Data, both enforced by sending Abort Operation: no On-demand
Ranging Data at all within 5 seconds of the command
(RAP/REQ/ORD/BI-01-C), and no following segment within 1 second of the
previous one (RAP/REQ/ORD/BI-02-C).
The watchdog the requester arms while waiting for a Control Point
response covered the first of those, but ran for 10 seconds, so it
would abort well after the specified deadline. Arm it for 5 seconds
instead. The per-segment deadline is handled separately by
ras_cp_arm_segment_timeout(), which uses RAS_SEGMENT_TIMEOUT.
Assisted-by: Claude:claude-opus-4.8
---
src/shared/rap.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/shared/rap.c b/src/shared/rap.c
index b45bfc9e6..7c9a439a5 100644
--- a/src/shared/rap.c
+++ b/src/shared/rap.c
@@ -32,7 +32,7 @@
* response for. Guards against a lost/dropped notification leaving
* ras_ondemand_client permanently stuck waiting.
*/
-#define RAS_CP_RESPONSE_TIMEOUT (10 * 1000)
+#define RAS_CP_RESPONSE_TIMEOUT (5 * 1000)
#define RAS_SEGMENT_TIMEOUT (1 * 1000)
/* Delay between On-demand ranging-data notification segments, see
* send_ondemand_segment_data_cb().
--
2.43.0
^ permalink raw reply related [flat|nested] 15+ messages in thread* [PATCH 06/11] shared/rap: Add real-time ranging data delivery on the responder
2026-10-01 13:18 [PATCH 00/11] Ranging Profile and Ranging Service PTS fixes Cheng Jiang
` (4 preceding siblings ...)
2026-10-01 13:18 ` [PATCH 05/11] shared/rap: Shorten the requester Control Point response timeout Cheng Jiang
@ 2026-10-01 13:18 ` Cheng Jiang
2026-10-01 13:18 ` [PATCH 07/11] shared/rap: Add requester ranging data subscription API Cheng Jiang
` (4 subsequent siblings)
10 siblings, 0 replies; 15+ messages in thread
From: Cheng Jiang @ 2026-10-01 13:18 UTC (permalink / raw)
To: linux-bluetooth
Cc: jinwang.li, wei.deng, xiuzhuo.shang, shuai.zhang, Cheng Jiang
Implement the RAS responder side of Real-time Ranging Data. Completed
ranging-data segments are queued in a per-connection FIFO and delivered
on the Real-time Ranging Data characteristic, honoring the transport
selected by the client's CCCD: notifications are paced with a fixed
inter-segment interval, while indications are driven by ATT confirmations
so the next segment is only sent once the previous one is acknowledged.
The pending real-time segment queue is cleared when a new CS procedure
starts, so stale segments from an aborted or superseded procedure are
never delivered to the peer.
Assisted-by: Claude:claude-opus-4.8
---
src/shared/rap.c | 175 +++++++++++++++++++++++++++++++++++------------
1 file changed, 132 insertions(+), 43 deletions(-)
diff --git a/src/shared/rap.c b/src/shared/rap.c
index 7c9a439a5..b1108ad68 100644
--- a/src/shared/rap.c
+++ b/src/shared/rap.c
@@ -419,6 +419,22 @@ struct bt_rap_db {
struct ras *ras;
};
+struct ras_realtime_segment {
+ uint8_t *value;
+ uint16_t len;
+};
+
+static void ras_realtime_segment_free(void *data)
+{
+ struct ras_realtime_segment *segment = data;
+
+ if (!segment)
+ return;
+
+ free(segment->value);
+ free(segment);
+}
+
struct bt_rap {
int ref_count;
struct bt_rap_db *lrapdb;
@@ -460,6 +476,11 @@ struct bt_rap {
* centrals cannot evict or receive each other's cached data.
*/
struct ras_ondemand_cache cache;
+
+ /* Serialized real-time segments await transport-paced delivery. */
+ struct queue *realtime_segments;
+ unsigned int realtime_send_id;
+ bool realtime_indicating;
};
static struct queue *rap_db;
@@ -1019,6 +1040,9 @@ static void rap_free(void *data)
rap_db_free(rap->rrapdb);
free(rap->cache.data.iov_base);
+ if (rap->realtime_send_id)
+ timeout_remove(rap->realtime_send_id);
+ queue_destroy(rap->realtime_segments, ras_realtime_segment_free);
if (rap->resptracker) {
free(rap->resptracker->segment_data.iov_base);
@@ -2130,6 +2154,91 @@ static void send_ondemand_segment_data(struct bt_rap *rap, struct ras *ras)
send_ondemand_segment_data_cb(rap);
}
+static bool send_realtime_segment_cb(void *user_data);
+
+static void ras_realtime_segments_clear(struct bt_rap *rap)
+{
+ if (!rap)
+ return;
+
+ if (rap->realtime_send_id) {
+ timeout_remove(rap->realtime_send_id);
+ rap->realtime_send_id = 0;
+ }
+
+ queue_destroy(rap->realtime_segments, ras_realtime_segment_free);
+ rap->realtime_segments = queue_new();
+}
+
+static void realtime_ind_conf_cb(uint8_t opcode, const void *pdu,
+ uint16_t length, void *user_data)
+{
+ struct bt_rap *rap = user_data;
+
+ if (!rap)
+ return;
+
+ rap->realtime_indicating = false;
+ send_realtime_segment_cb(rap);
+}
+
+static bool send_realtime_segment_cb(void *user_data)
+{
+ struct bt_rap *rap = user_data;
+ struct ras *ras = rap && rap->lrapdb ? rap->lrapdb->ras : NULL;
+ struct ras_realtime_segment *segment;
+
+ if (!rap || !ras || !rap->realtime_segments)
+ return false;
+
+ rap->realtime_send_id = 0;
+ if (rap->realtime_indicating || queue_isempty(rap->realtime_segments))
+ return false;
+
+ segment = queue_pop_head(rap->realtime_segments);
+ if (!segment)
+ return false;
+
+ if (ras_data_ccc_use_indication(ras->realtime_ccc_value)) {
+ uint16_t vhandle = gatt_db_attribute_get_handle(
+ gatt_db_attribute_get_value(ras->realtime_chrc));
+ uint8_t *pdu = malloc(2 + segment->len);
+ unsigned int id = 0;
+
+ if (pdu) {
+ put_le16(vhandle, pdu);
+ memcpy(pdu + 2, segment->value, segment->len);
+ id = bt_att_send(bt_rap_get_att(rap), BT_ATT_OP_HANDLE_IND,
+ pdu, 2 + segment->len, realtime_ind_conf_cb,
+ rap, NULL);
+ free(pdu);
+ }
+
+ ras_realtime_segment_free(segment);
+ if (!id) {
+ DBG(rap, "Failed to send real-time indication");
+ return false;
+ }
+
+ rap->realtime_indicating = true;
+ return false;
+ }
+
+ /* Reached whenever the client enabled notifications, so
+ * gatt_db_attribute_notify() picks the notification transport.
+ */
+ if (!gatt_db_attribute_notify(ras->realtime_chrc, segment->value,
+ segment->len, bt_rap_get_att(rap)))
+ DBG(rap, "Failed to send real-time notification");
+ ras_realtime_segment_free(segment);
+
+ if (!queue_isempty(rap->realtime_segments))
+ rap->realtime_send_id = timeout_add(RAS_SEGMENT_NFY_PACE_MS,
+ send_realtime_segment_cb, rap, NULL);
+
+ return false;
+}
+
static void send_ras_segment_data(struct bt_rap *rap,
struct cs_procedure_data *proc)
{
@@ -2168,7 +2277,6 @@ static void send_ras_segment_data(struct bt_rap *rap,
uint16_t seg_len;
uint8_t *seg;
uint16_t wr = 0;
- bool ok = true;
if (index > total_len)
index = total_len;
@@ -2178,25 +2286,14 @@ static void send_ras_segment_data(struct bt_rap *rap,
if (unsent_data_size == 0)
return;
- /* Set last_segment if procedure complete or fits in segment */
- if ((proc->local_status != CS_PROC_PARTIAL_RESULTS &&
- unsent_data_size <= raw_payload_size) ||
- (proc->contains_complete_subevent_ &&
- unsent_data_size <= raw_payload_size)) {
+ /* Only the final completed procedure segment terminates the stream. */
+ if (proc->local_status != CS_PROC_PARTIAL_RESULTS &&
+ unsent_data_size <= raw_payload_size) {
proc->segmentation_header_.last_segment = 1;
} else {
proc->segmentation_header_.last_segment = 0;
}
- /* Wait for more data if needed and not last segment */
- if (unsent_data_size < raw_payload_size &&
- proc->segmentation_header_.last_segment == 0) {
- DBG(rap, "waiting for more data (unsent=%zu < "
- "payload=%u)", unsent_data_size,
- raw_payload_size);
- return;
- }
-
copy_size = (uint16_t)((unsent_data_size < raw_payload_size) ?
unsent_data_size : raw_payload_size);
seg_len = (uint16_t)(header_len + copy_size);
@@ -2215,37 +2312,25 @@ static void send_ras_segment_data(struct bt_rap *rap,
copy_size);
wr += copy_size;
- /* Try sending to real-time characteristic */
- if (ras->realtime_chrc) {
- if (ras_data_ccc_use_indication(ras->realtime_ccc_value))
- ok = gatt_db_attribute_indicate(
- ras->realtime_chrc, seg, wr,
- bt_rap_get_att(rap));
- else
- ok = gatt_db_attribute_notify(
- ras->realtime_chrc, seg, wr,
- bt_rap_get_att(rap));
- }
+ {
+ struct ras_realtime_segment *segment = new0(
+ struct ras_realtime_segment, 1);
- /* Try sending to on-demand characteristic */
- if (ras->ondemand_chrc && ok) {
- if (ras_data_ccc_use_indication(ras->ondemand_ccc_value))
- ok = gatt_db_attribute_indicate(
- ras->ondemand_chrc, seg, wr,
- bt_rap_get_att(rap));
- else
- ok = gatt_db_attribute_notify(
- ras->ondemand_chrc, seg, wr,
- bt_rap_get_att(rap));
- }
-
- free(seg);
+ if (!segment || !queue_push_tail(rap->realtime_segments,
+ segment)) {
+ free(segment);
+ free(seg);
+ DBG(rap, "Failed to queue real-time RAS segment");
+ return;
+ }
- if (!ok) {
- DBG(rap, "Failed to send RAS notification");
- return;
+ segment->value = seg;
+ segment->len = wr;
}
+ if (!rap->realtime_send_id && !rap->realtime_indicating)
+ send_realtime_segment_cb(rap);
+
/* Advance read cursor and update segmentation state */
proc->ras_raw_data_index_ += copy_size;
proc->segmentation_header_.first_segment = 0;
@@ -2621,7 +2706,10 @@ static void handle_local_subevent_result(struct bt_rap *rap,
struct cs_procedure_data *cur = resptracker->current_proc;
if (has_header_fields && cur->counter != proc_counter) {
- /* Safety: a new procedure; destroy the previous one */
+ /* A new real-time procedure supersedes queued old ranging data. */
+ if (!ras_is_ondemand_mode(rap->lrapdb->ras))
+ ras_realtime_segments_clear(rap);
+
resptracker_reset_current_proc(resptracker);
}
}
@@ -3220,6 +3308,7 @@ struct bt_rap *bt_rap_new(struct gatt_db *ldb, struct gatt_db *rdb)
rap->pending = queue_new();
rap->ready_cbs = queue_new();
rap->notify = queue_new();
+ rap->realtime_segments = queue_new();
if (!rdb)
goto done;
--
2.43.0
^ permalink raw reply related [flat|nested] 15+ messages in thread* [PATCH 07/11] shared/rap: Add requester ranging data subscription API
2026-10-01 13:18 [PATCH 00/11] Ranging Profile and Ranging Service PTS fixes Cheng Jiang
` (5 preceding siblings ...)
2026-10-01 13:18 ` [PATCH 06/11] shared/rap: Add real-time ranging data delivery on the responder Cheng Jiang
@ 2026-10-01 13:18 ` Cheng Jiang
2026-10-01 13:18 ` [PATCH 08/11] profiles/ranging: Expose requester ranging data mode over D-Bus Cheng Jiang
` (3 subsequent siblings)
10 siblings, 0 replies; 15+ messages in thread
From: Cheng Jiang @ 2026-10-01 13:18 UTC (permalink / raw)
To: linux-bluetooth
Cc: jinwang.li, wei.deng, xiuzhuo.shang, shuai.zhang, Cheng Jiang
Give the RAS requester control over how Ranging Data is received from the
peer. bt_rap_set_ranging_data_mode() picks Real-time, On-demand or neither,
together with the CCC value to write on the peer, and
bt_rap_disable_realtime_ranging() drops a Real-time subscription again.
bt_rap_set_ondemand_ranging() selects the mode to use before GATT discovery
has completed.
Setting a mode first unsubscribes whatever is currently subscribed, so the
modes do not stack, and fails when the RAS Features read has not completed
or the peer does not advertise the mode being asked for.
The requested transport applies to the RAS Control Point, Ranging Data
Ready and Ranging Data Overwritten characteristics. On-demand Ranging Data
itself stays on notifications whatever is asked for: it carries the
segmented transfer, and gating each segment on a confirmation would slow a
large one down for no benefit on the receiving side. On-demand mode also
reads Ranging Data Ready once subscribed, to pick up a counter published
before the subscription existed.
Real-time reception is guarded by the deadlines RAP 4.4.1.1 sets for the
Requester: the CCCD is disabled again if no first segment arrives within
5 seconds, or if more than 1 second passes between continuation segments,
so a peer that stops sending does not leave the subscription open.
Assisted-by: Claude:claude-opus-4.8
---
src/shared/rap.c | 270 ++++++++++++++++++++++++++++++++++++++---------
src/shared/rap.h | 21 ++++
2 files changed, 244 insertions(+), 47 deletions(-)
diff --git a/src/shared/rap.c b/src/shared/rap.c
index b1108ad68..c0305a9e3 100644
--- a/src/shared/rap.c
+++ b/src/shared/rap.c
@@ -34,6 +34,7 @@
*/
#define RAS_CP_RESPONSE_TIMEOUT (5 * 1000)
#define RAS_SEGMENT_TIMEOUT (1 * 1000)
+#define RAS_REALTIME_FIRST_SEGMENT_TIMEOUT (5 * 1000)
/* Delay between On-demand ranging-data notification segments, see
* send_ondemand_segment_data_cb().
*/
@@ -470,6 +471,15 @@ struct bt_rap {
* bt_rap_attach() to take effect.
*/
bool ondemand_mode;
+ uint16_t realtime_ccc_value;
+ uint16_t ondemand_ccc_value;
+ unsigned int realtime_notify_id;
+ unsigned int realtime_timeout_id;
+ unsigned int ondemand_notify_id;
+ unsigned int cp_notify_id;
+ unsigned int ready_notify_id;
+ unsigned int overwritten_notify_id;
+ uint32_t ras_features;
/* Responder role: this connection's on-demand ranging data cache.
* Per-connection (not on the shared struct ras) so that concurrent
@@ -957,6 +967,15 @@ void bt_rap_detach(struct bt_rap *rap)
bt_gatt_client_idle_unregister(rap->client, rap->idle_id);
bt_gatt_client_unref(rap->client);
rap->client = NULL;
+ if (rap->realtime_timeout_id) {
+ timeout_remove(rap->realtime_timeout_id);
+ rap->realtime_timeout_id = 0;
+ }
+ rap->realtime_notify_id = 0;
+ rap->ondemand_notify_id = 0;
+ rap->cp_notify_id = 0;
+ rap->ready_notify_id = 0;
+ rap->overwritten_notify_id = 0;
bt_att_unregister_disconnect(rap->att, rap->disconn_id);
rap->att = NULL;
@@ -3397,6 +3416,7 @@ static void rap_notify_destroy(void *data)
static unsigned int bt_rap_register_notify(struct bt_rap *rap,
uint16_t value_handle,
+ uint16_t ccc_value,
rap_notify_t func,
void *user_data)
{
@@ -3409,9 +3429,13 @@ static unsigned int bt_rap_register_notify(struct bt_rap *rap,
DBG(rap, "register for notifications");
- notify->id = bt_gatt_client_register_notify(rap->client,
- value_handle, ras_register,
- rap_notify, notify,
+ if (ccc_value)
+ notify->id = bt_gatt_client_register_notify_with_ccc(rap->client,
+ value_handle, ccc_value, ras_register,
+ rap_notify, notify, rap_notify_destroy);
+ else
+ notify->id = bt_gatt_client_register_notify(rap->client,
+ value_handle, ras_register, rap_notify, notify,
rap_notify_destroy);
if (!notify->id) {
DBG(rap, "Unable to register for notifications");
@@ -3424,6 +3448,85 @@ static unsigned int bt_rap_register_notify(struct bt_rap *rap,
return notify->id;
}
+bool bt_rap_disable_realtime_ranging(struct bt_rap *rap)
+{
+ unsigned int notify_id;
+
+ if (!rap || !rap->client)
+ return false;
+
+ if (rap->realtime_timeout_id) {
+ timeout_remove(rap->realtime_timeout_id);
+ rap->realtime_timeout_id = 0;
+ }
+
+ if (!rap->realtime_notify_id)
+ return true;
+
+ notify_id = rap->realtime_notify_id;
+ rap->realtime_notify_id = 0;
+
+ return bt_gatt_client_unregister_notify(rap->client, notify_id);
+}
+
+static bool ras_realtime_timeout_cb(void *user_data)
+{
+ struct bt_rap *rap = user_data;
+
+ rap->realtime_timeout_id = 0;
+ DBG(rap, "Real-time Ranging Data segment timeout; disabling CCCD");
+ bt_rap_disable_realtime_ranging(rap);
+
+ return false;
+}
+
+static void ras_realtime_arm_timeout(struct bt_rap *rap, unsigned int timeout)
+{
+ if (rap->realtime_timeout_id)
+ timeout_remove(rap->realtime_timeout_id);
+
+ rap->realtime_timeout_id = timeout_add(timeout, ras_realtime_timeout_cb,
+ rap, NULL);
+}
+
+static void ras_realtime_cancel_timeout(struct bt_rap *rap)
+{
+ if (!rap->realtime_timeout_id)
+ return;
+
+ timeout_remove(rap->realtime_timeout_id);
+ rap->realtime_timeout_id = 0;
+}
+
+static bool bt_rap_unregister_notify(struct bt_rap *rap, unsigned int *id)
+{
+ unsigned int notify_id;
+
+ if (!*id)
+ return true;
+
+ notify_id = *id;
+ *id = 0;
+ return bt_gatt_client_unregister_notify(rap->client, notify_id);
+}
+
+static bool bt_rap_disable_ranging_data(struct bt_rap *rap)
+{
+ bool success = true;
+
+ if (!rap || !rap->client)
+ return false;
+
+ ras_realtime_cancel_timeout(rap);
+
+ success &= bt_rap_unregister_notify(rap, &rap->realtime_notify_id);
+ success &= bt_rap_unregister_notify(rap, &rap->ondemand_notify_id);
+ success &= bt_rap_unregister_notify(rap, &rap->cp_notify_id);
+ success &= bt_rap_unregister_notify(rap, &rap->ready_notify_id);
+ success &= bt_rap_unregister_notify(rap, &rap->overwritten_notify_id);
+ return success;
+}
+
static void ras_ondemand_client_reset(struct ras *ras)
{
if (!ras)
@@ -4160,12 +4263,22 @@ static void ras_segment_notify_cb(struct bt_rap *rap, uint16_t value_handle,
{
const char *label = user_data ? user_data : "real-time";
struct ras *ras = rap_get_ras(rap);
+ struct iovec iov = { .iov_base = (void *)value, .iov_len = length };
+ struct segmentation_header seg_hdr;
+ bool realtime = !strcmp(label, "real-time");
DBG(rap, "Received %s notification: handle=0x%04x len=%u",
label, value_handle, length);
ras_reassemble_segment_notify(rap, value, length);
+ if (realtime && parse_segmentation_header(&iov, &seg_hdr)) {
+ if (seg_hdr.last_segment)
+ ras_realtime_cancel_timeout(rap);
+ else
+ ras_realtime_arm_timeout(rap, RAS_SEGMENT_TIMEOUT);
+ }
+
if (ras && ras->client.pending_op == RAS_CP_PENDING_GET)
ras_cp_arm_segment_timeout(rap, ras);
}
@@ -4309,7 +4422,8 @@ static void ras_cp_response_notify_cb(struct bt_rap *rap,
*/
static void rap_subscribe_ondemand_chrc(struct bt_rap *rap,
struct gatt_db_attribute *chrc,
- rap_notify_t func, const char *name)
+ uint16_t ccc_value, rap_notify_t func, const char *name,
+ unsigned int *notify_id_out)
{
uint16_t value_handle;
unsigned int notify_id;
@@ -4325,13 +4439,112 @@ static void rap_subscribe_ondemand_chrc(struct bt_rap *rap,
return;
}
- notify_id = bt_rap_register_notify(rap, value_handle, func,
+ notify_id = bt_rap_register_notify(rap, value_handle, ccc_value, func,
(void *)name);
if (!notify_id)
DBG(rap, "Failed to register for %s notifications", name);
else
DBG(rap, "Registered for %s notifications: id=%u", name,
notify_id);
+
+ if (notify_id_out)
+ *notify_id_out = notify_id;
+}
+
+static void ras_data_ready_read_complete(struct bt_rap *rap, bool success,
+ uint8_t att_ecode, const uint8_t *value,
+ uint16_t length, void *user_data)
+{
+ if (!success) {
+ DBG(rap, "Unable to read Ranging Data Ready: error 0x%02x",
+ att_ecode);
+ return;
+ }
+
+ /* This read only synchronizes the characteristic as required by RAS.
+ * A Ready notification, not its current value, starts a retrieval. */
+ DBG(rap, "Read Ranging Data Ready (%u bytes)", length);
+}
+
+static bool rap_subscribe_ranging_data(struct bt_rap *rap,
+ enum bt_rap_ranging_data_mode mode)
+{
+ struct ras *ras = rap_get_ras(rap);
+
+ if (!ras || !rap->client)
+ return false;
+
+ if (mode == BT_RAP_RANGING_DATA_REALTIME) {
+ uint16_t value_handle;
+
+ if (!(rap->ras_features & 0x01) || !ras->realtime_chrc ||
+ !gatt_db_attribute_get_char_data(ras->realtime_chrc, NULL,
+ &value_handle, NULL, NULL, NULL))
+ return false;
+
+ rap->realtime_notify_id = bt_rap_register_notify(rap, value_handle,
+ rap->realtime_ccc_value, ras_segment_notify_cb,
+ "real-time");
+ if (rap->realtime_notify_id)
+ ras_realtime_arm_timeout(rap,
+ RAS_REALTIME_FIRST_SEGMENT_TIMEOUT);
+ return rap->realtime_notify_id != 0;
+ }
+
+ if (mode == BT_RAP_RANGING_DATA_ONDEMAND) {
+ uint16_t value_handle;
+
+ /* Data is segmented and can be large; keep it notification-based.
+ * The control/status characteristics use the requested transport. */
+ rap_subscribe_ondemand_chrc(rap, ras->ondemand_chrc,
+ BT_RAP_RANGING_NOTIFY,
+ ras_segment_notify_cb, "on-demand", &rap->ondemand_notify_id);
+ rap_subscribe_ondemand_chrc(rap, ras->cp_chrc,
+ rap->ondemand_ccc_value,
+ ras_cp_response_notify_cb, "RAS Control Point", &rap->cp_notify_id);
+ rap_subscribe_ondemand_chrc(rap, ras->ready_chrc,
+ rap->ondemand_ccc_value,
+ ras_data_ready_notify_cb, "Ranging Data Ready", &rap->ready_notify_id);
+ rap_subscribe_ondemand_chrc(rap, ras->overwritten_chrc,
+ rap->ondemand_ccc_value,
+ ras_data_overwritten_notify_cb, "Ranging Data Overwritten",
+ &rap->overwritten_notify_id);
+ if (ras->ready_chrc &&
+ gatt_db_attribute_get_char_data(ras->ready_chrc, NULL,
+ &value_handle, NULL, NULL, NULL))
+ rap_read_value(rap, value_handle, ras_data_ready_read_complete,
+ NULL);
+ return rap->ondemand_notify_id || rap->cp_notify_id ||
+ rap->ready_notify_id || rap->overwritten_notify_id;
+ }
+
+ return true;
+}
+
+bool bt_rap_set_ranging_data_mode(struct bt_rap *rap,
+ enum bt_rap_ranging_data_mode mode,
+ enum bt_rap_ranging_transport transport)
+{
+ if (!rap || !rap->client || !rap->ras_features)
+ return false;
+
+ if (mode == BT_RAP_RANGING_DATA_REALTIME &&
+ transport != BT_RAP_RANGING_NOTIFY &&
+ transport != BT_RAP_RANGING_INDICATE)
+ return false;
+
+ if (mode == BT_RAP_RANGING_DATA_ONDEMAND &&
+ transport != BT_RAP_RANGING_NOTIFY &&
+ transport != BT_RAP_RANGING_INDICATE)
+ return false;
+
+ if (!bt_rap_disable_ranging_data(rap))
+ return false;
+
+ rap->realtime_ccc_value = transport;
+ rap->ondemand_ccc_value = transport;
+
+ return rap_subscribe_ranging_data(rap, mode);
}
static void read_ras_features(struct bt_rap *rap, bool success,
@@ -4379,6 +4592,7 @@ static void read_ras_features(struct bt_rap *rap, bool success,
abort_operation = (features & 0x04) != 0;
if (ras)
ras->client.abort_operation = abort_operation;
+ rap->ras_features = features;
DBG(rap, "RAS Features - Real-time: %s, Retrieve Lost: %s, Abort: %s",
supports_realtime ? "Yes" : "No",
@@ -4392,48 +4606,10 @@ static void read_ras_features(struct bt_rap *rap, bool success,
return;
}
- if (!rap->ondemand_mode) {
- /* Register for real-time characteristic notifications */
- if (supports_realtime && ras->realtime_chrc) {
- uint16_t value_handle;
-
- if (gatt_db_attribute_get_char_data(ras->realtime_chrc,
- NULL, &value_handle,
- NULL, NULL, NULL)) {
- unsigned int notify_id;
-
- notify_id = bt_rap_register_notify(rap,
- value_handle,
- ras_segment_notify_cb,
- "real-time");
- if (!notify_id)
- DBG(rap, "Failed to register for "
- "real-time notifications");
- else
- DBG(rap, "Registered for real-time "
- "features: id=%u", notify_id);
- }
- }
-
- return;
- }
-
- /* On-demand mode: subscribe to each on-demand characteristic
- * independently so a partially-capable peer doesn't block the
- * others.
- */
- rap_subscribe_ondemand_chrc(rap, ras->ondemand_chrc,
- ras_segment_notify_cb,
- "on-demand");
- rap_subscribe_ondemand_chrc(rap, ras->cp_chrc,
- ras_cp_response_notify_cb,
- "RAS Control Point");
- rap_subscribe_ondemand_chrc(rap, ras->ready_chrc,
- ras_data_ready_notify_cb,
- "Ranging Data Ready");
- rap_subscribe_ondemand_chrc(rap, ras->overwritten_chrc,
- ras_data_overwritten_notify_cb,
- "Ranging Data Overwritten");
+ if (!rap_subscribe_ranging_data(rap, rap->ondemand_mode ?
+ BT_RAP_RANGING_DATA_ONDEMAND :
+ BT_RAP_RANGING_DATA_REALTIME))
+ DBG(rap, "Failed to subscribe to requested Ranging Data mode");
}
static void foreach_rap_char(struct gatt_db_attribute *attr, void *user_data)
diff --git a/src/shared/rap.h b/src/shared/rap.h
index 9ab577710..9a721df95 100644
--- a/src/shared/rap.h
+++ b/src/shared/rap.h
@@ -303,3 +303,24 @@ void bt_rap_set_conn_interval(struct bt_rap *rap, uint16_t conn_interval);
* GATT discovery to completion to take effect.
*/
bool bt_rap_set_ondemand_ranging(struct bt_rap *rap, bool enable);
+
+enum bt_rap_ranging_data_mode {
+ BT_RAP_RANGING_DATA_DISABLED,
+ BT_RAP_RANGING_DATA_REALTIME,
+ BT_RAP_RANGING_DATA_ONDEMAND,
+};
+
+enum bt_rap_ranging_transport {
+ BT_RAP_RANGING_NOTIFY = 0x0001,
+ BT_RAP_RANGING_INDICATE = 0x0002,
+};
+
+/* Select the Requester's received Ranging Data subscription. This can only be
+ * called after remote RAS discovery and Features read complete. */
+bool bt_rap_set_ranging_data_mode(struct bt_rap *rap,
+ enum bt_rap_ranging_data_mode mode,
+ enum bt_rap_ranging_transport transport);
+
+/* Initiator role: stop receiving Real-time Ranging Data. The GATT client
+ * disables the characteristic CCCD when this is its final subscription. */
+bool bt_rap_disable_realtime_ranging(struct bt_rap *rap);
--
2.43.0
^ permalink raw reply related [flat|nested] 15+ messages in thread* [PATCH 08/11] profiles/ranging: Expose requester ranging data mode over D-Bus
2026-10-01 13:18 [PATCH 00/11] Ranging Profile and Ranging Service PTS fixes Cheng Jiang
` (6 preceding siblings ...)
2026-10-01 13:18 ` [PATCH 07/11] shared/rap: Add requester ranging data subscription API Cheng Jiang
@ 2026-10-01 13:18 ` Cheng Jiang
2026-10-01 13:18 ` [PATCH 09/11] client: Add Channel Sounding ranging data mode control Cheng Jiang
` (2 subsequent siblings)
10 siblings, 0 replies; 15+ messages in thread
From: Cheng Jiang @ 2026-10-01 13:18 UTC (permalink / raw)
To: linux-bluetooth
Cc: jinwang.li, wei.deng, xiuzhuo.shang, shuai.zhang, Cheng Jiang
Add a SetRangingDataMode method to the Channel Sounding interface so a
client can select how Ranging Data is received from the peer (Real-time
or On-demand) and the CCCD transport to use, wiring through to
bt_rap_set_ranging_data_mode().
When a measurement stops, disable the Real-time Ranging Data
subscription so the peer receives the CCCD disable that the requester
owns.
Assisted-by: Claude:claude-opus-4.8
---
profiles/ranging/rap.c | 34 ++++++++++++++++++++++++++++++++++
1 file changed, 34 insertions(+)
diff --git a/profiles/ranging/rap.c b/profiles/ranging/rap.c
index b494d188c..de9cd3d1c 100644
--- a/profiles/ranging/rap.c
+++ b/profiles/ranging/rap.c
@@ -878,6 +878,12 @@ static DBusMessage *stop_measurement(DBusConnection *conn,
bt_rap_hci_set_procedure_data_cb(data->hci_sm, NULL, NULL, NULL);
+ /* The Requester owns the Real-time CCCD subscription. Unregister it when
+ * the procedure ends so the peer receives the required CCCD disable. */
+ if (!bt_rap_disable_realtime_ranging(data->rap))
+ return g_dbus_create_error(msg, DBUS_ERROR_FAILED,
+ "Disable Real-time Ranging Data failed");
+
memset(&data->active_session, 0, sizeof(data->active_session));
g_dbus_emit_property_changed(btd_get_dbus_connection(),
@@ -887,6 +893,30 @@ static DBusMessage *stop_measurement(DBusConnection *conn,
return dbus_message_new_method_return(msg);
}
+static DBusMessage *set_ranging_data_mode(DBusConnection *conn,
+ DBusMessage *msg, void *user_data)
+{
+ struct rap_data *data = user_data;
+ uint8_t mode;
+ uint8_t transport;
+
+ if (!dbus_message_get_args(msg, NULL, DBUS_TYPE_BYTE, &mode,
+ DBUS_TYPE_BYTE, &transport,
+ DBUS_TYPE_INVALID))
+ return g_dbus_create_error(msg, DBUS_ERROR_INVALID_ARGS,
+ "Expected Ranging Data mode byte");
+
+ if (mode > BT_RAP_RANGING_DATA_ONDEMAND)
+ return g_dbus_create_error(msg, DBUS_ERROR_INVALID_ARGS,
+ "Invalid Ranging Data mode");
+
+ if (!bt_rap_set_ranging_data_mode(data->rap, mode, transport))
+ return g_dbus_create_error(msg, DBUS_ERROR_FAILED,
+ "Set Ranging Data mode failed");
+
+ return dbus_message_new_method_return(msg);
+}
+
static const GDBusMethodTable cs_dbus_methods[] = {
{ GDBUS_METHOD("StartMeasurement",
GDBUS_ARGS({ "params", "a{sv}" }),
@@ -896,6 +926,10 @@ static const GDBusMethodTable cs_dbus_methods[] = {
NULL,
NULL,
stop_measurement) },
+ { GDBUS_METHOD("SetRangingDataMode",
+ GDBUS_ARGS({ "mode", "y" }, { "transport", "y" }),
+ NULL,
+ set_ranging_data_mode) },
{ }
};
--
2.43.0
^ permalink raw reply related [flat|nested] 15+ messages in thread* [PATCH 09/11] client: Add Channel Sounding ranging data mode control
2026-10-01 13:18 [PATCH 00/11] Ranging Profile and Ranging Service PTS fixes Cheng Jiang
` (7 preceding siblings ...)
2026-10-01 13:18 ` [PATCH 08/11] profiles/ranging: Expose requester ranging data mode over D-Bus Cheng Jiang
@ 2026-10-01 13:18 ` Cheng Jiang
2026-10-01 13:18 ` [PATCH 10/11] doc: Document " Cheng Jiang
2026-10-01 13:18 ` [PATCH 11/11] doc/qualification: Add RAP/RAS PTS qualification results Cheng Jiang
10 siblings, 0 replies; 15+ messages in thread
From: Cheng Jiang @ 2026-10-01 13:18 UTC (permalink / raw)
To: linux-bluetooth
Cc: jinwang.li, wei.deng, xiuzhuo.shang, shuai.zhang, Cheng Jiang
Add a "ranging-data-mode" command to the Channel Sounding submenu that
selects how Ranging Data is received from a peer (disabled, real-time or
on-demand) and the CCCD transport (notify or indicate), invoking the
ChannelSounding1.SetRangingDataMode method on the device.
Add an advertising helper so the Responder can advertise the Ranging
Service UUID for discovery, together with a getter for the default
LEAdvertisingManager proxy it needs.
Assisted-by: Claude:claude-opus-4.8
---
client/cs.c | 82 +++++++++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 82 insertions(+)
diff --git a/client/cs.c b/client/cs.c
index 7533d053b..5d9f6ca31 100644
--- a/client/cs.c
+++ b/client/cs.c
@@ -1429,6 +1429,85 @@ static void cmd_cs_stop(int argc, char *argv[])
bt_shell_printf("Failed to send StopMeasurement\n");
}
+/* ---- ranging-data-mode ---- */
+
+static void ranging_data_mode_setup(DBusMessageIter *iter, void *user_data)
+{
+ uint16_t values = PTR_TO_UINT(user_data);
+ uint8_t mode = values & 0xff;
+ uint8_t transport = values >> 8;
+
+ dbus_message_iter_append_basic(iter, DBUS_TYPE_BYTE, &mode);
+ dbus_message_iter_append_basic(iter, DBUS_TYPE_BYTE, &transport);
+}
+
+static void ranging_data_mode_reply(DBusMessage *message, void *user_data)
+{
+ DBusError error;
+
+ dbus_error_init(&error);
+ if (dbus_set_error_from_message(&error, message)) {
+ bt_shell_printf("SetRangingDataMode failed: %s\n", error.message);
+ dbus_error_free(&error);
+ return;
+ }
+
+ bt_shell_printf("Ranging Data mode updated\n");
+}
+
+static void cmd_cs_ranging_data_mode(int argc, char *argv[])
+{
+ const char *dev_path;
+ GDBusProxy *proxy;
+ uint8_t mode;
+ uint8_t transport = 0x01;
+
+ if (argc < 3 || argc > 4) {
+ bt_shell_printf("Usage: ranging-data-mode <dev_addr>"
+ " <disabled/realtime/ondemand> [notify/indicate]\n");
+ return;
+ }
+
+ if (!strcmp(argv[2], "disabled"))
+ mode = 0x00;
+ else if (!strcmp(argv[2], "realtime"))
+ mode = 0x01;
+ else if (!strcmp(argv[2], "ondemand"))
+ mode = 0x02;
+ else {
+ bt_shell_printf("Invalid Ranging Data mode: %s\n", argv[2]);
+ return;
+ }
+
+ if (argc == 4) {
+ if (!strcmp(argv[3], "notify"))
+ transport = 0x01;
+ else if (!strcmp(argv[3], "indicate"))
+ transport = 0x02;
+ else {
+ bt_shell_printf("Invalid Ranging Data transport: %s\n", argv[3]);
+ return;
+ }
+ }
+
+ dev_path = cs_resolve_address(argv[1]);
+ if (!dev_path) {
+ bt_shell_printf("Device %s not found\n", argv[1]);
+ return;
+ }
+
+ proxy = cs_find_proxy(dev_path);
+ if (!proxy) {
+ bt_shell_printf("No ChannelSounding1 interface for that device\n");
+ return;
+ }
+
+ if (!g_dbus_proxy_method_call(proxy, "SetRangingDataMode",
+ ranging_data_mode_setup, ranging_data_mode_reply,
+ UINT_TO_PTR(mode | (transport << 8)), NULL))
+ bt_shell_printf("Failed to send SetRangingDataMode\n");
+}
+
/* ---- show ---- */
static void cmd_cs_show(int argc, char *argv[])
@@ -1545,6 +1624,9 @@ static const struct bt_shell_menu cs_menu = {
cmd_cs_stop,
"Stop the active distance measurement;"
" address required when multiple are active" },
+ { "ranging-data-mode", "<dev_addr> <disabled/realtime/ondemand> [notify/indicate]",
+ cmd_cs_ranging_data_mode,
+ "Set RAP Requester Ranging Data subscription mode" },
{ "show", NULL,
cmd_cs_show,
"Show active session id and current"
--
2.43.0
^ permalink raw reply related [flat|nested] 15+ messages in thread* [PATCH 10/11] doc: Document Channel Sounding ranging data mode control
2026-10-01 13:18 [PATCH 00/11] Ranging Profile and Ranging Service PTS fixes Cheng Jiang
` (8 preceding siblings ...)
2026-10-01 13:18 ` [PATCH 09/11] client: Add Channel Sounding ranging data mode control Cheng Jiang
@ 2026-10-01 13:18 ` Cheng Jiang
2026-10-01 13:18 ` [PATCH 11/11] doc/qualification: Add RAP/RAS PTS qualification results Cheng Jiang
10 siblings, 0 replies; 15+ messages in thread
From: Cheng Jiang @ 2026-10-01 13:18 UTC (permalink / raw)
To: linux-bluetooth
Cc: jinwang.li, wei.deng, xiuzhuo.shang, shuai.zhang, Cheng Jiang
Document the ChannelSounding1.SetRangingDataMode method and the
bluetoothctl "ranging-data-mode" and "advertise-rangeservice" commands
added for RAP Requester and Responder control.
Assisted-by: Claude:claude-opus-4.8
---
doc/bluetoothctl-cs.rst | 24 ++++++++++++++++++++++
doc/org.bluez.ChannelSounding1.rst | 32 ++++++++++++++++++++++++++++++
2 files changed, 56 insertions(+)
diff --git a/doc/bluetoothctl-cs.rst b/doc/bluetoothctl-cs.rst
index c3d67fd2d..eecf4bfe4 100644
--- a/doc/bluetoothctl-cs.rst
+++ b/doc/bluetoothctl-cs.rst
@@ -103,6 +103,30 @@ are active the address is required to identify which one to stop.
:Example Stop a second device:
| **> stop 11:22:33:44:55:66**
+ranging-data-mode
+-----------------
+
+Controls the RAP Requester's received Ranging Data subscriptions independently
+of the local CS measurement lifecycle. Changing the mode disables the current
+RAS data CCCDs before enabling the requested mode.
+
+:Usage: **> ranging-data-mode <dev_addr> <disabled/realtime/ondemand> [notify/indicate]**
+:Uses: **org.bluez.ChannelSounding1(5)** method **SetRangingDataMode**
+:disabled: Disables all Ranging Data subscriptions.
+:realtime: Enables Real-time Ranging Data; transport defaults to ``notify``.
+:ondemand: Enables On-demand Data, Control Point, Ready, and Overwritten;
+ transport defaults to ``notify``. On-demand Data remains notification-based
+ so segmented transfers are not limited by indication confirmation pacing.
+:notify: Uses CCCD ``0x0001`` for the selected Ranging Data mode.
+:indicate: Uses CCCD ``0x0002`` for the selected Ranging Data mode.
+
+:Example Disable Ranging Data reception:
+ | **> ranging-data-mode AA:BB:CC:DD:EE:FF disabled**
+:Example Enable Real-time indications:
+ | **> ranging-data-mode AA:BB:CC:DD:EE:FF realtime indicate**
+:Example Enable On-demand indications:
+ | **> ranging-data-mode AA:BB:CC:DD:EE:FF ondemand indicate**
+
show
----
diff --git a/doc/org.bluez.ChannelSounding1.rst b/doc/org.bluez.ChannelSounding1.rst
index 95d6bc7b2..32c83e36f 100644
--- a/doc/org.bluez.ChannelSounding1.rst
+++ b/doc/org.bluez.ChannelSounding1.rst
@@ -258,6 +258,38 @@ Examples:
:bluetoothctl stop a specific device when multiple are active:
| [cs] > stop AA:BB:CC:DD:EE:FF
+void SetRangingDataMode(byte mode, byte transport)
+``````````````````````````````````````````````````
+
+Selects the RAP Requester's received Ranging Data subscription after remote
+RAS discovery has completed. This is independent of the local Channel Sounding
+measurement lifecycle. Changing mode first disables all current Ranging Data
+CCCD subscriptions, then enables the selected mode.
+
+``mode`` values:
+
+:0x00: Disabled. Stop receiving Ranging Data and disable all RAS data CCCDs.
+:0x01: Real-time. Enable the Real-time Ranging Data CCCD.
+:0x02: On-demand. Enable On-demand Data, Control Point, Ready, and Overwritten
+ CCCDs.
+
+``transport`` selects the CCCD value for Real-time or On-demand mode. It is
+ignored for Disabled mode. Use ``0x01`` for notifications or ``0x02`` for
+indications. For On-demand mode, this applies to Control Point, Ready, and
+Overwritten; segmented On-demand Data always uses notifications.
+
+Raises ``org.bluez.Error.Failed`` when remote RAS discovery and Features read
+have not completed, or the selected mode is unsupported by the peer.
+
+Example:
+
+:bluetoothctl disable Real-time Ranging Data:
+ | [cs] > ranging-data-mode AA:BB:CC:DD:EE:FF disabled
+:bluetoothctl enable Real-time indications:
+ | [cs] > ranging-data-mode AA:BB:CC:DD:EE:FF realtime indicate
+:bluetoothctl enable On-demand indications:
+ | [cs] > ranging-data-mode AA:BB:CC:DD:EE:FF ondemand indicate
+
Signals
-------
--
2.43.0
^ permalink raw reply related [flat|nested] 15+ messages in thread* [PATCH 11/11] doc/qualification: Add RAP/RAS PTS qualification results
2026-10-01 13:18 [PATCH 00/11] Ranging Profile and Ranging Service PTS fixes Cheng Jiang
` (9 preceding siblings ...)
2026-10-01 13:18 ` [PATCH 10/11] doc: Document " Cheng Jiang
@ 2026-10-01 13:18 ` Cheng Jiang
10 siblings, 0 replies; 15+ messages in thread
From: Cheng Jiang @ 2026-10-01 13:18 UTC (permalink / raw)
To: linux-bluetooth
Cc: jinwang.li, wei.deng, xiuzhuo.shang, shuai.zhang, Cheng Jiang
Record the PTS results for the Ranging Profile and the Ranging Service.
Both are split per case prefix, as the other qualification documents are:
rap-pts.rst keeps the 19 RAP Requester and Responder cases, and the new
ras-pts.rst carries the 31 RAS Server cases.
Every case is listed on its own row rather than collapsed into a range, so
the file states which cases were actually run. RAS/SR/SPE/BI-02-C is
listed as not applicable: RAS.TS Table 5.1 selects it only when Abort
Operation is unsupported (NOT RAS 3/4), and the IUT supports it, so
RAS/SR/RCP/BV-03-C and RAS/SR/SPE/BI-05-C cover that path instead.
ras-pts.rst also records which transport the server must pick when a
client enables both notifications and indications, since the rule differs
per characteristic and each variant has its own case.
Assisted-by: Claude:claude-opus-4.8
---
doc/qualification/rap-pts.rst | 180 ++++++++++++++++++++++++++++++++++
doc/qualification/ras-pts.rst | 135 +++++++++++++++++++++++++
2 files changed, 315 insertions(+)
create mode 100644 doc/qualification/rap-pts.rst
create mode 100644 doc/qualification/ras-pts.rst
diff --git a/doc/qualification/rap-pts.rst b/doc/qualification/rap-pts.rst
new file mode 100644
index 000000000..a2647f701
--- /dev/null
+++ b/doc/qualification/rap-pts.rst
@@ -0,0 +1,180 @@
+.. SPDX-License-Identifier: GPL-2.0-or-later
+.. Copyright © 2026 Qualcomm Technologies, Inc.
+
+================
+RAP test results
+================
+
+:PTS version: 8.14.0.4
+:BlueZ: 5.87 with Channel Sounding support
+
+RAS Server test results are in ``ras-pts.rst``.
+
+Setup
+=====
+
+- Enable ``Experimental = true`` in ``/etc/bluetooth/main.conf``.
+- Use a ``NoInputNoOutput`` bluetoothctl agent and make it the default agent.
+- Before cases that establish a new encrypted link, delete the DUT bond and the
+ corresponding PTS LTK/bond. Removing only one side causes authentication
+ failure.
+- Do not use the bluetoothctl ``gatt`` menu to write RAS CCCDs. Use the
+ ``cs ranging-data-mode`` command so RAP owns subscription lifetime.
+
+Requester results
+=================
+
++----------------------------+----------+--------------------------------------------------------------------------+
+| Test name | Result | Notes |
++============================+==========+==========================================================================+
+| RAP/REQ/RRD/BV-01-C | PASS | Configure the requested Real-time notification or indication transport. |
++----------------------------+----------+--------------------------------------------------------------------------+
+| RAP/REQ/RRD/BI-01-C | PASS | The Requester disables the Real-time CCCD after 5 seconds without a |
+| | | first segment. |
++----------------------------+----------+--------------------------------------------------------------------------+
+| RAP/REQ/RRD/BI-02-C | PASS | The Requester disables the Real-time CCCD after 1 second without a |
+| | | continuation segment. |
++----------------------------+----------+--------------------------------------------------------------------------+
+| RAP/REQ/CGGIT/SER/BV-01-C | PASS | Discover the remote primary Ranging Service. |
++----------------------------+----------+--------------------------------------------------------------------------+
+| RAP/REQ/CGGIT/CHA/BV-01-C | PASS | Discover the RAS Features characteristic. |
++----------------------------+----------+--------------------------------------------------------------------------+
+| RAP/REQ/CGGIT/CHA/BV-02-C | PASS | Discover the Real-time Ranging Data characteristic. |
++----------------------------+----------+--------------------------------------------------------------------------+
+| RAP/REQ/CGGIT/CHA/BV-03-C | PASS | Discover the On-demand Ranging Data characteristic. |
++----------------------------+----------+--------------------------------------------------------------------------+
+| RAP/REQ/CGGIT/CHA/BV-04-C | PASS | Discover the RAS Control Point characteristic. |
++----------------------------+----------+--------------------------------------------------------------------------+
+| RAP/REQ/CGGIT/CHA/BV-05-C | PASS | Discover the Ranging Data Ready characteristic. |
++----------------------------+----------+--------------------------------------------------------------------------+
+| RAP/REQ/CGGIT/CHA/BV-06-C | PASS | Discover the Ranging Data Overwritten characteristic. |
++----------------------------+----------+--------------------------------------------------------------------------+
+| RAP/REQ/ORD/BV-01-C | PASS | See the On-demand ranging data procedures below. |
++----------------------------+----------+--------------------------------------------------------------------------+
+| RAP/REQ/ORD/BI-01-C | PASS | The Requester sends Abort Operation after 5 seconds without the first |
+| | | On-demand segment. |
++----------------------------+----------+--------------------------------------------------------------------------+
+| RAP/REQ/ORD/BI-02-C | PASS | The Requester sends Abort Operation when a continuation segment is |
+| | | missing for more than one second. |
++----------------------------+----------+--------------------------------------------------------------------------+
+| RAP/REQ/ORD/BI-03-C | PASS | Ignore Complete Lost and RFU Control Point responses; receive normal |
+| | | data and ACK the subsequent Complete response. |
++----------------------------+----------+--------------------------------------------------------------------------+
+| RAP/REQ/ORD/BV-05-C | PASS | Configure On-demand notification transport; PTS validates Ranging Data |
+| | | Ready notification handling. |
++----------------------------+----------+--------------------------------------------------------------------------+
+| RAP/REQ/ORD/BV-06-C | PASS | On the PTS MMI, promptly read Ranging Data Ready after PTS suppresses |
+| | | its notification. |
++----------------------------+----------+--------------------------------------------------------------------------+
+| RAP/REQ/ORD/BV-07-C | PASS | Configure On-demand indication transport; PTS validates Ranging Data |
+| | | Overwritten indication reception. |
++----------------------------+----------+--------------------------------------------------------------------------+
+| RAP/REQ/ORD/BV-08-C | PASS | Configure On-demand notification transport; PTS validates Ranging Data |
+| | | Overwritten notification reception. |
++----------------------------+----------+--------------------------------------------------------------------------+
+
+Responder results
+=================
+
++----------------------------+----------+--------------------------------------------------------------------------+
+| Test name | Result | Notes |
++============================+==========+==========================================================================+
+| RAP/RES/RSPF/BV-01-C | PASS | Discoverable advertising includes the Ranging Service UUID (0x185B). |
++----------------------------+----------+--------------------------------------------------------------------------+
+
+
+Before ``RAP/RES/RSPF/BV-01-C``, advertise the Ranging Service UUID so PTS can
+discover the Responder::
+
+ menu advertise
+ uuids 0000185b-0000-1000-8000-00805f9b34fb
+ back
+ advertise peripheral
+
+Stop it after the case with ``advertise off``.
+
+On-demand ranging data procedures
+=================================
+
+Some RAS Requester cases are exercised against PTS's SIG pre-generated ranging
+data (``TSPX_test_method``) when the PTS controller does not support Channel
+Sounding. The procedures below use the ``ranging-data-mode`` command to select
+the transport PTS expects for each case.
+
+For ``RAP/REQ/ORD/BV-01-C``, set ``TSPX_test_method`` to ``pre-generated
+ranging data`` and select the ``2_procedures`` input.
+
+After connection, pairing, and encryption complete, run the following in the
+same bluetoothctl session::
+
+ menu cs
+ ranging-data-mode <pts_addr> ondemand indicate
+
+This configures indications for Ranging Data Ready, RAS Control Point, and
+Ranging Data Overwritten. Segmented On-demand Ranging Data remains configured
+for notifications so large pre-generated streams are not stalled by
+per-indication confirmation pacing.
+
+The Requester reads the Ranging Data Ready characteristic, waits for the PTS
+Ready indication, writes ``Get Ranging Data``, reassembles the data, and writes
+``ACK Ranging Data`` after the PTS Complete response.
+
+For ``RAP/REQ/ORD/BI-01-C``, use the same setup but configure PTS not to send
+the first On-demand segment after ``Get Ranging Data``. The Requester writes
+``Abort Operation`` after five seconds. For ``RAP/REQ/ORD/BI-02-C``, have PTS
+send only First Segment; the Requester's one-second continuation watchdog
+sends Abort within the PTS assertion window.
+
+For ``RAP/REQ/ORD/BI-03-C``, use the same setup and allow PTS to inject
+Complete Lost Ranging Data Segments and RFU Control Point responses during the
+transfer. No additional DUT action is required: the Requester ignores them,
+then ACKs the normal Complete Ranging Data response.
+
+For ``RAP/REQ/ORD/BV-05-C``, select notification transport after security is
+complete::
+
+ menu cs
+ ranging-data-mode <pts_addr> ondemand notify
+
+This configures the Ranging Data Ready path for the PTS notification round.
+
+For ``RAP/REQ/ORD/BV-07-C``, select indication transport to receive the
+Ranging Data Overwritten indication::
+
+ menu cs
+ ranging-data-mode <pts_addr> ondemand indicate
+
+PTS can log failed attempts to send an indication during this case even though
+its final verdict is PASS; record the final PTS verdict rather than treating
+that server-side diagnostic alone as a DUT failure.
+
+For ``RAP/REQ/ORD/BV-08-C``, select notification transport to receive the
+Ranging Data Overwritten notification::
+
+ menu cs
+ ranging-data-mode <pts_addr> ondemand notify
+
+For ``RAP/REQ/ORD/BV-06-C``, after connection and security complete, first use
+indication transport to enter the PTS On-demand flow, then switch to the
+notification transport used by the lost-Ready round::
+
+ menu cs
+ ranging-data-mode <pts_addr> ondemand indicate
+ ranging-data-mode <pts_addr> ondemand notify
+
+Wait for ``Ranging Data mode updated`` after each command. PTS does not advance
+to the lost-Ready MMI until both transitions have been completed. It then
+suppresses the Ranging Data Ready notification and presents an MMI asking for a
+read of the Ranging Data Ready characteristic. Complete the MMI promptly, before
+its short timeout, from the bluetoothctl GATT menu::
+
+ back
+ menu gatt
+ select-attribute 00002c18-0000-1000-8000-00805f9b34fb
+ read
+
+Selecting the characteristic by UUID avoids depending on the handles PTS
+happens to assign; ``select-attribute`` matches characteristics before
+descriptors, so this selects the value attribute and not its CCCD. Use
+``list-attributes`` if the remote database needs to be inspected first. Return
+to PTS and continue the MMI after the read request is sent.
diff --git a/doc/qualification/ras-pts.rst b/doc/qualification/ras-pts.rst
new file mode 100644
index 000000000..3b9bfd0d0
--- /dev/null
+++ b/doc/qualification/ras-pts.rst
@@ -0,0 +1,135 @@
+.. SPDX-License-Identifier: GPL-2.0-or-later
+.. Copyright © 2026 Qualcomm Technologies, Inc.
+
+================
+RAS test results
+================
+
+:PTS version: 8.14.0.4
+:BlueZ: 5.87 with Channel Sounding support
+
+RAP Requester and Responder test results are in ``rap-pts.rst``.
+
+Setup
+=====
+
+- Enable ``Experimental = true`` in ``/etc/bluetooth/main.conf``.
+- Use a ``NoInputNoOutput`` bluetoothctl agent and make it the default agent.
+- Before cases that establish a new encrypted link, delete the DUT bond and the
+ corresponding PTS LTK/bond. Removing only one side causes authentication
+ failure.
+- The DUT acts as the CS Reflector. Set the role from the bluetoothctl Channel
+ Sounding menu before the first case::
+
+ menu cs
+ role 0x02
+
+Tests
+=====
+
++------------------------------+----------+----------------------------------------------------------------+
+| Test name | Result | Notes |
++==============================+==========+================================================================+
+| IOPT/RAS/SR/GATTDB/BV-01-I | PASS | RAS database validation. |
++------------------------------+----------+----------------------------------------------------------------+
+| RAS/SR/SGGIT/SER/BV-01-C | PASS | Primary, unique Ranging Service discovery. |
++------------------------------+----------+----------------------------------------------------------------+
+| RAS/SR/SGGIT/CHA/BV-01-C | PASS | RAS Features characteristic declaration. |
++------------------------------+----------+----------------------------------------------------------------+
+| RAS/SR/SGGIT/CHA/BV-02-C | PASS | Real-time Ranging Data characteristic declaration. |
++------------------------------+----------+----------------------------------------------------------------+
+| RAS/SR/SGGIT/CHA/BV-03-C | PASS | On-demand Ranging Data characteristic declaration. |
++------------------------------+----------+----------------------------------------------------------------+
+| RAS/SR/SGGIT/CHA/BV-04-C | PASS | RAS Control Point characteristic declaration. |
++------------------------------+----------+----------------------------------------------------------------+
+| RAS/SR/SGGIT/CHA/BV-08-C | PASS | Ranging Data Ready characteristic declaration. |
++------------------------------+----------+----------------------------------------------------------------+
+| RAS/SR/SGGIT/CHA/BV-12-C | PASS | Ranging Data Overwritten characteristic declaration. |
++------------------------------+----------+----------------------------------------------------------------+
+| RAS/SR/RCO/BV-01-C | PASS | RAS Features read. |
++------------------------------+----------+----------------------------------------------------------------+
+| RAS/SR/RCO/BV-02-C | PASS | Ranging Data Ready notification or indication. |
++------------------------------+----------+----------------------------------------------------------------+
+| RAS/SR/RCO/BV-03-C | PASS | Ranging Data Ready read. |
++------------------------------+----------+----------------------------------------------------------------+
+| RAS/SR/RCO/BV-04-C | PASS | Ranging Data Overwritten read. |
++------------------------------+----------+----------------------------------------------------------------+
+| RAS/SR/RCO/BV-05-C | PASS | Ranging Data Ready, Indication transport. |
++------------------------------+----------+----------------------------------------------------------------+
+| RAS/SR/RCO/BV-06-C | PASS | Ranging Data Ready, Notification transport. |
++------------------------------+----------+----------------------------------------------------------------+
+| RAS/SR/RCO/BV-07-C | PASS | Ranging Data Ready, Indication and Read. |
++------------------------------+----------+----------------------------------------------------------------+
+| RAS/SR/RCO/BV-08-C | PASS | Ranging Data Overwritten is indicated when the client enabled |
+| | | both transports. |
++------------------------------+----------+----------------------------------------------------------------+
+| RAS/SR/RCO/BV-09-C | PASS | Ranging Data Ready is indicated when the client enabled both |
+| | | transports. |
++------------------------------+----------+----------------------------------------------------------------+
+| RAS/SR/RRD/BV-01-C | PASS | Real-time Ranging Data delivery. |
++------------------------------+----------+----------------------------------------------------------------+
+| RAS/SR/RRD/BV-03-C | PASS | Real-time Ranging Data is notified when the client enabled |
+| | | both transports. |
++------------------------------+----------+----------------------------------------------------------------+
+| RAS/SR/RRD/BV-04-C | PASS | Overwritten Real-time data restarts on the new Ranging |
+| | | Counter. |
++------------------------------+----------+----------------------------------------------------------------+
+| RAS/SR/RRD/BV-05-C | PASS | Real-time subscription is dropped on disconnection. |
++------------------------------+----------+----------------------------------------------------------------+
+| RAS/SR/RCP/BV-01-C | PASS | Get Ranging Data, segmented transfer and ACK. |
++------------------------------+----------+----------------------------------------------------------------+
+| RAS/SR/RCP/BV-03-C | PASS | Abort Operation stops an in-progress transfer. |
++------------------------------+----------+----------------------------------------------------------------+
+| RAS/SR/RCP/BV-05-C | PASS | Control Point state is reset on disconnection. |
++------------------------------+----------+----------------------------------------------------------------+
+| RAS/SR/SPE/BI-01-C | PASS | Op Code Not Supported, Retrieve Lost Ranging Data Segments. |
++------------------------------+----------+----------------------------------------------------------------+
+| RAS/SR/SPE/BI-03-C | PASS | Op Code Not Supported, Set Filter. |
++------------------------------+----------+----------------------------------------------------------------+
+| RAS/SR/SPE/BI-04-C | PASS | Op Code Not Supported, RFU. |
++------------------------------+----------+----------------------------------------------------------------+
+| RAS/SR/SPE/BI-05-C | PASS | Invalid Parameter, Abort Operation. |
++------------------------------+----------+----------------------------------------------------------------+
+| RAS/SR/SPE/BI-06-C | PASS | Get Ranging Data and ACK Ranging Data errors. |
++------------------------------+----------+----------------------------------------------------------------+
+| RAS/SR/SPE/BI-07-C | PASS | Server Busy. |
++------------------------------+----------+----------------------------------------------------------------+
+| RAS/SR/SPE/BI-11-C | PASS | Real-time and On-demand CCCDs are mutually exclusive. |
++------------------------------+----------+----------------------------------------------------------------+
+
+Not applicable
+==============
+
++------------------------------+-----------------------------------------------+
+| Test name | Reason |
++==============================+===============================================+
+| RAS/SR/SPE/BI-02-C | Applies when Abort Operation is not supported |
+| | (NOT RAS 3/4 in RAS.TS Table 5.1). The IUT |
+| | supports it, so RAS/SR/RCP/BV-03-C and |
+| | RAS/SR/SPE/BI-05-C are run instead. |
++------------------------------+-----------------------------------------------+
+
+Pre-generated ranging data
+==========================
+
+With ``TSPX_test_method`` set to pre-generated CS data, the whole ranging data
+arrives in one go rather than one CS subevent at a time, so the On-demand
+notification path sends its segments back to back with no air time between
+them. ``RAS/SR/SPE/BI-05-C`` writes an invalid Abort Operation mid-transfer
+and needs the transfer to still be running when it does, so the per-segment
+delay has to leave the peer a window: it fails at 0 ms and passes at the 5 ms
+``RAS_SEGMENT_NFY_PACE_MS`` uses. Feeding the test data one subevent at a
+time rather than in one go does not change that, since the segments of a
+subevent still go out back to back. Raise the delay if PTS reports Complete
+Ranging Data where it expected Invalid Parameter.
+
+Transport selection
+===================
+
+When a client enables both notifications and indications on a characteristic,
+the transport the server must use differs per characteristic:
+
+- Ranging Data Ready and Ranging Data Overwritten are indicated (RAS 3.4.2 and
+ 3.5.2), covered by ``RAS/SR/RCO/BV-08-C`` and ``RAS/SR/RCO/BV-09-C``.
+- On-demand and Real-time Ranging Data are notified (RAS 3.2.2 and 3.2.3.1),
+ covered by ``RAS/SR/RCO/BV-05..07-C`` and ``RAS/SR/RRD/BV-03-C``.
--
2.43.0
^ permalink raw reply related [flat|nested] 15+ messages in thread