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/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
@@ -724,12 +719,13 @@ class Session:
724
719
  self.is_responder = not self.is_initiator
725
720
 
726
721
  # Listen for connection events
727
- connection.on('disconnection', self.on_disconnection)
722
+ connection.on(connection.EVENT_DISCONNECTION, self.on_disconnection)
728
723
  connection.on(
729
- 'connection_encryption_change', self.on_connection_encryption_change
724
+ connection.EVENT_CONNECTION_ENCRYPTION_CHANGE,
725
+ self.on_connection_encryption_change,
730
726
  )
731
727
  connection.on(
732
- 'connection_encryption_key_refresh',
728
+ connection.EVENT_CONNECTION_ENCRYPTION_KEY_REFRESH,
733
729
  self.on_connection_encryption_key_refresh,
734
730
  )
735
731
 
@@ -766,7 +762,9 @@ class Session:
766
762
 
767
763
  # OOB
768
764
  self.oob_data_flag = (
769
- 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
770
768
  )
771
769
 
772
770
  # Set up addresses
@@ -813,7 +811,7 @@ class Session:
813
811
  self.tk = bytes(16)
814
812
 
815
813
  @property
816
- def pkx(self) -> Tuple[bytes, bytes]:
814
+ def pkx(self) -> tuple[bytes, bytes]:
817
815
  return (self.ecc_key.x[::-1], self.peer_public_key_x)
818
816
 
819
817
  @property
@@ -825,7 +823,7 @@ class Session:
825
823
  return self.pkx[0 if self.is_responder else 1]
826
824
 
827
825
  @property
828
- def nx(self) -> Tuple[bytes, bytes]:
826
+ def nx(self) -> tuple[bytes, bytes]:
829
827
  assert self.peer_random_value
830
828
  return (self.r, self.peer_random_value)
831
829
 
@@ -899,7 +897,7 @@ class Session:
899
897
 
900
898
  self.send_pairing_failed(SMP_CONFIRM_VALUE_FAILED_ERROR)
901
899
 
902
- utils.cancel_on_event(self.connection, 'disconnection', prompt())
900
+ self.connection.cancel_on_disconnection(prompt())
903
901
 
904
902
  def prompt_user_for_numeric_comparison(
905
903
  self, code: int, next_steps: Callable[[], None]
@@ -918,7 +916,7 @@ class Session:
918
916
 
919
917
  self.send_pairing_failed(SMP_CONFIRM_VALUE_FAILED_ERROR)
920
918
 
921
- utils.cancel_on_event(self.connection, 'disconnection', prompt())
919
+ self.connection.cancel_on_disconnection(prompt())
922
920
 
923
921
  def prompt_user_for_number(self, next_steps: Callable[[int], None]) -> None:
924
922
  async def prompt() -> None:
@@ -935,12 +933,11 @@ class Session:
935
933
  logger.warning(f'exception while prompting: {error}')
936
934
  self.send_pairing_failed(SMP_PASSKEY_ENTRY_FAILED_ERROR)
937
935
 
938
- utils.cancel_on_event(self.connection, 'disconnection', prompt())
936
+ self.connection.cancel_on_disconnection(prompt())
939
937
 
940
- def display_passkey(self) -> None:
941
- # Generate random Passkey/PIN code
942
- self.passkey = secrets.randbelow(1000000)
943
- 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()
944
941
  logger.debug(f'Pairing PIN CODE: {self.passkey:06}')
945
942
  self.passkey_ready.set()
946
943
 
@@ -949,14 +946,7 @@ class Session:
949
946
  self.tk = self.passkey.to_bytes(16, byteorder='little')
950
947
  logger.debug(f'TK from passkey = {self.tk.hex()}')
951
948
 
952
- try:
953
- utils.cancel_on_event(
954
- self.connection,
955
- 'disconnection',
956
- self.pairing_config.delegate.display_number(self.passkey, digits=6),
957
- )
958
- except Exception as error:
959
- logger.warning(f'exception while displaying number: {error}')
949
+ await self.pairing_config.delegate.display_number(self.passkey, digits=6)
960
950
 
961
951
  def input_passkey(self, next_steps: Optional[Callable[[], None]] = None) -> None:
962
952
  # Prompt the user for the passkey displayed on the peer
@@ -978,9 +968,16 @@ class Session:
978
968
  self, next_steps: Optional[Callable[[], None]] = None
979
969
  ) -> None:
980
970
  if self.passkey_display:
981
- self.display_passkey()
982
- if next_steps is not None:
983
- 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}')
984
981
  else:
985
982
  self.input_passkey(next_steps)
986
983
 
@@ -1050,7 +1047,7 @@ class Session:
1050
1047
  )
1051
1048
 
1052
1049
  # Perform the next steps asynchronously in case we need to wait for input
1053
- utils.cancel_on_event(self.connection, 'disconnection', next_steps())
1050
+ self.connection.cancel_on_disconnection(next_steps())
1054
1051
  else:
1055
1052
  confirm_value = crypto.c1(
1056
1053
  self.tk,
@@ -1173,8 +1170,8 @@ class Session:
1173
1170
  self.connection.transport == PhysicalTransport.BR_EDR
1174
1171
  and self.initiator_key_distribution & SMP_ENC_KEY_DISTRIBUTION_FLAG
1175
1172
  ):
1176
- self.ctkd_task = utils.cancel_on_event(
1177
- 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()
1178
1175
  )
1179
1176
  elif not self.sc:
1180
1177
  # Distribute the LTK, EDIV and RAND
@@ -1212,8 +1209,8 @@ class Session:
1212
1209
  self.connection.transport == PhysicalTransport.BR_EDR
1213
1210
  and self.responder_key_distribution & SMP_ENC_KEY_DISTRIBUTION_FLAG
1214
1211
  ):
1215
- self.ctkd_task = utils.cancel_on_event(
1216
- 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()
1217
1214
  )
1218
1215
  # Distribute the LTK, EDIV and RAND
1219
1216
  elif not self.sc:
@@ -1268,7 +1265,7 @@ class Session:
1268
1265
  f'{[c.__name__ for c in self.peer_expected_distributions]}'
1269
1266
  )
1270
1267
 
1271
- def check_key_distribution(self, command_class: Type[SMP_Command]) -> None:
1268
+ def check_key_distribution(self, command_class: type[SMP_Command]) -> None:
1272
1269
  # First, check that the connection is encrypted
1273
1270
  if not self.connection.is_encrypted:
1274
1271
  logger.warning(
@@ -1305,17 +1302,18 @@ class Session:
1305
1302
 
1306
1303
  # Wait for the pairing process to finish
1307
1304
  assert self.pairing_result
1308
- await utils.cancel_on_event(
1309
- self.connection, 'disconnection', self.pairing_result
1310
- )
1305
+ await self.connection.cancel_on_disconnection(self.pairing_result)
1311
1306
 
1312
1307
  def on_disconnection(self, _: int) -> None:
1313
- self.connection.remove_listener('disconnection', self.on_disconnection)
1314
1308
  self.connection.remove_listener(
1315
- 'connection_encryption_change', self.on_connection_encryption_change
1309
+ self.connection.EVENT_DISCONNECTION, self.on_disconnection
1310
+ )
1311
+ self.connection.remove_listener(
1312
+ self.connection.EVENT_CONNECTION_ENCRYPTION_CHANGE,
1313
+ self.on_connection_encryption_change,
1316
1314
  )
1317
1315
  self.connection.remove_listener(
1318
- 'connection_encryption_key_refresh',
1316
+ self.connection.EVENT_CONNECTION_ENCRYPTION_KEY_REFRESH,
1319
1317
  self.on_connection_encryption_key_refresh,
1320
1318
  )
1321
1319
  self.manager.on_session_end(self)
@@ -1325,7 +1323,7 @@ class Session:
1325
1323
  if self.is_initiator:
1326
1324
  self.distribute_keys()
1327
1325
 
1328
- utils.cancel_on_event(self.connection, 'disconnection', self.on_pairing())
1326
+ self.connection.cancel_on_disconnection(self.on_pairing())
1329
1327
 
1330
1328
  def on_connection_encryption_change(self) -> None:
1331
1329
  if self.connection.is_encrypted and not self.completed:
@@ -1376,8 +1374,10 @@ class Session:
1376
1374
  ediv=self.ltk_ediv,
1377
1375
  rand=self.ltk_rand,
1378
1376
  )
1377
+ if not self.peer_ltk:
1378
+ logger.error("peer_ltk is None")
1379
1379
  peer_ltk_key = PairingKeys.Key(
1380
- value=self.peer_ltk,
1380
+ value=self.peer_ltk or b'',
1381
1381
  authenticated=authenticated,
1382
1382
  ediv=self.peer_ediv,
1383
1383
  rand=self.peer_rand,
@@ -1434,10 +1434,8 @@ class Session:
1434
1434
  def on_smp_pairing_request_command(
1435
1435
  self, command: SMP_Pairing_Request_Command
1436
1436
  ) -> None:
1437
- utils.cancel_on_event(
1438
- self.connection,
1439
- 'disconnection',
1440
- self.on_smp_pairing_request_command_async(command),
1437
+ self.connection.cancel_on_disconnection(
1438
+ self.on_smp_pairing_request_command_async(command)
1441
1439
  )
1442
1440
 
1443
1441
  async def on_smp_pairing_request_command_async(
@@ -1501,7 +1499,7 @@ class Session:
1501
1499
  # Display a passkey if we need to
1502
1500
  if not self.sc:
1503
1501
  if self.pairing_method == PairingMethod.PASSKEY and self.passkey_display:
1504
- self.display_passkey()
1502
+ await self.display_passkey()
1505
1503
 
1506
1504
  # Respond
1507
1505
  self.send_pairing_response_command()
@@ -1683,7 +1681,7 @@ class Session:
1683
1681
  ):
1684
1682
  return
1685
1683
  elif self.pairing_method == PairingMethod.PASSKEY:
1686
- assert self.passkey and self.confirm_value
1684
+ assert self.passkey is not None and self.confirm_value is not None
1687
1685
  # Check that the random value matches what was committed to earlier
1688
1686
  confirm_verifier = crypto.f4(
1689
1687
  self.pkb,
@@ -1712,7 +1710,7 @@ class Session:
1712
1710
  ):
1713
1711
  self.send_pairing_random_command()
1714
1712
  elif self.pairing_method == PairingMethod.PASSKEY:
1715
- assert self.passkey and self.confirm_value
1713
+ assert self.passkey is not None and self.confirm_value is not None
1716
1714
  # Check that the random value matches what was committed to earlier
1717
1715
  confirm_verifier = crypto.f4(
1718
1716
  self.pka,
@@ -1749,7 +1747,7 @@ class Session:
1749
1747
  ra = bytes(16)
1750
1748
  rb = ra
1751
1749
  elif self.pairing_method == PairingMethod.PASSKEY:
1752
- assert self.passkey
1750
+ assert self.passkey is not None
1753
1751
  ra = self.passkey.to_bytes(16, byteorder='little')
1754
1752
  rb = ra
1755
1753
  elif self.pairing_method == PairingMethod.OOB:
@@ -1848,19 +1846,23 @@ class Session:
1848
1846
  elif self.pairing_method == PairingMethod.PASSKEY:
1849
1847
  self.send_pairing_confirm_command()
1850
1848
  else:
1851
- if self.pairing_method == PairingMethod.PASSKEY:
1852
- self.display_or_input_passkey()
1853
1849
 
1854
- # Send our public key back to the initiator
1855
- 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()
1856
1853
 
1857
- if self.pairing_method in (
1858
- PairingMethod.JUST_WORKS,
1859
- PairingMethod.NUMERIC_COMPARISON,
1860
- PairingMethod.OOB,
1861
- ):
1862
- # We can now send the confirmation value
1863
- 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()
1864
1866
 
1865
1867
  def on_smp_pairing_dhkey_check_command(
1866
1868
  self, command: SMP_Pairing_DHKey_Check_Command
@@ -1882,7 +1884,7 @@ class Session:
1882
1884
  self.wait_before_continuing = None
1883
1885
  self.send_pairing_dhkey_check_command()
1884
1886
 
1885
- utils.cancel_on_event(self.connection, 'disconnection', next_steps())
1887
+ self.connection.cancel_on_disconnection(next_steps())
1886
1888
  else:
1887
1889
  self.send_pairing_dhkey_check_command()
1888
1890
  else:
@@ -1932,9 +1934,9 @@ class Manager(utils.EventEmitter):
1932
1934
  '''
1933
1935
 
1934
1936
  device: Device
1935
- sessions: Dict[int, Session]
1937
+ sessions: dict[int, Session]
1936
1938
  pairing_config_factory: Callable[[Connection], PairingConfig]
1937
- session_proxy: Type[Session]
1939
+ session_proxy: type[Session]
1938
1940
  _ecc_key: Optional[crypto.EccKey]
1939
1941
 
1940
1942
  def __init__(
@@ -1962,7 +1964,7 @@ class Manager(utils.EventEmitter):
1962
1964
  def on_smp_security_request_command(
1963
1965
  self, connection: Connection, request: SMP_Security_Request_Command
1964
1966
  ) -> None:
1965
- connection.emit('security_request', request.auth_req)
1967
+ connection.emit(connection.EVENT_SECURITY_REQUEST, request.auth_req)
1966
1968
 
1967
1969
  def on_smp_pdu(self, connection: Connection, pdu: bytes) -> None:
1968
1970
  # Parse the L2CAP payload into an SMP Command object
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
  ):
@@ -20,9 +20,9 @@ import logging
20
20
  import os
21
21
  from typing import Optional
22
22
 
23
+ from bumble import utils
23
24
  from bumble.transport.common import (
24
25
  Transport,
25
- AsyncPipeSink,
26
26
  SnoopingTransport,
27
27
  TransportSpecError,
28
28
  )
@@ -195,6 +195,7 @@ async def _open_transport(scheme: str, spec: Optional[str]) -> Transport:
195
195
 
196
196
 
197
197
  # -----------------------------------------------------------------------------
198
+ @utils.deprecated("RemoteLink has been removed. Use open_transport instead.")
198
199
  async def open_transport_or_link(name: str) -> Transport:
199
200
  """
200
201
  Open a transport or a link relay.
@@ -205,21 +206,6 @@ async def open_transport_or_link(name: str) -> Transport:
205
206
  When the name starts with "link-relay:", open a link relay (see RemoteLink
206
207
  for details on what the arguments are).
207
208
  For other namespaces, see `open_transport`.
208
-
209
209
  """
210
- if name.startswith('link-relay:'):
211
- logger.warning('Link Relay has been deprecated.')
212
- from bumble.controller import Controller
213
- from bumble.link import RemoteLink # lazy import
214
-
215
- link = RemoteLink(name[11:])
216
- await link.wait_until_connected()
217
- controller = Controller('remote', link=link) # type:ignore[arg-type]
218
-
219
- class LinkTransport(Transport):
220
- async def close(self):
221
- link.close()
222
-
223
- return _wrap_transport(LinkTransport(controller, AsyncPipeSink(controller)))
224
210
 
225
211
  return await open_transport(name)
@@ -22,7 +22,7 @@ import os
22
22
  import pathlib
23
23
  import platform
24
24
  import sys
25
- from typing import Dict, Optional
25
+ from typing import Optional
26
26
 
27
27
  import grpc.aio
28
28
 
@@ -143,7 +143,7 @@ def publish_grpc_port(grpc_port: int, instance_number: int) -> bool:
143
143
 
144
144
  # -----------------------------------------------------------------------------
145
145
  async def open_android_netsim_controller_transport(
146
- server_host: Optional[str], server_port: int, options: Dict[str, str]
146
+ server_host: Optional[str], server_port: int, options: dict[str, str]
147
147
  ) -> Transport:
148
148
  if not server_port:
149
149
  raise TransportSpecError('invalid port')
@@ -288,7 +288,7 @@ async def open_android_netsim_controller_transport(
288
288
  async def open_android_netsim_host_transport_with_address(
289
289
  server_host: Optional[str],
290
290
  server_port: int,
291
- options: Optional[Dict[str, str]] = None,
291
+ options: Optional[dict[str, str]] = None,
292
292
  ):
293
293
  if server_host == '_' or not server_host:
294
294
  server_host = 'localhost'
@@ -313,7 +313,7 @@ async def open_android_netsim_host_transport_with_address(
313
313
 
314
314
  # -----------------------------------------------------------------------------
315
315
  async def open_android_netsim_host_transport_with_channel(
316
- channel, options: Optional[Dict[str, str]] = None
316
+ channel, options: Optional[dict[str, str]] = None
317
317
  ):
318
318
  # Wrapper for I/O operations
319
319
  class HciDevice:
@@ -451,7 +451,7 @@ async def open_android_netsim_transport(spec: Optional[str]) -> Transport:
451
451
  port = 0
452
452
  params_offset = 0
453
453
 
454
- options: Dict[str, str] = {}
454
+ options: dict[str, str] = {}
455
455
  for param in params[params_offset:]:
456
456
  if '=' not in param:
457
457
  raise TransportSpecError('invalid parameter, expected <name>=<value>')
@@ -21,7 +21,7 @@ import struct
21
21
  import asyncio
22
22
  import logging
23
23
  import io
24
- from typing import Any, ContextManager, Tuple, Optional, Protocol, Dict
24
+ from typing import Any, ContextManager, Optional, Protocol
25
25
 
26
26
  from bumble import core
27
27
  from bumble import hci
@@ -38,7 +38,7 @@ logger = logging.getLogger(__name__)
38
38
  # Information needed to parse HCI packets with a generic parser:
39
39
  # For each packet type, the info represents:
40
40
  # (length-size, length-offset, unpack-type)
41
- HCI_PACKET_INFO: Dict[int, Tuple[int, int, str]] = {
41
+ HCI_PACKET_INFO: dict[int, tuple[int, int, str]] = {
42
42
  hci.HCI_COMMAND_PACKET: (1, 2, 'B'),
43
43
  hci.HCI_ACL_DATA_PACKET: (2, 2, 'H'),
44
44
  hci.HCI_SYNCHRONOUS_DATA_PACKET: (1, 2, 'B'),
@@ -108,8 +108,8 @@ class PacketParser:
108
108
  NEED_BODY = 2
109
109
 
110
110
  sink: Optional[TransportSink]
111
- extended_packet_info: Dict[int, Tuple[int, int, str]]
112
- packet_info: Optional[Tuple[int, int, str]] = None
111
+ extended_packet_info: dict[int, tuple[int, int, str]]
112
+ packet_info: Optional[tuple[int, int, str]] = None
113
113
 
114
114
  def __init__(self, sink: Optional[TransportSink] = None) -> None:
115
115
  self.sink = sink
bumble/transport/pyusb.py CHANGED
@@ -23,7 +23,7 @@ import time
23
23
  import usb.core
24
24
  import usb.util
25
25
 
26
- from typing import Optional, Set
26
+ from typing import Optional
27
27
  from usb.core import Device as UsbDevice
28
28
  from usb.core import USBError
29
29
  from usb.util import CTRL_TYPE_CLASS, CTRL_RECIPIENT_OTHER
@@ -49,7 +49,7 @@ logger = logging.getLogger(__name__)
49
49
  # -----------------------------------------------------------------------------
50
50
  # Global
51
51
  # -----------------------------------------------------------------------------
52
- devices_in_use: Set[int] = set()
52
+ devices_in_use: set[int] = set()
53
53
 
54
54
 
55
55
  # -----------------------------------------------------------------------------
bumble/utils.py CHANGED
@@ -27,11 +27,8 @@ from typing import (
27
27
  Any,
28
28
  Awaitable,
29
29
  Callable,
30
- List,
31
30
  Optional,
32
31
  Protocol,
33
- Set,
34
- Tuple,
35
32
  TypeVar,
36
33
  Union,
37
34
  overload,
@@ -156,7 +153,7 @@ class EventWatcher:
156
153
  ```
157
154
  '''
158
155
 
159
- handlers: List[Tuple[pyee.EventEmitter, str, Callable[..., Any]]]
156
+ handlers: list[tuple[pyee.EventEmitter, str, Callable[..., Any]]]
160
157
 
161
158
  def __init__(self) -> None:
162
159
  self.handlers = []
@@ -329,7 +326,7 @@ class AsyncRunner:
329
326
  default_queue = WorkQueue()
330
327
 
331
328
  # Shared set of running tasks
332
- running_tasks: Set[Awaitable] = set()
329
+ running_tasks: set[Awaitable] = set()
333
330
 
334
331
  @staticmethod
335
332
  def run_in_task(queue=None):