bumble 0.0.179__py3-none-any.whl → 0.0.181__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/apps/bench.py +110 -81
- bumble/apps/ble_rpa_tool.py +63 -0
- bumble/apps/console.py +4 -4
- bumble/apps/controller_info.py +34 -2
- bumble/apps/pair.py +6 -8
- bumble/att.py +53 -11
- bumble/controller.py +28 -1
- bumble/crypto.py +10 -0
- bumble/device.py +630 -134
- bumble/drivers/__init__.py +27 -31
- bumble/drivers/common.py +45 -0
- bumble/drivers/rtk.py +11 -4
- bumble/gatt.py +183 -58
- bumble/gatt_client.py +56 -20
- bumble/gatt_server.py +30 -22
- bumble/hci.py +227 -92
- bumble/helpers.py +81 -42
- bumble/hfp.py +37 -27
- bumble/hid.py +282 -61
- bumble/host.py +158 -93
- bumble/l2cap.py +11 -3
- bumble/profiles/asha_service.py +2 -2
- bumble/profiles/bap.py +1247 -0
- bumble/profiles/cap.py +52 -0
- bumble/profiles/csip.py +205 -0
- bumble/rfcomm.py +24 -17
- bumble/smp.py +1 -1
- bumble/transport/__init__.py +49 -21
- bumble/transport/android_emulator.py +1 -1
- bumble/transport/common.py +3 -2
- bumble/transport/hci_socket.py +1 -4
- bumble/transport/usb.py +1 -1
- bumble/utils.py +3 -6
- {bumble-0.0.179.dist-info → bumble-0.0.181.dist-info}/METADATA +1 -1
- {bumble-0.0.179.dist-info → bumble-0.0.181.dist-info}/RECORD +40 -35
- {bumble-0.0.179.dist-info → bumble-0.0.181.dist-info}/entry_points.txt +1 -0
- {bumble-0.0.179.dist-info → bumble-0.0.181.dist-info}/LICENSE +0 -0
- {bumble-0.0.179.dist-info → bumble-0.0.181.dist-info}/WHEEL +0 -0
- {bumble-0.0.179.dist-info → bumble-0.0.181.dist-info}/top_level.txt +0 -0
bumble/profiles/cap.py
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
# Copyright 2021-2023 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
|
+
# -----------------------------------------------------------------------------
|
|
17
|
+
# Imports
|
|
18
|
+
# -----------------------------------------------------------------------------
|
|
19
|
+
from __future__ import annotations
|
|
20
|
+
|
|
21
|
+
from bumble import gatt
|
|
22
|
+
from bumble import gatt_client
|
|
23
|
+
from bumble.profiles import csip
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
# -----------------------------------------------------------------------------
|
|
27
|
+
# Server
|
|
28
|
+
# -----------------------------------------------------------------------------
|
|
29
|
+
class CommonAudioServiceService(gatt.TemplateService):
|
|
30
|
+
UUID = gatt.GATT_COMMON_AUDIO_SERVICE
|
|
31
|
+
|
|
32
|
+
def __init__(
|
|
33
|
+
self,
|
|
34
|
+
coordinated_set_identification_service: csip.CoordinatedSetIdentificationService,
|
|
35
|
+
) -> None:
|
|
36
|
+
self.coordinated_set_identification_service = (
|
|
37
|
+
coordinated_set_identification_service
|
|
38
|
+
)
|
|
39
|
+
super().__init__(
|
|
40
|
+
characteristics=[],
|
|
41
|
+
included_services=[coordinated_set_identification_service],
|
|
42
|
+
)
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
# -----------------------------------------------------------------------------
|
|
46
|
+
# Client
|
|
47
|
+
# -----------------------------------------------------------------------------
|
|
48
|
+
class CommonAudioServiceServiceProxy(gatt_client.ProfileServiceProxy):
|
|
49
|
+
SERVICE_CLASS = CommonAudioServiceService
|
|
50
|
+
|
|
51
|
+
def __init__(self, service_proxy: gatt_client.ServiceProxy) -> None:
|
|
52
|
+
self.service_proxy = service_proxy
|
bumble/profiles/csip.py
ADDED
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
# Copyright 2021-2023 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
|
+
# -----------------------------------------------------------------------------
|
|
17
|
+
# Imports
|
|
18
|
+
# -----------------------------------------------------------------------------
|
|
19
|
+
from __future__ import annotations
|
|
20
|
+
import enum
|
|
21
|
+
import struct
|
|
22
|
+
from typing import Optional
|
|
23
|
+
|
|
24
|
+
from bumble import core
|
|
25
|
+
from bumble import crypto
|
|
26
|
+
from bumble import device
|
|
27
|
+
from bumble import gatt
|
|
28
|
+
from bumble import gatt_client
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
# -----------------------------------------------------------------------------
|
|
32
|
+
# Constants
|
|
33
|
+
# -----------------------------------------------------------------------------
|
|
34
|
+
class SirkType(enum.IntEnum):
|
|
35
|
+
'''Coordinated Set Identification Service - 5.1 Set Identity Resolving Key.'''
|
|
36
|
+
|
|
37
|
+
ENCRYPTED = 0x00
|
|
38
|
+
PLAINTEXT = 0x01
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
class MemberLock(enum.IntEnum):
|
|
42
|
+
'''Coordinated Set Identification Service - 5.3 Set Member Lock.'''
|
|
43
|
+
|
|
44
|
+
UNLOCKED = 0x01
|
|
45
|
+
LOCKED = 0x02
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
# -----------------------------------------------------------------------------
|
|
49
|
+
# Crypto Toolbox
|
|
50
|
+
# -----------------------------------------------------------------------------
|
|
51
|
+
def s1(m: bytes) -> bytes:
|
|
52
|
+
'''
|
|
53
|
+
Coordinated Set Identification Service - 4.3 s1 SALT generation function.
|
|
54
|
+
'''
|
|
55
|
+
return crypto.aes_cmac(m[::-1], bytes(16))[::-1]
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def k1(n: bytes, salt: bytes, p: bytes) -> bytes:
|
|
59
|
+
'''
|
|
60
|
+
Coordinated Set Identification Service - 4.4 k1 derivation function.
|
|
61
|
+
'''
|
|
62
|
+
t = crypto.aes_cmac(n[::-1], salt[::-1])
|
|
63
|
+
return crypto.aes_cmac(p[::-1], t)[::-1]
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def sef(k: bytes, r: bytes) -> bytes:
|
|
67
|
+
'''
|
|
68
|
+
Coordinated Set Identification Service - 4.5 SIRK encryption function sef.
|
|
69
|
+
'''
|
|
70
|
+
return crypto.xor(k1(k, s1(b'SIRKenc'[::-1]), b'csis'[::-1]), r)
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def sih(k: bytes, r: bytes) -> bytes:
|
|
74
|
+
'''
|
|
75
|
+
Coordinated Set Identification Service - 4.7 Resolvable Set Identifier hash function sih.
|
|
76
|
+
'''
|
|
77
|
+
return crypto.e(k, r + bytes(13))[:3]
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def generate_rsi(sirk: bytes) -> bytes:
|
|
81
|
+
'''
|
|
82
|
+
Coordinated Set Identification Service - 4.8 Resolvable Set Identifier generation operation.
|
|
83
|
+
'''
|
|
84
|
+
prand = crypto.generate_prand()
|
|
85
|
+
return sih(sirk, prand) + prand
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
# -----------------------------------------------------------------------------
|
|
89
|
+
# Server
|
|
90
|
+
# -----------------------------------------------------------------------------
|
|
91
|
+
class CoordinatedSetIdentificationService(gatt.TemplateService):
|
|
92
|
+
UUID = gatt.GATT_COORDINATED_SET_IDENTIFICATION_SERVICE
|
|
93
|
+
|
|
94
|
+
set_identity_resolving_key: bytes
|
|
95
|
+
set_identity_resolving_key_characteristic: gatt.Characteristic
|
|
96
|
+
coordinated_set_size_characteristic: Optional[gatt.Characteristic] = None
|
|
97
|
+
set_member_lock_characteristic: Optional[gatt.Characteristic] = None
|
|
98
|
+
set_member_rank_characteristic: Optional[gatt.Characteristic] = None
|
|
99
|
+
|
|
100
|
+
def __init__(
|
|
101
|
+
self,
|
|
102
|
+
set_identity_resolving_key: bytes,
|
|
103
|
+
set_identity_resolving_key_type: SirkType,
|
|
104
|
+
coordinated_set_size: Optional[int] = None,
|
|
105
|
+
set_member_lock: Optional[MemberLock] = None,
|
|
106
|
+
set_member_rank: Optional[int] = None,
|
|
107
|
+
) -> None:
|
|
108
|
+
characteristics = []
|
|
109
|
+
|
|
110
|
+
self.set_identity_resolving_key = set_identity_resolving_key
|
|
111
|
+
self.set_identity_resolving_key_type = set_identity_resolving_key_type
|
|
112
|
+
self.set_identity_resolving_key_characteristic = gatt.Characteristic(
|
|
113
|
+
uuid=gatt.GATT_SET_IDENTITY_RESOLVING_KEY_CHARACTERISTIC,
|
|
114
|
+
properties=gatt.Characteristic.Properties.READ
|
|
115
|
+
| gatt.Characteristic.Properties.NOTIFY,
|
|
116
|
+
permissions=gatt.Characteristic.Permissions.READABLE,
|
|
117
|
+
value=gatt.CharacteristicValue(read=self.on_sirk_read),
|
|
118
|
+
)
|
|
119
|
+
characteristics.append(self.set_identity_resolving_key_characteristic)
|
|
120
|
+
|
|
121
|
+
if coordinated_set_size is not None:
|
|
122
|
+
self.coordinated_set_size_characteristic = gatt.Characteristic(
|
|
123
|
+
uuid=gatt.GATT_COORDINATED_SET_SIZE_CHARACTERISTIC,
|
|
124
|
+
properties=gatt.Characteristic.Properties.READ
|
|
125
|
+
| gatt.Characteristic.Properties.NOTIFY,
|
|
126
|
+
permissions=gatt.Characteristic.Permissions.READABLE,
|
|
127
|
+
value=struct.pack('B', coordinated_set_size),
|
|
128
|
+
)
|
|
129
|
+
characteristics.append(self.coordinated_set_size_characteristic)
|
|
130
|
+
|
|
131
|
+
if set_member_lock is not None:
|
|
132
|
+
self.set_member_lock_characteristic = gatt.Characteristic(
|
|
133
|
+
uuid=gatt.GATT_SET_MEMBER_LOCK_CHARACTERISTIC,
|
|
134
|
+
properties=gatt.Characteristic.Properties.READ
|
|
135
|
+
| gatt.Characteristic.Properties.NOTIFY
|
|
136
|
+
| gatt.Characteristic.Properties.WRITE,
|
|
137
|
+
permissions=gatt.Characteristic.Permissions.READABLE
|
|
138
|
+
| gatt.Characteristic.Permissions.WRITEABLE,
|
|
139
|
+
value=struct.pack('B', set_member_lock),
|
|
140
|
+
)
|
|
141
|
+
characteristics.append(self.set_member_lock_characteristic)
|
|
142
|
+
|
|
143
|
+
if set_member_rank is not None:
|
|
144
|
+
self.set_member_rank_characteristic = gatt.Characteristic(
|
|
145
|
+
uuid=gatt.GATT_SET_MEMBER_RANK_CHARACTERISTIC,
|
|
146
|
+
properties=gatt.Characteristic.Properties.READ
|
|
147
|
+
| gatt.Characteristic.Properties.NOTIFY,
|
|
148
|
+
permissions=gatt.Characteristic.Permissions.READABLE,
|
|
149
|
+
value=struct.pack('B', set_member_rank),
|
|
150
|
+
)
|
|
151
|
+
characteristics.append(self.set_member_rank_characteristic)
|
|
152
|
+
|
|
153
|
+
super().__init__(characteristics)
|
|
154
|
+
|
|
155
|
+
def on_sirk_read(self, _connection: Optional[device.Connection]) -> bytes:
|
|
156
|
+
if self.set_identity_resolving_key_type == SirkType.PLAINTEXT:
|
|
157
|
+
return bytes([SirkType.PLAINTEXT]) + self.set_identity_resolving_key
|
|
158
|
+
else:
|
|
159
|
+
raise NotImplementedError('TODO: Pending async Characteristic read.')
|
|
160
|
+
|
|
161
|
+
def get_advertising_data(self) -> bytes:
|
|
162
|
+
return bytes(
|
|
163
|
+
core.AdvertisingData(
|
|
164
|
+
[
|
|
165
|
+
(
|
|
166
|
+
core.AdvertisingData.RESOLVABLE_SET_IDENTIFIER,
|
|
167
|
+
generate_rsi(self.set_identity_resolving_key),
|
|
168
|
+
),
|
|
169
|
+
]
|
|
170
|
+
)
|
|
171
|
+
)
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
# -----------------------------------------------------------------------------
|
|
175
|
+
# Client
|
|
176
|
+
# -----------------------------------------------------------------------------
|
|
177
|
+
class CoordinatedSetIdentificationProxy(gatt_client.ProfileServiceProxy):
|
|
178
|
+
SERVICE_CLASS = CoordinatedSetIdentificationService
|
|
179
|
+
|
|
180
|
+
set_identity_resolving_key: gatt_client.CharacteristicProxy
|
|
181
|
+
coordinated_set_size: Optional[gatt_client.CharacteristicProxy] = None
|
|
182
|
+
set_member_lock: Optional[gatt_client.CharacteristicProxy] = None
|
|
183
|
+
set_member_rank: Optional[gatt_client.CharacteristicProxy] = None
|
|
184
|
+
|
|
185
|
+
def __init__(self, service_proxy: gatt_client.ServiceProxy) -> None:
|
|
186
|
+
self.service_proxy = service_proxy
|
|
187
|
+
|
|
188
|
+
self.set_identity_resolving_key = service_proxy.get_characteristics_by_uuid(
|
|
189
|
+
gatt.GATT_SET_IDENTITY_RESOLVING_KEY_CHARACTERISTIC
|
|
190
|
+
)[0]
|
|
191
|
+
|
|
192
|
+
if characteristics := service_proxy.get_characteristics_by_uuid(
|
|
193
|
+
gatt.GATT_COORDINATED_SET_SIZE_CHARACTERISTIC
|
|
194
|
+
):
|
|
195
|
+
self.coordinated_set_size = characteristics[0]
|
|
196
|
+
|
|
197
|
+
if characteristics := service_proxy.get_characteristics_by_uuid(
|
|
198
|
+
gatt.GATT_SET_MEMBER_LOCK_CHARACTERISTIC
|
|
199
|
+
):
|
|
200
|
+
self.set_member_lock = characteristics[0]
|
|
201
|
+
|
|
202
|
+
if characteristics := service_proxy.get_characteristics_by_uuid(
|
|
203
|
+
gatt.GATT_SET_MEMBER_RANK_CHARACTERISTIC
|
|
204
|
+
):
|
|
205
|
+
self.set_member_rank = characteristics[0]
|
bumble/rfcomm.py
CHANGED
|
@@ -118,8 +118,8 @@ CRC_TABLE = bytes([
|
|
|
118
118
|
0XBA, 0X2B, 0X59, 0XC8, 0XBD, 0X2C, 0X5E, 0XCF
|
|
119
119
|
])
|
|
120
120
|
|
|
121
|
-
|
|
122
|
-
|
|
121
|
+
RFCOMM_DEFAULT_WINDOW_SIZE = 16
|
|
122
|
+
RFCOMM_DEFAULT_MAX_FRAME_SIZE = 2000
|
|
123
123
|
|
|
124
124
|
RFCOMM_DYNAMIC_CHANNEL_NUMBER_START = 1
|
|
125
125
|
RFCOMM_DYNAMIC_CHANNEL_NUMBER_END = 30
|
|
@@ -438,14 +438,16 @@ class DLC(EventEmitter):
|
|
|
438
438
|
multiplexer: Multiplexer,
|
|
439
439
|
dlci: int,
|
|
440
440
|
max_frame_size: int,
|
|
441
|
-
|
|
441
|
+
window_size: int,
|
|
442
442
|
) -> None:
|
|
443
443
|
super().__init__()
|
|
444
444
|
self.multiplexer = multiplexer
|
|
445
445
|
self.dlci = dlci
|
|
446
|
-
self.
|
|
447
|
-
self.
|
|
448
|
-
self.
|
|
446
|
+
self.max_frame_size = max_frame_size
|
|
447
|
+
self.window_size = window_size
|
|
448
|
+
self.rx_credits = window_size
|
|
449
|
+
self.rx_threshold = window_size // 2
|
|
450
|
+
self.tx_credits = window_size
|
|
449
451
|
self.tx_buffer = b''
|
|
450
452
|
self.state = DLC.State.INIT
|
|
451
453
|
self.role = multiplexer.role
|
|
@@ -537,11 +539,11 @@ class DLC(EventEmitter):
|
|
|
537
539
|
if len(data) and self.sink:
|
|
538
540
|
self.sink(data) # pylint: disable=not-callable
|
|
539
541
|
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
542
|
+
# Update the credits
|
|
543
|
+
if self.rx_credits > 0:
|
|
544
|
+
self.rx_credits -= 1
|
|
545
|
+
else:
|
|
546
|
+
logger.warning(color('!!! received frame with no rx credits', 'red'))
|
|
545
547
|
|
|
546
548
|
# Check if there's anything to send (including credits)
|
|
547
549
|
self.process_tx()
|
|
@@ -580,9 +582,9 @@ class DLC(EventEmitter):
|
|
|
580
582
|
cl=0xE0,
|
|
581
583
|
priority=7,
|
|
582
584
|
ack_timer=0,
|
|
583
|
-
max_frame_size=
|
|
585
|
+
max_frame_size=self.max_frame_size,
|
|
584
586
|
max_retransmissions=0,
|
|
585
|
-
window_size=
|
|
587
|
+
window_size=self.window_size,
|
|
586
588
|
)
|
|
587
589
|
mcc = RFCOMM_Frame.make_mcc(mcc_type=RFCOMM_MCC_PN_TYPE, c_r=0, data=bytes(pn))
|
|
588
590
|
logger.debug(f'>>> PN Response: {pn}')
|
|
@@ -591,7 +593,7 @@ class DLC(EventEmitter):
|
|
|
591
593
|
|
|
592
594
|
def rx_credits_needed(self) -> int:
|
|
593
595
|
if self.rx_credits <= self.rx_threshold:
|
|
594
|
-
return
|
|
596
|
+
return self.window_size - self.rx_credits
|
|
595
597
|
|
|
596
598
|
return 0
|
|
597
599
|
|
|
@@ -843,7 +845,12 @@ class Multiplexer(EventEmitter):
|
|
|
843
845
|
)
|
|
844
846
|
await self.disconnection_result
|
|
845
847
|
|
|
846
|
-
async def open_dlc(
|
|
848
|
+
async def open_dlc(
|
|
849
|
+
self,
|
|
850
|
+
channel: int,
|
|
851
|
+
max_frame_size: int = RFCOMM_DEFAULT_MAX_FRAME_SIZE,
|
|
852
|
+
window_size: int = RFCOMM_DEFAULT_WINDOW_SIZE,
|
|
853
|
+
) -> DLC:
|
|
847
854
|
if self.state != Multiplexer.State.CONNECTED:
|
|
848
855
|
if self.state == Multiplexer.State.OPENING:
|
|
849
856
|
raise InvalidStateError('open already in progress')
|
|
@@ -855,9 +862,9 @@ class Multiplexer(EventEmitter):
|
|
|
855
862
|
cl=0xF0,
|
|
856
863
|
priority=7,
|
|
857
864
|
ack_timer=0,
|
|
858
|
-
max_frame_size=
|
|
865
|
+
max_frame_size=max_frame_size,
|
|
859
866
|
max_retransmissions=0,
|
|
860
|
-
window_size=
|
|
867
|
+
window_size=window_size,
|
|
861
868
|
)
|
|
862
869
|
mcc = RFCOMM_Frame.make_mcc(mcc_type=RFCOMM_MCC_PN_TYPE, c_r=1, data=bytes(pn))
|
|
863
870
|
logger.debug(f'>>> Sending MCC: {pn}')
|
bumble/smp.py
CHANGED
|
@@ -1090,7 +1090,7 @@ class Session:
|
|
|
1090
1090
|
# We can now encrypt the connection with the short term key, so that we can
|
|
1091
1091
|
# distribute the long term and/or other keys over an encrypted connection
|
|
1092
1092
|
self.manager.device.host.send_command_sync(
|
|
1093
|
-
HCI_LE_Enable_Encryption_Command(
|
|
1093
|
+
HCI_LE_Enable_Encryption_Command(
|
|
1094
1094
|
connection_handle=self.connection.handle,
|
|
1095
1095
|
random_number=bytes(8),
|
|
1096
1096
|
encrypted_diversifier=0,
|
bumble/transport/__init__.py
CHANGED
|
@@ -18,6 +18,7 @@
|
|
|
18
18
|
from contextlib import asynccontextmanager
|
|
19
19
|
import logging
|
|
20
20
|
import os
|
|
21
|
+
from typing import Optional
|
|
21
22
|
|
|
22
23
|
from .common import Transport, AsyncPipeSink, SnoopingTransport
|
|
23
24
|
from ..snoop import create_snooper
|
|
@@ -52,8 +53,16 @@ def _wrap_transport(transport: Transport) -> Transport:
|
|
|
52
53
|
async def open_transport(name: str) -> Transport:
|
|
53
54
|
"""
|
|
54
55
|
Open a transport by name.
|
|
55
|
-
The name must be <type>:<parameters>
|
|
56
|
-
Where <parameters> depend on the type (and may be empty for some types)
|
|
56
|
+
The name must be <type>:<metadata><parameters>
|
|
57
|
+
Where <parameters> depend on the type (and may be empty for some types), and
|
|
58
|
+
<metadata> is either omitted, or a ,-separated list of <key>=<value> pairs,
|
|
59
|
+
enclosed in [].
|
|
60
|
+
If there are not metadata or parameter, the : after the <type> may be omitted.
|
|
61
|
+
Examples:
|
|
62
|
+
* usb:0
|
|
63
|
+
* usb:[driver=rtk]0
|
|
64
|
+
* android-netsim
|
|
65
|
+
|
|
57
66
|
The supported types are:
|
|
58
67
|
* serial
|
|
59
68
|
* udp
|
|
@@ -71,87 +80,106 @@ async def open_transport(name: str) -> Transport:
|
|
|
71
80
|
* android-netsim
|
|
72
81
|
"""
|
|
73
82
|
|
|
74
|
-
|
|
83
|
+
scheme, *tail = name.split(':', 1)
|
|
84
|
+
spec = tail[0] if tail else None
|
|
85
|
+
if spec:
|
|
86
|
+
# Metadata may precede the spec
|
|
87
|
+
if spec.startswith('['):
|
|
88
|
+
metadata_str, *tail = spec[1:].split(']')
|
|
89
|
+
spec = tail[0] if tail else None
|
|
90
|
+
metadata = dict([entry.split('=') for entry in metadata_str.split(',')])
|
|
91
|
+
else:
|
|
92
|
+
metadata = None
|
|
93
|
+
|
|
94
|
+
transport = await _open_transport(scheme, spec)
|
|
95
|
+
if metadata:
|
|
96
|
+
transport.source.metadata = { # type: ignore[attr-defined]
|
|
97
|
+
**metadata,
|
|
98
|
+
**getattr(transport.source, 'metadata', {}),
|
|
99
|
+
}
|
|
100
|
+
# pylint: disable=line-too-long
|
|
101
|
+
logger.debug(f'HCI metadata: {transport.source.metadata}') # type: ignore[attr-defined]
|
|
102
|
+
|
|
103
|
+
return _wrap_transport(transport)
|
|
75
104
|
|
|
76
105
|
|
|
77
106
|
# -----------------------------------------------------------------------------
|
|
78
|
-
async def _open_transport(
|
|
107
|
+
async def _open_transport(scheme: str, spec: Optional[str]) -> Transport:
|
|
79
108
|
# pylint: disable=import-outside-toplevel
|
|
80
109
|
# pylint: disable=too-many-return-statements
|
|
81
110
|
|
|
82
|
-
scheme, *spec = name.split(':', 1)
|
|
83
111
|
if scheme == 'serial' and spec:
|
|
84
112
|
from .serial import open_serial_transport
|
|
85
113
|
|
|
86
|
-
return await open_serial_transport(spec
|
|
114
|
+
return await open_serial_transport(spec)
|
|
87
115
|
|
|
88
116
|
if scheme == 'udp' and spec:
|
|
89
117
|
from .udp import open_udp_transport
|
|
90
118
|
|
|
91
|
-
return await open_udp_transport(spec
|
|
119
|
+
return await open_udp_transport(spec)
|
|
92
120
|
|
|
93
121
|
if scheme == 'tcp-client' and spec:
|
|
94
122
|
from .tcp_client import open_tcp_client_transport
|
|
95
123
|
|
|
96
|
-
return await open_tcp_client_transport(spec
|
|
124
|
+
return await open_tcp_client_transport(spec)
|
|
97
125
|
|
|
98
126
|
if scheme == 'tcp-server' and spec:
|
|
99
127
|
from .tcp_server import open_tcp_server_transport
|
|
100
128
|
|
|
101
|
-
return await open_tcp_server_transport(spec
|
|
129
|
+
return await open_tcp_server_transport(spec)
|
|
102
130
|
|
|
103
131
|
if scheme == 'ws-client' and spec:
|
|
104
132
|
from .ws_client import open_ws_client_transport
|
|
105
133
|
|
|
106
|
-
return await open_ws_client_transport(spec
|
|
134
|
+
return await open_ws_client_transport(spec)
|
|
107
135
|
|
|
108
136
|
if scheme == 'ws-server' and spec:
|
|
109
137
|
from .ws_server import open_ws_server_transport
|
|
110
138
|
|
|
111
|
-
return await open_ws_server_transport(spec
|
|
139
|
+
return await open_ws_server_transport(spec)
|
|
112
140
|
|
|
113
141
|
if scheme == 'pty':
|
|
114
142
|
from .pty import open_pty_transport
|
|
115
143
|
|
|
116
|
-
return await open_pty_transport(spec
|
|
144
|
+
return await open_pty_transport(spec)
|
|
117
145
|
|
|
118
146
|
if scheme == 'file':
|
|
119
147
|
from .file import open_file_transport
|
|
120
148
|
|
|
121
149
|
assert spec is not None
|
|
122
|
-
return await open_file_transport(spec
|
|
150
|
+
return await open_file_transport(spec)
|
|
123
151
|
|
|
124
152
|
if scheme == 'vhci':
|
|
125
153
|
from .vhci import open_vhci_transport
|
|
126
154
|
|
|
127
|
-
return await open_vhci_transport(spec
|
|
155
|
+
return await open_vhci_transport(spec)
|
|
128
156
|
|
|
129
157
|
if scheme == 'hci-socket':
|
|
130
158
|
from .hci_socket import open_hci_socket_transport
|
|
131
159
|
|
|
132
|
-
return await open_hci_socket_transport(spec
|
|
160
|
+
return await open_hci_socket_transport(spec)
|
|
133
161
|
|
|
134
162
|
if scheme == 'usb':
|
|
135
163
|
from .usb import open_usb_transport
|
|
136
164
|
|
|
137
|
-
assert spec
|
|
138
|
-
return await open_usb_transport(spec
|
|
165
|
+
assert spec
|
|
166
|
+
return await open_usb_transport(spec)
|
|
139
167
|
|
|
140
168
|
if scheme == 'pyusb':
|
|
141
169
|
from .pyusb import open_pyusb_transport
|
|
142
170
|
|
|
143
|
-
assert spec
|
|
144
|
-
return await open_pyusb_transport(spec
|
|
171
|
+
assert spec
|
|
172
|
+
return await open_pyusb_transport(spec)
|
|
145
173
|
|
|
146
174
|
if scheme == 'android-emulator':
|
|
147
175
|
from .android_emulator import open_android_emulator_transport
|
|
148
176
|
|
|
149
|
-
return await open_android_emulator_transport(spec
|
|
177
|
+
return await open_android_emulator_transport(spec)
|
|
150
178
|
|
|
151
179
|
if scheme == 'android-netsim':
|
|
152
180
|
from .android_netsim import open_android_netsim_transport
|
|
153
181
|
|
|
154
|
-
return await open_android_netsim_transport(spec
|
|
182
|
+
return await open_android_netsim_transport(spec)
|
|
155
183
|
|
|
156
184
|
raise ValueError('unknown transport scheme')
|
|
157
185
|
|
|
@@ -69,7 +69,7 @@ async def open_android_emulator_transport(spec: Optional[str]) -> Transport:
|
|
|
69
69
|
mode = 'host'
|
|
70
70
|
server_host = 'localhost'
|
|
71
71
|
server_port = '8554'
|
|
72
|
-
if spec
|
|
72
|
+
if spec:
|
|
73
73
|
params = spec.split(',')
|
|
74
74
|
for param in params:
|
|
75
75
|
if param.startswith('mode='):
|
bumble/transport/common.py
CHANGED
|
@@ -21,7 +21,7 @@ import struct
|
|
|
21
21
|
import asyncio
|
|
22
22
|
import logging
|
|
23
23
|
import io
|
|
24
|
-
from typing import ContextManager, Tuple, Optional, Protocol, Dict
|
|
24
|
+
from typing import Any, ContextManager, Tuple, Optional, Protocol, Dict
|
|
25
25
|
|
|
26
26
|
from bumble import hci
|
|
27
27
|
from bumble.colors import color
|
|
@@ -42,6 +42,7 @@ HCI_PACKET_INFO: Dict[int, Tuple[int, int, str]] = {
|
|
|
42
42
|
hci.HCI_ACL_DATA_PACKET: (2, 2, 'H'),
|
|
43
43
|
hci.HCI_SYNCHRONOUS_DATA_PACKET: (1, 2, 'B'),
|
|
44
44
|
hci.HCI_EVENT_PACKET: (1, 1, 'B'),
|
|
45
|
+
hci.HCI_ISO_DATA_PACKET: (2, 2, 'H'),
|
|
45
46
|
}
|
|
46
47
|
|
|
47
48
|
|
|
@@ -150,7 +151,7 @@ class PacketParser:
|
|
|
150
151
|
try:
|
|
151
152
|
self.sink.on_packet(bytes(self.packet))
|
|
152
153
|
except Exception as error:
|
|
153
|
-
logger.
|
|
154
|
+
logger.exception(
|
|
154
155
|
color(f'!!! Exception in on_packet: {error}', 'red')
|
|
155
156
|
)
|
|
156
157
|
self.reset()
|
bumble/transport/hci_socket.py
CHANGED
|
@@ -59,10 +59,7 @@ async def open_hci_socket_transport(spec: Optional[str]) -> Transport:
|
|
|
59
59
|
) from error
|
|
60
60
|
|
|
61
61
|
# Compute the adapter index
|
|
62
|
-
if spec
|
|
63
|
-
adapter_index = 0
|
|
64
|
-
else:
|
|
65
|
-
adapter_index = int(spec)
|
|
62
|
+
adapter_index = int(spec) if spec else 0
|
|
66
63
|
|
|
67
64
|
# Bind the socket
|
|
68
65
|
# NOTE: since Python doesn't support binding with the required address format (yet),
|
bumble/transport/usb.py
CHANGED
bumble/utils.py
CHANGED
|
@@ -280,17 +280,14 @@ class AsyncRunner:
|
|
|
280
280
|
def wrapper(*args, **kwargs):
|
|
281
281
|
coroutine = func(*args, **kwargs)
|
|
282
282
|
if queue is None:
|
|
283
|
-
#
|
|
283
|
+
# Spawn the coroutine as a task
|
|
284
284
|
async def run():
|
|
285
285
|
try:
|
|
286
286
|
await coroutine
|
|
287
287
|
except Exception:
|
|
288
|
-
logger.
|
|
289
|
-
f'{color("!!! Exception in wrapper:", "red")} '
|
|
290
|
-
f'{traceback.format_exc()}'
|
|
291
|
-
)
|
|
288
|
+
logger.exception(color("!!! Exception in wrapper:", "red"))
|
|
292
289
|
|
|
293
|
-
|
|
290
|
+
AsyncRunner.spawn(run())
|
|
294
291
|
else:
|
|
295
292
|
# Queue the coroutine to be awaited by the work queue
|
|
296
293
|
queue.enqueue(coroutine)
|