Skip to content

BLE central RPA doesn't work when connecting to peripheral #104

@vandy

Description

@vandy

Checklist

  • Checked the issue tracker for similar issues to ensure this is not a duplicate
  • Read the documentation to confirm the issue is not addressed there and your configuration is set correctly
  • Tested with the latest version to ensure the issue hasn't been fixed

How often does this bug occurs?

always

Expected behavior

When BLE_OWN_ADDR_RPA_PUBLIC_DEFAULT is used in ble_gap_disc(own_addr_type, ...) and ble_gap_connect(own_addr_type, ...) central device should send CONN_IND with random (generated) address instead of public address.

Actual behavior (suspected bug)

Central sends CONN_IND with public address.

Error logs or terminal output

Steps to reproduce the behavior

  1. Run BLE peripheral which advertises (connectable undirected). I use RPA but it doesn't matter.
  2. Run BLE central which scans for advertisements and connects to BLE peripheral using RPA_PUBLIC_ID
  3. Connection occurs but BLE peripheral sees central's public address instead of RPA address.

Project release version

v5.4

System architecture

Intel/AMD 64-bit (modern PC, older Mac)

Operating system

Windows

Operating system version

Windows 10

Shell

other (details in Additional context)

Additional context

I'm using 2 esp32h2 devices: peripheral (gatt server, dev.A) and central (gatt client, dev.B), and Android nRF Connect app.

Peripheral is a custom application, but generally it's based on bleprph example. It's configured as:

  ble_hs_cfg.sm_sc = 1;
  ble_hs_cfg.sm_bonding = 1;
  ble_hs_cfg.sm_our_key_dist |= BLE_SM_PAIR_KEY_DIST_ENC; // actually not needed, because using SC only
  ble_hs_cfg.sm_their_key_dist |= BLE_SM_PAIR_KEY_DIST_ENC; // actually not needed, because using SC only
  ble_hs_cfg.sm_our_key_dist |= BLE_SM_PAIR_KEY_DIST_ID;
  ble_hs_cfg.sm_their_key_dist |= BLE_SM_PAIR_KEY_DIST_ID;

  ble_store_config_init();

dev.A advertises using BLE_OWN_ADDR_PUBLIC or BLE_OWN_ADDR_RPA_PUBLIC_DEFAULT set manually (or with ble_hs_id_infer_auto(usePrivacy, &advertiseAddressType) with static address not set). It works as expected. When using RPA dev.A correctly generates a random address and sets TxAddr flag to 1 in ADV_IND. I can connect with my Android app, bond and read characteristics and descriptors values.

Image

But there is weird behavior for me. When the Android sends CONNECT_IND it uses RPA address but sets RxAddr flag to 0 (instead of 1). In spite of that everything works as expected. As we can see on the screenshot, Android is using RPA when connecting to the dev.A (TxAddr == 1 and Initiator Addr starts with 0b01). Advertising address is correct (as in ADV_IND) but I think RxAddr flag should be 1, because of TxAddr == 1 in the previous ADV_IND.

Image

My dev.B is blecent example slightly modified to minimum functionality.

    ble_hs_cfg.sm_sc = 1;
    ble_hs_cfg.sm_bonding = 1;
    ble_hs_cfg.sm_our_key_dist |= BLE_SM_PAIR_KEY_DIST_ID;
    ble_hs_cfg.sm_their_key_dist |= BLE_SM_PAIR_KEY_DIST_ID;

    ble_store_config_init();

static void blecent_on_sync(void) {
    uint8_t own_addr_type = BLE_OWN_ADDR_RPA_PUBLIC_DEFAULT;
    struct ble_gap_disc_params disc_params = {0};

    disc_params.filter_duplicates = 1;
    disc_params.passive = 1;
    disc_params.itvl = 0;
    disc_params.window = 0;
    disc_params.filter_policy = 0;
    disc_params.limited = 0;

    ble_gap_disc(own_addr_type, 15000, &disc_params, blecent_gap_event, NULL);
}

static int blecent_gap_event(struct ble_gap_event *event, void *arg) {
  switch (event->type) {
    case BLE_GAP_EVENT_DISC:
      // ...
      ble_gap_disc_cancel();
      ble_gap_connect(BLE_OWN_ADDR_RPA_PUBLIC_DEFAULT, &event->disc.addr, 30000, NULL, blecent_gap_event, NULL);
      break;

    case BLE_GAP_EVENT_CONNECT:
      ble_gap_security_initiate(event->connect.conn_handle);
      break;
  }

  return 0;
}

So it simply scans for advertisements, finds specific device and tries to connect and pair/bond with it. It also sets RxAddr == 0 as the Android app (AdvAddr is correct RPA though), but the main thing is it sets TxAddr as 0 and sends its public address instead of RPA with Public identity.

Image

As described in Core Spec 5.4 | Vol 6, Part B 2.3.1.1 (ADV_IND)

The PDU shall be used in connectable and scannable undirected advertising events. The TxAdd in the advertising physical channel PDU header indicates whether the advertiser’s address in the AdvA field is public (TxAdd = 0) or random (TxAdd = 1). [...] The Payload consists of AdvA and AdvData fields. The AdvA field shall contain the advertiser’s public or random device address as indicated by TxAdd.

Therefore dev.A acts according to the spec.

As described in Core Spec 5.4 | Vol 6, Part B 2.3.3.1 (CONNECT_IND and AUX_CONNECT_REQ)

TxAdd in the advertising physical channel PDU header indicates whether the address in the InitA field is public (TxAdd = 0) or random (TxAdd = 1). The RxAdd in the advertising physical channel PDU header indicates whether the address in the AdvA field is public (RxAdd = 0) or random (RxAdd = 1). [...] The Payload consists of InitA, AdvA, and LLData fields. The InitA field shall contain the Initiator’s public or random device address as indicated by TxAdd. The AdvA field shall contain the advertiser’s public or random device address as indicated by RxAdd.

Therefore Android app and dev.B seems to act not according to the spec.

As described in Core Spec 5.4 | Vol 6, Part B 6.4 (PRIVACY IN THE INITIATING STATE)

The Link Layer shall use resolvable private addresses for the initiator’s device address (InitA field) when initiating connection establishment with an associated device that exists in the Resolving List. The initiator’s device address (InitA field) in the initiating PDU is generated using the Resolving List Local IRK and the Resolvable Private Address Generation procedure (see Section 1.3.2.2). The Link Layer should not set the InitA field to the same value as the TargetA field in the received advertising PDU. The Link Layer shall use the public address or the Host-provided address for the initiator’s device address (InitA field) when initiating connection establishment with a device that is not in the Resolving List.
6.5 PRIVACY OF THE DEVICE
A device wanting to maintain its privacy shall not use its Identity Address in any advertising PDU. The Host may command the Controller to advertise, scan, or initiate a connection using a Resolvable Private Address when the resolving list is enabled. If the local IRK in the resolving list associated with the peer Identity Address is all zeros, the Controller will use the Identity Address. If the peer IRK in the resolving list associated with the peer Identity Address is all zeros, the Controller will accept the Identity Address. If the Host has instructed the Controller to use device privacy mode with a peer Identity Address, the Controller will accept the peer’s Identity Address. This implies that the device's network privacy is violated. To maintain a device’s network privacy, the Host should only populate entries in the Controller’s resolving list with non-zero IRKs and not instruct the Controller to use device privacy mode.

I cannot understand BLE spec. My understanding is that central device should be able to use RPA when trying to connect to a peripheral (even if it's not going to pair/bond with it). Just to protect itself from the peripheral seeing the real (Identity) address of the central. If the central eventually wants to pair/bond than it discloses its Identity (public or static) address to the peripheral. (This happens in the phone app scenario. Android connects to my peripheral with a random address, when I pair/bond these devices my dev.A sees (and stores) Android's "real" address). But this doesn't happen with nimble central dev.B which doesn't send RPA during connection (instead it sends public address).

How to force nimble central to send CONNECT_IND with RPA?

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