Skip to content

Commit 28f2479

Browse files
committed
Restoring bond clarification
1 parent c0de160 commit 28f2479

File tree

3 files changed

+44
-30
lines changed

3 files changed

+44
-30
lines changed

dfu/src/main/java/no/nordicsemi/android/dfu/BaseCustomDfuImpl.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -464,7 +464,8 @@ void finalize(final Intent intent, final boolean forceRefresh) {
464464

465465
if (restoreBond && (mFileType & DfuBaseService.TYPE_APPLICATION) > 0) {
466466
// Restore pairing when application was updated.
467-
createBond();
467+
if (!createBond())
468+
logw("Creating bond failed");
468469
alreadyWaited = false;
469470
}
470471
}

dfu/src/main/java/no/nordicsemi/android/dfu/BaseDfuImpl.java

Lines changed: 15 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -447,7 +447,10 @@ void enableCCCD(@NonNull final BluetoothGattCharacteristic characteristic, final
447447
while ((!cccdEnabled && mConnected && mError == 0) || mPaused) {
448448
mLock.wait();
449449
// Check the value of the CCCD
450-
cccdEnabled = descriptor.getValue() != null && descriptor.getValue().length == 2 && descriptor.getValue()[0] > 0 && descriptor.getValue()[1] == 0;
450+
cccdEnabled = descriptor.getValue() != null
451+
&& descriptor.getValue().length == 2
452+
&& descriptor.getValue()[0] > 0
453+
&& descriptor.getValue()[1] == 0;
451454
}
452455
}
453456
} catch (final InterruptedException e) {
@@ -570,14 +573,13 @@ void writeOpCode(@NonNull final BluetoothGattCharacteristic characteristic, @Non
570573

571574
/**
572575
* Creates bond to the device. Works on all APIs since 18th (Android 4.3).
576+
* This method will only be called in this library after bond information was removed.
573577
*
574-
* @return true if it's already bonded or the bonding has started
578+
* @return true if bonding has started, false otherwise.
575579
*/
576580
@SuppressWarnings("UnusedReturnValue")
577581
boolean createBond() {
578582
final BluetoothDevice device = mGatt.getDevice();
579-
if (device.getBondState() == BluetoothDevice.BOND_BONDED)
580-
return true;
581583

582584
boolean result;
583585
mRequestCompleted = false;
@@ -593,7 +595,7 @@ boolean createBond() {
593595
// We have to wait until device is bounded
594596
try {
595597
synchronized (mLock) {
596-
while (!mRequestCompleted && !mAborted)
598+
while (result && !mRequestCompleted && !mAborted)
597599
mLock.wait();
598600
}
599601
} catch (final InterruptedException e) {
@@ -610,12 +612,14 @@ boolean createBond() {
610612
*/
611613
private boolean createBondApi18(@NonNull final BluetoothDevice device) {
612614
/*
613-
* There is a createBond() method in BluetoothDevice class but for now it's hidden. We will call it using reflections. It has been revealed in KitKat (Api19)
615+
* There is a createBond() method in BluetoothDevice class but for now it's hidden.
616+
* We will call it using reflections. It has been revealed in KitKat (Api19)
614617
*/
615618
try {
616619
final Method createBond = device.getClass().getMethod("createBond");
617620
mService.sendLogBroadcast(DfuBaseService.LOG_LEVEL_DEBUG, "gatt.getDevice().createBond() (hidden)");
618-
return (Boolean) createBond.invoke(device);
621+
//noinspection ConstantConditions
622+
return (Boolean) createBond.invoke(device);
619623
} catch (final Exception e) {
620624
Log.w(TAG, "An exception occurred while creating bond", e);
621625
}
@@ -636,14 +640,16 @@ boolean removeBond() {
636640
mService.sendLogBroadcast(DfuBaseService.LOG_LEVEL_VERBOSE, "Removing bond information...");
637641
boolean result = false;
638642
/*
639-
* There is a removeBond() method in BluetoothDevice class but for now it's hidden. We will call it using reflections.
643+
* There is a removeBond() method in BluetoothDevice class but for now it's hidden.
644+
* We will call it using reflections.
640645
*/
641646
try {
642647
//noinspection JavaReflectionMemberAccess
643648
final Method removeBond = device.getClass().getMethod("removeBond");
644649
mRequestCompleted = false;
645650
mService.sendLogBroadcast(DfuBaseService.LOG_LEVEL_DEBUG, "gatt.getDevice().removeBond() (hidden)");
646-
result = (Boolean) removeBond.invoke(device);
651+
//noinspection ConstantConditions
652+
result = (Boolean) removeBond.invoke(device);
647653

648654
// We have to wait until device is unbounded
649655
try {

dfu/src/main/java/no/nordicsemi/android/dfu/DfuBaseService.java

Lines changed: 27 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -233,20 +233,20 @@ public abstract class DfuBaseService extends IntentService implements DfuProgres
233233
*/
234234
public static final String EXTRA_CURRENT_MTU = "no.nordicsemi.android.dfu.extra.EXTRA_CURRENT_MTU";
235235
/**
236-
* Set this flag to true to enable experimental buttonless feature in Secure DFU. When the
237-
* experimental Buttonless DFU Service is found on a device, the service will use it to
236+
* Set this flag to true to enable experimental buttonless feature in Secure DFU from SDK 12.
237+
* When the experimental Buttonless DFU Service is found on a device, the service will use it to
238238
* switch the device to the bootloader mode, connect to it in that mode and proceed with DFU.
239239
* <p>
240240
* <b>Please, read the information below before setting it to true.</b>
241241
* <p>
242242
* In the SDK 12.x the Buttonless DFU feature for Secure DFU was experimental.
243243
* It is NOT recommended to use it: it was not properly tested, had implementation bugs
244244
* (e.g. https://devzone.nordicsemi.com/question/100609/sdk-12-bootloader-erased-after-programming/)
245-
* and does not required encryption and therefore may lead to DOS attack (anyone can use it
245+
* and does not require encryption and therefore may lead to DOS attack (anyone can use it
246246
* to switch the device to bootloader mode). However, as there is no other way to trigger
247247
* bootloader mode on devices without a button, this DFU Library supports this service,
248248
* but the feature must be explicitly enabled here.
249-
* Be aware, that setting this flag to false will no protect your devices from this kind of
249+
* Be aware, that setting this flag to false will not protect your devices from this kind of
250250
* attacks, as an attacker may use another app for that purpose. To be sure your device is
251251
* secure remove this experimental service from your device.
252252
* <p>
@@ -260,9 +260,7 @@ public abstract class DfuBaseService extends IntentService implements DfuProgres
260260
* 0x01 - Success<br>
261261
* The device should disconnect and restart in DFU mode after sending the notification.
262262
* <p>
263-
* In SDK 13 this issue will be fixed by a proper implementation (bonding required,
264-
* passing bond information to the bootloader, encryption, well tested).
265-
* It is recommended to use this new service when SDK 13 (or later) is out.
263+
* In SDK 14 this issue was fixed by Buttonless Service With Bonds.
266264
*/
267265
public static final String EXTRA_UNSAFE_EXPERIMENTAL_BUTTONLESS_DFU = "no.nordicsemi.android.dfu.extra.EXTRA_UNSAFE_EXPERIMENTAL_BUTTONLESS_DFU";
268266
/**
@@ -877,16 +875,21 @@ public void onConnectionStateChange(final BluetoothGatt gatt, final int status,
877875
mConnectionState = STATE_CONNECTED;
878876

879877
/*
880-
* The onConnectionStateChange callback is called just after establishing connection and before sending Encryption Request BLE event in case of a paired device.
881-
* In that case and when the Service Changed CCCD is enabled we will get the indication after initializing the encryption, about 1600 milliseconds later.
882-
* If we discover services right after connecting, the onServicesDiscovered callback will be called immediately, before receiving the indication and the following
883-
* service discovery and we may end up with old, application's services instead.
878+
* The onConnectionStateChange callback is called just after establishing connection and before sending Encryption Request BLE event in case of a paired device.
879+
* In that case and when the Service Changed CCCD is enabled we will get the indication after initializing the encryption, about 1600 milliseconds later.
880+
* If we discover services right after connecting, the onServicesDiscovered callback will be called immediately, before receiving the indication and the following
881+
* service discovery and we may end up with old, application's services instead.
884882
*
885-
* This is to support the buttonless switch from application to bootloader mode where the DFU bootloader notifies the master about service change.
886-
* Tested on Nexus 4 (Android 4.4.4 and 5), Nexus 5 (Android 5), Samsung Note 2 (Android 4.4.2). The time after connection to end of service discovery is about 1.6s
887-
* on Samsung Note 2.
883+
* This is to support the buttonless switch from application to bootloader mode where the DFU bootloader notifies the master about service change.
884+
* Tested on Nexus 4 (Android 4.4.4 and 5), Nexus 5 (Android 5), Samsung Note 2 (Android 4.4.2). The time after connection to end of service discovery is about 1.6s
885+
* on Samsung Note 2.
888886
*
889-
* NOTE: We are doing this to avoid the hack with calling the hidden gatt.refresh() method, at least for bonded devices.
887+
* NOTE: We are doing this to avoid the hack with calling the hidden gatt.refresh()
888+
* method, at least for bonded devices.
889+
*
890+
* IMPORTANT: BluetoothDevice.getBondState() returns true if the bond information
891+
* is present on Android, not necessarily when the link is established or even
892+
* encrypted. This is a security issue, but in here it does not matter.
890893
*/
891894
if (gatt.getDevice().getBondState() == BluetoothDevice.BOND_BONDED) {
892895
logi("Waiting 1600 ms for a possible Service Changed indication...");
@@ -1627,19 +1630,23 @@ protected void close(@NonNull final BluetoothGatt gatt) {
16271630
*/
16281631
protected void refreshDeviceCache(@NonNull final BluetoothGatt gatt, final boolean force) {
16291632
/*
1630-
* If the device is bonded this is up to the Service Changed characteristic to notify Android that the services has changed.
1631-
* There is no need for this trick in that case.
1632-
* If not bonded, the Android should not keep the services cached when the Service Changed characteristic is present in the target device database.
1633-
* However, due to the Android bug (still exists in Android 5.0.1), it is keeping them anyway and the only way to clear services is by using this hidden refresh method.
1633+
* If the device is bonded this is up to the Service Changed characteristic to notify Android
1634+
* that the services has changed. There is no need for this trick in that case.
1635+
* If not bonded, the Android should not keep the services cached when the Service Changed
1636+
* characteristic is present in the target device database.
1637+
* However, due to the Android bug, it is keeping them anyway and the only way to clear
1638+
* services is by using this hidden refresh method.
16341639
*/
16351640
if (force || gatt.getDevice().getBondState() == BluetoothDevice.BOND_NONE) {
16361641
sendLogBroadcast(LOG_LEVEL_DEBUG, "gatt.refresh() (hidden)");
16371642
/*
1638-
* There is a refresh() method in BluetoothGatt class but for now it's hidden. We will call it using reflections.
1643+
* There is a refresh() method in BluetoothGatt class but for now it's hidden.
1644+
* We will call it using reflections.
16391645
*/
16401646
try {
16411647
//noinspection JavaReflectionMemberAccess
16421648
final Method refresh = gatt.getClass().getMethod("refresh");
1649+
//noinspection ConstantConditions
16431650
final boolean success = (Boolean) refresh.invoke(gatt);
16441651
logi("Refreshing result: " + success);
16451652
} catch (final Exception e) {

0 commit comments

Comments
 (0)