Repository: nrfconnect/sdk-nrf
Pull request #21514: bluetooth: services: Implement real-time ranging data service
Author: login: olivier-le-sage
Labels: name: doc-required
description: PR must not be merged without tech writer approval.
name: changelog-entry-required
description: Update changelog before merge. Remove label if entry is not needed or already added.
Created: 2025-04-08T10:38:42Z | Merged: 2025-04-24T15:06:35Z

See commit messages.


Diff:
diff --git a/doc/nrf/libraries/bluetooth/services/rreq.rst b/doc/nrf/libraries/bluetooth/services/rreq.rst
index 263ab37bec57..99fe79331005 100644
--- a/doc/nrf/libraries/bluetooth/services/rreq.rst
+++ b/doc/nrf/libraries/bluetooth/services/rreq.rst
@@ -12,7 +12,7 @@ Overview
 
 This library implements the Ranging Requestor for Channel Sounding with the corresponding set of characteristics defined in the `Ranging Service Specification`_ and the `Ranging Profile Specification`_.
 
-This library supports On Demand Ranging Data.
+This library supports On Demand Ranging Data and Real-time Ranging Data.
 
 Configuration
 *************
diff --git a/doc/nrf/libraries/bluetooth/services/rrsp.rst b/doc/nrf/libraries/bluetooth/services/rrsp.rst
index e4cc632d74f0..7e1249a692b6 100644
--- a/doc/nrf/libraries/bluetooth/services/rrsp.rst
+++ b/doc/nrf/libraries/bluetooth/services/rrsp.rst
@@ -12,7 +12,7 @@ Overview
 
 This library implements the Ranging Responder for Channel Sounding with the corresponding set of characteristics defined in the `Ranging Service Specification`_ and the `Ranging Profile Specification`_.
 
-This library supports On Demand Ranging Data.
+This library supports On Demand Ranging Data and Real-time Ranging Data.
 
 Configuration
 *************
diff --git a/include/bluetooth/services/ras.h b/include/bluetooth/services/ras.h
index 24331d7b6643..5901e88cc397 100644
--- a/include/bluetooth/services/ras.h
+++ b/include/bluetooth/services/ras.h
@@ -90,6 +90,14 @@ extern "C" {
 	 (BT_RAS_MAX_STEPS_PER_PROCEDURE * BT_RAS_STEP_MODE_LEN) +                                 \
 	 (BT_RAS_MAX_STEPS_PER_PROCEDURE * BT_RAS_MAX_STEP_DATA_LEN))
 
+/** @brief RAS Features as defined in RAS Specification, Table 3.3. */
+enum ras_feat {
+	RAS_FEAT_REALTIME_RD          = BIT(0),
+	RAS_FEAT_RETRIEVE_LOST_RD_SEG = BIT(1),
+	RAS_FEAT_ABORT_OP             = BIT(2),
+	RAS_FEAT_FILTER_RD            = BIT(3),
+};
+
 /** @brief Ranging Header structure as defined in RAS Specification, Table 3.7. */
 struct ras_ranging_header {
 	/** Ranging Counter is lower 12-bits of CS Procedure_Counter provided by the Core Controller
@@ -329,15 +337,15 @@ typedef void (*bt_ras_rreq_rd_ready_cb_t)(struct bt_conn *conn, uint16_t ranging
  */
 typedef void (*bt_ras_rreq_rd_overwritten_cb_t)(struct bt_conn *conn, uint16_t ranging_counter);
 
-/** @brief Ranging data get complete callback. Called when ranging data get procedure has completed.
+/** @brief Ranging data complete callback. Called when complete ranging data has been received from
+ * the peer.
  *
  * @param[in] conn            Connection Object.
- * @param[in] ranging_counter Ranging counter which has been completed.
- * @param[in] err             Error code, 0 if the ranging data get was successful. Otherwise a
- * negative error code.
+ * @param[in] ranging_counter Ranging counter that has been received.
+ * @param[in] err             Error code, 0 if successful. Otherwise a negative error code.
  */
-typedef void (*bt_ras_rreq_ranging_data_get_complete_t)(struct bt_conn *conn,
-							uint16_t ranging_counter, int err);
+typedef void (*bt_ras_rreq_ranging_data_received_t)(struct bt_conn *conn, uint16_t ranging_counter,
+						    int err);
 
 /** @brief RAS features read callback.
  *
@@ -366,11 +374,27 @@ typedef void (*bt_ras_rreq_features_read_cb_t)(struct bt_conn *conn, uint32_t fe
  */
 int bt_ras_rreq_alloc_and_assign_handles(struct bt_gatt_dm *dm, struct bt_conn *conn);
 
+/** @brief Register a callback for GATT subscriptions and unsubscriptions attempted by the RREQ.
+ *
+ * @note This callback will not be called when attempting to subscribe or unsubscribe to a
+ * characteristic to which the device is already subscribed or unsubscribed (respectively).
+ *
+ * @param[in] conn Connection object.
+ * @param[in] subscription_change_cb CCC write request response callback.
+ *
+ * @retval 0 If the operation was successful.
+ *           Otherwise, a negative error code is returned.
+ */
+int bt_ras_rreq_subscription_change_cb_register(struct bt_conn *conn,
+						bt_gatt_subscribe_func_t subscription_change_cb);
+
 /** @brief Get ranging data for given ranging counter.
  *
  * @note This should only be called after receiving a ranging data ready callback and
  * when subscribed to ondemand ranging data and RAS-CP.
  *
+ * @note Using this API is not allowed when the RAS server uses real-time ranging data.
+ *
  * @param[in] conn                 Connection Object.
  * @param[in] ranging_data_out     Simple buffer to store received ranging data.
  * @param[in] ranging_counter      Ranging counter to get.
@@ -381,7 +405,7 @@ int bt_ras_rreq_alloc_and_assign_handles(struct bt_gatt_dm *dm, struct bt_conn *
  */
 int bt_ras_rreq_cp_get_ranging_data(struct bt_conn *conn, struct net_buf_simple *ranging_data_out,
 				    uint16_t ranging_counter,
-				    bt_ras_rreq_ranging_data_get_complete_t data_get_complete_cb);
+				    bt_ras_rreq_ranging_data_received_t data_get_complete_cb);
 
 /** @brief Free RREQ context for connection. This will unsubscribe from any remaining subscriptions.
  *
@@ -421,6 +445,9 @@ int bt_ras_rreq_cp_unsubscribe(struct bt_conn *conn);
  * @note Calling from BT RX thread may return an error as bt_gatt_subscribe will not block if
  * there are no available TX buffers.
  *
+ * @note On-Demand and Real-time ranging data are not compatible and attempting to
+ *       subscribe to both at the same time will be rejected by the RAS server.
+ *
  * @param[in] conn Connection Object, which already has associated RREQ context.
  *
  * @retval 0 If the operation was successful.
@@ -492,6 +519,42 @@ int bt_ras_rreq_rd_overwritten_subscribe(struct bt_conn *conn, bt_ras_rreq_rd_ov
  */
 int bt_ras_rreq_rd_overwritten_unsubscribe(struct bt_conn *conn);
 
+/** @brief Subscribe to real-time ranging data notifications.
+ *
+ * @note Calling from BT RX thread may return an error as bt_gatt_subscribe will not block if
+ * there are no available TX buffers.
+ *
+ * @note The ranging_data_out buffer is automatically reset after the data_received_cb
+ *       callback.
+ *
+ * @note On-Demand and Real-time ranging data are not compatible and attempting to
+ *       subscribe to both at the same time will be rejected by the RAS server.
+ *
+ * @note The data callback will be called many times (for as long as the RRSP continues
+ *       to send notifications).
+ *
+ * @param[in] conn Connection Object that already has an associated RREQ context.
+ * @param[in] ranging_data_out     Simple buffer to store received ranging data.
+ * @param[in] data_received_cb     Callback called when complete ranging data is received.
+ *
+ * @retval 0 If the operation was successful.
+ *           Otherwise, a negative error code is returned.
+ */
+int bt_ras_rreq_realtime_rd_subscribe(struct bt_conn *conn, struct net_buf_simple *ranging_data_out,
+				      bt_ras_rreq_ranging_data_received_t data_received_cb);
+
+/** @brief Unsubscribe from real-time ranging data notifications.
+ *
+ * @note Calling from BT RX thread may return an error as bt_gatt_unsubscribe will not block if
+ * there are no available TX buffers.
+ *
+ * @param[in] conn Connection Object that already has an associated RREQ context.
+ *
+ * @retval 0 If the operation was successful.
+ *           Otherwise, a negative error code is returned.
+ */
+int bt_ras_rreq_realtime_rd_unsubscribe(struct bt_conn *conn);
+
 /** @brief Read supported RAS features from peer.
  *
  * @note Calling from BT RX thread may return an error as bt_gatt_read will not block if
diff --git a/samples/bluetooth/channel_sounding_ras_initiator/src/main.c b/samples/bluetooth/channel_sounding_ras_initiator/src/main.c
index d6e362d86de8..7fd88f034c25 100644
--- a/samples/bluetooth/channel_sounding_ras_initiator/src/main.c
+++ b/samples/bluetooth/channel_sounding_ras_initiator/src/main.c
@@ -44,6 +44,7 @@ static K_SEM_DEFINE(sem_connected, 0, 1);
 static K_SEM_DEFINE(sem_discovery_done, 0, 1);
 static K_SEM_DEFINE(sem_mtu_exchange_done, 0, 1);
 static K_SEM_DEFINE(sem_security, 0, 1);
+static K_SEM_DEFINE(sem_ras_features, 0, 1);
 static K_SEM_DEFINE(sem_local_steps, 1, 1);
 
 static K_MUTEX_DEFINE(distance_estimate_buffer_mutex);
@@ -53,6 +54,7 @@ NET_BUF_SIMPLE_DEFINE_STATIC(latest_local_steps, LOCAL_PROCEDURE_MEM);
 NET_BUF_SIMPLE_DEFINE_STATIC(latest_peer_steps, BT_RAS_PROCEDURE_MEM);
 static int32_t most_recent_local_ranging_counter = PROCEDURE_COUNTER_NONE;
 static int32_t dropped_ranging_counter = PROCEDURE_COUNTER_NONE;
+static uint32_t ras_feature_bits;
 
 static uint8_t buffer_index;
 static uint8_t buffer_num_valid;
@@ -121,17 +123,26 @@ static cs_de_dist_estimates_t get_distance(uint8_t ap)
 	return averaged_result;
 }
 
-static void ranging_data_get_complete_cb(struct bt_conn *conn, uint16_t ranging_counter, int err)
+static void ranging_data_cb(struct bt_conn *conn, uint16_t ranging_counter, int err)
 {
 	ARG_UNUSED(conn);
 
 	if (err) {
-		LOG_ERR("Error when getting ranging data with ranging counter %d (err %d)",
+		LOG_ERR("Error when receiving ranging data with ranging counter %d (err %d)",
 			ranging_counter, err);
 		return;
 	}
 
-	LOG_DBG("Ranging data get completed for ranging counter %d", ranging_counter);
+	if (ranging_counter != most_recent_local_ranging_counter) {
+		LOG_INF("Ranging data dropped as peer ranging counter doesn't match local ranging "
+			"data counter. (peer: %u, local: %u)",
+			ranging_counter, most_recent_local_ranging_counter);
+		net_buf_simple_reset(&latest_local_steps);
+		k_sem_give(&sem_local_steps);
+		return;
+	}
+
+	LOG_DBG("Ranging data received for ranging counter %d", ranging_counter);
 
 	/* This struct is static to avoid putting it on the stack (it's very large) */
 	static cs_de_report_t cs_de_report;
@@ -140,7 +151,11 @@ static void ranging_data_get_complete_cb(struct bt_conn *conn, uint16_t ranging_
 			      &cs_de_report);
 
 	net_buf_simple_reset(&latest_local_steps);
-	net_buf_simple_reset(&latest_peer_steps);
+
+	if (ras_feature_bits & RAS_FEAT_REALTIME_RD) {
+		net_buf_simple_reset(&latest_peer_steps);
+	}
+
 	k_sem_give(&sem_local_steps);
 
 	cs_de_quality_t quality = cs_de_calc(&cs_de_report);
@@ -166,7 +181,7 @@ static void subevent_result_cb(struct bt_conn *conn, struct bt_conn_le_cs_subeve
 
 		if (sem_state < 0) {
 			dropped_ranging_counter = result->header.procedure_counter;
-			LOG_DBG("Dropped subevent results due to unfinished ranging data request.");
+			LOG_INF("Dropped subevent results. Waiting for ranging data from peer.");
 			return;
 		}
 
@@ -211,7 +226,7 @@ static void ranging_data_ready_cb(struct bt_conn *conn, uint16_t ranging_counter
 	if (ranging_counter == most_recent_local_ranging_counter) {
 		int err = bt_ras_rreq_cp_get_ranging_data(connection, &latest_peer_steps,
 							  ranging_counter,
-							  ranging_data_get_complete_cb);
+							  ranging_data_cb);
 		if (err) {
 			LOG_ERR("Get ranging data failed (err %d)", err);
 			net_buf_simple_reset(&latest_local_steps);
@@ -404,6 +419,18 @@ static void procedure_enable_cb(struct bt_conn *conn,
 	}
 }
 
+void ras_features_read_cb(struct bt_conn *conn, uint32_t feature_bits, int err)
+{
+	if (err) {
+		LOG_WRN("Error while reading RAS feature bits (err %d)", err);
+	} else {
+		LOG_INF("Read RAS feature bits: 0x%x", feature_bits);
+		ras_feature_bits = feature_bits;
+	}
+
+	k_sem_give(&sem_ras_features);
+}
+
 static void scan_filter_match(struct bt_scan_device_info *device_info,
 			      struct bt_scan_filter_match *filter_match, bool connectable)
 {
@@ -534,28 +561,48 @@ int main(void)
 		return 0;
 	}
 
-	err = bt_ras_rreq_rd_overwritten_subscribe(connection, ranging_data_overwritten_cb);
+	err = bt_ras_rreq_read_features(connection, ras_features_read_cb);
 	if (err) {
-		LOG_ERR("RAS RREQ ranging data overwritten subscribe failed (err %d)", err);
+		LOG_ERR("Could not get RAS features from peer (err %d)", err);
 		return 0;
 	}
 
-	err = bt_ras_rreq_rd_ready_subscribe(connection, ranging_data_ready_cb);
-	if (err) {
-		LOG_ERR("RAS RREQ ranging data ready subscribe failed (err %d)", err);
-		return 0;
-	}
+	k_sem_take(&sem_ras_features, K_FOREVER);
 
-	err = bt_ras_rreq_on_demand_rd_subscribe(connection);
-	if (err) {
-		LOG_ERR("RAS RREQ On-demand ranging data subscribe failed (err %d)", err);
-		return 0;
-	}
+	const bool realtime_rd = ras_feature_bits & RAS_FEAT_REALTIME_RD;
 
-	err = bt_ras_rreq_cp_subscribe(connection);
-	if (err) {
-		LOG_ERR("RAS RREQ CP subscribe failed (err %d)", err);
-		return 0;
+	if (realtime_rd) {
+		err = bt_ras_rreq_realtime_rd_subscribe(connection,
+							&latest_peer_steps,
+							ranging_data_cb);
+		if (err) {
+			LOG_ERR("RAS RREQ Real-time ranging data subscribe failed (err %d)", err);
+			return 0;
+		}
+	} else {
+		err = bt_ras_rreq_rd_overwritten_subscribe(connection, ranging_data_overwritten_cb);
+		if (err) {
+			LOG_ERR("RAS RREQ ranging data overwritten subscribe failed (err %d)", err);
+			return 0;
+		}
+
+		err = bt_ras_rreq_rd_ready_subscribe(connection, ranging_data_ready_cb);
+		if (err) {
+			LOG_ERR("RAS RREQ ranging data ready subscribe failed (err %d)", err);
+			return 0;
+		}
+
+		err = bt_ras_rreq_on_demand_rd_subscribe(connection);
+		if (err) {
+			LOG_ERR("RAS RREQ On-demand ranging data subscribe failed (err %d)", err);
+			return 0;
+		}
+
+		err = bt_ras_rreq_cp_subscribe(connection);
+		if (err) {
+			LOG_ERR("RAS RREQ CP subscribe failed (err %d)", err);
+			return 0;
+		}
 	}
 
 	err = bt_le_cs_read_remote_supported_capabilities(connection);
@@ -605,8 +652,8 @@ int main(void)
 	const struct bt_le_cs_set_procedure_parameters_param procedure_params = {
 		.config_id = CS_CONFIG_ID,
 		.max_procedure_len = 1000,
-		.min_procedure_interval = 10,
-		.max_procedure_interval = 10,
+		.min_procedure_interval = realtime_rd ? 4 : 10,
+		.max_procedure_interval = realtime_rd ? 4 : 10,
 		.max_procedure_count = 0,
 		.min_subevent_len = 60000,
 		.max_subevent_len = 60000,
diff --git a/subsys/bluetooth/services/ras/ras_internal.h b/subsys/bluetooth/services/ras/ras_internal.h
index 3456617bf4fa..cd6181a654d3 100644
--- a/subsys/bluetooth/services/ras/ras_internal.h
+++ b/subsys/bluetooth/services/ras/ras_internal.h
@@ -57,14 +57,6 @@ enum rascp_rsp_code {
 	RASCP_RESPONSE_NO_RECORDS_FOUND        = 0x08,
 };
 
-/** @brief RAS Features as defined in RAS Specification, Table 3.3. */
-enum ras_feat {
-	RAS_FEAT_REALTIME_RD          = BIT(0),
-	RAS_FEAT_RETRIEVE_LOST_RD_SEG = BIT(1),
-	RAS_FEAT_ABORT_OP             = BIT(2),
-	RAS_FEAT_FILTER_RD            = BIT(3),
-};
-
 /** @brief RAS ATT Application error codes as defined in RAS Specification, Table 2.1. */
 enum ras_att_error {
 	RAS_ATT_ERROR_CCC_CONFIG         = 0xFD,
diff --git a/subsys/bluetooth/services/ras/rreq/ras_rreq.c b/subsys/bluetooth/services/ras/rreq/ras_rreq.c
index f35854d288ce..b6083b65e56c 100644
--- a/subsys/bluetooth/services/ras/rreq/ras_rreq.c
+++ b/subsys/bluetooth/services/ras/rreq/ras_rreq.c
@@ -30,14 +30,16 @@ struct bt_ras_rreq_cp {
 };
 
 struct bt_ras_on_demand_rd {
-	int error_status;
-	struct bt_gatt_subscribe_params subscribe_params;
 	struct net_buf_simple *ranging_data_out;
-	bt_ras_rreq_ranging_data_get_complete_t cb;
-	uint16_t counter_in_progress;
-	uint8_t next_expected_segment_counter;
+	bt_ras_rreq_ranging_data_received_t data_cb;
+	struct bt_gatt_subscribe_params subscribe_params;
 	bool data_get_in_progress;
-	bool last_segment_received;
+};
+
+struct bt_ras_real_time_rd {
+	struct net_buf_simple *ranging_data_out;
+	bt_ras_rreq_ranging_data_received_t data_cb;
+	struct bt_gatt_subscribe_params subscribe_params;
 };
 
 struct bt_ras_rd_ready {
@@ -59,9 +61,17 @@ static struct bt_ras_rreq {
 	struct bt_conn *conn;
 	struct bt_ras_rreq_cp cp;
 	struct bt_ras_on_demand_rd on_demand_rd;
+	struct bt_ras_real_time_rd real_time_rd;
 	struct bt_ras_rd_ready rd_ready;
 	struct bt_ras_rd_overwritten rd_overwritten;
 	struct bt_ras_features_read features_read;
+
+	bt_gatt_subscribe_func_t subscribe_cb;
+	uint16_t counter_in_progress;
+	uint8_t next_expected_segment_counter;
+	bool last_segment_received;
+	int data_error_status;
+	bool realtime;
 } rreq_pool[CONFIG_BT_RAS_RREQ_MAX_ACTIVE_CONN];
 
 static struct bt_ras_rreq *ras_rreq_find(struct bt_conn *conn)
@@ -117,7 +127,7 @@ static void disconnected(struct bt_conn *conn, uint8_t reason)
 	}
 
 	if (rreq->on_demand_rd.data_get_in_progress) {
-		rreq->on_demand_rd.cb(conn, rreq->on_demand_rd.counter_in_progress, -ENOTCONN);
+		rreq->on_demand_rd.data_cb(conn, rreq->counter_in_progress, -ENOTCONN);
 	}
 
 	bt_ras_rreq_free(conn);
@@ -149,6 +159,11 @@ static uint8_t ranging_data_ready_notify_func(struct bt_conn *conn,
 		return BT_GATT_ITER_STOP;
 	}
 
+	if (rreq->realtime) {
+		LOG_DBG("Received unexpected ranging data ready indication in real-time mode.");
+		return BT_GATT_ITER_CONTINUE;
+	}
+
 	struct net_buf_simple rd_ready;
 
 	net_buf_simple_init_with_data(&rd_ready, (uint8_t *)data, length);
@@ -163,15 +178,24 @@ static uint8_t ranging_data_ready_notify_func(struct bt_conn *conn,
 
 static void data_receive_finished(struct bt_ras_rreq *rreq)
 {
-	if (rreq->on_demand_rd.error_status == 0 && !rreq->on_demand_rd.last_segment_received) {
+	if (rreq->data_error_status == 0 && !rreq->last_segment_received) {
 		LOG_WRN("Ranging data completed with missing segments");
-		rreq->on_demand_rd.error_status = -ENODATA;
+		rreq->data_error_status = -ENODATA;
 	}
 
-	rreq->on_demand_rd.data_get_in_progress = false;
+	if (rreq->realtime) {
+		rreq->real_time_rd.data_cb(rreq->conn, rreq->counter_in_progress,
+					   rreq->data_error_status);
+		net_buf_simple_reset(rreq->real_time_rd.ranging_data_out);
+	} else {
+		rreq->on_demand_rd.data_cb(rreq->conn, rreq->counter_in_progress,
+					   rreq->data_error_status);
+		rreq->on_demand_rd.data_get_in_progress = false;
+	}
 
-	rreq->on_demand_rd.cb(rreq->conn, rreq->on_demand_rd.counter_in_progress,
-			      rreq->on_demand_rd.error_status);
+	rreq->last_segment_received = false;
+	rreq->next_expected_segment_counter = 0;
+	rreq->data_error_status = 0;
 }
 
 static uint8_t ranging_data_overwritten_notify_func(struct bt_conn *conn,
@@ -196,13 +220,19 @@ static uint8_t ranging_data_overwritten_notify_func(struct bt_conn *conn,
 		return BT_GATT_ITER_STOP;
 	}
 
+	if (rreq->realtime) {
+		LOG_DBG("Received unexpected ranging data overwritten indication in real-time "
+			"mode.");
+		return BT_GATT_ITER_CONTINUE;
+	}
+
 	struct net_buf_simple rd_overwritten;
 
 	net_buf_simple_init_with_data(&rd_overwritten, (uint8_t *)data, length);
 	uint16_t ranging_counter = net_buf_simple_pull_le16(&rd_overwritten);
 
 	if (rreq->on_demand_rd.data_get_in_progress &&
-	    rreq->on_demand_rd.counter_in_progress == ranging_counter) {
+	    rreq->counter_in_progress == ranging_counter) {
 		if (rreq->cp.state != BT_RAS_RREQ_CP_STATE_NONE) {
 			LOG_DBG("Overwritten received while writing to RAS-CP, will continue "
 				"waiting for RAS-CP response");
@@ -210,7 +240,7 @@ static uint8_t ranging_data_overwritten_notify_func(struct bt_conn *conn,
 		}
 
 		LOG_DBG("Ranging counter %d overwritten whilst receiving", ranging_counter);
-		rreq->on_demand_rd.error_status = -EACCES;
+		rreq->data_error_status = -EACCES;
 		data_receive_finished(rreq);
 
 		return BT_GATT_ITER_CONTINUE;
@@ -227,7 +257,7 @@ static void ack_ranging_data(struct bt_ras_rreq *rreq)
 {
 	NET_BUF_SIMPLE_DEFINE(ack_buf, RASCP_CMD_OPCODE_LEN + sizeof(uint16_t));
 	net_buf_simple_add_u8(&ack_buf, RASCP_OPCODE_ACK_RD);
-	net_buf_simple_add_le16(&ack_buf, rreq->on_demand_rd.counter_in_progress);
+	net_buf_simple_add_le16(&ack_buf, rreq->counter_in_progress);
 
 	int err = bt_gatt_write_without_response(rreq->conn, rreq->cp.subscribe_params.value_handle,
 						 ack_buf.data, ack_buf.len, false);
@@ -240,7 +270,7 @@ static void ack_ranging_data(struct bt_ras_rreq *rreq)
 	}
 
 	rreq->cp.state = BT_RAS_RREQ_CP_STATE_ACK_RD_WRITTEN;
-	LOG_DBG("Ack Ranging data for counter %d", rreq->on_demand_rd.counter_in_progress);
+	LOG_DBG("Ack Ranging data for counter %d", rreq->counter_in_progress);
 }
 
 static void handle_rsp_code(uint8_t rsp_code, struct bt_ras_rreq *rreq)
@@ -250,8 +280,8 @@ static void handle_rsp_code(uint8_t rsp_code, struct bt_ras_rreq *rreq)
 		if (rreq->on_demand_rd.data_get_in_progress &&
 		    rsp_code == RASCP_RESPONSE_PROCEDURE_NOT_COMPLETED) {
 			LOG_DBG("Ranging counter %d aborted whilst receiving",
-				rreq->on_demand_rd.counter_in_progress);
-			rreq->on_demand_rd.error_status = -EACCES;
+				rreq->counter_in_progress);
+			rreq->data_error_status = -EACCES;
 			data_receive_finished(rreq);
 			break;
 		}
@@ -265,7 +295,7 @@ static void handle_rsp_code(uint8_t rsp_code, struct bt_ras_rreq *rreq)
 
 		if (rsp_code != RASCP_RESPONSE_SUCCESS) {
 			LOG_DBG("Get Ranging Data returned an error %d", rsp_code);
-			rreq->on_demand_rd.error_status = -ENOENT;
+			rreq->data_error_status = -ENOENT;
 			data_receive_finished(rreq);
 			break;
 		}
@@ -324,7 +354,7 @@ static uint8_t ras_cp_notify_func(struct bt_conn *conn, struct bt_gatt_subscribe
 		uint16_t ranging_counter = net_buf_simple_pull_le16(&rsp);
 
 		if (!rreq->on_demand_rd.data_get_in_progress ||
-		    rreq->on_demand_rd.counter_in_progress != ranging_counter) {
+		    rreq->counter_in_progress != ranging_counter) {
 			LOG_WRN("Received complete ranging data response with unexpected ranging "
 				"counter %d",
 				ranging_counter);
@@ -355,6 +385,63 @@ static uint8_t ras_cp_notify_func(struct bt_conn *conn, struct bt_gatt_subscribe
 	return BT_GATT_ITER_CONTINUE;
 }
 
+static void store_ranging_data_segment(struct bt_ras_rreq *rreq, const void *data, uint16_t length)
+{
+	struct net_buf_simple segment;
+
+	net_buf_simple_init_with_data(&segment, (uint8_t *)data, length);
+
+	uint8_t segmentation_header = net_buf_simple_pull_u8(&segment);
+
+	bool first_segment = segmentation_header & BIT(0);
+	bool last_segment = segmentation_header & BIT(1);
+	uint8_t rolling_segment_counter = segmentation_header >> 2;
+
+	if (first_segment && rolling_segment_counter != 0) {
+		LOG_WRN("Ranging Data notification received invalid "
+			"rolling_segment_counter %d",
+			rolling_segment_counter);
+		rreq->data_error_status = -EINVAL;
+		return;
+	}
+
+	if (rreq->realtime && first_segment) {
+		struct ras_ranging_header *ranging_header =
+			(struct ras_ranging_header *)segment.data;
+		rreq->counter_in_progress = ranging_header->ranging_counter;
+	}
+
+	if (rreq->next_expected_segment_counter != rolling_segment_counter) {
+		LOG_WRN("No support for receiving segments out of order, expected counter %d, "
+			"received counter %d",
+			rreq->next_expected_segment_counter, rolling_segment_counter);
+		rreq->data_error_status = -ENODATA;
+		return;
+	}
+
+	uint16_t ranging_data_segment_length = segment.len;
+	struct net_buf_simple *ranging_data_out = rreq->realtime
+							  ? rreq->real_time_rd.ranging_data_out
+							  : rreq->on_demand_rd.ranging_data_out;
+
+	if (net_buf_simple_tailroom(ranging_data_out) < ranging_data_segment_length) {
+		LOG_WRN("Ranging data out buffer not large enough for next segment");
+		rreq->data_error_status = -ENOMEM;
+		return;
+	}
+
+	uint8_t *ranging_data_segment =
+		net_buf_simple_pull_mem(&segment, ranging_data_segment_length);
+	net_buf_simple_add_mem(ranging_data_out, ranging_data_segment, ranging_data_segment_length);
+
+	if (last_segment) {
+		rreq->last_segment_received = true;
+	}
+
+	/* Segment counter is between 0-63. */
+	rreq->next_expected_segment_counter = (rolling_segment_counter + 1) & BIT_MASK(6);
+}
+
 static uint8_t ras_on_demand_ranging_data_notify_func(struct bt_conn *conn,
 						      struct bt_gatt_subscribe_params *params,
 						      const void *data, uint16_t length)
@@ -362,8 +449,8 @@ static uint8_t ras_on_demand_ranging_data_notify_func(struct bt_conn *conn,
 	LOG_DBG("On-demand Ranging Data notification received");
 
 	if (data == NULL) {
-		LOG_DBG("On demand ranging data unsubscribed");
-		return BT_GATT_ITER_STOP;
+		LOG_DBG("Ignored Ranging Data notification with no data.");
+		return BT_GATT_ITER_CONTINUE;
 	}
 
 	struct bt_ras_rreq *rreq = ras_rreq_find(conn);
@@ -374,74 +461,79 @@ static uint8_t ras_on_demand_ranging_data_notify_func(struct bt_conn *conn,
 		return BT_GATT_ITER_STOP;
 	}
 
+	if (rreq->on_demand_rd.data_cb == NULL || rreq->on_demand_rd.ranging_data_out == NULL) {
+		LOG_WRN("Ranging data notification received without required buffer "
+			"or callback, unsubscribing");
+		return BT_GATT_ITER_STOP;
+	}
+
 	if (!rreq->on_demand_rd.data_get_in_progress) {
 		LOG_WRN("Unexpected On-demand Ranging Data notification received");
 		return BT_GATT_ITER_CONTINUE;
 	}
 
-	if (rreq->on_demand_rd.last_segment_received) {
+	if (rreq->last_segment_received) {
 		LOG_WRN("On-demand Ranging Data notification received after last segment");
 		return BT_GATT_ITER_CONTINUE;
 	}
 
-	if (rreq->on_demand_rd.error_status) {
+	if (rreq->data_error_status) {
 		/* Already had an error receiving this ranging counter, so exit here. */
 		return BT_GATT_ITER_CONTINUE;
 	}
 
 	if (length < 2) {
 		LOG_WRN("On-demand Ranging Data notification received invalid length");
-		rreq->on_demand_rd.error_status = -EINVAL;
+		rreq->data_error_status = -EINVAL;
 		return BT_GATT_ITER_CONTINUE;
 	}
 
-	struct net_buf_simple segment;
+	store_ranging_data_segment(rreq, data, length);
 
-	net_buf_simple_init_with_data(&segment, (uint8_t *)data, length);
-
-	uint8_t segmentation_header = net_buf_simple_pull_u8(&segment);
+	return BT_GATT_ITER_CONTINUE;
+}
 
-	bool first_segment = segmentation_header & BIT(0);
-	bool last_segment = segmentation_header & BIT(1);
-	uint8_t rolling_segment_counter = segmentation_header >> 2;
+static uint8_t ras_real_time_ranging_data_notify_func(struct bt_conn *conn,
+						      struct bt_gatt_subscribe_params *params,
+						      const void *data, uint16_t length)
+{
+	LOG_DBG("Real-time Ranging Data notification received");
 
-	if (first_segment && rolling_segment_counter != 0) {
-		LOG_WRN("On-demand Ranging Data notification received invalid "
-			"rolling_segment_counter %d",
-			rolling_segment_counter);
-		rreq->on_demand_rd.error_status = -EINVAL;
+	if (data == NULL) {
+		LOG_DBG("Ignored Ranging Data notification with no data.");
 		return BT_GATT_ITER_CONTINUE;
 	}
 
-	if (rreq->on_demand_rd.next_expected_segment_counter != rolling_segment_counter) {
-		LOG_WRN("No support for receiving segments out of order, expected counter %d, "
-			"received counter %d",
-			rreq->on_demand_rd.next_expected_segment_counter, rolling_segment_counter);
-		rreq->on_demand_rd.error_status = -ENODATA;
-		return BT_GATT_ITER_CONTINUE;
+	struct bt_ras_rreq *rreq = ras_rreq_find(conn);
+
+	if (rreq == NULL) {
+		LOG_WRN("Real-time ranging data notification received without associated RREQ "
+			"context, unsubscribing");
+		return BT_GATT_ITER_STOP;
 	}
 
-	uint16_t ranging_data_segment_length = segment.len;
+	if (rreq->real_time_rd.data_cb == NULL || rreq->real_time_rd.ranging_data_out == NULL) {
+		LOG_WRN("Ranging data notification received without required buffer "
+			"or callback, unsubscribing");
+		return BT_GATT_ITER_STOP;
+	}
 
-	if (net_buf_simple_tailroom(rreq->on_demand_rd.ranging_data_out) <
-	    ranging_data_segment_length) {
-		LOG_WRN("Ranging data out buffer not large enough for next segment");
-		rreq->on_demand_rd.error_status = -ENOMEM;
+	if (rreq->data_error_status) {
+		/* Already had an error receiving this ranging counter, so exit here. */
 		return BT_GATT_ITER_CONTINUE;
 	}
 
-	uint8_t *ranging_data_segment =
-		net_buf_simple_pull_mem(&segment, ranging_data_segment_length);
-	net_buf_simple_add_mem(rreq->on_demand_rd.ranging_data_out, ranging_data_segment,
-			       ranging_data_segment_length);
-
-	if (last_segment) {
-		rreq->on_demand_rd.last_segment_received = true;
+	if (length < 2) {
+		LOG_WRN("Real-time Ranging Data notification received invalid length");
+		rreq->data_error_status = -EINVAL;
+		return BT_GATT_ITER_CONTINUE;
 	}
 
-	/* Segment counter is between 0-63. */
-	rreq->on_demand_rd.next_expected_segment_counter =
-		(rolling_segment_counter + 1) & BIT_MASK(6);
+	store_ranging_data_segment(rreq, data, length);
+
+	if (rreq->last_segment_received || rreq->data_error_status) {
+		data_receive_finished(rreq);
+	}
 
 	return BT_GATT_ITER_CONTINUE;
 }
@@ -449,8 +541,39 @@ static uint8_t ras_on_demand_ranging_data_notify_func(struct bt_conn *conn,
 static void subscribed_func(struct bt_conn *conn, uint8_t err,
 			    struct bt_gatt_subscribe_params *params)
 {
-	if (err) {
-		LOG_ERR("Subscribe to ccc_handle %d failed, err %d", params->ccc_handle, err);
+	struct bt_ras_rreq *rreq = ras_rreq_find(conn);
+
+	if (rreq) {
+		if (!err) {
+			if (params->ccc_handle == rreq->real_time_rd.subscribe_params.ccc_handle) {
+				if (params->value != 0) {
+					rreq->realtime = true;
+					LOG_DBG("Subscribed to Real-time Ranging Data");
+				} else {
+					rreq->real_time_rd.data_cb = NULL;
+					rreq->real_time_rd.ranging_data_out = NULL;
+					rreq->realtime = false;
+					LOG_DBG("Unsubscribed to Real-time Ranging Data");
+				}
+			} else if (params->ccc_handle ==
+				   rreq->on_demand_rd.subscribe_params.ccc_handle) {
+				if (params->value != 0) {
+					LOG_DBG("Subscribed to On-demand Ranging Data");
+				} else {
+					LOG_DBG("Unsubscribed to On-demand Ranging Data");
+				}
+				rreq->real_time_rd.data_cb = NULL;
+				rreq->real_time_rd.ranging_data_out = NULL;
+				rreq->realtime = false;
+			}
+		} else {
+			LOG_WRN("Subscribe to ccc_handle %d failed, err %d", params->ccc_handle,
+				err);
+		}
+
+		if (rreq->subscribe_cb) {
+			rreq->subscribe_cb(conn, err, params);
+		}
 	}
 }
 
@@ -618,6 +741,40 @@ static int ondemand_rd_subscribe_params_populate(struct bt_gatt_dm *dm, struct b
 	return 0;
 }
 
+static int realtime_rd_subscribe_params_populate(struct bt_gatt_dm *dm, struct bt_ras_rreq *rreq)
+{
+	const struct bt_gatt_dm_attr *gatt_chrc;
+	const struct bt_gatt_dm_attr *gatt_data_desc;
+	const struct bt_gatt_dm_attr *gatt_ccc_desc;
+
+	/* RAS Real-time ranging data characteristic (optional). */
+	gatt_chrc = bt_gatt_dm_char_by_uuid(dm, BT_UUID_RAS_REALTIME_RD);
+	gatt_data_desc = bt_gatt_dm_desc_by_uuid(dm, gatt_chrc, BT_UUID_RAS_REALTIME_RD);
+	gatt_ccc_desc = bt_gatt_dm_desc_by_uuid(dm, gatt_chrc, BT_UUID_GATT_CCC);
+
+	if (!gatt_chrc && !gatt_data_desc && !gatt_ccc_desc) {
+		LOG_DBG("Real-time ranging data not supported on the RAS server.");
+		return 0;
+	} else if (!gatt_chrc) {
+		LOG_WRN("Could not locate Real-time ranging data characteristic");
+		return -EINVAL;
+	} else if (!gatt_data_desc) {
+		LOG_WRN("Could not locate Real-time ranging data descriptor");
+		return -EINVAL;
+	} else if (!gatt_ccc_desc) {
+		LOG_WRN("Could not locate Real-time ranging data CCC");
+		return -EINVAL;
+	}
+
+	rreq->real_time_rd.subscribe_params.value_handle = gatt_data_desc->handle;
+	rreq->real_time_rd.subscribe_params.ccc_handle = gatt_ccc_desc->handle;
+	rreq->real_time_rd.subscribe_params.notify = ras_real_time_ranging_data_notify_func;
+	rreq->real_time_rd.subscribe_params.value = BT_GATT_CCC_NOTIFY | BT_GATT_CCC_INDICATE;
+	rreq->real_time_rd.subscribe_params.subscribe = subscribed_func;
+
+	return 0;
+}
+
 static int rd_ready_subscribe_params_populate(struct bt_gatt_dm *dm, struct bt_ras_rreq *rreq)
 {
 	const struct bt_gatt_dm_attr *gatt_chrc;
@@ -691,14 +848,14 @@ int bt_ras_rreq_on_demand_rd_subscribe(struct bt_conn *conn)
 		return -EINVAL;
 	}
 
+	rreq->on_demand_rd.subscribe_params.value = BT_GATT_CCC_NOTIFY | BT_GATT_CCC_INDICATE;
+
 	err = bt_gatt_subscribe(conn, &rreq->on_demand_rd.subscribe_params);
 	if (err && err != -EALREADY) {
 		LOG_DBG("On-demand ranging data subscribe failed (err %d)", err);
 		return err;
 	}
 
-	LOG_DBG("On-demand ranging data subscribed");
-
 	return 0;
 }
 
@@ -717,8 +874,6 @@ int bt_ras_rreq_on_demand_rd_unsubscribe(struct bt_conn *conn)
 		return err;
 	}
 
-	LOG_DBG("On-demand ranging data unsubscribed");
-
 	return 0;
 }
 
@@ -731,6 +886,8 @@ int bt_ras_rreq_rd_ready_subscribe(struct bt_conn *conn, bt_ras_rreq_rd_ready_cb
 		return -EINVAL;
 	}
 
+	rreq->real_time_rd.subscribe_params.value = BT_GATT_CCC_NOTIFY | BT_GATT_CCC_INDICATE;
+
 	err = bt_gatt_subscribe(conn, &rreq->rd_ready.subscribe_params);
 	if (err && err != -EALREADY) {
 		LOG_DBG("Ranging data ready subscribe failed (err %d)", err);
@@ -806,6 +963,49 @@ int bt_ras_rreq_rd_overwritten_unsubscribe(struct bt_conn *conn)
 	return 0;
 }
 
+int bt_ras_rreq_realtime_rd_subscribe(struct bt_conn *conn, struct net_buf_simple *ranging_data_out,
+				      bt_ras_rreq_ranging_data_received_t data_received_cb)
+{
+	int err;
+	struct bt_ras_rreq *rreq = ras_rreq_find(conn);
+
+	if (!rreq) {
+		return -EINVAL;
+	}
+
+	err = bt_gatt_subscribe(conn, &rreq->real_time_rd.subscribe_params);
+	if (err && err != -EINVAL) {
+		LOG_DBG("Real-time ranging data subscribe failed (err %d)", err);
+		return err;
+	}
+
+	if (!err) {
+		rreq->real_time_rd.data_cb = data_received_cb;
+		rreq->real_time_rd.ranging_data_out = ranging_data_out;
+		net_buf_simple_reset(ranging_data_out);
+	}
+
+	return 0;
+}
+
+int bt_ras_rreq_realtime_rd_unsubscribe(struct bt_conn *conn)
+{
+	int err;
+	struct bt_ras_rreq *rreq = ras_rreq_find(conn);
+
+	if (!rreq) {
+		return -EINVAL;
+	}
+
+	err = bt_gatt_unsubscribe(conn, &rreq->real_time_rd.subscribe_params);
+	if (err && err != -EINVAL) {
+		LOG_DBG("Real-time ranging data unsubscribe failed (err %d)", err);
+		return err;
+	}
+
+	return 0;
+}
+
 void bt_ras_rreq_free(struct bt_conn *conn)
 {
 	int err = 0;
@@ -837,6 +1037,11 @@ void bt_ras_rreq_free(struct bt_conn *conn)
 			LOG_WRN("Failed to unsubscribe to ondemand ranging data: %d", err);
 		}
 
+		err = bt_gatt_unsubscribe(conn, &rreq->real_time_rd.subscribe_params);
+		if (err != 0 && err != -EINVAL) {
+			LOG_WRN("Failed to unsubscribe to real-time ranging data: %d", err);
+		}
+
 		err = bt_gatt_unsubscribe(conn, &rreq->rd_ready.subscribe_params);
 		if (err != 0 && err != -EINVAL) {
 			LOG_WRN("Failed to unsubscribe to ranging data ready: %d", err);
@@ -875,6 +1080,12 @@ int bt_ras_rreq_alloc_and_assign_handles(struct bt_gatt_dm *dm, struct bt_conn *
 		return err;
 	}
 
+	err = realtime_rd_subscribe_params_populate(dm, rreq);
+	if (err) {
+		bt_ras_rreq_free(conn);
+		return err;
+	}
+
 	err = rd_ready_subscribe_params_populate(dm, rreq);
 	if (err) {
 		bt_ras_rreq_free(conn);
@@ -902,6 +1113,20 @@ int bt_ras_rreq_alloc_and_assign_handles(struct bt_gatt_dm *dm, struct bt_conn *
 	return 0;
 }
 
+int bt_ras_rreq_subscription_change_cb_register(struct bt_conn *conn,
+						bt_gatt_subscribe_func_t subscription_change_cb)
+{
+	struct bt_ras_rreq *rreq = ras_rreq_find(conn);
+
+	if (rreq == NULL) {
+		return -EINVAL;
+	}
+
+	rreq->subscribe_cb = subscription_change_cb;
+
+	return 0;
+}
+
 int bt_ras_rreq_read_features(struct bt_conn *conn, bt_ras_rreq_features_read_cb_t cb)
 {
 	int err;
@@ -933,7 +1158,7 @@ int bt_ras_rreq_read_features(struct bt_conn *conn, bt_ras_rreq_features_read_cb
 
 int bt_ras_rreq_cp_get_ranging_data(struct bt_conn *conn, struct net_buf_simple *ranging_data_out,
 				    uint16_t ranging_counter,
-				    bt_ras_rreq_ranging_data_get_complete_t cb)
+				    bt_ras_rreq_ranging_data_received_t cb)
 {
 	int err;
 	struct bt_ras_rreq *rreq = ras_rreq_find(conn);
@@ -942,6 +1167,10 @@ int bt_ras_rreq_cp_get_ranging_data(struct bt_conn *conn, struct net_buf_simple
 		return -EINVAL;
 	}
 
+	if (rreq->realtime) {
+		return -EACCES;
+	}
+
 	if (rreq->cp.state != BT_RAS_RREQ_CP_STATE_NONE ||
 	    rreq->on_demand_rd.data_get_in_progress) {
 		return -EBUSY;
@@ -949,15 +1178,15 @@ int bt_ras_rreq_cp_get_ranging_data(struct bt_conn *conn, struct net_buf_simple
 
 	rreq->on_demand_rd.data_get_in_progress = true;
 	rreq->on_demand_rd.ranging_data_out = ranging_data_out;
-	rreq->on_demand_rd.counter_in_progress = ranging_counter;
-	rreq->on_demand_rd.cb = cb;
-	rreq->on_demand_rd.next_expected_segment_counter = 0;
-	rreq->on_demand_rd.last_segment_received = false;
-	rreq->on_demand_rd.error_status = 0;
+	rreq->counter_in_progress = ranging_counter;
+	rreq->on_demand_rd.data_cb = cb;
+	rreq->next_expected_segment_counter = 0;
+	rreq->last_segment_received = false;
+	rreq->data_error_status = 0;
 
 	NET_BUF_SIMPLE_DEFINE(get_ranging_data, RASCP_CMD_OPCODE_LEN + sizeof(uint16_t));
 	net_buf_simple_add_u8(&get_ranging_data, RASCP_OPCODE_GET_RD);
-	net_buf_simple_add_le16(&get_ranging_data, rreq->on_demand_rd.counter_in_progress);
+	net_buf_simple_add_le16(&get_ranging_data, rreq->counter_in_progress);
 
 	err = bt_gatt_write_without_response(conn, rreq->cp.subscribe_params.value_handle,
 					     get_ranging_data.data, get_ranging_data.len, false);
@@ -978,10 +1207,8 @@ void bt_ras_rreq_rd_subevent_data_parse(struct net_buf_simple *peer_ranging_data
 					bt_ras_rreq_subevent_header_cb_t subevent_header_cb,
 					bt_ras_rreq_step_data_cb_t step_data_cb, void *user_data)
 {
-	if (!peer_ranging_data_buf
-		|| !local_step_data_buf
-		|| peer_ranging_data_buf->len == 0
-		|| local_step_data_buf->len == 0) {
+	if (!peer_ranging_data_buf || !local_step_data_buf || peer_ranging_data_buf->len == 0 ||
+	    local_step_data_buf->len == 0) {
 		LOG_ERR("Tried to parse empty step data.");
 		return;
 	}
diff --git a/subsys/bluetooth/services/ras/rrsp/ras_rd_buffer.c b/subsys/bluetooth/services/ras/rrsp/ras_rd_buffer.c
index ee38000a9c43..3816461b6232 100644
--- a/subsys/bluetooth/services/ras/rrsp/ras_rd_buffer.c
+++ b/subsys/bluetooth/services/ras/rrsp/ras_rd_buffer.c
@@ -104,8 +104,8 @@ static struct ras_rd_buffer *rd_buffer_alloc(struct bt_conn *conn, uint16_t rang
 		if (rd_buffer_pool[i].conn == conn) {
 			conn_buffer_count++;
 
-			const uint16_t ranging_counter_age = ranging_counter
-				- rd_buffer_pool[i].ranging_counter;
+			const uint16_t ranging_counter_age =
+				ranging_counter - rd_buffer_pool[i].ranging_counter;
 
 			/* Only overwrite buffers that have ranging data stored
 			 * and are not being read.
@@ -150,9 +150,8 @@ static struct ras_rd_buffer *rd_buffer_alloc(struct bt_conn *conn, uint16_t rang
 	return NULL;
 }
 
-static void cs_procedure_enable(struct bt_conn *conn,
-				uint8_t status,
-				struct bt_conn_le_cs_procedure_enable_complete *params)
+static void cs_procedure_enabled(struct bt_conn *conn, uint8_t status,
+				 struct bt_conn_le_cs_procedure_enable_complete *params)
 {
 	uint8_t conn_index = bt_conn_index(conn);
 
@@ -227,7 +226,7 @@ static void subevent_data_available(struct bt_conn *conn,
 		buf = rd_buffer_alloc(conn, result->header.procedure_counter);
 
 		if (!buf) {
-			LOG_ERR("Failed to allocate buffer for procedure %u",
+			LOG_INF("Failed to allocate buffer for procedure %u",
 				result->header.procedure_counter);
 			drop_procedure_counter[conn_index] = result->header.procedure_counter;
 
@@ -305,7 +304,7 @@ static void disconnected(struct bt_conn *conn, uint8_t reason)
 }
 
 BT_CONN_CB_DEFINE(conn_callbacks) = {
-	.le_cs_procedure_enable_complete = cs_procedure_enable,
+	.le_cs_procedure_enable_complete = cs_procedure_enabled,
 	.le_cs_subevent_data_available = subevent_data_available,
 	.disconnected = disconnected,
 };
diff --git a/subsys/bluetooth/services/ras/rrsp/ras_rrsp.c b/subsys/bluetooth/services/ras/rrsp/ras_rrsp.c
index e0e0e4557f1b..bbf05bcb1513 100644
--- a/subsys/bluetooth/services/ras/rrsp/ras_rrsp.c
+++ b/subsys/bluetooth/services/ras/rrsp/ras_rrsp.c
@@ -31,7 +31,7 @@ static struct bt_ras_rrsp {
 	struct k_work status_work;
 	struct k_timer rascp_timeout;
 
-	struct bt_gatt_indicate_params ondemand_ind_params;
+	struct bt_gatt_indicate_params ranging_data_ind_params;
 	struct bt_gatt_indicate_params rascp_ind_params;
 	struct bt_gatt_indicate_params rd_status_params;
 
@@ -50,20 +50,26 @@ static struct bt_ras_rrsp {
 } rrsp_pool[CONFIG_BT_RAS_RRSP_MAX_ACTIVE_CONN];
 
 static struct k_work_q rrsp_wq;
-static uint32_t ras_features; /* No optional features supported. */
+
+static uint32_t ras_optional_features = RAS_FEAT_REALTIME_RD;
 
 static void send_data_work_handler(struct k_work *work);
 static void rascp_work_handler(struct k_work *work);
 static void status_work_handler(struct k_work *work);
 static void rascp_timeout_handler(struct k_timer *timer);
 
-static int ondemand_rd_notify_or_indicate(struct bt_conn *conn, struct net_buf_simple *buf);
+static int ranging_data_notify_or_indicate(struct bt_conn *conn, struct net_buf_simple *buf);
 static int rd_status_notify_or_indicate(struct bt_conn *conn, const struct bt_uuid *uuid,
 					uint16_t ranging_counter);
 
 static void rascp_send_complete_rd_rsp(struct bt_conn *conn, uint16_t ranging_counter);
 static void rascp_cmd_handle(struct bt_ras_rrsp *rrsp);
 
+static ssize_t ondemand_rd_ccc_cfg_write_cb(struct bt_conn *conn, const struct bt_gatt_attr *attr,
+					    uint16_t value);
+static ssize_t realtime_rd_ccc_cfg_write_cb(struct bt_conn *conn, const struct bt_gatt_attr *attr,
+					    uint16_t value);
+
 static struct bt_ras_rrsp *rrsp_find(struct bt_conn *conn)
 {
 	for (size_t i = 0; i < ARRAY_SIZE(rrsp_pool); i++) {
@@ -131,7 +137,8 @@ void bt_ras_rrsp_free(struct bt_conn *conn)
 static ssize_t ras_features_read(struct bt_conn *conn, const struct bt_gatt_attr *attr, void *buf,
 				 uint16_t len, uint16_t offset)
 {
-	return bt_gatt_attr_read(conn, attr, buf, len, offset, &ras_features, sizeof(ras_features));
+	return bt_gatt_attr_read(conn, attr, buf, len, offset, &ras_optional_features,
+				 sizeof(ras_optional_features));
 }
 
 static ssize_t rd_ready_read(struct bt_conn *conn, const struct bt_gatt_attr *attr, void *buf,
@@ -185,11 +192,6 @@ static ssize_t ras_cp_write(struct bt_conn *conn, struct bt_gatt_attr const *att
 	return len;
 }
 
-static void ondemand_rd_ccc_cfg_changed(struct bt_gatt_attr const *attr, uint16_t value)
-{
-	LOG_DBG("On-demand Ranging Data CCCD changed: %u", value);
-}
-
 static void ras_cp_ccc_cfg_changed(struct bt_gatt_attr const *attr, uint16_t value)
 {
 	LOG_DBG("RAS-CP CCCD changed: %u", value);
@@ -205,6 +207,86 @@ static void rd_overwritten_ccc_cfg_changed(struct bt_gatt_attr const *attr, uint
 	LOG_DBG("Ranging Data Overwritten CCCD changed: %u", value);
 }
 
+BT_GATT_SERVICE_DEFINE(
+	rrsp_svc, BT_GATT_PRIMARY_SERVICE(BT_UUID_RANGING_SERVICE),
+	/* RAS Features */
+	BT_GATT_CHARACTERISTIC(BT_UUID_RAS_FEATURES, BT_GATT_CHRC_READ, BT_GATT_PERM_READ_ENCRYPT,
+			       ras_features_read, NULL, NULL),
+	/* On-demand Ranging Data */
+	BT_GATT_CHARACTERISTIC(BT_UUID_RAS_ONDEMAND_RD, BT_GATT_CHRC_INDICATE | BT_GATT_CHRC_NOTIFY,
+			       BT_GATT_PERM_NONE, NULL, NULL, NULL),
+	BT_GATT_CCC_MANAGED(((struct _bt_gatt_ccc[]){BT_GATT_CCC_INITIALIZER(
+				    NULL, ondemand_rd_ccc_cfg_write_cb, NULL)}),
+			    BT_GATT_PERM_READ_ENCRYPT | BT_GATT_PERM_WRITE_ENCRYPT),
+	/* Real-time Ranging Data */
+	BT_GATT_CHARACTERISTIC(BT_UUID_RAS_REALTIME_RD, BT_GATT_CHRC_INDICATE | BT_GATT_CHRC_NOTIFY,
+			       BT_GATT_PERM_NONE, NULL, NULL, NULL),
+	BT_GATT_CCC_MANAGED(((struct _bt_gatt_ccc[]){BT_GATT_CCC_INITIALIZER(
+				    NULL, realtime_rd_ccc_cfg_write_cb, NULL)}),
+			    BT_GATT_PERM_READ_ENCRYPT | BT_GATT_PERM_WRITE_ENCRYPT),
+	/* RAS-CP */
+	BT_GATT_CHARACTERISTIC(BT_UUID_RAS_CP,
+			       BT_GATT_CHRC_WRITE_WITHOUT_RESP | BT_GATT_CHRC_INDICATE,
+			       BT_GATT_PERM_WRITE_ENCRYPT, NULL, ras_cp_write, NULL),
+	BT_GATT_CCC(ras_cp_ccc_cfg_changed, BT_GATT_PERM_READ_ENCRYPT | BT_GATT_PERM_WRITE_ENCRYPT),
+	/* Ranging Data Ready */
+	BT_GATT_CHARACTERISTIC(BT_UUID_RAS_RD_READY,
+			       BT_GATT_CHRC_READ | BT_GATT_CHRC_INDICATE | BT_GATT_CHRC_NOTIFY,
+			       BT_GATT_PERM_READ_ENCRYPT, rd_ready_read, NULL, NULL),
+	BT_GATT_CCC(rd_ready_ccc_cfg_changed,
+		    BT_GATT_PERM_READ_ENCRYPT | BT_GATT_PERM_WRITE_ENCRYPT),
+	/* Ranging Data Overwritten */
+	BT_GATT_CHARACTERISTIC(BT_UUID_RAS_RD_OVERWRITTEN,
+			       BT_GATT_CHRC_READ | BT_GATT_CHRC_INDICATE | BT_GATT_CHRC_NOTIFY,
+			       BT_GATT_PERM_READ_ENCRYPT, rd_overwritten_read, NULL, NULL),
+	BT_GATT_CCC(rd_overwritten_ccc_cfg_changed,
+		    BT_GATT_PERM_READ_ENCRYPT | BT_GATT_PERM_WRITE_ENCRYPT),
+);
+
+static ssize_t ondemand_rd_ccc_cfg_write_cb(struct bt_conn *conn, const struct bt_gatt_attr *attr,
+					    uint16_t value)
+{
+	if (!value) {
+		LOG_DBG("Disabled On-demand Ranging Data mode.");
+	} else {
+		struct bt_gatt_attr *realtime_rd_attr =
+			bt_gatt_find_by_uuid(rrsp_svc.attrs, 0, BT_UUID_RAS_REALTIME_RD);
+
+		if (bt_gatt_is_subscribed(conn, realtime_rd_attr,
+					  BT_GATT_CCC_NOTIFY | BT_GATT_CCC_INDICATE)) {
+			LOG_DBG("On-demand Ranging Data CCCD write rejected because Real-time "
+				"Ranging Data is currently enabled.");
+			return BT_GATT_ERR(RAS_ATT_ERROR_CCC_CONFIG);
+		}
+
+		LOG_DBG("Enabled On-demand Ranging Data mode.");
+	}
+
+	return sizeof(value);
+}
+
+static ssize_t realtime_rd_ccc_cfg_write_cb(struct bt_conn *conn, const struct bt_gatt_attr *attr,
+					    uint16_t value)
+{
+	if (!value) {
+		LOG_DBG("Disabled Real-time Ranging Data mode.");
+	} else {
+		struct bt_gatt_attr *ondemand_rd_attr =
+			bt_gatt_find_by_uuid(rrsp_svc.attrs, 0, BT_UUID_RAS_ONDEMAND_RD);
+
+		if (bt_gatt_is_subscribed(conn, ondemand_rd_attr,
+					  BT_GATT_CCC_NOTIFY | BT_GATT_CCC_INDICATE)) {
+			LOG_DBG("Real-time Ranging Data CCCD write rejected because On-demand "
+				"Ranging Data is currently enabled.");
+			return BT_GATT_ERR(RAS_ATT_ERROR_CCC_CONFIG);
+		}
+
+		LOG_DBG("Enabled Real-time Ranging Data mode.");
+	}
+
+	return sizeof(value);
+}
+
 #if defined(CONFIG_BT_RAS_RRSP_AUTO_ALLOC_INSTANCE)
 static void connected(struct bt_conn *conn, uint8_t err)
 {
@@ -273,9 +355,9 @@ static int rd_segment_send(struct bt_ras_rrsp *rrsp)
 
 		(void)net_buf_simple_remove_mem(&segment_buf, (max_data_len - actual_data_len));
 
-		err = ondemand_rd_notify_or_indicate(rrsp->conn, &segment_buf);
+		err = ranging_data_notify_or_indicate(rrsp->conn, &segment_buf);
 		if (err) {
-			LOG_WRN("ondemand_rd_notify_or_indicate failed err %d", err);
+			LOG_WRN("ranging_data_notify_or_indicate failed err %d", err);
 
 			/* Keep retrying */
 			rrsp->active_buf_read_cursor -= actual_data_len;
@@ -292,10 +374,28 @@ static int rd_segment_send(struct bt_ras_rrsp *rrsp)
 		k_work_submit_to_queue(&rrsp_wq, &rrsp->send_data_work);
 	} else {
 		LOG_DBG("All segments sent");
-		rascp_send_complete_rd_rsp(rrsp->conn, rrsp->active_buf->ranging_counter);
+
 		rrsp->streaming = false;
 		k_work_cancel(&rrsp->send_data_work);
-		k_timer_start(&rrsp->rascp_timeout, RASCP_ACK_DATA_TIMEOUT, K_NO_WAIT);
+
+		struct bt_gatt_attr *ondemand_rd_attr =
+			bt_gatt_find_by_uuid(rrsp_svc.attrs, 0, BT_UUID_RAS_ONDEMAND_RD);
+
+		if (bt_gatt_is_subscribed(rrsp->conn, ondemand_rd_attr,
+					  BT_GATT_CCC_NOTIFY | BT_GATT_CCC_INDICATE)) {
+			rascp_send_complete_rd_rsp(rrsp->conn, rrsp->active_buf->ranging_counter);
+			k_timer_start(&rrsp->rascp_timeout, RASCP_ACK_DATA_TIMEOUT, K_NO_WAIT);
+		} else {
+			struct bt_gatt_attr *realtime_rd_attr =
+				bt_gatt_find_by_uuid(rrsp_svc.attrs, 0, BT_UUID_RAS_REALTIME_RD);
+
+			if (bt_gatt_is_subscribed(rrsp->conn, realtime_rd_attr,
+						  BT_GATT_CCC_NOTIFY | BT_GATT_CCC_INDICATE)) {
+				bt_ras_rd_buffer_release(rrsp->active_buf);
+				rrsp->active_buf = NULL;
+				rrsp->active_buf_read_cursor = 0;
+			}
+		}
 	}
 
 	return 0;
@@ -375,22 +475,49 @@ static void rascp_timeout_handler(struct k_timer *timer)
 	k_work_submit_to_queue(&rrsp_wq, &rrsp->status_work);
 }
 
-static void rd_ready_handle(struct bt_conn *conn, uint16_t ranging_counter)
+static void new_rd_handle(struct bt_conn *conn, uint16_t ranging_counter)
 {
 	struct bt_ras_rrsp *rrsp = rrsp_find(conn);
 
 	if (rrsp) {
-		rrsp->ready_ranging_counter = ranging_counter;
-		rrsp->notify_ready = true;
-		k_work_submit_to_queue(&rrsp_wq, &rrsp->status_work);
+		struct bt_gatt_attr *ondemand_rd_attr =
+			bt_gatt_find_by_uuid(rrsp_svc.attrs, 0, BT_UUID_RAS_ONDEMAND_RD);
+
+		if (bt_gatt_is_subscribed(conn, ondemand_rd_attr,
+					  BT_GATT_CCC_NOTIFY | BT_GATT_CCC_INDICATE)) {
+			rrsp->ready_ranging_counter = ranging_counter;
+			rrsp->notify_ready = true;
+			k_work_submit_to_queue(&rrsp_wq, &rrsp->status_work);
+		} else {
+			struct bt_gatt_attr *realtime_rd_attr =
+				bt_gatt_find_by_uuid(rrsp_svc.attrs, 0, BT_UUID_RAS_REALTIME_RD);
+
+			if (bt_gatt_is_subscribed(conn, realtime_rd_attr,
+						  BT_GATT_CCC_NOTIFY | BT_GATT_CCC_INDICATE)) {
+				if (!rrsp->streaming) {
+					rrsp->active_buf =
+						bt_ras_rd_buffer_claim(conn, ranging_counter);
+					rrsp->active_buf_read_cursor = 0;
+					rrsp->segment_counter = 0;
+					rrsp->streaming = true;
+
+					k_work_submit_to_queue(&rrsp_wq, &rrsp->send_data_work);
+				} else {
+					LOG_DBG("Dropped new ranging data.");
+				}
+			}
+		}
 	}
 }
 
 static void rd_overwritten_handle(struct bt_conn *conn, uint16_t ranging_counter)
 {
 	struct bt_ras_rrsp *rrsp = rrsp_find(conn);
+	struct bt_gatt_attr *ondemand_rd_attr =
+		bt_gatt_find_by_uuid(rrsp_svc.attrs, 0, BT_UUID_RAS_ONDEMAND_RD);
 
-	if (rrsp) {
+	if (rrsp && bt_gatt_is_subscribed(conn, ondemand_rd_attr,
+					  BT_GATT_CCC_NOTIFY | BT_GATT_CCC_INDICATE)) {
 		rrsp->overwritten_ranging_counter = ranging_counter;
 		rrsp->notify_overwritten = true;
 		k_work_submit_to_queue(&rrsp_wq, &rrsp->status_work);
@@ -398,7 +525,7 @@ static void rd_overwritten_handle(struct bt_conn *conn, uint16_t ranging_counter
 }
 
 static struct bt_ras_rd_buffer_cb rd_buffer_callbacks = {
-	.new_ranging_data_received = rd_ready_handle,
+	.new_ranging_data_received = new_rd_handle,
 	.ranging_data_overwritten = rd_overwritten_handle,
 };
 
@@ -417,43 +544,7 @@ static int ras_rrsp_init(void)
 
 SYS_INIT(ras_rrsp_init, APPLICATION, CONFIG_APPLICATION_INIT_PRIORITY);
 
-BT_GATT_SERVICE_DEFINE(rrsp_svc,
-	BT_GATT_PRIMARY_SERVICE(BT_UUID_RANGING_SERVICE),
-	/* RAS Features */
-	BT_GATT_CHARACTERISTIC(BT_UUID_RAS_FEATURES,
-				BT_GATT_CHRC_READ,
-				BT_GATT_PERM_READ_ENCRYPT, ras_features_read, NULL, NULL),
-	/* On-demand Ranging Data */
-	BT_GATT_CHARACTERISTIC(BT_UUID_RAS_ONDEMAND_RD,
-				BT_GATT_CHRC_INDICATE | BT_GATT_CHRC_NOTIFY,
-				BT_GATT_PERM_NONE,
-				NULL, NULL, NULL),
-	BT_GATT_CCC(ondemand_rd_ccc_cfg_changed,
-				BT_GATT_PERM_READ_ENCRYPT | BT_GATT_PERM_WRITE_ENCRYPT),
-	/* RAS-CP */
-	BT_GATT_CHARACTERISTIC(BT_UUID_RAS_CP,
-				BT_GATT_CHRC_WRITE_WITHOUT_RESP | BT_GATT_CHRC_INDICATE,
-				BT_GATT_PERM_WRITE_ENCRYPT,
-				NULL, ras_cp_write, NULL),
-	BT_GATT_CCC(ras_cp_ccc_cfg_changed,
-				BT_GATT_PERM_READ_ENCRYPT | BT_GATT_PERM_WRITE_ENCRYPT),
-	/* Ranging Data Ready */
-	BT_GATT_CHARACTERISTIC(BT_UUID_RAS_RD_READY,
-				BT_GATT_CHRC_READ | BT_GATT_CHRC_INDICATE | BT_GATT_CHRC_NOTIFY,
-				BT_GATT_PERM_READ_ENCRYPT,
-				rd_ready_read, NULL, NULL),
-	BT_GATT_CCC(rd_ready_ccc_cfg_changed,
-				BT_GATT_PERM_READ_ENCRYPT | BT_GATT_PERM_WRITE_ENCRYPT),
-	/* Ranging Data Overwritten */
-	BT_GATT_CHARACTERISTIC(BT_UUID_RAS_RD_OVERWRITTEN,
-				BT_GATT_CHRC_READ | BT_GATT_CHRC_INDICATE | BT_GATT_CHRC_NOTIFY,
-				BT_GATT_PERM_READ_ENCRYPT,
-				rd_overwritten_read, NULL, NULL),
-	BT_GATT_CCC(rd_overwritten_ccc_cfg_changed,
-				BT_GATT_PERM_READ_ENCRYPT | BT_GATT_PERM_WRITE_ENCRYPT),
-);
-
-static void ondemand_rd_notify_sent_cb(struct bt_conn *conn, void *user_data)
+static void ranging_data_notify_sent_cb(struct bt_conn *conn, void *user_data)
 {
 	struct bt_ras_rrsp *rrsp = rrsp_find(conn);
 
@@ -463,8 +554,8 @@ static void ondemand_rd_notify_sent_cb(struct bt_conn *conn, void *user_data)
 	}
 }
 
-static void ondemand_rd_indicate_sent_cb(struct bt_conn *conn,
-					 struct bt_gatt_indicate_params *params, uint8_t err)
+static void ranging_data_indicate_sent_cb(struct bt_conn *conn,
+					  struct bt_gatt_indicate_params *params, uint8_t err)
 {
 	struct bt_ras_rrsp *rrsp = rrsp_find(conn);
 
@@ -474,10 +565,24 @@ static void ondemand_rd_indicate_sent_cb(struct bt_conn *conn,
 	}
 }
 
-static int ondemand_rd_notify_or_indicate(struct bt_conn *conn, struct net_buf_simple *buf)
+static int ranging_data_notify_or_indicate(struct bt_conn *conn, struct net_buf_simple *buf)
 {
-	struct bt_gatt_attr *attr =
-		bt_gatt_find_by_uuid(rrsp_svc.attrs, 0, BT_UUID_RAS_ONDEMAND_RD);
+	struct bt_gatt_attr *attr;
+
+	struct bt_ras_rrsp *rrsp = rrsp_find(conn);
+
+	__ASSERT_NO_MSG(rrsp);
+
+	attr = bt_gatt_find_by_uuid(rrsp_svc.attrs, 0, BT_UUID_RAS_REALTIME_RD);
+
+	if (!bt_gatt_is_subscribed(conn, attr, BT_GATT_CCC_INDICATE | BT_GATT_CCC_NOTIFY)) {
+		attr = bt_gatt_find_by_uuid(rrsp_svc.attrs, 0, BT_UUID_RAS_ONDEMAND_RD);
+	}
+
+	if (!bt_gatt_is_subscribed(conn, attr, BT_GATT_CCC_INDICATE | BT_GATT_CCC_NOTIFY)) {
+		LOG_WRN("Peer has not enabled Real-time or On-demand ranging data.");
+		return -EINVAL;
+	}
 
 	__ASSERT_NO_MSG(attr);
 
@@ -488,7 +593,7 @@ static int ondemand_rd_notify_or_indicate(struct bt_conn *conn, struct net_buf_s
 		params.uuid = NULL;
 		params.data = buf->data;
 		params.len = buf->len;
-		params.func = ondemand_rd_notify_sent_cb;
+		params.func = ranging_data_notify_sent_cb;
 
 		return bt_gatt_notify_cb(conn, &params);
 	} else if (bt_gatt_is_subscribed(conn, attr, BT_GATT_CCC_INDICATE)) {
@@ -496,17 +601,16 @@ static int ondemand_rd_notify_or_indicate(struct bt_conn *conn, struct net_buf_s
 
 		__ASSERT_NO_MSG(rrsp);
 
-		rrsp->ondemand_ind_params.attr = attr;
-		rrsp->ondemand_ind_params.uuid = NULL;
-		rrsp->ondemand_ind_params.data = buf->data;
-		rrsp->ondemand_ind_params.len = buf->len;
-		rrsp->ondemand_ind_params.func = ondemand_rd_indicate_sent_cb;
-		rrsp->ondemand_ind_params.destroy = NULL;
+		rrsp->ranging_data_ind_params.attr = attr;
+		rrsp->ranging_data_ind_params.uuid = NULL;
+		rrsp->ranging_data_ind_params.data = buf->data;
+		rrsp->ranging_data_ind_params.len = buf->len;
+		rrsp->ranging_data_ind_params.func = ranging_data_indicate_sent_cb;
+		rrsp->ranging_data_ind_params.destroy = NULL;
 
-		return bt_gatt_indicate(conn, &rrsp->ondemand_ind_params);
+		return bt_gatt_indicate(conn, &rrsp->ranging_data_ind_params);
 	}
 
-	LOG_WRN("Peer is not subscribed to ranging data characteristic.");
 	return -EINVAL;
 }
 
@@ -604,6 +708,16 @@ static void rascp_cmd_handle(struct bt_ras_rrsp *rrsp)
 		return;
 	}
 
+	struct bt_gatt_attr *realtime_rd_attr =
+		bt_gatt_find_by_uuid(rrsp_svc.attrs, 0, BT_UUID_RAS_REALTIME_RD);
+
+	if (bt_gatt_is_subscribed(rrsp->conn, realtime_rd_attr,
+				  BT_GATT_CCC_NOTIFY | BT_GATT_CCC_INDICATE)) {
+		LOG_WRN("Peer accessing RAS-CP while RRSP is operating in real-time mode.");
+		rascp_send_rsp_code(rrsp->conn, RASCP_RESPONSE_INVALID_PARAMETER);
+		return;
+	}
+
 	switch (opcode) {
 	case RASCP_OPCODE_GET_RD: {
 		if (param_len != sizeof(uint16_t)) {


Reviews:
user: login: olivier-le-sage
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-09T11:01:21Z
user: login: olivier-le-sage
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-10T10:19:44Z
user: login: Tsjerfell
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-04-14T12:18:48Z
user: login: sean-madigan
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-15T08:42:17Z
user: login: sean-madigan
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-15T08:45:55Z
user: login: peknis
state: CHANGES_REQUESTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-15T10:04:03Z
user: login: olivier-le-sage
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-15T11:09:20Z
user: login: peknis
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-15T11:24:51Z
user: login: olivier-le-sage
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-15T14:40:27Z
user: login: sean-madigan
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-04-15T14:47:03Z
user: login: alexstanoev-nordic
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-04-16T14:52:00Z
user: login: olivier-le-sage
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-22T08:29:49Z
user: login: peknis
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-22T08:32:19Z
user: login: peknis
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-04-22T08:33:19Z
user: login: richabp
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-04-22T10:49:58Z

Review comments:
diff_hunk: @@ -205,6 +207,86 @@ static void rd_overwritten_ccc_cfg_changed(struct bt_gatt_attr const *attr, uint
 	LOG_DBG("Ranging Data Overwritten CCCD changed: %u", value);
 }
 
+BT_GATT_SERVICE_DEFINE(
+	rrsp_svc, BT_GATT_PRIMARY_SERVICE(BT_UUID_RANGING_SERVICE),
+	/* RAS Features */
+	BT_GATT_CHARACTERISTIC(BT_UUID_RAS_FEATURES, BT_GATT_CHRC_READ, BT_GATT_PERM_READ_ENCRYPT,
+			       ras_features_read, NULL, NULL),
+	/* On-demand Ranging Data */
+	BT_GATT_CHARACTERISTIC(BT_UUID_RAS_ONDEMAND_RD, BT_GATT_CHRC_INDICATE | BT_GATT_CHRC_NOTIFY,
+			       BT_GATT_PERM_NONE, NULL, NULL, NULL),
+	BT_GATT_CCC_MANAGED(((struct _bt_gatt_ccc[]){BT_GATT_CCC_INITIALIZER(
path: subsys/bluetooth/services/ras/rrsp/ras_rrsp.c
user: login: olivier-le-sage
body: will be followed up upstream https://github.com/zephyrproject-rtos/zephyr/pull/88257
created_at: 2025-04-09T11:01:20Z
author_association: CONTRIBUTOR
diff_hunk: @@ -374,83 +460,119 @@ static uint8_t ras_on_demand_ranging_data_notify_func(struct bt_conn *conn,
 		return BT_GATT_ITER_STOP;
 	}
 
+	if (rreq->on_demand_rd.data_cb == NULL || rreq->on_demand_rd.ranging_data_out == NULL) {
+		LOG_WRN("Ranging data notification received without required buffer "
+			"or callback, unsubscribing");
+		return BT_GATT_ITER_STOP;
+	}
+
 	if (!rreq->on_demand_rd.data_get_in_progress) {
 		LOG_WRN("Unexpected On-demand Ranging Data notification received");
 		return BT_GATT_ITER_CONTINUE;
 	}
 
-	if (rreq->on_demand_rd.last_segment_received) {
+	if (rreq->last_segment_received) {
 		LOG_WRN("On-demand Ranging Data notification received after last segment");
 		return BT_GATT_ITER_CONTINUE;
 	}
 
-	if (rreq->on_demand_rd.error_status) {
+	if (rreq->data_error_status) {
 		/* Already had an error receiving this ranging counter, so exit here. */
 		return BT_GATT_ITER_CONTINUE;
 	}
 
 	if (length < 2) {
 		LOG_WRN("On-demand Ranging Data notification received invalid length");
-		rreq->on_demand_rd.error_status = -EINVAL;
+		rreq->data_error_status = -EINVAL;
 		return BT_GATT_ITER_CONTINUE;
 	}
 
-	struct net_buf_simple segment;
+	store_ranging_data_segment(rreq, data, length);
 
-	net_buf_simple_init_with_data(&segment, (uint8_t *)data, length);
-
-	uint8_t segmentation_header = net_buf_simple_pull_u8(&segment);
+	return BT_GATT_ITER_CONTINUE;
+}
 
-	bool first_segment = segmentation_header & BIT(0);
-	bool last_segment = segmentation_header & BIT(1);
-	uint8_t rolling_segment_counter = segmentation_header >> 2;
+static uint8_t ras_real_time_ranging_data_notify_func(struct bt_conn *conn,
+						      struct bt_gatt_subscribe_params *params,
+						      const void *data, uint16_t length)
+{
+	LOG_DBG("Real-time Ranging Data notification received");
 
-	if (first_segment && rolling_segment_counter != 0) {
-		LOG_WRN("On-demand Ranging Data notification received invalid "
-			"rolling_segment_counter %d",
-			rolling_segment_counter);
-		rreq->on_demand_rd.error_status = -EINVAL;
+	if (data == NULL) {
+		LOG_DBG("Ignored Ranging Data notification with no data.");
 		return BT_GATT_ITER_CONTINUE;
 	}
 
-	if (rreq->on_demand_rd.next_expected_segment_counter != rolling_segment_counter) {
-		LOG_WRN("No support for receiving segments out of order, expected counter %d, "
-			"received counter %d",
-			rreq->on_demand_rd.next_expected_segment_counter, rolling_segment_counter);
-		rreq->on_demand_rd.error_status = -ENODATA;
-		return BT_GATT_ITER_CONTINUE;
+	struct bt_ras_rreq *rreq = ras_rreq_find(conn);
+
+	if (rreq == NULL) {
+		LOG_WRN("Real-time ranging data notification received without associated RREQ "
+			"context, unsubscribing");
+		return BT_GATT_ITER_STOP;
 	}
 
-	uint16_t ranging_data_segment_length = segment.len;
+	if (rreq->real_time_rd.data_cb == NULL || rreq->real_time_rd.ranging_data_out == NULL) {
+		LOG_WRN("Ranging data notification received without required buffer "
+			"or callback, unsubscribing");
+		return BT_GATT_ITER_STOP;
+	}
 
-	if (net_buf_simple_tailroom(rreq->on_demand_rd.ranging_data_out) <
-	    ranging_data_segment_length) {
-		LOG_WRN("Ranging data out buffer not large enough for next segment");
-		rreq->on_demand_rd.error_status = -ENOMEM;
+	if (rreq->data_error_status) {
+		/* Already had an error receiving this ranging counter, so exit here. */
 		return BT_GATT_ITER_CONTINUE;
 	}
 
-	uint8_t *ranging_data_segment =
-		net_buf_simple_pull_mem(&segment, ranging_data_segment_length);
-	net_buf_simple_add_mem(rreq->on_demand_rd.ranging_data_out, ranging_data_segment,
-			       ranging_data_segment_length);
-
-	if (last_segment) {
-		rreq->on_demand_rd.last_segment_received = true;
+	if (length < 2) {
+		LOG_WRN("Real-time Ranging Data notification received invalid length");
+		rreq->data_error_status = -EINVAL;
+		return BT_GATT_ITER_CONTINUE;
 	}
 
-	/* Segment counter is between 0-63. */
-	rreq->on_demand_rd.next_expected_segment_counter =
-		(rolling_segment_counter + 1) & BIT_MASK(6);
+	store_ranging_data_segment(rreq, data, length);
+
+	if (rreq->last_segment_received) {
path: subsys/bluetooth/services/ras/rreq/ras_rreq.c
user: login: olivier-le-sage
body: feedback from Sean: need to consider the case where segments are received in incorrect order, probably need to reset the ranging data out buffer as well
created_at: 2025-04-10T10:19:43Z
author_association: CONTRIBUTOR
diff_hunk: @@ -163,15 +178,23 @@ static uint8_t ranging_data_ready_notify_func(struct bt_conn *conn,
 
 static void data_receive_finished(struct bt_ras_rreq *rreq)
 {
-	if (rreq->on_demand_rd.error_status == 0 && !rreq->on_demand_rd.last_segment_received) {
+	if (rreq->data_error_status == 0 && !rreq->last_segment_received) {
 		LOG_WRN("Ranging data completed with missing segments");
-		rreq->on_demand_rd.error_status = -ENODATA;
+		rreq->data_error_status = -ENODATA;
 	}
 
-	rreq->on_demand_rd.data_get_in_progress = false;
+	if (rreq->realtime) {
+		rreq->real_time_rd.data_cb(rreq->conn, rreq->counter_in_progress,
+					   rreq->data_error_status);
+		net_buf_simple_reset(rreq->real_time_rd.ranging_data_out);
+	} else {
+		rreq->on_demand_rd.data_cb(rreq->conn, rreq->counter_in_progress,
+					   rreq->data_error_status);
+		rreq->on_demand_rd.data_get_in_progress = false;
+	}
 
-	rreq->on_demand_rd.cb(rreq->conn, rreq->on_demand_rd.counter_in_progress,
-			      rreq->on_demand_rd.error_status);
+	rreq->last_segment_received = false;
+	rreq->next_expected_segment_counter = 0;
path: subsys/bluetooth/services/ras/rreq/ras_rreq.c
user: login: sean-madigan
body: Should we reset deata_error_status to 0 here?
created_at: 2025-04-15T08:42:16Z
author_association: CONTRIBUTOR
diff_hunk: @@ -374,83 +460,119 @@ static uint8_t ras_on_demand_ranging_data_notify_func(struct bt_conn *conn,
 		return BT_GATT_ITER_STOP;
 	}
 
+	if (rreq->on_demand_rd.data_cb == NULL || rreq->on_demand_rd.ranging_data_out == NULL) {
+		LOG_WRN("Ranging data notification received without required buffer "
+			"or callback, unsubscribing");
+		return BT_GATT_ITER_STOP;
+	}
+
 	if (!rreq->on_demand_rd.data_get_in_progress) {
 		LOG_WRN("Unexpected On-demand Ranging Data notification received");
 		return BT_GATT_ITER_CONTINUE;
 	}
 
-	if (rreq->on_demand_rd.last_segment_received) {
+	if (rreq->last_segment_received) {
 		LOG_WRN("On-demand Ranging Data notification received after last segment");
 		return BT_GATT_ITER_CONTINUE;
 	}
 
-	if (rreq->on_demand_rd.error_status) {
+	if (rreq->data_error_status) {
 		/* Already had an error receiving this ranging counter, so exit here. */
 		return BT_GATT_ITER_CONTINUE;
 	}
 
 	if (length < 2) {
 		LOG_WRN("On-demand Ranging Data notification received invalid length");
-		rreq->on_demand_rd.error_status = -EINVAL;
+		rreq->data_error_status = -EINVAL;
 		return BT_GATT_ITER_CONTINUE;
 	}
 
-	struct net_buf_simple segment;
+	store_ranging_data_segment(rreq, data, length);
 
-	net_buf_simple_init_with_data(&segment, (uint8_t *)data, length);
-
-	uint8_t segmentation_header = net_buf_simple_pull_u8(&segment);
+	return BT_GATT_ITER_CONTINUE;
+}
 
-	bool first_segment = segmentation_header & BIT(0);
-	bool last_segment = segmentation_header & BIT(1);
-	uint8_t rolling_segment_counter = segmentation_header >> 2;
+static uint8_t ras_real_time_ranging_data_notify_func(struct bt_conn *conn,
+						      struct bt_gatt_subscribe_params *params,
+						      const void *data, uint16_t length)
+{
+	LOG_DBG("Real-time Ranging Data notification received");
 
-	if (first_segment && rolling_segment_counter != 0) {
-		LOG_WRN("On-demand Ranging Data notification received invalid "
-			"rolling_segment_counter %d",
-			rolling_segment_counter);
-		rreq->on_demand_rd.error_status = -EINVAL;
+	if (data == NULL) {
+		LOG_DBG("Ignored Ranging Data notification with no data.");
 		return BT_GATT_ITER_CONTINUE;
 	}
 
-	if (rreq->on_demand_rd.next_expected_segment_counter != rolling_segment_counter) {
-		LOG_WRN("No support for receiving segments out of order, expected counter %d, "
-			"received counter %d",
-			rreq->on_demand_rd.next_expected_segment_counter, rolling_segment_counter);
-		rreq->on_demand_rd.error_status = -ENODATA;
-		return BT_GATT_ITER_CONTINUE;
+	struct bt_ras_rreq *rreq = ras_rreq_find(conn);
+
+	if (rreq == NULL) {
+		LOG_WRN("Real-time ranging data notification received without associated RREQ "
+			"context, unsubscribing");
+		return BT_GATT_ITER_STOP;
 	}
 
-	uint16_t ranging_data_segment_length = segment.len;
+	if (rreq->real_time_rd.data_cb == NULL || rreq->real_time_rd.ranging_data_out == NULL) {
+		LOG_WRN("Ranging data notification received without required buffer "
+			"or callback, unsubscribing");
+		return BT_GATT_ITER_STOP;
+	}
 
-	if (net_buf_simple_tailroom(rreq->on_demand_rd.ranging_data_out) <
-	    ranging_data_segment_length) {
-		LOG_WRN("Ranging data out buffer not large enough for next segment");
-		rreq->on_demand_rd.error_status = -ENOMEM;
+	if (rreq->data_error_status) {
+		/* Already had an error receiving this ranging counter, so exit here. */
 		return BT_GATT_ITER_CONTINUE;
 	}
 
-	uint8_t *ranging_data_segment =
-		net_buf_simple_pull_mem(&segment, ranging_data_segment_length);
-	net_buf_simple_add_mem(rreq->on_demand_rd.ranging_data_out, ranging_data_segment,
-			       ranging_data_segment_length);
-
-	if (last_segment) {
-		rreq->on_demand_rd.last_segment_received = true;
+	if (length < 2) {
+		LOG_WRN("Real-time Ranging Data notification received invalid length");
+		rreq->data_error_status = -EINVAL;
+		return BT_GATT_ITER_CONTINUE;
 	}
 
-	/* Segment counter is between 0-63. */
-	rreq->on_demand_rd.next_expected_segment_counter =
-		(rolling_segment_counter + 1) & BIT_MASK(6);
+	store_ranging_data_segment(rreq, data, length);
+
+	if (rreq->last_segment_received || rreq->data_error_status) {
+		data_receive_finished(rreq);
path: subsys/bluetooth/services/ras/rreq/ras_rreq.c
user: login: sean-madigan
body: Not sure if this is an issue - but if you missed the first segment then we'd end up calling the callback for all remaining segments with the same error
created_at: 2025-04-15T08:45:55Z
author_association: CONTRIBUTOR
diff_hunk: @@ -329,15 +337,15 @@ typedef void (*bt_ras_rreq_rd_ready_cb_t)(struct bt_conn *conn, uint16_t ranging
  */
 typedef void (*bt_ras_rreq_rd_overwritten_cb_t)(struct bt_conn *conn, uint16_t ranging_counter);
 
-/** @brief Ranging data get complete callback. Called when ranging data get procedure has completed.
+/** @brief Ranging data complete callback. Called when complete ranging data has been received from
+ * the peer.
  *
  * @param[in] conn            Connection Object.
- * @param[in] ranging_counter Ranging counter which has been completed.
- * @param[in] err             Error code, 0 if the ranging data get was successful. Otherwise a
- * negative error code.
+ * @param[in] ranging_counter Ranging counter which has been received.
path: include/bluetooth/services/ras.h
user: login: peknis
body: ```suggestion
 * @param[in] ranging_counter Ranging counter that has been received.
```
created_at: 2025-04-15T09:59:48Z
author_association: CONTRIBUTOR
diff_hunk: @@ -329,15 +337,15 @@ typedef void (*bt_ras_rreq_rd_ready_cb_t)(struct bt_conn *conn, uint16_t ranging
  */
 typedef void (*bt_ras_rreq_rd_overwritten_cb_t)(struct bt_conn *conn, uint16_t ranging_counter);
 
-/** @brief Ranging data get complete callback. Called when ranging data get procedure has completed.
+/** @brief Ranging data complete callback. Called when complete ranging data has been received from
path: include/bluetooth/services/ras.h
user: login: peknis
body: Brief is usually just one sentence in imperative format.
created_at: 2025-04-15T10:01:11Z
author_association: CONTRIBUTOR
diff_hunk: @@ -366,11 +374,27 @@ typedef void (*bt_ras_rreq_features_read_cb_t)(struct bt_conn *conn, uint32_t fe
  */
 int bt_ras_rreq_alloc_and_assign_handles(struct bt_gatt_dm *dm, struct bt_conn *conn);
 
+/** @brief Register a callback for GATT subscriptions and unsubscriptions attempted by the RREQ.
+ *
+ * @note This callback will not be called when attempting to subscribe or unsubscribe to a
+ * characteristic to which the device is already subscribed or unsubscribed (respectively).
+ *
+ * @param[in] conn Connection object.
+ * @param[in] subscription_change_cb CCC write request response callback
path: include/bluetooth/services/ras.h
user: login: peknis
body: ```suggestion
 * @param[in] subscription_change_cb CCC write request response callback.
```
created_at: 2025-04-15T10:01:48Z
author_association: CONTRIBUTOR
diff_hunk: @@ -492,6 +519,42 @@ int bt_ras_rreq_rd_overwritten_subscribe(struct bt_conn *conn, bt_ras_rreq_rd_ov
  */
 int bt_ras_rreq_rd_overwritten_unsubscribe(struct bt_conn *conn);
 
+/** @brief Subscribe to real-time ranging data notifications.
+ *
+ * @note Calling from BT RX thread may return an error as bt_gatt_subscribe will not block if
+ * there are no available TX buffers.
+ *
+ * @note The ranging_data_out buffer is automatically reset after the data_received_cb
+ *       callback.
+ *
+ * @note On-Demand and Real-time ranging data are not compatible and attempting to
+ *       subscribe to both at the same time will be rejected by the RAS server.
+ *
+ * @note The data callback will be called many times (for as long as the RRSP continues
+ *       to send notifications).
+ *
+ * @param[in] conn Connection Object, which already has associated RREQ context.
path: include/bluetooth/services/ras.h
user: login: peknis
body: ```suggestion
 * @param[in] conn Connection Object that already has associated RREQ context.
```
created_at: 2025-04-15T10:02:44Z
author_association: CONTRIBUTOR
diff_hunk: @@ -492,6 +519,42 @@ int bt_ras_rreq_rd_overwritten_subscribe(struct bt_conn *conn, bt_ras_rreq_rd_ov
  */
 int bt_ras_rreq_rd_overwritten_unsubscribe(struct bt_conn *conn);
 
+/** @brief Subscribe to real-time ranging data notifications.
+ *
+ * @note Calling from BT RX thread may return an error as bt_gatt_subscribe will not block if
+ * there are no available TX buffers.
+ *
+ * @note The ranging_data_out buffer is automatically reset after the data_received_cb
+ *       callback.
+ *
+ * @note On-Demand and Real-time ranging data are not compatible and attempting to
+ *       subscribe to both at the same time will be rejected by the RAS server.
+ *
+ * @note The data callback will be called many times (for as long as the RRSP continues
+ *       to send notifications).
+ *
+ * @param[in] conn Connection Object, which already has associated RREQ context.
+ * @param[in] ranging_data_out     Simple buffer to store received ranging data.
+ * @param[in] data_received_cb     Callback called when complete ranging data is received.
+ *
+ * @retval 0 If the operation was successful.
+ *           Otherwise, a negative error code is returned.
+ */
+int bt_ras_rreq_realtime_rd_subscribe(struct bt_conn *conn, struct net_buf_simple *ranging_data_out,
+				      bt_ras_rreq_ranging_data_received_t data_received_cb);
+
+/** @brief Unsubscribe from real-time ranging data notifications.
+ *
+ * @note Calling from BT RX thread may return an error as bt_gatt_unsubscribe will not block if
+ * there are no available TX buffers.
+ *
+ * @param[in] conn Connection Object, which already has associated RREQ context.
path: include/bluetooth/services/ras.h
user: login: peknis
body: ```suggestion
 * @param[in] conn Connection Object that already has associated RREQ context.
```
created_at: 2025-04-15T10:03:13Z
author_association: CONTRIBUTOR
diff_hunk: @@ -329,15 +337,15 @@ typedef void (*bt_ras_rreq_rd_ready_cb_t)(struct bt_conn *conn, uint16_t ranging
  */
 typedef void (*bt_ras_rreq_rd_overwritten_cb_t)(struct bt_conn *conn, uint16_t ranging_counter);
 
-/** @brief Ranging data get complete callback. Called when ranging data get procedure has completed.
+/** @brief Ranging data complete callback. Called when complete ranging data has been received from
path: include/bluetooth/services/ras.h
user: login: olivier-le-sage
body: Can you give a suggestion on how to rewrite it?
created_at: 2025-04-15T11:09:20Z
author_association: CONTRIBUTOR
diff_hunk: @@ -329,15 +337,15 @@ typedef void (*bt_ras_rreq_rd_ready_cb_t)(struct bt_conn *conn, uint16_t ranging
  */
 typedef void (*bt_ras_rreq_rd_overwritten_cb_t)(struct bt_conn *conn, uint16_t ranging_counter);
 
-/** @brief Ranging data get complete callback. Called when ranging data get procedure has completed.
+/** @brief Ranging data complete callback. Called when complete ranging data has been received from
path: include/bluetooth/services/ras.h
user: login: peknis
body: Oh, this is a hard one. Receive complete ranging data? Then an additional line below to describe more?
created_at: 2025-04-15T11:24:51Z
author_association: CONTRIBUTOR
diff_hunk: @@ -374,83 +460,119 @@ static uint8_t ras_on_demand_ranging_data_notify_func(struct bt_conn *conn,
 		return BT_GATT_ITER_STOP;
 	}
 
+	if (rreq->on_demand_rd.data_cb == NULL || rreq->on_demand_rd.ranging_data_out == NULL) {
+		LOG_WRN("Ranging data notification received without required buffer "
+			"or callback, unsubscribing");
+		return BT_GATT_ITER_STOP;
+	}
+
 	if (!rreq->on_demand_rd.data_get_in_progress) {
 		LOG_WRN("Unexpected On-demand Ranging Data notification received");
 		return BT_GATT_ITER_CONTINUE;
 	}
 
-	if (rreq->on_demand_rd.last_segment_received) {
+	if (rreq->last_segment_received) {
 		LOG_WRN("On-demand Ranging Data notification received after last segment");
 		return BT_GATT_ITER_CONTINUE;
 	}
 
-	if (rreq->on_demand_rd.error_status) {
+	if (rreq->data_error_status) {
 		/* Already had an error receiving this ranging counter, so exit here. */
 		return BT_GATT_ITER_CONTINUE;
 	}
 
 	if (length < 2) {
 		LOG_WRN("On-demand Ranging Data notification received invalid length");
-		rreq->on_demand_rd.error_status = -EINVAL;
+		rreq->data_error_status = -EINVAL;
 		return BT_GATT_ITER_CONTINUE;
 	}
 
-	struct net_buf_simple segment;
+	store_ranging_data_segment(rreq, data, length);
 
-	net_buf_simple_init_with_data(&segment, (uint8_t *)data, length);
-
-	uint8_t segmentation_header = net_buf_simple_pull_u8(&segment);
+	return BT_GATT_ITER_CONTINUE;
+}
 
-	bool first_segment = segmentation_header & BIT(0);
-	bool last_segment = segmentation_header & BIT(1);
-	uint8_t rolling_segment_counter = segmentation_header >> 2;
+static uint8_t ras_real_time_ranging_data_notify_func(struct bt_conn *conn,
+						      struct bt_gatt_subscribe_params *params,
+						      const void *data, uint16_t length)
+{
+	LOG_DBG("Real-time Ranging Data notification received");
 
-	if (first_segment && rolling_segment_counter != 0) {
-		LOG_WRN("On-demand Ranging Data notification received invalid "
-			"rolling_segment_counter %d",
-			rolling_segment_counter);
-		rreq->on_demand_rd.error_status = -EINVAL;
+	if (data == NULL) {
+		LOG_DBG("Ignored Ranging Data notification with no data.");
 		return BT_GATT_ITER_CONTINUE;
 	}
 
-	if (rreq->on_demand_rd.next_expected_segment_counter != rolling_segment_counter) {
-		LOG_WRN("No support for receiving segments out of order, expected counter %d, "
-			"received counter %d",
-			rreq->on_demand_rd.next_expected_segment_counter, rolling_segment_counter);
-		rreq->on_demand_rd.error_status = -ENODATA;
-		return BT_GATT_ITER_CONTINUE;
+	struct bt_ras_rreq *rreq = ras_rreq_find(conn);
+
+	if (rreq == NULL) {
+		LOG_WRN("Real-time ranging data notification received without associated RREQ "
+			"context, unsubscribing");
+		return BT_GATT_ITER_STOP;
 	}
 
-	uint16_t ranging_data_segment_length = segment.len;
+	if (rreq->real_time_rd.data_cb == NULL || rreq->real_time_rd.ranging_data_out == NULL) {
+		LOG_WRN("Ranging data notification received without required buffer "
+			"or callback, unsubscribing");
+		return BT_GATT_ITER_STOP;
+	}
 
-	if (net_buf_simple_tailroom(rreq->on_demand_rd.ranging_data_out) <
-	    ranging_data_segment_length) {
-		LOG_WRN("Ranging data out buffer not large enough for next segment");
-		rreq->on_demand_rd.error_status = -ENOMEM;
+	if (rreq->data_error_status) {
+		/* Already had an error receiving this ranging counter, so exit here. */
 		return BT_GATT_ITER_CONTINUE;
 	}
 
-	uint8_t *ranging_data_segment =
-		net_buf_simple_pull_mem(&segment, ranging_data_segment_length);
-	net_buf_simple_add_mem(rreq->on_demand_rd.ranging_data_out, ranging_data_segment,
-			       ranging_data_segment_length);
-
-	if (last_segment) {
-		rreq->on_demand_rd.last_segment_received = true;
+	if (length < 2) {
+		LOG_WRN("Real-time Ranging Data notification received invalid length");
+		rreq->data_error_status = -EINVAL;
+		return BT_GATT_ITER_CONTINUE;
 	}
 
-	/* Segment counter is between 0-63. */
-	rreq->on_demand_rd.next_expected_segment_counter =
-		(rolling_segment_counter + 1) & BIT_MASK(6);
+	store_ranging_data_segment(rreq, data, length);
+
+	if (rreq->last_segment_received || rreq->data_error_status) {
+		data_receive_finished(rreq);
path: subsys/bluetooth/services/ras/rreq/ras_rreq.c
user: login: olivier-le-sage
body: think it's not likely to happen anyway. If I understand correctly it can only happen if we run out of ATT buffers, or the peer didn't send the first segment to begin with
created_at: 2025-04-15T14:40:27Z
author_association: CONTRIBUTOR
diff_hunk: @@ -329,15 +337,15 @@ typedef void (*bt_ras_rreq_rd_ready_cb_t)(struct bt_conn *conn, uint16_t ranging
  */
 typedef void (*bt_ras_rreq_rd_overwritten_cb_t)(struct bt_conn *conn, uint16_t ranging_counter);
 
-/** @brief Ranging data get complete callback. Called when ranging data get procedure has completed.
+/** @brief Ranging data complete callback. Called when complete ranging data has been received from
path: include/bluetooth/services/ras.h
user: login: olivier-le-sage
body: Shall we leave it as-is then? I've only copied the format of the other callbacks in this file. See for instance the docs for `bt_ras_rreq_rd_overwritten_cb_t` and `bt_ras_rreq_rd_ready_cb_t`


created_at: 2025-04-22T08:29:49Z
author_association: CONTRIBUTOR
diff_hunk: @@ -329,15 +337,15 @@ typedef void (*bt_ras_rreq_rd_ready_cb_t)(struct bt_conn *conn, uint16_t ranging
  */
 typedef void (*bt_ras_rreq_rd_overwritten_cb_t)(struct bt_conn *conn, uint16_t ranging_counter);
 
-/** @brief Ranging data get complete callback. Called when ranging data get procedure has completed.
+/** @brief Ranging data complete callback. Called when complete ranging data has been received from
path: include/bluetooth/services/ras.h
user: login: peknis
body: Fair enough.
created_at: 2025-04-22T08:32:19Z
author_association: CONTRIBUTOR

PR comments:
user: login: NordicBuilder
created_at: 2025-04-08T10:47:57Z
body: ## CI Information

<sup>_To view the history of this post, clich the 'edited' button above_</sup>
Build number: 18
### Inputs:
#### Sources:
sdk-nrf: PR head: [b8cf847b784bf5753d21319dfb606b19be6cf4d7](https://github.com/nrfconnect/sdk-nrf/pull/21514/commits/b8cf847b784bf5753d21319dfb606b19be6cf4d7)
<details><summary> more details</summary>

sdk-nrf:
> PR head: [b8cf847b784bf5753d21319dfb606b19be6cf4d7](https://github.com/nrfconnect/sdk-nrf/pull/21514/commits/b8cf847b784bf5753d21319dfb606b19be6cf4d7)
> merge base: [070104218b699d93c5bd1cc985a3704e55ba1aaa](https://github.com/nrfconnect/sdk-nrf/commit/070104218b699d93c5bd1cc985a3704e55ba1aaa)
> target head (main): [793abf4e49f0ae5040fd98a0587e210c9b5d0a91](https://github.com/nrfconnect/sdk-nrf/commit/793abf4e49f0ae5040fd98a0587e210c9b5d0a91)
> [Diff](https://github.com/nrfconnect/sdk-nrf/compare/070104218b699d93c5bd1cc985a3704e55ba1aaa..b8cf847b784bf5753d21319dfb606b19be6cf4d7)</details>

#### Github labels
| Enabled | Name | Description |
| -- | -- | -- |
|| ci-disabled | _Disable the ci execution_ | 
|| ci-all-test | _Run all of ci, no test spec filtering will be done_ | 
|| ci-force-downstream | _Force execution of downstream even if twister fails_ | 
|| ci-run-twister | _Force run twister_ | 
|| ci-run-zephyr-twister | _Force run zephyr twister_ | 

<details><summary> List of changed files detected by CI (8)</summary>

```
doc
│  ├── nrf
│  │  ├── libraries
│  │  │  ├── bluetooth
│  │  │  │  ├── services
│  │  │  │  │  ├── rreq.rst
│  │  │  │  │  │ rrsp.rst
include
│  ├── bluetooth
│  │  ├── services
│  │  │  │ ras.h
samples
│  ├── bluetooth
│  │  ├── channel_sounding_ras_initiator
│  │  │  ├── src
│  │  │  │  │ main.c
subsys
│  ├── bluetooth
│  │  ├── services
│  │  │  ├── ras
│  │  │  │  ├── ras_internal.h
│  │  │  │  ├── rreq
│  │  │  │  │  │ ras_rreq.c
│  │  │  │  ├── rrsp
│  │  │  │  │  ├── ras_rd_buffer.c
│  │  │  │  │  │ ras_rrsp.c
```
</details>

### Outputs:

#### Toolchain
Version: 7cbc0036f4
Build docker image: docker-dtr.nordicsemi.no/sw-production/ncs-build:7cbc0036f4_8bf7ca4353

#### Test Spec & Results: :white_check_mark: Success; :x: Failure; :orange_circle: Queued; :yellow_circle: Progress; :white_medium_square: Skipped; :warning: Quarantine
- :white_medium_square: Toolchain - Skipped: existing toolchain is used
- :white_check_mark: Build twister - Skipped: Skipping Build & Test as it succeeded in a previous run: 17
- :white_check_mark: Integration tests
   - :white_check_mark: desktop52_verification - Skipped: Job was skipped as it succeeded in a previous run
   - :white_check_mark: test_ble_nrf_config
   - :white_check_mark: test-fw-nrfconnect-ble_samples - Skipped: Job was skipped as it succeeded in a previous run
   - :white_check_mark: test-sdk-find-my - Skipped: Job was skipped as it succeeded in a previous run

<details><summary>Disabled integration tests</summary>

- 
   - doc-internal
   - test-fw-nrfconnect-apps
   - test-fw-nrfconnect-ble_mesh
   - test-fw-nrfconnect-boot
   - test-fw-nrfconnect-chip
   - test-fw-nrfconnect-fem
   - test-fw-nrfconnect-nfc
   - test-fw-nrfconnect-nrf-iot_cloud
   - test-fw-nrfconnect-nrf-iot_libmodem-nrf
   - test-fw-nrfconnect-nrf-iot_mosh
   - test-fw-nrfconnect-nrf-iot_positioning
   - test-fw-nrfconnect-nrf-iot_samples
   - test-fw-nrfconnect-nrf-iot_serial_lte_modem
   - test-fw-nrfconnect-nrf-iot_thingy91
   - test-fw-nrfconnect-nrf-iot_zephyr_lwm2m
   - test-fw-nrfconnect-nrf_crypto
   - test-fw-nrfconnect-proprietary_esb
   - test-fw-nrfconnect-ps
   - test-fw-nrfconnect-rpc
   - test-fw-nrfconnect-rs
   - test-fw-nrfconnect-tfm
   - test-fw-nrfconnect-thread
   - test-low-level
   - test-sdk-audio
   - test-sdk-dfu
   - test-sdk-mcuboot
   - test-sdk-pmic-samples
   - test-sdk-wifi
   - test-secdom-samples-public
</details>



*Note: This message is automatically posted and updated by the CI*
author_association: CONTRIBUTOR
user: login: github-actions[bot]
created_at: 2025-04-09T07:57:23Z
body: You can find the documentation preview for this PR [here](https://ncsdoc.z6.web.core.windows.net/PR-21514/).

Preview links for modified nRF Connect SDK documents:

https://ncsdoc.z6.web.core.windows.net/PR-21514/nrf/libraries/bluetooth/services/rreq.html
https://ncsdoc.z6.web.core.windows.net/PR-21514/nrf/libraries/bluetooth/services/rrsp.html
author_association: NONE
user: login: olivier-le-sage
created_at: 2025-04-09T10:59:48Z
body: will rebase on https://github.com/nrfconnect/sdk-nrf/pull/21563 later
author_association: CONTRIBUTOR
user: login: olivier-le-sage
created_at: 2025-04-14T07:14:17Z
body: tests available here https://projecttools.nordicsemi.no/bitbucket/projects/DRGN/repos/dragoon/pull-requests/12713/overview have mostly run in babblesim
author_association: CONTRIBUTOR