h2zero / NimBLE-Arduino

A fork of the NimBLE library structured for compilation with Arduino, for use with ESP32, nRF5x.
https://h2zero.github.io/NimBLE-Arduino/
Apache License 2.0
667 stars 138 forks source link

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

Open KlausMu opened 3 months ago

KlausMu commented 3 months ago

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:

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 commented 3 weeks ago

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 commented 1 week ago

@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 commented 1 week ago

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 commented 1 week ago

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 commented 1 week ago

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 commented 1 week ago

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 commented 1 week ago

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 commented 1 week ago

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 commented 1 week ago

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