Web Bluetooth: Exposing the Negotiated ATT MTU

⚑ Chromium πŸ”§ C++ / Web Bluetooth πŸ‘€ Helmut Januschka

The 20-byte wall, and giving the web a way to ask how big its packets can actually be

Status: 🚧 In Progress (WIP)

The Problem

Web Bluetooth has a long-standing rough edge around the ATT MTU - the maximum size of a single GATT packet.

Three issues describe the same underlying gap from different angles:

The 20-byte number is not arbitrary: it is the default ATT MTU of 23 bytes minus the 3-byte ATT header. Until the MTU is negotiated up, that is all you get per packet.

The Background

Part of this story predates my involvement. The "Exchange MTU" gap (40163619) was addressed for Android by FranΓ§ois Beaufort, who landed the larger-MTU request and later removed the experimental flag:

That makes the platform negotiate a bigger MTU, but it does not tell the web page what the negotiated value ended up being. Without that, authors are still guessing - chunking writes to 20 bytes "to be safe" even when the link negotiated a much larger MTU. The corresponding spec work is tracked in WebBluetoothCG/web-bluetooth#383.

The Fix

IN REVIEW Web Bluetooth: Expose negotiated ATT MTU via getNegotiatedMTU()

The change adds BluetoothRemoteGATTCharacteristic.getNegotiatedMTU(), giving pages a direct way to read the negotiated MTU so they can size writes accordingly instead of assuming 20 bytes. It wires up platform backends across the board:

This is still a work in progress - the API surface and the per-platform backends are being iterated on alongside the spec discussion - but the goal is to close all three issues at once: stop the silent failures on Windows, build on the Exchange MTU work, and finally expose the negotiated value to the web.

Test Rig and Demos

Verifying an MTU API across four platforms needs a real peripheral, not a mock. I built an interactive sampler backed by ESP32-C3 firmware (ESP32C3_All_BLE_Tester, advertised as dino tester) that echoes back the received length and peer MTU, so silent truncation is visible.

Launch Chrome with the feature flag so the new API is available:

--enable-features=NewBLEGattSessionHandling,WebBluetooth

Three demos exercise the three issues:

  1. Progressive JPEG streamer (#40163619 + #40265040) - calls getNegotiatedMTU(), then pulls an embedded progressive JPEG chunk-by-chunk over notifications; the browser repaints scans blurry-to-sharp as bytes arrive. Payloads are capped at 244 bytes to dodge the Chromium notification cap on macOS/CoreBluetooth.
  2. MTU conformance suite (#40265040) - eight checks: the method exists, returns a Promise, resolves to an integer in spec range, matches the device-reported peer MTU, is idempotent, survives concurrent calls, and actually drives the maximum write size.
  3. Write-size probe (#40686244) - writes payloads at 1, 19, 20, 21, 22, ... up to 512 bytes through both writeValueWithResponse() and writeValueWithoutResponse(), and flags any silent truncation. On Windows with an effective MTU of 517, both APIs now accept up to 512 bytes - exactly the case from the original bug.

There are also pre-built Chrome for Android APKs from CL 7879985 on the sampler page for testing on a real device.