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.
@@ -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
- return
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
- device: device.Device, connection: device.Connection
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(device)
261
- await sdp_client.connect(connection)
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(cls, d_bytes, x_bytes, y_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 r():
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(bytes(reversed(key))), modes.ECB())
133
+ cipher = Cipher(algorithms.AES(reverse(key)), modes.ECB())
119
134
  encryptor = cipher.encryptor()
120
- return bytes(reversed(encryptor.update(bytes(reversed(data)))))
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(k, r, preq, pres, iat, rat, ia, ra): # pylint: disable=redefined-outer-name
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 bytes(
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(bytes(reversed(w)), salt)
210
+ t = aes_cmac(reverse(w), salt)
191
211
  key_id = bytes([0x62, 0x74, 0x6C, 0x65])
192
212
  return (
193
- bytes(
194
- reversed(
195
- aes_cmac(
196
- bytes([0])
197
- + key_id
198
- + bytes(reversed(n1))
199
- + bytes(reversed(n2))
200
- + bytes(reversed(a1))
201
- + bytes(reversed(a2))
202
- + bytes([1, 0]),
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
- bytes(
208
- reversed(
209
- aes_cmac(
210
- bytes([1])
211
- + key_id
212
- + bytes(reversed(n1))
213
- + bytes(reversed(n2))
214
- + bytes(reversed(a1))
215
- + bytes(reversed(a2))
216
- + bytes([1, 0]),
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(w, n1, n2, r, io_cap, a1, a2): # pylint: disable=redefined-outer-name
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 bytes(
231
- reversed(
232
- aes_cmac(
233
- bytes(reversed(n1))
234
- + bytes(reversed(n2))
235
- + bytes(reversed(r))
236
- + bytes(reversed(io_cap))
237
- + bytes(reversed(a1))
238
- + bytes(reversed(a2)),
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
- bytes(reversed(u)) + bytes(reversed(v)) + bytes(reversed(y)),
254
- bytes(reversed(x)),
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))