diff --git a/zephcore/adapters/ble/ZephyrBLE.cpp b/zephcore/adapters/ble/ZephyrBLE.cpp index 7ed103e..5367651 100644 --- a/zephcore/adapters/ble/ZephyrBLE.cpp +++ b/zephcore/adapters/ble/ZephyrBLE.cpp @@ -6,8 +6,12 @@ * Pairing is triggered reactively by ATT_ERR_AUTHENTICATION on secured GATT * attributes (Apple §55 compliant — no proactive Security Request on connect). * - * Advertising: ESP32 enables CONFIG_BT_PRIVACY in esp32_common.conf; nRF uses public identity. - * Android Flutter may fail connect-from-app to RPA unless bonded; Arduino MeshCore uses public. + * Advertising: always uses the identity address (BT_LE_ADV_OPT_USE_IDENTITY in + * start_adv). On ESP32, CONFIG_BT_PRIVACY=y is load-bearing (the Espressif + * controller's privacy-OFF Secure-Connections path MIC-fails against iOS — see + * findings.md Issue #34), but USE_IDENTITY keeps us off the RPA so the Android + * companion's "connect from app" still works. nRF/MG24 keep privacy OFF and + * already advertise identity, so USE_IDENTITY is a no-op there. */ #include @@ -1224,9 +1228,15 @@ static void start_adv(void) uint16_t interval = fast_adv_active ? BT_ADV_FAST_INTERVAL : BT_ADV_INTERVAL; + /* USE_IDENTITY: advertise the stable identity address instead of an RPA, + * even when CONFIG_BT_PRIVACY=y. Keeps the controller in its (working) + * privacy-enabled SC encryption path for iOS while exposing a fixed address + * so the Android companion app's "connect from app" flow works. See + * findings.md Issue #34 iOS-privacy capture (controller MIC failure 0x3d on + * the privacy-off SC path). */ struct bt_le_adv_param adv_param = { .id = BT_ID_DEFAULT, - .options = BT_LE_ADV_OPT_CONN, + .options = BT_LE_ADV_OPT_CONN | BT_LE_ADV_OPT_USE_IDENTITY, .interval_min = interval, .interval_max = interval, }; diff --git a/zephcore/boards/common/esp32_common.conf b/zephcore/boards/common/esp32_common.conf index 0e3aa06..051bb78 100644 --- a/zephcore/boards/common/esp32_common.conf +++ b/zephcore/boards/common/esp32_common.conf @@ -8,16 +8,23 @@ # DLE managed internally by Espressif blob — no CONFIG_BT_CTLR_DATA_LENGTH_MAX # TX power managed internally by Espressif blob — no CONFIG_BT_CTLR_TX_PWR_* -# Host privacy: ESP32-only override. Required for reliable iOS SMP on the -# Espressif controller (Zephyr #84182-class). nRF/MG24 keep privacy off -# because it breaks Android companion "connect from app" (manual pairing -# from system BT settings works, but the app flow silently fails). +# Host privacy: ESP32-only override. Load-bearing for iOS on the Espressif +# controller (Zephyr #84182-class). nRF/MG24 keep privacy OFF. # -# Re-confirmed 2026-06-15 on shipping blob 19f979cfe6: privacy OFF still breaks -# iOS. iOS *system* pairing completes (PIN prompt, shows as paired) but the -# companion app's GATT connection drops immediately — the #84182 initial-pairing -# breakage the blob did NOT fix. Privacy stays ON; the Android "connect from -# app" tradeoff remains. +# Root cause pinned 2026-06-15 by firmware BLE/SMP debug capture on shipping +# blob 19f979cfe6 (xiao_esp32s3, real iOS): with privacy OFF the SC pairing +# completes the full passkey + DHKey-check phase, the host hands the controller +# a valid LTK (HCI LTK Request Reply, status 0x00), then the controller fails +# to start link encryption — HCI Disconnect reason 0x3d (MIC failure), Encrypt +# Change status 0x1f. It is a bug in the Espressif controller's privacy-OFF +# Secure-Connections path (closed blob); the host did everything right (nRF runs +# the identical host SMP code and bonds with iOS privacy-OFF fine). Privacy ON +# keeps the controller on its working privacy-enabled SC path. +# +# Android "connect from app" is NOT sacrificed: ZephyrBLE advertises with +# BT_LE_ADV_OPT_USE_IDENTITY, so even with privacy ON we expose the stable +# identity address (no RPA) — the thing the Android companion needs. HW- +# confirmed: iOS pairs + syncs, normal disconnect 0x13. CONFIG_BT_PRIVACY=y # BLE thread stacks — ESP32 software BLE controller needs larger stacks.