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/link.py
CHANGED
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
12
|
# See the License for the specific language governing permissions and
|
|
13
13
|
# limitations under the License.
|
|
14
|
+
from __future__ import annotations
|
|
14
15
|
|
|
15
16
|
import asyncio
|
|
16
17
|
|
|
@@ -18,18 +19,12 @@ import asyncio
|
|
|
18
19
|
# Imports
|
|
19
20
|
# -----------------------------------------------------------------------------
|
|
20
21
|
import logging
|
|
21
|
-
from typing import
|
|
22
|
+
from typing import TYPE_CHECKING
|
|
22
23
|
|
|
23
|
-
from bumble import
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
HCI_SUCCESS,
|
|
28
|
-
HCI_UNKNOWN_CONNECTION_IDENTIFIER_ERROR,
|
|
29
|
-
Address,
|
|
30
|
-
HCI_Connection_Complete_Event,
|
|
31
|
-
Role,
|
|
32
|
-
)
|
|
24
|
+
from bumble import core, hci, ll, lmp
|
|
25
|
+
|
|
26
|
+
if TYPE_CHECKING:
|
|
27
|
+
from bumble import controller
|
|
33
28
|
|
|
34
29
|
# -----------------------------------------------------------------------------
|
|
35
30
|
# Logging
|
|
@@ -37,18 +32,6 @@ from bumble.hci import (
|
|
|
37
32
|
logger = logging.getLogger(__name__)
|
|
38
33
|
|
|
39
34
|
|
|
40
|
-
# -----------------------------------------------------------------------------
|
|
41
|
-
# Utils
|
|
42
|
-
# -----------------------------------------------------------------------------
|
|
43
|
-
def parse_parameters(params_str):
|
|
44
|
-
result = {}
|
|
45
|
-
for param_str in params_str.split(','):
|
|
46
|
-
if '=' in param_str:
|
|
47
|
-
key, value = param_str.split('=')
|
|
48
|
-
result[key] = value
|
|
49
|
-
return result
|
|
50
|
-
|
|
51
|
-
|
|
52
35
|
# -----------------------------------------------------------------------------
|
|
53
36
|
# TODO: add more support for various LL exchanges
|
|
54
37
|
# (see Vol 6, Part B - 2.4 DATA CHANNEL PDU)
|
|
@@ -62,37 +45,34 @@ class LocalLink:
|
|
|
62
45
|
|
|
63
46
|
def __init__(self):
|
|
64
47
|
self.controllers = set()
|
|
65
|
-
self.pending_connection = None
|
|
66
48
|
self.pending_classic_connection = None
|
|
67
49
|
|
|
68
50
|
############################################################
|
|
69
51
|
# Common utils
|
|
70
52
|
############################################################
|
|
71
53
|
|
|
72
|
-
def add_controller(self, controller):
|
|
54
|
+
def add_controller(self, controller: controller.Controller):
|
|
73
55
|
logger.debug(f'new controller: {controller}')
|
|
74
56
|
self.controllers.add(controller)
|
|
75
57
|
|
|
76
|
-
def remove_controller(self, controller):
|
|
58
|
+
def remove_controller(self, controller: controller.Controller):
|
|
77
59
|
self.controllers.remove(controller)
|
|
78
60
|
|
|
79
|
-
def
|
|
61
|
+
def find_le_controller(self, address: hci.Address) -> controller.Controller | None:
|
|
80
62
|
for controller in self.controllers:
|
|
81
|
-
|
|
82
|
-
|
|
63
|
+
for connection in controller.le_connections.values():
|
|
64
|
+
if connection.self_address == address:
|
|
65
|
+
return controller
|
|
83
66
|
return None
|
|
84
67
|
|
|
85
68
|
def find_classic_controller(
|
|
86
|
-
self, address: Address
|
|
87
|
-
) ->
|
|
69
|
+
self, address: hci.Address
|
|
70
|
+
) -> controller.Controller | None:
|
|
88
71
|
for controller in self.controllers:
|
|
89
72
|
if controller.public_address == address:
|
|
90
73
|
return controller
|
|
91
74
|
return None
|
|
92
75
|
|
|
93
|
-
def get_pending_connection(self):
|
|
94
|
-
return self.pending_connection
|
|
95
|
-
|
|
96
76
|
############################################################
|
|
97
77
|
# LE handlers
|
|
98
78
|
############################################################
|
|
@@ -100,16 +80,16 @@ class LocalLink:
|
|
|
100
80
|
def on_address_changed(self, controller):
|
|
101
81
|
pass
|
|
102
82
|
|
|
103
|
-
def
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
83
|
+
def send_acl_data(
|
|
84
|
+
self,
|
|
85
|
+
sender_controller: controller.Controller,
|
|
86
|
+
destination_address: hci.Address,
|
|
87
|
+
transport: core.PhysicalTransport,
|
|
88
|
+
data: bytes,
|
|
89
|
+
):
|
|
110
90
|
# Send the data to the first controller with a matching address
|
|
111
91
|
if transport == core.PhysicalTransport.LE:
|
|
112
|
-
destination_controller = self.
|
|
92
|
+
destination_controller = self.find_le_controller(destination_address)
|
|
113
93
|
source_address = sender_controller.random_address
|
|
114
94
|
elif transport == core.PhysicalTransport.BR_EDR:
|
|
115
95
|
destination_controller = self.find_classic_controller(destination_address)
|
|
@@ -118,262 +98,52 @@ class LocalLink:
|
|
|
118
98
|
raise ValueError("unsupported transport type")
|
|
119
99
|
|
|
120
100
|
if destination_controller is not None:
|
|
121
|
-
destination_controller.on_link_acl_data(source_address, transport, data)
|
|
122
|
-
|
|
123
|
-
def on_connection_complete(self):
|
|
124
|
-
# Check that we expect this call
|
|
125
|
-
if not self.pending_connection:
|
|
126
|
-
logger.warning('on_connection_complete with no pending connection')
|
|
127
|
-
return
|
|
128
|
-
|
|
129
|
-
central_address, le_create_connection_command = self.pending_connection
|
|
130
|
-
self.pending_connection = None
|
|
131
|
-
|
|
132
|
-
# Find the controller that initiated the connection
|
|
133
|
-
if not (central_controller := self.find_controller(central_address)):
|
|
134
|
-
logger.warning('!!! Initiating controller not found')
|
|
135
|
-
return
|
|
136
|
-
|
|
137
|
-
# Connect to the first controller with a matching address
|
|
138
|
-
if peripheral_controller := self.find_controller(
|
|
139
|
-
le_create_connection_command.peer_address
|
|
140
|
-
):
|
|
141
|
-
central_controller.on_link_peripheral_connection_complete(
|
|
142
|
-
le_create_connection_command, HCI_SUCCESS
|
|
143
|
-
)
|
|
144
|
-
peripheral_controller.on_link_central_connected(central_address)
|
|
145
|
-
return
|
|
146
|
-
|
|
147
|
-
# No peripheral found
|
|
148
|
-
central_controller.on_link_peripheral_connection_complete(
|
|
149
|
-
le_create_connection_command, HCI_CONNECTION_ACCEPT_TIMEOUT_ERROR
|
|
150
|
-
)
|
|
151
|
-
|
|
152
|
-
def connect(self, central_address, le_create_connection_command):
|
|
153
|
-
logger.debug(
|
|
154
|
-
f'$$$ CONNECTION {central_address} -> '
|
|
155
|
-
f'{le_create_connection_command.peer_address}'
|
|
156
|
-
)
|
|
157
|
-
self.pending_connection = (central_address, le_create_connection_command)
|
|
158
|
-
asyncio.get_running_loop().call_soon(self.on_connection_complete)
|
|
159
|
-
|
|
160
|
-
def on_disconnection_complete(
|
|
161
|
-
self, initiating_address, target_address, disconnect_command
|
|
162
|
-
):
|
|
163
|
-
# Find the controller that initiated the disconnection
|
|
164
|
-
if not (initiating_controller := self.find_controller(initiating_address)):
|
|
165
|
-
logger.warning('!!! Initiating controller not found')
|
|
166
|
-
return
|
|
167
|
-
|
|
168
|
-
# Disconnect from the first controller with a matching address
|
|
169
|
-
if target_controller := self.find_controller(target_address):
|
|
170
|
-
target_controller.on_link_disconnected(
|
|
171
|
-
initiating_address, disconnect_command.reason
|
|
172
|
-
)
|
|
173
|
-
|
|
174
|
-
initiating_controller.on_link_disconnection_complete(
|
|
175
|
-
disconnect_command, HCI_SUCCESS
|
|
176
|
-
)
|
|
177
|
-
|
|
178
|
-
def disconnect(self, initiating_address, target_address, disconnect_command):
|
|
179
|
-
logger.debug(
|
|
180
|
-
f'$$$ DISCONNECTION {initiating_address} -> '
|
|
181
|
-
f'{target_address}: reason = {disconnect_command.reason}'
|
|
182
|
-
)
|
|
183
|
-
args = [initiating_address, target_address, disconnect_command]
|
|
184
|
-
asyncio.get_running_loop().call_soon(self.on_disconnection_complete, *args)
|
|
185
|
-
|
|
186
|
-
# pylint: disable=too-many-arguments
|
|
187
|
-
def on_connection_encrypted(
|
|
188
|
-
self, central_address, peripheral_address, rand, ediv, ltk
|
|
189
|
-
):
|
|
190
|
-
logger.debug(f'*** ENCRYPTION {central_address} -> {peripheral_address}')
|
|
191
|
-
|
|
192
|
-
if central_controller := self.find_controller(central_address):
|
|
193
|
-
central_controller.on_link_encrypted(peripheral_address, rand, ediv, ltk)
|
|
194
|
-
|
|
195
|
-
if peripheral_controller := self.find_controller(peripheral_address):
|
|
196
|
-
peripheral_controller.on_link_encrypted(central_address, rand, ediv, ltk)
|
|
197
|
-
|
|
198
|
-
def create_cis(
|
|
199
|
-
self,
|
|
200
|
-
central_controller: controller.Controller,
|
|
201
|
-
peripheral_address: Address,
|
|
202
|
-
cig_id: int,
|
|
203
|
-
cis_id: int,
|
|
204
|
-
) -> None:
|
|
205
|
-
logger.debug(
|
|
206
|
-
f'$$$ CIS Request {central_controller.random_address} -> {peripheral_address}'
|
|
207
|
-
)
|
|
208
|
-
if peripheral_controller := self.find_controller(peripheral_address):
|
|
209
101
|
asyncio.get_running_loop().call_soon(
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
cis_id,
|
|
102
|
+
lambda: destination_controller.on_link_acl_data(
|
|
103
|
+
source_address, transport, data
|
|
104
|
+
)
|
|
214
105
|
)
|
|
215
106
|
|
|
216
|
-
def
|
|
107
|
+
def send_advertising_pdu(
|
|
217
108
|
self,
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
)
|
|
226
|
-
if central_controller := self.find_controller(central_address):
|
|
227
|
-
asyncio.get_running_loop().call_soon(
|
|
228
|
-
central_controller.on_link_cis_established, cig_id, cis_id
|
|
229
|
-
)
|
|
230
|
-
asyncio.get_running_loop().call_soon(
|
|
231
|
-
peripheral_controller.on_link_cis_established, cig_id, cis_id
|
|
232
|
-
)
|
|
109
|
+
sender_controller: controller.Controller,
|
|
110
|
+
packet: ll.AdvertisingPdu,
|
|
111
|
+
):
|
|
112
|
+
loop = asyncio.get_running_loop()
|
|
113
|
+
for c in self.controllers:
|
|
114
|
+
if c != sender_controller:
|
|
115
|
+
loop.call_soon(c.on_ll_advertising_pdu, packet)
|
|
233
116
|
|
|
234
|
-
def
|
|
117
|
+
def send_ll_control_pdu(
|
|
235
118
|
self,
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
)
|
|
244
|
-
if peer_controller := self.find_controller(peer_address):
|
|
245
|
-
asyncio.get_running_loop().call_soon(
|
|
246
|
-
initiator_controller.on_link_cis_disconnected, cig_id, cis_id
|
|
247
|
-
)
|
|
248
|
-
asyncio.get_running_loop().call_soon(
|
|
249
|
-
peer_controller.on_link_cis_disconnected, cig_id, cis_id
|
|
119
|
+
sender_address: hci.Address,
|
|
120
|
+
receiver_address: hci.Address,
|
|
121
|
+
packet: ll.ControlPdu,
|
|
122
|
+
):
|
|
123
|
+
if not (receiver_controller := self.find_le_controller(receiver_address)):
|
|
124
|
+
raise core.InvalidArgumentError(
|
|
125
|
+
f"Unable to find controller for address {receiver_address}"
|
|
250
126
|
)
|
|
127
|
+
asyncio.get_running_loop().call_soon(
|
|
128
|
+
lambda: receiver_controller.on_ll_control_pdu(sender_address, packet)
|
|
129
|
+
)
|
|
251
130
|
|
|
252
131
|
############################################################
|
|
253
132
|
# Classic handlers
|
|
254
133
|
############################################################
|
|
255
134
|
|
|
256
|
-
def
|
|
257
|
-
logger.debug(
|
|
258
|
-
f'[Classic] {initiator_controller.public_address} connects to {responder_address}'
|
|
259
|
-
)
|
|
260
|
-
responder_controller = self.find_classic_controller(responder_address)
|
|
261
|
-
if responder_controller is None:
|
|
262
|
-
initiator_controller.on_classic_connection_complete(
|
|
263
|
-
responder_address, HCI_PAGE_TIMEOUT_ERROR
|
|
264
|
-
)
|
|
265
|
-
return
|
|
266
|
-
self.pending_classic_connection = (initiator_controller, responder_controller)
|
|
267
|
-
|
|
268
|
-
responder_controller.on_classic_connection_request(
|
|
269
|
-
initiator_controller.public_address,
|
|
270
|
-
HCI_Connection_Complete_Event.LinkType.ACL,
|
|
271
|
-
)
|
|
272
|
-
|
|
273
|
-
def classic_accept_connection(
|
|
274
|
-
self, responder_controller, initiator_address, responder_role
|
|
275
|
-
):
|
|
276
|
-
logger.debug(
|
|
277
|
-
f'[Classic] {responder_controller.public_address} accepts to connect {initiator_address}'
|
|
278
|
-
)
|
|
279
|
-
initiator_controller = self.find_classic_controller(initiator_address)
|
|
280
|
-
if initiator_controller is None:
|
|
281
|
-
responder_controller.on_classic_connection_complete(
|
|
282
|
-
responder_controller.public_address, HCI_PAGE_TIMEOUT_ERROR
|
|
283
|
-
)
|
|
284
|
-
return
|
|
285
|
-
|
|
286
|
-
async def task():
|
|
287
|
-
if responder_role != Role.PERIPHERAL:
|
|
288
|
-
initiator_controller.on_classic_role_change(
|
|
289
|
-
responder_controller.public_address, int(not (responder_role))
|
|
290
|
-
)
|
|
291
|
-
initiator_controller.on_classic_connection_complete(
|
|
292
|
-
responder_controller.public_address, HCI_SUCCESS
|
|
293
|
-
)
|
|
294
|
-
|
|
295
|
-
asyncio.create_task(task())
|
|
296
|
-
responder_controller.on_classic_role_change(
|
|
297
|
-
initiator_controller.public_address, responder_role
|
|
298
|
-
)
|
|
299
|
-
responder_controller.on_classic_connection_complete(
|
|
300
|
-
initiator_controller.public_address, HCI_SUCCESS
|
|
301
|
-
)
|
|
302
|
-
self.pending_classic_connection = None
|
|
303
|
-
|
|
304
|
-
def classic_disconnect(self, initiator_controller, responder_address, reason):
|
|
305
|
-
logger.debug(
|
|
306
|
-
f'[Classic] {initiator_controller.public_address} disconnects {responder_address}'
|
|
307
|
-
)
|
|
308
|
-
responder_controller = self.find_classic_controller(responder_address)
|
|
309
|
-
|
|
310
|
-
async def task():
|
|
311
|
-
initiator_controller.on_classic_disconnected(responder_address, reason)
|
|
312
|
-
|
|
313
|
-
asyncio.create_task(task())
|
|
314
|
-
responder_controller.on_classic_disconnected(
|
|
315
|
-
initiator_controller.public_address, reason
|
|
316
|
-
)
|
|
317
|
-
|
|
318
|
-
def classic_switch_role(
|
|
319
|
-
self, initiator_controller, responder_address, initiator_new_role
|
|
320
|
-
):
|
|
321
|
-
responder_controller = self.find_classic_controller(responder_address)
|
|
322
|
-
if responder_controller is None:
|
|
323
|
-
return
|
|
324
|
-
|
|
325
|
-
async def task():
|
|
326
|
-
initiator_controller.on_classic_role_change(
|
|
327
|
-
responder_address, initiator_new_role
|
|
328
|
-
)
|
|
329
|
-
|
|
330
|
-
asyncio.create_task(task())
|
|
331
|
-
responder_controller.on_classic_role_change(
|
|
332
|
-
initiator_controller.public_address, int(not (initiator_new_role))
|
|
333
|
-
)
|
|
334
|
-
|
|
335
|
-
def classic_sco_connect(
|
|
336
|
-
self,
|
|
337
|
-
initiator_controller: controller.Controller,
|
|
338
|
-
responder_address: Address,
|
|
339
|
-
link_type: int,
|
|
340
|
-
):
|
|
341
|
-
logger.debug(
|
|
342
|
-
f'[Classic] {initiator_controller.public_address} connects SCO to {responder_address}'
|
|
343
|
-
)
|
|
344
|
-
responder_controller = self.find_classic_controller(responder_address)
|
|
345
|
-
# Initiator controller should handle it.
|
|
346
|
-
assert responder_controller
|
|
347
|
-
|
|
348
|
-
responder_controller.on_classic_connection_request(
|
|
349
|
-
initiator_controller.public_address,
|
|
350
|
-
link_type,
|
|
351
|
-
)
|
|
352
|
-
|
|
353
|
-
def classic_accept_sco_connection(
|
|
135
|
+
def send_lmp_packet(
|
|
354
136
|
self,
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
137
|
+
sender_controller: controller.Controller,
|
|
138
|
+
receiver_address: hci.Address,
|
|
139
|
+
packet: lmp.Packet,
|
|
358
140
|
):
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
initiator_controller = self.find_classic_controller(initiator_address)
|
|
363
|
-
if initiator_controller is None:
|
|
364
|
-
responder_controller.on_classic_sco_connection_complete(
|
|
365
|
-
responder_controller.public_address,
|
|
366
|
-
HCI_UNKNOWN_CONNECTION_IDENTIFIER_ERROR,
|
|
367
|
-
link_type,
|
|
141
|
+
if not (receiver_controller := self.find_classic_controller(receiver_address)):
|
|
142
|
+
raise core.InvalidArgumentError(
|
|
143
|
+
f"Unable to find controller for address {receiver_address}"
|
|
368
144
|
)
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
initiator_controller.on_classic_sco_connection_complete(
|
|
373
|
-
responder_controller.public_address, HCI_SUCCESS, link_type
|
|
145
|
+
asyncio.get_running_loop().call_soon(
|
|
146
|
+
lambda: receiver_controller.on_lmp_packet(
|
|
147
|
+
sender_controller.public_address, packet
|
|
374
148
|
)
|
|
375
|
-
|
|
376
|
-
asyncio.create_task(task())
|
|
377
|
-
responder_controller.on_classic_sco_connection_complete(
|
|
378
|
-
initiator_controller.public_address, HCI_SUCCESS, link_type
|
|
379
149
|
)
|
bumble/ll.py
ADDED
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
# Copyright 2021-2025 Google LLC
|
|
2
|
+
#
|
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
# you may not use this file except in compliance with the License.
|
|
5
|
+
# You may obtain a copy of the License at
|
|
6
|
+
#
|
|
7
|
+
# https://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
#
|
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
# See the License for the specific language governing permissions and
|
|
13
|
+
# limitations under the License.
|
|
14
|
+
|
|
15
|
+
# -----------------------------------------------------------------------------
|
|
16
|
+
# Imports
|
|
17
|
+
# -----------------------------------------------------------------------------
|
|
18
|
+
from __future__ import annotations
|
|
19
|
+
|
|
20
|
+
import dataclasses
|
|
21
|
+
from typing import ClassVar
|
|
22
|
+
|
|
23
|
+
from bumble import hci
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
# -----------------------------------------------------------------------------
|
|
27
|
+
# Advertising PDU
|
|
28
|
+
# -----------------------------------------------------------------------------
|
|
29
|
+
class AdvertisingPdu:
|
|
30
|
+
"""Base Advertising Physical Channel PDU class.
|
|
31
|
+
|
|
32
|
+
See Core Spec 6.0, Volume 6, Part B, 2.3. Advertising physical channel PDU.
|
|
33
|
+
|
|
34
|
+
Currently these messages don't really follow the LL spec, because LL protocol is
|
|
35
|
+
context-aware and we don't have real physical transport.
|
|
36
|
+
"""
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
@dataclasses.dataclass
|
|
40
|
+
class ConnectInd(AdvertisingPdu):
|
|
41
|
+
initiator_address: hci.Address
|
|
42
|
+
advertiser_address: hci.Address
|
|
43
|
+
interval: int
|
|
44
|
+
latency: int
|
|
45
|
+
timeout: int
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
@dataclasses.dataclass
|
|
49
|
+
class AdvInd(AdvertisingPdu):
|
|
50
|
+
advertiser_address: hci.Address
|
|
51
|
+
data: bytes
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
@dataclasses.dataclass
|
|
55
|
+
class AdvDirectInd(AdvertisingPdu):
|
|
56
|
+
advertiser_address: hci.Address
|
|
57
|
+
target_address: hci.Address
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
@dataclasses.dataclass
|
|
61
|
+
class AdvNonConnInd(AdvertisingPdu):
|
|
62
|
+
advertiser_address: hci.Address
|
|
63
|
+
data: bytes
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
@dataclasses.dataclass
|
|
67
|
+
class AdvExtInd(AdvertisingPdu):
|
|
68
|
+
advertiser_address: hci.Address
|
|
69
|
+
data: bytes
|
|
70
|
+
|
|
71
|
+
target_address: hci.Address | None = None
|
|
72
|
+
adi: int | None = None
|
|
73
|
+
tx_power: int | None = None
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
# -----------------------------------------------------------------------------
|
|
77
|
+
# LL Control PDU
|
|
78
|
+
# -----------------------------------------------------------------------------
|
|
79
|
+
class ControlPdu:
|
|
80
|
+
"""Base LL Control PDU Class.
|
|
81
|
+
|
|
82
|
+
See Core Spec 6.0, Volume 6, Part B, 2.4.2. LL Control PDU.
|
|
83
|
+
|
|
84
|
+
Currently these messages don't really follow the LL spec, because LL protocol is
|
|
85
|
+
context-aware and we don't have real physical transport.
|
|
86
|
+
"""
|
|
87
|
+
|
|
88
|
+
class Opcode(hci.SpecableEnum):
|
|
89
|
+
LL_CONNECTION_UPDATE_IND = 0x00
|
|
90
|
+
LL_CHANNEL_MAP_IND = 0x01
|
|
91
|
+
LL_TERMINATE_IND = 0x02
|
|
92
|
+
LL_ENC_REQ = 0x03
|
|
93
|
+
LL_ENC_RSP = 0x04
|
|
94
|
+
LL_START_ENC_REQ = 0x05
|
|
95
|
+
LL_START_ENC_RSP = 0x06
|
|
96
|
+
LL_UNKNOWN_RSP = 0x07
|
|
97
|
+
LL_FEATURE_REQ = 0x08
|
|
98
|
+
LL_FEATURE_RSP = 0x09
|
|
99
|
+
LL_PAUSE_ENC_REQ = 0x0A
|
|
100
|
+
LL_PAUSE_ENC_RSP = 0x0B
|
|
101
|
+
LL_VERSION_IND = 0x0C
|
|
102
|
+
LL_REJECT_IND = 0x0D
|
|
103
|
+
LL_PERIPHERAL_FEATURE_REQ = 0x0E
|
|
104
|
+
LL_CONNECTION_PARAM_REQ = 0x0F
|
|
105
|
+
LL_CONNECTION_PARAM_RSP = 0x10
|
|
106
|
+
LL_REJECT_EXT_IND = 0x11
|
|
107
|
+
LL_PING_REQ = 0x12
|
|
108
|
+
LL_PING_RSP = 0x13
|
|
109
|
+
LL_LENGTH_REQ = 0x14
|
|
110
|
+
LL_LENGTH_RSP = 0x15
|
|
111
|
+
LL_PHY_REQ = 0x16
|
|
112
|
+
LL_PHY_RSP = 0x17
|
|
113
|
+
LL_PHY_UPDATE_IND = 0x18
|
|
114
|
+
LL_MIN_USED_CHANNELS_IND = 0x19
|
|
115
|
+
LL_CTE_REQ = 0x1A
|
|
116
|
+
LL_CTE_RSP = 0x1B
|
|
117
|
+
LL_PERIODIC_SYNC_IND = 0x1C
|
|
118
|
+
LL_CLOCK_ACCURACY_REQ = 0x1D
|
|
119
|
+
LL_CLOCK_ACCURACY_RSP = 0x1E
|
|
120
|
+
LL_CIS_REQ = 0x1F
|
|
121
|
+
LL_CIS_RSP = 0x20
|
|
122
|
+
LL_CIS_IND = 0x21
|
|
123
|
+
LL_CIS_TERMINATE_IND = 0x22
|
|
124
|
+
LL_POWER_CONTROL_REQ = 0x23
|
|
125
|
+
LL_POWER_CONTROL_RSP = 0x24
|
|
126
|
+
LL_POWER_CHANGE_IND = 0x25
|
|
127
|
+
LL_SUBRATE_REQ = 0x26
|
|
128
|
+
LL_SUBRATE_IND = 0x27
|
|
129
|
+
LL_CHANNEL_REPORTING_IND = 0x28
|
|
130
|
+
LL_CHANNEL_STATUS_IND = 0x29
|
|
131
|
+
LL_PERIODIC_SYNC_WR_IND = 0x2A
|
|
132
|
+
LL_FEATURE_EXT_REQ = 0x2B
|
|
133
|
+
LL_FEATURE_EXT_RSP = 0x2C
|
|
134
|
+
LL_CS_SEC_RSP = 0x2D
|
|
135
|
+
LL_CS_CAPABILITIES_REQ = 0x2E
|
|
136
|
+
LL_CS_CAPABILITIES_RSP = 0x2F
|
|
137
|
+
LL_CS_CONFIG_REQ = 0x30
|
|
138
|
+
LL_CS_CONFIG_RSP = 0x31
|
|
139
|
+
LL_CS_REQ = 0x32
|
|
140
|
+
LL_CS_RSP = 0x33
|
|
141
|
+
LL_CS_IND = 0x34
|
|
142
|
+
LL_CS_TERMINATE_REQ = 0x35
|
|
143
|
+
LL_CS_FAE_REQ = 0x36
|
|
144
|
+
LL_CS_FAE_RSP = 0x37
|
|
145
|
+
LL_CS_CHANNEL_MAP_IND = 0x38
|
|
146
|
+
LL_CS_SEC_REQ = 0x39
|
|
147
|
+
LL_CS_TERMINATE_RSP = 0x3A
|
|
148
|
+
LL_FRAME_SPACE_REQ = 0x3B
|
|
149
|
+
LL_FRAME_SPACE_RSP = 0x3C
|
|
150
|
+
|
|
151
|
+
opcode: ClassVar[Opcode]
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
@dataclasses.dataclass
|
|
155
|
+
class TerminateInd(ControlPdu):
|
|
156
|
+
opcode = ControlPdu.Opcode.LL_TERMINATE_IND
|
|
157
|
+
|
|
158
|
+
error_code: int
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
@dataclasses.dataclass
|
|
162
|
+
class EncReq(ControlPdu):
|
|
163
|
+
opcode = ControlPdu.Opcode.LL_ENC_REQ
|
|
164
|
+
|
|
165
|
+
rand: bytes
|
|
166
|
+
ediv: int
|
|
167
|
+
ltk: bytes
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
@dataclasses.dataclass
|
|
171
|
+
class CisReq(ControlPdu):
|
|
172
|
+
opcode = ControlPdu.Opcode.LL_CIS_REQ
|
|
173
|
+
|
|
174
|
+
cig_id: int
|
|
175
|
+
cis_id: int
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
@dataclasses.dataclass
|
|
179
|
+
class CisRsp(ControlPdu):
|
|
180
|
+
opcode = ControlPdu.Opcode.LL_CIS_REQ
|
|
181
|
+
|
|
182
|
+
cig_id: int
|
|
183
|
+
cis_id: int
|
|
184
|
+
|
|
185
|
+
|
|
186
|
+
@dataclasses.dataclass
|
|
187
|
+
class CisInd(ControlPdu):
|
|
188
|
+
opcode = ControlPdu.Opcode.LL_CIS_REQ
|
|
189
|
+
|
|
190
|
+
cig_id: int
|
|
191
|
+
cis_id: int
|
|
192
|
+
|
|
193
|
+
|
|
194
|
+
@dataclasses.dataclass
|
|
195
|
+
class CisTerminateInd(ControlPdu):
|
|
196
|
+
opcode = ControlPdu.Opcode.LL_CIS_TERMINATE_IND
|
|
197
|
+
|
|
198
|
+
cig_id: int
|
|
199
|
+
cis_id: int
|
|
200
|
+
error_code: int
|