Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow only certain bonded client to connect to NimBLEServer. Whitelist in advertising does not work for already bonded peers #651

Open
KlausMu opened this issue Mar 21, 2024 · 66 comments

Comments

@KlausMu
Copy link

KlausMu commented Mar 21, 2024

Hi, first of all thanks a lot for this library! So much better than the bluedroid based library. Solved a lot of issues.

I want an ESP32 to act as a server, to be precise as an HID bluetooth keyboard.

I need to connect to different clients (in my case a FireTV and an AppleTV) and to switch the connection to them at runtime. Only one should be connected at the same time.

I am able to pair and bond both of them. One after the other. In the list of bonded peers I have two peers afterwards.

Remark: It is possible to connect both of them at the same time, if advertising is not stopped after the first connect. But it does not make sense because both devices would receive the same keyboard keys. I cannot force certain keys to be send to a certain peer.

So to achieve switching between the two peers, I tried to:

  • NimBLEDevice::getServer()->disconnect(...) // the currently connected peer
  • add the other device to the whitelist (AppleTV or FireTV) and restart advertising with advertising->setScanFilter(true, true);

The whitelist filter works in principle (bonding is only possible based on the whitelist). But if a client is already bonded, it can reconnect no matter if there is a whitelist set in advertising or not.

So how can I force only a certain bonded client to connect to the NimBLEServer?

I tried to pair the HID without bonding, NimBLEDevice::setSecurityAuth(false, false, false);, but when doing so, reconnects completely fail. It seems that keyboards always need to be bonded, not only to be paired.

@h2zero
Copy link
Owner

h2zero commented Jun 13, 2024

This should now be resolved in the master branch, use the new onIdentity client and server callback to know when you can store the ID address and add it to the whitelist.

@KlausMu
Copy link
Author

KlausMu commented Jun 23, 2024

@h2zero Thanks for the hint that the new onIdentity callback was introduced.
Could you please give me some advice on how to use this new callback?

I pair the ESP32 as a HID to a client, let's say a Windows machine. My server callback onIdentity and also the default callback (if I don't provide my own one) is never called. That's because the event BLE_GAP_EVENT_IDENTITY_RESOLVED never fires.

But even if it fired, what should I do in the callback? My problem is, that if a client connects for the first time, it is automatically added to the list of bonded peers. And whenever I start advertising again (with a whitelist), the whitelist is not used if an already bonded client connects. The whitelist only works for not already bonded clients. How does the new callback help here?

@h2zero
Copy link
Owner

h2zero commented Jun 23, 2024

The use case for this callback is when bonding with devices that use a random resolvable address and you want to add the id address to the whitelist. The onAuthenticationComplete callback did not provide the correct identity address so adding it to the whitelist there wouldn't have the desired effect.

To answer your question though, it doesn't help your case please ignore my previous comment as I thought this was a different issue and didn't read it again.

Have you tried clearing the whitelist and then adding the device you want? Alternatively you could try using directed advertising.

@KlausMu
Copy link
Author

KlausMu commented Jun 24, 2024

Directed advertising sounds good. I tried this:
advertising->setAdvertisementType(BLE_GAP_CONN_MODE_DIR);
When doing so, a bonded client does not reconnect at all. No matter if I use a whitelist or not.
How do I tell the directed advertising to which client the advertising should be sent?

BTW, clearing the whitelist of course would work, but this is not want I need. I need two clients bonded at the same time. I need to switch the connection between these two bonded clients at runtime.
I can successfully bond two clients. I simply power off the first one while the second one gets bonded.
When starting advertising with two bonded clients, it is random which one connects first. I need to control which of the two bonded clients connects to the server.

@h2zero
Copy link
Owner

h2zero commented Jun 24, 2024

What I meant was you could just change the whitelist before advertising to have one or the other device in it instead of both.

Directed advertising sadly does not have an API but you can use custom data like so:

// 0x17 = public address, change to 0x18 for random, 0x11 - 0x66 = address
char directedAdv[8] = {0x17, 0x06, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66}; 
NimBLEAdvertisementData data;
data.addData(directedAdv, sizeof(directedAdv));
advertising->setAdvertisementData(data);
advertising->setAdvertisementType(BLE_GAP_CONN_MODE_DIR);
advertising->start();

@KlausMu
Copy link
Author

KlausMu commented Jun 25, 2024

Thanks for the snippet. I tried it, with the following result:

D NimBLEAdvertising: >> Advertising start: customAdvData: 1, customScanResponseData: 0
primary service
           uuid 0x1800
         handle 1
     end_handle 5
characteristic
           uuid 0x2a00
...
E NimBLEAdvertising: Unable to advertise - Duration too long
D NimBLEAdvertising: << Advertising start

I think the Unable to advertise - Duration too long should not be there. At least the already bonded client does not reconnect.
Do you have any idea what could have gone wrong?

@h2zero
Copy link
Owner

h2zero commented Jun 25, 2024

I think the duration is limited to 2 seconds for directed advertising, so you would have to restart it every time it ends with that duration.

@KlausMu
Copy link
Author

KlausMu commented Jun 26, 2024

Got it working. But not with your snippet. I think it cannot work.

Unable to advertise - Duration too long is misleading. This is not the reason. The message appears immediatly, no waiting time at all.

Why it is not working: when calling NimBLEAdvertising::start(), this line is called. In the next line, since dirAddr == nullptr, peerAddr will be NULL when calling ble_gap_adv_start

In ble_gap_adv_start this line is executed, with direct_addr being NULL.

In ble_gap_adv_validate, because of this line, the result is BLE_HS_EINVAL.

So for directed advertising to work, you must provide the peer address when calling NimBLEAdvertising::start(), like in this example:

// either public address
NimBLEAddress directedAddress = NimBLEAddress("11:22:33.44:55:66", BLE_ADDR_PUBLIC);
// or in case of a random address
//NimBLEAddress directedAddress = NimBLEAddress("11:22:33.44:55:66", BLE_ADDR_RANDOM);
advertising->setAdvertisementType(BLE_GAP_CONN_MODE_DIR);
advertising->start(BLE_HS_FOREVER, nullptr, &directedAddress);

So thanks for your support, for me this is now working. I tested it with public and random addresses.
I can connect and bond several devices - one after each other, turning the already bonded devices off while bonding the next one.
With directed advertising I can connect exactly to the device I want to connect to. Switching the connection is very fast.

@h2zero
Copy link
Owner

h2zero commented Jun 26, 2024

Glad you got it working, I completely forgot about that parameter being added to advertising start call 👍

@KlausMu KlausMu closed this as completed Jul 17, 2024
@rac146
Copy link

rac146 commented Oct 19, 2024

Hey all - I was trying to get the directed advertising method outlined by @KlausMu to work with an Android phone but it doesn't seem to be accepting the direct advertising request. Does this have something to do with RPA from Android? The Bond being stored in Nimble is just the regular device address so I tried to direct the advertisement with that address, but no luck..

Upon initial bonding, I saw 2 addresses logged (the other looks to be random). I did try to set that random address like this:

NimBLEAddress directedAddress = NimBLEAddress("11:22:33.44:55:66", BLE_ADDR_RANDOM);

Android DID respond with that, but it just deleted the bond, saying can't communicate with the device..

@h2zero
Copy link
Owner

h2zero commented Oct 19, 2024

The directed address should be the static id address of the device, which is what is stored when bonded.

@KlausMu
Copy link
Author

KlausMu commented Oct 20, 2024

If you are not sure which is the static id address, you can use

NimBLEConnInfo connInfo = BLEDevice::getServer()->getPeerIDInfo(0);
Serial.println(BLEAddress(connInfo.getAddress()).toString().c_str());

or

Serial.println(BLEDevice::getBondedAddress(0).toString().c_str());

(assuming that you have only one device bonded and connected).

Then this should work

directedAddress = NimBLEAddress("11:22:33.44:55:66", BLE_ADDR_PUBLIC);
advertising->setScanFilter(false, false);
advertising->setAdvertisementType(BLE_GAP_CONN_MODE_DIR);
advertising->start(BLE_HS_FOREVER, nullptr, &directedAddress);

But I also struggled with direct advertisement to devices with random addresses.
To be honest, I can't remember exactly

  • if it finally worked
  • or if I had to tell the mobile phone to not use random addresses

I believe directed advertising worked even if the phone used RPA.
But I just tried to repeat the test and was not successfull.

Since phones are not my use case, but only media devices which are not using RPA, I don't have a lot of experience with devices using RPA.

@rac146
Copy link

rac146 commented Oct 20, 2024

OK I did a few more tests with different devices and can see a pattern..

If the static address in your check above MATCHES the ID address (BLEAddress(connInfo.getIdAddress()).toString().c_str()), then we know the device is NOT using RPA.

An Android TV device with no RPA (Id address == address), works just fine with direct advertising.

An Android phone with RPA (Id address != address), is causing issues. Switching the directed address to BLE_ADDR_RANDOM doesn't seem to work for the phone (at least the one I tried).

I looked into some other Bluetooth API's that also support direct advertising and saw Zephyr - they have 2 different directed advertising modes: Zephyr. Does that suggest there's some else lower in stack that needs to support devices that use RPA?

@h2zero
Copy link
Owner

h2zero commented Oct 21, 2024

It would t make sense to direct advertising to an RPA because it could change at any time, most phones change their address every 15 minutes.

I think this may be resolved in the master branch which provides a callback when the identity address is received.

@KlausMu
Copy link
Author

KlausMu commented Oct 21, 2024

Another detail: it seems only 2 bonds can be saved in the NVS (I'm using an ESP32). If I bond the third peer, one of the older two is deleted.

I saw a lot uses of "BLE_STORE_MAX_BONDS" in the code, but I cannot find where it is defined or where it can be changed.
@h2zero could you please help out?

@KlausMu
Copy link
Author

KlausMu commented Oct 21, 2024

@rac146 which version of NimBLE are you using? Currently for me directed advertising only works with master branch, not with 1.4.2 ...

@rac146
Copy link

rac146 commented Oct 21, 2024

I've been using master branch..

@h2zero
Copy link
Owner

h2zero commented Oct 21, 2024

I saw a lot uses of "BLE_STORE_MAX_BONDS" in the code, but I cannot find where it is defined or where it can be changed.
@h2zero could you please help out?

// #define CONFIG_BT_NIMBLE_MAX_BONDS 3

If you're using the master branch you should obtain the ID address in this new callback:

virtual void onIdentity(NimBLEConnInfo& connInfo);

@KlausMu
Copy link
Author

KlausMu commented Oct 23, 2024

I saw a lot uses of "BLE_STORE_MAX_BONDS" in the code, but I cannot find where it is defined or where it can be changed.
@h2zero could you please help out?

// #define CONFIG_BT_NIMBLE_MAX_BONDS 3

@h2zero thanks for pointing me to // #define CONFIG_BT_NIMBLE_MAX_BONDS 3
It turned out that this was not the limiting factor, since only 2 bonds have been stored.
In my case it seems that I have 4 CCCDs, so each bond takes 4 CCCDs.
Default is // #define CONFIG_BT_NIMBLE_MAX_CCCDS 8
If I raise the maximum numbers of CCCDs to 12, I can store up to 3 bonds.

@KlausMu
Copy link
Author

KlausMu commented Oct 23, 2024

I've been using master branch..

@rac146 Do you think you could repeat the test with version 1.4.2, just to be sure that direct advertising is really not working in the latest official release? Your code should almost stay the same. Only the callbacks for onConnect and onDisconnect changed.
In version 2.0.0 (master) you get NimBLEConnInfo& connInfo in these callbacks as parameters.
In version 1.4.2 you can get the same information inside the callback with NimBLEConnInfo connInfo = pServer->getPeerInfo(0);

If I understand everything correctly, the situation is:

NimBLE version direct advertising for
public addresses
direct advertising for
random addresses
1.4.2 🗙 🗙
2.0.0 (master) 🗙

✓ working
🗙 not working

Unfortunately I cannot switch in my published project to the master branch, as long as there are still breaking changes. I ran into at least one between March and October. That's totally fine for a master branch, but I cannot use this in a published project which could break when the NimBLE master changed.

@rac146
Copy link

rac146 commented Oct 23, 2024

So apologies here - I've been running the esp-nimble-cpp branch, as I'm building an ESP-IDF project. I know the branches are similar, but might not give you the answer you're looking for in regards to the NimBLE-Arduino branch.

Here's the table as it stands, we can fill in as we test.. I'll update this with my esp-nimble-cpp 1.4.1 findings

NimBLE version direct advertising for public addresses direct advertising for random addresses
NimBLE-Arduino 1.42 NO NO
NimBLE-Arduino master Assuming works ?
NimBLE-ESP 1.41 ? ?
NimBLE-ESP master YES NO

@KlausMu
Copy link
Author

KlausMu commented Oct 23, 2024

🗙from my posting above means NO. So with NimBLE-Arduino, only public addresses on master works. The three other combinations didn't work, at least in my tests.
update: @rac146 on "NimBLE-Arduino master" you can put YES in first column and NO in second

@KlausMu
Copy link
Author

KlausMu commented Nov 2, 2024

@rac146 And I also have problems when reusing already bonded peers when going from 1.4.2 to 2.0.0 or vice versa.
See #740

@KlausMu KlausMu reopened this Nov 2, 2024
@KlausMu
Copy link
Author

KlausMu commented Nov 2, 2024

I would like to reopen this issue, because direct advertisement only works in 2.0.0, not in 1.4.2
And in both versions, it does not work if a device uses private addresses.

@sanastasiou
Copy link

Just dropping by to verify that directed advertising is not working with 1.4.2.

@sanastasiou
Copy link

Just tested with iOS 17.6.1 and following code for directed advertising after bonding a device:

        fNimbleAdvertising->setScanFilter(false, false);
        fNimbleAdvertising->setAdvertisementType(BLE_GAP_CONN_MODE_DIR);
        NimBLEAddress directedAddress = NimBLEAddress(fBondedDevices[fBondedDeviceIndex].toString().c_str(), BLE_ADDR_PUBLIC);
        fNimbleAdvertising->startDirectedAdvertising(BLE_HS_FOREVER, nullptr, &directedAddress);
I NimBLEDevice: BLE Host Task Started
I NimBLEDevice: NimBle host synced.
D NimBLEDevice: >> setPower: 7 (type: 11)
D NimBLEDevice: << setPower
BLE stack initialization state: true
Server BLE address: C8:C9:A3:CA:14:22
Device BLE Pin: 060927
D NimBLEDevice: Setting bonding: 1, mitm: 1, sc: 1
D NimBLEDevice: >> setLocalMTU: 512
D NimBLEDevice: << setLocalMTU
D NimBLEServer: >> createService - 00002ffa-0000-1000-8000-00805f9b34fb
D NimBLEServer: << createService
ServiceHandler::addEncryptedCharacteristics() - Characteristics in service: [82c314b2-e53a-4d5e-b021-fc5887b4373f]
ServiceHandler::addEncryptedCharacteristics() - Characteristics in service: [3c5577d7-d182-482a-940a-7e5062441f43]
D NimBLEService: >> start(): Starting service: UUID: 00002ffa-0000-1000-8000-00805f9b34fb, handle: 0x0000
D NimBLEService: Adding 2 characteristics for service UUID: 00002ffa-0000-1000-8000-00805f9b34fb, handle: 0x0000
D NimBLEService: << start()
Initializing display controller
SSD1306 initialized properly...
Number of bonded devices: 1
Found bonded ble client with address:  106097662453008
Real voltage:      17.41 V
Is advertising: false
Is connected: false
Is bonded: false
Number of peer devices: 0
Ping value: 0
LedControl::update() - state: Init
DirectedAdvertiser::loadBondedDevices()
Loaded bonded device: 60:7e:c9:7b:65:10
D NimBLEAdvertising: >> setAdvertisementData
D NimBLEAdvertising: << setAdvertisementData
D NimBLEAdvertising: >> setScanResponseData
D NimBLEAdvertising: << setScanResponseData
DirectedAdvertiser::loadBondedDevices()
Loaded bonded device: 60:7e:c9:7b:65:10
DirectedAdvertiser::update() - switch from: State_Init to: State_Stopped
BluetoothController::update() - switch from: State_Init to: State_Disconnected
BluetoothController::update() - switch from: State_Disconnected to: State_Advertising
D NimBLEAdvertising: >> setScanFilter: scanRequestWhitelistOnly: 0, connectWhitelistOnly: 0
D NimBLEAdvertising: << setScanFilter
D NimBLEAdvertising: >> Advertising start: customAdvData: 1, customScanResponseData: 1
primary service
           uuid 0x1800
         handle 1
     end_handle 5
characteristic
           uuid 0x2a00
     def_handle 2
     val_handle 3
   min_key_size 0
          flags [READ]
characteristic
           uuid 0x2a01
     def_handle 4
     val_handle 5
   min_key_size 0
          flags [READ]
primary service
           uuid 0x1801
         handle 6
     end_handle 13
characteristic
           uuid 0x2a05
     def_handle 7
     val_handle 8
   min_key_size 0
          flags [INDICATE]
ccc descriptor
           uuid 0x2902
         handle 9
   min_key_size 0
          flags [READ|WRITE]
characteristic
           uuid 0x2b3a
     def_handle 10
     val_handle 11
   min_key_size 0
          flags [READ]
characteristic
           uuid 0x2b29
     def_handle 12
     val_handle 13
   min_key_size 0
          flags [READ|WRITE]
primary service
           uuid 00002ffa-0000-1000-8000-00805f9b34fb
         handle 14
     end_handle 20
characteristic
           uuid 82c314b2-e53a-4d5e-b021-fc5887b4373f
     def_handle 15
     val_handle 16
   min_key_size 0
          flags [READ|NOTIFY|INDICATE]
ccc descriptor
           uuid 0x2902
         handle 17
   min_key_size 0
          flags [READ|WRITE]
characteristic
           uuid 3c5577d7-d182-482a-940a-7e5062441f43
     def_handle 18
     val_handle 19
   min_key_size 0
          flags [READ|WRITE|NOTIFY|INDICATE|READ_ENC|READ_AUTHEN|WRITE_ENC|WRITE_AUTHEN]
ccc descriptor
           uuid 0x2902
         handle 20
   min_key_size 0
          flags [READ|WRITE]
D NimBLEAdvertising: << Advertising start
Directed Advertising to bonded device[0]: 60:7e:c9:7b:65:10

The result of this is, that my phone cannot even see my BLE server, let alone connect to it. I verified that the address used 60:7e:c9:7b:65:10 is indeed the BLE MAC Address of my iPhone.

Used master, commit: c388672

Currently at a loss as to what I should change/fix in my code.

Same code also does not work in 1.4.2 either.

If anyone has any suggestion, I'll be happy to test/try them out.

@h2zero
Copy link
Owner

h2zero commented Nov 4, 2024

NimBLEAddress directedAddress = NimBLEAddress(fBondedDevices[fBondedDeviceIndex].toString().c_str(), BLE_ADDR_PUBLIC);

I have a suspicion this is the cause of some trouble, try changing BLE_ADDR_PUBLIC to BLE_ADDR_RANDOM.

@sanastasiou
Copy link

@h2zero will try this asap and get back to you!

@sanastasiou
Copy link

@h2zero tried with BLE_ADDR_RANDOM. Still invisible to my phones. Will try deleting everything and rebonding.

@h2zero
Copy link
Owner

h2zero commented Nov 4, 2024

Can you provide the specific address?

@sanastasiou
Copy link

@h2zero I tried following:

  1. Deleting all bonded devices
  2. Rebonding -> works
  3. Changing both Android/Ios to BLE_ADDR_RANDOM -> did not work
  4. Changed ad duration to 2000ms and switch over both bonded devices -> invisible to both ( I see both MAC addresses in the console alternative for 2s each )

What do u mean by specific address? Like const auto dir_addr = NimBLEAddress("60:7e:c9:7b:65:10"); ?

@sanastasiou
Copy link

        static auto dir_addr = NimBLEAddress("60:7e:c9:7b:65:10");
        fNimbleAdvertising->startDirectedAdvertising(AD_DURATION, [this](NimBLEAdvertising* pAdvertising){
            this->onAdvertisementStopped(pAdvertising);
        }, &dir_addr);

No change. Also my config is like this:

platform = espressif32 @ ^6.9.0  ; Latest stable platform version
framework = arduino
lib_deps =
	https://github.com/h2zero/NimBLE-Arduino.git#c3886723116e202a102900709a9cf00a4d434512

@KlausMu
Copy link
Author

KlausMu commented Nov 4, 2024

I also repeated the test, with ESP32-S3, with this setup and result from yesterday:

NimBLE-Arduino 2.0.0:

board peer not using RPA (Windows) peer using RPA (Android)
ESP32-S3, directed NO YES

Windows (only has public address): does not work, neither with BLE_ADDR_PUBLIC nor with BLE_ADDR_RANDOM

Android (which provides both public and private address)_

test nr address type result
1 public BLE_ADDR_PUBLIC NO (it worked yesterday, I swear !!!)
2 public BLE_ADDR_RANDOM NO
3 private BLE_ADDR_PUBLIC NO (as expected)
4 private BLE_ADDR_RANDOM YES -> but got another private address in onConnect
5 private (2nd one) BLE_ADDR_RANDOM NO
6 private (1st one) BLE_ADDR_RANDOM NO

So test nr. 4 works, and test nr 6 not, although the data provided was the same. I really have a headache now ...

Then I deleted all bonds and repeated steps 1 to 6. This time 4 failed. I don't understand what is going on :-)

@h2zero
Copy link
Owner

h2zero commented Nov 4, 2024

I'll see what I can find later this week, hopefully there is something easy to fix.

@h2zero
Copy link
Owner

h2zero commented Nov 5, 2024

I believe I have found the cause, and it is not an issue with this library. Basically it comes down to lack of support for directed advertising by some device (all Apple devices for sure). Take a read of this: https://developer.apple.com/accessories/Accessory-Design-Guidelines.pdf specifically page 236.

@KlausMu
Copy link
Author

KlausMu commented Nov 5, 2024

Interesting. At least this gives an explanation for iOS.

But why does direct advertisement from ESP32 to Windows work with 2.0.0 (and is indeed really reliable) and not with 1.4.2?

Why does it not work at all from ESP32-S3 to Windows (not with 2.0.0 and not with 1.4.2)?

And why does it sometimes work with Android?

It's a mystery ...

@h2zero
Copy link
Owner

h2zero commented Nov 5, 2024

Still investigating, using this for testing:

#include <NimBLEDevice.h>

class serverCB : public NimBLEServerCallbacks {
  void onConnect(NimBLEServer* pServer, ble_gap_conn_desc* desc) {
    Serial.print("Client address: ");
    Serial.println(NimBLEAddress(desc->peer_ota_addr).toString().c_str());
  }

  void onDisconnect(NimBLEServer* pServer) {
    Serial.println("Client disconnected - start advertising");
    if (NimBLEDevice::getNumBonds()) {
      NimBLEAddress baddr = NimBLEDevice::getBondedAddress(0);
      Serial.printf("Starting advertising with bonded address %s\n", baddr.toString().c_str());
      pServer->getAdvertising()->setAdvertisementType(1);
      pServer->getAdvertising()->start(0 ,nullptr, &baddr);
    } else {
      Serial.println("starting normal advertising");
      pServer->getAdvertising()->setAdvertisementType(2);
      pServer->getAdvertising()->start();
    }
  }
};

void setup() {
  Serial.begin(115200);
  Serial.println("Starting NimBLE Server");
  NimBLEDevice::init("NimBLE");
  NimBLEDevice::setSecurityAuth(true, true, true);
  NimBLEDevice::setSecurityPasskey(123456);
  NimBLEDevice::setSecurityIOCap(BLE_HS_IO_DISPLAY_ONLY);
  NimBLEServer *pServer = NimBLEDevice::createServer();
  pServer->setCallbacks(new serverCB());
  pServer->advertiseOnDisconnect(false);
  NimBLEService *pService = pServer->createService("ABCD");
  NimBLECharacteristic *pNonSecureCharacteristic = pService->createCharacteristic("1234", NIMBLE_PROPERTY::READ );
  NimBLECharacteristic *pSecureCharacteristic = pService->createCharacteristic("1235", NIMBLE_PROPERTY::READ | NIMBLE_PROPERTY::READ_ENC | NIMBLE_PROPERTY::READ_AUTHEN);

  pService->start();
  pNonSecureCharacteristic->setValue("Hello Non Secure BLE");
  pSecureCharacteristic->setValue("Hello Secure BLE");

  NimBLEAdvertising *pAdvertising = NimBLEDevice::getAdvertising();
  if (NimBLEDevice::getNumBonds()) {
    NimBLEAddress baddr = NimBLEDevice::getBondedAddress(0);
    Serial.printf("Starting advertising with bonded address %s\n", baddr.toString().c_str());
    pServer->getAdvertising()->setAdvertisementType(1);
    pAdvertising->start(0 ,nullptr, &baddr);
  } else {
    pAdvertising->addServiceUUID("ABCD");
    Serial.println("Starting normal advertising");
    pServer->getAdvertising()->setAdvertisementType(2);
    pAdvertising->start();
  }
}

void loop() {
}

If you would like to use this for your own testing please do and report back your findings. Don't bother trying against apple devices though 😄.

I have verified with a sniffer that this does indeed advertise directed to the address provided on an esp32 dev board using release v1.4.2.

@sanastasiou
Copy link

Hm, ok so assuming iOS doesn't support this and whitelisting is broken in ESP32 completely plus switching on / off encrypted attributes on the fly also has issues I see no way to provide truly secure communications between ESP and all kinds of smartphones.

So I am guessing time to switch ECU on my side. But still thanks a lot for all your efforts @h2zero and @KlausMu !

@h2zero
Copy link
Owner

h2zero commented Nov 5, 2024

@sanastasiou Yes, the esp32 is not an option for your purposes and I would suggest a c3 or s3 variant.

@sanastasiou
Copy link

@h2zero thanks, is whitelisting supposed to be working in S3/C3 ?

@h2zero
Copy link
Owner

h2zero commented Nov 5, 2024

Yes @sanastasiou, they have controller based privacy and can manage the RPA's correctly.

@h2zero
Copy link
Owner

h2zero commented Nov 5, 2024

@sanastasiou
Copy link

Just rewrote the code and tested with C3 device.

Works with iOS perfectly.

Does not work with Android. Android does not see the device after the bonded address is added to the whitelist. Without whitelist everything works. With active whitelist nothing works for android.

As usual used master branch of the library plus latest espressiff/arduino versions.

Can provide logs if you're interested.

@sanastasiou
Copy link

Hm, after doing a reset on the ECU it works with both devices. Investigating further.

@sanastasiou
Copy link

sanastasiou commented Nov 5, 2024

So found the Android issue.. During bonding a check is done to see if the device has bonded already in the authentication callback.

This code which works in 1.4.2 now fails:

            const auto peerDevices = fPServer->getPeerDevices();
            std::cout << "ServiceHandler::isConnectedDeviceBonded() - Number of peer devices: " << peerDevices.size() << std::endl;
            const auto isBonded = ((fPServer->getConnectedCount() == 1U) && (peerDevices.size() == 1U) && fPServer->getPeerInfo(peerDevices[0]).isBonded());

This is printed:

ServiceHandler::isConnectedDeviceBonded() - Number of peer devices: 1
E NimBLEServer: No peer at index 1

Although the bonding itself succeeds. After a reset the whitelist is populated with the bonded addresses and everything works fine. It just doesn't work exactly after bonding.. but I can work around this. In any case, it's inconsistent behavior that peer size is 1 but peer[0] doesn't exist.

@h2zero
Copy link
Owner

h2zero commented Nov 6, 2024

Can you share more code details?

@sanastasiou
Copy link

Sure. So in the server callbacks:

        void ServerCallbacks::onAuthenticationComplete(NimBLEConnInfo& connInfo)
        {
            const std::lock_guard<std::mutex> guard(fBleController.getMutex());
            const bool isBonded = (connInfo.isBonded());
            const bool isEncrypted = (connInfo.isEncrypted());
            const bool isAuthenticated = (connInfo.isAuthenticated());
            std::cout << std::boolalpha << "ServerCallbacks::onAuthenticationComplete() - encrypted: " << isEncrypted << std::endl;
            std::cout << std::boolalpha << "ServerCallbacks::onAuthenticationComplete() - authenticated: " << isAuthenticated << std::endl;
            std::cout << std::boolalpha << "ServerCallbacks::onAuthenticationComplete() - bonded: " << isBonded << std::endl;

            if(fBleController.isPairing())
            {
                /** Check that encryption was successful, if not we disconnect the client */
                if(!(isBonded && isEncrypted && isAuthenticated))
                {
                    std::cout << "Encrypt connection failed with client: " << connInfo.getAddress().toString() <<  std::endl;
                    return;
                }
                else
                {
                    //bug in stack, this returns false here..
                    //std::cout << std::boolalpha << "ServerCallbacks::onAuthenticationComplete() - bonded: " << NimBLEDevice::isBonded(connInfo.getIdAddress().toString()) << std::endl;
                    fBleController.expirePairingTimer();
                }
            }
            else
            {
                if(isBonded && isEncrypted && isAuthenticated)
                {
                    std::cout << std::boolalpha << "ServerCallbacks::onAuthenticationComplete() - Client with address: " << connInfo.getAddress().toString() << " is authenticated." << std::endl;
                }
                else
                {
                    std::cout << "Attempted to authenticate while not pairing -> reject connection." << std::endl;
                    NimBLEDevice::getServer()->disconnect(connInfo.getConnHandle());
                    return;
                }
            }
        }

When this is true and we are bonding for the first time there is no way to actually check if the device has bonded outside of this function.

This used to work with the code above ( peer[0].isBonded ) with 1.4.2 but it now no longer works because for whatever reason size == 1 but peer[0] does not exist. Also disconnecting at this line: fBleController.expirePairingTimer(); before the authentication callback returns, results in the bonded device not being stored as bonded at all.

What I now do is, blindly accepting that a device has bonded when the authentication callback is called without looking at bonded devices / peer info at all. Tested that and it works fine.

I also have a question. When I bond with my Android phone and then disconnect I can see during scan the device and I can reconnect ( ✔️ ).

If I scan however with my unbonded iOS phone I still can see the device ( ❌ ) but I cannot connect to it ( ✔️ ).

I would have thought that when whitelisting for scan response and connect are both true, the device would be invisible to non whitelisted peers. Is that not the case? fNimbleAdvertising->setScanFilter(true, true);

@h2zero
Copy link
Owner

h2zero commented Nov 6, 2024

So, what appears to happen is that callback is called before the bonded state is set for the peer in the stack, all good though, just check for authentication and or encryption. Whitelist does not prevent other devices from reporting them as seen, only prevents any connection attempt, which scan response is a pseudo connection, so it cannot get the data in the scan response and cannot connect. All devices can see your advertisements, BLE wouldn't work otherwise, but you can control the sensitive data, though not encrypted in the scan response.

@KlausMu
Copy link
Author

KlausMu commented Nov 6, 2024

Still investigating, using this for testing:

#include <NimBLEDevice.h>

class serverCB : public NimBLEServerCallbacks {
...

...
I have verified with a sniffer that this does indeed advertise directed to the address provided on an esp32 dev board using release v1.4.2.

I am not sure if this code snippet can reveal the problem. My test case was always

  • bond the ESP32 to Windows (can only work if the ESP32 behaves as something like keyboard, mouse, audio device, DLNA etc.) - no authentication is involved
  • disconnect the ESP32
  • start direct advertisement to this specific Windows machine

With your code snippet no available device at all shows up in Windows when trying to pair, so I cannot even bond the device.

As said earlier, Android and iOS are not really my use case, but of course I can contribute test results.
To me currently only devices not using RPA are relevant, like a Fire TV, NVIDIA shield or other such devices. But even with these there are currently some problems. I was not able to test all these devices until now. But only the combination ESP32 + Windows / 2.0.0 seems to be reliable until now, Android and iOS not.

What I am more afraid of:

  • why does ESP32 + Windows with NimBLE 1.4.2 not work?
  • why does ESP32-S3 + Windows does not work at all (neither 1.4.2 nor 2.0.0)

If there is anything I can do to provide more test results, please let me know. Currently I don't know what to do.

@sanastasiou
Copy link

Just adding my 2 cents here for master branch.

Although whitelisting works with C3 with master I have observed following behavior.

  • Bond device
  • Start advertising with whitelist -> works
  • Bond other device and start advertising with whitelist -> works
  • Abruptly disconnect android device by e.g. power off of the device
  • Advertisement still works, but upon connection the peer device receives a broken service without any characteristics and cannot do anything.

I could reproduce the latter in master branch 100%. I switched back to 1.4.2 where the above scenario works perfectly without any issues. Imo master branch needs some work.

@h2zero
Copy link
Owner

h2zero commented Nov 10, 2024

There are no changes in the master branch that would affect this as far as I can see.

@sanastasiou
Copy link

Well I could reproduce it 100% tried several times. The exact same steps work flawlessly in 1.4.2. Also I am pretty sure something change from 1.4.2 to master regarding NV storage of bonds.

Having bonded devices stored into NV from master and going back to 1.4.2 results 100% reproducible exception during loading of bonds.

@h2zero
Copy link
Owner

h2zero commented Nov 10, 2024

Could you please try with the release/1.5 branch? I wonder if this is a core issue.

@sanastasiou
Copy link

@h2zero sure, the bonds thing or the other issue as well? The latter would take some time.

@h2zero
Copy link
Owner

h2zero commented Nov 11, 2024

Let's start with the bonds issue as that seems to be what changed with the master branch

@KlausMu
Copy link
Author

KlausMu commented Nov 11, 2024

Having bonded devices stored into NV from master and going back to 1.4.2 results 100% reproducible exception during loading of bonds.

In my tests, both directions did not work: going to master (silent failure) and going back to 1.4.2 (crash). please see this issue

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

4 participants