diff --git a/esphome/components/ble_device_base/ble_gatt_client.cpp b/esphome/components/ble_device_base/ble_gatt_client.cpp deleted file mode 100644 index 66c9f7d0f2..0000000000 --- a/esphome/components/ble_device_base/ble_gatt_client.cpp +++ /dev/null @@ -1,43 +0,0 @@ -#include "ble_gatt_client.h" - -#ifdef USE_BLE_GATT_CLIENT - -#include "esphome/core/log.h" - -namespace esphome::ble_device_base { - -static const char *const TAG = "ble_gatt_client"; - -const GattCharacteristic *find_characteristic(const GattServiceTable &table, const GattService &service, - const ESPBTUUID &uuid) { - // 32-bit range math: a corrupt first/count pair cannot wrap past the check. - uint32_t end = uint32_t(service.first_characteristic) + service.characteristic_count; - if (end > table.characteristic_count) { - ESP_LOGW(TAG, "characteristic range out of bounds"); - return nullptr; - } - for (uint32_t i = service.first_characteristic; i < end; i++) { - if (table.characteristics[i].uuid == uuid) - return &table.characteristics[i]; - } - return nullptr; -} - -uint16_t find_cccd(const GattServiceTable &table, const GattCharacteristic &characteristic) { - uint32_t end = uint32_t(characteristic.first_descriptor) + characteristic.descriptor_count; - if (end > table.descriptor_count) { - // Corrupt range, not a missing CCCD. - ESP_LOGW(TAG, "descriptor range out of bounds"); - return 0; - } - const ESPBTUUID cccd_uuid = ESPBTUUID::from_uint16(CCCD_UUID); - for (uint32_t i = characteristic.first_descriptor; i < end; i++) { - if (table.descriptors[i].uuid == cccd_uuid) - return table.descriptors[i].handle; - } - return 0; -} - -} // namespace esphome::ble_device_base - -#endif // USE_BLE_GATT_CLIENT diff --git a/esphome/components/ble_device_base/ble_gatt_client.h b/esphome/components/ble_device_base/ble_gatt_client.h index 89c220cbe3..879340890e 100644 --- a/esphome/components/ble_device_base/ble_gatt_client.h +++ b/esphome/components/ble_device_base/ble_gatt_client.h @@ -145,32 +145,6 @@ concept BLEGattConnectionContract = requires(T conn, GattClientListener *listene { conn.set_connection_type(ConnectionType{}) } -> std::same_as; }; -// ---- service table lookup helpers ---- -// -// Neutral, bounds-checked walks over a materialized GattServiceTable for -// direct consumers that resolve a known device's handles by UUID (streaming -// consumers forward the raw database and never need these). Linear search: -// the table exists only between discovery and release_services(), for one -// small known device. - -/// Client Characteristic Configuration descriptor UUID (Bluetooth spec). -static constexpr uint16_t CCCD_UUID = 0x2902; - -inline const GattService *find_service(const GattServiceTable &table, const ESPBTUUID &uuid) { - for (uint16_t i = 0; i < table.service_count; i++) { - if (table.services[i].uuid == uuid) - return &table.services[i]; - } - return nullptr; -} - -const GattCharacteristic *find_characteristic(const GattServiceTable &table, const GattService &service, - const ESPBTUUID &uuid); - -/// Handle of the characteristic's Client Characteristic Configuration -/// descriptor (0x2902), or 0 when it has none. -uint16_t find_cccd(const GattServiceTable &table, const GattCharacteristic &characteristic); - } // namespace esphome::ble_device_base #endif // USE_BLE_GATT_CLIENT diff --git a/esphome/components/bluetooth_connection/bluetooth_connection_bluedroid.cpp b/esphome/components/bluetooth_connection/bluetooth_connection_bluedroid.cpp index 21cffa5063..311e17415c 100644 --- a/esphome/components/bluetooth_connection/bluetooth_connection_bluedroid.cpp +++ b/esphome/components/bluetooth_connection/bluetooth_connection_bluedroid.cpp @@ -32,10 +32,6 @@ using esp32_ble_tracker::ClientState; using esp32_ble_tracker::ConnectionType; static constexpr uint16_t UNSET_CONN_ID = 0xFFFF; -// Bounds one characteristic's descriptor walk against a stack that never -// reports end-of-range. -static constexpr uint16_t MAX_DESCRIPTORS_PER_CHARACTERISTIC = 64; - // ---- tracker surface ---- void BluedroidGattClient::connect() { this->tracker_connect_(); } @@ -272,9 +268,6 @@ int BluedroidGattClient::update_connection_params(uint16_t min_interval, uint16_ void BluedroidGattClient::release_services() { this->service_total_ = 0; -#ifdef USE_BLE_GATT_SERVICE_TABLE - this->free_service_table_(); -#endif // Always set: terminates any in-flight stream on every cache config. this->services_released_ = true; #ifndef CONFIG_BT_GATTC_CACHE_NVS_FLASH @@ -282,183 +275,6 @@ void BluedroidGattClient::release_services() { #endif } -#ifdef USE_BLE_GATT_SERVICE_TABLE -ble_device_base::GattServiceTable BluedroidGattClient::get_service_table() { - if (this->table_storage_ == nullptr && - (this->services_released_ || this->service_total_ == 0 || !this->build_service_table_())) { - return {}; - } - return this->table_view_(); -} - -// The view is carved from the storage block and the counts on each call -// (a cold path) rather than cached, saving a per-instance table member. -ble_device_base::GattServiceTable BluedroidGattClient::table_view_() const { - size_t svc_bytes = this->service_total_ * sizeof(ble_device_base::GattService); - size_t char_bytes = this->table_char_total_ * sizeof(ble_device_base::GattCharacteristic); - return {reinterpret_cast(this->table_storage_), - reinterpret_cast(this->table_storage_ + svc_bytes), - reinterpret_cast(this->table_storage_ + svc_bytes + char_bytes), - this->service_total_, - this->table_char_total_, - this->table_desc_total_}; -} - -void BluedroidGattClient::free_service_table_() { - if (this->table_storage_ == nullptr) { - return; - } - RAMAllocator allocator(RAMAllocator::ALLOC_INTERNAL); - allocator.deallocate(this->table_storage_, 0); - this->table_storage_ = nullptr; - this->table_char_total_ = 0; - this->table_desc_total_ = 0; -} - -template -bool BluedroidGattClient::walk_database_(ServiceFn &&on_service, CharFn &&on_char, DescFn &&on_desc) { - // Shared enumeration for both table-build passes: an identical walk order - // is what lets the counting pass size the block the filling pass fills. - // INVALID_OFFSET/NOT_FOUND mean end-of-range; anything else is a failure. - for (uint16_t s = 0; s < this->service_total_; s++) { - esp_gattc_service_elem_t svc; - uint16_t svc_count = 1; - if (esp_ble_gattc_get_service(this->gattc_if_, this->conn_id_, nullptr, &svc, &svc_count, s) != ESP_GATT_OK || - svc_count == 0) { - return false; - } - if (!on_service(s, svc)) { - return false; - } - uint16_t svc_chars = 0; - if (esp_ble_gattc_get_attr_count(this->gattc_if_, this->conn_id_, ESP_GATT_DB_CHARACTERISTIC, svc.start_handle, - svc.end_handle, 0, &svc_chars) != ESP_GATT_OK) { - return false; - } - for (uint16_t c = 0; c < svc_chars; c++) { - esp_gattc_char_elem_t chr; - uint16_t char_count = 1; - auto status = esp_ble_gattc_get_all_char(this->gattc_if_, this->conn_id_, svc.start_handle, svc.end_handle, &chr, - &char_count, c); - if (status != ESP_GATT_OK || char_count == 0) { - // An early terminator contradicts svc_chars from the same cache; - // never build a silently truncated table. - return false; - } - if (!on_char(svc, chr)) { - return false; - } - for (uint16_t d = 0; d != MAX_DESCRIPTORS_PER_CHARACTERISTIC; d++) { - esp_gattc_descr_elem_t desc; - uint16_t desc_count = 1; - auto desc_status = - esp_ble_gattc_get_all_descr(this->gattc_if_, this->conn_id_, chr.char_handle, &desc, &desc_count, d); - if (desc_status == ESP_GATT_INVALID_OFFSET || desc_status == ESP_GATT_NOT_FOUND) { - break; - } - if (desc_status != ESP_GATT_OK || desc_count == 0) { - return false; - } - if (!on_desc(chr, desc)) { - return false; - } - } - } - } - return true; -} - -bool BluedroidGattClient::build_service_table_() { - // Pass 1: count, so one exact-size block holds the whole table. - uint16_t char_total = 0; - uint16_t desc_total = 0; - bool counted = this->walk_database_([](uint16_t, const esp_gattc_service_elem_t &) { return true; }, - [&](const esp_gattc_service_elem_t &, const esp_gattc_char_elem_t &) { - char_total++; - return true; - }, - [&](const esp_gattc_char_elem_t &, const esp_gattc_descr_elem_t &) { - desc_total++; - return true; - }); - if (!counted) { - ESP_LOGW(TAG, "[%d] Service table walk failed during count", this->connection_index_); - return false; - } - - // The arrays share one block; carving stays aligned because each struct's - // strictest member is the UUID and array sizes are multiples of it. - size_t svc_bytes = this->service_total_ * sizeof(ble_device_base::GattService); - size_t char_bytes = char_total * sizeof(ble_device_base::GattCharacteristic); - size_t total_bytes = svc_bytes + char_bytes + desc_total * sizeof(ble_device_base::GattDescriptor); - RAMAllocator allocator(RAMAllocator::ALLOC_INTERNAL); - this->table_storage_ = allocator.allocate(total_bytes); - if (this->table_storage_ == nullptr) { - ESP_LOGW(TAG, "[%d] Service table allocation failed (%u bytes)", this->connection_index_, - static_cast(total_bytes)); - return false; - } - auto *services = reinterpret_cast(this->table_storage_); - auto *characteristics = reinterpret_cast(this->table_storage_ + svc_bytes); - auto *descriptors = - reinterpret_cast(this->table_storage_ + svc_bytes + char_bytes); - - // Pass 2: fill, bounded by the pass-1 totals. A bound trip or a shortfall - // means the cached database changed between the passes; fail the build - // rather than serve an inconsistent table (the consumer retries). - uint16_t char_index = 0; - uint16_t desc_index = 0; - ble_device_base::GattService *cur_service = nullptr; - ble_device_base::GattCharacteristic *cur_char = nullptr; - bool filled = this->walk_database_( - [&](uint16_t s, const esp_gattc_service_elem_t &svc) { - cur_service = &services[s]; - cur_service->uuid = ble_device_base::ESPBTUUID::from_uuid(svc.uuid); - cur_service->start_handle = svc.start_handle; - cur_service->end_handle = svc.end_handle; - cur_service->first_characteristic = char_index; - cur_service->characteristic_count = 0; - return true; - }, - [&](const esp_gattc_service_elem_t &svc, const esp_gattc_char_elem_t &chr) { - if (char_index >= char_total) { - return false; - } - cur_char = &characteristics[char_index++]; - cur_char->uuid = ble_device_base::ESPBTUUID::from_uuid(chr.uuid); - cur_char->value_handle = chr.char_handle; - // Bluedroid addresses descriptors by characteristic handle, so the - // table's end_handle only needs the service-bounded upper bound. - cur_char->end_handle = svc.end_handle; - cur_char->properties = chr.properties; - cur_char->first_descriptor = desc_index; - cur_char->descriptor_count = 0; - cur_service->characteristic_count++; - return true; - }, - [&](const esp_gattc_char_elem_t &, const esp_gattc_descr_elem_t &desc) { - if (desc_index >= desc_total) { - return false; - } - descriptors[desc_index].uuid = ble_device_base::ESPBTUUID::from_uuid(desc.uuid); - descriptors[desc_index].handle = desc.handle; - desc_index++; - cur_char->descriptor_count++; - return true; - }); - if (!filled || char_index != char_total || desc_index != desc_total) { - // Walk error or the database changed between passes; better an empty - // table than a corrupt one. - ESP_LOGW(TAG, "[%d] Service table walk mismatch, discarding", this->connection_index_); - this->free_service_table_(); - return false; - } - this->table_char_total_ = char_total; - this->table_desc_total_ = desc_total; - return true; -} -#endif // USE_BLE_GATT_SERVICE_TABLE - // ---- internals ---- bool BluedroidGattClient::check_addr_(const esp_bd_addr_t &addr) const { @@ -505,11 +321,6 @@ void BluedroidGattClient::log_gattc_warning_(const char *operation, int code) { // ---- service streaming ---- int BluedroidGattClient::handle_search_cmpl_(esp_gatt_status_t status) { -#ifdef USE_BLE_GATT_SERVICE_TABLE - // Re-discovery moves the counts table_view_() derives offsets from; free - // the stale table. - this->free_service_table_(); -#endif // Step down from the fast discovery params. this->update_conn_params_(MEDIUM_MIN_CONN_INTERVAL, MEDIUM_MAX_CONN_INTERVAL, 0, MEDIUM_CONN_TIMEOUT, "medium"); if (status != ESP_GATT_OK) { diff --git a/esphome/components/bluetooth_connection/bluetooth_connection_bluedroid.h b/esphome/components/bluetooth_connection/bluetooth_connection_bluedroid.h index b59fa98cbc..2dd3cb239a 100644 --- a/esphome/components/bluetooth_connection/bluetooth_connection_bluedroid.h +++ b/esphome/components/bluetooth_connection/bluetooth_connection_bluedroid.h @@ -69,14 +69,9 @@ class BluedroidGattClient final : public esp32_ble_tracker::ESPBTClient, public int notify_characteristic(uint16_t handle, bool enable); int pair(); int update_connection_params(uint16_t min_interval, uint16_t max_interval, uint16_t latency, uint16_t timeout); - // On-demand table for direct consumers; the proxy streams instead, so the - // materializer compiles only under USE_BLE_GATT_SERVICE_TABLE (emitted by - // direct-consumer codegen, never by the proxy). -#ifdef USE_BLE_GATT_SERVICE_TABLE - ble_device_base::GattServiceTable get_service_table(); -#else + // Contract stub: the proxy streams in place; the on-demand materializer + // for direct consumers lands with #18205. ble_device_base::GattServiceTable get_service_table() { return {}; } -#endif void release_services(); #ifdef USE_BLUETOOTH_PROXY @@ -102,21 +97,9 @@ class BluedroidGattClient final : public esp32_ble_tracker::ESPBTClient, public const char *param_type); int check_and_log_error_(const char *operation, esp_err_t err); void log_gattc_warning_(const char *operation, int code); -#ifdef USE_BLE_GATT_SERVICE_TABLE - template - bool walk_database_(ServiceFn &&on_service, CharFn &&on_char, DescFn &&on_desc); - bool build_service_table_(); - void free_service_table_(); - ble_device_base::GattServiceTable table_view_() const; -#endif // Group 1: pointers / composed objects ble_device_base::GattClientListener *listener_{nullptr}; -#ifdef USE_BLE_GATT_SERVICE_TABLE - // One exact-size block carved into the three arrays; the view is rebuilt - // per (cold) call instead of cached. - uint8_t *table_storage_{nullptr}; -#endif // Group 2: 4-byte types uint32_t disconnecting_started_{0}; @@ -126,11 +109,6 @@ class BluedroidGattClient final : public esp32_ble_tracker::ESPBTClient, public // Group 4: 2-byte types uint16_t conn_id_{0xFFFF}; uint16_t service_total_{0}; -#ifdef USE_BLE_GATT_SERVICE_TABLE - // Filled element counts of the materialized table (0 when none). - uint16_t table_char_total_{0}; - uint16_t table_desc_total_{0}; -#endif // Group 5: 1-byte types esp_gatt_if_t gattc_if_{ESP_GATT_IF_NONE}; // uint8_t width keeps the object at 48 bytes diff --git a/esphome/core/defines.h b/esphome/core/defines.h index e87c8044f4..e3c4dd87ce 100644 --- a/esphome/core/defines.h +++ b/esphome/core/defines.h @@ -307,7 +307,6 @@ #define USE_ESP32_BLE_SERVER_ON_DISCONNECT #define USE_ESP32_BLE_TRACKER #define USE_BLE_GATT_CLIENT -#define USE_BLE_GATT_SERVICE_TABLE #define ESPHOME_BLE_GATT_CLIENT_COUNT 1 #define ESPHOME_ESP32_BLE_TRACKER_LISTENER_COUNT 1 #define ESPHOME_ESP32_BLE_TRACKER_CLIENT_COUNT 1 diff --git a/tests/components/ble_device_base/test_gatt_client_contract.cpp b/tests/components/ble_device_base/test_gatt_client_contract.cpp index 17c166ebb6..abc0a2290b 100644 --- a/tests/components/ble_device_base/test_gatt_client_contract.cpp +++ b/tests/components/ble_device_base/test_gatt_client_contract.cpp @@ -78,54 +78,4 @@ TEST(BleGattClientContract, MinimalImplementerCompilesAndRoutesEvents) { EXPECT_EQ(table.descriptor_count, 0); } -// A radon_eye_rd200-shaped table: two services, the second holding a -// notifying characteristic with a CCCD and a bare write characteristic. -class ServiceTableLookup : public ::testing::Test { - protected: - void SetUp() override { - this->services_[0] = {ESPBTUUID::from_uint16(0x1800), 0x0001, 0x0005, 0, 1}; - this->services_[1] = {ESPBTUUID::from_uint16(0x1523), 0x0010, 0x0020, 1, 2}; - this->characteristics_[0] = {ESPBTUUID::from_uint16(0x2A00), 0x0003, 0x0003, 0x02, 0, 0}; - this->characteristics_[1] = {ESPBTUUID::from_uint16(0x1525), 0x0012, 0x0014, 0x10, 0, 1}; - this->characteristics_[2] = {ESPBTUUID::from_uint16(0x1524), 0x0016, 0x0016, 0x04, 1, 0}; - this->descriptors_[0] = {ESPBTUUID::from_uint16(0x2902), 0x0013}; - this->table_ = {this->services_, this->characteristics_, this->descriptors_, 2, 3, 1}; - } - - GattService services_[2]; - GattCharacteristic characteristics_[3]; - GattDescriptor descriptors_[1]; - GattServiceTable table_; -}; - -TEST_F(ServiceTableLookup, FindsServicesAndCharacteristicsByUuid) { - const GattService *service = find_service(this->table_, ESPBTUUID::from_uint16(0x1523)); - ASSERT_NE(service, nullptr); - EXPECT_EQ(service->start_handle, 0x0010); - EXPECT_EQ(find_service(this->table_, ESPBTUUID::from_uint16(0xFFFF)), nullptr); - - const GattCharacteristic *characteristic = - find_characteristic(this->table_, *service, ESPBTUUID::from_uint16(0x1525)); - ASSERT_NE(characteristic, nullptr); - EXPECT_EQ(characteristic->value_handle, 0x0012); - // The lookup is scoped to the service: 0x2A00 lives in the other service. - EXPECT_EQ(find_characteristic(this->table_, *service, ESPBTUUID::from_uint16(0x2A00)), nullptr); -} - -TEST_F(ServiceTableLookup, FindsTheCccdAndReportsItsAbsence) { - const GattService *service = find_service(this->table_, ESPBTUUID::from_uint16(0x1523)); - const GattCharacteristic *notify_char = find_characteristic(this->table_, *service, ESPBTUUID::from_uint16(0x1525)); - EXPECT_EQ(find_cccd(this->table_, *notify_char), 0x0013); - const GattCharacteristic *write_char = find_characteristic(this->table_, *service, ESPBTUUID::from_uint16(0x1524)); - EXPECT_EQ(find_cccd(this->table_, *write_char), 0); -} - -TEST_F(ServiceTableLookup, RejectsRangesThatOverrunTheTable) { - // A corrupt index range must fail the lookup, not walk out of bounds. - GattService bad_service = {ESPBTUUID::from_uint16(0x1523), 0x0010, 0x0020, 2, 5}; - EXPECT_EQ(find_characteristic(this->table_, bad_service, ESPBTUUID::from_uint16(0x1524)), nullptr); - GattCharacteristic bad_char = {ESPBTUUID::from_uint16(0x1525), 0x0012, 0x0014, 0x10, 0, 9}; - EXPECT_EQ(find_cccd(this->table_, bad_char), 0); -} - } // namespace esphome::ble_device_base::testing