Skip to content

BLE profile switching unreliable with two bonded hosts in range — advertising is unfiltered, so the wrong host can take the connection #901

Description

@yuyudhan

Setup: Corne split on nice!nano v2 (nrf52840_ble + split + storage), rev b982049, keyboard.toml config. Two computers on the same desk: one bonded to profile 0, the other to profile 1.

What happens: With both machines powered on, switching to profile 0 often never connects — the keyboard just keeps advertising (my status display shows the advertising state indefinitely, 60s+). Switching back to profile 1 works in 1-2s. If I turn Bluetooth off on the profile-1 machine, profile 0 connects fine. So the profiles themselves are fine, they just fight each other.

What I found digging through the code: advertise() in rmk/src/ble/mod.rs always uses Advertisement::ConnectableScannableUndirected with the default (unfiltered) AdvFilterPolicy, and the adv payload is identical for every profile. A profile only decides which bond gets loaded into the stack (update_stack_bonds()). So after a profile switch, every bonded host in range races for the same advertisement and the first CONNECT_IND wins. From reading the code (I don't have a debug probe attached, so this part is inferred, not logged): when the "wrong" host wins, encryption can't succeed against the loaded LTK, and while that connection lingers the keyboard isn't advertising — so the host the profile was actually switched to has no way in until the link drops and the race starts over.

The wireless docs say "If you've connected a host to a profile, other devices will not be able to connect to this profile without manually clearing it first" — that holds at the encryption level, but the other device can still connect and occupy the slot, which blocks the host the profile was switched to.

Suggested fix (patched locally, tested today): do what ZMK does — when the active profile has a bond, advertise only to that peer:

  • peripheral.set_filter_accept_list(&[peer_identity_addr]) before advertising
  • filter_policy: FilterConnAndScan when the profile has a bond, Unfiltered when it's empty — so pairing new hosts still works, and BT_CLR returns the profile to open advertising
  • I gated it on identity.irk.is_some(): a bond without an IRK can't be resolved from rotating private addresses, so those fall back to open advertising instead of risking locking that host out

trouble already syncs the controller resolving list from bond IRKs, so RPAs from macOS/Windows resolve before the connection is accepted, and nrf-sdc implements all the needed HCI commands (LeAddDeviceToFilterAcceptList etc.). The whole change is ~20 lines in rmk/src/ble/mod.rs plus two ControllerCmdSync bounds on BleTransport.

Tested it today over repeated switches between the two computers: the wrong machine no longer takes the connection slot, and the intended one reconnects each time (with normal host-side latency, a second or two). Before the patch, switching toward the second machine frequently hung in advertising forever.

Happy to open a PR if you'd take this.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions