bumble 0.0.211__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 (95) 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 +482 -31
  6. bumble/apps/console.py +5 -5
  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 +204 -43
  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 +15 -18
  26. bumble/avc.py +7 -7
  27. bumble/avctp.py +5 -5
  28. bumble/avdtp.py +138 -88
  29. bumble/avrcp.py +52 -58
  30. bumble/colors.py +2 -2
  31. bumble/controller.py +84 -23
  32. bumble/core.py +13 -7
  33. bumble/{crypto.py → crypto/__init__.py} +11 -95
  34. bumble/crypto/builtin.py +652 -0
  35. bumble/crypto/cryptography.py +84 -0
  36. bumble/device.py +688 -345
  37. bumble/drivers/__init__.py +2 -2
  38. bumble/drivers/common.py +0 -2
  39. bumble/drivers/intel.py +40 -40
  40. bumble/drivers/rtk.py +28 -35
  41. bumble/gatt.py +7 -9
  42. bumble/gatt_adapters.py +4 -5
  43. bumble/gatt_client.py +31 -34
  44. bumble/gatt_server.py +15 -17
  45. bumble/hci.py +2635 -2878
  46. bumble/helpers.py +4 -5
  47. bumble/hfp.py +76 -57
  48. bumble/hid.py +24 -12
  49. bumble/host.py +117 -34
  50. bumble/keys.py +68 -52
  51. bumble/l2cap.py +329 -403
  52. bumble/link.py +6 -270
  53. bumble/pairing.py +23 -20
  54. bumble/pandora/__init__.py +1 -1
  55. bumble/pandora/config.py +2 -2
  56. bumble/pandora/device.py +6 -6
  57. bumble/pandora/host.py +38 -39
  58. bumble/pandora/l2cap.py +4 -4
  59. bumble/pandora/security.py +73 -57
  60. bumble/pandora/utils.py +3 -3
  61. bumble/profiles/aics.py +3 -5
  62. bumble/profiles/ancs.py +3 -1
  63. bumble/profiles/ascs.py +143 -136
  64. bumble/profiles/asha.py +13 -8
  65. bumble/profiles/bap.py +3 -4
  66. bumble/profiles/csip.py +3 -5
  67. bumble/profiles/device_information_service.py +2 -2
  68. bumble/profiles/gap.py +2 -2
  69. bumble/profiles/gatt_service.py +1 -3
  70. bumble/profiles/hap.py +42 -58
  71. bumble/profiles/le_audio.py +4 -4
  72. bumble/profiles/mcp.py +16 -13
  73. bumble/profiles/vcs.py +8 -10
  74. bumble/profiles/vocs.py +6 -9
  75. bumble/rfcomm.py +27 -18
  76. bumble/rtp.py +1 -2
  77. bumble/sdp.py +2 -2
  78. bumble/smp.py +71 -69
  79. bumble/tools/rtk_util.py +2 -2
  80. bumble/transport/__init__.py +2 -16
  81. bumble/transport/android_netsim.py +5 -5
  82. bumble/transport/common.py +4 -4
  83. bumble/transport/pyusb.py +2 -2
  84. bumble/utils.py +2 -5
  85. bumble/vendor/android/hci.py +118 -200
  86. bumble/vendor/zephyr/hci.py +32 -27
  87. {bumble-0.0.211.dist-info → bumble-0.0.213.dist-info}/METADATA +5 -5
  88. {bumble-0.0.211.dist-info → bumble-0.0.213.dist-info}/RECORD +92 -93
  89. {bumble-0.0.211.dist-info → bumble-0.0.213.dist-info}/WHEEL +1 -1
  90. {bumble-0.0.211.dist-info → bumble-0.0.213.dist-info}/entry_points.txt +0 -1
  91. bumble/apps/link_relay/__init__.py +0 -0
  92. bumble/apps/link_relay/link_relay.py +0 -289
  93. bumble/apps/link_relay/logging.yml +0 -21
  94. {bumble-0.0.211.dist-info → bumble-0.0.213.dist-info}/licenses/LICENSE +0 -0
  95. {bumble-0.0.211.dist-info → bumble-0.0.213.dist-info}/top_level.txt +0 -0
bumble/host.py CHANGED
@@ -26,10 +26,7 @@ from typing import (
26
26
  Any,
27
27
  Awaitable,
28
28
  Callable,
29
- Deque,
30
- Dict,
31
29
  Optional,
32
- Set,
33
30
  cast,
34
31
  TYPE_CHECKING,
35
32
  )
@@ -41,7 +38,6 @@ from bumble.snoop import Snooper
41
38
  from bumble import drivers
42
39
  from bumble import hci
43
40
  from bumble.core import (
44
- PhysicalTransport,
45
41
  PhysicalTransport,
46
42
  ConnectionPHY,
47
43
  ConnectionParameters,
@@ -75,6 +71,11 @@ class DataPacketQueue(utils.EventEmitter):
75
71
 
76
72
  max_packet_size: int
77
73
 
74
+ class PerConnectionState:
75
+ def __init__(self) -> None:
76
+ self.in_flight = 0
77
+ self.drained = asyncio.Event()
78
+
78
79
  def __init__(
79
80
  self,
80
81
  max_packet_size: int,
@@ -85,11 +86,16 @@ class DataPacketQueue(utils.EventEmitter):
85
86
  self.max_packet_size = max_packet_size
86
87
  self.max_in_flight = max_in_flight
87
88
  self._in_flight = 0 # Total number of packets in flight across all connections
88
- self._in_flight_per_connection: dict[int, int] = collections.defaultdict(
89
- int
90
- ) # Number of packets in flight per connection
89
+ self._connection_state: dict[int, DataPacketQueue.PerConnectionState] = (
90
+ collections.defaultdict(DataPacketQueue.PerConnectionState)
91
+ )
92
+ self._drained_per_connection: dict[int, asyncio.Event] = (
93
+ collections.defaultdict(asyncio.Event)
94
+ )
91
95
  self._send = send
92
- self._packets: Deque[tuple[hci.HCI_Packet, int]] = collections.deque()
96
+ self._packets: collections.deque[tuple[hci.HCI_Packet, int]] = (
97
+ collections.deque()
98
+ )
93
99
  self._queued = 0
94
100
  self._completed = 0
95
101
 
@@ -137,36 +143,40 @@ class DataPacketQueue(utils.EventEmitter):
137
143
  self._completed += flushed_count
138
144
  self._packets = collections.deque(packets_to_keep)
139
145
 
140
- if connection_handle in self._in_flight_per_connection:
141
- in_flight = self._in_flight_per_connection[connection_handle]
146
+ if connection_state := self._connection_state.pop(connection_handle, None):
147
+ in_flight = connection_state.in_flight
142
148
  self._completed += in_flight
143
149
  self._in_flight -= in_flight
144
- del self._in_flight_per_connection[connection_handle]
150
+ connection_state.drained.set()
145
151
 
146
152
  def _check_queue(self) -> None:
147
153
  while self._packets and self._in_flight < self.max_in_flight:
148
154
  packet, connection_handle = self._packets.pop()
149
155
  self._send(packet)
150
156
  self._in_flight += 1
151
- self._in_flight_per_connection[connection_handle] += 1
157
+ connection_state = self._connection_state[connection_handle]
158
+ connection_state.in_flight += 1
159
+ connection_state.drained.clear()
152
160
 
153
161
  def on_packets_completed(self, packet_count: int, connection_handle: int) -> None:
154
162
  """Mark one or more packets associated with a connection as completed."""
155
- if connection_handle not in self._in_flight_per_connection:
163
+ if connection_handle not in self._connection_state:
156
164
  logger.warning(
157
165
  f'received completion for unknown connection {connection_handle}'
158
166
  )
159
167
  return
160
168
 
161
- in_flight_for_connection = self._in_flight_per_connection[connection_handle]
162
- if packet_count <= in_flight_for_connection:
163
- self._in_flight_per_connection[connection_handle] -= packet_count
169
+ connection_state = self._connection_state[connection_handle]
170
+ if packet_count <= connection_state.in_flight:
171
+ connection_state.in_flight -= packet_count
164
172
  else:
165
173
  logger.warning(
166
174
  f'{packet_count} completed for {connection_handle} '
167
- f'but only {in_flight_for_connection} in flight'
175
+ f'but only {connection_state.in_flight} in flight'
168
176
  )
169
- self._in_flight_per_connection[connection_handle] = 0
177
+ connection_state.in_flight = 0
178
+ if connection_state.in_flight == 0:
179
+ connection_state.drained.set()
170
180
 
171
181
  if packet_count <= self._in_flight:
172
182
  self._in_flight -= packet_count
@@ -181,6 +191,13 @@ class DataPacketQueue(utils.EventEmitter):
181
191
  self._check_queue()
182
192
  self.emit('flow')
183
193
 
194
+ async def drain(self, connection_handle: int) -> None:
195
+ """Wait until there are no pending packets for a connection."""
196
+ if not (connection_state := self._connection_state.get(connection_handle)):
197
+ raise ValueError('no such connection')
198
+
199
+ await connection_state.drained.wait()
200
+
184
201
 
185
202
  # -----------------------------------------------------------------------------
186
203
  class Connection:
@@ -234,16 +251,16 @@ class IsoLink:
234
251
 
235
252
  # -----------------------------------------------------------------------------
236
253
  class Host(utils.EventEmitter):
237
- connections: Dict[int, Connection]
238
- cis_links: Dict[int, IsoLink]
239
- bis_links: Dict[int, IsoLink]
240
- sco_links: Dict[int, ScoLink]
254
+ connections: dict[int, Connection]
255
+ cis_links: dict[int, IsoLink]
256
+ bis_links: dict[int, IsoLink]
257
+ sco_links: dict[int, ScoLink]
241
258
  bigs: dict[int, set[int]]
242
259
  acl_packet_queue: Optional[DataPacketQueue] = None
243
260
  le_acl_packet_queue: Optional[DataPacketQueue] = None
244
261
  iso_packet_queue: Optional[DataPacketQueue] = None
245
262
  hci_sink: Optional[TransportSink] = None
246
- hci_metadata: Dict[str, Any]
263
+ hci_metadata: dict[str, Any]
247
264
  long_term_key_provider: Optional[
248
265
  Callable[[int, bytes, int], Awaitable[Optional[bytes]]]
249
266
  ]
@@ -435,6 +452,14 @@ class Host(utils.EventEmitter):
435
452
  )
436
453
  )
437
454
  )
455
+ if self.supports_command(hci.HCI_SET_EVENT_MASK_PAGE_2_COMMAND):
456
+ await self.send_command(
457
+ hci.HCI_Set_Event_Mask_Page_2_Command(
458
+ event_mask_page_2=hci.HCI_Set_Event_Mask_Page_2_Command.mask(
459
+ [hci.HCI_ENCRYPTION_CHANGE_V2_EVENT]
460
+ )
461
+ )
462
+ )
438
463
 
439
464
  if (
440
465
  self.local_version is not None
@@ -456,6 +481,7 @@ class Host(utils.EventEmitter):
456
481
  hci.HCI_LE_READ_LOCAL_P_256_PUBLIC_KEY_COMPLETE_EVENT,
457
482
  hci.HCI_LE_GENERATE_DHKEY_COMPLETE_EVENT,
458
483
  hci.HCI_LE_ENHANCED_CONNECTION_COMPLETE_EVENT,
484
+ hci.HCI_LE_ENHANCED_CONNECTION_COMPLETE_V2_EVENT,
459
485
  hci.HCI_LE_DIRECTED_ADVERTISING_REPORT_EVENT,
460
486
  hci.HCI_LE_PHY_UPDATE_COMPLETE_EVENT,
461
487
  hci.HCI_LE_EXTENDED_ADVERTISING_REPORT_EVENT,
@@ -804,7 +830,7 @@ class Host(utils.EventEmitter):
804
830
  ) != 0
805
831
 
806
832
  @property
807
- def supported_commands(self) -> Set[int]:
833
+ def supported_commands(self) -> set[int]:
808
834
  return set(
809
835
  op_code
810
836
  for op_code, mask in hci.HCI_SUPPORTED_COMMANDS_MASKS.items()
@@ -827,8 +853,8 @@ class Host(utils.EventEmitter):
827
853
  def on_packet(self, packet: bytes) -> None:
828
854
  try:
829
855
  hci_packet = hci.HCI_Packet.from_bytes(packet)
830
- except Exception as error:
831
- logger.warning(f'!!! error parsing packet from bytes: {error}')
856
+ except Exception:
857
+ logger.exception('!!! error parsing packet from bytes')
832
858
  return
833
859
 
834
860
  if self.ready or (
@@ -1118,11 +1144,19 @@ class Host(utils.EventEmitter):
1118
1144
  else:
1119
1145
  self.emit('connection_phy_update_failure', connection.handle, event.status)
1120
1146
 
1121
- def on_hci_le_advertising_report_event(self, event):
1147
+ def on_hci_le_advertising_report_event(
1148
+ self,
1149
+ event: (
1150
+ hci.HCI_LE_Advertising_Report_Event
1151
+ | hci.HCI_LE_Extended_Advertising_Report_Event
1152
+ ),
1153
+ ):
1122
1154
  for report in event.reports:
1123
1155
  self.emit('advertising_report', report)
1124
1156
 
1125
- def on_hci_le_extended_advertising_report_event(self, event):
1157
+ def on_hci_le_extended_advertising_report_event(
1158
+ self, event: hci.HCI_LE_Extended_Advertising_Report_Event
1159
+ ):
1126
1160
  self.on_hci_le_advertising_report_event(event)
1127
1161
 
1128
1162
  def on_hci_le_advertising_set_terminated_event(self, event):
@@ -1253,7 +1287,24 @@ class Host(utils.EventEmitter):
1253
1287
  self.cis_links[event.connection_handle] = IsoLink(
1254
1288
  handle=event.connection_handle, packet_queue=self.iso_packet_queue
1255
1289
  )
1256
- self.emit('cis_establishment', event.connection_handle)
1290
+ self.emit(
1291
+ 'cis_establishment',
1292
+ event.connection_handle,
1293
+ event.cig_sync_delay,
1294
+ event.cis_sync_delay,
1295
+ event.transport_latency_c_to_p,
1296
+ event.transport_latency_p_to_c,
1297
+ event.phy_c_to_p,
1298
+ event.phy_p_to_c,
1299
+ event.nse,
1300
+ event.bn_c_to_p,
1301
+ event.bn_p_to_c,
1302
+ event.ft_c_to_p,
1303
+ event.ft_p_to_c,
1304
+ event.max_pdu_c_to_p,
1305
+ event.max_pdu_p_to_c,
1306
+ event.iso_interval,
1307
+ )
1257
1308
  else:
1258
1309
  self.emit(
1259
1310
  'cis_establishment_failure', event.connection_handle, event.status
@@ -1341,6 +1392,15 @@ class Host(utils.EventEmitter):
1341
1392
  def on_hci_synchronous_connection_changed_event(self, event):
1342
1393
  pass
1343
1394
 
1395
+ def on_hci_mode_change_event(self, event: hci.HCI_Mode_Change_Event):
1396
+ self.emit(
1397
+ 'mode_change',
1398
+ event.connection_handle,
1399
+ event.status,
1400
+ event.current_mode,
1401
+ event.interval,
1402
+ )
1403
+
1344
1404
  def on_hci_role_change_event(self, event):
1345
1405
  if event.status == hci.HCI_SUCCESS:
1346
1406
  logger.debug(
@@ -1356,6 +1416,10 @@ class Host(utils.EventEmitter):
1356
1416
  self.emit('role_change_failure', event.bd_addr, event.status)
1357
1417
 
1358
1418
  def on_hci_le_data_length_change_event(self, event):
1419
+ if (connection := self.connections.get(event.connection_handle)) is None:
1420
+ logger.warning('!!! DATA LENGTH CHANGE: unknown handle')
1421
+ return
1422
+
1359
1423
  self.emit(
1360
1424
  'connection_data_length_change',
1361
1425
  event.connection_handle,
@@ -1376,13 +1440,30 @@ class Host(utils.EventEmitter):
1376
1440
  event.status,
1377
1441
  )
1378
1442
 
1379
- def on_hci_encryption_change_event(self, event):
1443
+ def on_hci_encryption_change_event(self, event: hci.HCI_Encryption_Change_Event):
1380
1444
  # Notify the client
1381
1445
  if event.status == hci.HCI_SUCCESS:
1382
1446
  self.emit(
1383
1447
  'connection_encryption_change',
1384
1448
  event.connection_handle,
1385
1449
  event.encryption_enabled,
1450
+ 0,
1451
+ )
1452
+ else:
1453
+ self.emit(
1454
+ 'connection_encryption_failure', event.connection_handle, event.status
1455
+ )
1456
+
1457
+ def on_hci_encryption_change_v2_event(
1458
+ self, event: hci.HCI_Encryption_Change_V2_Event
1459
+ ):
1460
+ # Notify the client
1461
+ if event.status == hci.HCI_SUCCESS:
1462
+ self.emit(
1463
+ 'connection_encryption_change',
1464
+ event.connection_handle,
1465
+ event.encryption_enabled,
1466
+ event.encryption_key_size,
1386
1467
  )
1387
1468
  else:
1388
1469
  self.emit(
@@ -1496,13 +1577,15 @@ class Host(utils.EventEmitter):
1496
1577
  self.emit('inquiry_complete')
1497
1578
 
1498
1579
  def on_hci_inquiry_result_with_rssi_event(self, event):
1499
- for response in event.responses:
1580
+ for bd_addr, class_of_device, rssi in zip(
1581
+ event.bd_addr, event.class_of_device, event.rssi
1582
+ ):
1500
1583
  self.emit(
1501
1584
  'inquiry_result',
1502
- response.bd_addr,
1503
- response.class_of_device,
1585
+ bd_addr,
1586
+ class_of_device,
1504
1587
  b'',
1505
- response.rssi,
1588
+ rssi,
1506
1589
  )
1507
1590
 
1508
1591
  def on_hci_extended_inquiry_result_event(self, event):
bumble/keys.py CHANGED
@@ -22,14 +22,15 @@
22
22
  # -----------------------------------------------------------------------------
23
23
  from __future__ import annotations
24
24
  import asyncio
25
+ import dataclasses
25
26
  import logging
26
27
  import os
27
28
  import json
28
- from typing import TYPE_CHECKING, Dict, List, Optional, Tuple, Type
29
+ from typing import TYPE_CHECKING, Optional, Any
29
30
  from typing_extensions import Self
30
31
 
31
32
  from bumble.colors import color
32
- from bumble.hci import Address
33
+ from bumble import hci
33
34
 
34
35
  if TYPE_CHECKING:
35
36
  from bumble.device import Device
@@ -42,16 +43,17 @@ logger = logging.getLogger(__name__)
42
43
 
43
44
 
44
45
  # -----------------------------------------------------------------------------
46
+ @dataclasses.dataclass
45
47
  class PairingKeys:
48
+ @dataclasses.dataclass
46
49
  class Key:
47
- def __init__(self, value, authenticated=False, ediv=None, rand=None):
48
- self.value = value
49
- self.authenticated = authenticated
50
- self.ediv = ediv
51
- self.rand = rand
50
+ value: bytes
51
+ authenticated: bool = False
52
+ ediv: Optional[int] = None
53
+ rand: Optional[bytes] = None
52
54
 
53
55
  @classmethod
54
- def from_dict(cls, key_dict):
56
+ def from_dict(cls, key_dict: dict[str, Any]) -> PairingKeys.Key:
55
57
  value = bytes.fromhex(key_dict['value'])
56
58
  authenticated = key_dict.get('authenticated', False)
57
59
  ediv = key_dict.get('ediv')
@@ -61,7 +63,7 @@ class PairingKeys:
61
63
 
62
64
  return cls(value, authenticated, ediv, rand)
63
65
 
64
- def to_dict(self):
66
+ def to_dict(self) -> dict[str, Any]:
65
67
  key_dict = {'value': self.value.hex(), 'authenticated': self.authenticated}
66
68
  if self.ediv is not None:
67
69
  key_dict['ediv'] = self.ediv
@@ -70,39 +72,42 @@ class PairingKeys:
70
72
 
71
73
  return key_dict
72
74
 
73
- def __init__(self):
74
- self.address_type = None
75
- self.ltk = None
76
- self.ltk_central = None
77
- self.ltk_peripheral = None
78
- self.irk = None
79
- self.csrk = None
80
- self.link_key = None # Classic
81
-
82
- @staticmethod
83
- def key_from_dict(keys_dict, key_name):
75
+ address_type: Optional[hci.AddressType] = None
76
+ ltk: Optional[Key] = None
77
+ ltk_central: Optional[Key] = None
78
+ ltk_peripheral: Optional[Key] = None
79
+ irk: Optional[Key] = None
80
+ csrk: Optional[Key] = None
81
+ link_key: Optional[Key] = None # Classic
82
+ link_key_type: Optional[int] = None # Classic
83
+
84
+ @classmethod
85
+ def key_from_dict(cls, keys_dict: dict[str, Any], key_name: str) -> Optional[Key]:
84
86
  key_dict = keys_dict.get(key_name)
85
87
  if key_dict is None:
86
88
  return None
87
89
 
88
90
  return PairingKeys.Key.from_dict(key_dict)
89
91
 
90
- @staticmethod
91
- def from_dict(keys_dict):
92
- keys = PairingKeys()
93
-
94
- keys.address_type = keys_dict.get('address_type')
95
- keys.ltk = PairingKeys.key_from_dict(keys_dict, 'ltk')
96
- keys.ltk_central = PairingKeys.key_from_dict(keys_dict, 'ltk_central')
97
- keys.ltk_peripheral = PairingKeys.key_from_dict(keys_dict, 'ltk_peripheral')
98
- keys.irk = PairingKeys.key_from_dict(keys_dict, 'irk')
99
- keys.csrk = PairingKeys.key_from_dict(keys_dict, 'csrk')
100
- keys.link_key = PairingKeys.key_from_dict(keys_dict, 'link_key')
101
-
102
- return keys
103
-
104
- def to_dict(self):
105
- keys = {}
92
+ @classmethod
93
+ def from_dict(cls, keys_dict: dict[str, Any]) -> PairingKeys:
94
+ return PairingKeys(
95
+ address_type=(
96
+ hci.AddressType(t)
97
+ if (t := keys_dict.get('address_type')) is not None
98
+ else None
99
+ ),
100
+ ltk=PairingKeys.key_from_dict(keys_dict, 'ltk'),
101
+ ltk_central=PairingKeys.key_from_dict(keys_dict, 'ltk_central'),
102
+ ltk_peripheral=PairingKeys.key_from_dict(keys_dict, 'ltk_peripheral'),
103
+ irk=PairingKeys.key_from_dict(keys_dict, 'irk'),
104
+ csrk=PairingKeys.key_from_dict(keys_dict, 'csrk'),
105
+ link_key=PairingKeys.key_from_dict(keys_dict, 'link_key'),
106
+ link_key_type=keys_dict.get('link_key_type'),
107
+ )
108
+
109
+ def to_dict(self) -> dict[str, Any]:
110
+ keys: dict[str, Any] = {}
106
111
 
107
112
  if self.address_type is not None:
108
113
  keys['address_type'] = self.address_type
@@ -125,9 +130,12 @@ class PairingKeys:
125
130
  if self.link_key is not None:
126
131
  keys['link_key'] = self.link_key.to_dict()
127
132
 
133
+ if self.link_key_type is not None:
134
+ keys['link_key_type'] = self.link_key_type
135
+
128
136
  return keys
129
137
 
130
- def print(self, prefix=''):
138
+ def print(self, prefix: str = '') -> None:
131
139
  keys_dict = self.to_dict()
132
140
  for container_property, value in keys_dict.items():
133
141
  if isinstance(value, dict):
@@ -149,27 +157,35 @@ class KeyStore:
149
157
  async def get(self, _name: str) -> Optional[PairingKeys]:
150
158
  return None
151
159
 
152
- async def get_all(self) -> List[Tuple[str, PairingKeys]]:
160
+ async def get_all(self) -> list[tuple[str, PairingKeys]]:
153
161
  return []
154
162
 
155
163
  async def delete_all(self) -> None:
156
164
  all_keys = await self.get_all()
157
165
  await asyncio.gather(*(self.delete(name) for (name, _) in all_keys))
158
166
 
159
- async def get_resolving_keys(self):
167
+ async def get_resolving_keys(self) -> list[tuple[bytes, hci.Address]]:
160
168
  all_keys = await self.get_all()
161
169
  resolving_keys = []
162
170
  for name, keys in all_keys:
163
171
  if keys.irk is not None:
164
- if keys.address_type is None:
165
- address_type = Address.RANDOM_DEVICE_ADDRESS
166
- else:
167
- address_type = keys.address_type
168
- resolving_keys.append((keys.irk.value, Address(name, address_type)))
172
+ resolving_keys.append(
173
+ (
174
+ keys.irk.value,
175
+ hci.Address(
176
+ name,
177
+ (
178
+ keys.address_type
179
+ if keys.address_type is not None
180
+ else hci.Address.RANDOM_DEVICE_ADDRESS
181
+ ),
182
+ ),
183
+ )
184
+ )
169
185
 
170
186
  return resolving_keys
171
187
 
172
- async def print(self, prefix=''):
188
+ async def print(self, prefix: str = '') -> None:
173
189
  entries = await self.get_all()
174
190
  separator = ''
175
191
  for name, keys in entries:
@@ -177,8 +193,8 @@ class KeyStore:
177
193
  keys.print(prefix=prefix + ' ')
178
194
  separator = '\n'
179
195
 
180
- @staticmethod
181
- def create_for_device(device: Device) -> KeyStore:
196
+ @classmethod
197
+ def create_for_device(cls, device: Device) -> KeyStore:
182
198
  if device.config.keystore is None:
183
199
  return MemoryKeyStore()
184
200
 
@@ -256,7 +272,7 @@ class JsonKeyStore(KeyStore):
256
272
 
257
273
  @classmethod
258
274
  def from_device(
259
- cls: Type[Self], device: Device, filename: Optional[str] = None
275
+ cls: type[Self], device: Device, filename: Optional[str] = None
260
276
  ) -> Self:
261
277
  if not filename:
262
278
  # Extract the filename from the config if there is one
@@ -266,9 +282,9 @@ class JsonKeyStore(KeyStore):
266
282
  filename = params[0]
267
283
 
268
284
  # Use a namespace based on the device address
269
- if device.public_address not in (Address.ANY, Address.ANY_RANDOM):
285
+ if device.public_address not in (hci.Address.ANY, hci.Address.ANY_RANDOM):
270
286
  namespace = str(device.public_address)
271
- elif device.random_address != Address.ANY_RANDOM:
287
+ elif device.random_address != hci.Address.ANY_RANDOM:
272
288
  namespace = str(device.random_address)
273
289
  else:
274
290
  namespace = JsonKeyStore.DEFAULT_NAMESPACE
@@ -340,7 +356,7 @@ class JsonKeyStore(KeyStore):
340
356
 
341
357
  # -----------------------------------------------------------------------------
342
358
  class MemoryKeyStore(KeyStore):
343
- all_keys: Dict[str, PairingKeys]
359
+ all_keys: dict[str, PairingKeys]
344
360
 
345
361
  def __init__(self) -> None:
346
362
  self.all_keys = {}
@@ -355,5 +371,5 @@ class MemoryKeyStore(KeyStore):
355
371
  async def get(self, name: str) -> Optional[PairingKeys]:
356
372
  return self.all_keys.get(name)
357
373
 
358
- async def get_all(self) -> List[Tuple[str, PairingKeys]]:
374
+ async def get_all(self) -> list[tuple[str, PairingKeys]]:
359
375
  return list(self.all_keys.items())