Skip to content

Commit

Permalink
Be more rigorous about the connection algorithm
Browse files Browse the repository at this point in the history
and BluetoothGATTRemoteServer life cycle.
  • Loading branch information
jyasskin committed Mar 16, 2015
1 parent c759e74 commit 9d630d3
Show file tree
Hide file tree
Showing 2 changed files with 113 additions and 12 deletions.
86 changes: 74 additions & 12 deletions index.html
Expand Up @@ -549,10 +549,10 @@ <h2>Global Bluetooth device properties</h2>
</li>

<li>
An <dfn>LE-connected flag</dfn>,
indicating whether the device is connected to the UA over the LE physical transport.
This flag is initially false.
The <a>Connection Modes and Procedures</a> create and destroy LE connections.
An <a>ATT Bearer</a>, over which all GATT communication happens.
The <a>ATT Bearer</a> is created by procedures described in
"Connection Establishment" under <a>GAP Interoperability Requirements</a>.
It is disconnected in ways [[BLUETOOTH41]] isn't entirely clear about.
</li>

<li>
Expand Down Expand Up @@ -864,7 +864,33 @@ <h2><a>BluetoothDevice</a></h2>

<dt>Promise&lt;BluetoothGATTRemoteServer> connectGATT()</dt>
<dd>
Establishes a connection to the device.
The UA MUST return <a>a new promise</a> <var>promise</var> and
run the following steps <a>in parallel</a>:
<ol>
<li>
If <code>this@[[\representedDevice]]</code> has no <a>ATT Bearer</a>,
attempt to create one using the procedures described
in "Connection Establishment" under <a>GAP Interoperability Requirements</a>.
</li>
<li>
If this attempt fails,
reject <var>promise</var> with a <a>NetworkError</a> and abort these steps.
</li>
<li>
<a>Queue a task</a> to do the following steps:
<ol>
<li>
If <code>this.gattServer</code> is <code>null</code>,
set it to a new <a>BluetoothGATTRemoteServer</a> instance
with its <code>device</code> attribute initialized to <code>this</code>
and its <code>connected</code> attribute initialized to <code>true</code>.
</li>
<li>
Resolve <var>promise</var> with <code>this.gattServer</code>.
</li>
</ol>
</li>
</ol>
</dd>
</dl>

Expand Down Expand Up @@ -1069,11 +1095,37 @@ <h2>Identifying Services, Characteristics, and Descriptors</h2>
<h2><a>BluetoothGATTRemoteServer</a></h2>

<dl title="interface BluetoothGATTRemoteServer : ServiceEventHandlers" class="idl">
<dt>Promise&lt;void> disconnect()</dt>
<dt>readonly attribute BluetoothDevice device</dt>
<dd>
Closes the site's connection to the device.
Note that this will not always destroy the physical link itself,
since there may be other sites with open connections.
The device running this server.
</dd>

<dt>readonly attribute boolean connected</dt>
<dd>
True while this <a>script execution environment</a>
is connected to <code>this.device</code>.
This can be false while the UA is physically connected.
</dd>

<dt>void disconnect()</dt>
<dd>
The UA MUST perform the following steps:
<ol>
<li>
Set <code>this.connected</code> to <code>false</code>.
</li>
<li>
<a>In parallel</a>:
if, for all <a>script execution environment</a>s,
all <a>BluetoothDevice</a>s <code><var>device</var></code>
with <code><var>device</var>@[[\representedDevice]]</code>
the <a>same device</a> as <code>this.device@[[\representedDevice]]</code>,
<code><var>device</var>.gattServer === null</code>
or <code>!<var>device</var>.gattServer.connected</code>,
the UA MAY destroy
<code><var>device</var>@[[\representedDevice]]</code>'s <a>ATT Bearer</a>.
</li>
</ol>
</dd>

<dt>Promise&lt;BluetoothGATTService> getPrimaryService(BluetoothServiceUuid service)</dt>
Expand Down Expand Up @@ -2753,7 +2805,10 @@ <h2>Terminology and Conventions</h2>
<ol>
<li value="2">Profile Overview
<ol>
<li value="4"><dfn>Profile Fundamentals</dfn></li>
<li value="4">
<dfn>Profile Fundamentals</dfn>,
defines the <dfn>ATT Bearer</dfn>
</li>
<li value="5">Attribute Protocol
<ol>
<li value="2"><dfn>Attribute Caching</dfn></li>
Expand Down Expand Up @@ -2814,11 +2869,16 @@ <h2>Terminology and Conventions</h2>
<li value="14"><dfn>Procedure Timeouts</dfn></li>
</ol>
</li>
<li value="6">GAP Interoperability Requirements
<li value="6"><dfn>GAP Interoperability Requirements</dfn>
<ol>
<li value="1">BR/EDR GAP Interoperability Requirements
<ol>
<li value="1">Connection Establishment</li>
</ol>
</li>
<li value="2">LE GAP Interoperability Requirements
<ol>
<li value="1"><dfn>Connection Establishment</dfn></li>
<li value="1">Connection Establishment</li>
</ol>
</li>
</ol>
Expand Down Expand Up @@ -2974,6 +3034,8 @@ <h2>Terminology and Conventions</h2>
<ul>
<li>Errors
<ul>
<li><a href="https://heycam.github.io/webidl/#aborterror"
><dfn>AbortError</dfn></a></li>
<li><a href="https://heycam.github.io/webidl/#invalidmodificationerror"
><dfn>InvalidModificationError</dfn></a></li>
<li><a href="https://heycam.github.io/webidl/#networkerror"
Expand Down
39 changes: 39 additions & 0 deletions rationale.md
Expand Up @@ -78,3 +78,42 @@ and the [Android API](https://developer.android.com/reference/android/bluetooth/
doesn't document which it prefers when both are available.
Neither gives enough control to
implement a web standard that gives the web page the ability to choose.

## Why do we have just one connection instead of one per call to connect()?

If each call to `BluetoothDevice.connect()` returned a separate `BluetoothGATTRemoteServer` instance,
which could be disconnected independently of other concurrent instances,
then components of a page could do their own lifecycle management,
disconnecting when they were finished with a device.
This implies separate Service, Characteristic, and Descriptor instances for each connection because
we'd want _some_ way to get a `BluetoothGATTRemoteServer` instance given a `BluetoothGATTService`,
but we wouldn't want to return a different one than
the component-using-the-Service controlled the lifetime of.
However, because events bubble, this would result in a separate, e.g.,
`characteristicvaluechanged` event to arrive at the `BluetoothDevice` for each connection.
To avoid that, we have only one connection per device.

## Why is `BluetoothGATTRemoteServer` created lazily?

If/when Bluetooth devices support more than GATT communication,
this may allow implementations to avoid creating data structures that aren't used.

## Why are GATT connection and disconnection asymmetric?

`BluetoothGATTRemoteServer.disconnect()` exists
to release the current script's claim on the GATT connection,
but there's no parallel `BluetoothGATTRemoteServer.connect()`.
Users just have to call `BluetoothDevice.connectGATT()` again.
If we made users call `BluetoothDevice.gattServer.connect()`,
it would still need to return a `Promise<BluetoothGATTRemoteServer>` (WebBluetoothCG/web-bluetooth#65),
and would just be more characters in the common case.
We could provide both, but they'd be two ways of spelling the same operation,
which seems more confusing than worth it.

## Why doesn't `BluetoothGATTRemoteServer.disconnect()` return a `Promise`?

Disconnection can easily take effect synchronously
(if it is physically asynchronous, simply discard events that came in after it was requested).
We're also not signaling any error conditions from the disconnection:
if other operations were in flight, they'll return a NetworkError,
which still allows the possibility that they took effect.

0 comments on commit 9d630d3

Please sign in to comment.