Skip to content

fix: report a stale bond on iPhones that only see the device disconnect (OK-63686) - #953

Merged
originalix merged 5 commits into
onekeyfrom
fix/ios-ble-peer-terminated-stale-bond
Sep 21, 2026
Merged

originalix merged 5 commits into
onekeyfrom
fix/ios-ble-peer-terminated-stale-bond

Conversation

@originalix

@originalix originalix commented Sep 21, 2026 •

Copy link
Copy Markdown
Contributor

Summary

On an iPhone 17 Pro, reconnecting to a Pro 2 that was wiped ends in "Device not found" (105) instead of the pairing-invalid guidance. This reports BleBondInvalid (724) for that case, so the app shows its existing "forget this device" dialog. Releases all packages as 1.2.4 (1.2.4-alpha.2 was the build tested on the device; same code).

OK-63686

What happens on the phone

From an app log of an iPhone 17 Pro (iOS 26.6.2), 11 attempts out of 11 after a device wipe:

connect completed     { elapsedMs: 841, succeeded: true }
GATT setup completed  { elapsedMs: 25,  succeeded: false }
device error          { errorCode: 0, message: "BleError: Device … was disconnected" }
  • Older iPhones name the lost bond with CBErrorPeerRemovedPairingInformation (14), which already maps to 719. The iPhone 17 Pro only reports CBErrorPeripheralDisconnected (7) on the operation that was in flight. A third-party scanner app gets the same code from the same device, so nothing the transport sends causes or avoids it. Pro 2 firmware drops a link on its second key failure, which is the likely source of the disconnect.
  • The drop lands in GATT setup (discoverAllServicesAndCharacteristics), about 25 ms after the connect. That raw ble-plx error is not classified, so Core retries it and then answers DeviceNotFound.
  • fix: recover reset Pro2 pairing on iOS (OK-63686) #952 records its evidence in the protocol probes, which run after GATT setup and were never reached: the same log from a build with 1.2.3-alpha.11 shows the identical sequence and no [BleBondDiagnostic] line. Its later revision (1.2.4-alpha.0) keeps that evidence across retries but still records it only in the probes.
  • Before this change no app log contained the native code of this failure; code 7 was inferred from the scanner app's message on the same phone. The device test below confirms it: 6 drops out of 6 logged { stage: 'gatt-setup', errorCode: 201, iosErrorCode: 7 }.

Change

bleIosStaleBond.ts reads the native code from the failed operation, the only place it appears on iOS (the disconnect event carries no reason). It is noted at the three places a native error can surface before the first response: GATT setup, the notification stream, and the packet writer used by both protocol probes.

A device that reboots or powers off right after connecting ends one link the same way, so:

  • the first such attempt keeps its original error and Core retries as before;
  • the second acquire attempt in a row is reported as BleBondInvalid with params: { phase: 'connect', reason: 'peer_disconnected' }. Both must be code 7, on a link that attempt opened, within 10 s of connecting and before any response, and at most 60 s apart;
  • an attempt that succeeds or fails any other way starts the count over;
  • firmware-install reconnects (skipProtocolProbe) are never counted, because FirmwareUpdateV4 treats stale-bond codes as terminal.

CBError 14 during GATT setup now maps to 719 as it does on connect and on writes.

Each of those failures now logs its native codes ([ReactNativeBleTransport] iOS operation failed { stage, errorCode, iosErrorCode, attErrorCode }), so the device test shows what the phone really reports even if the verdict does not fire. Codes only; no reason text or identifier beyond the existing 8-character suffix.

iOS only. Android and the other transports are untouched, and an iPhone that reports code 14 behaves as before.

Limits

  • This is an inference from a disconnect pattern; iOS gives no direct signal and no way to query a bond. A false positive costs the user one unnecessary re-pair.
  • iOS never re-pairs on its own, so the user still has to forget the device in system settings. There is no in-place recovery like the Android one in fix: report an invalid bond when Android says the device keys are missing #951.
  • A call with retryCount: 0 makes a single attempt, so its first failure is still "Device not found" and the next call reports 724. The onboarding connect uses retryCount: 1 and gets 724 directly.
  • If the link ends between two operations, no operation carries code 7 and that attempt is not counted. All 11 logged drops landed inside GATT setup.
  • No HCI capture from the iPhone, so the second-key-failure explanation comes from the firmware source and the Android captures in fix: report an invalid bond when Android says the device keys are missing #951.

Test plan

  • iosPeerTermination.test.ts: 11 tests driving acquire() with the logged error shape — first attempt keeps its error, second reports 724 with params that survive serialization; drops seen by the write path and by the notification stream; expiry; reset after another failure and after a success; firmware-install reconnect; code 14 in GATT setup. Each hook was removed in turn and the matching test failed.
  • Transport suite: 14 suites, 252 tests pass. eslint and tsc --noEmit clean for the package.
  • Device: iPhone 17 Pro, iOS 26.6.2, Pro 2, app build 6130048a2e with SDK 1.2.4-alpha.2 (fix: show the pairing-invalid dialog on iPhones that only see the device disconnect (OK-63686) app-monorepo#13678). Three wipe-and-reconnect cycles: each onboarding connect made two attempts, both ended in GATT setup with native code 7, and the call returned 724 with params: { phase: 'connect', reason: 'peer_disconnected' } in about 2.9 s; no 105 in that build's log. After forgetting the device in iOS settings, the reconnect paired and the following calls succeeded.
  • Not yet run: a regression pass on an older iPhone, and a Bluetooth firmware update on iOS.

@originalix
originalix merged commit ce20da4 into onekey Sep 21, 2026
10 checks passed
@originalix
originalix deleted the fix/ios-ble-peer-terminated-stale-bond branch September 21, 2026 10:02
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants