deviceplug / btleplug

Rust Cross-Platform Host-Side Bluetooth LE Access Library

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

MTU Size Negotiation / Retrieval in the public API

Nyrox opened this issue · comments

Feature Description

It would be very useful to be able to negotiate or at least get the negotiated transfer size for a peripheral.
We are very aware that getting this right in a cross-platform manner is quite a pain and other libraries like Noble for NodeJS have just straight up chosen to not support this, but even a platform-specific API to retrieve it on some platforms would be better than nothing.

We have a custom serial protocol built on top of a characteristic pair and settling for the minimum MTU of 20 makes some operations unreasonably slow.

Would love some input on this, I am also open to writing some of the code (specifically for corebluetooth) if this is deemed a worthwhile feature. Thank you.

I gathered some information regarding this:

  • Windows - GattSession.MaxPduSize
    • Have not used.
  • macOS / iOS - CBPeripheral.maximumWriteValueLength
    • I've used peripheral.maximumWriteValueLength(for: .withoutResponse) reliably in the past. .withResponse has some issues. See here.
  • Linux
    • Does Bluez support this?
  • Android - BluetoothGatt.requestMtu
    • The way I understand it and have used it in the past, you call requestMtu with the largest you can support (e.g. 512), and it will negotiate the min(host.maxMtu, periph.maxMtu) and call onMtuChanged with the new mtu size.

Seems like Windows and macOS/iOS both automatically negotiate, whilst Android requires you to request it. We could have a bit of code that performs the request on the Android version automatically so that the btleplug interface could be consistent. No clue what to do for Linux.

Bluez seems to support MTU retrieval since version 5.62: bluez/bluez@1abf140

Relevant discussion: bluez/bluez#199

Bluez makes it a property of a characteristic, because with Bluetooth 5.2 Enhanced Attribute Protocol it can actually be different.. Probably a good idea to reflect this in btleplug API too?

I find it kind of silly that they implemented it for a feature that has no wide support yet, and they neglect implementing it on the device level that every existing API uses and all peripheral devices < Bluetooth 5.2 uses.

In any case, I think there should be 2 different methods to get MTU. The device level negotiated MTU is done through the ATT_EXCHANGE_MTU_REQ PDU when using the fixed L2CAP CID for LE Attribute protocol (an unenhanced ATT bearer), which hasn't changed with the introduction of EATT. However, when creating multiple "enhanced ATT bearers" via the L2CAP_CREDIT_BASED_CONNECTION_REQ PDU (dynamic CIDs) it allows you to specify the MTU size for these channels.

commented

Ok, so progress is happening, which is awesome, but we've got a few issues here.

  • #305 and #317 are conflicting. The fact that #317 didn't call out #305 makes me think they may not have seen there was already some progress there (and they may not have seen this discussion.
  • I'd like to bring in corebluetooth if possible also, but that implementation may be on me. I need to read up on @robbym's research in the earlier posts here but I'm swamped so that may be a bit.
  • lol bluez gonna bluez.

Any update on this? We also need to be able to retrieve/negotiate the MTU size on Linux, Android, and Windows if that's possible.

Also pinging on this feature. It would bump performance up a bunch having the larger mtu.

Sorry I haven't touched this in a year. Work had paused our Bluetooth project this passed year so I haven't worked on anything Bluetooth related. We should be resuming progress soon and I can continue where I left off.

As @qdot points out in #317, there is conflict in the way something like Windows and Android gets the MTU. It would probably better to have the interface be an async method, and just immediately return in the Windows case. @MnlPhlp suggestion also has merit, we could have code automatically request the mtu on Android during the connection process to emulate what Windows (and presumably iOS) does. I need to really dive into bluez's approach and really see how they view the world.

In any case, we'd need to nail down the API before I move forward on the implementation(s). Will try to find time to see if there are other cross-platform solutions with MTU support.

commented

I can reach out to the simpleble people and see, been talking to them lately anyways.

I checked their bluez backend. Looks like they're just grabbing it from the first characteristic in the first service they see lol. Link to code

uint16_t PeripheralBase::mtu() {
    if (!is_connected()) return 0;

    for (auto bluez_service : device_->services()) {
        for (auto bluez_characteristic : bluez_service->characteristics()) {
            // The value provided by Bluez includes an extra 3 bytes from the GATT header
            // which needs to be removed.
            return bluez_characteristic->mtu() - 3;
        }
    }
    return 0;
}

Yup, that's basically how it's offered by Bluez. In my opinion the parameter should have been added to the peripheral instead of the characteristic, but it is what it is.

The MTU is something that is negotiated by the peripheral where the central does its best to accommodate it, but once it's set it shouldn't be changed.