* [PATCH BlueZ v1 2/3] shared: add support for on-demand notification handling
2026-09-08 4:27 [PATCH BlueZ v1 0/3] RAS: Add support for on-demand ranging data Naga Bhavani Akella
2026-09-08 4:27 ` [PATCH BlueZ v1 1/3] rap: Add OndemandRanging configuration option Naga Bhavani Akella
@ 2026-09-08 4:27 ` Naga Bhavani Akella
2026-09-08 4:27 ` [PATCH BlueZ v1 3/3] profiles: honor OndemandRanging config for RAS Initiator Naga Bhavani Akella
2026-09-08 19:20 ` [PATCH BlueZ v1 0/3] RAS: Add support for on-demand ranging data patchwork-bot+bluetooth
3 siblings, 0 replies; 8+ messages in thread
From: Naga Bhavani Akella @ 2026-09-08 4:27 UTC (permalink / raw)
To: linux-bluetooth
Cc: luiz.dentz, quic_mohamull, quic_hbandi, quic_anubhavg,
Naga Bhavani Akella
When OndemandRanging is enabled in main.conf, allow the reflector role
to send on-demand ranging data. In the initiator role, handle
on-demand ranging data notifications and the associated control point
opcodes.
---
src/shared/rap.c | 849 +++++++++++++++++++++++++++++++++++++++++++----
src/shared/rap.h | 6 +
2 files changed, 788 insertions(+), 67 deletions(-)
diff --git a/src/shared/rap.c b/src/shared/rap.c
index be80154cd..e55bbe088 100644
--- a/src/shared/rap.c
+++ b/src/shared/rap.c
@@ -25,8 +25,15 @@
#include "src/shared/gatt-db.h"
#include "src/shared/gatt-server.h"
#include "src/shared/gatt-client.h"
+#include "src/shared/timeout.h"
#include "src/shared/rap.h"
+/* Watchdog for a RAS Control Point request the initiator is awaiting a
+ * response for. Guards against a lost/dropped notification leaving
+ * ras_ondemand_client permanently stuck waiting.
+ */
+#define RAS_CP_RESPONSE_TIMEOUT (10 * 1000)
+
#define DBG(_rap, fmt, ...) \
rap_debug(_rap, "%s:%s() " fmt, __FILE__, __func__, ##__VA_ARGS__)
@@ -226,7 +233,7 @@ enum cs_procedure_done_status {
CS_PROC_ABORTED = 0x0F
};
-/* Per-procedure tracking — one entry per HCI procedure counter */
+/* Per-procedure tracking - one entry per HCI procedure counter */
struct cs_proc_state {
uint16_t proc_counter;
struct bcs_procedure_data bcs_data;
@@ -276,6 +283,18 @@ struct cstracker {
struct ranging_header ranging_header_;
/* Client - subsequent segments appended using iovec */
struct iovec segment_data;
+ /* Client - segment reassembly continuity tracking: true from a
+ * valid first segment until a detected gap, an append failure,
+ * or bt_rap_detach(). NOT cleared on last_segment - a ranging
+ * procedure's later subevents can arrive as further first/last
+ * cycles without repeating first_segment=1, so this stays active
+ * across them (see ras_reassemble_segment_notify()).
+ */
+ bool reassembly_active;
+ /* Client - rolling_segment_counter value the next segment must
+ * carry; only meaningful while reassembly_active is true.
+ */
+ uint8_t expected_segment_counter;
/* Session-level config template and per-procedure state */
struct bcs_procedure_data bcs_proc_data;
@@ -284,6 +303,64 @@ struct cstracker {
uint64_t proc_start_timestamp_nanos;
};
+/* RAS Control Point op codes */
+enum ras_cp_opcode {
+ RAS_CP_OP_GET_RANGING_DATA = 0x00,
+ RAS_CP_OP_ACK_RANGING_DATA = 0x01,
+ /* 0x02 Retrieve_Lost_Ranging_Data_Segments,
+ * 0x03 Abort_Operation,
+ * 0x04 Set_Filter - optional, not implemented
+ */
+};
+
+/* RAS Control Point response op codes */
+enum ras_cp_rsp_opcode {
+ RAS_CP_RSP_COMPLETE_RANGING_DATA = 0x00,
+ RAS_CP_RSP_RESPONSE_CODE = 0x02,
+};
+
+/* RAS Control Point response code values */
+enum ras_cp_response_code {
+ RAS_CP_RSP_CODE_SUCCESS = 0x01,
+ RAS_CP_RSP_CODE_OPCODE_NOT_SUPPORTED = 0x02,
+ RAS_CP_RSP_CODE_INVALID_PARAMETER = 0x03,
+ RAS_CP_RSP_CODE_PROCEDURE_NOT_COMPLETED = 0x06,
+ RAS_CP_RSP_CODE_SERVER_BUSY = 0x07,
+ RAS_CP_RSP_CODE_NO_RECORDS_FOUND = 0x08,
+};
+
+/* Single-slot buffer for a completed CS Procedure's Ranging Data Body,
+ * retained until the client sends ACK_Ranging_Data. Lives on struct bt_rap
+ * (one per remote connection) rather than on the shared struct ras (one
+ * per local adapter GATT database) so that concurrently connected
+ * centrals cannot evict or receive each other's cached ranging data.
+ */
+struct ras_ondemand_cache {
+ bool valid;
+ uint16_t counter;
+ struct iovec data;
+ uint16_t send_index;
+ struct segmentation_header seg_hdr;
+};
+
+/* Initiator role: which RAS Control Point request the client is
+ * currently awaiting a response for.
+ */
+enum ras_cp_pending {
+ RAS_CP_PENDING_NONE = 0,
+ RAS_CP_PENDING_GET,
+ RAS_CP_PENDING_ACK,
+};
+
+struct ras_ondemand_client {
+ enum ras_cp_pending pending_op;
+ uint16_t pending_counter;
+ /* Watchdog for the outstanding request named above; fires if the
+ * peer never sends a Control Point response.
+ */
+ unsigned int cp_timeout_id;
+};
+
/* Ranging Service context */
struct ras {
struct bt_rap_db *rapdb;
@@ -305,8 +382,21 @@ struct ras {
/* CCC state tracking for mutual exclusivity */
uint16_t realtime_ccc_value;
uint16_t ondemand_ccc_value;
+
+ /* Initiator role: pending RAS Control Point request state */
+ struct ras_ondemand_client client;
};
+static inline bool ras_is_ondemand_mode(const struct ras *ras)
+{
+ return ras && ras->ondemand_ccc_value != 0;
+}
+
+/* Initiator role: forward declaration needed by bt_rap_detach() below;
+ * defined alongside the other RAS Control Point client helpers.
+ */
+static void ras_ondemand_client_reset(struct ras *ras);
+
struct bt_rap_db {
struct gatt_db *db;
struct ras *ras;
@@ -341,6 +431,18 @@ struct bt_rap {
uint16_t conn_interval;
struct cstracker *resptracker;
struct cstracker *reqtracker;
+
+ /* Initiator role: false = real-time (default), true = on-demand.
+ * Must be set before the RAS Features read completes during
+ * bt_rap_attach() to take effect.
+ */
+ bool ondemand_mode;
+
+ /* Responder role: this connection's on-demand ranging data cache.
+ * Per-connection (not on the shared struct ras) so that concurrent
+ * centrals cannot evict or receive each other's cached data.
+ */
+ struct ras_ondemand_cache cache;
};
static struct queue *rap_db;
@@ -821,6 +923,51 @@ void bt_rap_detach(struct bt_rap *rap)
bt_att_unregister_disconnect(rap->att, rap->disconn_id);
rap->att = NULL;
+ /* Segment reassembly state (reqtracker) is not tied to any one
+ * connection instance - drop it here too, otherwise a stale
+ * expected_segment_counter/ranging_header_/segment_data left over
+ * from before the disconnect could be fed by a continuation
+ * segment on the next connection whose rolling counter happens to
+ * collide (it is only 6 bits wide).
+ */
+ if (rap->reqtracker) {
+ free(rap->reqtracker->segment_data.iov_base);
+ rap->reqtracker->segment_data.iov_base = NULL;
+ rap->reqtracker->segment_data.iov_len = 0;
+ rap->reqtracker->reassembly_active = false;
+ rap->reqtracker->expected_segment_counter = 0;
+ }
+
+ /* Responder role: drop any un-ACKed on-demand cache for this
+ * 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));
+
+ if (rap->rrapdb && rap->rrapdb->ras) {
+ struct ras *ras = rap->rrapdb->ras;
+
+ ras_ondemand_client_reset(ras);
+
+ /* foreach_rap_char() only (re-)issues the RAS Features
+ * read and its resulting notify subscriptions when a
+ * *_chrc pointer is NULL. Clear all of them here so the
+ * next bt_rap_attach()'s rediscovery pass re-arms
+ * subscriptions against the freshly cloned client
+ * instead of silently staying unsubscribed - or, if the
+ * remote GATT db was rebuilt (unbonded device,
+ * GattCacheDisable, service-changed), holding onto
+ * gatt_db_attribute pointers that no longer exist.
+ */
+ ras->feat_chrc = NULL;
+ ras->realtime_chrc = NULL;
+ ras->ondemand_chrc = NULL;
+ ras->cp_chrc = NULL;
+ ras->ready_chrc = NULL;
+ ras->overwritten_chrc = NULL;
+ }
+
queue_foreach(bt_rap_cbs, rap_detached, rap);
}
@@ -855,6 +1002,8 @@ static void rap_free(void *data)
rap_db_free(rap->rrapdb);
+ free(rap->cache.data.iov_base);
+
if (rap->resptracker) {
free(rap->resptracker->segment_data.iov_base);
free(rap->resptracker);
@@ -1045,25 +1194,18 @@ static void ras_features_read_cb(struct gatt_db_attribute *attrib,
gatt_db_attribute_read_result(attrib, id, 0, value, sizeof(value));
}
-static void ras_ondemand_read_cb(struct gatt_db_attribute *attrib,
- unsigned int id, uint16_t offset,
- uint8_t opcode, struct bt_att *att,
- void *user_data)
-{
- struct ras *ras = user_data;
-
- if (ras)
- bt_rap_get_session(att, ras->rapdb->db);
-
- /* No static read data – on‑demand data is pushed via
- * notifications
- */
- gatt_db_attribute_read_result(attrib, id, 0, NULL, 0);
-}
+static void ras_cp_send_response_code(struct bt_rap *rap, struct ras *ras,
+ uint8_t response_code);
+static void ras_cp_send_complete_ranging_data(struct bt_rap *rap,
+ struct ras *ras, uint16_t counter);
+static void send_ondemand_segment_data(struct bt_rap *rap, struct ras *ras);
+static void ras_ondemand_cache_clear(struct bt_rap *rap);
/*
- * Control point handler.
- * Parses the opcode and acts on queued data (implementation TBD).
+ * 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.
*/
static void ras_control_point_write_cb(struct gatt_db_attribute *attrib,
unsigned int id, uint16_t offset,
@@ -1072,14 +1214,75 @@ static void ras_control_point_write_cb(struct gatt_db_attribute *attrib,
void *user_data)
{
struct ras *ras = user_data;
+ struct bt_rap *rap;
+ uint16_t counter;
- if (ras)
- bt_rap_get_session(att, ras->rapdb->db);
+ if (!ras) {
+ gatt_db_attribute_write_result(attrib, id,
+ BT_ATT_ERROR_UNLIKELY);
+ return;
+ }
+
+ rap = bt_rap_get_session(att, ras->rapdb->db);
+
+ if (!rap) {
+ gatt_db_attribute_write_result(attrib, id,
+ BT_ATT_ERROR_UNLIKELY);
+ return;
+ }
+
+ if (!len) {
+ gatt_db_attribute_write_result(attrib, id, 0);
+ return;
+ }
+
+ switch (value[0]) {
+ case RAS_CP_OP_GET_RANGING_DATA:
+ if (len != 3) {
+ ras_cp_send_response_code(rap, ras,
+ RAS_CP_RSP_CODE_INVALID_PARAMETER);
+ break;
+ }
+
+ counter = get_le16(value + 1);
+
+ if (!rap->cache.valid || rap->cache.counter != counter) {
+ ras_cp_send_response_code(rap, ras,
+ RAS_CP_RSP_CODE_NO_RECORDS_FOUND);
+ break;
+ }
+
+ send_ondemand_segment_data(rap, ras);
+ break;
+ case RAS_CP_OP_ACK_RANGING_DATA:
+ if (len != 3) {
+ ras_cp_send_response_code(rap, ras,
+ RAS_CP_RSP_CODE_INVALID_PARAMETER);
+ break;
+ }
+
+ counter = get_le16(value + 1);
+
+ if (!rap->cache.valid || rap->cache.counter != counter) {
+ ras_cp_send_response_code(rap, ras,
+ RAS_CP_RSP_CODE_NO_RECORDS_FOUND);
+ break;
+ }
- /* Control point handler - implementation TBD */
+ ras_ondemand_cache_clear(rap);
+
+ ras_cp_send_response_code(rap, ras, RAS_CP_RSP_CODE_SUCCESS);
+ break;
+ default:
+ ras_cp_send_response_code(rap, ras,
+ RAS_CP_RSP_CODE_OPCODE_NOT_SUPPORTED);
+ break;
+ }
+
+ gatt_db_attribute_write_result(attrib, id, 0);
}
-/* Data Ready – returns the latest ranging counter. */
+/* Data Ready - returns the latest ranging counter. */
static void ras_data_ready_read_cb(struct gatt_db_attribute *attrib,
unsigned int id, uint16_t offset,
uint8_t opcode, struct bt_att *att,
@@ -1096,7 +1299,7 @@ static void ras_data_ready_read_cb(struct gatt_db_attribute *attrib,
gatt_db_attribute_read_result(attrib, id, 0, value, sizeof(value));
}
-/* Data Overwritten – indicates how many results were overwritten. */
+/* Data Overwritten - indicates how many results were overwritten. */
static void ras_data_overwritten_read_cb(struct gatt_db_attribute *attrib,
unsigned int id, uint16_t offset,
uint8_t opcode, struct bt_att *att,
@@ -1177,7 +1380,7 @@ static void ras_ranging_data_ccc_write_cb(struct gatt_db_attribute *attrib,
gatt_db_attribute_write_result(attrib, id, 0);
}
-/* Service registration – store attribute pointers */
+/* Service registration - store attribute pointers */
static struct ras *register_ras_service(struct gatt_db *db)
{
struct ras *ras;
@@ -1234,8 +1437,7 @@ static struct ras *register_ras_service(struct gatt_db *db)
BT_ATT_PERM_READ_ENCRYPT,
BT_GATT_CHRC_PROP_NOTIFY |
BT_GATT_CHRC_PROP_INDICATE,
- ras_ondemand_read_cb, NULL,
- ras);
+ NULL, NULL, ras);
ras->ondemand_ccc = gatt_db_service_add_ccc_custom(ras->svc,
BT_ATT_PERM_READ | BT_ATT_PERM_WRITE,
@@ -1495,6 +1697,179 @@ static bool ras_maybe_prepend_ranging_header(struct cs_procedure_data *d)
return ok;
}
+static void ras_ondemand_cache_clear(struct bt_rap *rap)
+{
+ if (!rap)
+ return;
+
+ 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;
+}
+
+static void ras_ondemand_cache_store(struct bt_rap *rap,
+ struct cs_procedure_data *proc)
+{
+ if (!rap || !proc)
+ return;
+
+ rap->cache.data = proc->ras_raw_data_;
+ rap->cache.counter = proc->counter;
+ rap->cache.valid = true;
+ rap->cache.send_index = 0;
+ memset(&rap->cache.seg_hdr, 0, sizeof(rap->cache.seg_hdr));
+
+ proc->ras_raw_data_.iov_base = NULL;
+ proc->ras_raw_data_.iov_len = 0;
+ proc->ras_raw_data_index_ = 0;
+}
+
+static void ras_send_ranging_data_ready(struct bt_rap *rap, struct ras *ras,
+ uint16_t counter)
+{
+ uint8_t value[2];
+
+ if (!rap || !ras || !ras->ready_chrc)
+ return;
+
+ put_le16(counter, value);
+ 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,
+ struct ras *ras, uint16_t counter)
+{
+ uint8_t value[2];
+
+ if (!rap || !ras || !ras->overwritten_chrc)
+ return;
+
+ put_le16(counter, value);
+ 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,
+ uint8_t response_code)
+{
+ uint8_t value[2] = { RAS_CP_RSP_RESPONSE_CODE, response_code };
+
+ if (!rap || !ras || !ras->cp_chrc)
+ return;
+
+ gatt_db_attribute_notify(ras->cp_chrc, value, sizeof(value),
+ bt_rap_get_att(rap));
+}
+
+static void ras_cp_send_complete_ranging_data(struct bt_rap *rap,
+ struct ras *ras, uint16_t counter)
+{
+ uint8_t value[3];
+
+ if (!rap || !ras || !ras->cp_chrc)
+ return;
+
+ 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));
+}
+
+static void send_ondemand_segment_data(struct bt_rap *rap, struct ras *ras)
+{
+ 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;
+
+ value_max = ras_att_value_payload_max(rap);
+
+ if (value_max <= header_len) {
+ DBG(rap, "value_max(%u) too small for header", value_max);
+ /* Tell the initiator now instead of leaving it waiting
+ * indefinitely for a Complete_Ranging_Data response that
+ * will never come; the cached data is left intact so a
+ * later retry (e.g. after MTU/DLE negotiation) can still
+ * succeed.
+ */
+ ras_cp_send_response_code(rap, ras,
+ RAS_CP_RSP_CODE_SERVER_BUSY);
+ return;
+ }
+
+ 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;
+ uint16_t copy_size;
+ uint16_t seg_len;
+ uint8_t *seg;
+ uint16_t wr = 0;
+ bool ok;
+
+ rap->cache.seg_hdr.last_segment =
+ (unsent_data_size <= raw_payload_size) ? 1 : 0;
+
+ copy_size = (uint16_t)((unsent_data_size < raw_payload_size) ?
+ unsent_data_size : raw_payload_size);
+ seg_len = (uint16_t)(header_len + copy_size);
+ seg = (uint8_t *)malloc(seg_len);
+
+ if (!seg) {
+ DBG(rap, "OOM (%u)", seg_len);
+ ras_cp_send_response_code(rap, ras,
+ RAS_CP_RSP_CODE_SERVER_BUSY);
+ return;
+ }
+
+ wr += (uint16_t)
+ serialize_segmentation_header(&rap->cache.seg_hdr,
+ seg + wr, seg_len - wr);
+ memcpy(seg + wr,
+ (const uint8_t *)rap->cache.data.iov_base + index,
+ copy_size);
+ wr += copy_size;
+
+ ok = gatt_db_attribute_notify(ras->ondemand_chrc, seg, wr,
+ bt_rap_get_att(rap));
+
+ free(seg);
+
+ if (!ok) {
+ 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.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);
+
+ if (rap->cache.seg_hdr.last_segment) {
+ ras_cp_send_complete_ranging_data(rap, ras,
+ rap->cache.counter);
+ return;
+ }
+ }
+}
+
static void send_ras_segment_data(struct bt_rap *rap,
struct cs_procedure_data *proc)
{
@@ -2046,12 +2421,32 @@ static void handle_local_subevent_result(struct bt_rap *rap,
/* Ensure first segment body starts with the 4-byte RangingHeader */
ras_maybe_prepend_ranging_header(proc);
- if (subevt_done_status != SUBEVENT_DONE_PARTIAL_RESULTS)
- /* Send RAS raw segment data */
- send_ras_segment_data(rap, proc);
+ if (subevt_done_status != SUBEVENT_DONE_PARTIAL_RESULTS) {
+ struct ras *ras = rap->lrapdb ? rap->lrapdb->ras : NULL;
+
+ /* Real-time Ranging mode
+ */
+ if (!ras_is_ondemand_mode(ras))
+ send_ras_segment_data(rap, proc);
+ }
/* Procedure complete? Clean up */
if (proc_done_status == CS_PROC_ALL_RESULTS_COMPLETE) {
+ struct ras *ras = rap->lrapdb ? rap->lrapdb->ras : NULL;
+
+ if (ras_is_ondemand_mode(ras)) {
+ if (rap->cache.valid) {
+ uint16_t evicted_counter = rap->cache.counter;
+
+ ras_ondemand_cache_clear(rap);
+ ras_send_ranging_data_overwritten(rap, ras,
+ evicted_counter);
+ }
+
+ ras_ondemand_cache_store(rap, proc);
+ ras_send_ranging_data_ready(rap, ras, proc->counter);
+ }
+
DBG(rap, "Destroying CsProcedureData counter=%u and "
"clearing current_proc", proc->counter);
resptracker_reset_current_proc(resptracker);
@@ -2524,6 +2919,16 @@ void bt_rap_set_conn_interval(struct bt_rap *rap, uint16_t conn_interval)
rap->conn_interval = conn_interval;
}
+bool bt_rap_set_ondemand_ranging(struct bt_rap *rap, bool enable)
+{
+ if (!rap)
+ return false;
+
+ rap->ondemand_mode = enable;
+
+ return true;
+}
+
struct bt_rap *bt_rap_new(struct gatt_db *ldb, struct gatt_db *rdb)
{
struct bt_rap *rap;
@@ -2656,6 +3061,91 @@ static unsigned int bt_rap_register_notify(struct bt_rap *rap,
return notify->id;
}
+static void ras_ondemand_client_reset(struct ras *ras)
+{
+ if (!ras)
+ return;
+
+ if (ras->client.cp_timeout_id) {
+ timeout_remove(ras->client.cp_timeout_id);
+ ras->client.cp_timeout_id = 0;
+ }
+
+ ras->client.pending_op = RAS_CP_PENDING_NONE;
+ ras->client.pending_counter = 0;
+}
+
+/* Initiator role: fires when the peer never answers an outstanding
+ * Get_Ranging_Data/ACK_Ranging_Data request. Without this, a dropped
+ * notification would leave client.pending_op stuck forever, silently
+ * blocking all future Ranging Data Ready handling for this connection.
+ */
+static bool ras_cp_timeout_cb(void *user_data)
+{
+ struct bt_rap *rap = user_data;
+ struct ras *ras = rap_get_ras(rap);
+
+ if (ras) {
+ DBG(rap, "RAS CP response timed out (pending_op=%d "
+ "counter=%u) - resetting", ras->client.pending_op,
+ ras->client.pending_counter);
+
+ /* The glib source is about to be destroyed on return; avoid
+ * ras_ondemand_client_reset() also calling timeout_remove()
+ * on it.
+ */
+ ras->client.cp_timeout_id = 0;
+ ras_ondemand_client_reset(ras);
+ }
+
+ return false;
+}
+
+static void ras_cp_arm_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_CP_RESPONSE_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;
+
+ if (!rap || !ras || !ras->cp_chrc || !rap->client)
+ return false;
+
+ if (!gatt_db_attribute_get_char_data(ras->cp_chrc, NULL,
+ &value_handle, NULL, NULL, NULL))
+ return false;
+
+ value[0] = opcode;
+ put_le16(counter, value + 1);
+
+ return bt_gatt_client_write_without_response(rap->client,
+ value_handle, false,
+ value, sizeof(value));
+}
+
+static bool ras_cp_send_get_ranging_data(struct bt_rap *rap, struct ras *ras,
+ uint16_t counter)
+{
+ return ras_cp_write_op(rap, ras, RAS_CP_OP_GET_RANGING_DATA, counter);
+}
+
+static bool ras_cp_send_ack_ranging_data(struct bt_rap *rap, struct ras *ras,
+ uint16_t counter)
+{
+ return ras_cp_write_op(rap, ras, RAS_CP_OP_ACK_RANGING_DATA, counter);
+}
+
static inline bool parse_segmentation_header(struct iovec *iov,
struct segmentation_header *s)
{
@@ -3138,7 +3628,7 @@ static void parse_ras_data_segments(struct bt_rap *rap,
static bool process_first_segment(struct bt_rap *rap,
struct cstracker *reqtracker,
- struct iovec *iov, bool last_segment)
+ struct iovec *iov)
{
uint16_t counter_config_val;
int8_t selected_tx_power;
@@ -3180,16 +3670,11 @@ static bool process_first_segment(struct bt_rap *rap,
"with %zu bytes", iov->iov_len);
}
- if (!last_segment)
- return false;
-
- DBG(rap, "Single-segment: first=1, last=1");
return true;
}
-static void ras_realtime_notify_cb(struct bt_rap *rap, uint16_t value_handle,
- const uint8_t *value, uint16_t length,
- void *user_data)
+static void ras_reassemble_segment_notify(struct bt_rap *rap,
+ const uint8_t *value, uint16_t length)
{
struct iovec iov = { .iov_base = (void *)value, .iov_len = length };
struct segmentation_header seg_hdr;
@@ -3200,9 +3685,6 @@ static void ras_realtime_notify_cb(struct bt_rap *rap, uint16_t value_handle,
return;
}
- DBG(rap, "Received real-time notification: handle=0x%04x len=%u",
- value_handle, length);
-
if (!parse_segmentation_header(&iov, &seg_hdr)) {
DBG(rap, "Failed to parse segmentation header");
return;
@@ -3220,14 +3702,40 @@ static void ras_realtime_notify_cb(struct bt_rap *rap, uint16_t value_handle,
reqtracker = rap->reqtracker;
if (seg_hdr.first_segment) {
- if (!process_first_segment(rap, reqtracker, &iov,
- seg_hdr.last_segment))
+ if (!process_first_segment(rap, reqtracker, &iov))
return;
+
+ reqtracker->reassembly_active = true;
+ reqtracker->expected_segment_counter =
+ (seg_hdr.rolling_segment_counter + 1) & 0x3F;
} else {
+ if (!reqtracker->reassembly_active) {
+ DBG(rap, "Continuation segment with no first "
+ "segment received this cycle - dropping");
+ return;
+ }
+
+ if (seg_hdr.rolling_segment_counter !=
+ reqtracker->expected_segment_counter) {
+ DBG(rap, "Segment counter gap (got=%u want=%u) - "
+ "discarding in-progress transfer",
+ seg_hdr.rolling_segment_counter,
+ reqtracker->expected_segment_counter);
+ free(reqtracker->segment_data.iov_base);
+ reqtracker->segment_data.iov_base = NULL;
+ reqtracker->segment_data.iov_len = 0;
+ reqtracker->reassembly_active = false;
+ return;
+ }
+
+ reqtracker->expected_segment_counter =
+ (seg_hdr.rolling_segment_counter + 1) & 0x3F;
+
if (iov.iov_len > 0) {
if (!util_iov_append(&reqtracker->segment_data,
iov.iov_base, iov.iov_len)) {
DBG(rap, "Failed to append segment data");
+ reqtracker->reassembly_active = false;
return;
}
DBG(rap, "Continuation segment: appended "
@@ -3237,11 +3745,197 @@ static void ras_realtime_notify_cb(struct bt_rap *rap, uint16_t value_handle,
}
}
- /* Last segment: parse complete RAS data */
+ /* Last segment: parse the RAS data accumulated so far.
+ *
+ * Do NOT clear reassembly_active here. On real hardware a single
+ * ranging procedure's data can arrive as several ATT-level
+ * first/last segment cycles - one per subevent - with the 4-byte
+ * Ranging Header (and first_segment=1) sent only once, on the very
+ * first cycle. Later cycles are continuation-only (first_segment=0)
+ * even though they each close with last_segment=1. Leaving
+ * reassembly_active set lets those later continuation segments be
+ * accepted instead of being dropped as "no first segment received".
+ * parse_ras_data_segments() already frees segment_data before
+ * returning, so the next continuation simply starts a fresh
+ * accumulation; a genuinely new procedure still resets all of this
+ * state unconditionally via its own first_segment=1.
+ */
if (seg_hdr.last_segment)
parse_ras_data_segments(rap, reqtracker);
}
+/* Real-time and on-demand notifications share identical segment
+ * reassembly (see ras_reassemble_segment_notify()); the only difference
+ * is the log label, passed in via user_data at registration time. For
+ * on-demand, the last segment does not itself trigger ACK, the ACK is
+ * driven by the RAS Control Point's Complete_Ranging_Data response
+ * (see ras_cp_response_notify_cb()).
+ */
+static void ras_segment_notify_cb(struct bt_rap *rap, uint16_t value_handle,
+ const uint8_t *value, uint16_t length,
+ void *user_data)
+{
+ const char *label = user_data ? user_data : "real-time";
+
+ DBG(rap, "Received %s notification: handle=0x%04x len=%u",
+ label, value_handle, length);
+
+ ras_reassemble_segment_notify(rap, value, length);
+}
+
+/* Initiator role: reacts to a Ranging Data Ready notification/indication
+ * by requesting the newly-available data via Get_Ranging_Data.
+ */
+static void ras_data_ready_notify_cb(struct bt_rap *rap, uint16_t value_handle,
+ const uint8_t *value, uint16_t length,
+ void *user_data)
+{
+ struct ras *ras = rap_get_ras(rap);
+ uint16_t counter;
+
+ if (!rap || !ras || length < 2) {
+ DBG(rap, "Invalid Ranging Data Ready notification");
+ return;
+ }
+
+ counter = get_le16(value);
+
+ if (ras->client.pending_op != RAS_CP_PENDING_NONE) {
+ DBG(rap, "CP busy (pending_op=%d pending_counter=%u), "
+ "dropping Ready for counter=%u", ras->client.pending_op,
+ ras->client.pending_counter, counter);
+ return;
+ }
+
+ ras->client.pending_op = RAS_CP_PENDING_GET;
+ ras->client.pending_counter = counter;
+
+ if (!ras_cp_send_get_ranging_data(rap, ras, counter)) {
+ DBG(rap, "Failed to send Get_Ranging_Data for counter=%u",
+ counter);
+ ras_ondemand_client_reset(ras);
+ return;
+ }
+
+ ras_cp_arm_timeout(rap, ras);
+}
+
+/* Initiator role: reacts to a Ranging Data Overwritten notification
+ * by abandoning any pending request for that counter.
+ */
+static void ras_data_overwritten_notify_cb(struct bt_rap *rap,
+ uint16_t value_handle,
+ const uint8_t *value, uint16_t length,
+ void *user_data)
+{
+ struct ras *ras = rap_get_ras(rap);
+ uint16_t counter;
+
+ if (!rap || !ras || length < 2) {
+ DBG(rap, "Invalid Ranging Data Overwritten notification");
+ return;
+ }
+
+ counter = get_le16(value);
+
+ DBG(rap, "Ranging Data Overwritten: counter=%u", counter);
+
+ if (ras->client.pending_counter == counter)
+ ras_ondemand_client_reset(ras);
+}
+
+/* Initiator role: handles RAS Control Point responses sent by the remote
+ * reflector.
+ */
+static void ras_cp_response_notify_cb(struct bt_rap *rap,
+ uint16_t value_handle,
+ const uint8_t *value, uint16_t length,
+ void *user_data)
+{
+ struct ras *ras = rap_get_ras(rap);
+ uint16_t counter;
+ uint8_t code;
+
+ if (!rap || !ras || length < 2) {
+ DBG(rap, "Invalid RAS Control Point response");
+ return;
+ }
+
+ switch (value[0]) {
+ case RAS_CP_RSP_COMPLETE_RANGING_DATA:
+ if (ras->client.pending_op != RAS_CP_PENDING_GET) {
+ DBG(rap, "Unexpected Complete_Ranging_Data "
+ "(pending_op=%d)", ras->client.pending_op);
+ return;
+ }
+
+ if (length < 3) {
+ DBG(rap, "Complete_Ranging_Data too short (len=%u)",
+ length);
+ ras_ondemand_client_reset(ras);
+ return;
+ }
+
+ counter = get_le16(value + 1);
+ if (counter != ras->client.pending_counter) {
+ DBG(rap, "Complete_Ranging_Data counter mismatch "
+ "(got=%u want=%u)", counter,
+ ras->client.pending_counter);
+ ras_ondemand_client_reset(ras);
+ return;
+ }
+
+ ras->client.pending_op = RAS_CP_PENDING_ACK;
+ if (!ras_cp_send_ack_ranging_data(rap, ras, counter))
+ ras_ondemand_client_reset(ras);
+ else
+ ras_cp_arm_timeout(rap, ras);
+ break;
+ case RAS_CP_RSP_RESPONSE_CODE:
+ code = value[1];
+ 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);
+ break;
+ default:
+ DBG(rap, "Unknown RAS CP response opcode 0x%02x", value[0]);
+ ras_ondemand_client_reset(ras);
+ break;
+ }
+}
+
+/* Initiator role: subscribe to a single on-demand-related characteristic,
+ * logging (not failing) on any missing attribute/handle so a
+ * partially-capable peer doesn't block subscription to the others.
+ */
+static void rap_subscribe_ondemand_chrc(struct bt_rap *rap,
+ struct gatt_db_attribute *chrc,
+ rap_notify_t func, const char *name)
+{
+ uint16_t value_handle;
+ unsigned int notify_id;
+
+ if (!chrc) {
+ DBG(rap, "%s characteristic not found on peer", name);
+ return;
+ }
+
+ if (!gatt_db_attribute_get_char_data(chrc, NULL, &value_handle,
+ NULL, NULL, NULL)) {
+ DBG(rap, "Unable to resolve %s value handle", name);
+ return;
+ }
+
+ notify_id = bt_rap_register_notify(rap, value_handle, 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);
+}
+
static void read_ras_features(struct bt_rap *rap, bool success,
uint8_t att_ecode,
const uint8_t *value, uint16_t length,
@@ -3293,32 +3987,53 @@ static void read_ras_features(struct bt_rap *rap, bool success,
DBG(rap, "RAS features read successfully, capabilities determined");
- /* Register for real-time characteristic notifications if supported */
- if (supports_realtime && ras && ras->realtime_chrc && rap->client) {
- uint16_t value_handle;
- bt_uuid_t uuid;
-
- if (gatt_db_attribute_get_char_data(ras->realtime_chrc,
- NULL, &value_handle,
- NULL, NULL, &uuid)) {
- unsigned int notify_id;
-
- notify_id = bt_rap_register_notify(rap,
- value_handle,
- ras_realtime_notify_cb,
- NULL);
- if (!notify_id)
- DBG(rap, "Failed to register for "
- "real-time notifications");
- else
- DBG(rap, "Registered for real-time "
- "features: id=%u", notify_id);
+ if (!ras || !rap->client) {
+ DBG(rap, "No remote RAS attributes/client to subscribe to");
+ 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);
+ }
}
- } else {
- DBG(rap, "On demand ranging "
- "remote device - skipping notification "
- "registration");
+
+ 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");
}
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 b0cf307c2..9ab577710 100644
--- a/src/shared/rap.h
+++ b/src/shared/rap.h
@@ -297,3 +297,9 @@ void bt_rap_set_local_sw_time(struct bt_rap *rap, uint8_t local_sw_time);
void bt_rap_set_remote_sw_time(struct bt_rap *rap, uint8_t remote_sw_time);
void bt_rap_set_conn_interval(struct bt_rap *rap, uint16_t conn_interval);
+
+/* Initiator role: select real-time (false, default) or on-demand (true)
+ * ranging data delivery. Must be called before bt_rap_attach() drives
+ * GATT discovery to completion to take effect.
+ */
+bool bt_rap_set_ondemand_ranging(struct bt_rap *rap, bool enable);
--
^ permalink raw reply related [flat|nested] 8+ messages in thread