bumble 0.0.219__py3-none-any.whl → 0.0.221__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- bumble/_version.py +2 -2
- bumble/a2dp.py +5 -5
- bumble/apps/auracast.py +746 -479
- bumble/apps/bench.py +4 -5
- bumble/apps/console.py +5 -10
- bumble/apps/controller_info.py +12 -7
- bumble/apps/controller_loopback.py +1 -2
- bumble/apps/device_info.py +2 -3
- bumble/apps/gatt_dump.py +0 -1
- bumble/apps/lea_unicast/app.py +1 -1
- bumble/apps/pair.py +49 -46
- bumble/apps/pandora_server.py +2 -2
- bumble/apps/player/player.py +10 -12
- bumble/apps/rfcomm_bridge.py +10 -11
- bumble/apps/scan.py +1 -3
- bumble/apps/speaker/speaker.py +3 -4
- bumble/at.py +4 -5
- bumble/att.py +91 -25
- bumble/audio/io.py +8 -6
- bumble/avc.py +1 -2
- bumble/avctp.py +2 -3
- bumble/avdtp.py +53 -57
- bumble/avrcp.py +25 -27
- bumble/codecs.py +15 -15
- bumble/colors.py +7 -8
- bumble/controller.py +1201 -643
- bumble/core.py +41 -49
- bumble/crypto/__init__.py +2 -1
- bumble/crypto/builtin.py +2 -8
- bumble/data_types.py +2 -1
- bumble/decoder.py +2 -3
- bumble/device.py +278 -325
- bumble/drivers/__init__.py +3 -2
- bumble/drivers/intel.py +6 -8
- bumble/drivers/rtk.py +1 -1
- bumble/gatt.py +9 -9
- bumble/gatt_adapters.py +6 -6
- bumble/gatt_client.py +110 -60
- bumble/gatt_server.py +209 -139
- bumble/hci.py +87 -74
- bumble/helpers.py +5 -5
- bumble/hfp.py +27 -26
- bumble/hid.py +9 -9
- bumble/host.py +44 -50
- bumble/keys.py +17 -17
- bumble/l2cap.py +1015 -218
- bumble/link.py +54 -284
- bumble/ll.py +200 -0
- bumble/lmp.py +324 -0
- bumble/pairing.py +14 -15
- bumble/pandora/__init__.py +2 -2
- bumble/pandora/device.py +6 -4
- bumble/pandora/host.py +19 -10
- bumble/pandora/l2cap.py +8 -9
- bumble/pandora/security.py +18 -16
- bumble/pandora/utils.py +4 -4
- bumble/profiles/aics.py +6 -8
- bumble/profiles/ams.py +3 -5
- bumble/profiles/ancs.py +11 -11
- bumble/profiles/ascs.py +5 -5
- bumble/profiles/asha.py +10 -9
- bumble/profiles/bass.py +9 -3
- bumble/profiles/battery_service.py +1 -2
- bumble/profiles/csip.py +9 -10
- bumble/profiles/device_information_service.py +16 -17
- bumble/profiles/gap.py +3 -4
- bumble/profiles/gatt_service.py +0 -1
- bumble/profiles/gmap.py +12 -13
- bumble/profiles/hap.py +3 -3
- bumble/profiles/heart_rate_service.py +7 -8
- bumble/profiles/le_audio.py +1 -1
- bumble/profiles/mcp.py +28 -28
- bumble/profiles/pacs.py +13 -17
- bumble/profiles/pbp.py +16 -0
- bumble/profiles/vcs.py +2 -2
- bumble/profiles/vocs.py +6 -9
- bumble/rfcomm.py +19 -18
- bumble/sdp.py +12 -11
- bumble/smp.py +20 -30
- bumble/snoop.py +12 -5
- bumble/tools/generate_company_id_list.py +1 -1
- bumble/tools/intel_util.py +2 -2
- bumble/tools/rtk_fw_download.py +1 -1
- bumble/tools/rtk_util.py +1 -1
- bumble/transport/__init__.py +1 -2
- bumble/transport/android_emulator.py +2 -3
- bumble/transport/android_netsim.py +49 -40
- bumble/transport/common.py +9 -9
- bumble/transport/file.py +1 -2
- bumble/transport/hci_socket.py +2 -3
- bumble/transport/pty.py +3 -5
- bumble/transport/pyusb.py +8 -5
- bumble/transport/serial.py +1 -2
- bumble/transport/vhci.py +1 -2
- bumble/transport/ws_server.py +2 -3
- bumble/utils.py +23 -14
- bumble/vendor/android/hci.py +4 -2
- {bumble-0.0.219.dist-info → bumble-0.0.221.dist-info}/METADATA +4 -3
- bumble-0.0.221.dist-info/RECORD +185 -0
- bumble-0.0.219.dist-info/RECORD +0 -183
- {bumble-0.0.219.dist-info → bumble-0.0.221.dist-info}/WHEEL +0 -0
- {bumble-0.0.219.dist-info → bumble-0.0.221.dist-info}/entry_points.txt +0 -0
- {bumble-0.0.219.dist-info → bumble-0.0.221.dist-info}/licenses/LICENSE +0 -0
- {bumble-0.0.219.dist-info → bumble-0.0.221.dist-info}/top_level.txt +0 -0
bumble/host.py
CHANGED
|
@@ -22,7 +22,8 @@ import collections
|
|
|
22
22
|
import dataclasses
|
|
23
23
|
import logging
|
|
24
24
|
import struct
|
|
25
|
-
from
|
|
25
|
+
from collections.abc import Awaitable, Callable
|
|
26
|
+
from typing import TYPE_CHECKING, Any, cast
|
|
26
27
|
|
|
27
28
|
from bumble import drivers, hci, utils
|
|
28
29
|
from bumble.colors import color
|
|
@@ -108,8 +109,7 @@ class DataPacketQueue(utils.EventEmitter):
|
|
|
108
109
|
|
|
109
110
|
if self._packets:
|
|
110
111
|
logger.debug(
|
|
111
|
-
f'{self._in_flight} packets in flight, '
|
|
112
|
-
f'{len(self._packets)} in queue'
|
|
112
|
+
f'{self._in_flight} packets in flight, {len(self._packets)} in queue'
|
|
113
113
|
)
|
|
114
114
|
|
|
115
115
|
def flush(self, connection_handle: int) -> None:
|
|
@@ -199,7 +199,7 @@ class Connection:
|
|
|
199
199
|
self.peer_address = peer_address
|
|
200
200
|
self.assembler = hci.HCI_AclDataPacketAssembler(self.on_acl_pdu)
|
|
201
201
|
self.transport = transport
|
|
202
|
-
acl_packet_queue:
|
|
202
|
+
acl_packet_queue: DataPacketQueue | None = (
|
|
203
203
|
host.le_acl_packet_queue
|
|
204
204
|
if transport == PhysicalTransport.LE
|
|
205
205
|
else host.acl_packet_queue
|
|
@@ -242,20 +242,18 @@ class Host(utils.EventEmitter):
|
|
|
242
242
|
bis_links: dict[int, IsoLink]
|
|
243
243
|
sco_links: dict[int, ScoLink]
|
|
244
244
|
bigs: dict[int, set[int]]
|
|
245
|
-
acl_packet_queue:
|
|
246
|
-
le_acl_packet_queue:
|
|
247
|
-
iso_packet_queue:
|
|
248
|
-
hci_sink:
|
|
245
|
+
acl_packet_queue: DataPacketQueue | None = None
|
|
246
|
+
le_acl_packet_queue: DataPacketQueue | None = None
|
|
247
|
+
iso_packet_queue: DataPacketQueue | None = None
|
|
248
|
+
hci_sink: TransportSink | None = None
|
|
249
249
|
hci_metadata: dict[str, Any]
|
|
250
|
-
long_term_key_provider:
|
|
251
|
-
|
|
252
|
-
]
|
|
253
|
-
link_key_provider: Optional[Callable[[hci.Address], Awaitable[Optional[bytes]]]]
|
|
250
|
+
long_term_key_provider: Callable[[int, bytes, int], Awaitable[bytes | None]] | None
|
|
251
|
+
link_key_provider: Callable[[hci.Address], Awaitable[bytes | None]] | None
|
|
254
252
|
|
|
255
253
|
def __init__(
|
|
256
254
|
self,
|
|
257
|
-
controller_source:
|
|
258
|
-
controller_sink:
|
|
255
|
+
controller_source: TransportSource | None = None,
|
|
256
|
+
controller_sink: TransportSink | None = None,
|
|
259
257
|
) -> None:
|
|
260
258
|
super().__init__()
|
|
261
259
|
|
|
@@ -267,7 +265,7 @@ class Host(utils.EventEmitter):
|
|
|
267
265
|
self.sco_links = {} # SCO links, by connection handle
|
|
268
266
|
self.bigs = {} # BIG Handle to BIS Handles
|
|
269
267
|
self.pending_command = None
|
|
270
|
-
self.pending_response:
|
|
268
|
+
self.pending_response: asyncio.Future[Any] | None = None
|
|
271
269
|
self.number_of_supported_advertising_sets = 0
|
|
272
270
|
self.maximum_advertising_data_length = 31
|
|
273
271
|
self.local_version = None
|
|
@@ -280,7 +278,7 @@ class Host(utils.EventEmitter):
|
|
|
280
278
|
self.long_term_key_provider = None
|
|
281
279
|
self.link_key_provider = None
|
|
282
280
|
self.pairing_io_capability_provider = None # Classic only
|
|
283
|
-
self.snooper:
|
|
281
|
+
self.snooper: Snooper | None = None
|
|
284
282
|
|
|
285
283
|
# Connect to the source and sink if specified
|
|
286
284
|
if controller_source:
|
|
@@ -291,9 +289,9 @@ class Host(utils.EventEmitter):
|
|
|
291
289
|
def find_connection_by_bd_addr(
|
|
292
290
|
self,
|
|
293
291
|
bd_addr: hci.Address,
|
|
294
|
-
transport:
|
|
292
|
+
transport: int | None = None,
|
|
295
293
|
check_address_type: bool = False,
|
|
296
|
-
) ->
|
|
294
|
+
) -> Connection | None:
|
|
297
295
|
for connection in self.connections.values():
|
|
298
296
|
if bytes(connection.peer_address) == bytes(bd_addr):
|
|
299
297
|
if (
|
|
@@ -633,7 +631,7 @@ class Host(utils.EventEmitter):
|
|
|
633
631
|
)
|
|
634
632
|
|
|
635
633
|
@property
|
|
636
|
-
def controller(self) ->
|
|
634
|
+
def controller(self) -> TransportSink | None:
|
|
637
635
|
return self.hci_sink
|
|
638
636
|
|
|
639
637
|
@controller.setter
|
|
@@ -642,7 +640,7 @@ class Host(utils.EventEmitter):
|
|
|
642
640
|
if controller:
|
|
643
641
|
self.set_packet_source(controller)
|
|
644
642
|
|
|
645
|
-
def set_packet_sink(self, sink:
|
|
643
|
+
def set_packet_sink(self, sink: TransportSink | None) -> None:
|
|
646
644
|
self.hci_sink = sink
|
|
647
645
|
|
|
648
646
|
def set_packet_source(self, source: TransportSource) -> None:
|
|
@@ -657,7 +655,7 @@ class Host(utils.EventEmitter):
|
|
|
657
655
|
self.hci_sink.on_packet(bytes(packet))
|
|
658
656
|
|
|
659
657
|
async def send_command(
|
|
660
|
-
self, command, check_result=False, response_timeout:
|
|
658
|
+
self, command, check_result=False, response_timeout: int | None = None
|
|
661
659
|
):
|
|
662
660
|
# Wait until we can send (only one pending command at a time)
|
|
663
661
|
async with self.command_semaphore:
|
|
@@ -707,7 +705,7 @@ class Host(utils.EventEmitter):
|
|
|
707
705
|
|
|
708
706
|
asyncio.create_task(send_command(command))
|
|
709
707
|
|
|
710
|
-
def
|
|
708
|
+
def send_acl_sdu(self, connection_handle: int, sdu: bytes) -> None:
|
|
711
709
|
if not (connection := self.connections.get(connection_handle)):
|
|
712
710
|
logger.warning(f'connection 0x{connection_handle:04X} not found')
|
|
713
711
|
return
|
|
@@ -718,27 +716,24 @@ class Host(utils.EventEmitter):
|
|
|
718
716
|
)
|
|
719
717
|
return
|
|
720
718
|
|
|
721
|
-
# Create a PDU
|
|
722
|
-
l2cap_pdu = bytes(L2CAP_PDU(cid, pdu))
|
|
723
|
-
|
|
724
719
|
# Send the data to the controller via ACL packets
|
|
725
|
-
|
|
726
|
-
offset
|
|
727
|
-
|
|
728
|
-
while bytes_remaining:
|
|
729
|
-
data_total_length = min(bytes_remaining, packet_queue.max_packet_size)
|
|
720
|
+
max_packet_size = packet_queue.max_packet_size
|
|
721
|
+
for offset in range(0, len(sdu), max_packet_size):
|
|
722
|
+
pdu = sdu[offset : offset + max_packet_size]
|
|
730
723
|
acl_packet = hci.HCI_AclDataPacket(
|
|
731
724
|
connection_handle=connection_handle,
|
|
732
|
-
pb_flag=
|
|
725
|
+
pb_flag=1 if offset > 0 else 0,
|
|
733
726
|
bc_flag=0,
|
|
734
|
-
data_total_length=
|
|
735
|
-
data=
|
|
727
|
+
data_total_length=len(pdu),
|
|
728
|
+
data=pdu,
|
|
729
|
+
)
|
|
730
|
+
logger.debug(
|
|
731
|
+
'>>> ACL packet enqueue: (Handle=0x%04X) %s', connection_handle, pdu
|
|
736
732
|
)
|
|
737
|
-
logger.debug(f'>>> ACL packet enqueue: (CID={cid}) {acl_packet}')
|
|
738
733
|
packet_queue.enqueue(acl_packet, connection_handle)
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
734
|
+
|
|
735
|
+
def send_l2cap_pdu(self, connection_handle: int, cid: int, pdu: bytes) -> None:
|
|
736
|
+
self.send_acl_sdu(connection_handle, bytes(L2CAP_PDU(cid, pdu)))
|
|
742
737
|
|
|
743
738
|
def get_data_packet_queue(self, connection_handle: int) -> DataPacketQueue | None:
|
|
744
739
|
if connection := self.connections.get(connection_handle):
|
|
@@ -903,7 +898,7 @@ class Host(utils.EventEmitter):
|
|
|
903
898
|
self.emit('l2cap_pdu', connection.handle, cid, pdu)
|
|
904
899
|
|
|
905
900
|
def on_command_processed(
|
|
906
|
-
self, event:
|
|
901
|
+
self, event: hci.HCI_Command_Complete_Event | hci.HCI_Command_Status_Event
|
|
907
902
|
):
|
|
908
903
|
if self.pending_response:
|
|
909
904
|
# Check that it is what we were expecting
|
|
@@ -966,11 +961,11 @@ class Host(utils.EventEmitter):
|
|
|
966
961
|
|
|
967
962
|
def on_hci_le_connection_complete_event(
|
|
968
963
|
self,
|
|
969
|
-
event:
|
|
970
|
-
hci.HCI_LE_Connection_Complete_Event
|
|
971
|
-
hci.HCI_LE_Enhanced_Connection_Complete_Event
|
|
972
|
-
hci.HCI_LE_Enhanced_Connection_Complete_V2_Event
|
|
973
|
-
|
|
964
|
+
event: (
|
|
965
|
+
hci.HCI_LE_Connection_Complete_Event
|
|
966
|
+
| hci.HCI_LE_Enhanced_Connection_Complete_Event
|
|
967
|
+
| hci.HCI_LE_Enhanced_Connection_Complete_V2_Event
|
|
968
|
+
),
|
|
974
969
|
):
|
|
975
970
|
# Check if this is a cancellation
|
|
976
971
|
if event.status == hci.HCI_SUCCESS:
|
|
@@ -1015,10 +1010,10 @@ class Host(utils.EventEmitter):
|
|
|
1015
1010
|
|
|
1016
1011
|
def on_hci_le_enhanced_connection_complete_event(
|
|
1017
1012
|
self,
|
|
1018
|
-
event:
|
|
1019
|
-
hci.HCI_LE_Enhanced_Connection_Complete_Event
|
|
1020
|
-
hci.HCI_LE_Enhanced_Connection_Complete_V2_Event
|
|
1021
|
-
|
|
1013
|
+
event: (
|
|
1014
|
+
hci.HCI_LE_Enhanced_Connection_Complete_Event
|
|
1015
|
+
| hci.HCI_LE_Enhanced_Connection_Complete_V2_Event
|
|
1016
|
+
),
|
|
1022
1017
|
):
|
|
1023
1018
|
# Just use the same implementation as for the non-enhanced event for now
|
|
1024
1019
|
self.on_hci_le_connection_complete_event(event)
|
|
@@ -1397,8 +1392,7 @@ class Host(utils.EventEmitter):
|
|
|
1397
1392
|
if event.status == hci.HCI_SUCCESS:
|
|
1398
1393
|
# Create/update the connection
|
|
1399
1394
|
logger.debug(
|
|
1400
|
-
f'### SCO CONNECTION: [0x{event.connection_handle:04X}] '
|
|
1401
|
-
f'{event.bd_addr}'
|
|
1395
|
+
f'### SCO CONNECTION: [0x{event.connection_handle:04X}] {event.bd_addr}'
|
|
1402
1396
|
)
|
|
1403
1397
|
|
|
1404
1398
|
self.sco_links[event.connection_handle] = ScoLink(
|
|
@@ -1450,7 +1444,7 @@ class Host(utils.EventEmitter):
|
|
|
1450
1444
|
def on_hci_le_data_length_change_event(
|
|
1451
1445
|
self, event: hci.HCI_LE_Data_Length_Change_Event
|
|
1452
1446
|
):
|
|
1453
|
-
if
|
|
1447
|
+
if event.connection_handle not in self.connections:
|
|
1454
1448
|
logger.warning('!!! DATA LENGTH CHANGE: unknown handle')
|
|
1455
1449
|
return
|
|
1456
1450
|
|
bumble/keys.py
CHANGED
|
@@ -27,7 +27,7 @@ import dataclasses
|
|
|
27
27
|
import json
|
|
28
28
|
import logging
|
|
29
29
|
import os
|
|
30
|
-
from typing import TYPE_CHECKING, Any
|
|
30
|
+
from typing import TYPE_CHECKING, Any
|
|
31
31
|
|
|
32
32
|
from typing_extensions import Self
|
|
33
33
|
|
|
@@ -51,8 +51,8 @@ class PairingKeys:
|
|
|
51
51
|
class Key:
|
|
52
52
|
value: bytes
|
|
53
53
|
authenticated: bool = False
|
|
54
|
-
ediv:
|
|
55
|
-
rand:
|
|
54
|
+
ediv: int | None = None
|
|
55
|
+
rand: bytes | None = None
|
|
56
56
|
|
|
57
57
|
@classmethod
|
|
58
58
|
def from_dict(cls, key_dict: dict[str, Any]) -> PairingKeys.Key:
|
|
@@ -74,17 +74,17 @@ class PairingKeys:
|
|
|
74
74
|
|
|
75
75
|
return key_dict
|
|
76
76
|
|
|
77
|
-
address_type:
|
|
78
|
-
ltk:
|
|
79
|
-
ltk_central:
|
|
80
|
-
ltk_peripheral:
|
|
81
|
-
irk:
|
|
82
|
-
csrk:
|
|
83
|
-
link_key:
|
|
84
|
-
link_key_type:
|
|
77
|
+
address_type: hci.AddressType | None = None
|
|
78
|
+
ltk: Key | None = None
|
|
79
|
+
ltk_central: Key | None = None
|
|
80
|
+
ltk_peripheral: Key | None = None
|
|
81
|
+
irk: Key | None = None
|
|
82
|
+
csrk: Key | None = None
|
|
83
|
+
link_key: Key | None = None # Classic
|
|
84
|
+
link_key_type: int | None = None # Classic
|
|
85
85
|
|
|
86
86
|
@classmethod
|
|
87
|
-
def key_from_dict(cls, keys_dict: dict[str, Any], key_name: str) ->
|
|
87
|
+
def key_from_dict(cls, keys_dict: dict[str, Any], key_name: str) -> Key | None:
|
|
88
88
|
key_dict = keys_dict.get(key_name)
|
|
89
89
|
if key_dict is None:
|
|
90
90
|
return None
|
|
@@ -156,7 +156,7 @@ class KeyStore:
|
|
|
156
156
|
async def update(self, name: str, keys: PairingKeys) -> None:
|
|
157
157
|
pass
|
|
158
158
|
|
|
159
|
-
async def get(self, _name: str) ->
|
|
159
|
+
async def get(self, _name: str) -> PairingKeys | None:
|
|
160
160
|
return None
|
|
161
161
|
|
|
162
162
|
async def get_all(self) -> list[tuple[str, PairingKeys]]:
|
|
@@ -274,7 +274,7 @@ class JsonKeyStore(KeyStore):
|
|
|
274
274
|
|
|
275
275
|
@classmethod
|
|
276
276
|
def from_device(
|
|
277
|
-
cls: type[Self], device: Device, filename:
|
|
277
|
+
cls: type[Self], device: Device, filename: str | None = None
|
|
278
278
|
) -> Self:
|
|
279
279
|
if not filename:
|
|
280
280
|
# Extract the filename from the config if there is one
|
|
@@ -297,7 +297,7 @@ class JsonKeyStore(KeyStore):
|
|
|
297
297
|
# Try to open the file, without failing. If the file does not exist, it
|
|
298
298
|
# will be created upon saving.
|
|
299
299
|
try:
|
|
300
|
-
with open(self.filename,
|
|
300
|
+
with open(self.filename, encoding='utf-8') as json_file:
|
|
301
301
|
db = json.load(json_file)
|
|
302
302
|
except FileNotFoundError:
|
|
303
303
|
db = {}
|
|
@@ -348,7 +348,7 @@ class JsonKeyStore(KeyStore):
|
|
|
348
348
|
key_map.clear()
|
|
349
349
|
await self.save(db)
|
|
350
350
|
|
|
351
|
-
async def get(self, name: str) ->
|
|
351
|
+
async def get(self, name: str) -> PairingKeys | None:
|
|
352
352
|
_, key_map = await self.load()
|
|
353
353
|
if name not in key_map:
|
|
354
354
|
return None
|
|
@@ -370,7 +370,7 @@ class MemoryKeyStore(KeyStore):
|
|
|
370
370
|
async def update(self, name: str, keys: PairingKeys) -> None:
|
|
371
371
|
self.all_keys[name] = keys
|
|
372
372
|
|
|
373
|
-
async def get(self, name: str) ->
|
|
373
|
+
async def get(self, name: str) -> PairingKeys | None:
|
|
374
374
|
return self.all_keys.get(name)
|
|
375
375
|
|
|
376
376
|
async def get_all(self) -> list[tuple[str, PairingKeys]]:
|