Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 10 additions & 2 deletions bumble/device.py
Original file line number Diff line number Diff line change
Expand Up @@ -5229,7 +5229,11 @@ async def create_big(
)
await established
except hci.HCI_Error:
del self.bigs[big_handle]
# The establishment-failure event handler may have already
# removed the entry; pop so this cleanup cannot raise KeyError
# and mask the HCI_Error (see on_big_termination for the same
# idempotent pattern).
self.bigs.pop(big_handle, None)
raise

return big
Expand Down Expand Up @@ -5278,7 +5282,11 @@ async def create_big_sync(
)
await established
except hci.HCI_Error:
del self.big_syncs[big_handle]
# The establishment-failure event handler may have already
# removed the entry; pop so this cleanup cannot raise KeyError
# and mask the HCI_Error (see on_big_termination for the same
# idempotent pattern).
self.big_syncs.pop(big_handle, None)
raise

return big_sync
Expand Down
40 changes: 40 additions & 0 deletions tests/device_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
Advertisement,
AdvertisingEventProperties,
AdvertisingParameters,
BigSyncParameters,
CigParameters,
CisLink,
Connection,
Expand Down Expand Up @@ -881,6 +882,45 @@ async def test_get_remote_classic_features():
)


# -----------------------------------------------------------------------------
@pytest.mark.asyncio
async def test_create_big_sync_failure_propagates_hci_error():
# Regression: on a BIG sync establishment failure the event handler removes
# the big_syncs entry, and create_big_sync's except clause then removed it
# again with `del`, raising KeyError and masking the real HCI status.
device = Device(host=Host(None, None))
status = HCI_CONNECTION_FAILED_TO_BE_ESTABLISHED_ERROR

async def fake_send_async_command(command, check_status=True):
# The controller accepted the command; a failure event then arrives and
# its handler removes the entry before create_big_sync's except runs.
big_handle = next(iter(device.big_syncs))
asyncio.get_running_loop().call_soon(
device.on_big_sync_establishment,
status,
big_handle,
0, # transport_latency_big
0, # nse
0, # bn
0, # pto
0, # irc
0, # max_pdu
0, # iso_interval
[], # bis_handles
)
return HCI_SUCCESS

device.send_async_command = fake_send_async_command # type: ignore[assignment]
pa_sync = mock.Mock(sync_handle=0)
parameters = BigSyncParameters(big_sync_timeout=1000, bis=[1])

with pytest.raises(HCI_Error) as exc_info:
await device.create_big_sync(pa_sync, parameters)

assert exc_info.value.error_code == status
assert device.big_syncs == {}


# -----------------------------------------------------------------------------
async def run_test_device():
await test_device_connect_parallel()
Expand Down
Loading