From d49c608920a9dc0d842817515904b761b10d6412 Mon Sep 17 00:00:00 2001 From: CValdesS Date: Wed, 12 Aug 2026 19:17:31 +0200 Subject: [PATCH] fix(nrf54l15): request BLE security on connect Every characteristic and CCC in mesh_svc carries BT_GATT_PERM_*_AUTHEN, but connected_cb deliberately skipped bt_conn_set_security(), leaving the escalation to the central. That only works with centrals that react to an "Insufficient Authentication" ATT error by pairing: iOS CoreBluetooth and BlueZ do, Chrome's Web Bluetooth on Windows does not. Captured over RTT while connecting from client.meshtastic.org on Windows: the link came up unencrypted, MTU negotiated to 247, service discovery completed, and then the CCC writes for fromNum (handle 0x000c) and logRadio (0x0010) were both answered with an ATT Error Response. With no subscription to fromNum the client never sends want_config, so it hangs on "loading" - and 60 s later the BLE zombie watchdog reboots the board. No SMP traffic appeared at all, which is also why Windows never showed a pairing prompt. Send a Security Request from connected_cb instead, asking for level 4 (LE Secure Connections + the fixed passkey) to match what the AUTHEN permissions require. A peer holding a valid bond just encrypts; one with a stale bond gets unpaired by the existing security_changed_cb handler and pairs cleanly on the next attempt. The comment removed here justified the old behaviour with a pairing dialog that broke advertising restart. That failure mode predates the adv_restart_work queue and did not reproduce. Verified on an nRF54L15-DK: "smp_send_security_req" -> pairing -> "BLE security level 4 established" -> "Client wants config" -> full config stream and packets in both directions, from both the Windows web client and the iOS app. A later reconnect reached level 4 with no re-pairing, and the session logged zero BLE zombie reboots. --- src/platform/nrf54l15/NRF54L15Bluetooth.cpp | 21 ++++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/src/platform/nrf54l15/NRF54L15Bluetooth.cpp b/src/platform/nrf54l15/NRF54L15Bluetooth.cpp index 9ce03263161..c48ab3d0d5d 100644 --- a/src/platform/nrf54l15/NRF54L15Bluetooth.cpp +++ b/src/platform/nrf54l15/NRF54L15Bluetooth.cpp @@ -397,11 +397,22 @@ static void connected_cb(struct bt_conn *conn, uint8_t err) meshtastic::BluetoothStatus newStatus(meshtastic::BluetoothStatus::ConnectionState::CONNECTED); bluetoothStatus->updateStatus(&newStatus); - // nRF54L15-DK has no screen - cannot display a PIN to the user. - // Requesting BT_SECURITY_L2 causes the OS to show a pairing dialog that - // the user dismisses, triggering disconnect + advertising restart failure. - // Skip security negotiation; the Meshtastic app works over plain GATT. - // (Security can be re-enabled once a display or NFC OOB path is available.) +#if defined(CONFIG_BT_SMP) + // Every characteristic and CCC in mesh_svc carries BT_GATT_PERM_*_AUTHEN, so + // nothing works until the link is encrypted AND authenticated (level 4 here: + // LE Secure Connections + the fixed passkey). We used to leave the escalation + // to the central, relying on it reacting to the "Insufficient Authentication" + // ATT error - iOS CoreBluetooth and BlueZ do, but Chrome's Web Bluetooth on + // Windows does not: it forwards the error to JS, the client never subscribes to + // fromNum, never sends want_config, and hangs on "loading" until the BLE zombie + // watchdog reboots us. Send a Security Request instead so the peer either + // encrypts with its existing LTK or starts pairing. If it holds a stale bond, + // security_changed_cb below unpairs it so the next attempt is clean. + int sec_err = bt_conn_set_security(conn, BT_SECURITY_L4); + if (sec_err) { + LOG_WARN("BLE security request failed: %d", sec_err); + } +#endif } static void disconnected_cb(struct bt_conn *conn, uint8_t reason)