fix: report an invalid bond when Android says the device keys are missing - #951
Merged
Merged
Conversation
originalix
force-pushed
the
fix/android-ble-key-missing-bond-invalid
branch
from
September 21, 2026 01:45
1d5a9ae to
0fc494d
Compare
|
Review the following changes in direct dependencies. Learn more about Socket for GitHub.
|
wabicai
approved these changes
Sep 21, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
React Native BLE on Android: when a device has lost its keys (wiped, or the bond was removed on the device) and Android keeps its side of the bond, report
BleBondInvalid(724) instead ofBleDeviceNotBonded(705) orBleDeviceDisconnected(720). The app already maps 724 to the "pairing is no longer valid, forget the device" dialog, so the user is no longer stuck retrying behind a "Bluetooth unpaired" toast.Also bumps every package to
1.2.3-alpha.9and depends on@onekeyfe/react-native-ble-utils0.1.8 (OneKeyHQ/react-native-ble-utils#26, published).Intent and context
Reported on a Pixel 9a (Android 17) with Pro 2 and Neo: after a device wipe the app loops on "Bluetooth unpaired" and never tells the user to forget the device. Phones whose Bluetooth stack still removes the bond on key missing re-pair on the next attempt and do not reproduce it, which is why it looked device-specific. Keeping the bond is the newer Android behaviour, so the affected population grows with Android 16+.
Root cause
BOND_BONDED, sopairDeviceskips pairing. Connect, MTU and GATT setup succeed because none of them need encryption; the first write is held behind link encryption, and the device drops the link (GATT_CONN_TERMINATE_PEER_USER, status 19) when encryption fails with key missing.ACTION_KEY_MISSING. Nothing consumed it, so the transport saw an ordinary disconnect: the V1 probe path mapped it to 705 and the V2 path to 720.isBleStaleBondErrorTextonly knows ATT 5/15 and iOS CBError 14.BONDING -> BONDED) and only then broadcasts key missing; a successful one replaces the bond (BONDING -> NONE -> BONDING -> BONDED).onDeviceBondStateread the first as success and the second as failure.Design decisions
ACTION_KEY_MISSINGfor the same device, received after the current link attempt started. Without it the original error is returned unchanged, including the existing write-disconnect -> 705 mapping.resolveAndroidBondInvalidruns at theacquire()andcall()boundaries, only for link-loss shaped errors withinANDROID_KEY_MISSING_LINK_WINDOW_MS(10 s) of link start, and waits at mostANDROID_KEY_MISSING_GRACE_MS(500 ms) because the broadcast and the GATT disconnect are delivered separately. A wedged write (710) is excluded: the system is still re-pairing then and has not reported key missing yet.FirmwareUpdateV4treats 715/719/724 as terminal.onDeviceBondStaterejects with 724 when key missing arrives during the wait. TheBONDING -> NONEdebounce applies only when the system, not this transport, started the bonding (pairDevice().initiated === false); a bonding the transport starts is handled exactly as before.supportsDeviceKeyMissing). On Android below 16, on a native build without the event, or with an olderreact-native-ble-utilsresolved by the app, nothing waits and behaviour is unchanged.Compatibility and risk
ensureConnectedalready treats it as terminal, so there is no retry loop.FirmwareUpdateV3's rethrow list does not include 719/724. Its trigger call happens minutes after link start, outside the window, so it is left unchanged.package.jsonfiles, the same set as the previous alpha bump. alpha.8 is taken byfeat/keystone-integration, hence alpha.9.Hardware coverage
Evidence captured on a Pixel 9a (Android 17) with Pro 2, using logcat,
dumpsys activity broadcastsanddumpsys bluetooth_manager:KEY_MISSINGis an implicit ordered broadcast requiring onlyBLUETOOTH_CONNECT, enqueued about 15 ms after the ACL drop.Not yet exercised end to end on a device with the new native build. Android 16 (not 17) and non-Pixel 16+ phones have no device data.
Test plan
androidKeyMissing.test.ts(12 cases): upgrade to 724 with the signal, original error without it, a signal from an earlier link or another device is ignored, no waiting outside the link window or when unsupported, fullacquire()over a stale bond including the serialized Core response, and the four bond-wait cases.sinceMsbound, the debounce, or the link-start stamp each fails the matching case.yarn agent:check --profile commitpassed for the transport change and for the dependency bump. After the version bump touched every package the full run passed 47 steps; the remaining failures reproduce on the unmodified base (hwk-trezor-schema-utilsDTS build, andhwk-trezor-connectortests needing the unbuilt privatehwk-trezor-transport-common).Update: re-pair in place instead of failing (alpha.10)
The first build showed the 724 dialog as designed, but the goal is that a lost bond is re-paired in place, as nRF Connect manages. HCI captures (btsnoop) of three attempts on a Pixel 9a / Android 17 with a wiped Pro 2 show why the app could not:
The framework's auth retry for the rejected CCCD write waits behind encryption #1 and, when it fails, encrypts again with the stale key (Android 17 keeps it: "will not remove the keys"). Pro 2 BLE firmware (
ok_ble_peer_manage.c,key_missing_repair_process) keeps the link and sends a Security Request after the first key failure of a link, and drops it on the second. Whether an attempt survives depended only on whether the CCCD write landed before encryption #1 finished.Change (
bleEncryption.ts,waitForAndroidLinkSecurity): when the device was bonded before the acquire, the transport waits forACTION_ENCRYPTION_CHANGE(react-native-ble-utils 0.1.9) before subscribing to notifications.BleBondInvalidonKEY_MISSING,BleDeviceDisconnectedif the link drops without it,BleDeviceNotBondedafterANDROID_SYSTEM_REPAIR_TIMEOUT_MS(35 s; the stack's SMP timeout is 30 s).ANDROID_ENCRYPTION_RESULT_TIMEOUT_MS(1.5 s) → proceed exactly as before.Known limit: hd-core
ensureConnectedbounds each attempt by the call'stimeout(10 s default, 30 s in onboarding). A user-confirmed re-pair can outlive it; the pairing still completes and fixes the bond, and the next attempt reuses the link.Tests:
androidEncryption.test.ts(20 cases: state machine and acquire ordering — notifications are not subscribed before the result, GATT stays idle during re-pair). Mutation-checked: removing the gate, the 0x06 hold, the ACL-drop invalidation, or the link marking each fails its case. Transport suite 241/241.Versions:
@onekeyfe/react-native-ble-utils0.1.9, all packages1.2.3-alpha.10.