bumble 0.0.213__py3-none-any.whl → 0.0.215__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.
Files changed (123) hide show
  1. bumble/_version.py +16 -3
  2. bumble/a2dp.py +15 -16
  3. bumble/apps/auracast.py +14 -38
  4. bumble/apps/bench.py +10 -15
  5. bumble/apps/ble_rpa_tool.py +1 -0
  6. bumble/apps/console.py +22 -25
  7. bumble/apps/controller_info.py +20 -25
  8. bumble/apps/controller_loopback.py +6 -10
  9. bumble/apps/controllers.py +2 -3
  10. bumble/apps/device_info.py +4 -5
  11. bumble/apps/gatt_dump.py +3 -3
  12. bumble/apps/gg_bridge.py +7 -8
  13. bumble/apps/hci_bridge.py +4 -3
  14. bumble/apps/l2cap_bridge.py +5 -5
  15. bumble/apps/lea_unicast/app.py +16 -26
  16. bumble/apps/pair.py +30 -43
  17. bumble/apps/pandora_server.py +5 -4
  18. bumble/apps/player/player.py +20 -24
  19. bumble/apps/rfcomm_bridge.py +4 -10
  20. bumble/apps/scan.py +17 -8
  21. bumble/apps/show.py +4 -5
  22. bumble/apps/speaker/speaker.py +23 -27
  23. bumble/apps/unbond.py +3 -3
  24. bumble/apps/usb_probe.py +2 -4
  25. bumble/att.py +241 -246
  26. bumble/audio/io.py +5 -9
  27. bumble/avc.py +2 -2
  28. bumble/avctp.py +6 -7
  29. bumble/avdtp.py +19 -22
  30. bumble/avrcp.py +1097 -589
  31. bumble/codecs.py +2 -0
  32. bumble/controller.py +142 -35
  33. bumble/core.py +567 -248
  34. bumble/crypto/__init__.py +2 -2
  35. bumble/crypto/builtin.py +1 -1
  36. bumble/crypto/cryptography.py +2 -4
  37. bumble/data_types.py +1025 -0
  38. bumble/device.py +319 -267
  39. bumble/drivers/__init__.py +3 -2
  40. bumble/drivers/intel.py +3 -4
  41. bumble/drivers/rtk.py +26 -9
  42. bumble/gap.py +4 -4
  43. bumble/gatt.py +3 -2
  44. bumble/gatt_adapters.py +3 -11
  45. bumble/gatt_client.py +69 -81
  46. bumble/gatt_server.py +124 -124
  47. bumble/hci.py +114 -18
  48. bumble/helpers.py +19 -26
  49. bumble/hfp.py +10 -21
  50. bumble/hid.py +22 -16
  51. bumble/host.py +191 -103
  52. bumble/keys.py +5 -3
  53. bumble/l2cap.py +138 -104
  54. bumble/link.py +18 -19
  55. bumble/logging.py +65 -0
  56. bumble/pairing.py +7 -6
  57. bumble/pandora/__init__.py +9 -8
  58. bumble/pandora/config.py +3 -1
  59. bumble/pandora/device.py +3 -2
  60. bumble/pandora/host.py +38 -36
  61. bumble/pandora/l2cap.py +22 -21
  62. bumble/pandora/security.py +15 -15
  63. bumble/pandora/utils.py +5 -3
  64. bumble/profiles/aics.py +11 -11
  65. bumble/profiles/ams.py +403 -0
  66. bumble/profiles/ancs.py +6 -7
  67. bumble/profiles/ascs.py +14 -9
  68. bumble/profiles/asha.py +8 -12
  69. bumble/profiles/bap.py +11 -23
  70. bumble/profiles/bass.py +2 -7
  71. bumble/profiles/battery_service.py +3 -4
  72. bumble/profiles/cap.py +1 -2
  73. bumble/profiles/csip.py +2 -6
  74. bumble/profiles/device_information_service.py +2 -2
  75. bumble/profiles/gap.py +4 -4
  76. bumble/profiles/gatt_service.py +1 -4
  77. bumble/profiles/gmap.py +5 -5
  78. bumble/profiles/hap.py +62 -59
  79. bumble/profiles/heart_rate_service.py +5 -4
  80. bumble/profiles/le_audio.py +3 -1
  81. bumble/profiles/mcp.py +3 -7
  82. bumble/profiles/pacs.py +3 -6
  83. bumble/profiles/pbp.py +2 -0
  84. bumble/profiles/tmap.py +2 -3
  85. bumble/profiles/vcs.py +2 -8
  86. bumble/profiles/vocs.py +8 -8
  87. bumble/rfcomm.py +11 -14
  88. bumble/rtp.py +1 -0
  89. bumble/sdp.py +10 -8
  90. bumble/smp.py +151 -159
  91. bumble/snoop.py +5 -5
  92. bumble/tools/generate_company_id_list.py +1 -0
  93. bumble/tools/intel_fw_download.py +3 -3
  94. bumble/tools/intel_util.py +5 -4
  95. bumble/tools/rtk_fw_download.py +6 -3
  96. bumble/tools/rtk_util.py +26 -8
  97. bumble/transport/__init__.py +19 -15
  98. bumble/transport/android_emulator.py +8 -13
  99. bumble/transport/android_netsim.py +19 -18
  100. bumble/transport/common.py +12 -15
  101. bumble/transport/file.py +1 -1
  102. bumble/transport/hci_socket.py +4 -6
  103. bumble/transport/pty.py +5 -6
  104. bumble/transport/pyusb.py +7 -10
  105. bumble/transport/serial.py +2 -1
  106. bumble/transport/tcp_client.py +2 -2
  107. bumble/transport/tcp_server.py +11 -14
  108. bumble/transport/udp.py +3 -3
  109. bumble/transport/unix.py +67 -1
  110. bumble/transport/usb.py +6 -6
  111. bumble/transport/vhci.py +0 -1
  112. bumble/transport/ws_client.py +2 -1
  113. bumble/transport/ws_server.py +3 -2
  114. bumble/utils.py +20 -5
  115. bumble/vendor/android/hci.py +1 -2
  116. bumble/vendor/zephyr/hci.py +0 -1
  117. {bumble-0.0.213.dist-info → bumble-0.0.215.dist-info}/METADATA +4 -2
  118. bumble-0.0.215.dist-info/RECORD +183 -0
  119. bumble-0.0.213.dist-info/RECORD +0 -180
  120. {bumble-0.0.213.dist-info → bumble-0.0.215.dist-info}/WHEEL +0 -0
  121. {bumble-0.0.213.dist-info → bumble-0.0.215.dist-info}/entry_points.txt +0 -0
  122. {bumble-0.0.213.dist-info → bumble-0.0.215.dist-info}/licenses/LICENSE +0 -0
  123. {bumble-0.0.213.dist-info → bumble-0.0.215.dist-info}/top_level.txt +0 -0
bumble/smp.py CHANGED
@@ -23,38 +23,41 @@
23
23
  # Imports
24
24
  # -----------------------------------------------------------------------------
25
25
  from __future__ import annotations
26
- import logging
26
+
27
27
  import asyncio
28
28
  import enum
29
- from dataclasses import dataclass
29
+ import logging
30
+ from dataclasses import dataclass, field
30
31
  from typing import (
31
32
  TYPE_CHECKING,
32
33
  Any,
33
34
  Awaitable,
34
35
  Callable,
36
+ ClassVar,
35
37
  Optional,
38
+ TypeVar,
36
39
  cast,
37
40
  )
38
41
 
39
-
42
+ from bumble import crypto, utils
40
43
  from bumble.colors import color
41
- from bumble.hci import (
42
- Address,
43
- Role,
44
- HCI_LE_Enable_Encryption_Command,
45
- HCI_Object,
46
- key_with_value,
47
- )
48
44
  from bumble.core import (
49
- PhysicalTransport,
50
45
  AdvertisingData,
51
46
  InvalidArgumentError,
47
+ PhysicalTransport,
52
48
  ProtocolError,
53
49
  name_or_number,
54
50
  )
51
+ from bumble.hci import (
52
+ Address,
53
+ Fields,
54
+ HCI_LE_Enable_Encryption_Command,
55
+ HCI_Object,
56
+ Role,
57
+ key_with_value,
58
+ metadata,
59
+ )
55
60
  from bumble.keys import PairingKeys
56
- from bumble import crypto
57
- from bumble import utils
58
61
 
59
62
  if TYPE_CHECKING:
60
63
  from bumble.device import Connection, Device
@@ -200,31 +203,32 @@ def error_name(error_code: int) -> str:
200
203
  # -----------------------------------------------------------------------------
201
204
  # Classes
202
205
  # -----------------------------------------------------------------------------
206
+ @dataclass
203
207
  class SMP_Command:
204
208
  '''
205
209
  See Bluetooth spec @ Vol 3, Part H - 3 SECURITY MANAGER PROTOCOL
206
210
  '''
207
211
 
208
- smp_classes: dict[int, type[SMP_Command]] = {}
209
- fields: Any
210
- code = 0
211
- name = ''
212
+ smp_classes: ClassVar[dict[int, type[SMP_Command]]] = {}
213
+ fields: ClassVar[Fields]
214
+ code: int = field(default=0, init=False)
215
+ name: str = field(default='', init=False)
216
+ _payload: Optional[bytes] = field(default=None, init=False)
212
217
 
213
- @staticmethod
214
- def from_bytes(pdu: bytes) -> "SMP_Command":
218
+ @classmethod
219
+ def from_bytes(cls, pdu: bytes) -> "SMP_Command":
215
220
  code = pdu[0]
216
221
 
217
- cls = SMP_Command.smp_classes.get(code)
218
- if cls is None:
219
- instance = SMP_Command(pdu)
222
+ subclass = SMP_Command.smp_classes.get(code)
223
+ if subclass is None:
224
+ instance = SMP_Command()
220
225
  instance.name = SMP_Command.command_name(code)
221
226
  instance.code = code
227
+ instance.payload = pdu
222
228
  return instance
223
- self = cls.__new__(cls)
224
- SMP_Command.__init__(self, pdu)
225
- if hasattr(self, 'fields'):
226
- self.init_from_bytes(pdu, 1)
227
- return self
229
+ instance = subclass(**HCI_Object.dict_from_bytes(pdu, 1, subclass.fields))
230
+ instance.payload = pdu[1:]
231
+ return instance
228
232
 
229
233
  @staticmethod
230
234
  def command_name(code: int) -> str:
@@ -264,36 +268,35 @@ class SMP_Command:
264
268
  def keypress_notification_type_name(notification_type: int) -> str:
265
269
  return name_or_number(SMP_KEYPRESS_NOTIFICATION_TYPE_NAMES, notification_type)
266
270
 
267
- @staticmethod
268
- def subclass(fields):
269
- def inner(cls):
270
- cls.name = cls.__name__.upper()
271
- cls.code = key_with_value(SMP_COMMAND_NAMES, cls.name)
272
- if cls.code is None:
273
- raise KeyError(
274
- f'Command name {cls.name} not found in SMP_COMMAND_NAMES'
275
- )
276
- cls.fields = fields
271
+ _Command = TypeVar("_Command", bound="SMP_Command")
277
272
 
278
- # Register a factory for this class
279
- SMP_Command.smp_classes[cls.code] = cls
273
+ @classmethod
274
+ def subclass(cls, subclass: type[_Command]) -> type[_Command]:
275
+ subclass.name = subclass.__name__.upper()
276
+ subclass.code = key_with_value(SMP_COMMAND_NAMES, subclass.name)
277
+ if subclass.code is None:
278
+ raise KeyError(
279
+ f'Command name {subclass.name} not found in SMP_COMMAND_NAMES'
280
+ )
281
+ subclass.fields = HCI_Object.fields_from_dataclass(subclass)
280
282
 
281
- return cls
283
+ # Register a factory for this class
284
+ SMP_Command.smp_classes[subclass.code] = subclass
282
285
 
283
- return inner
286
+ return subclass
284
287
 
285
- def __init__(self, pdu: Optional[bytes] = None, **kwargs: Any) -> None:
286
- if hasattr(self, 'fields') and kwargs:
287
- HCI_Object.init_from_fields(self, self.fields, kwargs)
288
- if pdu is None:
289
- pdu = bytes([self.code]) + HCI_Object.dict_to_bytes(kwargs, self.fields)
290
- self.pdu = pdu
288
+ @property
289
+ def payload(self) -> bytes:
290
+ if self._payload is None:
291
+ self._payload = HCI_Object.dict_to_bytes(self.__dict__, self.fields)
292
+ return self._payload
291
293
 
292
- def init_from_bytes(self, pdu: bytes, offset: int) -> None:
293
- return HCI_Object.init_from_bytes(self, pdu, offset, self.fields)
294
+ @payload.setter
295
+ def payload(self, value: bytes) -> None:
296
+ self._payload = value
294
297
 
295
298
  def __bytes__(self):
296
- return self.pdu
299
+ return bytes([self.code]) + self.payload
297
300
 
298
301
  def __str__(self):
299
302
  result = color(self.name, 'yellow')
@@ -306,206 +309,192 @@ class SMP_Command:
306
309
 
307
310
 
308
311
  # -----------------------------------------------------------------------------
309
- @SMP_Command.subclass(
310
- [
311
- ('io_capability', {'size': 1, 'mapper': SMP_Command.io_capability_name}),
312
- ('oob_data_flag', 1),
313
- ('auth_req', {'size': 1, 'mapper': SMP_Command.auth_req_str}),
314
- ('maximum_encryption_key_size', 1),
315
- (
316
- 'initiator_key_distribution',
317
- {'size': 1, 'mapper': SMP_Command.key_distribution_str},
318
- ),
319
- (
320
- 'responder_key_distribution',
321
- {'size': 1, 'mapper': SMP_Command.key_distribution_str},
322
- ),
323
- ]
324
- )
312
+ @SMP_Command.subclass
313
+ @dataclass
325
314
  class SMP_Pairing_Request_Command(SMP_Command):
326
315
  '''
327
316
  See Bluetooth spec @ Vol 3, Part H - 3.5.1 Pairing Request
328
317
  '''
329
318
 
330
- io_capability: int
331
- oob_data_flag: int
332
- auth_req: int
333
- maximum_encryption_key_size: int
334
- initiator_key_distribution: int
335
- responder_key_distribution: int
319
+ io_capability: int = field(
320
+ metadata=metadata({'size': 1, 'mapper': SMP_Command.io_capability_name})
321
+ )
322
+ oob_data_flag: int = field(metadata=metadata(1))
323
+ auth_req: int = field(
324
+ metadata=metadata({'size': 1, 'mapper': SMP_Command.auth_req_str})
325
+ )
326
+ maximum_encryption_key_size: int = field(metadata=metadata(1))
327
+ initiator_key_distribution: int = field(
328
+ metadata=metadata({'size': 1, 'mapper': SMP_Command.key_distribution_str})
329
+ )
330
+ responder_key_distribution: int = field(
331
+ metadata=metadata({'size': 1, 'mapper': SMP_Command.key_distribution_str})
332
+ )
336
333
 
337
334
 
338
335
  # -----------------------------------------------------------------------------
339
- @SMP_Command.subclass(
340
- [
341
- ('io_capability', {'size': 1, 'mapper': SMP_Command.io_capability_name}),
342
- ('oob_data_flag', 1),
343
- ('auth_req', {'size': 1, 'mapper': SMP_Command.auth_req_str}),
344
- ('maximum_encryption_key_size', 1),
345
- (
346
- 'initiator_key_distribution',
347
- {'size': 1, 'mapper': SMP_Command.key_distribution_str},
348
- ),
349
- (
350
- 'responder_key_distribution',
351
- {'size': 1, 'mapper': SMP_Command.key_distribution_str},
352
- ),
353
- ]
354
- )
336
+ @SMP_Command.subclass
337
+ @dataclass
355
338
  class SMP_Pairing_Response_Command(SMP_Command):
356
339
  '''
357
340
  See Bluetooth spec @ Vol 3, Part H - 3.5.2 Pairing Response
358
341
  '''
359
342
 
360
- io_capability: int
361
- oob_data_flag: int
362
- auth_req: int
363
- maximum_encryption_key_size: int
364
- initiator_key_distribution: int
365
- responder_key_distribution: int
343
+ io_capability: int = field(
344
+ metadata=metadata({'size': 1, 'mapper': SMP_Command.io_capability_name})
345
+ )
346
+ oob_data_flag: int = field(metadata=metadata(1))
347
+ auth_req: int = field(
348
+ metadata=metadata({'size': 1, 'mapper': SMP_Command.auth_req_str})
349
+ )
350
+ maximum_encryption_key_size: int = field(metadata=metadata(1))
351
+ initiator_key_distribution: int = field(
352
+ metadata=metadata({'size': 1, 'mapper': SMP_Command.key_distribution_str})
353
+ )
354
+ responder_key_distribution: int = field(
355
+ metadata=metadata({'size': 1, 'mapper': SMP_Command.key_distribution_str})
356
+ )
366
357
 
367
358
 
368
359
  # -----------------------------------------------------------------------------
369
- @SMP_Command.subclass([('confirm_value', 16)])
360
+ @SMP_Command.subclass
361
+ @dataclass
370
362
  class SMP_Pairing_Confirm_Command(SMP_Command):
371
363
  '''
372
364
  See Bluetooth spec @ Vol 3, Part H - 3.5.3 Pairing Confirm
373
365
  '''
374
366
 
375
- confirm_value: bytes
367
+ confirm_value: bytes = field(metadata=metadata(16))
376
368
 
377
369
 
378
370
  # -----------------------------------------------------------------------------
379
- @SMP_Command.subclass([('random_value', 16)])
371
+ @SMP_Command.subclass
372
+ @dataclass
380
373
  class SMP_Pairing_Random_Command(SMP_Command):
381
374
  '''
382
375
  See Bluetooth spec @ Vol 3, Part H - 3.5.4 Pairing Random
383
376
  '''
384
377
 
385
- random_value: bytes
378
+ random_value: bytes = field(metadata=metadata(16))
386
379
 
387
380
 
388
381
  # -----------------------------------------------------------------------------
389
- @SMP_Command.subclass([('reason', {'size': 1, 'mapper': error_name})])
382
+ @SMP_Command.subclass
383
+ @dataclass
390
384
  class SMP_Pairing_Failed_Command(SMP_Command):
391
385
  '''
392
386
  See Bluetooth spec @ Vol 3, Part H - 3.5.5 Pairing Failed
393
387
  '''
394
388
 
395
- reason: int
389
+ reason: int = field(metadata=metadata({'size': 1, 'mapper': error_name}))
396
390
 
397
391
 
398
392
  # -----------------------------------------------------------------------------
399
- @SMP_Command.subclass([('public_key_x', 32), ('public_key_y', 32)])
393
+ @SMP_Command.subclass
394
+ @dataclass
400
395
  class SMP_Pairing_Public_Key_Command(SMP_Command):
401
396
  '''
402
397
  See Bluetooth spec @ Vol 3, Part H - 3.5.6 Pairing Public Key
403
398
  '''
404
399
 
405
- public_key_x: bytes
406
- public_key_y: bytes
400
+ public_key_x: bytes = field(metadata=metadata(32))
401
+ public_key_y: bytes = field(metadata=metadata(32))
407
402
 
408
403
 
409
404
  # -----------------------------------------------------------------------------
410
- @SMP_Command.subclass(
411
- [
412
- ('dhkey_check', 16),
413
- ]
414
- )
405
+ @SMP_Command.subclass
406
+ @dataclass
415
407
  class SMP_Pairing_DHKey_Check_Command(SMP_Command):
416
408
  '''
417
409
  See Bluetooth spec @ Vol 3, Part H - 3.5.7 Pairing DHKey Check
418
410
  '''
419
411
 
420
- dhkey_check: bytes
412
+ dhkey_check: bytes = field(metadata=metadata(16))
421
413
 
422
414
 
423
415
  # -----------------------------------------------------------------------------
424
- @SMP_Command.subclass(
425
- [
426
- (
427
- 'notification_type',
428
- {'size': 1, 'mapper': SMP_Command.keypress_notification_type_name},
429
- ),
430
- ]
431
- )
416
+ @SMP_Command.subclass
417
+ @dataclass
432
418
  class SMP_Pairing_Keypress_Notification_Command(SMP_Command):
433
419
  '''
434
420
  See Bluetooth spec @ Vol 3, Part H - 3.5.8 Keypress Notification
435
421
  '''
436
422
 
437
- notification_type: int
423
+ notification_type: int = field(
424
+ metadata=metadata(
425
+ {'size': 1, 'mapper': SMP_Command.keypress_notification_type_name}
426
+ )
427
+ )
438
428
 
439
429
 
440
430
  # -----------------------------------------------------------------------------
441
- @SMP_Command.subclass([('long_term_key', 16)])
431
+ @SMP_Command.subclass
432
+ @dataclass
442
433
  class SMP_Encryption_Information_Command(SMP_Command):
443
434
  '''
444
435
  See Bluetooth spec @ Vol 3, Part H - 3.6.2 Encryption Information
445
436
  '''
446
437
 
447
- long_term_key: bytes
438
+ long_term_key: bytes = field(metadata=metadata(16))
448
439
 
449
440
 
450
441
  # -----------------------------------------------------------------------------
451
- @SMP_Command.subclass([('ediv', 2), ('rand', 8)])
442
+ @SMP_Command.subclass
443
+ @dataclass
452
444
  class SMP_Master_Identification_Command(SMP_Command):
453
445
  '''
454
446
  See Bluetooth spec @ Vol 3, Part H - 3.6.3 Master Identification
455
447
  '''
456
448
 
457
- ediv: int
458
- rand: bytes
449
+ ediv: int = field(metadata=metadata(2))
450
+ rand: bytes = field(metadata=metadata(8))
459
451
 
460
452
 
461
453
  # -----------------------------------------------------------------------------
462
- @SMP_Command.subclass([('identity_resolving_key', 16)])
454
+ @SMP_Command.subclass
455
+ @dataclass
463
456
  class SMP_Identity_Information_Command(SMP_Command):
464
457
  '''
465
458
  See Bluetooth spec @ Vol 3, Part H - 3.6.4 Identity Information
466
459
  '''
467
460
 
468
- identity_resolving_key: bytes
461
+ identity_resolving_key: bytes = field(metadata=metadata(16))
469
462
 
470
463
 
471
464
  # -----------------------------------------------------------------------------
472
- @SMP_Command.subclass(
473
- [
474
- ('addr_type', Address.ADDRESS_TYPE_SPEC),
475
- ('bd_addr', Address.parse_address_preceded_by_type),
476
- ]
477
- )
465
+ @SMP_Command.subclass
466
+ @dataclass
478
467
  class SMP_Identity_Address_Information_Command(SMP_Command):
479
468
  '''
480
469
  See Bluetooth spec @ Vol 3, Part H - 3.6.5 Identity Address Information
481
470
  '''
482
471
 
483
- addr_type: int
484
- bd_addr: Address
472
+ addr_type: int = field(metadata=metadata(Address.ADDRESS_TYPE_SPEC))
473
+ bd_addr: Address = field(metadata=metadata(Address.parse_address_preceded_by_type))
485
474
 
486
475
 
487
476
  # -----------------------------------------------------------------------------
488
- @SMP_Command.subclass([('signature_key', 16)])
477
+ @SMP_Command.subclass
478
+ @dataclass
489
479
  class SMP_Signing_Information_Command(SMP_Command):
490
480
  '''
491
481
  See Bluetooth spec @ Vol 3, Part H - 3.6.6 Signing Information
492
482
  '''
493
483
 
494
- signature_key: bytes
484
+ signature_key: bytes = field(metadata=metadata(16))
495
485
 
496
486
 
497
487
  # -----------------------------------------------------------------------------
498
- @SMP_Command.subclass(
499
- [
500
- ('auth_req', {'size': 1, 'mapper': SMP_Command.auth_req_str}),
501
- ]
502
- )
488
+ @SMP_Command.subclass
489
+ @dataclass
503
490
  class SMP_Security_Request_Command(SMP_Command):
504
491
  '''
505
492
  See Bluetooth spec @ Vol 3, Part H - 3.6.7 Security Request
506
493
  '''
507
494
 
508
- auth_req: int
495
+ auth_req: int = field(
496
+ metadata=metadata({'size': 1, 'mapper': SMP_Command.auth_req_str})
497
+ )
509
498
 
510
499
 
511
500
  # -----------------------------------------------------------------------------
@@ -892,8 +881,8 @@ class Session:
892
881
  if response:
893
882
  next_steps()
894
883
  return
895
- except Exception as error:
896
- logger.warning(f'exception while confirm: {error}')
884
+ except Exception:
885
+ logger.exception('exception while confirm')
897
886
 
898
887
  self.send_pairing_failed(SMP_CONFIRM_VALUE_FAILED_ERROR)
899
888
 
@@ -911,8 +900,8 @@ class Session:
911
900
  if response:
912
901
  next_steps()
913
902
  return
914
- except Exception as error:
915
- logger.warning(f'exception while prompting: {error}')
903
+ except Exception:
904
+ logger.exception('exception while prompting')
916
905
 
917
906
  self.send_pairing_failed(SMP_CONFIRM_VALUE_FAILED_ERROR)
918
907
 
@@ -929,8 +918,8 @@ class Session:
929
918
  return
930
919
  logger.debug(f'user input: {passkey}')
931
920
  next_steps(passkey)
932
- except Exception as error:
933
- logger.warning(f'exception while prompting: {error}')
921
+ except Exception:
922
+ logger.exception('exception while prompting')
934
923
  self.send_pairing_failed(SMP_PASSKEY_ENTRY_FAILED_ERROR)
935
924
 
936
925
  self.connection.cancel_on_disconnection(prompt())
@@ -946,7 +935,9 @@ class Session:
946
935
  self.tk = self.passkey.to_bytes(16, byteorder='little')
947
936
  logger.debug(f'TK from passkey = {self.tk.hex()}')
948
937
 
949
- await self.pairing_config.delegate.display_number(self.passkey, digits=6)
938
+ self.connection.cancel_on_disconnection(
939
+ self.pairing_config.delegate.display_number(self.passkey, digits=6)
940
+ )
950
941
 
951
942
  def input_passkey(self, next_steps: Optional[Callable[[], None]] = None) -> None:
952
943
  # Prompt the user for the passkey displayed on the peer
@@ -976,8 +967,8 @@ class Session:
976
967
 
977
968
  try:
978
969
  self.connection.cancel_on_disconnection(display_passkey())
979
- except Exception as error:
980
- logger.warning(f'exception while displaying passkey: {error}')
970
+ except Exception:
971
+ logger.exception('exception while displaying passkey')
981
972
  else:
982
973
  self.input_passkey(next_steps)
983
974
 
@@ -1422,8 +1413,8 @@ class Session:
1422
1413
  if handler is not None:
1423
1414
  try:
1424
1415
  handler(command)
1425
- except Exception as error:
1426
- logger.exception(f'{color("!!! Exception in handler:", "red")} {error}')
1416
+ except Exception:
1417
+ logger.exception(color("!!! Exception in handler:", "red"))
1427
1418
  response = SMP_Pairing_Failed_Command(
1428
1419
  reason=SMP_UNSPECIFIED_REASON_ERROR
1429
1420
  )
@@ -1444,8 +1435,8 @@ class Session:
1444
1435
  # Check if the request should proceed
1445
1436
  try:
1446
1437
  accepted = await self.pairing_config.delegate.accept()
1447
- except Exception as error:
1448
- logger.warning(f'exception while accepting: {error}')
1438
+ except Exception:
1439
+ logger.exception('exception while accepting')
1449
1440
  accepted = False
1450
1441
  if not accepted:
1451
1442
  logger.debug('pairing rejected by delegate')
@@ -1569,11 +1560,12 @@ class Session:
1569
1560
  if self.pairing_method == PairingMethod.CTKD_OVER_CLASSIC:
1570
1561
  # Authentication is already done in SMP, so remote shall start keys distribution immediately
1571
1562
  return
1572
- elif self.sc:
1573
- if self.pairing_method == PairingMethod.PASSKEY:
1574
- self.display_or_input_passkey()
1575
1563
 
1564
+ if self.sc:
1576
1565
  self.send_public_key_command()
1566
+
1567
+ if self.pairing_method == PairingMethod.PASSKEY:
1568
+ self.display_or_input_passkey()
1577
1569
  else:
1578
1570
  if self.pairing_method == PairingMethod.PASSKEY:
1579
1571
  self.display_or_input_passkey(self.send_pairing_confirm_command)
@@ -1846,10 +1838,10 @@ class Session:
1846
1838
  elif self.pairing_method == PairingMethod.PASSKEY:
1847
1839
  self.send_pairing_confirm_command()
1848
1840
  else:
1841
+ # Send our public key back to the initiator
1842
+ self.send_public_key_command()
1849
1843
 
1850
1844
  def next_steps() -> None:
1851
- # Send our public key back to the initiator
1852
- self.send_public_key_command()
1853
1845
 
1854
1846
  if self.pairing_method in (
1855
1847
  PairingMethod.JUST_WORKS,
bumble/snoop.py CHANGED
@@ -12,21 +12,21 @@
12
12
  # See the License for the specific language governing permissions and
13
13
  # limitations under the License.
14
14
 
15
+ import datetime
16
+ import logging
17
+ import os
18
+ import struct
19
+
15
20
  # -----------------------------------------------------------------------------
16
21
  # Imports
17
22
  # -----------------------------------------------------------------------------
18
23
  from contextlib import contextmanager
19
24
  from enum import IntEnum
20
- import logging
21
- import struct
22
- import datetime
23
25
  from typing import BinaryIO, Generator
24
- import os
25
26
 
26
27
  from bumble import core
27
28
  from bumble.hci import HCI_COMMAND_PACKET, HCI_EVENT_PACKET
28
29
 
29
-
30
30
  # -----------------------------------------------------------------------------
31
31
  # Logging
32
32
  # -----------------------------------------------------------------------------
@@ -23,6 +23,7 @@
23
23
  # Imports
24
24
  # -----------------------------------------------------------------------------
25
25
  import sys
26
+
26
27
  import yaml
27
28
 
28
29
  # -----------------------------------------------------------------------------
@@ -17,15 +17,14 @@
17
17
  # -----------------------------------------------------------------------------
18
18
  import logging
19
19
  import pathlib
20
- import urllib.request
21
20
  import urllib.error
21
+ import urllib.request
22
22
 
23
23
  import click
24
24
 
25
25
  from bumble.colors import color
26
26
  from bumble.drivers import intel
27
27
 
28
-
29
28
  # -----------------------------------------------------------------------------
30
29
  # Logging
31
30
  # -----------------------------------------------------------------------------
@@ -43,7 +42,8 @@ LINUX_KERNEL_GIT_SOURCE = "https://git.kernel.org/pub/scm/linux/kernel/git/firmw
43
42
  # -----------------------------------------------------------------------------
44
43
  def download_file(base_url, name):
45
44
  url = f"{base_url}/{name}"
46
- with urllib.request.urlopen(url) as file:
45
+ request = urllib.request.Request(url, data=None, headers={"User-Agent": "Bumble"})
46
+ with urllib.request.urlopen(request) as file:
47
47
  data = file.read()
48
48
  print(f"Downloaded {name}: {len(data)} bytes")
49
49
  return data
@@ -12,18 +12,19 @@
12
12
  # See the License for the specific language governing permissions and
13
13
  # limitations under the License.
14
14
 
15
+ import asyncio
16
+
15
17
  # -----------------------------------------------------------------------------
16
18
  # Imports
17
19
  # -----------------------------------------------------------------------------
18
20
  import logging
19
- import asyncio
20
- import os
21
21
  from typing import Any, Optional
22
22
 
23
23
  import click
24
24
 
25
- from bumble.colors import color
25
+ import bumble.logging
26
26
  from bumble import transport
27
+ from bumble.colors import color
27
28
  from bumble.drivers import intel
28
29
  from bumble.host import Host
29
30
 
@@ -107,7 +108,7 @@ async def do_bootloader(usb_transport: str, force: bool) -> None:
107
108
  # -----------------------------------------------------------------------------
108
109
  @click.group()
109
110
  def main():
110
- logging.basicConfig(level=os.environ.get('BUMBLE_LOGLEVEL', 'INFO').upper())
111
+ bumble.logging.setup_basic_logging()
111
112
 
112
113
 
113
114
  @main.command
@@ -17,16 +17,16 @@
17
17
  # -----------------------------------------------------------------------------
18
18
  import logging
19
19
  import pathlib
20
- import urllib.request
21
20
  import urllib.error
21
+ import urllib.request
22
22
 
23
23
  import click
24
24
 
25
+ import bumble.logging
25
26
  from bumble.colors import color
26
27
  from bumble.drivers import rtk
27
28
  from bumble.tools import rtk_util
28
29
 
29
-
30
30
  # -----------------------------------------------------------------------------
31
31
  # Logging
32
32
  # -----------------------------------------------------------------------------
@@ -58,7 +58,9 @@ def download_file(base_url, name, remove_suffix):
58
58
  name = name.replace(".bin", "")
59
59
 
60
60
  url = f"{base_url}/{name}"
61
- with urllib.request.urlopen(url) as file:
61
+ logger.debug(f"downloading {url}")
62
+ request = urllib.request.Request(url, data=None, headers={"User-Agent": "Bumble"})
63
+ with urllib.request.urlopen(request) as file:
62
64
  data = file.read()
63
65
  print(f"Downloaded {name}: {len(data)} bytes")
64
66
  return data
@@ -84,6 +86,7 @@ def download_file(base_url, name, remove_suffix):
84
86
  @click.option("--parse", is_flag=True, help="Parse the FW image after saving")
85
87
  def main(output_dir, source, single, force, parse):
86
88
  """Download RTK firmware images and configs."""
89
+ bumble.logging.setup_basic_logging()
87
90
 
88
91
  # Check that the output dir exists
89
92
  if output_dir == '':