fix: split concatenated HCI events instead of dropping them#925
fix: split concatenated HCI events instead of dropping them#925yannpoupon wants to merge 2 commits into
Conversation
|
Thanks for the very detailed report. You're right that the original assumption that each USB transfer would only contain a single complete packet doesn't always hold, so what you're proposing here is right. |
Yes that sounds good for me if you add a fix for this issue in your PR and I could test it. |
Fix: USB transport silently drops HCI events packed in the same USB transfer
Summary
When a Bluetooth USB dongle delivers two HCI events in a single USB transfer, Bumble's
UsbTransport.dequeue()callssink.on_packet()once with the full buffer.HCI_Event.from_bytesthen parses only the first event, logs a warning about surplus bytes, and silently discards the rest. If the discarded bytes happen to be anHCI_COMMAND_COMPLETE_EVENT, the correspondingsend_sync_commandfuture never resolves, its 10-second timeout fires, and the caller raisesTimeoutError.Observed failure
Some tests fails sporadically (~1 out of 200 test) with a
TimeoutErrorduring the BLE connection setup.Pytest log except
Analysis
Why it is sporadic
The race condition requires a third-party BLE device to be advertising nearby at the precise moment the scan-disable command is being processed. The dongle's firmware batches the last incoming
ADV_INDtogether with the outgoing command-complete response into one USB interrupt transfer. When no such device is in range or the timing is slower is he two HCI events arrive in separate USB transfers and are parsed correctly.Pytest log for a working example
Root cause
UsbTransport.InSource.transfer_callbackenqueues the full USB buffer as a single item, prepended with the HCI packet-type byte:dequeue()then passes the whole blob tosink.on_packet()in one call:HCI_Event.from_bytes(inhci.py) is called on the full blob. It readsparam_lenfrom byte offset 2, parses those bytes, and if there is anythingleft it logs a warning and returns—leaving the tail bytes unconsumed and lost:
Fix
Split the USB buffer into individual HCI event frames inside
dequeue()and callsink.on_packet()once per frame. Non-event (ACL) transfers are passed through unchanged.Before vs. after — the 36-byte USB transfer
[1:33]HCI_LE_ADVERTISING_REPORT_EVENT(iPhone)[33:39]HCI_COMMAND_COMPLETE_EVENTfor scan-disableWith the fix,
send_sync_commandreceives the completion immediately, theconnect()coroutine proceeds toHCI_LE_CREATE_CONNECTION_COMMAND, and the BLE connection is established successfully.Affected file
bumble/transport/usb.py—UsbTransport.InSource.dequeue()