-
-
Notifications
You must be signed in to change notification settings - Fork 13
ble single device connection only. #102
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -26,6 +26,7 @@ TimerHandle_t gConnTuneTimer = nullptr; | |
| TimerHandle_t gPairingTimer = nullptr; | ||
| TimerHandle_t gAdvertisingWatchdogTimer = nullptr; | ||
| bool pairingModeActive = false; | ||
| bool pairingModeTransitionActive = false; | ||
|
|
||
| // Store the active connection handle for conn param updates | ||
| uint16_t activeConnHandle = 0; | ||
|
|
@@ -36,18 +37,26 @@ void stopPairingModeTimer() { | |
| } | ||
| } | ||
|
|
||
| void clearWhiteList() { | ||
| while (NimBLEDevice::getWhiteListCount() > 0) { | ||
| NimBLEDevice::whiteListRemove(NimBLEDevice::getWhiteListAddress(0)); | ||
| } | ||
| } | ||
|
|
||
| size_t syncWhiteListFromBonds() { | ||
| clearWhiteList(); | ||
| // Reconcile the whitelist to the current bond store. The caller stops | ||
| // advertising before entering here so we can safely prune stale entries | ||
| // without the unbounded remove loop that previously hit BLE_HS_EBUSY. | ||
| for (size_t i = NimBLEDevice::getWhiteListCount(); i > 0; --i) { | ||
| const NimBLEAddress addr = NimBLEDevice::getWhiteListAddress(i - 1); | ||
| if (!NimBLEDevice::isBonded(addr)) { | ||
| NimBLEDevice::whiteListRemove(addr); | ||
| } | ||
| } | ||
|
|
||
| // Add bonded addresses that aren't already present. Skip addresses already | ||
| // on the whitelist because NimBLE's whiteListAdd() touches the controller | ||
| // whitelist immediately. | ||
| const int bondCount = NimBLEDevice::getNumBonds(); | ||
| for (int i = 0; i < bondCount; ++i) { | ||
| NimBLEDevice::whiteListAdd(NimBLEDevice::getBondedAddress(i)); | ||
| const NimBLEAddress addr = NimBLEDevice::getBondedAddress(i); | ||
| if (!NimBLEDevice::onWhiteList(addr)) { | ||
| NimBLEDevice::whiteListAdd(addr); | ||
| } | ||
| } | ||
|
|
||
| return NimBLEDevice::getWhiteListCount(); | ||
|
|
@@ -77,21 +86,27 @@ void applyPreferredLinkParams(TimerHandle_t timer) { | |
| } | ||
|
|
||
| bool shouldAdvertiseWhilePowered() { | ||
| return pairingModeActive || NimBLEDevice::getNumBonds() > 0; | ||
| return !pairingModeTransitionActive && | ||
| (pairingModeActive || NimBLEDevice::getNumBonds() > 0); | ||
| } | ||
|
|
||
| bool startAdvertising(NimBLEServer *server) { | ||
| if (server == nullptr) { | ||
| if (server == nullptr || pairingModeTransitionActive) { | ||
| return false; | ||
| } | ||
|
|
||
| const size_t bondCount = static_cast<size_t>(NimBLEDevice::getNumBonds()); | ||
| const bool allowOpenAdvertising = pairingModeActive; | ||
|
|
||
| // Stop advertising BEFORE modifying the whitelist — the BLE controller | ||
| // rejects whitelist changes while advertising with a whitelist filter | ||
| // (rc=524 / BLE_HS_EBUSY), which previously caused an infinite loop. | ||
| auto *advertising = server->getAdvertising(); | ||
| advertising->stop(); | ||
|
|
||
| const size_t whiteListCount = syncWhiteListFromBonds(); | ||
|
|
||
| if (!allowOpenAdvertising && bondCount == 0) { | ||
| auto *advertising = server->getAdvertising(); | ||
| advertising->stop(); | ||
| USBSerial.println( | ||
| "[BLE] No bonds present and pairing mode inactive; advertising stopped"); | ||
| return false; | ||
|
|
@@ -122,8 +137,6 @@ bool startAdvertising(NimBLEServer *server) { | |
| // Flutter app's `startScan()` filters for CONFIG_SERVICE_UUID. | ||
| adv.addServiceUUID(NimBLEUUID(CONFIG_SERVICE_UUID)); | ||
|
|
||
| auto *advertising = server->getAdvertising(); | ||
| advertising->stop(); | ||
| advertising->removeAll(); | ||
| const bool configured = advertising->setInstanceData(kExtAdvInstance, adv); | ||
| const bool started = configured && advertising->start(kExtAdvInstance); | ||
|
|
@@ -133,11 +146,6 @@ bool startAdvertising(NimBLEServer *server) { | |
| static_cast<unsigned>(bondCount), static_cast<unsigned>(whiteListCount)); | ||
| return started; | ||
| #else | ||
| auto *advertising = server->getAdvertising(); | ||
|
|
||
| // Stop before reconfiguring (safe even if not running) | ||
| advertising->stop(); | ||
|
|
||
| // Configure payload once — NimBLE accumulates addServiceUUID calls | ||
| static bool payloadConfigured = false; | ||
| if (!payloadConfigured) { | ||
|
|
@@ -224,7 +232,9 @@ class BleServerConnectionCallbacks : public NimBLEServerCallbacks { | |
|
|
||
| // Restart immediately, then let the watchdog keep retrying forever if this | ||
| // first post-disconnect start does not stick. | ||
| startAdvertising(server); | ||
| if (!pairingModeTransitionActive) { | ||
| startAdvertising(server); | ||
| } | ||
| } | ||
|
|
||
| void onAuthenticationComplete(NimBLEConnInfo &connInfo) override { | ||
|
|
@@ -245,6 +255,26 @@ class BleServerConnectionCallbacks : public NimBLEServerCallbacks { | |
|
|
||
| if (!connInfo.isEncrypted() || !connInfo.isBonded()) { | ||
| USBSerial.println("[BLE] Rejecting untrusted BLE session"); | ||
|
|
||
| // If this address had a bond in NVS the phone must have forgotten its | ||
| // side of the keys. Remove the stale bond so the controller stops | ||
| // advertising to a device that can never reconnect. | ||
| const NimBLEAddress identityAddr = connInfo.getIdAddress(); | ||
| const NimBLEAddress peerAddr = connInfo.getAddress(); | ||
| bool removedBond = NimBLEDevice::deleteBond(identityAddr); | ||
| if (!removedBond && identityAddr != peerAddr) { | ||
| removedBond = NimBLEDevice::deleteBond(peerAddr); | ||
| } | ||
| if (removedBond) { | ||
| NimBLEDevice::whiteListRemove(identityAddr); | ||
| if (identityAddr != peerAddr) { | ||
| NimBLEDevice::whiteListRemove(peerAddr); | ||
| } | ||
| USBSerial.printf( | ||
| "[BLE] Removed stale bond for id=%s peer=%s (phone-side forget)\n", | ||
| identityAddr.toString().c_str(), peerAddr.toString().c_str()); | ||
| } | ||
|
|
||
| if (pServer != nullptr) { | ||
| pServer->disconnect(connInfo.getConnHandle()); | ||
| } | ||
|
|
@@ -337,7 +367,44 @@ void restartBLEAdvertising() { | |
| } | ||
|
|
||
| void enterBLEPairingMode() { | ||
| pairingModeTransitionActive = true; | ||
|
|
||
| // Single-bond model: clear any existing bond so the next device that | ||
| // connects becomes the sole bonded peer. | ||
| if (deviceConnected && pServer != nullptr && | ||
| connectedHandle != BLE_HS_CONN_HANDLE_NONE) { | ||
| pServer->disconnect(connectedHandle); | ||
| vTaskDelay(pdMS_TO_TICKS(100)); | ||
| } | ||
|
Comment on lines
+374
to
+378
|
||
|
|
||
| // Stop advertising before modifying bonds — NimBLE's bond deletion can | ||
| // fail (EBUSY) if the controller is actively advertising. | ||
| if (pServer != nullptr) { | ||
| auto *advertising = pServer->getAdvertising(); | ||
| if (advertising != nullptr) { | ||
| advertising->stop(); | ||
| } | ||
| } | ||
| vTaskDelay(pdMS_TO_TICKS(50)); | ||
|
|
||
| const int bondCount = NimBLEDevice::getNumBonds(); | ||
| bool cleared = false; | ||
| if (bondCount > 0) { | ||
| cleared = NimBLEDevice::deleteAllBonds(); | ||
| if (!cleared) { | ||
| // Retry individual bond deletion as fallback | ||
| for (int i = NimBLEDevice::getNumBonds() - 1; i >= 0; --i) { | ||
| NimBLEDevice::deleteBond(NimBLEDevice::getBondedAddress(i)); | ||
| } | ||
| cleared = NimBLEDevice::getNumBonds() == 0; | ||
| } | ||
| } | ||
| USBSerial.printf("[BLE] Cleared bonds: %s (was %d, now %d)\n", | ||
| cleared ? "OK" : (bondCount == 0 ? "NONE" : "FAILED"), | ||
| bondCount, NimBLEDevice::getNumBonds()); | ||
|
|
||
| pairingModeActive = true; | ||
| pairingModeTransitionActive = false; | ||
|
|
||
| if (gPairingTimer == nullptr) { | ||
| gPairingTimer = xTimerCreate("blePair", kPairingTimeoutTicks, pdFALSE, | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
syncWhiteListFromBonds()only ever adds bonded addresses; it never removes whitelist entries for bonds that were deleted (e.g., afterenterBLEPairingMode()clears bonds). With scan-filter whitelisting enabled (setScanFilter(false, true)), stale whitelist entries can still be allowed to connect, undermining the single-bond model and potentially enabling repeated reconnect/DoS until reboot. Consider explicitly clearing/pruning the whitelist to match current bonds (e.g., clear whitelist when bonds are cleared, or remove any whitelist addresses that are no longer present in the bond list using bounded retries to avoid the prior infinite loop).