Skip to content

Commit

Permalink
Merge pull request #502 from google/gbg/extended-advertising-terminat…
Browse files Browse the repository at this point in the history
…ion-reverse

support out of order advertising set termination / connection events
  • Loading branch information
barbibulle committed Jun 18, 2024
2 parents 7912231 + df5fc2d commit 32a41a8
Show file tree
Hide file tree
Showing 3 changed files with 68 additions and 9 deletions.
31 changes: 28 additions & 3 deletions bumble/device.py
Original file line number Diff line number Diff line change
Expand Up @@ -1856,6 +1856,7 @@ def __init__(

# Extended advertising.
self.extended_advertising_sets: Dict[int, AdvertisingSet] = {}
self.connecting_extended_advertising_sets: Dict[int, AdvertisingSet] = {}

# Legacy advertising.
# The advertising and scan response data, as well as the advertising interval
Expand Down Expand Up @@ -4009,14 +4010,28 @@ def on_advertising_set_termination(
)
return

if not (connection := self.lookup_connection(connection_handle)):
logger.warning(f'no connection for handle 0x{connection_handle:04x}')
if connection := self.lookup_connection(connection_handle):
# We have already received the connection complete event.
self._complete_le_extended_advertising_connection(
connection, advertising_set
)
return

# Associate the connection handle with the advertising set, the connection
# will complete later.
logger.debug(
f'the connection with handle {connection_handle:04X} will complete later'
)
self.connecting_extended_advertising_sets[connection_handle] = advertising_set

def _complete_le_extended_advertising_connection(
self, connection: Connection, advertising_set: AdvertisingSet
) -> None:
# Update the connection address.
connection.self_address = (
advertising_set.random_address
if advertising_set.advertising_parameters.own_address_type
if advertising_set.random_address is not None
and advertising_set.advertising_parameters.own_address_type
in (OwnAddressType.RANDOM, OwnAddressType.RESOLVABLE_OR_RANDOM)
else self.public_address
)
Expand Down Expand Up @@ -4147,6 +4162,16 @@ def on_connection(
if role == HCI_CENTRAL_ROLE or not self.supports_le_extended_advertising:
# We can emit now, we have all the info we need
self._emit_le_connection(connection)
return

if role == HCI_PERIPHERAL_ROLE and self.supports_le_extended_advertising:
if advertising_set := self.connecting_extended_advertising_sets.pop(
connection_handle, None
):
# We have already received the advertising set termination event.
self._complete_le_extended_advertising_connection(
connection, advertising_set
)

@host_event_handler
def on_connection_failure(self, transport, peer_address, error_code):
Expand Down
4 changes: 4 additions & 0 deletions bumble/host.py
Original file line number Diff line number Diff line change
Expand Up @@ -787,6 +787,10 @@ def on_hci_le_enhanced_connection_complete_event(self, event):
# Just use the same implementation as for the non-enhanced event for now
self.on_hci_le_connection_complete_event(event)

def on_hci_le_enhanced_connection_complete_v2_event(self, event):
# Just use the same implementation as for the v1 event for now
self.on_hci_le_enhanced_connection_complete_event(event)

def on_hci_connection_complete_event(self, event):
if event.status == hci.HCI_SUCCESS:
# Create/update the connection
Expand Down
42 changes: 36 additions & 6 deletions tests/device_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -301,9 +301,7 @@ async def test_legacy_advertising_connection(own_address_type):
else:
assert device.lookup_connection(0x0001).self_address == device.random_address

# For unknown reason, read_phy() in on_connection() would be killed at the end of
# test, so we force scheduling here to avoid an warning.
await asyncio.sleep(0.0001)
await async_barrier()


# -----------------------------------------------------------------------------
Expand Down Expand Up @@ -384,9 +382,41 @@ async def test_extended_advertising_connection(own_address_type):
else:
assert device.lookup_connection(0x0001).self_address == device.random_address

# For unknown reason, read_phy() in on_connection() would be killed at the end of
# test, so we force scheduling here to avoid an warning.
await asyncio.sleep(0.0001)
await async_barrier()


# -----------------------------------------------------------------------------
@pytest.mark.parametrize(
'own_address_type,',
(OwnAddressType.PUBLIC, OwnAddressType.RANDOM),
)
@pytest.mark.asyncio
async def test_extended_advertising_connection_out_of_order(own_address_type):
device = Device(host=mock.AsyncMock(spec=Host))
peer_address = Address('F0:F1:F2:F3:F4:F5')
advertising_set = await device.create_advertising_set(
advertising_parameters=AdvertisingParameters(own_address_type=own_address_type)
)
device.on_advertising_set_termination(
HCI_SUCCESS,
advertising_set.advertising_handle,
0x0001,
0,
)
device.on_connection(
0x0001,
BT_LE_TRANSPORT,
peer_address,
BT_PERIPHERAL_ROLE,
ConnectionParameters(0, 0, 0),
)

if own_address_type == OwnAddressType.PUBLIC:
assert device.lookup_connection(0x0001).self_address == device.public_address
else:
assert device.lookup_connection(0x0001).self_address == device.random_address

await async_barrier()


# -----------------------------------------------------------------------------
Expand Down

0 comments on commit 32a41a8

Please sign in to comment.