usb transport packet splitter#936
Open
barbibulle wants to merge 2 commits into
Open
Conversation
zxzxwu
requested changes
Jun 4, 2026
Collaborator
zxzxwu
left a comment
There was a problem hiding this comment.
Thanks for the PR! I did a review and found a potential bug in PacketSplitter.feed.
Bug in PacketSplitter.feed
If the incoming data chunk ends exactly at the header boundary, and the packet has a 0-length payload, the packet will not be emitted immediately. It will be stuck until the next feed call.
Scenario:
feedis called with data that exactly matches the header (e.g. 4 bytes for ACL header).self.packetaccumulates the header.databecomes empty.- The code hits
continueat the end of the header accumulation block:if (bytes_needed := self.header_size - len(self.packet)) > 0: self.packet += data[:bytes_needed] data = data[bytes_needed:] continue
- The loop terminates because
datais empty. - The code below the
ifblock (which calculatespacket_lengthand emits the packet if complete) is never reached.
If the payload length is 0, the packet is already complete but not emitted.
Suggested Fix:
Only continue if we don't have a complete header yet:
@@ -413,7 +413,8 @@
if (bytes_needed := self.header_size - len(self.packet)) > 0:
self.packet += data[:bytes_needed]
data = data[bytes_needed:]
- continue
+ if len(self.packet) < self.header_size:
+ continueUnit Tests
I recommend adding unit tests for PacketSplitter to verify this. Here are some tests I wrote that catch this issue:
import pytest
from bumble import hci
from bumble.transport import usb
def test_packet_splitter_complete():
emitted = []
splitter = usb.AclPacketSplitter(emitted.append)
packet = bytes([0x01, 0x00, 0x04, 0x00, 0x11, 0x22, 0x33, 0x44])
splitter.feed(packet)
assert emitted == [packet]
def test_packet_splitter_chunks():
emitted = []
splitter = usb.AclPacketSplitter(emitted.append)
packet = bytes([0x01, 0x00, 0x04, 0x00, 0x11, 0x22, 0x33, 0x44])
splitter.feed(packet[:4])
assert emitted == []
splitter.feed(packet[4:])
assert emitted == [packet]
def test_packet_splitter_multiple():
emitted = []
splitter = usb.AclPacketSplitter(emitted.append)
packet1 = bytes([0x01, 0x00, 0x04, 0x00, 0x11, 0x22, 0x33, 0x44])
packet2 = bytes([0x02, 0x00, 0x02, 0x00, 0x55, 0x66])
splitter.feed(packet1 + packet2)
assert emitted == [packet1, packet2]
def test_packet_splitter_partial():
emitted = []
splitter = usb.AclPacketSplitter(emitted.append)
packet1 = bytes([0x01, 0x00, 0x04, 0x00, 0x11, 0x22, 0x33, 0x44])
packet2 = bytes([0x02, 0x00, 0x02, 0x00, 0x55, 0x66])
splitter.feed(packet1 + packet2[:4])
assert emitted == [packet1]
splitter.feed(packet2[4:])
assert emitted == [packet1, packet2]
def test_packet_splitter_empty_payload():
emitted = []
splitter = usb.AclPacketSplitter(emitted.append)
packet = bytes([0x01, 0x00, 0x00, 0x00])
splitter.feed(packet)
assert emitted == [packet]
def test_sco_packet_splitter():
emitted = []
splitter = usb.ScoPacketSplitter(emitted.append)
packet = bytes([0x01, 0x00, 0x03, 0x11, 0x22, 0x33])
splitter.feed(packet)
assert emitted == [packet]
def test_event_packet_splitter():
emitted = []
splitter = usb.EventPacketSplitter(emitted.append)
packet = bytes([0x04, 0x02, 0x11, 0x22])
splitter.feed(packet)
assert emitted == [packet]
zxzxwu
approved these changes
Jun 4, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
This PR adds packet splitters for all incoming HCI data streams. Even though in most cases we observe that each incoming transfer only contains a single packet, in some cases (as reported in #925) there may be more than one packet (or even partial packets) in transfer buffers. The splitters accumulate data until a complete packet has been receive, then emit complete packets only.
Also included in this PR is the use of multiple transfers for isochronous endpoints (currently set at 2, which should be enough for a simple double-buffering mechanism), which helps avoid data drops for SCO packets (I was able to get HFP audio streams without audio glitches now, which were caused by data being dropped when data arrived before a new transfer was submitted, at the end of a completed transfer).