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

API which provides the negotiated MTU is required #383

Open
ghost opened this issue Jan 24, 2018 · 48 comments
Open

API which provides the negotiated MTU is required #383

ghost opened this issue Jan 24, 2018 · 48 comments

Comments

@ghost
Copy link

ghost commented Jan 24, 2018

Bluetooth mesh runs over Bluetooth LE and defines a proxy node and associated proxy protocol to allow standard (non-mesh) Bluetooth LE clients to talk to a mesh network.

The proxy protocol requires you to know the MTU which was negotiated behind the scenes by client and server so that packets can be filled and segmentation used when messages exceed the size of the MTU. MTU negotiation happens silently behind the scenes in Web Bluetooth and there's no API that lets you find out what the outcome of that negotiation was. This is needed for the reasons given. Please consider adding an API such as getNeogitatedMTU().

Thanks

--
[edit: Related chromium Issue 1164621: WebBluetooth: not implemented "Exchange MTU" step]

@Emill
Copy link

Emill commented Feb 7, 2020

Why doesn't this exist? It should be quite simple to specify and implement, right?

@reillyeon
Copy link
Contributor

Assuming that this information is consistently available across all platforms where Web Bluetooth is supported then this should be implementable.

For prioritization, it's important for us to understand what applications this feature is blocking. @bluetooth-mdw already mentioned Bluetooth Mesh. @Emill, what application are you trying to port to Web Bluetooth?

@Emill
Copy link

Emill commented Feb 7, 2020

There are a bunch of applications where the specifications assume the MTU is known.
For my case I'd like to implement Flic 2: https://github.com/50ButtonsEach/flic2-documentation/wiki/Flic-2-Protocol-Specification, where the fragmentation/segmentation can be much more efficient if the MTU is known (especially if LE Data Length extension is supported). Otherwise one have to be on the safe side and assume 23.

On Mac, Windows and BlueZ, the MTU is hardcoded and always negotiated by the system when a connection starts before GATT is activated for the user. On Android it's customizable but there's as far as I know no reason not to try setting it to 517 (maximum supported).

However, now when you mention it, the remote device may select a lower MTU than requested, and I'm not sure if that value can be retrieved from all platforms. :/
Mac: https://developer.apple.com/documentation/corebluetooth/cbperipheral/1620312-maximumwritevaluelength
Android: onMtuChanged
BlueZ: ?
Windows: Maybe https://docs.microsoft.com/en-us/uwp/api/windows.devices.bluetooth.genericattributeprofile.gattsession.maxpdusize#Windows_Devices_Bluetooth_GenericAttributeProfile_GattSession_MaxPduSize can be used?

@ghost
Copy link
Author

ghost commented Feb 10, 2020

Knowing the MTU is one aspect of this. Being able to negotiate an increase in MTU size is what the ExchangeMTU ATT PDU is really for though and an increased MTU can have a massive positive impact on data rates (i.e. throughput or latency, depending on perspective). For example, transferring 100K of data using write commands but with the default MTU of 23, could take around 8 seconds whereas transferring the same data in the same way with an MTU of 247 would probably take less than a second.

@Emill
Copy link

Emill commented Feb 10, 2020

I think it's sane that the system automatically negotiates the highest MTU the Bluetooth stack can handle at the beginning of the connection.

Android is currently broken both in its API and its implementation and doesn't follow the Bluetooth spec. It says the following about Exchange MTU:

This subprocedure shall only be initiated once during a connection.

However, every app can call requestMtu with its own MTU value and the stack will happily send out a new ATT_EXCHANGE_MTU_REQ with the app-supplied value every time requestMtu is called.

@andrejhronco
Copy link

Hi,
Has there been any movement on this in the last 2 years? It seems like an important feature for web bluetooth on Android. I'm dealing with this issue on a project now.

@scheib
Copy link
Contributor

scheib commented Aug 20, 2020 via email

@renu285
Copy link

renu285 commented Oct 6, 2020

This feature could be really useful for a lot of Applications.

@Sunbreak
Copy link

There are a bunch of applications where the specifications assume the MTU is known.
For my case I'd like to implement Flic 2: https://github.com/50ButtonsEach/flic2-documentation/wiki/Flic-2-Protocol-Specification, where the fragmentation/segmentation can be much more efficient if the MTU is known (especially if LE Data Length extension is supported). Otherwise one have to be on the safe side and assume 23.

On Mac, Windows and BlueZ, the MTU is hardcoded and always negotiated by the system when a connection starts before GATT is activated for the user. On Android it's customizable but there's as far as I know no reason not to try setting it to 517 (maximum supported).

However, now when you mention it, the remote device may select a lower MTU than requested, and I'm not sure if that value can be retrieved from all platforms. :/
Mac: https://developer.apple.com/documentation/corebluetooth/cbperipheral/1620312-maximumwritevaluelength
Android: onMtuChanged
BlueZ: ?
Windows: Maybe https://docs.microsoft.com/en-us/uwp/api/windows.devices.bluetooth.genericattributeprofile.gattsession.maxpdusize#Windows_Devices_Bluetooth_GenericAttributeProfile_GattSession_MaxPduSize can be used?

@Emill You're right

We've implement QuickBlue plugin of Flutter on Android/iOS/macOS/Windows

API declaration:
https://github.com/woodemi/quick_blue/blob/f24cf23b5fe1ed12dabb41f947b948a08569e67d/quick_blue_platform_interface/lib/quick_blue_platform_interface.dart#L55

@Sunbreak
Copy link

Sunbreak commented Nov 23, 2020

QuickBlue is extracted from NotepadCore: https://github.com/woodemi/notepad_core

requestMtu was implemented on Web in NotepadCore as well: https://github.com/woodemi/notepad_core/blob/cbf649b44576b808a5fd5d9ce0a2e592492e84a6/notepad_core_web/lib/notepad_core_web.dart#L105

Though there isn't MTU API in WebBluetooth, both Chrome & Edge(Chromiume) works with MTU <= 104 on macOS, or with expextedMtu <= 517 on Windows 10+ (Our device work with 247 after negotiation)

@Sunbreak
Copy link

Flutter on macOS negotiate with our device by 247

https://github.com/woodemi/quick_blue/blob/f24cf23b5fe1ed12dabb41f947b948a08569e67d/quick_blue_macos/macos/Classes/QuickBlueMacosPlugin.swift#L114

Have on idea why Chromiume works only with 104

Maybe related: #284 (comment)

@foldl
Copy link

foldl commented Feb 1, 2021

Could we have this API in the year 2021?

@scheib
Copy link
Contributor

scheib commented Feb 10, 2021

Acknowledging this. Google staffing & priorities don't have this being implemented now. Clear product / partner descriptions help identify the need when this is compared to other work items. Chromium is also open source, other contributors can submit patches.

@foldl
Copy link

foldl commented Mar 4, 2021

A survey on the availability of upstream APIs:

  • Win32/UWP: No.
  • Android: Yes.
  • iOS/MacOS: No. But there is func maximumWriteValueLength(for type: CBCharacteristicWriteType) -> Int
  • BlueZ: Yes (IMO).

So, this API is not easy.

@dlech
Copy link
Contributor

dlech commented Mar 4, 2021

* Win32/UWP: No.

Win10 has MaxPduSize

* BlueZ: Yes (IMO).

I think this is a no. The "mtu" property is server-only, which means it is not available to clients (which is what WebBluetooth is). Or is there some other way to get it?

@ghost
Copy link
Author

ghost commented Mar 4, 2021

@dlech I may have misunderstood what you mean by "server-only", but the maximum value of MTU supported by each of the client and server is exchanged and then "Both devices then use the minimum of these exchanged values for all further
communication" (from the Bluetooth core specification) so, it's definitely a property that both client and server use.

@foldl
Copy link

foldl commented Mar 4, 2021

Thanks for @dlech 's correction. I thought that I had searched win32 API thoroughly.

  • Win32 (BLE API introduced in Windows 8): No
  • UWP (< Build 15063): No
  • UWP (Build 15063 or newer): GattSession.MaxPduSize

@dlech
Copy link
Contributor

dlech commented Mar 4, 2021

it's definitely a property that both client and server use.

Yes, but I don't think there is a D-Bus API to read this number.

@ghost
Copy link
Author

ghost commented Mar 4, 2021

@dlech OK, I understand what you meant now. Thanks.

@dlech
Copy link
Contributor

dlech commented Sep 20, 2021

Yes, but I don't think there is a D-Bus API to read this number.

BlueZ issue requesting the feature: bluez/bluez#199

@dlech
Copy link
Contributor

dlech commented Sep 20, 2021

An interesting question brought up in the BlueZ issue is "should the MTU value be per-device or per-attribute"? The feature most people are requesting is "I want the MTU that is negotiated when the device connects", but the argument was made that with Bluetooth 5.2 Enhanced Attribute Protocol (EATT), it might make more sense to have a per-attribute property (and that this could even be different for tx vs rx).

@morgan-wild
Copy link

Hi everyone!
Any news? With a ridiculous MTU fixed to 20 usable bytes on Android, there is just no way to use bluetooth of some apps.

I'm working with IOT devices and PWAs. Since the mixed content policy changed and self signed certificates stopped to be recognized by Chrome, it is also impossible to communicate over Wifi (http or websocket).

To resume, there is no way to communicate with a IOT device from a PWA because of the choices of Google :s

@Sunbreak
Copy link

Sunbreak commented Nov 4, 2021

Hi everyone! Any news? With a ridiculous MTU fixed to 20 usable bytes on Android, there is just no way to use bluetooth of some apps.

You need requestConnectionPriority on Android: https://github.com/woodemi/notepad_core/blob/b0e329f3d6e02f14f8a0e5e48a6ddb48e026b658/notepad_core/android/src/main/kotlin/io/woodemi/notepad_core/NotepadCorePlugin.kt#L124-L136

            "requestMtu" -> {
                connectGatt?.requestMtu(call.argument<Int>("expectedMtu")!!)
                result.success(null)
            }
            "requestConnectionPriority" -> {
                val bleConnectionPriority = call.argument<String>("bleConnectionPriority")!!
                connectGatt?.requestConnectionPriority(when (bleConnectionPriority) {
                    "high" -> BluetoothGatt.CONNECTION_PRIORITY_HIGH
                    "lowPower" -> BluetoothGatt.CONNECTION_PRIORITY_LOW_POWER
                    else -> BluetoothGatt.CONNECTION_PRIORITY_BALANCED
                })
                result.success(null)
            }

@beaufortfrancois
Copy link
Member

The trickiest part is to make sure we can provide this functionality to all platforms where Web Bluetooth is supported: Android, Chrome OS, (Linux,) macOS, and Windows.

@foldl
Copy link

foldl commented Nov 4, 2021

Hi everyone! Any news? With a ridiculous MTU fixed to 20 usable bytes on Android, there is just no way to use bluetooth of some apps.

I'm working with IOT devices and PWAs. Since the mixed content policy changed and self signed certificates stopped to be recognized by Chrome, it is also impossible to communicate over Wifi (http or websocket).

To resume, there is no way to communicate with a IOT device from a PWA because of the choices of Google :s

You can let the device tell JS app the negotiated MTU through the value of a characteristic.

@morgan-wild
Copy link

Thank you for your replies.

I'm trying to solve it using the web api only for a PWA (no Android native code used in an hybrid app).

The Android platform allows to make a MTU request: If I open my web app and connect a bluetooth device, I will receive data limited to 20 bytes. Meanwhile, if I open a native app to make the MTU request (I use nRFConnect) and I back to the web app, I receive the entire data.
The Bluetooth Web API is great but a method to call this 'simple' request is missing.

So yes, a workarround may be to listen these short notifications and read a characteritic to get the entire data (the read method can get the value through multiple packet, the MTU is not a problem) but it is no efficient.

I'm sorry for my bad english :)

@beaufortfrancois
Copy link
Member

One thing that may be worth doing is adding a hint when connecting the GATT server to request a higher MTU when possible.

const device = await navigator.bluetooth.requestDevice({ filters: [{ services: [0x1234] }] });
 
 // Try requesting for a larger ATT MTU so that more information can be exchanged per transmission.
 const server = await device.gatt.connect({ largeMtuHint: true });
...

It would be up to the implementation to honor this hint as much as possible based on the platform.
On Android, it would call mBluetoothGatt.requestMtu(512) for instance. On macOS, it would be a no-op. Etc.

What do you think @reillyeon?

@beaufortfrancois
Copy link
Member

Another idea would be to simply update our Android implementation and always call mBluetoothGatt.requestMtu(512) when connecting to the GATT server if that doesn't cause any harm.

@reillyeon Shall I do this behind a flag so that we can try it out in the wild?

@Emill
Copy link

Emill commented Nov 4, 2021

517 is a more common MTU since then we can always send the maximum sized characteristic value length (512 bytes) regardless of packet type. But note my comment at #383 (comment).

@morgan-wild
Copy link

Another idea would be to simply update our Android implementation and always call mBluetoothGatt.requestMtu(512) when connecting to the GATT server if that doesn't cause any harm.

@reillyeon Shall I do this behind a flag so that we can try it out in the wild?

That is what some operating systems are doing. I noticed Windows make by itself a request to increase the MTU (255 bytes).

@beaufortfrancois
Copy link
Member

@Emill Thanks. requestMtu(517) will be then! What about your comment though? Shall we not do this or is there a way to monitor MTU changes to maintain the one we've set?

@morgan-wild
Copy link

morgan-wild commented Nov 4, 2021

517 sounds great 😍

@Emill
Copy link

Emill commented Nov 4, 2021

Last time I checked, Android will automatically send the onMtuChanged callback immediately after onConnectionStateChange (when indicating connected), if there is already an established MTU. This can for example happen when another app on the phone is already connected and the MTU exchange has already taken place. The documentation for onMtuChanged indeed hints this can happen.

In this case, to be able to distinguish this automatic callback from a manual one triggered as a response to requestMtu, and to not accidentally executing more than one request in parallel (which Android doesn't like), a flow that works is to do the following:

  1. After onConnectionStateChange indicates the connection is complete, start a service discovery.
  2. Now, either onMtuChanged will be called first or onServicesDiscovered will be called first.
  3. If onMtuChanged is called first, note down this MTU value and use it for the rest of the connection.
  4. If onServicesDiscovered is called first, only in this case send requestMtu. First when onMtuChanged has been called, Android's Bluetooth stack will start to accept other GATT operations from the application. (Sending requests while an MTU request is pending will fail).

I haven't tested if onMtuChanged will be triggered as well if another app calls requestMtu while connected. In this case and the mtu value changes from a previously negotiated one, Android's behaviour is broken since the BLE spec only allows one mtu exchange for the duration of a connection.

@beaufortfrancois
Copy link
Member

beaufortfrancois commented Nov 4, 2021

Thanks for all these details @Emill.
I've just updated my Chromium build with a naive approach that works locally. See https://chromium-review.googlesource.com/c/chromium/src/+/3260011

I'll have to update it with your findings though.

@beaufortfrancois
Copy link
Member

FYI This change is now in Chrome Canary.
Please give it a try and let me know if that works for you.

See https://twitter.com/quicksave2k/status/1460550201064763402 as well

@morgan-wild
Copy link

morgan-wild commented Nov 20, 2021

Hi everyone

I made some basic tests and it works on my Android device with the Canary!

@dlech
Copy link
Contributor

dlech commented Nov 20, 2021

This issue has veered off topic. The Android MTU negotiation issue should probably be discussed at https://crbug.com/1164621. The original issue here is a request for a browser API to retrieve the negotiated MTU on all platforms.

@dlech
Copy link
Contributor

dlech commented Nov 20, 2021

Yes, but I don't think there is a D-Bus API to read this number.

BlueZ issue requesting the feature: bluez/bluez#199

An update on this... a D-Bus API for this was added recently.

https://github.com/bluez/bluez/blob/6ea642f6ef2e2d5486aec092a2596f24e60bca3e/doc/gatt-api.txt#L297-L301

Unlike other platforms where the API provides a per-device value, BlueZ provides this on a per-characteristic basis. The rational being that Bluetooth 5.2 Enhanced Attribute Protocol (EATT) could result in characteristics having different individual MTU values.

So I propose that the Web Bluetooth API should also be a property/method on the characteristic object. The implementation on Linux/ChromOS can use the new BlueZ API while the Windows and Mac implementations can just return the device-level value for now and could be modified in the future if Microsoft or Apple adds new APIs for EATT.

Also, I think it makes sense to perhaps have an API like Apple's maximumWriteValueLength() rather than returning the MTU. If the problem we are solving with this is "what is the maximum number bytes can I pass to writeWithoutResponse() without getting an error", then it makes sense to return that number directly rather than expecting consumers of the API to know that you have to subtract 3 from the MTU to arrive at the correct answer.

@Emill
Copy link

Emill commented Nov 20, 2021

If we read the Bluetooth standard, we also see that MTU is also important when receiving notifications, since the MTU then tells how long notifications might be. If a client notices it retrieves a maximum length notification, it might want to issue a read operation to read the rest of the value. So the MTU is not only useful when writing. To continue on your route, in that case it might be an option to add a flag to the notification event that this is a maximum sized notification. Same could possibly be useful for read, in case the remote device does not support Long reads, then we know the value has been truncated to at most x bytes.

@tetap
Copy link

tetap commented Aug 25, 2022

Hasn't the problem been solved yet?

@EdwinFairchild
Copy link

Hello from 2023 , and no the problem has not been addressed.

@morgan-wild
Copy link

I still need it :s

@chengweih001
Copy link
Contributor

We will use https://bugs.chromium.org/p/chromium/issues/detail?id=1435103 to track this from an implementation perspective.

@CSC-Sendance
Copy link

We would also need a simple getNegotiatedMTU() or similar.

@geeqib23
Copy link

hello from 2024. Still unfixed it seems :((((

@morgan-wild
Copy link

morgan-wild commented Apr 25, 2024

That is so bad 😭

@geeqib23
Copy link

@morgan-wild Is there any workaround for this? I desperately need it. How can i transfer 20 bytes over and over again to transfer lots of continuos data. I can't figure it out. Would really appreciate some help.

@morgan-wild
Copy link

Nothing for the moment with the stable version or Chrome. I tried Canary one time during this discussion because a modified version with a bigger MTU was published and it worked.

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