From 7ad51b4b8f0899be0bb2fe788367b7c7b2353d84 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Wed, 12 Aug 2026 18:58:56 -0500 Subject: [PATCH] Pin the carve alignment, name the node-abort contract, fix strays The static_assert enforces what the carve comment claims; the node header now states that aborting inside on_connected() can leave on_disconnect unpaired; the passive-proxy test comment names the current gate; the pending-regs comment lands on the right member (lost to an aborted batch). --- esphome/components/ble_client/ble_client.h | 5 +++-- esphome/components/ble_client/ble_client_node.h | 2 ++ .../bluetooth_connection/gatt_service_table_bluedroid.cpp | 2 ++ .../bluetooth_proxy/test-passive.esp32-c6-idf.yaml | 2 +- 4 files changed, 8 insertions(+), 3 deletions(-) diff --git a/esphome/components/ble_client/ble_client.h b/esphome/components/ble_client/ble_client.h index d5019622da..bb4a24a19b 100644 --- a/esphome/components/ble_client/ble_client.h +++ b/esphome/components/ble_client/ble_client.h @@ -81,10 +81,11 @@ class BLEClient final : public BLEClientBase { std::vector nodes_; #ifdef USE_BLE_CLIENT_GATT_NODES - // Nodes on the neutral surface; fed the translated callbacks and - // auto-established after the on_connected fan-out. + // Raise if a migrated node needs more concurrent registrations. static constexpr uint8_t MAX_PENDING_NOTIFY_REGS = 4; + // Nodes on the neutral surface; fed the translated callbacks and + // auto-established after the on_connected fan-out. StaticVector gatt_nodes_; // Reconnect backoff after materializer failures. ConnectBackoff gatt_backoff_; diff --git a/esphome/components/ble_client/ble_client_node.h b/esphome/components/ble_client/ble_client_node.h index ea9984a588..394a908348 100644 --- a/esphome/components/ble_client/ble_client_node.h +++ b/esphome/components/ble_client/ble_client_node.h @@ -28,6 +28,8 @@ class BLEClientNode { // Neutral surface, delivered by both engines. The table is borrowed: copy // handles during on_connected(). All nodes see all completions; filter by // handle. + // A node that disconnects from inside on_connected() aborts the fan-out; + // the user's on_disconnect may then fire without a preceding on_connect. virtual void on_connected(const ble_device_base::GattServiceTable &table) {} virtual void on_disconnected() {} virtual void on_notify(uint16_t handle, const uint8_t *data, uint16_t len) {} diff --git a/esphome/components/bluetooth_connection/gatt_service_table_bluedroid.cpp b/esphome/components/bluetooth_connection/gatt_service_table_bluedroid.cpp index 66a6cf9824..4acc08fbaf 100644 --- a/esphome/components/bluetooth_connection/gatt_service_table_bluedroid.cpp +++ b/esphome/components/bluetooth_connection/gatt_service_table_bluedroid.cpp @@ -117,6 +117,8 @@ bool BluedroidServiceTable::build(esp_gatt_if_t gattc_if, uint16_t conn_id, uint // The arrays share one block; carving stays aligned because each struct's // strictest member is the UUID and array sizes are multiples of it. + static_assert(alignof(ble_device_base::GattService) == alignof(ble_device_base::GattCharacteristic) && + alignof(ble_device_base::GattCharacteristic) >= alignof(ble_device_base::GattDescriptor)); 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); diff --git a/tests/components/bluetooth_proxy/test-passive.esp32-c6-idf.yaml b/tests/components/bluetooth_proxy/test-passive.esp32-c6-idf.yaml index 81c6460be8..fd11e4e06e 100644 --- a/tests/components/bluetooth_proxy/test-passive.esp32-c6-idf.yaml +++ b/tests/components/bluetooth_proxy/test-passive.esp32-c6-idf.yaml @@ -1,5 +1,5 @@ # Advertisement-only proxy on esp32 by explicit choice: no GATT backend is -# compiled (USE_BLE_GATT_CLIENT unset), which pins the SERVES_PROXY gating and the +# compiled (USE_BLE_GATT_CLIENT unset), which pins the USE_BLUETOOTH_PROXY_CONNECTIONS gating and the # address-scoped maintenance path that a connections build never exercises. # Under batch grouping the active default build is what runs; the standalone # compile of this fixture is what exercises the passive gating.