From ca7186867a838e69e06ca956a947caeac61932e4 Mon Sep 17 00:00:00 2001 From: chrome builder Date: Thu, 18 Jun 2026 21:32:48 +0200 Subject: [PATCH 1/8] Add BluetoothRemoteGATTCharacteristic.getMTU() 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. --- index.bs | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/index.bs b/index.bs index 3649684..52a8203 100644 --- a/index.bs +++ b/index.bs @@ -3479,6 +3479,7 @@ peripheral's service. Promise getDescriptor(BluetoothDescriptorUUID descriptor); Promise> getDescriptors(optional BluetoothDescriptorUUID descriptor); + Promise getMTU(); Promise readValue(); Promise writeValue(BufferSource value); Promise writeValueWithResponse(BufferSource value); @@ -3624,6 +3625,38 @@ getDescriptors(descriptor) method retrieves a list of > child type="GATT Descriptor") +
+The getMTU() + method exposes the ATT_MTU that bounds the payload of outgoing PDUs +to the peer on the connection containing this characteristic. When invoked, it +MUST run the following steps: + +1. Let |global| be [=this=]'s [=relevant global object=]. +1. Let |gatt| be [=this=].{{BluetoothRemoteGATTCharacteristic/service}}.{{BluetoothRemoteGATTService/device}}.{{BluetoothDevice/gatt}}. +1. If |gatt|.{{BluetoothRemoteGATTServer/connected}} + is `false`, return [=a promise rejected with=] a "{{NetworkError}}" + {{DOMException}} and abort these steps. +1. Let |characteristic| be [=this=].{{[[representedCharacteristic]]}}. +1. If |characteristic| is `null`, return [=a promise rejected with=] an + "{{InvalidStateError}}" {{DOMException}} and abort these steps. +1. Return a |gatt|-[=connection-checking wrapper=] around [=a new promise=] + |promise| and run the following steps [=in parallel=]: + 1. Let |mtu| be the ATT_MTU negotiated for the [=ATT Bearer=] that + carries |characteristic|, as established by the [=Exchange MTU=] + procedure. This is the smaller of the local Host's transmit ATT_MTU + and the peer's receive ATT_MTU. + + Note: When no MTU exchange has occurred, |mtu| is the default + ATT_MTU of 23 octets. + 1. [=Queue a global task=] on the [=Bluetooth task source=] given + |global| to perform the following steps: + 1. If |promise| is not in |gatt|.{{[[activeAlgorithms]]}}, [=reject=] + |promise| with a "{{NetworkError}}" {{DOMException}} and abort + these steps. + 1. [=Resolve=] |promise| with |mtu|. + +
+
The readValue() method, when invoked, MUST run the following steps: From 913b577a68b0e3f3d4432eda3c46cc13405cac58 Mon Sep 17 00:00:00 2001 From: chrome builder Date: Fri, 19 Jun 2026 15:38:33 +0200 Subject: [PATCH 2/8] Expose maxWriteWithoutResponseSize attribute instead of getMTU() Address review feedback: rename getMTU() to a synchronous maxWriteWithoutResponseSize attribute (inspired by CoreBluetooth) that returns ATT_MTU - 3, the largest payload writeValueWithoutResponse() can send in a single PDU. Since platforms return a cached value without performing I/O, a plain attribute is more honest than a promise. Document that the value can change while connected and should be read each time rather than cached. --- index.bs | 56 ++++++++++++++++++++++++++++---------------------------- 1 file changed, 28 insertions(+), 28 deletions(-) diff --git a/index.bs b/index.bs index 52a8203..9f0b788 100644 --- a/index.bs +++ b/index.bs @@ -3476,10 +3476,10 @@ peripheral's service. readonly attribute UUID uuid; readonly attribute BluetoothCharacteristicProperties properties; readonly attribute DataView? value; + readonly attribute unsigned short maxWriteWithoutResponseSize; Promise getDescriptor(BluetoothDescriptorUUID descriptor); Promise> getDescriptors(optional BluetoothDescriptorUUID descriptor); - Promise getMTU(); Promise readValue(); Promise writeValue(BufferSource value); Promise writeValueWithResponse(BufferSource value); @@ -3504,6 +3504,19 @@ Heart Rate Measurement characteristic. value is the currently cached characteristic value. This value gets updated when the value of the characteristic is read or updated via a notification or indication. + +maxWriteWithoutResponseSize is the largest [=byte sequence=], in +octets, that {{BluetoothRemoteGATTCharacteristic/writeValueWithoutResponse()}} +can send to the peer in a single ATT PDU without it being fragmented or +rejected. It is derived from the ATT_MTU negotiated for the connection that +carries this characteristic. + +Note: This value can change while the device is connected, for example because +an [=Exchange MTU=] procedure completes after [=service discovery=]. On some +platforms an updated value can arrive late, after services are resolved. +Applications SHOULD read +{{BluetoothRemoteGATTCharacteristic/maxWriteWithoutResponseSize}} each time it +is needed rather than caching it.
Instances of {{BluetoothRemoteGATTCharacteristic}} are created with the @@ -3625,36 +3638,23 @@ getDescriptors(descriptor) method retrieves a list of > child type="GATT Descriptor") -
-The getMTU() - method exposes the ATT_MTU that bounds the payload of outgoing PDUs -to the peer on the connection containing this characteristic. When invoked, it -MUST run the following steps: +
+The maxWriteWithoutResponseSize +getter steps are: -1. Let |global| be [=this=]'s [=relevant global object=]. -1. Let |gatt| be [=this=].{{BluetoothRemoteGATTCharacteristic/service}}.{{BluetoothRemoteGATTService/device}}.{{BluetoothDevice/gatt}}. -1. If |gatt|.{{BluetoothRemoteGATTServer/connected}} - is `false`, return [=a promise rejected with=] a "{{NetworkError}}" - {{DOMException}} and abort these steps. -1. Let |characteristic| be [=this=].{{[[representedCharacteristic]]}}. -1. If |characteristic| is `null`, return [=a promise rejected with=] an - "{{InvalidStateError}}" {{DOMException}} and abort these steps. -1. Return a |gatt|-[=connection-checking wrapper=] around [=a new promise=] - |promise| and run the following steps [=in parallel=]: - 1. Let |mtu| be the ATT_MTU negotiated for the [=ATT Bearer=] that - carries |characteristic|, as established by the [=Exchange MTU=] - procedure. This is the smaller of the local Host's transmit ATT_MTU - and the peer's receive ATT_MTU. +1. Let |mtu| be the ATT_MTU negotiated for the [=ATT Bearer=] that carries + [=this=].{{[[representedCharacteristic]]}}, as established by the + [=Exchange MTU=] procedure. This is the smaller of the local Host's + transmit ATT_MTU and the peer's receive ATT_MTU. - Note: When no MTU exchange has occurred, |mtu| is the default - ATT_MTU of 23 octets. - 1. [=Queue a global task=] on the [=Bluetooth task source=] given - |global| to perform the following steps: - 1. If |promise| is not in |gatt|.{{[[activeAlgorithms]]}}, [=reject=] - |promise| with a "{{NetworkError}}" {{DOMException}} and abort - these steps. - 1. [=Resolve=] |promise| with |mtu|. + Note: When no MTU exchange has occurred, |mtu| is the default ATT_MTU of + 23 octets. +1. Return |mtu| − 3. + Note: 3 octets are subtracted to account for the ATT Write Command header + (1 octet opcode and 2 octet attribute handle), leaving the space available + for the attribute value in a single PDU.
From 908cb93bddb44e53721c201d2b9a342581544912 Mon Sep 17 00:00:00 2001 From: chrome builder Date: Fri, 19 Jun 2026 15:50:14 +0200 Subject: [PATCH 3/8] Add maxwritewithoutresponsesizechanged event Pair the live maxWriteWithoutResponseSize attribute with an event so applications can either read the current value at write time or react to changes pushed by the UA (e.g. a late Exchange MTU renegotiation). The event does not carry the value; listeners read the attribute, keeping a single source of truth. Add the onmaxwritewithoutresponsesizechanged handler to CharacteristicEventHandlers and a "Responding to MTU Changes" algorithm that fires the event. --- index.bs | 50 +++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 49 insertions(+), 1 deletion(-) diff --git a/index.bs b/index.bs index 9f0b788..3d7c57e 100644 --- a/index.bs +++ b/index.bs @@ -3516,7 +3516,8 @@ an [=Exchange MTU=] procedure completes after [=service discovery=]. On some platforms an updated value can arrive late, after services are resolved. Applications SHOULD read {{BluetoothRemoteGATTCharacteristic/maxWriteWithoutResponseSize}} each time it -is needed rather than caching it. +is needed rather than caching it. To be notified when it changes, listen for +the {{maxwritewithoutresponsesizechanged}} event.
Instances of {{BluetoothRemoteGATTCharacteristic}} are created with the @@ -4395,6 +4396,18 @@ interface participate in a tree. an active GATT connection is lost. +
+ + maxwritewithoutresponsesizechanged + +
+
+ Fired on a {{BluetoothRemoteGATTCharacteristic}} when its + {{BluetoothRemoteGATTCharacteristic/maxWriteWithoutResponseSize}} + changes, for example after an + [=Exchange MTU=] procedure renegotiates the ATT_MTU for the connection. +
+
serviceadded
@@ -4516,6 +4529,36 @@ it must perform the following steps:
+### Responding to MTU Changes ### {#mtu-change-events} + +
+When the ATT_MTU negotiated for an [=ATT Bearer=] changes, for example because +the UA receives the result of an [=Exchange MTU=] procedure, the UA must +perform the following steps for each Characteristic carried by that +[=ATT Bearer=]: + +1. For each |bluetoothGlobal| whose [=Bluetooth tree=] contains a + {{BluetoothRemoteGATTCharacteristic}} representing the Characteristic, + [=queue a global task=] on the [=Bluetooth task source=] given + |bluetoothGlobal|'s [=relevant global object=] to do the following + sub-steps: + 1. Let characteristicObject be the + {{BluetoothRemoteGATTCharacteristic}} in the Bluetooth tree + rooted at bluetoothGlobal that represents the + Characteristic. + 1. If characteristicObject + .service.device.gatt.{{BluetoothRemoteGATTServer/connected}} + is `false`, abort these sub-steps. + 1. Fire an event named {{maxwritewithoutresponsesizechanged}} with + its bubbles attribute initialized to true at + characteristicObject. + + Note: The new value is not carried on the event. Listeners read + characteristicObject.{{BluetoothRemoteGATTCharacteristic/maxWriteWithoutResponseSize}} + to obtain the current value. + +
+
### Responding to Service Changes ### {#service-change-events} @@ -4706,6 +4749,7 @@ Changed characteristic, it MUST perform the following steps. [SecureContext] interface mixin CharacteristicEventHandlers { attribute EventHandler oncharacteristicvaluechanged; + attribute EventHandler onmaxwritewithoutresponsesizechanged; }; @@ -4713,6 +4757,10 @@ Changed characteristic, it MUST perform the following steps. oncharacteristicvaluechanged is an Event handler IDL attribute for the {{characteristicvaluechanged}} event type. + +onmaxwritewithoutresponsesizechanged is an Event handler IDL +attribute for the {{maxwritewithoutresponsesizechanged}} event type. + [SecureContext] interface mixin BluetoothDeviceEventHandlers { From 9976a74dffd37998e45054148b4929785fc2eb4f Mon Sep 17 00:00:00 2001 From: chrome builder <helmut@januschka.com> Date: Fri, 19 Jun 2026 15:58:13 +0200 Subject: [PATCH 4/8] Fix bikeshed errors for maxWriteWithoutResponseSize Reference the attribute from its getter-steps block instead of defining a second <dfn>, which removes the duplicate id and the conflicting local attribute definition. Drop the unresolved "service discovery" autolink and move the trailing note out of the list so the closing tag parses as valid Markdown. --- index.bs | 21 ++++++++++----------- 1 file changed, 10 insertions(+), 11 deletions(-) diff --git a/index.bs b/index.bs index 3d7c57e..fe3bd14 100644 --- a/index.bs +++ b/index.bs @@ -3512,7 +3512,7 @@ rejected. It is derived from the ATT_MTU negotiated for the connection that carries this characteristic. Note: This value can change while the device is connected, for example because -an [=Exchange MTU=] procedure completes after [=service discovery=]. On some +an [=Exchange MTU=] procedure completes after service discovery. On some platforms an updated value can arrive late, after services are resolved. Applications SHOULD read {{BluetoothRemoteGATTCharacteristic/maxWriteWithoutResponseSize}} each time it @@ -3640,22 +3640,21 @@ getDescriptors(<var>descriptor</var>)</dfn></code> method retrieves a list of </div> <div algorithm="BluetoothRemoteGATTCharacteristic maxWriteWithoutResponseSize"> -The <dfn attribute -for="BluetoothRemoteGATTCharacteristic">maxWriteWithoutResponseSize</dfn> -getter steps are: +The {{BluetoothRemoteGATTCharacteristic/maxWriteWithoutResponseSize}} getter +steps are: 1. Let |mtu| be the ATT_MTU negotiated for the [=ATT Bearer=] that carries [=this=].{{[[representedCharacteristic]]}}, as established by the [=Exchange MTU=] procedure. This is the smaller of the local Host's - transmit ATT_MTU and the peer's receive ATT_MTU. - - Note: When no MTU exchange has occurred, |mtu| is the default ATT_MTU of - 23 octets. + transmit ATT_MTU and the peer's receive ATT_MTU. When no MTU exchange has + occurred, |mtu| is the default ATT_MTU of 23 octets. 1. Return |mtu| &minus; 3. - Note: 3 octets are subtracted to account for the ATT Write Command header - (1 octet opcode and 2 octet attribute handle), leaving the space available - for the attribute value in a single PDU. +<div class="note"> +Note: 3 octets are subtracted to account for the ATT Write Command header +(1 octet opcode and 2 octet attribute handle), leaving the space available +for the attribute value in a single PDU. +</div> </div> <div algorithm="BluetoothRemoteGATTCharacteristic readValue()"> From 0881c702292b67822152a8c29970362a7db40512 Mon Sep 17 00:00:00 2001 From: Helmut Januschka <helmut@januschka.com> Date: Tue, 23 Jun 2026 02:26:22 +0200 Subject: [PATCH 5/8] Name the characteristic loop variable in MTU change steps Introduce a "characteristic" variable in the "for each Characteristic" clause and reference it in the sub-steps instead of repeating "the Characteristic", per review feedback. --- index.bs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/index.bs b/index.bs index fe3bd14..5ff2247 100644 --- a/index.bs +++ b/index.bs @@ -4533,18 +4533,18 @@ it must perform the following steps: <div algorithm="mtu changes"> When the ATT_MTU negotiated for an [=ATT Bearer=] changes, for example because the UA receives the result of an [=Exchange MTU=] procedure, the UA must -perform the following steps for each <a>Characteristic</a> carried by that -[=ATT Bearer=]: +perform the following steps for each <a>Characteristic</a> +<var>characteristic</var> carried by that [=ATT Bearer=]: 1. For each |bluetoothGlobal| whose [=Bluetooth tree=] contains a - {{BluetoothRemoteGATTCharacteristic}} representing the <a>Characteristic</a>, + {{BluetoothRemoteGATTCharacteristic}} representing <var>characteristic</var>, [=queue a global task=] on the [=Bluetooth task source=] given |bluetoothGlobal|'s [=relevant global object=] to do the following sub-steps: 1. Let <var>characteristicObject</var> be the {{BluetoothRemoteGATTCharacteristic}} in the <a>Bluetooth tree</a> - rooted at <var>bluetoothGlobal</var> that represents the - <a>Characteristic</a>. + rooted at <var>bluetoothGlobal</var> that represents + <var>characteristic</var>. 1. If <code><var>characteristicObject</var> .service.device.gatt.{{BluetoothRemoteGATTServer/connected}}</code> is `false`, abort these sub-steps. From eb349b13721e7dc0395e7eb17f5eebc3314501cf Mon Sep 17 00:00:00 2001 From: Helmut Januschka <helmut@januschka.com> Date: Tue, 23 Jun 2026 02:31:40 +0200 Subject: [PATCH 6/8] Spec maxwritewithoutresponsesizechanged as a best-effort event Some platforms (notably CoreBluetooth) do not surface ATT_MTU changes to the UA, so the event cannot fire there. Document it as a best-effort hint to re-read maxWriteWithoutResponseSize rather than an authoritative or guaranteed signal, and tell applications not to rely on it as the only way to observe the value. --- index.bs | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/index.bs b/index.bs index 5ff2247..95a0882 100644 --- a/index.bs +++ b/index.bs @@ -3516,8 +3516,12 @@ an [=Exchange MTU=] procedure completes after service discovery. On some platforms an updated value can arrive late, after services are resolved. Applications SHOULD read {{BluetoothRemoteGATTCharacteristic/maxWriteWithoutResponseSize}} each time it -is needed rather than caching it. To be notified when it changes, listen for -the {{maxwritewithoutresponsesizechanged}} event. +is needed rather than caching it. The {{maxwritewithoutresponsesizechanged}} +event is provided as a best-effort notification, but it is not guaranteed to +fire on every platform: some operating systems do not expose a notification +when the ATT_MTU changes. Applications MUST NOT rely on the event as the only +way to observe the current value, and SHOULD treat it purely as a hint to +re-read the attribute. </div> Instances of {{BluetoothRemoteGATTCharacteristic}} are created with the @@ -4405,6 +4409,8 @@ interface <a>participate in a tree</a>. {{BluetoothRemoteGATTCharacteristic/maxWriteWithoutResponseSize}} <a href="#mtu-change-events">changes</a>, for example after an [=Exchange MTU=] procedure renegotiates the ATT_MTU for the connection. + This event is best-effort: it is not guaranteed to fire on platforms + that do not surface ATT_MTU changes to the UA. </dd> <dt> @@ -4536,6 +4542,13 @@ the UA receives the result of an [=Exchange MTU=] procedure, the UA must perform the following steps for each <a>Characteristic</a> <var>characteristic</var> carried by that [=ATT Bearer=]: +Note: These steps fire the {{maxwritewithoutresponsesizechanged}} event on a +best-effort basis. Some platforms do not notify the UA when the ATT_MTU +changes, in which case the event does not fire even though +{{BluetoothRemoteGATTCharacteristic/maxWriteWithoutResponseSize}} may return a +different value on a subsequent read. The event is therefore a hint to +re-read the attribute, not an authoritative signal. + 1. For each |bluetoothGlobal| whose [=Bluetooth tree=] contains a {{BluetoothRemoteGATTCharacteristic}} representing <var>characteristic</var>, [=queue a global task=] on the [=Bluetooth task source=] given From cdf67eeb1ea4d83e76707b26211f9cf54862effd Mon Sep 17 00:00:00 2001 From: Helmut Januschka <helmut@januschka.com> Date: Wed, 24 Jun 2026 10:06:13 +0200 Subject: [PATCH 7/8] Make maxWriteWithoutResponseSize read the current ATT_MTU Drop the cached internal-slot model in favor of an on-demand getter that reflects the ATT_MTU at read time. This way a fresh read returns the current value even on platforms (such as CoreBluetooth) that do not surface ATT_MTU changes via the maxwritewithoutresponsesizechanged event. The event remains a convenience notification fired when the UA observes a change; the attribute is the authoritative source. --- index.bs | 47 ++++++++++++++++++++++++++--------------------- 1 file changed, 26 insertions(+), 21 deletions(-) diff --git a/index.bs b/index.bs index 95a0882..cc5e7d0 100644 --- a/index.bs +++ b/index.bs @@ -3514,14 +3514,15 @@ carries this characteristic. Note: This value can change while the device is connected, for example because an [=Exchange MTU=] procedure completes after service discovery. On some platforms an updated value can arrive late, after services are resolved. -Applications SHOULD read -{{BluetoothRemoteGATTCharacteristic/maxWriteWithoutResponseSize}} each time it -is needed rather than caching it. The {{maxwritewithoutresponsesizechanged}} -event is provided as a best-effort notification, but it is not guaranteed to -fire on every platform: some operating systems do not expose a notification -when the ATT_MTU changes. Applications MUST NOT rely on the event as the only -way to observe the current value, and SHOULD treat it purely as a hint to -re-read the attribute. +Reading +{{BluetoothRemoteGATTCharacteristic/maxWriteWithoutResponseSize}} always +returns the current value, so applications SHOULD read it each time it is +needed rather than caching it in a variable. As a convenience, the UA also +[=fires an event=] named {{maxwritewithoutresponsesizechanged}} when it +observes a change, so applications that batch large transfers can react to an +increase instead of polling. Not every platform surfaces ATT_MTU changes, so +the event is not guaranteed to fire; the attribute remains the authoritative +source and a fresh read returns the current value regardless. </div> Instances of {{BluetoothRemoteGATTCharacteristic}} are created with the @@ -3647,17 +3648,20 @@ getDescriptors(<var>descriptor</var>)</dfn></code> method retrieves a list of The {{BluetoothRemoteGATTCharacteristic/maxWriteWithoutResponseSize}} getter steps are: -1. Let |mtu| be the ATT_MTU negotiated for the [=ATT Bearer=] that carries - [=this=].{{[[representedCharacteristic]]}}, as established by the +1. Let |mtu| be the ATT_MTU currently negotiated for the [=ATT Bearer=] that + carries [=this=].{{[[representedCharacteristic]]}}, as established by the [=Exchange MTU=] procedure. This is the smaller of the local Host's transmit ATT_MTU and the peer's receive ATT_MTU. When no MTU exchange has occurred, |mtu| is the default ATT_MTU of 23 octets. 1. Return |mtu| &minus; 3. <div class="note"> -Note: 3 octets are subtracted to account for the ATT Write Command header -(1 octet opcode and 2 octet attribute handle), leaving the space available -for the attribute value in a single PDU. +Note: The getter reflects the ATT_MTU at the time it is read, so a fresh read +always returns the current value even on platforms that do not surface +ATT_MTU changes via the {{maxwritewithoutresponsesizechanged}} event. +3 octets are subtracted to account for the ATT Write Command header (1 octet +opcode and 2 octet attribute handle), leaving the space available for the +attribute value in a single PDU. </div> </div> @@ -4409,8 +4413,9 @@ interface <a>participate in a tree</a>. {{BluetoothRemoteGATTCharacteristic/maxWriteWithoutResponseSize}} <a href="#mtu-change-events">changes</a>, for example after an [=Exchange MTU=] procedure renegotiates the ATT_MTU for the connection. - This event is best-effort: it is not guaranteed to fire on platforms - that do not surface ATT_MTU changes to the UA. + The UA fires this event when it observes such a change. Platforms that do + not surface ATT_MTU changes do not fire this event, but reading the + attribute still returns the current value. </dd> <dt> @@ -4542,12 +4547,12 @@ the UA receives the result of an [=Exchange MTU=] procedure, the UA must perform the following steps for each <a>Characteristic</a> <var>characteristic</var> carried by that [=ATT Bearer=]: -Note: These steps fire the {{maxwritewithoutresponsesizechanged}} event on a -best-effort basis. Some platforms do not notify the UA when the ATT_MTU -changes, in which case the event does not fire even though -{{BluetoothRemoteGATTCharacteristic/maxWriteWithoutResponseSize}} may return a -different value on a subsequent read. The event is therefore a hint to -re-read the attribute, not an authoritative signal. +Note: This event is a convenience notification. The +{{BluetoothRemoteGATTCharacteristic/maxWriteWithoutResponseSize}} getter always +reflects the current ATT_MTU when read, so reading it after a change returns the +new value whether or not this event fires. Platforms that do not surface +ATT_MTU changes to the UA never run these steps and so never fire the event, +but a fresh read of the attribute still returns the current value. 1. For each |bluetoothGlobal| whose [=Bluetooth tree=] contains a {{BluetoothRemoteGATTCharacteristic}} representing <var>characteristic</var>, From 621091e2b559a2ecafa1013dfbaeb689d598ff37 Mon Sep 17 00:00:00 2001 From: Helmut Januschka <helmut@januschka.com> Date: Wed, 24 Jun 2026 18:07:06 +0200 Subject: [PATCH 8/8] Couple maxWriteWithoutResponseSize updates to the change event Restore a stored value that is established at characteristic discovery and only updated when the UA observes an ATT_MTU change, at which point the maxwritewithoutresponsesizechanged event fires. This matches the cached Chromium implementation: the value never changes without the event firing, and platforms with no MTU-change notification (CoreBluetooth) keep a constant value and never fire the event. Replaces the earlier live-read wording that implied the value could change without an event. --- index.bs | 78 ++++++++++++++++++++++++++++++++++---------------------- 1 file changed, 47 insertions(+), 31 deletions(-) diff --git a/index.bs b/index.bs index cc5e7d0..16d8f7a 100644 --- a/index.bs +++ b/index.bs @@ -3514,15 +3514,14 @@ carries this characteristic. Note: This value can change while the device is connected, for example because an [=Exchange MTU=] procedure completes after service discovery. On some platforms an updated value can arrive late, after services are resolved. -Reading -{{BluetoothRemoteGATTCharacteristic/maxWriteWithoutResponseSize}} always -returns the current value, so applications SHOULD read it each time it is -needed rather than caching it in a variable. As a convenience, the UA also -[=fires an event=] named {{maxwritewithoutresponsesizechanged}} when it -observes a change, so applications that batch large transfers can react to an -increase instead of polling. Not every platform surfaces ATT_MTU changes, so -the event is not guaranteed to fire; the attribute remains the authoritative -source and a fresh read returns the current value regardless. +Whenever the UA observes that the ATT_MTU has changed it updates this value and +[=fires an event=] named {{maxwritewithoutresponsesizechanged}}, so the two stay +in sync: the value never changes without the event firing. Some platforms do +not surface ATT_MTU changes to the UA at all; on those platforms the value +stays constant after it is first established and the event never fires. +Applications that batch large transfers can listen for +{{maxwritewithoutresponsesizechanged}} to react to an increase instead of +re-reading the attribute on every write. </div> Instances of {{BluetoothRemoteGATTCharacteristic}} are created with the @@ -3542,6 +3541,20 @@ Instances of {{BluetoothRemoteGATTCharacteristic}} are created with the Characteristic has been removed or otherwise invalidated. </td> </tr> + <tr> + <td><dfn>\[[maxWriteWithoutResponseSize]]</dfn></td> + <td><code>20</code></td> + <td> + The value, in octets, exposed by + {{BluetoothRemoteGATTCharacteristic/maxWriteWithoutResponseSize}}. It is + established when the {{[[representedCharacteristic]]}} is discovered and + updated only when the UA learns of an ATT_MTU change for the + [=ATT Bearer=] that carries it, at which point the + {{maxwritewithoutresponsesizechanged}} event is fired. The initial value + of <code>20</code> corresponds to the default ATT_MTU of 23 octets minus + the 3-octet ATT Write Command header. + </td> + </tr> <tr> <td><dfn>\[[automatedCharacteristicReadResponse]]</dfn></td> <td><code>"not-expected"</code></td> @@ -3648,20 +3661,15 @@ getDescriptors(<var>descriptor</var>)</dfn></code> method retrieves a list of The {{BluetoothRemoteGATTCharacteristic/maxWriteWithoutResponseSize}} getter steps are: -1. Let |mtu| be the ATT_MTU currently negotiated for the [=ATT Bearer=] that - carries [=this=].{{[[representedCharacteristic]]}}, as established by the - [=Exchange MTU=] procedure. This is the smaller of the local Host's - transmit ATT_MTU and the peer's receive ATT_MTU. When no MTU exchange has - occurred, |mtu| is the default ATT_MTU of 23 octets. -1. Return |mtu| &minus; 3. +1. Return [=this=].{{[[maxWriteWithoutResponseSize]]}}. <div class="note"> -Note: The getter reflects the ATT_MTU at the time it is read, so a fresh read -always returns the current value even on platforms that do not surface -ATT_MTU changes via the {{maxwritewithoutresponsesizechanged}} event. -3 octets are subtracted to account for the ATT Write Command header (1 octet -opcode and 2 octet attribute handle), leaving the space available for the -attribute value in a single PDU. +Note: The stored value is the ATT_MTU negotiated for the [=ATT Bearer=] that +carries the characteristic, minus 3 octets. The 3 octets account for the ATT +Write Command header (1 octet opcode and 2 octet attribute handle), leaving the +space available for the attribute value in a single PDU. The value is +established when the characteristic is discovered and only changes when the UA +observes an ATT_MTU change; see [[#mtu-change-events]]. </div> </div> @@ -4413,9 +4421,9 @@ interface <a>participate in a tree</a>. {{BluetoothRemoteGATTCharacteristic/maxWriteWithoutResponseSize}} <a href="#mtu-change-events">changes</a>, for example after an [=Exchange MTU=] procedure renegotiates the ATT_MTU for the connection. - The UA fires this event when it observes such a change. Platforms that do - not surface ATT_MTU changes do not fire this event, but reading the - attribute still returns the current value. + The UA fires this event whenever it updates the value, so platforms that + never surface ATT_MTU changes (and therefore never change the value) also + never fire this event. </dd> <dt> @@ -4547,13 +4555,17 @@ the UA receives the result of an [=Exchange MTU=] procedure, the UA must perform the following steps for each <a>Characteristic</a> <var>characteristic</var> carried by that [=ATT Bearer=]: -Note: This event is a convenience notification. The -{{BluetoothRemoteGATTCharacteristic/maxWriteWithoutResponseSize}} getter always -reflects the current ATT_MTU when read, so reading it after a change returns the -new value whether or not this event fires. Platforms that do not surface -ATT_MTU changes to the UA never run these steps and so never fire the event, -but a fresh read of the attribute still returns the current value. - +Note: The UA only runs these steps when it observes an ATT_MTU change. Updating +the stored value and firing the {{maxwritewithoutresponsesizechanged}} event +happen together, so the value never changes without the event firing. Platforms +that do not surface ATT_MTU changes to the UA never run these steps, so on those +platforms the value stays constant after it is first established and the event +never fires. + +1. Let <var>newSize</var> be the ATT_MTU now negotiated for that + [=ATT Bearer=] &minus; 3. This is the smaller of the local Host's transmit + ATT_MTU and the peer's receive ATT_MTU, less the 3-octet ATT Write Command + header. 1. For each |bluetoothGlobal| whose [=Bluetooth tree=] contains a {{BluetoothRemoteGATTCharacteristic}} representing <var>characteristic</var>, [=queue a global task=] on the [=Bluetooth task source=] given @@ -4566,6 +4578,10 @@ but a fresh read of the attribute still returns the current value. 1. If <code><var>characteristicObject</var> .service.device.gatt.{{BluetoothRemoteGATTServer/connected}}</code> is `false`, abort these sub-steps. + 1. If <var>characteristicObject</var>.{{[[maxWriteWithoutResponseSize]]}} + equals <var>newSize</var>, abort these sub-steps. + 1. Set <var>characteristicObject</var>.{{[[maxWriteWithoutResponseSize]]}} + to <var>newSize</var>. 1. <a>Fire an event</a> named {{maxwritewithoutresponsesizechanged}} with its <code>bubbles</code> attribute initialized to <code>true</code> at <var>characteristicObject</var>.