From 5e55c0e358e298b4b1942bd301f41539526b44f4 Mon Sep 17 00:00:00 2001 From: Gilles Boccon-Gibod Date: Mon, 17 Mar 2025 19:56:02 -0400 Subject: [PATCH 1/2] add broadcast code encoding --- apps/auracast.py | 58 ++++++++++++++++++++++---------------- bumble/profiles/pbp.py | 2 +- bumble/transport/common.py | 5 +++- 3 files changed, 39 insertions(+), 26 deletions(-) diff --git a/apps/auracast.py b/apps/auracast.py index 6aa1b0ea..1f7033f0 100644 --- a/apps/auracast.py +++ b/apps/auracast.py @@ -99,6 +99,25 @@ def codec_config_string( return '\n'.join(indent + line for line in lines) +def broadcast_code_bytes(broadcast_code: str) -> bytes: + """ + Convert a broadcast code string to a 16-byte value. + + If `broadcast_code` is `0x` followed by 32 hex characters, it is interpreted as a + raw 16-byte raw broadcast code in big-endian byte order. + Otherwise, `broadcast_code` is converted to a 16-byte value as specified in + BLUETOOTH CORE SPECIFICATION Version 6.0 | Vol 3, Part C , section 3.2.6.3 + """ + if broadcast_code.startswith("0x") and len(broadcast_code) == 34: + return bytes.fromhex(broadcast_code[2:])[::-1] + + broadcast_code_utf8 = broadcast_code.encode("utf-8") + if len(broadcast_code_utf8) > 16: + raise ValueError("broadcast code must be <= 16 bytes in utf-8 encoding") + padding = bytes(16 - len(broadcast_code_utf8)) + return broadcast_code_utf8 + padding + + # ----------------------------------------------------------------------------- # Scan For Broadcasts # ----------------------------------------------------------------------------- @@ -234,22 +253,14 @@ class BroadcastScanner(pyee.EventEmitter): if self.biginfo: print(color(' BIG:', 'cyan')) - print( - color(' Number of BIS:', 'magenta'), - self.biginfo.num_bis, - ) - print( - color(' PHY: ', 'magenta'), - self.biginfo.phy.name, - ) - print( - color(' Framed: ', 'magenta'), - self.biginfo.framed, - ) - print( - color(' Encrypted: ', 'magenta'), - self.biginfo.encrypted, - ) + print(color(' Number of BIS:', 'magenta'), self.biginfo.num_bis) + print(color(' ISO Interval: ', 'magenta'), self.biginfo.iso_interval) + print(color(' Max PDU: ', 'magenta'), self.biginfo.max_pdu) + print(color(' SDU Interval: ', 'magenta'), self.biginfo.sdu_interval) + print(color(' Max SDU: ', 'magenta'), self.biginfo.max_sdu) + print(color(' PHY: ', 'magenta'), self.biginfo.phy.name) + print(color(' Framed: ', 'magenta'), self.biginfo.framed) + print(color(' Encrypted: ', 'magenta'), self.biginfo.encrypted) def on_sync_establishment(self) -> None: self.emit('sync_establishment') @@ -702,14 +713,13 @@ async def run_receive( def on_change() -> None: if ( - broadcast.basic_audio_announcement - and not basic_audio_announcement_scanned.is_set() - ): + broadcast.basic_audio_announcement and broadcast.biginfo + ) and not basic_audio_announcement_scanned.is_set(): basic_audio_announcement_scanned.set() broadcast.on('change', on_change) - if not broadcast.basic_audio_announcement: - print('Wait for Basic Audio Announcement...') + if not broadcast.basic_audio_announcement or not broadcast.biginfo: + print('Wait for Basic Audio Announcement and BIG Info...') await basic_audio_announcement_scanned.wait() print('Basic Audio Announcement found') broadcast.print() @@ -730,7 +740,7 @@ async def run_receive( big_sync_timeout=0x4000, bis=[bis.index for bis in subgroup.bis], broadcast_code=( - bytes.fromhex(broadcast_code) if broadcast_code else None + broadcast_code_bytes(broadcast_code) if broadcast_code else None ), ), ) @@ -944,7 +954,7 @@ async def run_transmit( max_transport_latency=65, rtn=4, broadcast_code=( - bytes.fromhex(broadcast_code) if broadcast_code else None + broadcast_code_bytes(broadcast_code) if broadcast_code else None ), ), ) @@ -1088,7 +1098,7 @@ def pair(ctx, transport, address): '--broadcast-code', metavar='BROADCAST_CODE', type=str, - help='Broadcast encryption code in hex format', + help='Broadcast encryption code (string or raw hex format prefixed with 0x)', ) @click.option( '--sync-timeout', diff --git a/bumble/profiles/pbp.py b/bumble/profiles/pbp.py index 058bd6dc..d905dd9c 100644 --- a/bumble/profiles/pbp.py +++ b/bumble/profiles/pbp.py @@ -40,7 +40,7 @@ class PublicBroadcastAnnouncement: def from_bytes(cls, data: bytes) -> Self: features = cls.Features(data[0]) metadata_length = data[1] - metadata_ltv = data[1 : 1 + metadata_length] + metadata_ltv = data[2 : 2 + metadata_length] return cls( features=features, metadata=le_audio.Metadata.from_bytes(metadata_ltv) ) diff --git a/bumble/transport/common.py b/bumble/transport/common.py index 13176375..ca12388e 100644 --- a/bumble/transport/common.py +++ b/bumble/transport/common.py @@ -302,7 +302,10 @@ class ParserSource(BaseSource): # ----------------------------------------------------------------------------- class StreamPacketSource(asyncio.Protocol, ParserSource): def data_received(self, data: bytes) -> None: - self.parser.feed_data(data) + try: + self.parser.feed_data(data) + except core.InvalidPacketError: + logger.warning("invalid packet, ignoring data") # ----------------------------------------------------------------------------- From 3495eb52ba6ade7629a2e2fd5d7a92e231d5f1a3 Mon Sep 17 00:00:00 2001 From: Gilles Boccon-Gibod Date: Wed, 19 Mar 2025 11:32:51 -0400 Subject: [PATCH 2/2] reset parser before raising exception --- bumble/transport/common.py | 1 + 1 file changed, 1 insertion(+) diff --git a/bumble/transport/common.py b/bumble/transport/common.py index ca12388e..b1dd0002 100644 --- a/bumble/transport/common.py +++ b/bumble/transport/common.py @@ -139,6 +139,7 @@ class PacketParser: packet_type ) or self.extended_packet_info.get(packet_type) if self.packet_info is None: + self.reset() raise core.InvalidPacketError( f'invalid packet type {packet_type}' )