Skip to content

Add BluetoothRemoteGATTCharacteristic.getMTU()#672

Open
hjanuschka wants to merge 1 commit into
WebBluetoothCG:mainfrom
hjanuschka:add-characteristic-getmtu
Open

Add BluetoothRemoteGATTCharacteristic.getMTU()#672
hjanuschka wants to merge 1 commit into
WebBluetoothCG:mainfrom
hjanuschka:add-characteristic-getmtu

Conversation

@hjanuschka

@hjanuschka hjanuschka commented Jun 18, 2026

Copy link
Copy Markdown

Hi folks 👋

I've been working on the Chromium implementation for exposing the
negotiated ATT MTU to web pages (crrev.com/c/7879985)

What this adds

A new getMTU() method on BluetoothRemoteGATTCharacteristic:

Promise<unsigned short> getMTU();

It resolves with the ATT_MTU negotiated for the connection that carries
the characteristic, i.e. the smaller of the local Host's transmit
ATT_MTU and the peer's receive ATT_MTU, as established by the Exchange
MTU procedure. When no exchange has happened it's the default of 23
octets.

Why I want this

Today there's no way for a site to know how big a single write can be.
The practical pain is writeValueWithoutResponse(): if you hand it more
than ATT_MTU - 3 bytes, the write either fails or gets silently
chopped depending on the platform, and the page has no way to tell ahead
of time. People end up hard-coding 20 (the old default payload) to be
safe, which leaves a lot of throughput on the table on modern devices
that happily negotiate 247+.

Exposing the MTU lets sites chunk their writes to fit a single PDU and
avoid unnecessary round trips. This has been asked for a few times over
the years (40265040 / 40686244 / 40163619 on the Chromium side).

Open questions

  • I put this on the characteristic to match where reads/writes live, but
    the MTU is really a per-connection property. Happy to move it to
    BluetoothRemoteGATTServer if folks feel that's a cleaner home.
  • Naming: getMTU() vs an mtu attribute. I went with a method since
    the value is only meaningful while connected and resolving a promise
    felt more honest than a sync attribute that can throw/stale, but I'm
    not attached to it.

Preview | Diff

Expose the negotiated ATT_MTU for the connection carrying a
characteristic via a new getMTU() method, returning the smaller of the
local Host's transmit ATT_MTU and the peer's receive ATT_MTU as
established by the Exchange MTU procedure.

Knowing the MTU lets sites size writeValueWithoutResponse() payloads to
fit a single PDU and avoid fragmentation.
@dlech

dlech commented Jun 18, 2026

Copy link
Copy Markdown
Contributor
  • I put this on the characteristic to match where reads/writes live

I think it has to be there because of the way BlueZ implements it. In theory, other OSes could add support for per-characteristic MTU in the future as well.

  • Naming: getMTU() vs an mtu attribute.

Since the actual useful information we want is "what is the max size can I use for write without response", I would use a name inspired from CoreBluetooth instead, e.g. maxWriteWithoutResponseSize. This would return MTU - 3. This way all users don't have to know to subtract 3 to get the actual value that is useful.

Since most OSes don't actually perform any I/O to get the number (they just return whatever number they currently know), making it a promise seems misleading.

I also know from experience with maintaining the Bleak Python library that MTU changes can come late (after connecting and resolving services), particularly on Windows. So it could actually make more sense to make this an event rather than an attribute. Or we just need to document that the value can change, so it should be read every time that it is used rather than caching it in some variable in the user application.

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

Successfully merging this pull request may close these issues.

2 participants