| // 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 <glib.h> |
| |
| #include "bluetooth/bluetooth.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, arg...) \ |
| rap_debug(_rap, "%s:%s() " fmt, __FILE__, __func__, ## arg) |
| |
| #define RAS_UUID16 0x185B |
| |
| /* Total number of attribute handles reserved for the RAS service */ |
| #define RAS_TOTAL_NUM_HANDLES 18 |
| |
| /* 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 *cp_chrc; |
| struct gatt_db_attribute *ready_chrc; |
| struct gatt_db_attribute *overwritten_chrc; |
| }; |
| |
| 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; |
| }; |
| |
| 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; |
| }; |
| |
| 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); |
| |
| 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 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)); |
| } |
| |
| /* 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(ras->svc, |
| BT_ATT_PERM_READ | |
| BT_ATT_PERM_WRITE); |
| |
| /* 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); |
| |
| gatt_db_service_add_ccc(ras->svc, |
| BT_ATT_PERM_READ | BT_ATT_PERM_WRITE); |
| |
| /* 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); |
| |
| 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); |
| |
| 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); |
| |
| 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; |
| } |
| |
| 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->lrapdb->db, &uuid, |
| foreach_rap_service, rap); |
| |
| return true; |
| } |