bumble 0.0.212__py3-none-any.whl → 0.0.213__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 (86) hide show
  1. bumble/_version.py +2 -2
  2. bumble/a2dp.py +6 -0
  3. bumble/apps/README.md +0 -3
  4. bumble/apps/auracast.py +11 -9
  5. bumble/apps/bench.py +480 -31
  6. bumble/apps/console.py +3 -3
  7. bumble/apps/controller_info.py +47 -10
  8. bumble/apps/controller_loopback.py +7 -3
  9. bumble/apps/controllers.py +2 -2
  10. bumble/apps/device_info.py +2 -2
  11. bumble/apps/gatt_dump.py +2 -2
  12. bumble/apps/gg_bridge.py +2 -2
  13. bumble/apps/hci_bridge.py +2 -2
  14. bumble/apps/l2cap_bridge.py +2 -2
  15. bumble/apps/lea_unicast/app.py +6 -1
  16. bumble/apps/pair.py +19 -11
  17. bumble/apps/pandora_server.py +2 -2
  18. bumble/apps/rfcomm_bridge.py +1 -1
  19. bumble/apps/scan.py +2 -2
  20. bumble/apps/show.py +4 -2
  21. bumble/apps/speaker/speaker.html +1 -0
  22. bumble/apps/speaker/speaker.js +113 -62
  23. bumble/apps/speaker/speaker.py +126 -18
  24. bumble/at.py +4 -4
  25. bumble/att.py +2 -6
  26. bumble/avc.py +7 -7
  27. bumble/avctp.py +3 -3
  28. bumble/avdtp.py +16 -20
  29. bumble/avrcp.py +41 -53
  30. bumble/colors.py +2 -2
  31. bumble/controller.py +84 -23
  32. bumble/device.py +348 -182
  33. bumble/drivers/__init__.py +2 -2
  34. bumble/drivers/common.py +0 -2
  35. bumble/drivers/intel.py +37 -40
  36. bumble/drivers/rtk.py +28 -35
  37. bumble/gatt.py +4 -4
  38. bumble/gatt_adapters.py +4 -5
  39. bumble/gatt_client.py +26 -31
  40. bumble/gatt_server.py +7 -11
  41. bumble/hci.py +2601 -2909
  42. bumble/helpers.py +4 -5
  43. bumble/hfp.py +32 -37
  44. bumble/host.py +94 -35
  45. bumble/keys.py +5 -5
  46. bumble/l2cap.py +310 -394
  47. bumble/link.py +6 -270
  48. bumble/pairing.py +23 -20
  49. bumble/pandora/__init__.py +1 -1
  50. bumble/pandora/config.py +2 -2
  51. bumble/pandora/device.py +6 -6
  52. bumble/pandora/host.py +27 -28
  53. bumble/pandora/l2cap.py +2 -2
  54. bumble/pandora/security.py +6 -6
  55. bumble/pandora/utils.py +3 -3
  56. bumble/profiles/ascs.py +132 -131
  57. bumble/profiles/asha.py +2 -2
  58. bumble/profiles/bap.py +3 -4
  59. bumble/profiles/csip.py +2 -2
  60. bumble/profiles/device_information_service.py +2 -2
  61. bumble/profiles/gap.py +2 -2
  62. bumble/profiles/hap.py +34 -33
  63. bumble/profiles/le_audio.py +4 -4
  64. bumble/profiles/mcp.py +4 -4
  65. bumble/profiles/vcs.py +3 -5
  66. bumble/rfcomm.py +10 -10
  67. bumble/rtp.py +1 -2
  68. bumble/sdp.py +2 -2
  69. bumble/smp.py +57 -61
  70. bumble/tools/rtk_util.py +2 -2
  71. bumble/transport/__init__.py +2 -16
  72. bumble/transport/android_netsim.py +5 -5
  73. bumble/transport/common.py +4 -4
  74. bumble/transport/pyusb.py +2 -2
  75. bumble/utils.py +2 -5
  76. bumble/vendor/android/hci.py +118 -200
  77. bumble/vendor/zephyr/hci.py +32 -27
  78. {bumble-0.0.212.dist-info → bumble-0.0.213.dist-info}/METADATA +2 -2
  79. {bumble-0.0.212.dist-info → bumble-0.0.213.dist-info}/RECORD +83 -86
  80. {bumble-0.0.212.dist-info → bumble-0.0.213.dist-info}/WHEEL +1 -1
  81. {bumble-0.0.212.dist-info → bumble-0.0.213.dist-info}/entry_points.txt +0 -1
  82. bumble/apps/link_relay/__init__.py +0 -0
  83. bumble/apps/link_relay/link_relay.py +0 -289
  84. bumble/apps/link_relay/logging.yml +0 -21
  85. {bumble-0.0.212.dist-info → bumble-0.0.213.dist-info}/licenses/LICENSE +0 -0
  86. {bumble-0.0.212.dist-info → bumble-0.0.213.dist-info}/top_level.txt +0 -0
bumble/profiles/hap.py CHANGED
@@ -20,7 +20,7 @@ import asyncio
20
20
  import functools
21
21
  from dataclasses import dataclass, field
22
22
  import logging
23
- from typing import Any, Dict, List, Optional, Set, Union
23
+ from typing import Any, Optional, Union
24
24
 
25
25
  from bumble import att, gatt, gatt_adapters, gatt_client
26
26
  from bumble.core import InvalidArgumentError, InvalidStateError
@@ -228,23 +228,25 @@ class HearingAccessService(gatt.TemplateService):
228
228
  hearing_aid_preset_control_point: gatt.Characteristic[bytes]
229
229
  active_preset_index_characteristic: gatt.Characteristic[bytes]
230
230
  active_preset_index: int
231
- active_preset_index_per_device: Dict[Address, int]
231
+ active_preset_index_per_device: dict[Address, int]
232
232
 
233
233
  device: Device
234
234
 
235
235
  server_features: HearingAidFeatures
236
- preset_records: Dict[int, PresetRecord] # key is the preset index
236
+ preset_records: dict[int, PresetRecord] # key is the preset index
237
237
  read_presets_request_in_progress: bool
238
238
 
239
- preset_changed_operations_history_per_device: Dict[
240
- Address, List[PresetChangedOperation]
239
+ other_server_in_binaural_set: Optional[HearingAccessService] = None
240
+
241
+ preset_changed_operations_history_per_device: dict[
242
+ Address, list[PresetChangedOperation]
241
243
  ]
242
244
 
243
245
  # Keep an updated list of connected client to send notification to
244
- currently_connected_clients: Set[Connection]
246
+ currently_connected_clients: set[Connection]
245
247
 
246
248
  def __init__(
247
- self, device: Device, features: HearingAidFeatures, presets: List[PresetRecord]
249
+ self, device: Device, features: HearingAidFeatures, presets: list[PresetRecord]
248
250
  ) -> None:
249
251
  self.active_preset_index_per_device = {}
250
252
  self.read_presets_request_in_progress = False
@@ -333,7 +335,7 @@ class HearingAccessService(gatt.TemplateService):
333
335
  # Update the active preset index if needed
334
336
  await self.notify_active_preset_for_connection(connection)
335
337
 
336
- utils.cancel_on_event(connection, 'disconnection', on_connection_async())
338
+ connection.cancel_on_disconnection(on_connection_async())
337
339
 
338
340
  def _on_read_active_preset_index(self, connection: Connection) -> bytes:
339
341
  del connection # Unused
@@ -379,7 +381,7 @@ class HearingAccessService(gatt.TemplateService):
379
381
  utils.AsyncRunner.spawn(self._read_preset_response(connection, presets))
380
382
 
381
383
  async def _read_preset_response(
382
- self, connection: Connection, presets: List[PresetRecord]
384
+ self, connection: Connection, presets: list[PresetRecord]
383
385
  ):
384
386
  # If the ATT bearer is terminated before all notifications or indications are sent, then the server shall consider the Read Presets Request operation aborted and shall not either continue or restart the operation when the client reconnects.
385
387
  try:
@@ -513,7 +515,7 @@ class HearingAccessService(gatt.TemplateService):
513
515
  for connection in self.currently_connected_clients:
514
516
  await self.notify_active_preset_for_connection(connection)
515
517
 
516
- async def set_active_preset(self, connection: Connection, value: bytes) -> None:
518
+ async def set_active_preset(self, value: bytes) -> None:
517
519
  index = value[1]
518
520
  preset = self.preset_records.get(index, None)
519
521
  if (
@@ -530,10 +532,10 @@ class HearingAccessService(gatt.TemplateService):
530
532
  self.active_preset_index = index
531
533
  await self.notify_active_preset()
532
534
 
533
- async def _on_set_active_preset(self, connection: Connection, value: bytes):
534
- await self.set_active_preset(connection, value)
535
+ async def _on_set_active_preset(self, _: Connection, value: bytes):
536
+ await self.set_active_preset(value)
535
537
 
536
- async def set_next_or_previous_preset(self, connection: Connection, is_previous):
538
+ async def set_next_or_previous_preset(self, is_previous):
537
539
  '''Set the next or the previous preset as active'''
538
540
 
539
541
  if self.active_preset_index == 0x00:
@@ -563,48 +565,47 @@ class HearingAccessService(gatt.TemplateService):
563
565
  self.active_preset_index = first_preset.index
564
566
  await self.notify_active_preset()
565
567
 
566
- async def _on_set_next_preset(
567
- self, connection: Connection, __value__: bytes
568
- ) -> None:
569
- await self.set_next_or_previous_preset(connection, False)
568
+ async def _on_set_next_preset(self, _: Connection, __value__: bytes) -> None:
569
+ await self.set_next_or_previous_preset(False)
570
570
 
571
- async def _on_set_previous_preset(
572
- self, connection: Connection, __value__: bytes
573
- ) -> None:
574
- await self.set_next_or_previous_preset(connection, True)
571
+ async def _on_set_previous_preset(self, _: Connection, __value__: bytes) -> None:
572
+ await self.set_next_or_previous_preset(True)
575
573
 
576
574
  async def _on_set_active_preset_synchronized_locally(
577
- self, connection: Connection, value: bytes
575
+ self, _: Connection, value: bytes
578
576
  ):
579
577
  if (
580
578
  self.server_features.preset_synchronization_support
581
- == PresetSynchronizationSupport.PRESET_SYNCHRONIZATION_IS_SUPPORTED
579
+ == PresetSynchronizationSupport.PRESET_SYNCHRONIZATION_IS_NOT_SUPPORTED
582
580
  ):
583
581
  raise att.ATT_Error(ErrorCode.PRESET_SYNCHRONIZATION_NOT_SUPPORTED)
584
- await self.set_active_preset(connection, value)
585
- # TODO (low priority) inform other server of the change
582
+ await self.set_active_preset(value)
583
+ if self.other_server_in_binaural_set:
584
+ await self.other_server_in_binaural_set.set_active_preset(value)
586
585
 
587
586
  async def _on_set_next_preset_synchronized_locally(
588
- self, connection: Connection, __value__: bytes
587
+ self, _: Connection, __value__: bytes
589
588
  ):
590
589
  if (
591
590
  self.server_features.preset_synchronization_support
592
- == PresetSynchronizationSupport.PRESET_SYNCHRONIZATION_IS_SUPPORTED
591
+ == PresetSynchronizationSupport.PRESET_SYNCHRONIZATION_IS_NOT_SUPPORTED
593
592
  ):
594
593
  raise att.ATT_Error(ErrorCode.PRESET_SYNCHRONIZATION_NOT_SUPPORTED)
595
- await self.set_next_or_previous_preset(connection, False)
596
- # TODO (low priority) inform other server of the change
594
+ await self.set_next_or_previous_preset(False)
595
+ if self.other_server_in_binaural_set:
596
+ await self.other_server_in_binaural_set.set_next_or_previous_preset(False)
597
597
 
598
598
  async def _on_set_previous_preset_synchronized_locally(
599
- self, connection: Connection, __value__: bytes
599
+ self, _: Connection, __value__: bytes
600
600
  ):
601
601
  if (
602
602
  self.server_features.preset_synchronization_support
603
- == PresetSynchronizationSupport.PRESET_SYNCHRONIZATION_IS_SUPPORTED
603
+ == PresetSynchronizationSupport.PRESET_SYNCHRONIZATION_IS_NOT_SUPPORTED
604
604
  ):
605
605
  raise att.ATT_Error(ErrorCode.PRESET_SYNCHRONIZATION_NOT_SUPPORTED)
606
- await self.set_next_or_previous_preset(connection, True)
607
- # TODO (low priority) inform other server of the change
606
+ await self.set_next_or_previous_preset(True)
607
+ if self.other_server_in_binaural_set:
608
+ await self.other_server_in_binaural_set.set_next_or_previous_preset(True)
608
609
 
609
610
 
610
611
  # -----------------------------------------------------------------------------
@@ -19,7 +19,7 @@ from __future__ import annotations
19
19
  import dataclasses
20
20
  import enum
21
21
  import struct
22
- from typing import Any, List, Type
22
+ from typing import Any
23
23
  from typing_extensions import Self
24
24
 
25
25
  from bumble.profiles import bap
@@ -108,13 +108,13 @@ class Metadata:
108
108
  return self.data
109
109
 
110
110
  @classmethod
111
- def from_bytes(cls: Type[Self], data: bytes) -> Self:
111
+ def from_bytes(cls: type[Self], data: bytes) -> Self:
112
112
  return cls(tag=Metadata.Tag(data[0]), data=data[1:])
113
113
 
114
114
  def __bytes__(self) -> bytes:
115
115
  return bytes([len(self.data) + 1, self.tag]) + self.data
116
116
 
117
- entries: List[Entry] = dataclasses.field(default_factory=list)
117
+ entries: list[Entry] = dataclasses.field(default_factory=list)
118
118
 
119
119
  def pretty_print(self, indent: str) -> str:
120
120
  """Convenience method to generate a string with one key-value pair per line."""
@@ -140,7 +140,7 @@ class Metadata:
140
140
  )
141
141
 
142
142
  @classmethod
143
- def from_bytes(cls: Type[Self], data: bytes) -> Self:
143
+ def from_bytes(cls: type[Self], data: bytes) -> Self:
144
144
  entries = []
145
145
  offset = 0
146
146
  length = len(data)
bumble/profiles/mcp.py CHANGED
@@ -29,7 +29,7 @@ from bumble import gatt
29
29
  from bumble import gatt_client
30
30
  from bumble import utils
31
31
 
32
- from typing import Type, Optional, ClassVar, Dict, TYPE_CHECKING
32
+ from typing import Optional, ClassVar, TYPE_CHECKING
33
33
  from typing_extensions import Self
34
34
 
35
35
  # -----------------------------------------------------------------------------
@@ -167,7 +167,7 @@ class ObjectId(int):
167
167
  '''See Media Control Service 4.4.2. Object ID field.'''
168
168
 
169
169
  @classmethod
170
- def create_from_bytes(cls: Type[Self], data: bytes) -> Self:
170
+ def create_from_bytes(cls: type[Self], data: bytes) -> Self:
171
171
  return cls(int.from_bytes(data, byteorder='little', signed=False))
172
172
 
173
173
  def __bytes__(self) -> bytes:
@@ -182,7 +182,7 @@ class GroupObjectType:
182
182
  object_id: ObjectId
183
183
 
184
184
  @classmethod
185
- def from_bytes(cls: Type[Self], data: bytes) -> Self:
185
+ def from_bytes(cls: type[Self], data: bytes) -> Self:
186
186
  return cls(
187
187
  object_type=ObjectType(data[0]),
188
188
  object_id=ObjectId.create_from_bytes(data[1:]),
@@ -310,7 +310,7 @@ class MediaControlServiceProxy(
310
310
  ):
311
311
  SERVICE_CLASS = MediaControlService
312
312
 
313
- _CHARACTERISTICS: ClassVar[Dict[str, core.UUID]] = {
313
+ _CHARACTERISTICS: ClassVar[dict[str, core.UUID]] = {
314
314
  'media_player_name': gatt.GATT_MEDIA_PLAYER_NAME_CHARACTERISTIC,
315
315
  'media_player_icon_object_id': gatt.GATT_MEDIA_PLAYER_ICON_OBJECT_ID_CHARACTERISTIC,
316
316
  'media_player_icon_url': gatt.GATT_MEDIA_PLAYER_ICON_URL_CHARACTERISTIC,
bumble/profiles/vcs.py CHANGED
@@ -20,7 +20,7 @@ from __future__ import annotations
20
20
  import dataclasses
21
21
  import enum
22
22
 
23
- from typing import Optional, Sequence
23
+ from typing import Sequence
24
24
 
25
25
  from bumble import att
26
26
  from bumble import utils
@@ -161,10 +161,8 @@ class VolumeControlService(gatt.TemplateService):
161
161
  handler = getattr(self, '_on_' + opcode.name.lower())
162
162
  if handler(*value[2:]):
163
163
  self.change_counter = (self.change_counter + 1) % 256
164
- utils.cancel_on_event(
165
- connection,
166
- 'disconnection',
167
- connection.device.notify_subscribers(attribute=self.volume_state),
164
+ connection.cancel_on_disconnection(
165
+ connection.device.notify_subscribers(attribute=self.volume_state)
168
166
  )
169
167
  self.emit(self.EVENT_VOLUME_STATE_CHANGE)
170
168
 
bumble/rfcomm.py CHANGED
@@ -22,7 +22,7 @@ import asyncio
22
22
  import collections
23
23
  import dataclasses
24
24
  import enum
25
- from typing import Callable, Dict, List, Optional, Tuple, Union, TYPE_CHECKING
25
+ from typing import Callable, Optional, Union, TYPE_CHECKING
26
26
  from typing_extensions import Self
27
27
 
28
28
 
@@ -123,7 +123,7 @@ RFCOMM_DYNAMIC_CHANNEL_NUMBER_END = 30
123
123
  # -----------------------------------------------------------------------------
124
124
  def make_service_sdp_records(
125
125
  service_record_handle: int, channel: int, uuid: Optional[UUID] = None
126
- ) -> List[sdp.ServiceAttribute]:
126
+ ) -> list[sdp.ServiceAttribute]:
127
127
  """
128
128
  Create SDP records for an RFComm service given a channel number and an
129
129
  optional UUID. A Service Class Attribute is included only if the UUID is not None.
@@ -169,7 +169,7 @@ def make_service_sdp_records(
169
169
 
170
170
 
171
171
  # -----------------------------------------------------------------------------
172
- async def find_rfcomm_channels(connection: Connection) -> Dict[int, List[UUID]]:
172
+ async def find_rfcomm_channels(connection: Connection) -> dict[int, list[UUID]]:
173
173
  """Searches all RFCOMM channels and their associated UUID from SDP service records.
174
174
 
175
175
  Args:
@@ -188,7 +188,7 @@ async def find_rfcomm_channels(connection: Connection) -> Dict[int, List[UUID]]:
188
188
  ],
189
189
  )
190
190
  for attribute_lists in search_result:
191
- service_classes: List[UUID] = []
191
+ service_classes: list[UUID] = []
192
192
  channel: Optional[int] = None
193
193
  for attribute in attribute_lists:
194
194
  # The layout is [[L2CAP_PROTOCOL], [RFCOMM_PROTOCOL, RFCOMM_CHANNEL]].
@@ -275,7 +275,7 @@ class RFCOMM_Frame:
275
275
  self.fcs = compute_fcs(bytes([self.address, self.control]) + self.length)
276
276
 
277
277
  @staticmethod
278
- def parse_mcc(data) -> Tuple[int, bool, bytes]:
278
+ def parse_mcc(data) -> tuple[int, bool, bytes]:
279
279
  mcc_type = data[0] >> 2
280
280
  c_r = bool((data[0] >> 1) & 1)
281
281
  length = data[1]
@@ -771,8 +771,8 @@ class Multiplexer(utils.EventEmitter):
771
771
  connection_result: Optional[asyncio.Future]
772
772
  disconnection_result: Optional[asyncio.Future]
773
773
  open_result: Optional[asyncio.Future]
774
- acceptor: Optional[Callable[[int], Optional[Tuple[int, int]]]]
775
- dlcs: Dict[int, DLC]
774
+ acceptor: Optional[Callable[[int], Optional[tuple[int, int]]]]
775
+ dlcs: dict[int, DLC]
776
776
 
777
777
  def __init__(self, l2cap_channel: l2cap.ClassicChannel, role: Role) -> None:
778
778
  super().__init__()
@@ -1088,8 +1088,8 @@ class Server(utils.EventEmitter):
1088
1088
  ) -> None:
1089
1089
  super().__init__()
1090
1090
  self.device = device
1091
- self.acceptors: Dict[int, Callable[[DLC], None]] = {}
1092
- self.dlc_configs: Dict[int, Tuple[int, int]] = {}
1091
+ self.acceptors: dict[int, Callable[[DLC], None]] = {}
1092
+ self.dlc_configs: dict[int, tuple[int, int]] = {}
1093
1093
 
1094
1094
  # Register ourselves with the L2CAP channel manager
1095
1095
  self.l2cap_server = device.create_l2cap_server(
@@ -1144,7 +1144,7 @@ class Server(utils.EventEmitter):
1144
1144
  # Notify
1145
1145
  self.emit(self.EVENT_START, multiplexer)
1146
1146
 
1147
- def accept_dlc(self, channel_number: int) -> Optional[Tuple[int, int]]:
1147
+ def accept_dlc(self, channel_number: int) -> Optional[tuple[int, int]]:
1148
1148
  return self.dlc_configs.get(channel_number)
1149
1149
 
1150
1150
  def on_dlc(self, dlc: DLC) -> None:
bumble/rtp.py CHANGED
@@ -17,7 +17,6 @@
17
17
  # -----------------------------------------------------------------------------
18
18
  from __future__ import annotations
19
19
  import struct
20
- from typing import List
21
20
 
22
21
 
23
22
  # -----------------------------------------------------------------------------
@@ -60,7 +59,7 @@ class MediaPacket:
60
59
  sequence_number: int,
61
60
  timestamp: int,
62
61
  ssrc: int,
63
- csrc_list: List[int],
62
+ csrc_list: list[int],
64
63
  payload_type: int,
65
64
  payload: bytes,
66
65
  ) -> None:
bumble/sdp.py CHANGED
@@ -19,7 +19,7 @@ from __future__ import annotations
19
19
  import asyncio
20
20
  import logging
21
21
  import struct
22
- from typing import Iterable, NewType, Optional, Union, Sequence, Type, TYPE_CHECKING
22
+ from typing import Iterable, NewType, Optional, Union, Sequence, TYPE_CHECKING
23
23
  from typing_extensions import Self
24
24
 
25
25
  from bumble import core, l2cap
@@ -547,7 +547,7 @@ class SDP_PDU:
547
547
  SDP_SERVICE_ATTRIBUTE_REQUEST: SDP_SERVICE_ATTRIBUTE_RESPONSE,
548
548
  SDP_SERVICE_SEARCH_ATTRIBUTE_REQUEST: SDP_SERVICE_SEARCH_ATTRIBUTE_RESPONSE,
549
549
  }
550
- sdp_pdu_classes: dict[int, Type[SDP_PDU]] = {}
550
+ sdp_pdu_classes: dict[int, type[SDP_PDU]] = {}
551
551
  name = None
552
552
  pdu_id = 0
553
553
 
bumble/smp.py CHANGED
@@ -26,18 +26,13 @@ from __future__ import annotations
26
26
  import logging
27
27
  import asyncio
28
28
  import enum
29
- import secrets
30
29
  from dataclasses import dataclass
31
30
  from typing import (
32
31
  TYPE_CHECKING,
33
32
  Any,
34
33
  Awaitable,
35
34
  Callable,
36
- Dict,
37
- List,
38
35
  Optional,
39
- Tuple,
40
- Type,
41
36
  cast,
42
37
  )
43
38
 
@@ -210,7 +205,7 @@ class SMP_Command:
210
205
  See Bluetooth spec @ Vol 3, Part H - 3 SECURITY MANAGER PROTOCOL
211
206
  '''
212
207
 
213
- smp_classes: Dict[int, Type[SMP_Command]] = {}
208
+ smp_classes: dict[int, type[SMP_Command]] = {}
214
209
  fields: Any
215
210
  code = 0
216
211
  name = ''
@@ -254,7 +249,7 @@ class SMP_Command:
254
249
 
255
250
  @staticmethod
256
251
  def key_distribution_str(value: int) -> str:
257
- key_types: List[str] = []
252
+ key_types: list[str] = []
258
253
  if value & SMP_ENC_KEY_DISTRIBUTION_FLAG:
259
254
  key_types.append('ENC')
260
255
  if value & SMP_ID_KEY_DISTRIBUTION_FLAG:
@@ -706,7 +701,7 @@ class Session:
706
701
  self.peer_identity_resolving_key = None
707
702
  self.peer_bd_addr: Optional[Address] = None
708
703
  self.peer_signature_key = None
709
- self.peer_expected_distributions: List[Type[SMP_Command]] = []
704
+ self.peer_expected_distributions: list[type[SMP_Command]] = []
710
705
  self.dh_key = b''
711
706
  self.confirm_value = None
712
707
  self.passkey: Optional[int] = None
@@ -767,7 +762,9 @@ class Session:
767
762
 
768
763
  # OOB
769
764
  self.oob_data_flag = (
770
- 1 if pairing_config.oob and pairing_config.oob.peer_data else 0
765
+ 1
766
+ if pairing_config.oob and (not self.sc or pairing_config.oob.peer_data)
767
+ else 0
771
768
  )
772
769
 
773
770
  # Set up addresses
@@ -814,7 +811,7 @@ class Session:
814
811
  self.tk = bytes(16)
815
812
 
816
813
  @property
817
- def pkx(self) -> Tuple[bytes, bytes]:
814
+ def pkx(self) -> tuple[bytes, bytes]:
818
815
  return (self.ecc_key.x[::-1], self.peer_public_key_x)
819
816
 
820
817
  @property
@@ -826,7 +823,7 @@ class Session:
826
823
  return self.pkx[0 if self.is_responder else 1]
827
824
 
828
825
  @property
829
- def nx(self) -> Tuple[bytes, bytes]:
826
+ def nx(self) -> tuple[bytes, bytes]:
830
827
  assert self.peer_random_value
831
828
  return (self.r, self.peer_random_value)
832
829
 
@@ -900,7 +897,7 @@ class Session:
900
897
 
901
898
  self.send_pairing_failed(SMP_CONFIRM_VALUE_FAILED_ERROR)
902
899
 
903
- utils.cancel_on_event(self.connection, 'disconnection', prompt())
900
+ self.connection.cancel_on_disconnection(prompt())
904
901
 
905
902
  def prompt_user_for_numeric_comparison(
906
903
  self, code: int, next_steps: Callable[[], None]
@@ -919,7 +916,7 @@ class Session:
919
916
 
920
917
  self.send_pairing_failed(SMP_CONFIRM_VALUE_FAILED_ERROR)
921
918
 
922
- utils.cancel_on_event(self.connection, 'disconnection', prompt())
919
+ self.connection.cancel_on_disconnection(prompt())
923
920
 
924
921
  def prompt_user_for_number(self, next_steps: Callable[[int], None]) -> None:
925
922
  async def prompt() -> None:
@@ -936,12 +933,11 @@ class Session:
936
933
  logger.warning(f'exception while prompting: {error}')
937
934
  self.send_pairing_failed(SMP_PASSKEY_ENTRY_FAILED_ERROR)
938
935
 
939
- utils.cancel_on_event(self.connection, 'disconnection', prompt())
936
+ self.connection.cancel_on_disconnection(prompt())
940
937
 
941
- def display_passkey(self) -> None:
942
- # Generate random Passkey/PIN code
943
- self.passkey = secrets.randbelow(1000000)
944
- assert self.passkey is not None
938
+ async def display_passkey(self) -> None:
939
+ # Get the passkey value from the delegate
940
+ self.passkey = await self.pairing_config.delegate.generate_passkey()
945
941
  logger.debug(f'Pairing PIN CODE: {self.passkey:06}')
946
942
  self.passkey_ready.set()
947
943
 
@@ -950,14 +946,7 @@ class Session:
950
946
  self.tk = self.passkey.to_bytes(16, byteorder='little')
951
947
  logger.debug(f'TK from passkey = {self.tk.hex()}')
952
948
 
953
- try:
954
- utils.cancel_on_event(
955
- self.connection,
956
- 'disconnection',
957
- self.pairing_config.delegate.display_number(self.passkey, digits=6),
958
- )
959
- except Exception as error:
960
- logger.warning(f'exception while displaying number: {error}')
949
+ await self.pairing_config.delegate.display_number(self.passkey, digits=6)
961
950
 
962
951
  def input_passkey(self, next_steps: Optional[Callable[[], None]] = None) -> None:
963
952
  # Prompt the user for the passkey displayed on the peer
@@ -979,9 +968,16 @@ class Session:
979
968
  self, next_steps: Optional[Callable[[], None]] = None
980
969
  ) -> None:
981
970
  if self.passkey_display:
982
- self.display_passkey()
983
- if next_steps is not None:
984
- next_steps()
971
+
972
+ async def display_passkey():
973
+ await self.display_passkey()
974
+ if next_steps is not None:
975
+ next_steps()
976
+
977
+ try:
978
+ self.connection.cancel_on_disconnection(display_passkey())
979
+ except Exception as error:
980
+ logger.warning(f'exception while displaying passkey: {error}')
985
981
  else:
986
982
  self.input_passkey(next_steps)
987
983
 
@@ -1051,7 +1047,7 @@ class Session:
1051
1047
  )
1052
1048
 
1053
1049
  # Perform the next steps asynchronously in case we need to wait for input
1054
- utils.cancel_on_event(self.connection, 'disconnection', next_steps())
1050
+ self.connection.cancel_on_disconnection(next_steps())
1055
1051
  else:
1056
1052
  confirm_value = crypto.c1(
1057
1053
  self.tk,
@@ -1174,8 +1170,8 @@ class Session:
1174
1170
  self.connection.transport == PhysicalTransport.BR_EDR
1175
1171
  and self.initiator_key_distribution & SMP_ENC_KEY_DISTRIBUTION_FLAG
1176
1172
  ):
1177
- self.ctkd_task = utils.cancel_on_event(
1178
- self.connection, 'disconnection', self.get_link_key_and_derive_ltk()
1173
+ self.ctkd_task = self.connection.cancel_on_disconnection(
1174
+ self.get_link_key_and_derive_ltk()
1179
1175
  )
1180
1176
  elif not self.sc:
1181
1177
  # Distribute the LTK, EDIV and RAND
@@ -1213,8 +1209,8 @@ class Session:
1213
1209
  self.connection.transport == PhysicalTransport.BR_EDR
1214
1210
  and self.responder_key_distribution & SMP_ENC_KEY_DISTRIBUTION_FLAG
1215
1211
  ):
1216
- self.ctkd_task = utils.cancel_on_event(
1217
- self.connection, 'disconnection', self.get_link_key_and_derive_ltk()
1212
+ self.ctkd_task = self.connection.cancel_on_disconnection(
1213
+ self.get_link_key_and_derive_ltk()
1218
1214
  )
1219
1215
  # Distribute the LTK, EDIV and RAND
1220
1216
  elif not self.sc:
@@ -1269,7 +1265,7 @@ class Session:
1269
1265
  f'{[c.__name__ for c in self.peer_expected_distributions]}'
1270
1266
  )
1271
1267
 
1272
- def check_key_distribution(self, command_class: Type[SMP_Command]) -> None:
1268
+ def check_key_distribution(self, command_class: type[SMP_Command]) -> None:
1273
1269
  # First, check that the connection is encrypted
1274
1270
  if not self.connection.is_encrypted:
1275
1271
  logger.warning(
@@ -1306,9 +1302,7 @@ class Session:
1306
1302
 
1307
1303
  # Wait for the pairing process to finish
1308
1304
  assert self.pairing_result
1309
- await utils.cancel_on_event(
1310
- self.connection, 'disconnection', self.pairing_result
1311
- )
1305
+ await self.connection.cancel_on_disconnection(self.pairing_result)
1312
1306
 
1313
1307
  def on_disconnection(self, _: int) -> None:
1314
1308
  self.connection.remove_listener(
@@ -1329,7 +1323,7 @@ class Session:
1329
1323
  if self.is_initiator:
1330
1324
  self.distribute_keys()
1331
1325
 
1332
- utils.cancel_on_event(self.connection, 'disconnection', self.on_pairing())
1326
+ self.connection.cancel_on_disconnection(self.on_pairing())
1333
1327
 
1334
1328
  def on_connection_encryption_change(self) -> None:
1335
1329
  if self.connection.is_encrypted and not self.completed:
@@ -1440,10 +1434,8 @@ class Session:
1440
1434
  def on_smp_pairing_request_command(
1441
1435
  self, command: SMP_Pairing_Request_Command
1442
1436
  ) -> None:
1443
- utils.cancel_on_event(
1444
- self.connection,
1445
- 'disconnection',
1446
- self.on_smp_pairing_request_command_async(command),
1437
+ self.connection.cancel_on_disconnection(
1438
+ self.on_smp_pairing_request_command_async(command)
1447
1439
  )
1448
1440
 
1449
1441
  async def on_smp_pairing_request_command_async(
@@ -1507,7 +1499,7 @@ class Session:
1507
1499
  # Display a passkey if we need to
1508
1500
  if not self.sc:
1509
1501
  if self.pairing_method == PairingMethod.PASSKEY and self.passkey_display:
1510
- self.display_passkey()
1502
+ await self.display_passkey()
1511
1503
 
1512
1504
  # Respond
1513
1505
  self.send_pairing_response_command()
@@ -1689,7 +1681,7 @@ class Session:
1689
1681
  ):
1690
1682
  return
1691
1683
  elif self.pairing_method == PairingMethod.PASSKEY:
1692
- assert self.passkey and self.confirm_value
1684
+ assert self.passkey is not None and self.confirm_value is not None
1693
1685
  # Check that the random value matches what was committed to earlier
1694
1686
  confirm_verifier = crypto.f4(
1695
1687
  self.pkb,
@@ -1718,7 +1710,7 @@ class Session:
1718
1710
  ):
1719
1711
  self.send_pairing_random_command()
1720
1712
  elif self.pairing_method == PairingMethod.PASSKEY:
1721
- assert self.passkey and self.confirm_value
1713
+ assert self.passkey is not None and self.confirm_value is not None
1722
1714
  # Check that the random value matches what was committed to earlier
1723
1715
  confirm_verifier = crypto.f4(
1724
1716
  self.pka,
@@ -1755,7 +1747,7 @@ class Session:
1755
1747
  ra = bytes(16)
1756
1748
  rb = ra
1757
1749
  elif self.pairing_method == PairingMethod.PASSKEY:
1758
- assert self.passkey
1750
+ assert self.passkey is not None
1759
1751
  ra = self.passkey.to_bytes(16, byteorder='little')
1760
1752
  rb = ra
1761
1753
  elif self.pairing_method == PairingMethod.OOB:
@@ -1854,19 +1846,23 @@ class Session:
1854
1846
  elif self.pairing_method == PairingMethod.PASSKEY:
1855
1847
  self.send_pairing_confirm_command()
1856
1848
  else:
1857
- if self.pairing_method == PairingMethod.PASSKEY:
1858
- self.display_or_input_passkey()
1859
1849
 
1860
- # Send our public key back to the initiator
1861
- self.send_public_key_command()
1850
+ def next_steps() -> None:
1851
+ # Send our public key back to the initiator
1852
+ self.send_public_key_command()
1862
1853
 
1863
- if self.pairing_method in (
1864
- PairingMethod.JUST_WORKS,
1865
- PairingMethod.NUMERIC_COMPARISON,
1866
- PairingMethod.OOB,
1867
- ):
1868
- # We can now send the confirmation value
1869
- self.send_pairing_confirm_command()
1854
+ if self.pairing_method in (
1855
+ PairingMethod.JUST_WORKS,
1856
+ PairingMethod.NUMERIC_COMPARISON,
1857
+ PairingMethod.OOB,
1858
+ ):
1859
+ # We can now send the confirmation value
1860
+ self.send_pairing_confirm_command()
1861
+
1862
+ if self.pairing_method == PairingMethod.PASSKEY:
1863
+ self.display_or_input_passkey(next_steps)
1864
+ else:
1865
+ next_steps()
1870
1866
 
1871
1867
  def on_smp_pairing_dhkey_check_command(
1872
1868
  self, command: SMP_Pairing_DHKey_Check_Command
@@ -1888,7 +1884,7 @@ class Session:
1888
1884
  self.wait_before_continuing = None
1889
1885
  self.send_pairing_dhkey_check_command()
1890
1886
 
1891
- utils.cancel_on_event(self.connection, 'disconnection', next_steps())
1887
+ self.connection.cancel_on_disconnection(next_steps())
1892
1888
  else:
1893
1889
  self.send_pairing_dhkey_check_command()
1894
1890
  else:
@@ -1938,9 +1934,9 @@ class Manager(utils.EventEmitter):
1938
1934
  '''
1939
1935
 
1940
1936
  device: Device
1941
- sessions: Dict[int, Session]
1937
+ sessions: dict[int, Session]
1942
1938
  pairing_config_factory: Callable[[Connection], PairingConfig]
1943
- session_proxy: Type[Session]
1939
+ session_proxy: type[Session]
1944
1940
  _ecc_key: Optional[crypto.EccKey]
1945
1941
 
1946
1942
  def __init__(
bumble/tools/rtk_util.py CHANGED
@@ -50,7 +50,7 @@ def do_parse(firmware_path):
50
50
 
51
51
  # -----------------------------------------------------------------------------
52
52
  async def do_load(usb_transport, force):
53
- async with await transport.open_transport_or_link(usb_transport) as (
53
+ async with await transport.open_transport(usb_transport) as (
54
54
  hci_source,
55
55
  hci_sink,
56
56
  ):
@@ -69,7 +69,7 @@ async def do_load(usb_transport, force):
69
69
 
70
70
  # -----------------------------------------------------------------------------
71
71
  async def do_drop(usb_transport):
72
- async with await transport.open_transport_or_link(usb_transport) as (
72
+ async with await transport.open_transport(usb_transport) as (
73
73
  hci_source,
74
74
  hci_sink,
75
75
  ):