| // SPDX-License-Identifier: LGPL-2.1-or-later |
| /* |
| * BlueZ - Bluetooth protocol stack for Linux |
| * |
| * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries. |
| */ |
| |
| #define _GNU_SOURCE |
| #include <inttypes.h> |
| #include <string.h> |
| #include <stdlib.h> |
| #include <stdbool.h> |
| #include <unistd.h> |
| #include <errno.h> |
| |
| #include "bluetooth/bluetooth.h" |
| #include "bluetooth/hci.h" |
| #include "bluetooth/uuid.h" |
| |
| #include "src/shared/queue.h" |
| #include "src/shared/util.h" |
| #include "src/shared/timeout.h" |
| #include "src/shared/att.h" |
| #include "src/shared/gatt-db.h" |
| #include "src/shared/gatt-server.h" |
| #include "src/shared/gatt-client.h" |
| #include "src/shared/rap.h" |
| |
| #define DBG(_rap, fmt, ...) \ |
| rap_debug(_rap, "%s:%s() " fmt, __FILE__, __func__, ##__VA_ARGS__) |
| |
| #define RAS_UUID16 0x185B |
| |
| /* Total number of attribute handles reserved for the RAS service */ |
| #define RAS_TOTAL_NUM_HANDLES 18 |
| |
| /* 2(rc+cfg) + 1(tx_pwr) + 1(4 bits antenna_mask, 2 bits reserved, |
| * 2 bits pct_format) |
| */ |
| #define RAS_RANGING_HEADER_SIZE 4 |
| #define TOTAL_RAS_RANGING_HEADER_SIZE 5 |
| #define ATT_OVERHEAD 3 /* 1(opcode) + 2(char handle) */ |
| #define RAS_STEP_ABORTED_BIT 0x80/* set step aborted */ |
| #define RAS_SUBEVENT_HEADER_SIZE 8 |
| |
| enum pct_format { |
| IQ = 0, |
| PHASE = 1, |
| }; |
| |
| enum ranging_done_status { |
| RANGING_DONE_ALL_RESULTS_COMPLETE = 0x0, |
| RANGING_DONE_PARTIAL_RESULTS = 0x1, |
| RANGING_DONE_ABORTED = 0xF, |
| }; |
| |
| enum subevent_done_status { |
| SUBEVENT_DONE_ALL_RESULTS_COMPLETE = 0x0, |
| SUBEVENT_DONE_PARTIAL_RESULTS = 0x1, |
| SUBEVENT_DONE_ABORTED = 0xF, |
| }; |
| |
| enum ranging_abort_reason { |
| RANGING_ABORT_NO_ABORT = 0x0, |
| RANGING_ABORT_LOCAL_HOST_OR_REMOTE = 0x1, |
| RANGING_ABORT_INSUFFICIENT_FILTERED_CHANNELS = 0x2, |
| RANGING_ABORT_INSTANT_HAS_PASSED = 0x3, |
| RANGING_ABORT_UNSPECIFIED = 0xF, |
| }; |
| |
| enum subevent_abort_reason { |
| SUBEVENT_ABORT_NO_ABORT = 0x0, |
| SUBEVENT_ABORT_LOCAL_HOST_OR_REMOTE = 0x1, |
| SUBEVENT_ABORT_NO_CS_SYNC_RECEIVED = 0x2, |
| SUBEVENT_ABORT_SCHEDULING_CONFLICTS_OR_LIMITED_RESOURCES = 0x3, |
| SUBEVENT_ABORT_UNSPECIFIED = 0xF, |
| }; |
| |
| /* Segmentation header: 1 byte |
| * bit 0: first_segment |
| * bit 1: last_segment |
| * bits 2-7: rolling_segment_counter (6 bits) |
| */ |
| struct segmentation_header { |
| uint8_t first_segment; |
| uint8_t last_segment; |
| uint8_t rolling_segment_counter; |
| }; |
| |
| /* Macros to pack/unpack segmentation header */ |
| #define SEG_HDR_PACK(first, last, counter) \ |
| ((uint8_t)(((first) ? 0x01 : 0x00) | \ |
| ((last) ? 0x02 : 0x00) | \ |
| (((counter) & 0x3F) << 2))) |
| |
| struct ranging_header { |
| /* Byte 0-1: 12-bit counter + 4-bit config_id */ |
| uint8_t counter_config[2]; |
| int8_t selected_tx_power; /* Byte 2: selected TX power */ |
| /* Byte 3: 4-bit antenna_mask + 2-bit reserved + 2-bit pct_format */ |
| uint8_t antenna_pct; |
| } __packed; |
| |
| static inline void ranging_header_set_counter(struct ranging_header *hdr, |
| uint16_t counter) |
| { |
| /* Counter is 12 bits, stored in lower 12 bits of first 2 bytes */ |
| hdr->counter_config[0] = counter & 0xFF; |
| hdr->counter_config[1] = (hdr->counter_config[1] & 0xF0) | |
| ((counter >> 8) & 0x0F); |
| } |
| |
| static inline void ranging_header_set_config_id(struct ranging_header *hdr, |
| uint8_t config_id) |
| { |
| /* Config ID is 4 bits, stored in upper 4 bits of byte 1 */ |
| hdr->counter_config[1] = (hdr->counter_config[1] & 0x0F) | |
| ((config_id & 0x0F) << 4); |
| } |
| |
| static inline void ranging_header_set_antenna_mask( |
| struct ranging_header *hdr, |
| uint8_t mask) |
| { |
| /* Antenna mask is 4 bits, stored in lower 4 bits of byte 3 */ |
| hdr->antenna_pct = (hdr->antenna_pct & 0xF0) | (mask & 0x0F); |
| } |
| |
| static inline void ranging_header_set_pct_format(struct ranging_header *hdr, |
| uint8_t format) |
| { |
| /* PCT format is 2 bits, stored in bits 6-7 of byte 3 */ |
| hdr->antenna_pct = (hdr->antenna_pct & 0x3F) | |
| ((format & 0x03) << 6); |
| } |
| |
| struct ras_subevent_header { |
| uint16_t start_acl_conn_event; |
| uint16_t frequency_compensation; |
| uint8_t ranging_done_status; |
| uint8_t subevent_done_status; |
| uint8_t ranging_abort_reason; |
| uint8_t subevent_abort_reason; |
| int8_t reference_power_level; |
| uint8_t num_steps_reported; |
| }; |
| |
| /* Macros to pack/unpack RAS subevent header status fields */ |
| #define RAS_DONE_STATUS_PACK(ranging, subevent) \ |
| ((uint8_t)(((ranging) & 0x0F) | (((subevent) & 0x0F) << 4))) |
| |
| #define RAS_ABORT_REASON_PACK(ranging, subevent) \ |
| ((uint8_t)(((ranging) & 0x0F) | (((subevent) & 0x0F) << 4))) |
| |
| struct ras_subevent { |
| struct ras_subevent_header subevent_header; |
| uint8_t subevent_data[]; |
| }; |
| |
| /* Role maps to Core CS roles (initiator/reflector) */ |
| enum cs_role { |
| CS_ROLE_INITIATOR = 0x00, |
| CS_ROLE_REFLECTOR = 0x01, |
| }; |
| |
| #define CS_INVALID_CONFIG_ID 0xFF |
| /* Minimal enums (align to controller values if needed) */ |
| enum cs_procedure_done_status { |
| CS_PROC_ALL_RESULTS_COMPLETE = 0x00, |
| CS_PROC_PARTIAL_RESULTS = 0x01, |
| CS_PROC_ABORTED = 0x02 |
| }; |
| |
| /* Main cs_procedure_data */ |
| struct cs_procedure_data { |
| /* Identity and counters */ |
| uint16_t counter; |
| uint8_t num_antenna_paths; |
| /* Flags and status */ |
| enum cs_procedure_done_status local_status; |
| enum cs_procedure_done_status remote_status; |
| bool contains_complete_subevent_; |
| /* RAS aggregation */ |
| struct segmentation_header segmentation_header_; |
| struct ranging_header ranging_header_; |
| struct iovec ras_raw_data_; /* raw concatenated */ |
| uint16_t ras_raw_data_index_; |
| struct ras_subevent_header ras_subevent_header_; |
| struct iovec ras_subevent_data_; /* buffer per subevent */ |
| uint8_t ras_subevent_counter_; |
| /* Reference power levels */ |
| int8_t initiator_reference_power_level; |
| int8_t reflector_reference_power_level; |
| bool ranging_header_prepended_; |
| bool ras_subevent_header_emitted; |
| }; |
| |
| struct cstracker { |
| enum cs_role role; /* INITIATOR/REFLECTOR */ |
| uint8_t config_id; /* CS_INVALID_CONFIG_ID */ |
| int8_t selected_tx_power; /* PROC_ENABLE_COMPLETE */ |
| uint8_t rtt_type; /* RTT type */ |
| struct cs_procedure_data *current_proc; |
| /* Cached header values for CONT events (per-connection state) */ |
| uint16_t last_proc_counter; |
| uint16_t last_start_acl_conn_evt_counter; |
| uint16_t last_freq_comp; |
| int8_t last_ref_pwr_lvl; |
| }; |
| |
| /* Ranging Service context */ |
| struct ras { |
| struct bt_rap_db *rapdb; |
| |
| /* Service and characteristic attributes */ |
| struct gatt_db_attribute *svc; |
| struct gatt_db_attribute *feat_chrc; |
| struct gatt_db_attribute *realtime_chrc; |
| struct gatt_db_attribute *realtime_chrc_ccc; |
| struct gatt_db_attribute *ondemand_chrc; |
| struct gatt_db_attribute *ondemand_ccc; |
| struct gatt_db_attribute *cp_chrc; |
| struct gatt_db_attribute *cp_ccc; |
| struct gatt_db_attribute *ready_chrc; |
| struct gatt_db_attribute *ready_ccc; |
| struct gatt_db_attribute *overwritten_chrc; |
| struct gatt_db_attribute *overwritten_ccc; |
| |
| /* CCC state tracking for mutual exclusivity */ |
| uint16_t realtime_ccc_value; |
| uint16_t ondemand_ccc_value; |
| }; |
| |
| struct bt_rap_db { |
| struct gatt_db *db; |
| struct ras *ras; |
| }; |
| |
| struct bt_rap { |
| int ref_count; |
| struct bt_rap_db *lrapdb; |
| struct bt_rap_db *rrapdb; |
| struct bt_gatt_client *client; |
| struct bt_att *att; |
| |
| unsigned int idle_id; |
| |
| struct queue *notify; |
| struct queue *pending; |
| struct queue *ready_cbs; |
| |
| bt_rap_debug_func_t debug_func; |
| bt_rap_destroy_func_t debug_destroy; |
| void *debug_data; |
| void *user_data; |
| struct cstracker *resptracker; |
| }; |
| |
| static struct queue *rap_db; |
| static struct queue *bt_rap_cbs; |
| static struct queue *sessions; |
| |
| struct bt_rap_cb { |
| unsigned int id; |
| bt_rap_func_t attached; |
| bt_rap_func_t detached; |
| void *user_data; |
| }; |
| |
| struct bt_rap_ready { |
| unsigned int id; |
| bt_rap_ready_func_t func; |
| bt_rap_destroy_func_t destroy; |
| void *data; |
| }; |
| |
| uint16_t default_ras_mtu = 247; /*Section 3.1.2 of RAP 1.0*/ |
| uint8_t ras_segment_header_size = 1; |
| |
| static struct cs_procedure_data *cs_procedure_data_create( |
| uint16_t procedure_counter, |
| uint8_t num_antenna_paths, |
| uint8_t configuration_id, |
| int8_t selected_tx_power) |
| { |
| struct cs_procedure_data *d; |
| uint8_t i; |
| uint8_t antenna_mask = 0; |
| |
| d = calloc(1, sizeof(struct cs_procedure_data)); |
| |
| if (!d) |
| return NULL; |
| |
| d->counter = procedure_counter; |
| d->num_antenna_paths = num_antenna_paths; |
| d->local_status = CS_PROC_PARTIAL_RESULTS; |
| d->remote_status = CS_PROC_PARTIAL_RESULTS; |
| d->contains_complete_subevent_ = false; |
| d->segmentation_header_.first_segment = 1; |
| d->segmentation_header_.last_segment = 0; |
| d->segmentation_header_.rolling_segment_counter = 0; |
| |
| /* Initialize ranging header using helper functions */ |
| memset(&d->ranging_header_, 0, sizeof(d->ranging_header_)); |
| ranging_header_set_counter(&d->ranging_header_, procedure_counter); |
| ranging_header_set_config_id(&d->ranging_header_, configuration_id); |
| d->ranging_header_.selected_tx_power = selected_tx_power; |
| |
| /* Build antenna mask */ |
| for (i = 0; i < num_antenna_paths; i++) |
| antenna_mask |= (1u << i); |
| ranging_header_set_antenna_mask(&d->ranging_header_, antenna_mask); |
| |
| ranging_header_set_pct_format(&d->ranging_header_, IQ); |
| memset(&d->ras_raw_data_, 0, sizeof(d->ras_raw_data_)); |
| d->ras_raw_data_index_ = 0; |
| memset(&d->ras_subevent_data_, 0, sizeof(d->ras_subevent_data_)); |
| d->ras_subevent_counter_ = 0; |
| d->initiator_reference_power_level = 0; |
| d->reflector_reference_power_level = 0; |
| d->ranging_header_prepended_ = false; |
| d->ras_subevent_header_emitted = false; |
| |
| return d; |
| } |
| |
| static void cs_procedure_data_destroy(struct cs_procedure_data *d) |
| { |
| if (!d) |
| return; |
| |
| free(d->ras_raw_data_.iov_base); |
| free(d->ras_subevent_data_.iov_base); |
| free(d); |
| } |
| |
| static void cs_pd_set_local_status(struct cs_procedure_data *d, |
| enum cs_procedure_done_status s) |
| { |
| if (d) |
| d->local_status = s; |
| } |
| |
| static void cs_pd_set_remote_status(struct cs_procedure_data *d, |
| enum cs_procedure_done_status s) |
| { |
| if (d) |
| d->remote_status = s; |
| } |
| |
| static void cs_pd_set_reference_power_levels(struct cs_procedure_data *d, |
| int8_t init_lvl, int8_t ref_lvl) |
| { |
| if (!d) |
| return; |
| |
| d->initiator_reference_power_level = init_lvl; |
| d->reflector_reference_power_level = ref_lvl; |
| } |
| |
| static void cs_pd_ras_begin_subevent(struct cs_procedure_data *d, |
| uint16_t start_acl_conn_event, |
| uint16_t frequency_compensation, |
| int8_t reference_power_level) |
| { |
| if (!d) |
| return; |
| |
| d->ras_subevent_counter_++; |
| d->ras_subevent_header_.start_acl_conn_event = start_acl_conn_event; |
| d->ras_subevent_header_.frequency_compensation = |
| frequency_compensation; |
| d->ras_subevent_header_.reference_power_level = reference_power_level; |
| d->ras_subevent_header_.num_steps_reported = 0; |
| d->ras_subevent_header_emitted = false; |
| d->ras_subevent_data_.iov_len = 0; |
| } |
| |
| static bool cs_pd_ras_append_subevent_bytes(struct cs_procedure_data *d, |
| const uint8_t *bytes, size_t len) |
| { |
| if (!d || !bytes || len == 0) |
| return false; |
| |
| return util_iov_append(&d->ras_subevent_data_, bytes, len) != NULL; |
| } |
| |
| static inline size_t serialize_ras_subevent_header( |
| const struct ras_subevent_header *h, |
| uint8_t *out, size_t out_len) |
| { |
| |
| if (!h || !out || out_len < RAS_SUBEVENT_HEADER_SIZE) |
| return 0; |
| |
| put_le16(h->start_acl_conn_event, out + 0); |
| put_le16(h->frequency_compensation, out + 2); |
| out[4] = RAS_DONE_STATUS_PACK(h->ranging_done_status, |
| h->subevent_done_status); |
| out[5] = RAS_ABORT_REASON_PACK(h->ranging_abort_reason, |
| h->subevent_abort_reason); |
| out[6] = h->reference_power_level; |
| out[7] = h->num_steps_reported; |
| |
| return RAS_SUBEVENT_HEADER_SIZE; |
| } |
| |
| static bool cs_pd_ras_commit_subevent(struct cs_procedure_data *d, |
| uint8_t num_steps_reported, |
| uint8_t ranging_done_status, |
| uint8_t subevent_done_status, |
| uint8_t ranging_abort_reason, |
| uint8_t subevent_abort_reason) |
| { |
| size_t hdr_sz; |
| size_t payload_sz; |
| size_t total; |
| uint8_t *buf; |
| size_t w; |
| bool ok; |
| |
| if (!d) |
| return false; |
| |
| d->ras_subevent_header_.num_steps_reported = |
| (uint8_t)(d->ras_subevent_header_.num_steps_reported + |
| num_steps_reported); |
| d->ras_subevent_header_.ranging_done_status = ranging_done_status; |
| d->ras_subevent_header_.subevent_done_status = subevent_done_status; |
| d->ras_subevent_header_.ranging_abort_reason = ranging_abort_reason; |
| d->ras_subevent_header_.subevent_abort_reason = subevent_abort_reason; |
| |
| if (subevent_done_status == SUBEVENT_DONE_ALL_RESULTS_COMPLETE) |
| d->contains_complete_subevent_ = true; |
| |
| if (subevent_done_status == SUBEVENT_DONE_PARTIAL_RESULTS) |
| return true; |
| |
| if (!d->ras_subevent_header_emitted) { |
| hdr_sz = RAS_SUBEVENT_HEADER_SIZE; |
| payload_sz = d->ras_subevent_data_.iov_len; |
| total = hdr_sz + payload_sz; |
| buf = (uint8_t *)malloc(total); |
| |
| if (!buf) |
| return false; |
| |
| w = serialize_ras_subevent_header(&d->ras_subevent_header_, |
| buf, total); |
| |
| if (w != hdr_sz) { |
| free(buf); |
| return false; |
| } |
| |
| if (payload_sz > 0) |
| memcpy(buf + hdr_sz, |
| (const uint8_t *)d->ras_subevent_data_.iov_base, |
| payload_sz); |
| |
| ok = util_iov_append(&d->ras_raw_data_, buf, total) != NULL; |
| free(buf); |
| |
| if (!ok) |
| return false; |
| |
| d->ras_subevent_data_.iov_len = 0; |
| d->ras_subevent_header_emitted = true; |
| } |
| |
| return true; |
| } |
| |
| static struct ras *rap_get_ras(struct bt_rap *rap) |
| { |
| if (!rap) |
| return NULL; |
| |
| if (rap->rrapdb->ras) |
| return rap->rrapdb->ras; |
| |
| rap->rrapdb->ras = new0(struct ras, 1); |
| rap->rrapdb->ras->rapdb = rap->rrapdb; |
| |
| return rap->rrapdb->ras; |
| } |
| |
| static void rap_detached(void *data, void *user_data) |
| { |
| struct bt_rap_cb *cb = data; |
| struct bt_rap *rap = user_data; |
| |
| cb->detached(rap, cb->user_data); |
| } |
| |
| void bt_rap_detach(struct bt_rap *rap) |
| { |
| if (!queue_remove(sessions, rap)) |
| return; |
| |
| bt_gatt_client_idle_unregister(rap->client, rap->idle_id); |
| bt_gatt_client_unref(rap->client); |
| rap->client = NULL; |
| |
| queue_foreach(bt_rap_cbs, rap_detached, rap); |
| } |
| |
| static void rap_db_free(void *data) |
| { |
| struct bt_rap_db *rapdb = data; |
| |
| if (!rapdb) |
| return; |
| |
| gatt_db_unref(rapdb->db); |
| |
| free(rapdb->ras); |
| free(rapdb); |
| } |
| |
| static void rap_ready_free(void *data) |
| { |
| struct bt_rap_ready *ready = data; |
| |
| if (ready->destroy) |
| ready->destroy(ready->data); |
| |
| free(ready); |
| } |
| |
| static void rap_free(void *data) |
| { |
| struct bt_rap *rap = data; |
| |
| bt_rap_detach(rap); |
| |
| rap_db_free(rap->rrapdb); |
| |
| if (rap->resptracker) { |
| free(rap->resptracker); |
| rap->resptracker = NULL; |
| } |
| |
| queue_destroy(rap->notify, free); |
| queue_destroy(rap->pending, NULL); |
| queue_destroy(rap->ready_cbs, rap_ready_free); |
| |
| free(rap); |
| } |
| |
| bool bt_rap_set_user_data(struct bt_rap *rap, void *user_data) |
| { |
| if (!rap) |
| return false; |
| |
| rap->user_data = user_data; |
| |
| return true; |
| } |
| |
| static bool rap_db_match(const void *data, const void *match_data) |
| { |
| const struct bt_rap_db *rapdb = data; |
| const struct gatt_db *db = match_data; |
| |
| return rapdb->db == db; |
| } |
| |
| struct bt_att *bt_rap_get_att(struct bt_rap *rap) |
| { |
| if (!rap) |
| return NULL; |
| |
| if (rap->att) |
| return rap->att; |
| |
| return bt_gatt_client_get_att(rap->client); |
| } |
| |
| struct bt_rap *bt_rap_ref(struct bt_rap *rap) |
| { |
| if (!rap) |
| return NULL; |
| |
| __sync_fetch_and_add(&rap->ref_count, 1); |
| |
| return rap; |
| } |
| |
| void bt_rap_unref(struct bt_rap *rap) |
| { |
| if (!rap) |
| return; |
| |
| if (__sync_sub_and_fetch(&rap->ref_count, 1)) |
| return; |
| |
| rap_free(rap); |
| } |
| |
| static void rap_debug(struct bt_rap *rap, const char *format, ...) |
| { |
| va_list ap; |
| |
| if (!rap || !format || !rap->debug_func) |
| return; |
| |
| va_start(ap, format); |
| util_debug_va(rap->debug_func, rap->debug_data, format, ap); |
| va_end(ap); |
| } |
| |
| bool bt_rap_set_debug(struct bt_rap *rap, bt_rap_debug_func_t func, |
| void *user_data, bt_rap_destroy_func_t destroy) |
| { |
| if (!rap) |
| return false; |
| |
| if (rap->debug_destroy) |
| rap->debug_destroy(rap->debug_data); |
| |
| rap->debug_func = func; |
| rap->debug_destroy = destroy; |
| rap->debug_data = user_data; |
| |
| return true; |
| } |
| |
| static void cs_tracker_init(struct cstracker *t) |
| { |
| if (!t) |
| return; |
| |
| memset(t, 0, sizeof(*t)); |
| t->role = CS_ROLE_REFLECTOR; |
| t->config_id = CS_INVALID_CONFIG_ID; |
| t->rtt_type = 0; |
| t->selected_tx_power = 0; |
| t->last_proc_counter = 0; |
| t->last_start_acl_conn_evt_counter = 0; |
| t->last_freq_comp = 0; |
| t->last_ref_pwr_lvl = 0; |
| } |
| |
| static void ras_features_read_cb(struct gatt_db_attribute *attrib, |
| unsigned int id, uint16_t offset, |
| uint8_t opcode, struct bt_att *att, |
| void *user_data) |
| { |
| /* |
| * Feature mask: bits 0-2 set: |
| * - Real-time ranging |
| * - Retrieve stored results |
| * - Abort operation |
| */ |
| uint8_t value[4] = { 0x01, 0x00, 0x00, 0x00 }; |
| |
| 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) |
| { |
| /* No static read data – on‑demand data is pushed via |
| * notifications |
| */ |
| gatt_db_attribute_read_result(attrib, id, 0, NULL, 0); |
| } |
| |
| /* |
| * Control point handler. |
| * Parses the opcode and acts on queued data (implementation TBD). |
| */ |
| static void ras_control_point_write_cb(struct gatt_db_attribute *attrib, |
| unsigned int id, uint16_t offset, |
| const uint8_t *value, size_t len, |
| uint8_t opcode, struct bt_att *att, |
| void *user_data) |
| { |
| /* Control point handler - implementation TBD */ |
| } |
| |
| /* 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, |
| void *user_data) |
| { |
| uint16_t counter = 0; |
| uint8_t value[2]; |
| |
| put_le16(counter, value); |
| gatt_db_attribute_read_result(attrib, id, 0, value, sizeof(value)); |
| } |
| |
| /* 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, |
| void *user_data) |
| { |
| uint8_t value[2] = { 0x00, 0x00 }; |
| |
| gatt_db_attribute_read_result(attrib, id, 0, value, sizeof(value)); |
| } |
| |
| static void ras_ranging_data_ccc_write_cb(struct gatt_db_attribute *attrib, |
| unsigned int id, uint16_t offset, |
| const uint8_t *value, size_t len, |
| uint8_t opcode, struct bt_att *att, |
| void *user_data) |
| { |
| struct ras *ras = user_data; |
| uint16_t ccc_value; |
| bool is_realtime; |
| uint16_t *this_ccc; |
| uint16_t *other_ccc; |
| |
| if (!ras) { |
| gatt_db_attribute_write_result(attrib, id, |
| BT_ATT_ERROR_UNLIKELY); |
| return; |
| } |
| |
| if (offset) { |
| gatt_db_attribute_write_result(attrib, id, |
| BT_ATT_ERROR_INVALID_OFFSET); |
| return; |
| } |
| |
| if (len != 2) { |
| gatt_db_attribute_write_result(attrib, id, |
| BT_ATT_ERROR_INVALID_ATTRIBUTE_VALUE_LEN); |
| return; |
| } |
| |
| ccc_value = get_le16(value); |
| |
| if (ccc_value != 0x0000 && ccc_value != 0x0001 && |
| ccc_value != 0x0002 && ccc_value != 0x0003) { |
| gatt_db_attribute_write_result(attrib, id, |
| BT_ERROR_WRITE_REQUEST_REJECTED); |
| return; |
| } |
| |
| /* Determine which CCC this is */ |
| is_realtime = (attrib == ras->realtime_chrc_ccc); |
| this_ccc = is_realtime ? &ras->realtime_ccc_value : |
| &ras->ondemand_ccc_value; |
| other_ccc = is_realtime ? &ras->ondemand_ccc_value : |
| &ras->realtime_ccc_value; |
| |
| /* Check mutual exclusivity: reject if trying to enable realtime |
| * while ondemand is already enabled. |
| * Test case: RAS/SR/SPE/BI-11-C [Client enables both Real-time |
| * Ranging Data and On-demand Ranging Data notifications or |
| * indications] |
| */ |
| if (ccc_value != 0x0000 && *other_ccc != 0x0000) { |
| gatt_db_attribute_write_result(attrib, id, |
| BT_ERROR_CCC_IMPROPERLY_CONFIGURED); |
| return; |
| } |
| |
| /* Update state */ |
| *this_ccc = ccc_value; |
| |
| gatt_db_attribute_write_result(attrib, id, 0); |
| } |
| |
| /* Service registration – store attribute pointers */ |
| static struct ras *register_ras_service(struct gatt_db *db) |
| { |
| struct ras *ras; |
| struct gatt_db_attribute *service; |
| bt_uuid_t uuid; |
| |
| if (!db) |
| return NULL; |
| |
| ras = new0(struct ras, 1); |
| if (!ras) |
| return NULL; |
| |
| /* Primary RAS service */ |
| bt_uuid16_create(&uuid, RAS_UUID16); |
| service = gatt_db_add_service(db, &uuid, true, RAS_TOTAL_NUM_HANDLES); |
| if (!service) { |
| free(ras); |
| return NULL; |
| } |
| |
| ras->svc = service; |
| |
| /* RAS Features */ |
| bt_uuid16_create(&uuid, RAS_FEATURES_UUID); |
| ras->feat_chrc = |
| gatt_db_service_add_characteristic(ras->svc, &uuid, |
| BT_ATT_PERM_READ | |
| BT_ATT_PERM_READ_ENCRYPT, |
| BT_GATT_CHRC_PROP_READ, |
| ras_features_read_cb, |
| NULL, ras); |
| |
| /* Real-time Ranging Data */ |
| bt_uuid16_create(&uuid, RAS_REALTIME_DATA_UUID); |
| ras->realtime_chrc = |
| gatt_db_service_add_characteristic(ras->svc, &uuid, |
| BT_ATT_PERM_READ | |
| BT_ATT_PERM_READ_ENCRYPT, |
| BT_GATT_CHRC_PROP_NOTIFY | |
| BT_GATT_CHRC_PROP_INDICATE, |
| NULL, NULL, ras); |
| |
| ras->realtime_chrc_ccc = |
| gatt_db_service_add_ccc_custom(ras->svc, |
| BT_ATT_PERM_READ | BT_ATT_PERM_WRITE, |
| ras_ranging_data_ccc_write_cb, ras); |
| |
| /* On-demand Ranging Data */ |
| bt_uuid16_create(&uuid, RAS_ONDEMAND_DATA_UUID); |
| ras->ondemand_chrc = |
| gatt_db_service_add_characteristic(ras->svc, &uuid, |
| BT_ATT_PERM_READ | |
| BT_ATT_PERM_READ_ENCRYPT, |
| BT_GATT_CHRC_PROP_NOTIFY | |
| BT_GATT_CHRC_PROP_INDICATE, |
| ras_ondemand_read_cb, NULL, |
| ras); |
| |
| ras->ondemand_ccc = gatt_db_service_add_ccc_custom(ras->svc, |
| BT_ATT_PERM_READ | BT_ATT_PERM_WRITE, |
| ras_ranging_data_ccc_write_cb, ras); |
| |
| /* RAS Control Point */ |
| bt_uuid16_create(&uuid, RAS_CONTROL_POINT_UUID); |
| ras->cp_chrc = |
| gatt_db_service_add_characteristic(ras->svc, &uuid, |
| BT_ATT_PERM_WRITE | |
| BT_ATT_PERM_WRITE_ENCRYPT, |
| BT_GATT_CHRC_PROP_WRITE_WITHOUT_RESP | |
| BT_GATT_CHRC_PROP_INDICATE, |
| NULL, |
| ras_control_point_write_cb, |
| ras); |
| |
| ras->cp_ccc = gatt_db_service_add_ccc(ras->svc, |
| BT_ATT_PERM_READ | BT_ATT_PERM_WRITE); |
| |
| /* RAS Data Ready */ |
| bt_uuid16_create(&uuid, RAS_DATA_READY_UUID); |
| ras->ready_chrc = |
| gatt_db_service_add_characteristic(ras->svc, &uuid, |
| BT_ATT_PERM_READ | |
| BT_ATT_PERM_READ_ENCRYPT, |
| BT_GATT_CHRC_PROP_READ | |
| BT_GATT_CHRC_PROP_NOTIFY | |
| BT_GATT_CHRC_PROP_INDICATE, |
| ras_data_ready_read_cb, NULL, |
| ras); |
| |
| ras->ready_ccc = gatt_db_service_add_ccc(ras->svc, |
| BT_ATT_PERM_READ | BT_ATT_PERM_WRITE); |
| |
| /* RAS Data Overwritten */ |
| bt_uuid16_create(&uuid, RAS_DATA_OVERWRITTEN_UUID); |
| ras->overwritten_chrc = |
| gatt_db_service_add_characteristic(ras->svc, &uuid, |
| BT_ATT_PERM_READ | |
| BT_ATT_PERM_READ_ENCRYPT, |
| BT_GATT_CHRC_PROP_READ | |
| BT_GATT_CHRC_PROP_NOTIFY | |
| BT_GATT_CHRC_PROP_INDICATE, |
| ras_data_overwritten_read_cb, |
| NULL, ras); |
| |
| ras->overwritten_ccc = gatt_db_service_add_ccc(ras->svc, |
| BT_ATT_PERM_READ | BT_ATT_PERM_WRITE); |
| |
| /* Activate the service */ |
| gatt_db_service_set_active(ras->svc, true); |
| |
| return ras; |
| } |
| |
| static struct bt_rap_db *rap_db_new(struct gatt_db *db) |
| { |
| struct bt_rap_db *rapdb; |
| |
| if (!db) |
| return NULL; |
| |
| rapdb = new0(struct bt_rap_db, 1); |
| if (!rapdb) |
| return NULL; |
| |
| rapdb->db = gatt_db_ref(db); |
| |
| if (!rap_db) |
| rap_db = queue_new(); |
| |
| rapdb->ras = register_ras_service(db); |
| if (rapdb->ras) |
| rapdb->ras->rapdb = rapdb; |
| |
| queue_push_tail(rap_db, rapdb); |
| |
| return rapdb; |
| } |
| |
| static struct bt_rap_db *rap_get_db(struct gatt_db *db) |
| { |
| struct bt_rap_db *rapdb; |
| |
| rapdb = queue_find(rap_db, rap_db_match, db); |
| if (rapdb) |
| return rapdb; |
| |
| return rap_db_new(db); |
| } |
| |
| void bt_rap_add_db(struct gatt_db *db) |
| { |
| rap_db_new(db); |
| } |
| |
| unsigned int bt_rap_register(bt_rap_func_t attached, bt_rap_func_t detached, |
| void *user_data) |
| { |
| struct bt_rap_cb *cb; |
| static unsigned int id; |
| |
| if (!attached && !detached) |
| return 0; |
| |
| if (!bt_rap_cbs) |
| bt_rap_cbs = queue_new(); |
| |
| cb = new0(struct bt_rap_cb, 1); |
| cb->id = ++id ? id : ++id; |
| cb->attached = attached; |
| cb->detached = detached; |
| cb->user_data = user_data; |
| |
| queue_push_tail(bt_rap_cbs, cb); |
| |
| return cb->id; |
| } |
| |
| static bool match_id(const void *data, const void *match_data) |
| { |
| const struct bt_rap_cb *cb = data; |
| unsigned int id = PTR_TO_UINT(match_data); |
| |
| return cb->id == id; |
| } |
| |
| bool bt_rap_unregister(unsigned int id) |
| { |
| struct bt_rap_cb *cb; |
| |
| cb = queue_remove_if(bt_rap_cbs, match_id, UINT_TO_PTR(id)); |
| if (!cb) |
| return false; |
| |
| free(cb); |
| |
| return true; |
| } |
| |
| static inline size_t serialize_segmentation_header( |
| const struct segmentation_header *s, |
| uint8_t *out, size_t out_len) |
| { |
| if (!s || !out || out_len < 1) |
| return 0; |
| |
| out[0] = SEG_HDR_PACK(s->first_segment, s->last_segment, |
| s->rolling_segment_counter); |
| |
| return 1; |
| } |
| |
| static inline bool serialize_ranging_header_iov(const struct ranging_header *r, |
| struct iovec *iov) |
| { |
| if (!r || !iov) |
| return false; |
| |
| /* Serialize the per-byte packed fields using util_iov_push functions */ |
| if (!util_iov_push_le16(iov, get_le16(r->counter_config))) |
| return false; |
| |
| if (!util_iov_push_u8(iov, r->selected_tx_power)) |
| return false; |
| |
| if (!util_iov_push_u8(iov, r->antenna_pct)) |
| return false; |
| |
| return true; |
| } |
| |
| static inline uint16_t ras_att_value_payload_max(struct bt_rap *rap) |
| { |
| struct bt_att *att = bt_rap_get_att(rap); |
| uint16_t mtu = att ? bt_att_get_mtu(att) : default_ras_mtu; |
| |
| return (uint16_t)(mtu > ATT_OVERHEAD ? |
| (mtu - ATT_OVERHEAD - TOTAL_RAS_RANGING_HEADER_SIZE - |
| ras_segment_header_size) : 0); |
| } |
| |
| /* Prepend data to an iovec - optimized to avoid unnecessary malloc/copy |
| * by using realloc and memmove instead of malloc/memcpy/free pattern. |
| * This reduces memory allocations and is more cache-friendly. |
| */ |
| static bool iov_prepend_bytes(struct iovec *iov, const uint8_t *bytes, |
| size_t len) |
| { |
| size_t new_len; |
| void *new_base; |
| |
| if (!iov || !bytes || len == 0) |
| return false; |
| |
| new_len = iov->iov_len + len; |
| |
| /* Use realloc to potentially expand in-place */ |
| new_base = realloc(iov->iov_base, new_len); |
| |
| if (!new_base) |
| return false; |
| |
| /* Move existing data forward to make room at the beginning */ |
| if (iov->iov_len > 0) |
| memmove((uint8_t *)new_base + len, new_base, iov->iov_len); |
| |
| /* Copy new data to the beginning */ |
| memcpy(new_base, bytes, len); |
| |
| iov->iov_base = new_base; |
| iov->iov_len = new_len; |
| |
| return true; |
| } |
| |
| /* Append the 4-byte RangingHeader to ras_raw_data_ on first segment */ |
| static bool ras_maybe_prepend_ranging_header(struct cs_procedure_data *d) |
| { |
| struct iovec temp_iov = { 0 }; |
| bool ok; |
| |
| if (!d) |
| return false; |
| |
| if (d->ranging_header_prepended_) |
| return false; |
| |
| if (!d->segmentation_header_.first_segment) |
| return false; |
| |
| if (d->ras_raw_data_index_ != 0) |
| return false; |
| |
| temp_iov.iov_base = malloc(4); |
| if (!temp_iov.iov_base) |
| return false; |
| temp_iov.iov_len = 0; |
| |
| /* Serialize ranging header into temporary iovec */ |
| if (!serialize_ranging_header_iov(&d->ranging_header_, &temp_iov)) { |
| free(temp_iov.iov_base); |
| return false; |
| } |
| |
| /* Prepend the serialized header to ras_raw_data_ */ |
| ok = iov_prepend_bytes(&d->ras_raw_data_, temp_iov.iov_base, |
| temp_iov.iov_len); |
| |
| /* Free temporary iovec buffer */ |
| free(temp_iov.iov_base); |
| |
| if (ok) |
| d->ranging_header_prepended_ = true; |
| |
| return ok; |
| } |
| |
| static void send_ras_segment_data(struct bt_rap *rap, |
| struct cs_procedure_data *proc) |
| { |
| struct ras *ras; |
| uint16_t value_max; |
| const uint16_t header_len = ras_segment_header_size; |
| uint16_t raw_payload_size; |
| |
| if (!rap || !proc) |
| return; |
| |
| if (!rap->lrapdb || !rap->lrapdb->ras) |
| return; |
| |
| ras = rap->lrapdb->ras; |
| value_max = ras_att_value_payload_max(rap); |
| |
| if (value_max == 0) { |
| DBG(rap, "value_max=0 (MTU not available?)"); |
| return; |
| } |
| |
| if (value_max <= header_len) { |
| DBG(rap, "value_max(%u) too small for header", value_max); |
| return; |
| } |
| |
| raw_payload_size = (uint16_t)(value_max - header_len); |
| |
| /* Convert tail recursion to loop */ |
| while (true) { |
| size_t total_len = proc->ras_raw_data_.iov_len; |
| size_t index = proc->ras_raw_data_index_; |
| size_t unsent_data_size; |
| uint16_t copy_size; |
| uint16_t seg_len; |
| uint8_t *seg; |
| uint16_t wr = 0; |
| bool ok = true; |
| |
| if (index > total_len) |
| index = total_len; |
| |
| unsent_data_size = total_len - index; |
| |
| if (unsent_data_size == 0) |
| return; |
| |
| /* Set last_segment if procedure complete or fits in segment */ |
| if ((proc->local_status != CS_PROC_PARTIAL_RESULTS && |
| unsent_data_size <= raw_payload_size) || |
| (proc->contains_complete_subevent_ && |
| unsent_data_size <= raw_payload_size)) { |
| proc->segmentation_header_.last_segment = 1; |
| } else { |
| proc->segmentation_header_.last_segment = 0; |
| } |
| |
| /* Wait for more data if needed and not last segment */ |
| if (unsent_data_size < raw_payload_size && |
| proc->segmentation_header_.last_segment == 0) { |
| DBG(rap, "waiting for more data (unsent=%zu < " |
| "payload=%u)", unsent_data_size, |
| raw_payload_size); |
| return; |
| } |
| |
| copy_size = (uint16_t)((unsent_data_size < raw_payload_size) ? |
| unsent_data_size : raw_payload_size); |
| seg_len = (uint16_t)(header_len + copy_size); |
| seg = (uint8_t *)malloc(seg_len); |
| |
| if (!seg) { |
| DBG(rap, "OOM (%u)", seg_len); |
| return; |
| } |
| |
| wr += (uint16_t)serialize_segmentation_header( |
| &proc->segmentation_header_, seg + wr, |
| seg_len - wr); |
| memcpy(seg + wr, |
| (const uint8_t *)proc->ras_raw_data_.iov_base + index, |
| copy_size); |
| wr += copy_size; |
| |
| /* Try sending to real-time characteristic */ |
| if (ras->realtime_chrc) |
| ok = gatt_db_attribute_notify(ras->realtime_chrc, seg, |
| wr, bt_rap_get_att(rap)); |
| |
| /* Try sending to on-demand characteristic */ |
| if (ras->ondemand_chrc && ok) |
| ok = gatt_db_attribute_notify(ras->ondemand_chrc, seg, |
| wr, bt_rap_get_att(rap)); |
| |
| free(seg); |
| |
| if (!ok) { |
| DBG(rap, "Failed to send RAS notification"); |
| return; |
| } |
| |
| /* Advance read cursor and update segmentation state */ |
| proc->ras_raw_data_index_ += copy_size; |
| proc->segmentation_header_.first_segment = 0; |
| proc->segmentation_header_.rolling_segment_counter = |
| (uint8_t)((proc->segmentation_header_ |
| .rolling_segment_counter + 1) & 0x3F); |
| |
| if (proc->segmentation_header_.last_segment || |
| proc->ras_raw_data_index_ >= |
| proc->ras_raw_data_.iov_len) { |
| DBG(rap, "RAS clear ras buffers"); |
| proc->ras_raw_data_.iov_len = 0; |
| proc->ras_raw_data_index_ = 0; |
| proc->ranging_header_prepended_ = false; |
| return; |
| } |
| } |
| } |
| |
| static inline void resptracker_reset_current_proc(struct cstracker *t) |
| { |
| if (!t) |
| return; |
| |
| if (t->current_proc) { |
| cs_procedure_data_destroy(t->current_proc); |
| t->current_proc = NULL; |
| } |
| } |
| |
| static void process_cs_mode_zero(struct bt_rap *rap, |
| struct cs_procedure_data *proc, |
| const struct cs_step_data *step, |
| uint8_t idx, uint8_t mode_byte) |
| { |
| const uint8_t *payload; |
| uint8_t plen; |
| |
| /* Mode 0: use raw structure bytes */ |
| payload = (const uint8_t *)&step->step_mode_data; |
| plen = step->step_data_length; |
| cs_pd_ras_append_subevent_bytes(proc, payload, plen); |
| DBG(rap, "step[%u]: mode=0x%02x Mode0 payload_len=%u sent", |
| idx, mode_byte, (unsigned int)plen); |
| } |
| |
| static void process_cs_mode_one(struct bt_rap *rap, |
| struct cs_procedure_data *proc, |
| const struct cs_step_data *step, |
| uint8_t idx, uint8_t mode_byte) |
| { |
| const struct cs_mode_one_data *m1 = |
| &step->step_mode_data.mode_one_data; |
| struct cstracker *resptracker = rap->resptracker; |
| struct iovec temp_iov = { 0 }; |
| uint16_t time_val; |
| uint32_t pct1; |
| uint32_t pct2; |
| enum cs_role cs_role = resptracker->role; |
| uint8_t cs_rtt_type = resptracker->rtt_type; |
| bool include_pct; |
| |
| temp_iov.iov_base = malloc(64); |
| if (!temp_iov.iov_base) { |
| DBG(rap, "Mode1 ERROR: malloc failed!"); |
| return; |
| } |
| temp_iov.iov_len = 0; |
| |
| include_pct = (cs_rtt_type == 0x01 || cs_rtt_type == 0x02); |
| |
| if (!util_iov_push_u8(&temp_iov, m1->packet_quality) || |
| !util_iov_push_u8(&temp_iov, m1->packet_nadm) || |
| !util_iov_push_u8(&temp_iov, m1->packet_rssi_dbm)) |
| goto done; |
| |
| /* Time value (2 bytes LE) - use the appropriate field based on role */ |
| if (cs_role == CS_ROLE_REFLECTOR) |
| time_val = m1->tod_toa_refl; |
| else |
| time_val = m1->toa_tod_init; |
| |
| if (!util_iov_push_le16(&temp_iov, time_val) || |
| !util_iov_push_u8(&temp_iov, m1->packet_ant)) |
| goto done; |
| |
| if (include_pct) { |
| /* PCT1 (3 bytes LE) - 12-bit I + 12-bit Q */ |
| pct1 = ((uint32_t)(m1->packet_pct1.i_sample & 0x0FFF)) | |
| (((uint32_t)(m1->packet_pct1.q_sample & 0x0FFF)) << |
| 12); |
| if (!util_iov_push_le24(&temp_iov, pct1)) |
| goto done; |
| |
| /* PCT2 (3 bytes LE) */ |
| pct2 = ((uint32_t)(m1->packet_pct2.i_sample & 0x0FFF)) | |
| (((uint32_t)(m1->packet_pct2.q_sample & 0x0FFF)) << |
| 12); |
| if (!util_iov_push_le24(&temp_iov, pct2)) |
| goto done; |
| } |
| |
| cs_pd_ras_append_subevent_bytes(proc, temp_iov.iov_base, |
| temp_iov.iov_len); |
| |
| done: |
| free(temp_iov.iov_base); |
| |
| DBG(rap, "step[%u]: mode=0x%02x Mode1 serialized payload_len=%zu " |
| "role=%s rtt_type=0x%02x pct=%s", |
| idx, mode_byte, temp_iov.iov_len, |
| cs_role == CS_ROLE_INITIATOR ? "INIT" : "REFL", cs_rtt_type, |
| include_pct ? "YES" : "NO"); |
| } |
| |
| static void process_cs_mode_two(struct bt_rap *rap, |
| struct cs_procedure_data *proc, |
| const struct cs_step_data *step, |
| uint8_t num_ant_paths, |
| uint8_t idx, uint8_t mode_byte) |
| { |
| const struct cs_mode_two_data *m2 = |
| &step->step_mode_data.mode_two_data; |
| struct iovec temp_iov = { 0 }; |
| uint8_t k; |
| uint8_t num_paths = (num_ant_paths + 1) < 5 ? |
| (num_ant_paths + 1) : 5; |
| |
| temp_iov.iov_base = malloc(128); |
| if (!temp_iov.iov_base) { |
| DBG(rap, "Mode2 ERROR: malloc failed!"); |
| return; |
| } |
| temp_iov.iov_len = 0; |
| |
| if (!util_iov_push_u8(&temp_iov, m2->ant_perm_index)) |
| goto done; |
| |
| /* Serialize each path: PCT (3 bytes LE) + quality (1 byte) */ |
| for (k = 0; k < num_paths; k++) { |
| /* Convert 4-byte structure PCT to 3-byte wire format */ |
| uint32_t pct = ((uint32_t)(m2->tone_pct[k].i_sample & |
| 0x0FFF)) | |
| (((uint32_t)(m2->tone_pct[k].q_sample & |
| 0x0FFF)) << 12); |
| if (!util_iov_push_le24(&temp_iov, pct) || |
| !util_iov_push_u8(&temp_iov, |
| m2->tone_quality_indicator[k])) |
| goto done; |
| } |
| |
| cs_pd_ras_append_subevent_bytes(proc, temp_iov.iov_base, |
| temp_iov.iov_len); |
| |
| done: |
| free(temp_iov.iov_base); |
| |
| DBG(rap, "step[%u]: mode=0x%02x Mode2 serialized payload_len=%zu " |
| "paths=%u", |
| idx, mode_byte, temp_iov.iov_len, num_paths); |
| } |
| |
| static void process_cs_mode_three(struct bt_rap *rap, |
| struct cs_procedure_data *proc, |
| const struct cs_step_data *step, |
| uint8_t num_ant_paths, |
| uint8_t idx, uint8_t mode_byte) |
| { |
| const struct cs_mode_three_data *m3 = |
| &step->step_mode_data.mode_three_data; |
| const struct cs_mode_one_data *m1 = &m3->mode_one_data; |
| const struct cs_mode_two_data *m2 = &m3->mode_two_data; |
| struct cstracker *resptracker = rap->resptracker; |
| struct iovec temp_iov = { 0 }; |
| uint16_t time_val; |
| uint32_t pct1; |
| uint32_t pct2; |
| enum cs_role cs_role = resptracker->role; |
| uint8_t cs_rtt_type = resptracker->rtt_type; |
| uint8_t k; |
| uint8_t num_paths = (num_ant_paths + 1) < 5 ? |
| (num_ant_paths + 1) : 5; |
| bool include_pct; |
| |
| temp_iov.iov_base = malloc(128); |
| if (!temp_iov.iov_base) { |
| DBG(rap, "Mode3 ERROR: malloc failed!"); |
| return; |
| } |
| temp_iov.iov_len = 0; |
| |
| /* Determine if PCT samples should be included */ |
| include_pct = (cs_rtt_type == 0x01 || cs_rtt_type == 0x02); |
| |
| if (!util_iov_push_u8(&temp_iov, m1->packet_quality) || |
| !util_iov_push_u8(&temp_iov, m1->packet_nadm) || |
| !util_iov_push_u8(&temp_iov, m1->packet_rssi_dbm)) |
| goto done; |
| |
| /* Time value (2 bytes LE) - use the appropriate field based on role */ |
| if (cs_role == CS_ROLE_REFLECTOR) |
| time_val = m1->tod_toa_refl; |
| else |
| time_val = m1->toa_tod_init; |
| |
| if (!util_iov_push_le16(&temp_iov, time_val) || |
| !util_iov_push_u8(&temp_iov, m1->packet_ant)) |
| goto done; |
| |
| /* PCT samples if RTT type contains sounding sequence */ |
| if (include_pct) { |
| /* PCT1 (3 bytes LE) - 12-bit I + 12-bit Q */ |
| pct1 = ((uint32_t)(m1->packet_pct1.i_sample & 0x0FFF)) | |
| (((uint32_t)(m1->packet_pct1.q_sample & 0x0FFF)) << |
| 12); |
| |
| if (!util_iov_push_le24(&temp_iov, pct1)) |
| goto done; |
| |
| /* PCT2 (3 bytes LE) */ |
| pct2 = ((uint32_t)(m1->packet_pct2.i_sample & 0x0FFF)) | |
| (((uint32_t)(m1->packet_pct2.q_sample & 0x0FFF)) << |
| 12); |
| |
| if (!util_iov_push_le24(&temp_iov, pct2)) |
| goto done; |
| } |
| |
| if (!util_iov_push_u8(&temp_iov, m2->ant_perm_index)) |
| goto done; |
| |
| for (k = 0; k < num_paths; k++) { |
| /* Convert 4-byte structure PCT to 3-byte wire format */ |
| uint32_t pct = ((uint32_t)(m2->tone_pct[k].i_sample & |
| 0x0FFF)) | |
| (((uint32_t)(m2->tone_pct[k].q_sample & |
| 0x0FFF)) << 12); |
| if (!util_iov_push_le24(&temp_iov, pct) || |
| !util_iov_push_u8(&temp_iov, |
| m2->tone_quality_indicator[k])) |
| goto done; |
| } |
| |
| cs_pd_ras_append_subevent_bytes(proc, temp_iov.iov_base, |
| temp_iov.iov_len); |
| |
| done: |
| free(temp_iov.iov_base); |
| |
| DBG(rap, "=== Mode3 END: step[%u] payload_len=%zu paths=%u role=%s " |
| "rtt_type=0x%02x pct=%s ===", |
| idx, temp_iov.iov_len, num_paths, |
| cs_role == CS_ROLE_INITIATOR ? "INIT" : "REFL", |
| cs_rtt_type, include_pct ? "YES" : "NO"); |
| } |
| |
| static void process_cs_mode_step(struct bt_rap *rap, |
| struct cs_procedure_data *proc, |
| const struct cs_step_data *step, |
| uint8_t num_ant_paths, |
| uint8_t idx) |
| { |
| const uint8_t mode = step->step_mode; |
| const uint8_t payload_len = step->step_data_length; |
| uint8_t mode_byte; |
| uint8_t mode_type; |
| const uint8_t *payload; |
| uint8_t plen; |
| bool step_aborted; |
| |
| /* Check if step is aborted: bit 7 of step_mode or 0 payload len */ |
| step_aborted = (mode & RAS_STEP_ABORTED_BIT) || (payload_len == 0); |
| |
| DBG(rap, "step[%u]: mode=0x%02x channel=%u payload_len=%u " |
| "aborted=%s", idx, mode, step->step_chnl, payload_len, |
| step_aborted ? "YES" : "NO"); |
| |
| mode_byte = step->step_mode; |
| |
| if (step_aborted) { |
| /* Ensure abort bit is set */ |
| mode_byte |= RAS_STEP_ABORTED_BIT; |
| cs_pd_ras_append_subevent_bytes(proc, &mode_byte, 1); |
| /* No payload when aborted - per RAS spec Table 3.8 */ |
| DBG(rap, "step[%u]: mode=0x%02x aborted, no payload sent", |
| idx, mode_byte); |
| return; |
| } |
| |
| mode_type = mode & 0x03; |
| |
| /* Mode byte first (without abort bit) */ |
| cs_pd_ras_append_subevent_bytes(proc, &mode_byte, 1); |
| |
| switch (mode_type) { |
| case CS_MODE_ZERO: |
| process_cs_mode_zero(rap, proc, step, idx, mode_byte); |
| break; |
| case CS_MODE_ONE: |
| process_cs_mode_one(rap, proc, step, idx, mode_byte); |
| break; |
| case CS_MODE_TWO: |
| process_cs_mode_two(rap, proc, step, num_ant_paths, idx, |
| mode_byte); |
| break; |
| case CS_MODE_THREE: |
| process_cs_mode_three(rap, proc, step, num_ant_paths, idx, |
| mode_byte); |
| break; |
| default: |
| /* Unknown mode: use raw structure bytes */ |
| payload = (const uint8_t *)&step->step_mode_data; |
| plen = step->step_data_length; |
| cs_pd_ras_append_subevent_bytes(proc, payload, plen); |
| DBG(rap, "step[%u]: mode=0x%02x unknown mode, " |
| "payload_len=%u sent", |
| idx, mode_byte, (unsigned int)plen); |
| break; |
| } |
| } |
| |
| /* Unified local subevent handler */ |
| static void handle_local_subevent_result(struct bt_rap *rap, |
| bool has_header_fields, |
| uint8_t config_id, |
| uint8_t num_ant_paths, |
| uint16_t proc_counter, |
| uint16_t start_acl_conn_evt_counter, |
| uint16_t freq_comp, |
| int8_t ref_pwr_lvl, |
| uint8_t proc_done_status, |
| uint8_t subevt_done_status, |
| uint8_t abort_reason, |
| uint8_t num_steps_reported, |
| const void *step_bytes) |
| { |
| struct cstracker *resptracker; |
| struct cs_procedure_data *proc; |
| const struct cs_step_data *steps; |
| uint8_t idx; |
| |
| if (!rap || !rap->resptracker || !step_bytes) |
| return; |
| |
| resptracker = rap->resptracker; |
| |
| if (resptracker->current_proc) { |
| struct cs_procedure_data *cur = resptracker->current_proc; |
| |
| if (has_header_fields && cur->counter != proc_counter) { |
| /* Safety: a new procedure; destroy the previous one */ |
| resptracker_reset_current_proc(resptracker); |
| } |
| } |
| |
| proc = resptracker->current_proc; |
| /* Cache header info from a RESULT event for later CONT usage */ |
| if (has_header_fields) { |
| resptracker->last_proc_counter = proc_counter; |
| resptracker->last_start_acl_conn_evt_counter = |
| start_acl_conn_evt_counter; |
| resptracker->last_freq_comp = freq_comp; |
| resptracker->last_ref_pwr_lvl = ref_pwr_lvl; |
| } |
| |
| /* Create the procedure on first use */ |
| if (!proc) { |
| uint16_t create_counter = has_header_fields ? proc_counter : |
| resptracker->last_proc_counter; |
| |
| proc = cs_procedure_data_create(create_counter, |
| num_ant_paths, |
| config_id, |
| resptracker->selected_tx_power); |
| if (!proc) |
| return; |
| |
| resptracker->current_proc = proc; |
| |
| /* Reference power levels and status defaults */ |
| cs_pd_set_reference_power_levels(proc, |
| has_header_fields ? ref_pwr_lvl : |
| resptracker->last_ref_pwr_lvl, |
| has_header_fields ? ref_pwr_lvl : |
| resptracker->last_ref_pwr_lvl); |
| cs_pd_set_local_status(proc, |
| (enum cs_procedure_done_status)proc_done_status); |
| cs_pd_set_remote_status(proc, |
| (enum cs_procedure_done_status)subevt_done_status); |
| } |
| |
| /* Begin a new RAS subevent only when we have header fields */ |
| if (has_header_fields) { |
| cs_pd_ras_begin_subevent(proc, |
| start_acl_conn_evt_counter, |
| freq_comp, |
| ref_pwr_lvl); |
| } |
| |
| /* step_bytes points to an array of struct cs_step_data */ |
| steps = (const struct cs_step_data *)step_bytes; |
| |
| /* Process each step using helper function */ |
| for (idx = 0; idx < num_steps_reported; idx++) |
| process_cs_mode_step(rap, proc, &steps[idx], num_ant_paths, |
| idx); |
| |
| /* Update status for this chunk */ |
| cs_pd_set_local_status(proc, |
| (enum cs_procedure_done_status)proc_done_status); |
| cs_pd_set_remote_status(proc, |
| (enum cs_procedure_done_status)subevt_done_status); |
| |
| /* Commit subevent chunk (RESULT or CONT) */ |
| cs_pd_ras_commit_subevent(proc, |
| num_steps_reported, |
| proc_done_status, |
| subevt_done_status, |
| abort_reason & 0x0F, |
| (abort_reason >> 4) & 0x0F); |
| |
| /* 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); |
| |
| /* Procedure complete? Clean up */ |
| if (proc_done_status == CS_PROC_ALL_RESULTS_COMPLETE) { |
| DBG(rap, "Destroying CsProcedureData counter=%u and " |
| "clearing current_proc", proc->counter); |
| resptracker_reset_current_proc(resptracker); |
| /* Reset cached header values for next procedure */ |
| resptracker->last_proc_counter = 0; |
| resptracker->last_start_acl_conn_evt_counter = 0; |
| resptracker->last_freq_comp = 0; |
| resptracker->last_ref_pwr_lvl = 0; |
| } |
| } |
| |
| static void form_ras_data_with_cs_subevent_result(struct bt_rap *rap, |
| const struct rap_ev_cs_subevent_result *data, |
| uint16_t length) |
| { |
| size_t base_len = offsetof(struct rap_ev_cs_subevent_result, |
| step_data); |
| |
| if (!rap || !rap->resptracker || !data) |
| return; |
| |
| /* Defensive check: base header must be present */ |
| if (length < base_len) |
| return; |
| |
| DBG(rap, "Received CS subevent result subevent: len=%d", length); |
| |
| handle_local_subevent_result(rap, |
| true, /* has header fields */ |
| data->config_id, |
| data->num_ant_paths, |
| data->proc_counter, |
| data->start_acl_conn_evt_counter, |
| data->freq_comp, |
| data->ref_pwr_lvl, |
| data->proc_done_status, |
| data->subevt_done_status, |
| data->abort_reason, |
| data->num_steps_reported, |
| data->step_data); /* start of steps */ |
| } |
| |
| static void form_ras_data_with_cs_subevent_result_cont(struct bt_rap *rap, |
| const struct rap_ev_cs_subevent_result_cont *cont, |
| uint16_t length) |
| { |
| size_t base_len = offsetof(struct rap_ev_cs_subevent_result_cont, |
| step_data); |
| struct cstracker *resptracker; |
| |
| if (!rap || !rap->resptracker || !cont) |
| return; |
| |
| if (length < base_len) |
| return; |
| |
| DBG(rap, "Received CS subevent result continue subevent: len=%d", |
| length); |
| |
| resptracker = rap->resptracker; |
| |
| /* Use cached header values captured from the last RESULT event */ |
| handle_local_subevent_result(rap, |
| false, /* CONT has no header fields */ |
| cont->config_id, |
| cont->num_ant_paths, |
| resptracker->last_proc_counter, |
| resptracker->last_start_acl_conn_evt_counter, |
| resptracker->last_freq_comp, |
| resptracker->last_ref_pwr_lvl, |
| cont->proc_done_status, |
| cont->subevt_done_status, |
| cont->abort_reason, |
| cont->num_steps_reported, |
| cont->step_data); |
| } |
| |
| void bt_rap_hci_cs_subevent_result_cont_callback(uint16_t length, |
| const void *param, |
| void *user_data) |
| { |
| const struct rap_ev_cs_subevent_result_cont *cont = param; |
| struct bt_rap *rap = user_data; |
| |
| DBG(rap, "Received CS subevent CONT: len=%d", length); |
| |
| form_ras_data_with_cs_subevent_result_cont(rap, cont, length); |
| } |
| |
| void bt_rap_hci_cs_subevent_result_callback(uint16_t length, |
| const void *param, |
| void *user_data) |
| { |
| const struct rap_ev_cs_subevent_result *data = param; |
| struct bt_rap *rap = user_data; |
| |
| DBG(rap, "Received CS subevent: len=%d", length); |
| |
| /* Populate CsProcedureData and send RAS payload */ |
| form_ras_data_with_cs_subevent_result(rap, data, length); |
| } |
| |
| void bt_rap_hci_cs_procedure_enable_complete_callback(uint16_t length, |
| const void *param, |
| void *user_data) |
| { |
| const struct rap_ev_cs_proc_enable_cmplt *data = param; |
| struct bt_rap *rap = user_data; |
| struct cstracker *resptracker; |
| |
| DBG(rap, "Received CS procedure enable complete subevent: len=%d", |
| length); |
| |
| if (!rap->resptracker) { |
| resptracker = new0(struct cstracker, 1); |
| cs_tracker_init(resptracker); |
| rap->resptracker = resptracker; |
| } |
| |
| resptracker = rap->resptracker; |
| |
| /* Populate responder tracker */ |
| resptracker->config_id = data->config_id; |
| resptracker->selected_tx_power = data->sel_tx_pwr; |
| } |
| |
| void bt_rap_hci_cs_sec_enable_complete_callback(uint16_t length, |
| const void *param, |
| void *user_data) |
| { |
| struct bt_rap *rap = user_data; |
| |
| DBG(rap, "Received CS security enable subevent: len=%d", length); |
| } |
| |
| void bt_rap_hci_cs_config_complete_callback(uint16_t length, |
| const void *param, |
| void *user_data) |
| { |
| const struct rap_ev_cs_config_cmplt *data = param; |
| struct bt_rap *rap = user_data; |
| struct cstracker *resptracker; |
| |
| if (!rap) |
| return; |
| |
| DBG(rap, "Received CS config complete subevent: len=%d", length); |
| |
| if (!rap->resptracker) { |
| resptracker = new0(struct cstracker, 1); |
| cs_tracker_init(resptracker); |
| rap->resptracker = resptracker; |
| } |
| |
| resptracker = rap->resptracker; |
| |
| /* Basic fields */ |
| resptracker->config_id = data->config_id; |
| resptracker->role = data->role; |
| resptracker->rtt_type = data->rtt_type; |
| } |
| |
| struct bt_rap *bt_rap_new(struct gatt_db *ldb, struct gatt_db *rdb) |
| { |
| struct bt_rap *rap; |
| struct bt_rap_db *rapdb; |
| |
| if (!ldb) |
| return NULL; |
| |
| rapdb = rap_get_db(ldb); |
| if (!rapdb) |
| return NULL; |
| |
| rap = new0(struct bt_rap, 1); |
| rap->lrapdb = rapdb; |
| rap->pending = queue_new(); |
| rap->ready_cbs = queue_new(); |
| rap->notify = queue_new(); |
| |
| if (!rdb) |
| goto done; |
| |
| rapdb = new0(struct bt_rap_db, 1); |
| rapdb->db = gatt_db_ref(rdb); |
| |
| rap->rrapdb = rapdb; |
| |
| done: |
| bt_rap_ref(rap); |
| |
| return rap; |
| } |
| |
| static void foreach_rap_char(struct gatt_db_attribute *attr, void *user_data) |
| { |
| struct bt_rap *rap = user_data; |
| uint16_t value_handle; |
| bt_uuid_t uuid; |
| bt_uuid_t uuid_features; |
| bt_uuid_t uuid_realtime; |
| bt_uuid_t uuid_ondemand; |
| bt_uuid_t uuid_cp; |
| bt_uuid_t uuid_dataready; |
| bt_uuid_t uuid_overwritten; |
| struct ras *ras; |
| |
| if (!gatt_db_attribute_get_char_data(attr, NULL, &value_handle, |
| NULL, NULL, &uuid)) |
| return; |
| |
| bt_uuid16_create(&uuid_features, RAS_FEATURES_UUID); |
| bt_uuid16_create(&uuid_realtime, RAS_REALTIME_DATA_UUID); |
| bt_uuid16_create(&uuid_ondemand, RAS_ONDEMAND_DATA_UUID); |
| bt_uuid16_create(&uuid_cp, RAS_CONTROL_POINT_UUID); |
| bt_uuid16_create(&uuid_dataready, RAS_DATA_READY_UUID); |
| bt_uuid16_create(&uuid_overwritten, RAS_DATA_OVERWRITTEN_UUID); |
| |
| if (!bt_uuid_cmp(&uuid, &uuid_features)) { |
| DBG(rap, "Features characteristic found: handle 0x%04x", |
| value_handle); |
| |
| ras = rap_get_ras(rap); |
| if (!ras || ras->feat_chrc) |
| return; |
| |
| ras->feat_chrc = attr; |
| } |
| |
| if (!bt_uuid_cmp(&uuid, &uuid_realtime)) { |
| DBG(rap, "Real Time Data characteristic found: handle 0x%04x", |
| value_handle); |
| |
| ras = rap_get_ras(rap); |
| if (!ras || ras->realtime_chrc) |
| return; |
| |
| ras->realtime_chrc = attr; |
| } |
| |
| if (!bt_uuid_cmp(&uuid, &uuid_ondemand)) { |
| DBG(rap, "On-demand Data characteristic found: handle 0x%04x", |
| value_handle); |
| |
| ras = rap_get_ras(rap); |
| if (!ras || ras->ondemand_chrc) |
| return; |
| |
| ras->ondemand_chrc = attr; |
| } |
| |
| if (!bt_uuid_cmp(&uuid, &uuid_cp)) { |
| DBG(rap, "Control Point characteristic found: handle 0x%04x", |
| value_handle); |
| |
| ras = rap_get_ras(rap); |
| if (!ras || ras->cp_chrc) |
| return; |
| |
| ras->cp_chrc = attr; |
| } |
| |
| if (!bt_uuid_cmp(&uuid, &uuid_dataready)) { |
| DBG(rap, "Data Ready characteristic found: handle 0x%04x", |
| value_handle); |
| |
| ras = rap_get_ras(rap); |
| if (!ras || ras->ready_chrc) |
| return; |
| |
| ras->ready_chrc = attr; |
| } |
| |
| if (!bt_uuid_cmp(&uuid, &uuid_overwritten)) { |
| DBG(rap, "Overwritten characteristic found: handle 0x%04x", |
| value_handle); |
| |
| ras = rap_get_ras(rap); |
| if (!ras || ras->overwritten_chrc) |
| return; |
| |
| ras->overwritten_chrc = attr; |
| } |
| } |
| |
| static void foreach_rap_service(struct gatt_db_attribute *attr, |
| void *user_data) |
| { |
| struct bt_rap *rap = user_data; |
| struct ras *ras = rap_get_ras(rap); |
| |
| ras->svc = attr; |
| |
| gatt_db_service_set_claimed(attr, true); |
| |
| gatt_db_service_foreach_char(attr, foreach_rap_char, rap); |
| } |
| |
| unsigned int bt_rap_ready_register(struct bt_rap *rap, |
| bt_rap_ready_func_t func, void *user_data, |
| bt_rap_destroy_func_t destroy) |
| { |
| struct bt_rap_ready *ready; |
| static unsigned int id; |
| |
| if (!rap) |
| return 0; |
| |
| DBG(rap, "bt_rap_ready_register"); |
| |
| ready = new0(struct bt_rap_ready, 1); |
| ready->id = ++id ? id : ++id; |
| ready->func = func; |
| ready->destroy = destroy; |
| ready->data = user_data; |
| |
| queue_push_tail(rap->ready_cbs, ready); |
| |
| return ready->id; |
| } |
| |
| static bool match_ready_id(const void *data, const void *match_data) |
| { |
| const struct bt_rap_ready *ready = data; |
| unsigned int id = PTR_TO_UINT(match_data); |
| |
| return ready->id == id; |
| } |
| |
| bool bt_rap_ready_unregister(struct bt_rap *rap, unsigned int id) |
| { |
| struct bt_rap_ready *ready; |
| |
| ready = queue_remove_if(rap->ready_cbs, match_ready_id, |
| UINT_TO_PTR(id)); |
| if (!ready) |
| return false; |
| |
| rap_ready_free(ready); |
| |
| return true; |
| } |
| |
| static struct bt_rap *bt_rap_ref_safe(struct bt_rap *rap) |
| { |
| if (!rap || !rap->ref_count) |
| return NULL; |
| |
| return bt_rap_ref(rap); |
| } |
| |
| static void rap_notify_ready(struct bt_rap *rap) |
| { |
| const struct queue_entry *entry; |
| |
| if (!bt_rap_ref_safe(rap)) |
| return; |
| |
| for (entry = queue_get_entries(rap->ready_cbs); entry; |
| entry = entry->next) { |
| struct bt_rap_ready *ready = entry->data; |
| |
| ready->func(rap, ready->data); |
| } |
| |
| bt_rap_unref(rap); |
| } |
| |
| static void rap_idle(void *data) |
| { |
| struct bt_rap *rap = data; |
| |
| rap->idle_id = 0; |
| rap_notify_ready(rap); |
| } |
| |
| bool bt_rap_attach(struct bt_rap *rap, struct bt_gatt_client *client) |
| { |
| bt_uuid_t uuid; |
| |
| if (!sessions) |
| sessions = queue_new(); |
| |
| queue_push_tail(sessions, rap); |
| |
| if (!client) |
| return true; |
| |
| if (rap->client) |
| return false; |
| |
| rap->client = bt_gatt_client_clone(client); |
| if (!rap->client) |
| return false; |
| |
| bt_gatt_client_idle_register(rap->client, rap_idle, rap, NULL); |
| |
| bt_uuid16_create(&uuid, RAS_UUID16); |
| |
| gatt_db_foreach_service(rap->rrapdb->db, &uuid, |
| foreach_rap_service, rap); |
| |
| return true; |
| } |