I am not able to perform DFU using the Legacy bootloader from SDK v11.0.0 or older with my Android 10 device and/or iOS device.
I am not able to perform DFU using the Legacy bootloader from SDK v11.0.0 or older with my Android 10 device and/or iOS device.
This is a known issue: https://github.com/NordicSemiconductor/IOS-Pods-DFU-Library/issues/368
Recent version of Android and iOS, or recent smartphones (we didn't pinpoint it yet), use private resolvable addresses and LL privacy. So when a peripheral performs directed advertisement without being bonded to the central, then the central device may not be able to resolve the address as there is no IRK in its internal resolving list and the directed advertisment packet may be ignored and the central does not send a connection request.
In the Legacy Bootloader we have peer data sharing feature where the application shares the bonding information with the bootloader. The peer data consists of the peer address, the identity resolving key, the encryption key of the link and the system attribute for the Service Changed indication.
typedef struct { ble_gap_addr_t addr; /**< BLE GAP address of the device that initiated the DFU process. */ ble_gap_irk_t irk; /**< IRK of the device that initiated the DFU process if this device uses Private Resolvable Addresses. */ ble_gap_enc_key_t enc_key; /**< Encryption key structure containing encrypted diversifier and LTK for re-establishing the bond. */ uint8_t sys_serv_attr[SYSTEM_SERVICE_ATT_SIZE]; /**< System service attributes for restoring of Service Changed Indication setting in DFU mode. */ } dfu_ble_peer_data_t;
If the central device is not bonded with the peripheral, then the peripheral will only populate the address field. The bootloader will then perfrom directed advertisment towards this address. If the central device is bonded, then the all the fields are populated and the bootloader will advertise using a whitelist.
/**@brief Function for starting advertising. */ static void advertising_start(void) { if (!m_is_advertising) { uint32_t err_code; // Initialize advertising parameters (used when starting advertising). memset(&m_adv_params, 0, sizeof(m_adv_params)); if (m_ble_peer_data_valid) { ble_gap_irk_t empty_irk = {{0}}; if (memcmp(m_ble_peer_data.irk.irk, empty_irk.irk, sizeof(empty_irk.irk)) == 0) { advertising_init(BLE_GAP_ADV_FLAGS_LE_ONLY_LIMITED_DISC_MODE); m_adv_params.type = BLE_GAP_ADV_TYPE_ADV_DIRECT_IND; m_adv_params.p_peer_addr = &m_ble_peer_data.addr; m_adv_params.fp = BLE_GAP_ADV_FP_ANY; m_adv_params.interval = 0; m_adv_params.timeout = 0; } else { ble_gap_irk_t * p_irk[1]; ble_gap_addr_t * p_addr[1]; p_irk[0] = &m_ble_peer_data.irk; p_addr[0] = &m_ble_peer_data.addr; ble_gap_whitelist_t whitelist; whitelist.addr_count = 1; whitelist.pp_addrs = p_addr; whitelist.irk_count = 1; whitelist.pp_irks = p_irk; advertising_init(BLE_GAP_ADV_FLAG_BR_EDR_NOT_SUPPORTED); m_adv_params.type = BLE_GAP_ADV_TYPE_ADV_IND; m_adv_params.fp = BLE_GAP_ADV_FP_FILTER_CONNREQ; m_adv_params.p_whitelist = &whitelist; m_adv_params.interval = APP_ADV_INTERVAL; m_adv_params.timeout = APP_ADV_TIMEOUT_IN_SECONDS; } } else { advertising_init(BLE_GAP_ADV_FLAGS_LE_ONLY_GENERAL_DISC_MODE); m_adv_params.type = BLE_GAP_ADV_TYPE_ADV_IND; m_adv_params.p_peer_addr = NULL; m_adv_params.fp = BLE_GAP_ADV_FP_ANY; m_adv_params.interval = APP_ADV_INTERVAL; m_adv_params.timeout = APP_ADV_TIMEOUT_IN_SECONDS; } err_code = sd_ble_gap_adv_start(&m_adv_params); APP_ERROR_CHECK(err_code); nrf_gpio_pin_clear(ADVERTISING_LED_PIN_NO); m_is_advertising = true; } }
If the central bonds with the application before resetting into the bootloader, then the bootloader will advertise using the a whitelist and the central will not have any issue with reconnecting to the bootloader after sending the DFU start command to the DFU service in the application, which triggers a reset into the bootloader. It is also possible to altered the code so that it performs undirected advertisment, i.e.
if (memcmp(m_ble_peer_data.irk.irk, empty_irk.irk, sizeof(empty_irk.irk)) == 0) { advertising_init(BLE_GAP_ADV_FLAGS_LE_ONLY_LIMITED_DISC_MODE); m_adv_params.type = BLE_GAP_ADV_TYPE_ADV_IND; // BLE_GAP_ADV_TYPE_ADV_DIRECT_IND; m_adv_params.p_peer_addr = &m_ble_peer_data.addr; m_adv_params.fp = BLE_GAP_ADV_FP_ANY; m_adv_params.interval = 300; m_adv_params.timeout = 180; }.
So the solution would be to either bond with the application before performing the DFU so that the bootloader uses advertising with the whitelist and not directed advertisement. Or modify the source code to perform undirected advertisment instead as shown above.
If you are interested in the relevant parts of the specification then see below
Best regards
Bjørn
It is important to understand that when the Legacy DFU solution was designed, then the addresses used in the directed advertisment packet needed to be public or random, they could not be resolvable as is allowed in the newer specifications.
BLUETOOTH SPECIFICATION Version 4.1 [Vol 6] page 40 of 174
2.3.1.2 ADV_DIRECT_IND
The Payload field consists of AdvA and InitA fields. The AdvA field shall contain the advertiser’s public or random device address as indicated by TxAdd. The InitA field is the address of the device to which this PDU is addressed. The InitA field shall contain the initiator’s public or random device address as indicated by RxAdd.
When the newer Bluetooth Specifications allowed directed advertisment packets to use resolvable addresses then they also added the Central Address Resolution characteristic to the GAP service.
BLUETOOTH CORE SPECIFICATION Version 5.2 | Vol 3, Part C page 1397
12.4 CENTRAL ADDRESS RESOLUTION
The Peripheral should check if the peer device supports address resolution by reading the Central Address Resolution characteristic before using directed
advertisement where the initiator address is set to a Resolvable Private Address (RPA).
The Legacy DFU solution will simply pass the Resolvable Private Address (RPA) used in the connection request to the bootloader that uses this as the initiator address in the directed advertisment packet, without checking this CAR characteristic.
Now, even if the bootloader is modified to check the CAR characteristic, the central could still ignore the directed advertisment packet as the central might not be able to resolve the Initiator address in the directed advertisment packet there is no entry in the Central's Bluetooth Controller Resolving List as the peripheral and central has not been bonded and exchanged IRKs.
BLUETOOTH CORE SPECIFICATION Version 5.2 | Vol 6, Part B page 3065
6.2.2 Connectable directed event type
The Link Layer shall use resolvable private addresses for the advertiser’s device address (AdvA field). If an IRK is available in the Link Layer Resolving
List for the peer device, then the target’s device address (TargetA field) shall use a resolvable private address. If an IRK is not available in the Link Layer
Resolving List or the IRK is set to zero for the peer device, then the target’s device address (TargetA field) shall use the Identity Address when entering the
Advertising State and using connectable directed events.
When an advertiser receives a connection request that contains a resolvable private address for the initiator’s address (InitA field) and address resolution is
enabled, the Link Layer shall resolve the private address (see Section 1.3.2.3).
So it seems that Android 10 enables Address Resolution and is not able to resolve the Initiator address as there is no IRK. The nRF should use the identity address instead of the Resolvable Private Address.
I'm here to say that we are seeing the same problem with Android 8 and 9 as well.
Do you have any more information on which versions might be affected?
Hi there! We faced this problem and applied the new Android DFU library to fix it. However, since doing so we observed that there are more instances of GATT error. We have been monitoring DFU failures in our app and you can see a trend after applying the fix at around mid/end Aug, the DFU disconnected problem users decreased but GATT error users increased significantly.