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/profiles/pacs.py
CHANGED
|
@@ -21,7 +21,7 @@ from __future__ import annotations
|
|
|
21
21
|
import dataclasses
|
|
22
22
|
import logging
|
|
23
23
|
import struct
|
|
24
|
-
from
|
|
24
|
+
from collections.abc import Sequence
|
|
25
25
|
|
|
26
26
|
from bumble import gatt, gatt_adapters, gatt_client, hci
|
|
27
27
|
from bumble.profiles import le_audio
|
|
@@ -39,7 +39,7 @@ class PacRecord:
|
|
|
39
39
|
'''Published Audio Capabilities Service, Table 3.2/3.4.'''
|
|
40
40
|
|
|
41
41
|
coding_format: hci.CodingFormat
|
|
42
|
-
codec_specific_capabilities:
|
|
42
|
+
codec_specific_capabilities: CodecSpecificCapabilities | bytes
|
|
43
43
|
metadata: le_audio.Metadata = dataclasses.field(default_factory=le_audio.Metadata)
|
|
44
44
|
|
|
45
45
|
@classmethod
|
|
@@ -56,7 +56,7 @@ class PacRecord:
|
|
|
56
56
|
offset += 1
|
|
57
57
|
metadata = le_audio.Metadata.from_bytes(data[offset : offset + metadata_size])
|
|
58
58
|
|
|
59
|
-
codec_specific_capabilities:
|
|
59
|
+
codec_specific_capabilities: CodecSpecificCapabilities | bytes
|
|
60
60
|
if coding_format.codec_id == hci.CodecID.VENDOR_SPECIFIC:
|
|
61
61
|
codec_specific_capabilities = codec_specific_capabilities_bytes
|
|
62
62
|
else:
|
|
@@ -101,10 +101,10 @@ class PacRecord:
|
|
|
101
101
|
class PublishedAudioCapabilitiesService(gatt.TemplateService):
|
|
102
102
|
UUID = gatt.GATT_PUBLISHED_AUDIO_CAPABILITIES_SERVICE
|
|
103
103
|
|
|
104
|
-
sink_pac:
|
|
105
|
-
sink_audio_locations:
|
|
106
|
-
source_pac:
|
|
107
|
-
source_audio_locations:
|
|
104
|
+
sink_pac: gatt.Characteristic[bytes] | None
|
|
105
|
+
sink_audio_locations: gatt.Characteristic[bytes] | None
|
|
106
|
+
source_pac: gatt.Characteristic[bytes] | None
|
|
107
|
+
source_audio_locations: gatt.Characteristic[bytes] | None
|
|
108
108
|
available_audio_contexts: gatt.Characteristic[bytes]
|
|
109
109
|
supported_audio_contexts: gatt.Characteristic[bytes]
|
|
110
110
|
|
|
@@ -115,9 +115,9 @@ class PublishedAudioCapabilitiesService(gatt.TemplateService):
|
|
|
115
115
|
available_source_context: ContextType,
|
|
116
116
|
available_sink_context: ContextType,
|
|
117
117
|
sink_pac: Sequence[PacRecord] = (),
|
|
118
|
-
sink_audio_locations:
|
|
118
|
+
sink_audio_locations: AudioLocation | None = None,
|
|
119
119
|
source_pac: Sequence[PacRecord] = (),
|
|
120
|
-
source_audio_locations:
|
|
120
|
+
source_audio_locations: AudioLocation | None = None,
|
|
121
121
|
) -> None:
|
|
122
122
|
characteristics = []
|
|
123
123
|
|
|
@@ -183,14 +183,10 @@ class PublishedAudioCapabilitiesService(gatt.TemplateService):
|
|
|
183
183
|
class PublishedAudioCapabilitiesServiceProxy(gatt_client.ProfileServiceProxy):
|
|
184
184
|
SERVICE_CLASS = PublishedAudioCapabilitiesService
|
|
185
185
|
|
|
186
|
-
sink_pac:
|
|
187
|
-
sink_audio_locations:
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
source_pac: Optional[gatt_client.CharacteristicProxy[list[PacRecord]]] = None
|
|
191
|
-
source_audio_locations: Optional[gatt_client.CharacteristicProxy[AudioLocation]] = (
|
|
192
|
-
None
|
|
193
|
-
)
|
|
186
|
+
sink_pac: gatt_client.CharacteristicProxy[list[PacRecord]] | None = None
|
|
187
|
+
sink_audio_locations: gatt_client.CharacteristicProxy[AudioLocation] | None = None
|
|
188
|
+
source_pac: gatt_client.CharacteristicProxy[list[PacRecord]] | None = None
|
|
189
|
+
source_audio_locations: gatt_client.CharacteristicProxy[AudioLocation] | None = None
|
|
194
190
|
available_audio_contexts: gatt_client.CharacteristicProxy[tuple[ContextType, ...]]
|
|
195
191
|
supported_audio_contexts: gatt_client.CharacteristicProxy[tuple[ContextType, ...]]
|
|
196
192
|
|
bumble/profiles/pbp.py
CHANGED
|
@@ -22,6 +22,7 @@ import enum
|
|
|
22
22
|
|
|
23
23
|
from typing_extensions import Self
|
|
24
24
|
|
|
25
|
+
from bumble import core, data_types, gatt
|
|
25
26
|
from bumble.profiles import le_audio
|
|
26
27
|
|
|
27
28
|
|
|
@@ -46,3 +47,18 @@ class PublicBroadcastAnnouncement:
|
|
|
46
47
|
return cls(
|
|
47
48
|
features=features, metadata=le_audio.Metadata.from_bytes(metadata_ltv)
|
|
48
49
|
)
|
|
50
|
+
|
|
51
|
+
def get_advertising_data(self) -> bytes:
|
|
52
|
+
return bytes(
|
|
53
|
+
core.AdvertisingData(
|
|
54
|
+
[
|
|
55
|
+
data_types.ServiceData16BitUUID(
|
|
56
|
+
gatt.GATT_PUBLIC_BROADCAST_ANNOUNCEMENT_SERVICE, bytes(self)
|
|
57
|
+
)
|
|
58
|
+
]
|
|
59
|
+
)
|
|
60
|
+
)
|
|
61
|
+
|
|
62
|
+
def __bytes__(self) -> bytes:
|
|
63
|
+
metadata_bytes = bytes(self.metadata)
|
|
64
|
+
return bytes([self.features, len(metadata_bytes)]) + metadata_bytes
|
bumble/profiles/vcs.py
CHANGED
|
@@ -20,9 +20,9 @@ from __future__ import annotations
|
|
|
20
20
|
|
|
21
21
|
import dataclasses
|
|
22
22
|
import enum
|
|
23
|
-
from
|
|
23
|
+
from collections.abc import Sequence
|
|
24
24
|
|
|
25
|
-
from bumble import att, device, gatt, gatt_adapters, gatt_client
|
|
25
|
+
from bumble import att, device, gatt, gatt_adapters, gatt_client
|
|
26
26
|
|
|
27
27
|
# -----------------------------------------------------------------------------
|
|
28
28
|
# Constants
|
bumble/profiles/vocs.py
CHANGED
|
@@ -18,7 +18,6 @@
|
|
|
18
18
|
# -----------------------------------------------------------------------------
|
|
19
19
|
import struct
|
|
20
20
|
from dataclasses import dataclass
|
|
21
|
-
from typing import Optional
|
|
22
21
|
|
|
23
22
|
from bumble import utils
|
|
24
23
|
from bumble.att import ATT_Error
|
|
@@ -69,7 +68,7 @@ class ErrorCode(utils.OpenIntEnum):
|
|
|
69
68
|
class VolumeOffsetState:
|
|
70
69
|
volume_offset: int = 0
|
|
71
70
|
change_counter: int = 0
|
|
72
|
-
attribute:
|
|
71
|
+
attribute: Characteristic | None = None
|
|
73
72
|
|
|
74
73
|
def __bytes__(self) -> bytes:
|
|
75
74
|
return struct.pack('<hB', self.volume_offset, self.change_counter)
|
|
@@ -93,7 +92,7 @@ class VolumeOffsetState:
|
|
|
93
92
|
@dataclass
|
|
94
93
|
class VocsAudioLocation:
|
|
95
94
|
audio_location: AudioLocation = AudioLocation.NOT_ALLOWED
|
|
96
|
-
attribute:
|
|
95
|
+
attribute: Characteristic | None = None
|
|
97
96
|
|
|
98
97
|
def __bytes__(self) -> bytes:
|
|
99
98
|
return struct.pack('<I', self.audio_location)
|
|
@@ -118,7 +117,6 @@ class VolumeOffsetControlPoint:
|
|
|
118
117
|
volume_offset_state: VolumeOffsetState
|
|
119
118
|
|
|
120
119
|
async def on_write(self, connection: Connection, value: bytes) -> None:
|
|
121
|
-
|
|
122
120
|
opcode = value[0]
|
|
123
121
|
if opcode != SetVolumeOffsetOpCode.SET_VOLUME_OFFSET:
|
|
124
122
|
raise ATT_Error(ErrorCode.OPCODE_NOT_SUPPORTED)
|
|
@@ -148,7 +146,7 @@ class VolumeOffsetControlPoint:
|
|
|
148
146
|
@dataclass
|
|
149
147
|
class AudioOutputDescription:
|
|
150
148
|
audio_output_description: str = ''
|
|
151
|
-
attribute:
|
|
149
|
+
attribute: Characteristic | None = None
|
|
152
150
|
|
|
153
151
|
@classmethod
|
|
154
152
|
def from_bytes(cls, data: bytes):
|
|
@@ -173,11 +171,10 @@ class VolumeOffsetControlService(TemplateService):
|
|
|
173
171
|
|
|
174
172
|
def __init__(
|
|
175
173
|
self,
|
|
176
|
-
volume_offset_state:
|
|
177
|
-
audio_location:
|
|
178
|
-
audio_output_description:
|
|
174
|
+
volume_offset_state: VolumeOffsetState | None = None,
|
|
175
|
+
audio_location: VocsAudioLocation | None = None,
|
|
176
|
+
audio_output_description: AudioOutputDescription | None = None,
|
|
179
177
|
) -> None:
|
|
180
|
-
|
|
181
178
|
self.volume_offset_state = (
|
|
182
179
|
VolumeOffsetState() if volume_offset_state is None else volume_offset_state
|
|
183
180
|
)
|
bumble/rfcomm.py
CHANGED
|
@@ -22,7 +22,8 @@ import collections
|
|
|
22
22
|
import dataclasses
|
|
23
23
|
import enum
|
|
24
24
|
import logging
|
|
25
|
-
from
|
|
25
|
+
from collections.abc import Callable
|
|
26
|
+
from typing import TYPE_CHECKING
|
|
26
27
|
|
|
27
28
|
from typing_extensions import Self
|
|
28
29
|
|
|
@@ -119,7 +120,7 @@ RFCOMM_DYNAMIC_CHANNEL_NUMBER_END = 30
|
|
|
119
120
|
|
|
120
121
|
# -----------------------------------------------------------------------------
|
|
121
122
|
def make_service_sdp_records(
|
|
122
|
-
service_record_handle: int, channel: int, uuid:
|
|
123
|
+
service_record_handle: int, channel: int, uuid: UUID | None = None
|
|
123
124
|
) -> list[sdp.ServiceAttribute]:
|
|
124
125
|
"""
|
|
125
126
|
Create SDP records for an RFComm service given a channel number and an
|
|
@@ -186,7 +187,7 @@ async def find_rfcomm_channels(connection: Connection) -> dict[int, list[UUID]]:
|
|
|
186
187
|
)
|
|
187
188
|
for attribute_lists in search_result:
|
|
188
189
|
service_classes: list[UUID] = []
|
|
189
|
-
channel:
|
|
190
|
+
channel: int | None = None
|
|
190
191
|
for attribute in attribute_lists:
|
|
191
192
|
# The layout is [[L2CAP_PROTOCOL], [RFCOMM_PROTOCOL, RFCOMM_CHANNEL]].
|
|
192
193
|
if attribute.id == sdp.SDP_PROTOCOL_DESCRIPTOR_LIST_ATTRIBUTE_ID:
|
|
@@ -207,7 +208,7 @@ async def find_rfcomm_channels(connection: Connection) -> dict[int, list[UUID]]:
|
|
|
207
208
|
# -----------------------------------------------------------------------------
|
|
208
209
|
async def find_rfcomm_channel_with_uuid(
|
|
209
210
|
connection: Connection, uuid: str | UUID
|
|
210
|
-
) ->
|
|
211
|
+
) -> int | None:
|
|
211
212
|
"""Searches an RFCOMM channel associated with given UUID from service records.
|
|
212
213
|
|
|
213
214
|
Args:
|
|
@@ -473,15 +474,15 @@ class DLC(utils.EventEmitter):
|
|
|
473
474
|
self.state = DLC.State.INIT
|
|
474
475
|
self.role = multiplexer.role
|
|
475
476
|
self.c_r = 1 if self.role == Multiplexer.Role.INITIATOR else 0
|
|
476
|
-
self.connection_result:
|
|
477
|
-
self.disconnection_result:
|
|
477
|
+
self.connection_result: asyncio.Future | None = None
|
|
478
|
+
self.disconnection_result: asyncio.Future | None = None
|
|
478
479
|
self.drained = asyncio.Event()
|
|
479
480
|
self.drained.set()
|
|
480
481
|
# Queued packets when sink is not set.
|
|
481
482
|
self._enqueued_rx_packets: collections.deque[bytes] = collections.deque(
|
|
482
483
|
maxlen=DEFAULT_RX_QUEUE_SIZE
|
|
483
484
|
)
|
|
484
|
-
self._sink:
|
|
485
|
+
self._sink: Callable[[bytes], None] | None = None
|
|
485
486
|
|
|
486
487
|
# Compute the MTU
|
|
487
488
|
max_overhead = 4 + 1 # header with 2-byte length + fcs
|
|
@@ -490,11 +491,11 @@ class DLC(utils.EventEmitter):
|
|
|
490
491
|
)
|
|
491
492
|
|
|
492
493
|
@property
|
|
493
|
-
def sink(self) ->
|
|
494
|
+
def sink(self) -> Callable[[bytes], None] | None:
|
|
494
495
|
return self._sink
|
|
495
496
|
|
|
496
497
|
@sink.setter
|
|
497
|
-
def sink(self, sink:
|
|
498
|
+
def sink(self, sink: Callable[[bytes], None] | None) -> None:
|
|
498
499
|
self._sink = sink
|
|
499
500
|
# Dump queued packets to sink
|
|
500
501
|
if sink:
|
|
@@ -712,7 +713,7 @@ class DLC(utils.EventEmitter):
|
|
|
712
713
|
self.drained.set()
|
|
713
714
|
|
|
714
715
|
# Stream protocol
|
|
715
|
-
def write(self, data:
|
|
716
|
+
def write(self, data: bytes | str) -> None:
|
|
716
717
|
# We can only send bytes
|
|
717
718
|
if not isinstance(data, bytes):
|
|
718
719
|
if isinstance(data, str):
|
|
@@ -769,10 +770,10 @@ class Multiplexer(utils.EventEmitter):
|
|
|
769
770
|
|
|
770
771
|
EVENT_DLC = "dlc"
|
|
771
772
|
|
|
772
|
-
connection_result:
|
|
773
|
-
disconnection_result:
|
|
774
|
-
open_result:
|
|
775
|
-
acceptor:
|
|
773
|
+
connection_result: asyncio.Future | None
|
|
774
|
+
disconnection_result: asyncio.Future | None
|
|
775
|
+
open_result: asyncio.Future | None
|
|
776
|
+
acceptor: Callable[[int], tuple[int, int] | None] | None
|
|
776
777
|
dlcs: dict[int, DLC]
|
|
777
778
|
|
|
778
779
|
def __init__(self, l2cap_channel: l2cap.ClassicChannel, role: Role) -> None:
|
|
@@ -784,7 +785,7 @@ class Multiplexer(utils.EventEmitter):
|
|
|
784
785
|
self.connection_result = None
|
|
785
786
|
self.disconnection_result = None
|
|
786
787
|
self.open_result = None
|
|
787
|
-
self.open_pn:
|
|
788
|
+
self.open_pn: RFCOMM_MCC_PN | None = None
|
|
788
789
|
self.open_rx_max_credits = 0
|
|
789
790
|
self.acceptor = None
|
|
790
791
|
|
|
@@ -1031,8 +1032,8 @@ class Multiplexer(utils.EventEmitter):
|
|
|
1031
1032
|
|
|
1032
1033
|
# -----------------------------------------------------------------------------
|
|
1033
1034
|
class Client:
|
|
1034
|
-
multiplexer:
|
|
1035
|
-
l2cap_channel:
|
|
1035
|
+
multiplexer: Multiplexer | None
|
|
1036
|
+
l2cap_channel: l2cap.ClassicChannel | None
|
|
1036
1037
|
|
|
1037
1038
|
def __init__(
|
|
1038
1039
|
self, connection: Connection, l2cap_mtu: int = RFCOMM_DEFAULT_L2CAP_MTU
|
|
@@ -1145,7 +1146,7 @@ class Server(utils.EventEmitter):
|
|
|
1145
1146
|
# Notify
|
|
1146
1147
|
self.emit(self.EVENT_START, multiplexer)
|
|
1147
1148
|
|
|
1148
|
-
def accept_dlc(self, channel_number: int) ->
|
|
1149
|
+
def accept_dlc(self, channel_number: int) -> tuple[int, int] | None:
|
|
1149
1150
|
return self.dlc_configs.get(channel_number)
|
|
1150
1151
|
|
|
1151
1152
|
def on_dlc(self, dlc: DLC) -> None:
|
bumble/sdp.py
CHANGED
|
@@ -20,7 +20,8 @@ from __future__ import annotations
|
|
|
20
20
|
import asyncio
|
|
21
21
|
import logging
|
|
22
22
|
import struct
|
|
23
|
-
from
|
|
23
|
+
from collections.abc import Iterable, Sequence
|
|
24
|
+
from typing import TYPE_CHECKING, NewType
|
|
24
25
|
|
|
25
26
|
from typing_extensions import Self
|
|
26
27
|
|
|
@@ -497,7 +498,7 @@ class ServiceAttribute:
|
|
|
497
498
|
@staticmethod
|
|
498
499
|
def find_attribute_in_list(
|
|
499
500
|
attribute_list: Iterable[ServiceAttribute], attribute_id: int
|
|
500
|
-
) ->
|
|
501
|
+
) -> DataElement | None:
|
|
501
502
|
return next(
|
|
502
503
|
(
|
|
503
504
|
attribute.value
|
|
@@ -528,7 +529,7 @@ class ServiceAttribute:
|
|
|
528
529
|
def to_string(self, with_colors=False):
|
|
529
530
|
if with_colors:
|
|
530
531
|
return (
|
|
531
|
-
f'Attribute(id={color(self.id_name(self.id),"magenta")},'
|
|
532
|
+
f'Attribute(id={color(self.id_name(self.id), "magenta")},'
|
|
532
533
|
f'value={self.value})'
|
|
533
534
|
)
|
|
534
535
|
|
|
@@ -778,11 +779,11 @@ class SDP_ServiceSearchAttributeResponse(SDP_PDU):
|
|
|
778
779
|
class Client:
|
|
779
780
|
def __init__(self, connection: Connection, mtu: int = 0) -> None:
|
|
780
781
|
self.connection = connection
|
|
781
|
-
self.channel:
|
|
782
|
+
self.channel: l2cap.ClassicChannel | None = None
|
|
782
783
|
self.mtu = mtu
|
|
783
784
|
self.request_semaphore = asyncio.Semaphore(1)
|
|
784
|
-
self.pending_request:
|
|
785
|
-
self.pending_response:
|
|
785
|
+
self.pending_request: SDP_PDU | None = None
|
|
786
|
+
self.pending_response: asyncio.futures.Future[SDP_PDU] | None = None
|
|
786
787
|
self.next_transaction_id = 0
|
|
787
788
|
|
|
788
789
|
async def connect(self) -> None:
|
|
@@ -898,7 +899,7 @@ class Client:
|
|
|
898
899
|
async def search_attributes(
|
|
899
900
|
self,
|
|
900
901
|
uuids: Iterable[core.UUID],
|
|
901
|
-
attribute_ids: Iterable[
|
|
902
|
+
attribute_ids: Iterable[int | tuple[int, int]],
|
|
902
903
|
) -> list[list[ServiceAttribute]]:
|
|
903
904
|
"""
|
|
904
905
|
Search for attributes by UUID and attribute IDs.
|
|
@@ -970,7 +971,7 @@ class Client:
|
|
|
970
971
|
async def get_attributes(
|
|
971
972
|
self,
|
|
972
973
|
service_record_handle: int,
|
|
973
|
-
attribute_ids: Iterable[
|
|
974
|
+
attribute_ids: Iterable[int | tuple[int, int]],
|
|
974
975
|
) -> list[ServiceAttribute]:
|
|
975
976
|
"""
|
|
976
977
|
Get attributes for a service.
|
|
@@ -1042,10 +1043,10 @@ class Client:
|
|
|
1042
1043
|
# -----------------------------------------------------------------------------
|
|
1043
1044
|
class Server:
|
|
1044
1045
|
CONTINUATION_STATE = bytes([0x01, 0x00])
|
|
1045
|
-
channel:
|
|
1046
|
+
channel: l2cap.ClassicChannel | None
|
|
1046
1047
|
Service = NewType('Service', list[ServiceAttribute])
|
|
1047
1048
|
service_records: dict[int, Service]
|
|
1048
|
-
current_response:
|
|
1049
|
+
current_response: None | bytes | tuple[int, list[int]]
|
|
1049
1050
|
|
|
1050
1051
|
def __init__(self, device: Device) -> None:
|
|
1051
1052
|
self.device = device
|
|
@@ -1123,7 +1124,7 @@ class Server:
|
|
|
1123
1124
|
self,
|
|
1124
1125
|
continuation_state: bytes,
|
|
1125
1126
|
transaction_id: int,
|
|
1126
|
-
) ->
|
|
1127
|
+
) -> bool | None:
|
|
1127
1128
|
# Check if this is a valid continuation
|
|
1128
1129
|
if len(continuation_state) > 1:
|
|
1129
1130
|
if (
|
bumble/smp.py
CHANGED
|
@@ -27,17 +27,9 @@ from __future__ import annotations
|
|
|
27
27
|
import asyncio
|
|
28
28
|
import enum
|
|
29
29
|
import logging
|
|
30
|
+
from collections.abc import Awaitable, Callable
|
|
30
31
|
from dataclasses import dataclass, field
|
|
31
|
-
from typing import
|
|
32
|
-
TYPE_CHECKING,
|
|
33
|
-
Any,
|
|
34
|
-
Awaitable,
|
|
35
|
-
Callable,
|
|
36
|
-
ClassVar,
|
|
37
|
-
Optional,
|
|
38
|
-
TypeVar,
|
|
39
|
-
cast,
|
|
40
|
-
)
|
|
32
|
+
from typing import TYPE_CHECKING, ClassVar, TypeVar, cast
|
|
41
33
|
|
|
42
34
|
from bumble import crypto, utils
|
|
43
35
|
from bumble.colors import color
|
|
@@ -213,10 +205,10 @@ class SMP_Command:
|
|
|
213
205
|
fields: ClassVar[Fields]
|
|
214
206
|
code: int = field(default=0, init=False)
|
|
215
207
|
name: str = field(default='', init=False)
|
|
216
|
-
_payload:
|
|
208
|
+
_payload: bytes | None = field(default=None, init=False)
|
|
217
209
|
|
|
218
210
|
@classmethod
|
|
219
|
-
def from_bytes(cls, pdu: bytes) ->
|
|
211
|
+
def from_bytes(cls, pdu: bytes) -> SMP_Command:
|
|
220
212
|
code = pdu[0]
|
|
221
213
|
|
|
222
214
|
subclass = SMP_Command.smp_classes.get(code)
|
|
@@ -554,7 +546,7 @@ class OobContext:
|
|
|
554
546
|
r: bytes
|
|
555
547
|
|
|
556
548
|
def __init__(
|
|
557
|
-
self, ecc_key:
|
|
549
|
+
self, ecc_key: crypto.EccKey | None = None, r: bytes | None = None
|
|
558
550
|
) -> None:
|
|
559
551
|
self.ecc_key = crypto.EccKey.generate() if ecc_key is None else ecc_key
|
|
560
552
|
self.r = crypto.r() if r is None else r
|
|
@@ -570,7 +562,7 @@ class OobLegacyContext:
|
|
|
570
562
|
|
|
571
563
|
tk: bytes
|
|
572
564
|
|
|
573
|
-
def __init__(self, tk:
|
|
565
|
+
def __init__(self, tk: bytes | None = None) -> None:
|
|
574
566
|
self.tk = crypto.r() if tk is None else tk
|
|
575
567
|
|
|
576
568
|
|
|
@@ -677,31 +669,31 @@ class Session:
|
|
|
677
669
|
self.stk = None
|
|
678
670
|
self.ltk_ediv = 0
|
|
679
671
|
self.ltk_rand = bytes(8)
|
|
680
|
-
self.link_key:
|
|
672
|
+
self.link_key: bytes | None = None
|
|
681
673
|
self.maximum_encryption_key_size: int = 0
|
|
682
674
|
self.initiator_key_distribution: int = 0
|
|
683
675
|
self.responder_key_distribution: int = 0
|
|
684
|
-
self.peer_random_value:
|
|
676
|
+
self.peer_random_value: bytes | None = None
|
|
685
677
|
self.peer_public_key_x: bytes = bytes(32)
|
|
686
678
|
self.peer_public_key_y = bytes(32)
|
|
687
679
|
self.peer_ltk = None
|
|
688
680
|
self.peer_ediv = None
|
|
689
|
-
self.peer_rand:
|
|
681
|
+
self.peer_rand: bytes | None = None
|
|
690
682
|
self.peer_identity_resolving_key = None
|
|
691
|
-
self.peer_bd_addr:
|
|
683
|
+
self.peer_bd_addr: Address | None = None
|
|
692
684
|
self.peer_signature_key = None
|
|
693
685
|
self.peer_expected_distributions: list[type[SMP_Command]] = []
|
|
694
686
|
self.dh_key = b''
|
|
695
687
|
self.confirm_value = None
|
|
696
|
-
self.passkey:
|
|
688
|
+
self.passkey: int | None = None
|
|
697
689
|
self.passkey_ready = asyncio.Event()
|
|
698
690
|
self.passkey_step = 0
|
|
699
691
|
self.passkey_display = False
|
|
700
692
|
self.pairing_method: PairingMethod = PairingMethod.JUST_WORKS
|
|
701
693
|
self.pairing_config = pairing_config
|
|
702
|
-
self.wait_before_continuing:
|
|
694
|
+
self.wait_before_continuing: asyncio.Future[None] | None = None
|
|
703
695
|
self.completed = False
|
|
704
|
-
self.ctkd_task:
|
|
696
|
+
self.ctkd_task: Awaitable[None] | None = None
|
|
705
697
|
|
|
706
698
|
# Decide if we're the initiator or the responder
|
|
707
699
|
self.is_initiator = is_initiator
|
|
@@ -720,7 +712,7 @@ class Session:
|
|
|
720
712
|
|
|
721
713
|
# Create a future that can be used to wait for the session to complete
|
|
722
714
|
if self.is_initiator:
|
|
723
|
-
self.pairing_result:
|
|
715
|
+
self.pairing_result: asyncio.Future[None] | None = (
|
|
724
716
|
asyncio.get_running_loop().create_future()
|
|
725
717
|
)
|
|
726
718
|
else:
|
|
@@ -828,7 +820,7 @@ class Session:
|
|
|
828
820
|
def auth_req(self) -> int:
|
|
829
821
|
return smp_auth_req(self.bonding, self.mitm, self.sc, self.keypress, self.ct2)
|
|
830
822
|
|
|
831
|
-
def get_long_term_key(self, rand: bytes, ediv: int) ->
|
|
823
|
+
def get_long_term_key(self, rand: bytes, ediv: int) -> bytes | None:
|
|
832
824
|
if not self.sc and not self.completed:
|
|
833
825
|
if rand == self.ltk_rand and ediv == self.ltk_ediv:
|
|
834
826
|
return self.stk
|
|
@@ -939,7 +931,7 @@ class Session:
|
|
|
939
931
|
self.pairing_config.delegate.display_number(self.passkey, digits=6)
|
|
940
932
|
)
|
|
941
933
|
|
|
942
|
-
def input_passkey(self, next_steps:
|
|
934
|
+
def input_passkey(self, next_steps: Callable[[], None] | None = None) -> None:
|
|
943
935
|
# Prompt the user for the passkey displayed on the peer
|
|
944
936
|
def after_input(passkey: int) -> None:
|
|
945
937
|
self.passkey = passkey
|
|
@@ -956,7 +948,7 @@ class Session:
|
|
|
956
948
|
self.prompt_user_for_number(after_input)
|
|
957
949
|
|
|
958
950
|
def display_or_input_passkey(
|
|
959
|
-
self, next_steps:
|
|
951
|
+
self, next_steps: Callable[[], None] | None = None
|
|
960
952
|
) -> None:
|
|
961
953
|
if self.passkey_display:
|
|
962
954
|
|
|
@@ -1006,7 +998,6 @@ class Session:
|
|
|
1006
998
|
self.send_command(response)
|
|
1007
999
|
|
|
1008
1000
|
def send_pairing_confirm_command(self) -> None:
|
|
1009
|
-
|
|
1010
1001
|
if self.pairing_method != PairingMethod.OOB:
|
|
1011
1002
|
self.r = crypto.r()
|
|
1012
1003
|
logger.debug(f'generated random: {self.r.hex()}')
|
|
@@ -1842,7 +1833,6 @@ class Session:
|
|
|
1842
1833
|
self.send_public_key_command()
|
|
1843
1834
|
|
|
1844
1835
|
def next_steps() -> None:
|
|
1845
|
-
|
|
1846
1836
|
if self.pairing_method in (
|
|
1847
1837
|
PairingMethod.JUST_WORKS,
|
|
1848
1838
|
PairingMethod.NUMERIC_COMPARISON,
|
|
@@ -1929,7 +1919,7 @@ class Manager(utils.EventEmitter):
|
|
|
1929
1919
|
sessions: dict[int, Session]
|
|
1930
1920
|
pairing_config_factory: Callable[[Connection], PairingConfig]
|
|
1931
1921
|
session_proxy: type[Session]
|
|
1932
|
-
_ecc_key:
|
|
1922
|
+
_ecc_key: crypto.EccKey | None
|
|
1933
1923
|
|
|
1934
1924
|
def __init__(
|
|
1935
1925
|
self,
|
|
@@ -2022,7 +2012,7 @@ class Manager(utils.EventEmitter):
|
|
|
2022
2012
|
self.device.on_pairing_start(session.connection)
|
|
2023
2013
|
|
|
2024
2014
|
async def on_pairing(
|
|
2025
|
-
self, session: Session, identity_address:
|
|
2015
|
+
self, session: Session, identity_address: Address | None, keys: PairingKeys
|
|
2026
2016
|
) -> None:
|
|
2027
2017
|
# Store the keys in the key store
|
|
2028
2018
|
if self.device.keystore and identity_address is not None:
|
|
@@ -2041,7 +2031,7 @@ class Manager(utils.EventEmitter):
|
|
|
2041
2031
|
|
|
2042
2032
|
def get_long_term_key(
|
|
2043
2033
|
self, connection: Connection, rand: bytes, ediv: int
|
|
2044
|
-
) ->
|
|
2034
|
+
) -> bytes | None:
|
|
2045
2035
|
if session := self.sessions.get(connection.handle):
|
|
2046
2036
|
return session.get_long_term_key(rand, ediv)
|
|
2047
2037
|
|
bumble/snoop.py
CHANGED
|
@@ -16,13 +16,14 @@ import datetime
|
|
|
16
16
|
import logging
|
|
17
17
|
import os
|
|
18
18
|
import struct
|
|
19
|
+
from collections.abc import Generator
|
|
19
20
|
|
|
20
21
|
# -----------------------------------------------------------------------------
|
|
21
22
|
# Imports
|
|
22
23
|
# -----------------------------------------------------------------------------
|
|
23
24
|
from contextlib import contextmanager
|
|
24
25
|
from enum import IntEnum
|
|
25
|
-
from typing import BinaryIO
|
|
26
|
+
from typing import BinaryIO
|
|
26
27
|
|
|
27
28
|
from bumble import core
|
|
28
29
|
from bumble.hci import HCI_COMMAND_PACKET, HCI_EVENT_PACKET
|
|
@@ -65,7 +66,7 @@ class BtSnooper(Snooper):
|
|
|
65
66
|
"""
|
|
66
67
|
|
|
67
68
|
IDENTIFICATION_PATTERN = b'btsnoop\0'
|
|
68
|
-
TIMESTAMP_ANCHOR = datetime.datetime(2000, 1, 1)
|
|
69
|
+
TIMESTAMP_ANCHOR = datetime.datetime(2000, 1, 1, tzinfo=datetime.timezone.utc)
|
|
69
70
|
TIMESTAMP_DELTA = 0x00E03AB44A676000
|
|
70
71
|
ONE_MS = datetime.timedelta(microseconds=1)
|
|
71
72
|
|
|
@@ -85,7 +86,13 @@ class BtSnooper(Snooper):
|
|
|
85
86
|
|
|
86
87
|
# Compute the current timestamp
|
|
87
88
|
timestamp = (
|
|
88
|
-
int(
|
|
89
|
+
int(
|
|
90
|
+
(
|
|
91
|
+
datetime.datetime.now(tz=datetime.timezone.utc)
|
|
92
|
+
- self.TIMESTAMP_ANCHOR
|
|
93
|
+
)
|
|
94
|
+
/ self.ONE_MS
|
|
95
|
+
)
|
|
89
96
|
+ self.TIMESTAMP_DELTA
|
|
90
97
|
)
|
|
91
98
|
|
|
@@ -129,7 +136,7 @@ def create_snooper(spec: str) -> Generator[Snooper, None, None]:
|
|
|
129
136
|
records will be written to that file if it can be opened/created.
|
|
130
137
|
The keyword args that may be referenced by the string pattern are:
|
|
131
138
|
now: the value of `datetime.now()`
|
|
132
|
-
utcnow: the value of `datetime.
|
|
139
|
+
utcnow: the value of `datetime.now(tz=datetime.timezone.utc)`
|
|
133
140
|
pid: the current process ID.
|
|
134
141
|
instance: the instance ID in the current process.
|
|
135
142
|
|
|
@@ -153,7 +160,7 @@ def create_snooper(spec: str) -> Generator[Snooper, None, None]:
|
|
|
153
160
|
global _SNOOPER_INSTANCE_COUNT
|
|
154
161
|
file_path = io_name.format(
|
|
155
162
|
now=datetime.datetime.now(),
|
|
156
|
-
utcnow=datetime.datetime.
|
|
163
|
+
utcnow=datetime.datetime.now(tz=datetime.timezone.utc),
|
|
157
164
|
pid=os.getpid(),
|
|
158
165
|
instance=_SNOOPER_INSTANCE_COUNT,
|
|
159
166
|
)
|
|
@@ -27,7 +27,7 @@ import sys
|
|
|
27
27
|
import yaml
|
|
28
28
|
|
|
29
29
|
# -----------------------------------------------------------------------------
|
|
30
|
-
with open(sys.argv[1]
|
|
30
|
+
with open(sys.argv[1]) as yaml_file:
|
|
31
31
|
root = yaml.safe_load(yaml_file)
|
|
32
32
|
companies = {}
|
|
33
33
|
for company in root["company_identifiers"]:
|
bumble/tools/intel_util.py
CHANGED
|
@@ -18,7 +18,7 @@ import asyncio
|
|
|
18
18
|
# Imports
|
|
19
19
|
# -----------------------------------------------------------------------------
|
|
20
20
|
import logging
|
|
21
|
-
from typing import Any
|
|
21
|
+
from typing import Any
|
|
22
22
|
|
|
23
23
|
import click
|
|
24
24
|
|
|
@@ -47,7 +47,7 @@ def print_device_info(device_info: dict[intel.ValueType, Any]) -> None:
|
|
|
47
47
|
|
|
48
48
|
|
|
49
49
|
# -----------------------------------------------------------------------------
|
|
50
|
-
async def get_driver(host: Host, force: bool) ->
|
|
50
|
+
async def get_driver(host: Host, force: bool) -> intel.Driver | None:
|
|
51
51
|
# Create a driver
|
|
52
52
|
driver = await intel.Driver.for_host(host, force)
|
|
53
53
|
if driver is None:
|
bumble/tools/rtk_fw_download.py
CHANGED
|
@@ -21,11 +21,11 @@ import urllib.error
|
|
|
21
21
|
import urllib.request
|
|
22
22
|
|
|
23
23
|
import click
|
|
24
|
+
from bumble.tools import rtk_util
|
|
24
25
|
|
|
25
26
|
import bumble.logging
|
|
26
27
|
from bumble.colors import color
|
|
27
28
|
from bumble.drivers import rtk
|
|
28
|
-
from bumble.tools import rtk_util
|
|
29
29
|
|
|
30
30
|
# -----------------------------------------------------------------------------
|
|
31
31
|
# Logging
|
bumble/tools/rtk_util.py
CHANGED
bumble/transport/__init__.py
CHANGED
|
@@ -18,7 +18,6 @@
|
|
|
18
18
|
import logging
|
|
19
19
|
import os
|
|
20
20
|
import re
|
|
21
|
-
from typing import Optional
|
|
22
21
|
|
|
23
22
|
from bumble import utils
|
|
24
23
|
from bumble.snoop import create_snooper
|
|
@@ -111,7 +110,7 @@ async def open_transport(name: str) -> Transport:
|
|
|
111
110
|
|
|
112
111
|
|
|
113
112
|
# -----------------------------------------------------------------------------
|
|
114
|
-
async def _open_transport(scheme: str, spec:
|
|
113
|
+
async def _open_transport(scheme: str, spec: str | None) -> Transport:
|
|
115
114
|
# pylint: disable=import-outside-toplevel
|
|
116
115
|
# pylint: disable=too-many-return-statements
|
|
117
116
|
|