/* * Copyright 2024 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include #include #include #include #include #include #include #include #include #include #include "bluetooth/log.h" #include "bta/include/bta_gatt_api.h" #include "bta/include/bta_ras_api.h" #include "bta/ras/ras_types.h" #include "btm_ble_api_types.h" #include "gatt/database.h" #include "gatt_api.h" #include "gattdefs.h" #include "gd/hci/controller_interface.h" #include "main/shim/entry.h" #include "osi/include/alarm.h" #include "stack/include/bt_types.h" #include "stack/include/btm_ble_addr.h" #include "stack/include/gap_api.h" #include "stack/include/main_thread.h" #include "types/ble_address_with_type.h" #include "types/bluetooth/uuid.h" #include "types/bt_transport.h" #include "types/raw_address.h" using namespace bluetooth; using namespace ::ras; using namespace ::ras::feature; using namespace ::ras::uuid; using bluetooth::ras::VendorSpecificCharacteristic; namespace { class RasClientImpl; RasClientImpl* instance; enum CallbackDataType { VENDOR_SPECIFIC_REPLY }; enum TimeoutType { TIMEOUT_NONE, FIRST_SEGMENT, FOLLOWING_SEGMENT, RANGING_DATA_READY }; enum RangingType { RANGING_TYPE_NONE, REAL_TIME, ON_DEMAND }; class RasClientImpl : public bluetooth::ras::RasClient { static constexpr uint16_t kCachedDataSize = 10; static constexpr uint16_t kInvalidGattHandle = 0x0000; static constexpr uint16_t kFirstSegmentRangingDataTimeoutMs = 5000; static constexpr uint16_t kFollowingSegmentTimeoutMs = 1000; static constexpr uint16_t kRangingDataReadyTimeoutMs = 5000; static constexpr uint16_t kInvalidConnInterval = 0; // valid value is from 0x0006 to 0x0C0 public: struct GattReadCallbackData { const bool is_last_; }; struct GattWriteCallbackData { const CallbackDataType type_; }; struct CachedRasData { uint8_t id_ = 0; uint32_t remote_supported_features_; std::unordered_map> vendor_specific_data_; }; struct RasTracker { RasTracker(const RawAddress& address, const RawAddress& address_for_cs) : address_(address), address_for_cs_(address_for_cs) {} ~RasTracker() { if (ranging_data_timeout_timer_ != nullptr) { alarm_free(ranging_data_timeout_timer_); } } tCONN_ID conn_id_; RawAddress address_; RawAddress address_for_cs_; const gatt::Service* service_ = nullptr; uint32_t remote_supported_features_; uint16_t latest_ranging_counter_ = 0; bool handling_on_demand_data_ = false; bool is_connected_ = false; bool service_search_complete_ = false; std::vector vendor_specific_characteristics_; uint8_t write_reply_counter_ = 0; uint8_t write_reply_success_counter_ = 0; alarm_t* ranging_data_timeout_timer_ = nullptr; RangingType ranging_type_ = RANGING_TYPE_NONE; TimeoutType timeout_type_ = TIMEOUT_NONE; uint16_t conn_interval_ = kInvalidConnInterval; const gatt::Characteristic* FindCharacteristicByUuid(Uuid uuid) { for (auto& characteristic : service_->characteristics) { if (characteristic.uuid == uuid) { return &characteristic; } } return nullptr; } const gatt::Characteristic* FindCharacteristicByHandle(uint16_t handle) { for (auto& characteristic : service_->characteristics) { if (characteristic.value_handle == handle) { return &characteristic; } } return nullptr; } VendorSpecificCharacteristic* GetVendorSpecificCharacteristic(const bluetooth::Uuid& uuid) { for (auto& characteristic : vendor_specific_characteristics_) { if (characteristic.characteristicUuid_ == uuid) { return &characteristic; } } return nullptr; } }; void Initialize() override { do_in_main_thread(base::BindOnce(&RasClientImpl::do_initialize, base::Unretained(this))); } void do_initialize() { auto controller = bluetooth::shim::GetController(); if (controller && !controller->SupportsBleChannelSounding()) { log::info("controller does not support channel sounding."); return; } BTA_GATTC_AppRegister( [](tBTA_GATTC_EVT event, tBTA_GATTC* p_data) { if (instance && p_data) { instance->GattcCallback(event, p_data); } }, base::Bind([](uint8_t client_id, uint8_t status) { if (status != GATT_SUCCESS) { log::error("Can't start Gatt client for Ranging Service"); return; } log::info("Initialize, client_id {}", client_id); instance->gatt_if_ = client_id; }), true); } void RegisterCallbacks(bluetooth::ras::RasClientCallbacks* callbacks) { callbacks_ = callbacks; } void Connect(const RawAddress& address) override { tBLE_BD_ADDR ble_bd_addr; ResolveAddress(ble_bd_addr, address); log::info("address {}, resolve {}", address, ble_bd_addr.bda); auto tracker = FindTrackerByAddress(ble_bd_addr.bda); if (tracker == nullptr) { trackers_.emplace_back(std::make_shared(ble_bd_addr.bda, address)); } else if (tracker->is_connected_) { log::info("Already connected"); auto characteristic = tracker->FindCharacteristicByUuid(kRasRealTimeRangingDataCharacteristic); uint16_t real_time_att_handle = characteristic == nullptr ? kInvalidGattHandle : characteristic->value_handle; // Check if the Real-Time ranging unsubscribed due to timeout if (characteristic != nullptr && tracker->ranging_type_ == RANGING_TYPE_NONE) { tracker->ranging_type_ = REAL_TIME; SubscribeCharacteristic(tracker, kRasRealTimeRangingDataCharacteristic); SetTimeOutAlarm(tracker, kFirstSegmentRangingDataTimeoutMs, TimeoutType::FIRST_SEGMENT); } callbacks_->OnConnected(address, real_time_att_handle, tracker->vendor_specific_characteristics_, tracker->conn_interval_); return; } BTA_GATTC_Open(gatt_if_, ble_bd_addr.bda, BTM_BLE_DIRECT_CONNECTION, true); } void SendVendorSpecificReply( const RawAddress& address, const std::vector& vendor_specific_data) { tBLE_BD_ADDR ble_bd_addr; ResolveAddress(ble_bd_addr, address); log::info("address {}, resolve {}", address, ble_bd_addr.bda); auto tracker = FindTrackerByAddress(ble_bd_addr.bda); for (auto& vendor_specific_characteristic : vendor_specific_data) { auto characteristic = tracker->FindCharacteristicByUuid(vendor_specific_characteristic.characteristicUuid_); if (characteristic == nullptr) { log::warn("Can't find characteristic uuid {}", vendor_specific_characteristic.characteristicUuid_); return; } log::debug("write to remote, uuid {}, len {}", vendor_specific_characteristic.characteristicUuid_, vendor_specific_characteristic.value_.size()); BTA_GATTC_WriteCharValue(tracker->conn_id_, characteristic->value_handle, GATT_WRITE_NO_RSP, vendor_specific_characteristic.value_, GATT_AUTH_REQ_NO_MITM, GattWriteCallback, &gatt_write_callback_data_); } } void GattcCallback(tBTA_GATTC_EVT event, tBTA_GATTC* p_data) { log::debug("event: {}", gatt_client_event_text(event)); switch (event) { case BTA_GATTC_OPEN_EVT: { OnGattConnected(p_data->open); } break; case BTA_GATTC_CLOSE_EVT: { OnGattDisconnected(p_data->close); break; } case BTA_GATTC_SEARCH_CMPL_EVT: { OnGattServiceSearchComplete(p_data->search_cmpl); } break; case BTA_GATTC_NOTIF_EVT: { OnGattNotification(p_data->notify); } break; case BTA_GATTC_CONN_UPDATE_EVT: { OnConnUpdated(p_data->conn_update); } break; default: log::warn("Unhandled event: {}", gatt_client_event_text(event)); } } void OnConnUpdated(const tBTA_GATTC_CONN_UPDATE& evt) const { auto tracker = FindTrackerByHandle(evt.conn_id); if (tracker == nullptr) { log::debug("no ongoing measurement, skip"); return; } tracker->conn_interval_ = evt.interval; log::info("conn interval is updated as {}", evt.interval); callbacks_->OnConnIntervalUpdated(tracker->address_for_cs_, tracker->conn_interval_); } void OnGattConnected(const tBTA_GATTC_OPEN& evt) { log::info("{}, conn_id=0x{:04x}, transport:{}, status:{}", evt.remote_bda, evt.conn_id, bt_transport_text(evt.transport), gatt_status_text(evt.status)); if (evt.transport != BT_TRANSPORT_LE) { log::warn("Only LE connection is allowed (transport {})", bt_transport_text(evt.transport)); BTA_GATTC_Close(evt.conn_id); return; } auto tracker = FindTrackerByAddress(evt.remote_bda); if (tracker == nullptr) { log::warn("Skipping unknown device, address: {}", evt.remote_bda); BTA_GATTC_Close(evt.conn_id); return; } if (evt.status != GATT_SUCCESS) { log::error("Failed to connect to server device {}", evt.remote_bda); return; } tracker->conn_id_ = evt.conn_id; tracker->is_connected_ = true; log::info("Search service"); BTA_GATTC_ServiceSearchRequest(tracker->conn_id_, kRangingService); } void OnGattDisconnected(const tBTA_GATTC_CLOSE& evt) { log::info("{}, conn_id=0x{:04x}, status:{}, reason:{}", evt.remote_bda, evt.conn_id, gatt_status_text(evt.status), gatt_disconnection_reason_text(evt.reason)); auto tracker = FindTrackerByAddress(evt.remote_bda); if (tracker == nullptr) { log::warn("Skipping unknown device, address: {}", evt.remote_bda); BTA_GATTC_Close(evt.conn_id); return; } callbacks_->OnDisconnected(tracker->address_for_cs_); trackers_.remove(tracker); } void OnGattServiceSearchComplete(const tBTA_GATTC_SEARCH_CMPL& evt) { auto tracker = FindTrackerByHandle(evt.conn_id); if (tracker == nullptr) { log::warn("Can't find tracker for conn_id:{}", evt.conn_id); return; } // Get Ranging Service bool service_found = false; const std::list* all_services = BTA_GATTC_GetServices(evt.conn_id); for (const auto& service : *all_services) { if (service.uuid == kRangingService) { tracker->service_ = &service; service_found = true; break; } } if (tracker->service_search_complete_) { log::info("Service search already completed, ignore"); return; } else if (!service_found) { log::error("Can't find Ranging Service in the services list"); return; } else { log::info("Found Ranging Service"); tracker->service_search_complete_ = true; ListCharacteristic(tracker); } if (UseCachedData(tracker)) { log::info("Use cached data for Ras features and vendor specific characteristic"); SubscribeCharacteristic(tracker, kRasControlPointCharacteristic); AllCharacteristicsReadComplete(tracker); } else { // Read Vendor Specific Uuid for (auto& vendor_specific_characteristic : tracker->vendor_specific_characteristics_) { log::debug("Read vendor specific characteristic uuid {}", vendor_specific_characteristic.characteristicUuid_); auto characteristic = tracker->FindCharacteristicByUuid( vendor_specific_characteristic.characteristicUuid_); BTA_GATTC_ReadCharacteristic( tracker->conn_id_, characteristic->value_handle, GATT_AUTH_REQ_NO_MITM, [](tCONN_ID conn_id, tGATT_STATUS status, uint16_t handle, uint16_t len, uint8_t* value, void* data) { instance->OnReadCharacteristicCallback(conn_id, status, handle, len, value, data); }, nullptr); } // Read Ras Features log::info("Read Ras Features"); auto characteristic = tracker->FindCharacteristicByUuid(kRasFeaturesCharacteristic); if (characteristic == nullptr) { log::error("Can not find Characteristic for Ras Features"); return; } BTA_GATTC_ReadCharacteristic( tracker->conn_id_, characteristic->value_handle, GATT_AUTH_REQ_NO_MITM, [](uint16_t conn_id, tGATT_STATUS status, uint16_t handle, uint16_t len, uint8_t* value, void* data) { instance->OnReadCharacteristicCallback(conn_id, status, handle, len, value, data); }, &gatt_read_callback_data_); SubscribeCharacteristic(tracker, kRasControlPointCharacteristic); } } bool UseCachedData(std::shared_ptr tracker) { auto cached_data = cached_data_.find(tracker->address_); if (cached_data == cached_data_.end()) { return false; } // Check if everything is cached auto cached_vendor_specific_data = cached_data->second.vendor_specific_data_; for (auto& vendor_specific_characteristic : tracker->vendor_specific_characteristics_) { auto uuid = vendor_specific_characteristic.characteristicUuid_; if (cached_vendor_specific_data.find(uuid) != cached_vendor_specific_data.end()) { vendor_specific_characteristic.value_ = cached_vendor_specific_data[uuid]; } else { return false; } } // Update remote supported features tracker->remote_supported_features_ = cached_data->second.remote_supported_features_; return true; } void OnGattNotification(const tBTA_GATTC_NOTIFY& evt) { auto tracker = FindTrackerByHandle(evt.conn_id); if (tracker == nullptr) { log::warn("Can't find tracker for conn_id:{}", evt.conn_id); return; } auto characteristic = tracker->FindCharacteristicByHandle(evt.handle); if (characteristic == nullptr) { log::warn("Can't find characteristic for handle:{}", evt.handle); return; } uint16_t uuid_16bit = characteristic->uuid.As16Bit(); log::debug("Handle uuid 0x{:04x}, {}, size {}", uuid_16bit, getUuidName(characteristic->uuid), evt.len); switch (uuid_16bit) { case kRasRealTimeRangingDataCharacteristic16bit: case kRasOnDemandDataCharacteristic16bit: { OnRemoteData(evt, tracker); break; } case kRasControlPointCharacteristic16bit: { OnControlPointEvent(evt, tracker); } break; case kRasRangingDataReadyCharacteristic16bit: { OnRangingDataReady(evt, tracker); } break; default: log::warn("Unexpected UUID"); } } void OnRemoteData(const tBTA_GATTC_NOTIFY& evt, std::shared_ptr tracker) { std::vector data; data.resize(evt.len); std::copy(evt.value, evt.value + evt.len, data.begin()); bool is_last = (data[0] >> 1 & 0x01); alarm_cancel(tracker->ranging_data_timeout_timer_); if (!is_last) { SetTimeOutAlarm(tracker, kFollowingSegmentTimeoutMs, FOLLOWING_SEGMENT); } callbacks_->OnRemoteData(tracker->address_for_cs_, data); } void OnControlPointEvent(const tBTA_GATTC_NOTIFY& evt, std::shared_ptr tracker) { switch (evt.value[0]) { case (uint8_t)EventCode::COMPLETE_RANGING_DATA_RESPONSE: { uint16_t ranging_counter = evt.value[1]; ranging_counter |= (evt.value[2] << 8); log::debug("Received complete ranging data response, ranging_counter: {}", ranging_counter); AckRangingData(ranging_counter, tracker); } break; case (uint8_t)EventCode::RESPONSE_CODE: { tracker->handling_on_demand_data_ = false; log::debug("Received response code 0x{:02x}", evt.value[1]); } break; default: log::warn("Unexpected event code 0x{:02x}", evt.value[0]); } } void OnRangingDataReady(const tBTA_GATTC_NOTIFY& evt, std::shared_ptr tracker) { if (evt.len != kRingingCounterSize) { log::error("Invalid len for ranging data ready"); return; } uint16_t ranging_counter = evt.value[0]; ranging_counter |= (evt.value[1] << 8); log::debug("ranging_counter: {}", ranging_counter); // Send get ranging data command tracker->latest_ranging_counter_ = ranging_counter; if (tracker->timeout_type_ == RANGING_DATA_READY) { alarm_cancel(tracker->ranging_data_timeout_timer_); } GetRangingData(ranging_counter, tracker); } void GetRangingData(uint16_t ranging_counter, std::shared_ptr tracker) { log::debug("ranging_counter:{}", ranging_counter); if (tracker->handling_on_demand_data_) { log::warn("Handling other procedure, skip"); return; } auto characteristic = tracker->FindCharacteristicByUuid(kRasControlPointCharacteristic); if (characteristic == nullptr) { log::warn("Can't find characteristic for RAS-CP"); return; } tracker->handling_on_demand_data_ = true; std::vector value(3); value[0] = (uint8_t)Opcode::GET_RANGING_DATA; value[1] = (uint8_t)(ranging_counter & 0xFF); value[2] = (uint8_t)((ranging_counter >> 8) & 0xFF); BTA_GATTC_WriteCharValue(tracker->conn_id_, characteristic->value_handle, GATT_WRITE_NO_RSP, value, GATT_AUTH_REQ_NO_MITM, GattWriteCallback, nullptr); SetTimeOutAlarm(tracker, kFirstSegmentRangingDataTimeoutMs, FIRST_SEGMENT); } void AckRangingData(uint16_t ranging_counter, std::shared_ptr tracker) { log::debug("ranging_counter:{}", ranging_counter); auto characteristic = tracker->FindCharacteristicByUuid(kRasControlPointCharacteristic); if (characteristic == nullptr) { log::warn("Can't find characteristic for RAS-CP"); return; } tracker->handling_on_demand_data_ = false; std::vector value(3); value[0] = (uint8_t)Opcode::ACK_RANGING_DATA; value[1] = (uint8_t)(ranging_counter & 0xFF); value[2] = (uint8_t)((ranging_counter >> 8) & 0xFF); BTA_GATTC_WriteCharValue(tracker->conn_id_, characteristic->value_handle, GATT_WRITE_NO_RSP, value, GATT_AUTH_REQ_NO_MITM, GattWriteCallback, nullptr); if (ranging_counter != tracker->latest_ranging_counter_) { GetRangingData(tracker->latest_ranging_counter_, tracker); } } void AbortOperation(std::shared_ptr tracker) { log::debug("address {}", tracker->address_for_cs_); auto characteristic = tracker->FindCharacteristicByUuid(kRasControlPointCharacteristic); if (characteristic == nullptr) { log::warn("Can't find characteristic for RAS-CP"); return; } tracker->handling_on_demand_data_ = false; std::vector value{static_cast(Opcode::ABORT_OPERATION)}; BTA_GATTC_WriteCharValue(tracker->conn_id_, characteristic->value_handle, GATT_WRITE_NO_RSP, value, GATT_AUTH_REQ_NO_MITM, GattWriteCallback, nullptr); } void GattWriteCallbackForVendorSpecificData(tCONN_ID conn_id, tGATT_STATUS status, uint16_t handle, const uint8_t* /*value*/, GattWriteCallbackData* data) { if (data != nullptr) { GattWriteCallbackData* structPtr = static_cast(data); if (structPtr->type_ == CallbackDataType::VENDOR_SPECIFIC_REPLY) { log::info("Write vendor specific reply complete"); auto tracker = FindTrackerByHandle(conn_id); tracker->write_reply_counter_++; if (status == GATT_SUCCESS) { tracker->write_reply_success_counter_++; } else { log::error( "Fail to write vendor specific reply conn_id {}, status {}, " "handle {}", conn_id, gatt_status_text(status), handle); } // All reply complete if (tracker->write_reply_counter_ == tracker->vendor_specific_characteristics_.size()) { log::info( "All vendor specific reply write complete, size {} " "successCounter {}", tracker->vendor_specific_characteristics_.size(), tracker->write_reply_success_counter_); bool success = tracker->write_reply_success_counter_ == tracker->vendor_specific_characteristics_.size(); tracker->write_reply_counter_ = 0; tracker->write_reply_success_counter_ = 0; callbacks_->OnWriteVendorSpecificReplyComplete(tracker->address_for_cs_, success); } return; } } } void GattWriteCallback(tCONN_ID conn_id, tGATT_STATUS status, uint16_t handle, const uint8_t* /*value*/) { if (status != GATT_SUCCESS) { log::error("Fail to write conn_id {}, status {}, handle {}", conn_id, gatt_status_text(status), handle); auto tracker = FindTrackerByHandle(conn_id); if (tracker == nullptr) { log::warn("Can't find tracker for conn_id:{}", conn_id); return; } auto characteristic = tracker->FindCharacteristicByHandle(handle); if (characteristic == nullptr) { log::warn("Can't find characteristic for handle:{}", handle); return; } if (characteristic->uuid == kRasControlPointCharacteristic) { log::error("Write RAS-CP command fail"); tracker->handling_on_demand_data_ = false; } return; } } static void GattWriteCallback(tCONN_ID conn_id, tGATT_STATUS status, uint16_t handle, uint16_t /*len*/, const uint8_t* value, void* data) { if (instance != nullptr) { if (data != nullptr) { GattWriteCallbackData* structPtr = static_cast(data); if (structPtr->type_ == CallbackDataType::VENDOR_SPECIFIC_REPLY) { instance->GattWriteCallbackForVendorSpecificData(conn_id, status, handle, value, structPtr); return; } } instance->GattWriteCallback(conn_id, status, handle, value); } } void SubscribeCharacteristic(std::shared_ptr tracker, const Uuid uuid) { auto characteristic = tracker->FindCharacteristicByUuid(uuid); if (characteristic == nullptr) { log::warn("Can't find characteristic 0x{:04x}", uuid.As16Bit()); return; } uint16_t ccc_handle = FindCccHandle(characteristic); if (ccc_handle == GAP_INVALID_HANDLE) { log::warn("Can't find Client Characteristic Configuration descriptor"); return; } tGATT_STATUS register_status = BTA_GATTC_RegisterForNotifications(gatt_if_, tracker->address_, characteristic->value_handle); if (register_status != GATT_SUCCESS) { log::error("Fail to register, {}", gatt_status_text(register_status)); return; } std::vector value(2); uint8_t* value_ptr = value.data(); // Register notify is supported if (characteristic->properties & GATT_CHAR_PROP_BIT_NOTIFY) { UINT16_TO_STREAM(value_ptr, GATT_CHAR_CLIENT_CONFIG_NOTIFICATION); } else { UINT16_TO_STREAM(value_ptr, GATT_CHAR_CLIENT_CONFIG_INDICTION); } BTA_GATTC_WriteCharDescr( tracker->conn_id_, ccc_handle, value, GATT_AUTH_REQ_NONE, [](tCONN_ID conn_id, tGATT_STATUS status, uint16_t handle, uint16_t len, const uint8_t* value, void* data) { if (instance) { instance->OnDescriptorWrite(conn_id, status, handle, len, value, data); } }, nullptr); } void UnsubscribeCharacteristic(std::shared_ptr tracker, const Uuid uuid) { auto characteristic = tracker->FindCharacteristicByUuid(uuid); if (characteristic == nullptr) { log::warn("Can't find characteristic 0x{:04x}", uuid.As16Bit()); return; } uint16_t ccc_handle = FindCccHandle(characteristic); if (ccc_handle == GAP_INVALID_HANDLE) { log::warn("Can't find Client Characteristic Configuration descriptor"); return; } tGATT_STATUS register_status = BTA_GATTC_DeregisterForNotifications( gatt_if_, tracker->address_, characteristic->value_handle); if (register_status != GATT_SUCCESS) { log::error("Fail to deregister, {}", gatt_status_text(register_status)); return; } log::info("UnsubscribeCharacteristic 0x{:04x}", uuid.As16Bit()); std::vector ccc_none(2, 0); BTA_GATTC_WriteCharDescr( tracker->conn_id_, ccc_handle, ccc_none, GATT_AUTH_REQ_NONE, [](tCONN_ID conn_id, tGATT_STATUS status, uint16_t handle, uint16_t len, const uint8_t* value, void* data) { if (instance) { instance->OnDescriptorWrite(conn_id, status, handle, len, value, data); } }, nullptr); } void OnDescriptorWrite(tCONN_ID conn_id, tGATT_STATUS status, uint16_t handle, uint16_t /*len*/, const uint8_t* /*value*/, void* /*data*/) { log::info("conn_id:{}, handle:{}, status:{}", conn_id, handle, gatt_status_text(status)); } void ListCharacteristic(std::shared_ptr tracker) { tracker->vendor_specific_characteristics_.clear(); for (auto& characteristic : tracker->service_->characteristics) { bool vendor_specific = !IsRangingServiceCharacteristic(characteristic.uuid); log::info( "{}Characteristic uuid:0x{:04x}, handle:0x{:04x}, " "properties:0x{:02x}, " "{}", vendor_specific ? "Vendor Specific " : "", characteristic.uuid.As16Bit(), characteristic.value_handle, characteristic.properties, getUuidName(characteristic.uuid)); if (vendor_specific) { VendorSpecificCharacteristic vendor_specific_characteristic; vendor_specific_characteristic.characteristicUuid_ = characteristic.uuid; tracker->vendor_specific_characteristics_.emplace_back(vendor_specific_characteristic); } for (auto& descriptor : characteristic.descriptors) { log::info("\tDescriptor uuid:0x{:04x}, handle:0x{:04x}, {}", descriptor.uuid.As16Bit(), descriptor.handle, getUuidName(descriptor.uuid)); } } } void ResolveAddress(tBLE_BD_ADDR& ble_bd_addr, const RawAddress& address) { ble_bd_addr.bda = address; ble_bd_addr.type = BLE_ADDR_RANDOM; maybe_resolve_address(&ble_bd_addr.bda, &ble_bd_addr.type); } void OnReadCharacteristicCallback(tCONN_ID conn_id, tGATT_STATUS status, uint16_t handle, uint16_t len, uint8_t* value, void* data) { log::info("conn_id: {}, handle: {}, len: {}", conn_id, handle, len); if (status != GATT_SUCCESS) { log::error("Fail with status {}", gatt_status_text(status)); return; } auto tracker = FindTrackerByHandle(conn_id); if (tracker == nullptr) { log::warn("Can't find tracker for conn_id:{}", conn_id); return; } auto characteristic = tracker->FindCharacteristicByHandle(handle); if (characteristic == nullptr) { log::warn("Can't find characteristic for handle:{}", handle); return; } auto vendor_specific_characteristic = tracker->GetVendorSpecificCharacteristic(characteristic->uuid); if (vendor_specific_characteristic != nullptr) { log::info("Update vendor specific data, uuid: {}", vendor_specific_characteristic->characteristicUuid_); vendor_specific_characteristic->value_.clear(); vendor_specific_characteristic->value_.reserve(len); vendor_specific_characteristic->value_.assign(value, value + len); return; } uint16_t uuid_16bit = characteristic->uuid.As16Bit(); log::info("Handle uuid 0x{:04x}, {}", uuid_16bit, getUuidName(characteristic->uuid)); switch (uuid_16bit) { case kRasFeaturesCharacteristic16bit: { if (len != kFeatureSize) { log::error("Invalid len for Ras features"); return; } STREAM_TO_UINT32(tracker->remote_supported_features_, value); log::info("Remote supported features : {}", GetFeaturesString(tracker->remote_supported_features_)); } break; default: log::warn("Unexpected UUID"); } // Check is last read reply or not GattReadCallbackData* cb_data = static_cast(data); if (cb_data != nullptr) { StoreCachedData(tracker); AllCharacteristicsReadComplete(tracker); } } void AllCharacteristicsReadComplete(std::shared_ptr tracker) { if (tracker->remote_supported_features_ & feature::kRealTimeRangingData) { log::info("Subscribe Real-time Ranging Data"); tracker->ranging_type_ = REAL_TIME; SubscribeCharacteristic(tracker, kRasRealTimeRangingDataCharacteristic); SetTimeOutAlarm(tracker, kFirstSegmentRangingDataTimeoutMs, TimeoutType::FIRST_SEGMENT); } else { log::info("Subscribe On-demand Ranging Data"); tracker->ranging_type_ = ON_DEMAND; SubscribeCharacteristic(tracker, kRasOnDemandDataCharacteristic); SubscribeCharacteristic(tracker, kRasRangingDataReadyCharacteristic); SubscribeCharacteristic(tracker, kRasRangingDataOverWrittenCharacteristic); SetTimeOutAlarm(tracker, kRangingDataReadyTimeoutMs, TimeoutType::RANGING_DATA_READY); } auto characteristic = tracker->FindCharacteristicByUuid(kRasRealTimeRangingDataCharacteristic); uint16_t real_time_att_handle = characteristic == nullptr ? kInvalidGattHandle : characteristic->value_handle; callbacks_->OnConnected(tracker->address_for_cs_, real_time_att_handle, tracker->vendor_specific_characteristics_, tracker->conn_interval_); } void StoreCachedData(std::shared_ptr tracker) { auto address = tracker->address_; auto cached_data = cached_data_.find(address); if (cached_data == cached_data_.end()) { uint8_t next_id = cached_data_.size(); // Remove oldest cached data if (cached_data_.size() >= kCachedDataSize) { auto oldest_cached_data = std::min_element( cached_data_.begin(), cached_data_.end(), [](const auto& a, const auto& b) { return a.second.id_ < b.second.id_; }); next_id = oldest_cached_data->second.id_ + kCachedDataSize; cached_data_.erase(oldest_cached_data); } // Create new cached data log::debug("Create new cached data {}", address); cached_data_[address].id_ = next_id; cached_data_[address].remote_supported_features_ = tracker->remote_supported_features_; for (auto data : tracker->vendor_specific_characteristics_) { cached_data_[address].vendor_specific_data_[data.characteristicUuid_] = data.value_; } // Check if the id will outside the valid range for the next data entry if (cached_data_[address].id_ == 255) { for (auto& [key, value] : cached_data_) { value.id_ %= (256 - kCachedDataSize); } } } } std::string GetFeaturesString(uint32_t value) { std::stringstream ss; ss << value; if (value == 0) { ss << "|No feature supported"; } else { if ((value & kRealTimeRangingData) != 0) { ss << "|Real-time Ranging Data"; } if ((value & kRetrieveLostRangingDataSegments) != 0) { ss << "|Retrieve Lost Ranging Data Segments"; } if ((value & kAbortOperation) != 0) { ss << "|Abort Operation"; } if ((value & kFilterRangingData) != 0) { ss << "|Filter Ranging Data"; } } return ss.str(); } uint16_t FindCccHandle(const gatt::Characteristic* characteristic) { for (auto descriptor : characteristic->descriptors) { if (descriptor.uuid == kClientCharacteristicConfiguration) { return descriptor.handle; } } return GAP_INVALID_HANDLE; } std::shared_ptr FindTrackerByHandle(tCONN_ID conn_id) const { for (auto tracker : trackers_) { if (tracker->conn_id_ == conn_id) { return tracker; } } return nullptr; } std::shared_ptr FindTrackerByAddress(const RawAddress& address) const { for (auto tracker : trackers_) { if (tracker->address_ == address) { return tracker; } } return nullptr; } void SetTimeOutAlarm(std::shared_ptr tracker, uint16_t interval_ms, TimeoutType timeout_type) { log::debug("ranging_type_: {}, {}", (uint8_t)tracker->ranging_type_, (uint8_t)timeout_type); tracker->timeout_type_ = timeout_type; tracker->ranging_data_timeout_timer_ = alarm_new("Ranging Data Timeout"); alarm_set_on_mloop( tracker->ranging_data_timeout_timer_, interval_ms, [](void* data) { if (instance) { instance->OnRangingDataTimeout(reinterpret_cast(data)); } }, &tracker->address_); } void OnRangingDataTimeout(RawAddress* address) { auto tracker = FindTrackerByAddress(*address); if (tracker == nullptr) { log::warn("Skipping unknown device, address: {}", *address); return; } switch (tracker->timeout_type_) { case FIRST_SEGMENT: case FOLLOWING_SEGMENT: { auto timeout_type_text = tracker->timeout_type_ == FIRST_SEGMENT ? "first segment" : "following segment"; if (tracker->ranging_type_ == REAL_TIME) { log::error("Timeout to receive {} of Real-time ranging data", timeout_type_text); UnsubscribeCharacteristic(tracker, kRasRealTimeRangingDataCharacteristic); tracker->ranging_type_ = RANGING_TYPE_NONE; } else { log::error("Timeout to receive {} of On-Demand ranging data", timeout_type_text); AbortOperation(tracker); } } break; case RANGING_DATA_READY: { log::error("Timeout to receive ranging data ready"); } break; default: log::error("Unexpected timeout type {}", (uint16_t)tracker->timeout_type_); return; } callbacks_->OnRemoteDataTimeout(tracker->address_for_cs_); } private: uint16_t gatt_if_; std::list> trackers_; bluetooth::ras::RasClientCallbacks* callbacks_; std::unordered_map cached_data_; GattReadCallbackData gatt_read_callback_data_{true}; GattWriteCallbackData gatt_write_callback_data_{CallbackDataType::VENDOR_SPECIFIC_REPLY}; }; } // namespace bluetooth::ras::RasClient* bluetooth::ras::GetRasClient() { if (instance == nullptr) { instance = new RasClientImpl(); } return instance; }