bumble 0.0.178__py3-none-any.whl → 0.0.180__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 +83 -68
- bumble/apps/bench.py +180 -24
- bumble/apps/controller_info.py +14 -0
- bumble/apps/pair.py +9 -2
- bumble/avdtp.py +3 -3
- bumble/crypto.py +82 -66
- bumble/device.py +247 -23
- bumble/gatt.py +117 -7
- bumble/gatt_client.py +56 -20
- bumble/hci.py +351 -78
- bumble/helpers.py +67 -42
- bumble/hid.py +8 -7
- bumble/l2cap.py +8 -0
- bumble/profiles/csip.py +147 -0
- bumble/rfcomm.py +2 -3
- bumble/sdp.py +4 -4
- bumble/smp.py +66 -43
- bumble/transport/common.py +1 -1
- bumble/transport/usb.py +58 -61
- bumble/utils.py +17 -1
- {bumble-0.0.178.dist-info → bumble-0.0.180.dist-info}/METADATA +1 -1
- {bumble-0.0.178.dist-info → bumble-0.0.180.dist-info}/RECORD +27 -26
- {bumble-0.0.178.dist-info → bumble-0.0.180.dist-info}/WHEEL +1 -1
- {bumble-0.0.178.dist-info → bumble-0.0.180.dist-info}/LICENSE +0 -0
- {bumble-0.0.178.dist-info → bumble-0.0.180.dist-info}/entry_points.txt +0 -0
- {bumble-0.0.178.dist-info → bumble-0.0.180.dist-info}/top_level.txt +0 -0
bumble/apps/controller_info.py
CHANGED
|
@@ -42,6 +42,8 @@ from bumble.hci import (
|
|
|
42
42
|
HCI_LE_Read_Number_Of_Supported_Advertising_Sets_Command,
|
|
43
43
|
HCI_LE_READ_MAXIMUM_ADVERTISING_DATA_LENGTH_COMMAND,
|
|
44
44
|
HCI_LE_Read_Maximum_Advertising_Data_Length_Command,
|
|
45
|
+
HCI_LE_READ_SUGGESTED_DEFAULT_DATA_LENGTH_COMMAND,
|
|
46
|
+
HCI_LE_Read_Suggested_Default_Data_Length_Command,
|
|
45
47
|
)
|
|
46
48
|
from bumble.host import Host
|
|
47
49
|
from bumble.transport import open_transport_or_link
|
|
@@ -117,6 +119,18 @@ async def get_le_info(host):
|
|
|
117
119
|
'\n',
|
|
118
120
|
)
|
|
119
121
|
|
|
122
|
+
if host.supports_command(HCI_LE_READ_SUGGESTED_DEFAULT_DATA_LENGTH_COMMAND):
|
|
123
|
+
response = await host.send_command(
|
|
124
|
+
HCI_LE_Read_Suggested_Default_Data_Length_Command()
|
|
125
|
+
)
|
|
126
|
+
if command_succeeded(response):
|
|
127
|
+
print(
|
|
128
|
+
color('Suggested Default Data Length:', 'yellow'),
|
|
129
|
+
f'{response.return_parameters.suggested_max_tx_octets}/'
|
|
130
|
+
f'{response.return_parameters.suggested_max_tx_time}',
|
|
131
|
+
'\n',
|
|
132
|
+
)
|
|
133
|
+
|
|
120
134
|
print(color('LE Features:', 'yellow'))
|
|
121
135
|
for feature in host.supported_le_features:
|
|
122
136
|
print(' ', name_or_number(HCI_LE_SUPPORTED_FEATURES_NAMES, feature))
|
bumble/apps/pair.py
CHANGED
|
@@ -291,6 +291,7 @@ async def pair(
|
|
|
291
291
|
mitm,
|
|
292
292
|
bond,
|
|
293
293
|
ctkd,
|
|
294
|
+
linger,
|
|
294
295
|
io,
|
|
295
296
|
oob,
|
|
296
297
|
prompt,
|
|
@@ -395,6 +396,7 @@ async def pair(
|
|
|
395
396
|
address_or_name,
|
|
396
397
|
transport=BT_LE_TRANSPORT if mode == 'le' else BT_BR_EDR_TRANSPORT,
|
|
397
398
|
)
|
|
399
|
+
pairing_failure = False
|
|
398
400
|
|
|
399
401
|
if not request:
|
|
400
402
|
try:
|
|
@@ -402,10 +404,12 @@ async def pair(
|
|
|
402
404
|
await connection.pair()
|
|
403
405
|
else:
|
|
404
406
|
await connection.authenticate()
|
|
405
|
-
return
|
|
406
407
|
except ProtocolError as error:
|
|
408
|
+
pairing_failure = True
|
|
407
409
|
print(color(f'Pairing failed: {error}', 'red'))
|
|
408
|
-
|
|
410
|
+
|
|
411
|
+
if not linger or pairing_failure:
|
|
412
|
+
return
|
|
409
413
|
else:
|
|
410
414
|
if mode == 'le':
|
|
411
415
|
# Advertise so that peers can find us and connect
|
|
@@ -455,6 +459,7 @@ class LogHandler(logging.Handler):
|
|
|
455
459
|
help='Enable CTKD',
|
|
456
460
|
show_default=True,
|
|
457
461
|
)
|
|
462
|
+
@click.option('--linger', default=True, is_flag=True, help='Linger after pairing')
|
|
458
463
|
@click.option(
|
|
459
464
|
'--io',
|
|
460
465
|
type=click.Choice(
|
|
@@ -490,6 +495,7 @@ def main(
|
|
|
490
495
|
mitm,
|
|
491
496
|
bond,
|
|
492
497
|
ctkd,
|
|
498
|
+
linger,
|
|
493
499
|
io,
|
|
494
500
|
oob,
|
|
495
501
|
prompt,
|
|
@@ -514,6 +520,7 @@ def main(
|
|
|
514
520
|
mitm,
|
|
515
521
|
bond,
|
|
516
522
|
ctkd,
|
|
523
|
+
linger,
|
|
517
524
|
io,
|
|
518
525
|
oob,
|
|
519
526
|
prompt,
|
bumble/avdtp.py
CHANGED
|
@@ -250,15 +250,15 @@ async def find_avdtp_service_with_sdp_client(
|
|
|
250
250
|
|
|
251
251
|
# -----------------------------------------------------------------------------
|
|
252
252
|
async def find_avdtp_service_with_connection(
|
|
253
|
-
|
|
253
|
+
connection: device.Connection,
|
|
254
254
|
) -> Optional[Tuple[int, int]]:
|
|
255
255
|
'''
|
|
256
256
|
Find an AVDTP service, for a connection, and return its version,
|
|
257
257
|
or None if none is found
|
|
258
258
|
'''
|
|
259
259
|
|
|
260
|
-
sdp_client = sdp.Client(
|
|
261
|
-
await sdp_client.connect(
|
|
260
|
+
sdp_client = sdp.Client(connection)
|
|
261
|
+
await sdp_client.connect()
|
|
262
262
|
service_version = await find_avdtp_service_with_sdp_client(sdp_client)
|
|
263
263
|
await sdp_client.disconnect()
|
|
264
264
|
|
bumble/crypto.py
CHANGED
|
@@ -21,6 +21,8 @@
|
|
|
21
21
|
# -----------------------------------------------------------------------------
|
|
22
22
|
# Imports
|
|
23
23
|
# -----------------------------------------------------------------------------
|
|
24
|
+
from __future__ import annotations
|
|
25
|
+
|
|
24
26
|
import logging
|
|
25
27
|
import operator
|
|
26
28
|
|
|
@@ -29,11 +31,13 @@ from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
|
|
|
29
31
|
from cryptography.hazmat.primitives.asymmetric.ec import (
|
|
30
32
|
generate_private_key,
|
|
31
33
|
ECDH,
|
|
34
|
+
EllipticCurvePrivateKey,
|
|
32
35
|
EllipticCurvePublicNumbers,
|
|
33
36
|
EllipticCurvePrivateNumbers,
|
|
34
37
|
SECP256R1,
|
|
35
38
|
)
|
|
36
39
|
from cryptography.hazmat.primitives import cmac
|
|
40
|
+
from typing import Tuple
|
|
37
41
|
|
|
38
42
|
|
|
39
43
|
# -----------------------------------------------------------------------------
|
|
@@ -46,16 +50,18 @@ logger = logging.getLogger(__name__)
|
|
|
46
50
|
# Classes
|
|
47
51
|
# -----------------------------------------------------------------------------
|
|
48
52
|
class EccKey:
|
|
49
|
-
def __init__(self, private_key):
|
|
53
|
+
def __init__(self, private_key: EllipticCurvePrivateKey) -> None:
|
|
50
54
|
self.private_key = private_key
|
|
51
55
|
|
|
52
56
|
@classmethod
|
|
53
|
-
def generate(cls):
|
|
57
|
+
def generate(cls) -> EccKey:
|
|
54
58
|
private_key = generate_private_key(SECP256R1())
|
|
55
59
|
return cls(private_key)
|
|
56
60
|
|
|
57
61
|
@classmethod
|
|
58
|
-
def from_private_key_bytes(
|
|
62
|
+
def from_private_key_bytes(
|
|
63
|
+
cls, d_bytes: bytes, x_bytes: bytes, y_bytes: bytes
|
|
64
|
+
) -> EccKey:
|
|
59
65
|
d = int.from_bytes(d_bytes, byteorder='big', signed=False)
|
|
60
66
|
x = int.from_bytes(x_bytes, byteorder='big', signed=False)
|
|
61
67
|
y = int.from_bytes(y_bytes, byteorder='big', signed=False)
|
|
@@ -65,7 +71,7 @@ class EccKey:
|
|
|
65
71
|
return cls(private_key)
|
|
66
72
|
|
|
67
73
|
@property
|
|
68
|
-
def x(self):
|
|
74
|
+
def x(self) -> bytes:
|
|
69
75
|
return (
|
|
70
76
|
self.private_key.public_key()
|
|
71
77
|
.public_numbers()
|
|
@@ -73,14 +79,14 @@ class EccKey:
|
|
|
73
79
|
)
|
|
74
80
|
|
|
75
81
|
@property
|
|
76
|
-
def y(self):
|
|
82
|
+
def y(self) -> bytes:
|
|
77
83
|
return (
|
|
78
84
|
self.private_key.public_key()
|
|
79
85
|
.public_numbers()
|
|
80
86
|
.y.to_bytes(32, byteorder='big')
|
|
81
87
|
)
|
|
82
88
|
|
|
83
|
-
def dh(self, public_key_x, public_key_y):
|
|
89
|
+
def dh(self, public_key_x: bytes, public_key_y: bytes) -> bytes:
|
|
84
90
|
x = int.from_bytes(public_key_x, byteorder='big', signed=False)
|
|
85
91
|
y = int.from_bytes(public_key_y, byteorder='big', signed=False)
|
|
86
92
|
public_key = EllipticCurvePublicNumbers(x, y, SECP256R1()).public_key()
|
|
@@ -93,14 +99,23 @@ class EccKey:
|
|
|
93
99
|
# Functions
|
|
94
100
|
# -----------------------------------------------------------------------------
|
|
95
101
|
|
|
102
|
+
|
|
96
103
|
# -----------------------------------------------------------------------------
|
|
97
|
-
def xor(x, y):
|
|
104
|
+
def xor(x: bytes, y: bytes) -> bytes:
|
|
98
105
|
assert len(x) == len(y)
|
|
99
106
|
return bytes(map(operator.xor, x, y))
|
|
100
107
|
|
|
101
108
|
|
|
102
109
|
# -----------------------------------------------------------------------------
|
|
103
|
-
def
|
|
110
|
+
def reverse(input: bytes) -> bytes:
|
|
111
|
+
'''
|
|
112
|
+
Returns bytes of input in reversed endianness.
|
|
113
|
+
'''
|
|
114
|
+
return input[::-1]
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
# -----------------------------------------------------------------------------
|
|
118
|
+
def r() -> bytes:
|
|
104
119
|
'''
|
|
105
120
|
Generate 16 bytes of random data
|
|
106
121
|
'''
|
|
@@ -108,20 +123,20 @@ def r():
|
|
|
108
123
|
|
|
109
124
|
|
|
110
125
|
# -----------------------------------------------------------------------------
|
|
111
|
-
def e(key, data):
|
|
126
|
+
def e(key: bytes, data: bytes) -> bytes:
|
|
112
127
|
'''
|
|
113
128
|
AES-128 ECB, expecting byte-swapped inputs and producing a byte-swapped output.
|
|
114
129
|
|
|
115
130
|
See Bluetooth spec Vol 3, Part H - 2.2.1 Security function e
|
|
116
131
|
'''
|
|
117
132
|
|
|
118
|
-
cipher = Cipher(algorithms.AES(
|
|
133
|
+
cipher = Cipher(algorithms.AES(reverse(key)), modes.ECB())
|
|
119
134
|
encryptor = cipher.encryptor()
|
|
120
|
-
return
|
|
135
|
+
return reverse(encryptor.update(reverse(data)))
|
|
121
136
|
|
|
122
137
|
|
|
123
138
|
# -----------------------------------------------------------------------------
|
|
124
|
-
def ah(k, r): # pylint: disable=redefined-outer-name
|
|
139
|
+
def ah(k: bytes, r: bytes) -> bytes: # pylint: disable=redefined-outer-name
|
|
125
140
|
'''
|
|
126
141
|
See Bluetooth spec Vol 3, Part H - 2.2.2 Random Address Hash function ah
|
|
127
142
|
'''
|
|
@@ -132,7 +147,16 @@ def ah(k, r): # pylint: disable=redefined-outer-name
|
|
|
132
147
|
|
|
133
148
|
|
|
134
149
|
# -----------------------------------------------------------------------------
|
|
135
|
-
def c1(
|
|
150
|
+
def c1(
|
|
151
|
+
k: bytes,
|
|
152
|
+
r: bytes,
|
|
153
|
+
preq: bytes,
|
|
154
|
+
pres: bytes,
|
|
155
|
+
iat: int,
|
|
156
|
+
rat: int,
|
|
157
|
+
ia: bytes,
|
|
158
|
+
ra: bytes,
|
|
159
|
+
) -> bytes: # pylint: disable=redefined-outer-name
|
|
136
160
|
'''
|
|
137
161
|
See Bluetooth spec, Vol 3, Part H - 2.2.3 Confirm value generation function c1 for
|
|
138
162
|
LE Legacy Pairing
|
|
@@ -144,7 +168,7 @@ def c1(k, r, preq, pres, iat, rat, ia, ra): # pylint: disable=redefined-outer-n
|
|
|
144
168
|
|
|
145
169
|
|
|
146
170
|
# -----------------------------------------------------------------------------
|
|
147
|
-
def s1(k, r1, r2):
|
|
171
|
+
def s1(k: bytes, r1: bytes, r2: bytes) -> bytes:
|
|
148
172
|
'''
|
|
149
173
|
See Bluetooth spec, Vol 3, Part H - 2.2.4 Key generation function s1 for LE Legacy
|
|
150
174
|
Pairing
|
|
@@ -154,7 +178,7 @@ def s1(k, r1, r2):
|
|
|
154
178
|
|
|
155
179
|
|
|
156
180
|
# -----------------------------------------------------------------------------
|
|
157
|
-
def aes_cmac(m, k):
|
|
181
|
+
def aes_cmac(m: bytes, k: bytes) -> bytes:
|
|
158
182
|
'''
|
|
159
183
|
See Bluetooth spec, Vol 3, Part H - 2.2.5 FunctionAES-CMAC
|
|
160
184
|
|
|
@@ -166,20 +190,16 @@ def aes_cmac(m, k):
|
|
|
166
190
|
|
|
167
191
|
|
|
168
192
|
# -----------------------------------------------------------------------------
|
|
169
|
-
def f4(u, v, x, z):
|
|
193
|
+
def f4(u: bytes, v: bytes, x: bytes, z: bytes) -> bytes:
|
|
170
194
|
'''
|
|
171
195
|
See Bluetooth spec, Vol 3, Part H - 2.2.6 LE Secure Connections Confirm Value
|
|
172
196
|
Generation Function f4
|
|
173
197
|
'''
|
|
174
|
-
return
|
|
175
|
-
reversed(
|
|
176
|
-
aes_cmac(bytes(reversed(u)) + bytes(reversed(v)) + z, bytes(reversed(x)))
|
|
177
|
-
)
|
|
178
|
-
)
|
|
198
|
+
return reverse(aes_cmac(reverse(u) + reverse(v) + z, reverse(x)))
|
|
179
199
|
|
|
180
200
|
|
|
181
201
|
# -----------------------------------------------------------------------------
|
|
182
|
-
def f5(w, n1, n2, a1, a2):
|
|
202
|
+
def f5(w: bytes, n1: bytes, n2: bytes, a1: bytes, a2: bytes) -> Tuple[bytes, bytes]:
|
|
183
203
|
'''
|
|
184
204
|
See Bluetooth spec, Vol 3, Part H - 2.2.7 LE Secure Connections Key Generation
|
|
185
205
|
Function f5
|
|
@@ -187,87 +207,83 @@ def f5(w, n1, n2, a1, a2):
|
|
|
187
207
|
NOTE: this returns a tuple: (MacKey, LTK) in little-endian byte order
|
|
188
208
|
'''
|
|
189
209
|
salt = bytes.fromhex('6C888391AAF5A53860370BDB5A6083BE')
|
|
190
|
-
t = aes_cmac(
|
|
210
|
+
t = aes_cmac(reverse(w), salt)
|
|
191
211
|
key_id = bytes([0x62, 0x74, 0x6C, 0x65])
|
|
192
212
|
return (
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
t,
|
|
204
|
-
)
|
|
213
|
+
reverse(
|
|
214
|
+
aes_cmac(
|
|
215
|
+
bytes([0])
|
|
216
|
+
+ key_id
|
|
217
|
+
+ reverse(n1)
|
|
218
|
+
+ reverse(n2)
|
|
219
|
+
+ reverse(a1)
|
|
220
|
+
+ reverse(a2)
|
|
221
|
+
+ bytes([1, 0]),
|
|
222
|
+
t,
|
|
205
223
|
)
|
|
206
224
|
),
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
t,
|
|
218
|
-
)
|
|
225
|
+
reverse(
|
|
226
|
+
aes_cmac(
|
|
227
|
+
bytes([1])
|
|
228
|
+
+ key_id
|
|
229
|
+
+ reverse(n1)
|
|
230
|
+
+ reverse(n2)
|
|
231
|
+
+ reverse(a1)
|
|
232
|
+
+ reverse(a2)
|
|
233
|
+
+ bytes([1, 0]),
|
|
234
|
+
t,
|
|
219
235
|
)
|
|
220
236
|
),
|
|
221
237
|
)
|
|
222
238
|
|
|
223
239
|
|
|
224
240
|
# -----------------------------------------------------------------------------
|
|
225
|
-
def f6(
|
|
241
|
+
def f6(
|
|
242
|
+
w: bytes, n1: bytes, n2: bytes, r: bytes, io_cap: bytes, a1: bytes, a2: bytes
|
|
243
|
+
) -> bytes: # pylint: disable=redefined-outer-name
|
|
226
244
|
'''
|
|
227
245
|
See Bluetooth spec, Vol 3, Part H - 2.2.8 LE Secure Connections Check Value
|
|
228
246
|
Generation Function f6
|
|
229
247
|
'''
|
|
230
|
-
return
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
bytes(reversed(w)),
|
|
240
|
-
)
|
|
248
|
+
return reverse(
|
|
249
|
+
aes_cmac(
|
|
250
|
+
reverse(n1)
|
|
251
|
+
+ reverse(n2)
|
|
252
|
+
+ reverse(r)
|
|
253
|
+
+ reverse(io_cap)
|
|
254
|
+
+ reverse(a1)
|
|
255
|
+
+ reverse(a2),
|
|
256
|
+
reverse(w),
|
|
241
257
|
)
|
|
242
258
|
)
|
|
243
259
|
|
|
244
260
|
|
|
245
261
|
# -----------------------------------------------------------------------------
|
|
246
|
-
def g2(u, v, x, y):
|
|
262
|
+
def g2(u: bytes, v: bytes, x: bytes, y: bytes) -> int:
|
|
247
263
|
'''
|
|
248
264
|
See Bluetooth spec, Vol 3, Part H - 2.2.9 LE Secure Connections Numeric Comparison
|
|
249
265
|
Value Generation Function g2
|
|
250
266
|
'''
|
|
251
267
|
return int.from_bytes(
|
|
252
268
|
aes_cmac(
|
|
253
|
-
|
|
254
|
-
|
|
269
|
+
reverse(u) + reverse(v) + reverse(y),
|
|
270
|
+
reverse(x),
|
|
255
271
|
)[-4:],
|
|
256
272
|
byteorder='big',
|
|
257
273
|
)
|
|
258
274
|
|
|
259
275
|
|
|
260
276
|
# -----------------------------------------------------------------------------
|
|
261
|
-
def h6(w, key_id):
|
|
277
|
+
def h6(w: bytes, key_id: bytes) -> bytes:
|
|
262
278
|
'''
|
|
263
279
|
See Bluetooth spec, Vol 3, Part H - 2.2.10 Link key conversion function h6
|
|
264
280
|
'''
|
|
265
|
-
return aes_cmac(key_id, w)
|
|
281
|
+
return reverse(aes_cmac(key_id, reverse(w)))
|
|
266
282
|
|
|
267
283
|
|
|
268
284
|
# -----------------------------------------------------------------------------
|
|
269
|
-
def h7(salt, w):
|
|
285
|
+
def h7(salt: bytes, w: bytes) -> bytes:
|
|
270
286
|
'''
|
|
271
287
|
See Bluetooth spec, Vol 3, Part H - 2.2.11 Link key conversion function h7
|
|
272
288
|
'''
|
|
273
|
-
return aes_cmac(w, salt)
|
|
289
|
+
return reverse(aes_cmac(reverse(w), salt))
|