From f2638ba0cd0ccd6e2ebf3468abfd345024bf3b49 Mon Sep 17 00:00:00 2001 From: Yann poupon Date: Mon, 18 May 2026 15:48:15 +0200 Subject: [PATCH 1/2] fix: split concatenated HCI events instead of dropping them --- bumble/transport/usb.py | 25 +++++++++++++++++++++---- 1 file changed, 21 insertions(+), 4 deletions(-) diff --git a/bumble/transport/usb.py b/bumble/transport/usb.py index 80fdcd3f..9b9e793b 100644 --- a/bumble/transport/usb.py +++ b/bumble/transport/usb.py @@ -291,11 +291,28 @@ async def dequeue(self): return if self.sink: try: - self.sink.on_packet(packet) + packet_type = packet[0] + len_packet = len(packet) + # A single USB interrupt transfer may contain multiple HCI events concatenated. + # Go through the packet buffer and dispatch each complete event individually so + # that no packet is silently discarded due to being concatenated with another packet. + if packet_type == hci.HCI_EVENT_PACKET and len_packet > 3 + packet[2]: + offset = 1 # skip the prepended packet_type byte + hci_event_header_len = 2 # event_code (1) + param_len (1) + while offset < len_packet: + if offset + hci_event_header_len > len_packet: # incomplete header + break + param_len = packet[offset + 1] + end = offset + hci_event_header_len + param_len + if end > len_packet: # incomplete packet + break + self.sink.on_packet(bytes([packet_type]) + packet[offset:end]) + offset = end + else: + self.sink.on_packet(packet) except Exception: - logger.exception( - color('!!! Exception in sink.on_packet', 'red') - ) + logger.exception(color('!!! Exception in sink.on_packet', 'red')) + def close(self): self.closed = True From babd22f969bc99460abfa2e30b70ce6a93f45631 Mon Sep 17 00:00:00 2001 From: Yann poupon Date: Mon, 18 May 2026 17:16:56 +0200 Subject: [PATCH 2/2] chore: apply black --- bumble/transport/usb.py | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/bumble/transport/usb.py b/bumble/transport/usb.py index 9b9e793b..98aec626 100644 --- a/bumble/transport/usb.py +++ b/bumble/transport/usb.py @@ -296,23 +296,31 @@ async def dequeue(self): # A single USB interrupt transfer may contain multiple HCI events concatenated. # Go through the packet buffer and dispatch each complete event individually so # that no packet is silently discarded due to being concatenated with another packet. - if packet_type == hci.HCI_EVENT_PACKET and len_packet > 3 + packet[2]: + if ( + packet_type == hci.HCI_EVENT_PACKET + and len_packet > 3 + packet[2] + ): offset = 1 # skip the prepended packet_type byte hci_event_header_len = 2 # event_code (1) + param_len (1) while offset < len_packet: - if offset + hci_event_header_len > len_packet: # incomplete header + if ( + offset + hci_event_header_len > len_packet + ): # incomplete header break param_len = packet[offset + 1] end = offset + hci_event_header_len + param_len if end > len_packet: # incomplete packet break - self.sink.on_packet(bytes([packet_type]) + packet[offset:end]) + self.sink.on_packet( + bytes([packet_type]) + packet[offset:end] + ) offset = end else: self.sink.on_packet(packet) except Exception: - logger.exception(color('!!! Exception in sink.on_packet', 'red')) - + logger.exception( + color('!!! Exception in sink.on_packet', 'red') + ) def close(self): self.closed = True