bumble 0.0.219__py3-none-any.whl → 0.0.221__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 (104) hide show
  1. bumble/_version.py +2 -2
  2. bumble/a2dp.py +5 -5
  3. bumble/apps/auracast.py +746 -479
  4. bumble/apps/bench.py +4 -5
  5. bumble/apps/console.py +5 -10
  6. bumble/apps/controller_info.py +12 -7
  7. bumble/apps/controller_loopback.py +1 -2
  8. bumble/apps/device_info.py +2 -3
  9. bumble/apps/gatt_dump.py +0 -1
  10. bumble/apps/lea_unicast/app.py +1 -1
  11. bumble/apps/pair.py +49 -46
  12. bumble/apps/pandora_server.py +2 -2
  13. bumble/apps/player/player.py +10 -12
  14. bumble/apps/rfcomm_bridge.py +10 -11
  15. bumble/apps/scan.py +1 -3
  16. bumble/apps/speaker/speaker.py +3 -4
  17. bumble/at.py +4 -5
  18. bumble/att.py +91 -25
  19. bumble/audio/io.py +8 -6
  20. bumble/avc.py +1 -2
  21. bumble/avctp.py +2 -3
  22. bumble/avdtp.py +53 -57
  23. bumble/avrcp.py +25 -27
  24. bumble/codecs.py +15 -15
  25. bumble/colors.py +7 -8
  26. bumble/controller.py +1201 -643
  27. bumble/core.py +41 -49
  28. bumble/crypto/__init__.py +2 -1
  29. bumble/crypto/builtin.py +2 -8
  30. bumble/data_types.py +2 -1
  31. bumble/decoder.py +2 -3
  32. bumble/device.py +278 -325
  33. bumble/drivers/__init__.py +3 -2
  34. bumble/drivers/intel.py +6 -8
  35. bumble/drivers/rtk.py +1 -1
  36. bumble/gatt.py +9 -9
  37. bumble/gatt_adapters.py +6 -6
  38. bumble/gatt_client.py +110 -60
  39. bumble/gatt_server.py +209 -139
  40. bumble/hci.py +87 -74
  41. bumble/helpers.py +5 -5
  42. bumble/hfp.py +27 -26
  43. bumble/hid.py +9 -9
  44. bumble/host.py +44 -50
  45. bumble/keys.py +17 -17
  46. bumble/l2cap.py +1015 -218
  47. bumble/link.py +54 -284
  48. bumble/ll.py +200 -0
  49. bumble/lmp.py +324 -0
  50. bumble/pairing.py +14 -15
  51. bumble/pandora/__init__.py +2 -2
  52. bumble/pandora/device.py +6 -4
  53. bumble/pandora/host.py +19 -10
  54. bumble/pandora/l2cap.py +8 -9
  55. bumble/pandora/security.py +18 -16
  56. bumble/pandora/utils.py +4 -4
  57. bumble/profiles/aics.py +6 -8
  58. bumble/profiles/ams.py +3 -5
  59. bumble/profiles/ancs.py +11 -11
  60. bumble/profiles/ascs.py +5 -5
  61. bumble/profiles/asha.py +10 -9
  62. bumble/profiles/bass.py +9 -3
  63. bumble/profiles/battery_service.py +1 -2
  64. bumble/profiles/csip.py +9 -10
  65. bumble/profiles/device_information_service.py +16 -17
  66. bumble/profiles/gap.py +3 -4
  67. bumble/profiles/gatt_service.py +0 -1
  68. bumble/profiles/gmap.py +12 -13
  69. bumble/profiles/hap.py +3 -3
  70. bumble/profiles/heart_rate_service.py +7 -8
  71. bumble/profiles/le_audio.py +1 -1
  72. bumble/profiles/mcp.py +28 -28
  73. bumble/profiles/pacs.py +13 -17
  74. bumble/profiles/pbp.py +16 -0
  75. bumble/profiles/vcs.py +2 -2
  76. bumble/profiles/vocs.py +6 -9
  77. bumble/rfcomm.py +19 -18
  78. bumble/sdp.py +12 -11
  79. bumble/smp.py +20 -30
  80. bumble/snoop.py +12 -5
  81. bumble/tools/generate_company_id_list.py +1 -1
  82. bumble/tools/intel_util.py +2 -2
  83. bumble/tools/rtk_fw_download.py +1 -1
  84. bumble/tools/rtk_util.py +1 -1
  85. bumble/transport/__init__.py +1 -2
  86. bumble/transport/android_emulator.py +2 -3
  87. bumble/transport/android_netsim.py +49 -40
  88. bumble/transport/common.py +9 -9
  89. bumble/transport/file.py +1 -2
  90. bumble/transport/hci_socket.py +2 -3
  91. bumble/transport/pty.py +3 -5
  92. bumble/transport/pyusb.py +8 -5
  93. bumble/transport/serial.py +1 -2
  94. bumble/transport/vhci.py +1 -2
  95. bumble/transport/ws_server.py +2 -3
  96. bumble/utils.py +23 -14
  97. bumble/vendor/android/hci.py +4 -2
  98. {bumble-0.0.219.dist-info → bumble-0.0.221.dist-info}/METADATA +4 -3
  99. bumble-0.0.221.dist-info/RECORD +185 -0
  100. bumble-0.0.219.dist-info/RECORD +0 -183
  101. {bumble-0.0.219.dist-info → bumble-0.0.221.dist-info}/WHEEL +0 -0
  102. {bumble-0.0.219.dist-info → bumble-0.0.221.dist-info}/entry_points.txt +0 -0
  103. {bumble-0.0.219.dist-info → bumble-0.0.221.dist-info}/licenses/LICENSE +0 -0
  104. {bumble-0.0.219.dist-info → bumble-0.0.221.dist-info}/top_level.txt +0 -0
bumble/profiles/pacs.py CHANGED
@@ -21,7 +21,7 @@ from __future__ import annotations
21
21
  import dataclasses
22
22
  import logging
23
23
  import struct
24
- from typing import Optional, Sequence, Union
24
+ from collections.abc import Sequence
25
25
 
26
26
  from bumble import gatt, gatt_adapters, gatt_client, hci
27
27
  from bumble.profiles import le_audio
@@ -39,7 +39,7 @@ class PacRecord:
39
39
  '''Published Audio Capabilities Service, Table 3.2/3.4.'''
40
40
 
41
41
  coding_format: hci.CodingFormat
42
- codec_specific_capabilities: Union[CodecSpecificCapabilities, bytes]
42
+ codec_specific_capabilities: CodecSpecificCapabilities | bytes
43
43
  metadata: le_audio.Metadata = dataclasses.field(default_factory=le_audio.Metadata)
44
44
 
45
45
  @classmethod
@@ -56,7 +56,7 @@ class PacRecord:
56
56
  offset += 1
57
57
  metadata = le_audio.Metadata.from_bytes(data[offset : offset + metadata_size])
58
58
 
59
- codec_specific_capabilities: Union[CodecSpecificCapabilities, bytes]
59
+ codec_specific_capabilities: CodecSpecificCapabilities | bytes
60
60
  if coding_format.codec_id == hci.CodecID.VENDOR_SPECIFIC:
61
61
  codec_specific_capabilities = codec_specific_capabilities_bytes
62
62
  else:
@@ -101,10 +101,10 @@ class PacRecord:
101
101
  class PublishedAudioCapabilitiesService(gatt.TemplateService):
102
102
  UUID = gatt.GATT_PUBLISHED_AUDIO_CAPABILITIES_SERVICE
103
103
 
104
- sink_pac: Optional[gatt.Characteristic[bytes]]
105
- sink_audio_locations: Optional[gatt.Characteristic[bytes]]
106
- source_pac: Optional[gatt.Characteristic[bytes]]
107
- source_audio_locations: Optional[gatt.Characteristic[bytes]]
104
+ sink_pac: gatt.Characteristic[bytes] | None
105
+ sink_audio_locations: gatt.Characteristic[bytes] | None
106
+ source_pac: gatt.Characteristic[bytes] | None
107
+ source_audio_locations: gatt.Characteristic[bytes] | None
108
108
  available_audio_contexts: gatt.Characteristic[bytes]
109
109
  supported_audio_contexts: gatt.Characteristic[bytes]
110
110
 
@@ -115,9 +115,9 @@ class PublishedAudioCapabilitiesService(gatt.TemplateService):
115
115
  available_source_context: ContextType,
116
116
  available_sink_context: ContextType,
117
117
  sink_pac: Sequence[PacRecord] = (),
118
- sink_audio_locations: Optional[AudioLocation] = None,
118
+ sink_audio_locations: AudioLocation | None = None,
119
119
  source_pac: Sequence[PacRecord] = (),
120
- source_audio_locations: Optional[AudioLocation] = None,
120
+ source_audio_locations: AudioLocation | None = None,
121
121
  ) -> None:
122
122
  characteristics = []
123
123
 
@@ -183,14 +183,10 @@ class PublishedAudioCapabilitiesService(gatt.TemplateService):
183
183
  class PublishedAudioCapabilitiesServiceProxy(gatt_client.ProfileServiceProxy):
184
184
  SERVICE_CLASS = PublishedAudioCapabilitiesService
185
185
 
186
- sink_pac: Optional[gatt_client.CharacteristicProxy[list[PacRecord]]] = None
187
- sink_audio_locations: Optional[gatt_client.CharacteristicProxy[AudioLocation]] = (
188
- None
189
- )
190
- source_pac: Optional[gatt_client.CharacteristicProxy[list[PacRecord]]] = None
191
- source_audio_locations: Optional[gatt_client.CharacteristicProxy[AudioLocation]] = (
192
- None
193
- )
186
+ sink_pac: gatt_client.CharacteristicProxy[list[PacRecord]] | None = None
187
+ sink_audio_locations: gatt_client.CharacteristicProxy[AudioLocation] | None = None
188
+ source_pac: gatt_client.CharacteristicProxy[list[PacRecord]] | None = None
189
+ source_audio_locations: gatt_client.CharacteristicProxy[AudioLocation] | None = None
194
190
  available_audio_contexts: gatt_client.CharacteristicProxy[tuple[ContextType, ...]]
195
191
  supported_audio_contexts: gatt_client.CharacteristicProxy[tuple[ContextType, ...]]
196
192
 
bumble/profiles/pbp.py CHANGED
@@ -22,6 +22,7 @@ import enum
22
22
 
23
23
  from typing_extensions import Self
24
24
 
25
+ from bumble import core, data_types, gatt
25
26
  from bumble.profiles import le_audio
26
27
 
27
28
 
@@ -46,3 +47,18 @@ class PublicBroadcastAnnouncement:
46
47
  return cls(
47
48
  features=features, metadata=le_audio.Metadata.from_bytes(metadata_ltv)
48
49
  )
50
+
51
+ def get_advertising_data(self) -> bytes:
52
+ return bytes(
53
+ core.AdvertisingData(
54
+ [
55
+ data_types.ServiceData16BitUUID(
56
+ gatt.GATT_PUBLIC_BROADCAST_ANNOUNCEMENT_SERVICE, bytes(self)
57
+ )
58
+ ]
59
+ )
60
+ )
61
+
62
+ def __bytes__(self) -> bytes:
63
+ metadata_bytes = bytes(self.metadata)
64
+ return bytes([self.features, len(metadata_bytes)]) + metadata_bytes
bumble/profiles/vcs.py CHANGED
@@ -20,9 +20,9 @@ from __future__ import annotations
20
20
 
21
21
  import dataclasses
22
22
  import enum
23
- from typing import Sequence
23
+ from collections.abc import Sequence
24
24
 
25
- from bumble import att, device, gatt, gatt_adapters, gatt_client, utils
25
+ from bumble import att, device, gatt, gatt_adapters, gatt_client
26
26
 
27
27
  # -----------------------------------------------------------------------------
28
28
  # Constants
bumble/profiles/vocs.py CHANGED
@@ -18,7 +18,6 @@
18
18
  # -----------------------------------------------------------------------------
19
19
  import struct
20
20
  from dataclasses import dataclass
21
- from typing import Optional
22
21
 
23
22
  from bumble import utils
24
23
  from bumble.att import ATT_Error
@@ -69,7 +68,7 @@ class ErrorCode(utils.OpenIntEnum):
69
68
  class VolumeOffsetState:
70
69
  volume_offset: int = 0
71
70
  change_counter: int = 0
72
- attribute: Optional[Characteristic] = None
71
+ attribute: Characteristic | None = None
73
72
 
74
73
  def __bytes__(self) -> bytes:
75
74
  return struct.pack('<hB', self.volume_offset, self.change_counter)
@@ -93,7 +92,7 @@ class VolumeOffsetState:
93
92
  @dataclass
94
93
  class VocsAudioLocation:
95
94
  audio_location: AudioLocation = AudioLocation.NOT_ALLOWED
96
- attribute: Optional[Characteristic] = None
95
+ attribute: Characteristic | None = None
97
96
 
98
97
  def __bytes__(self) -> bytes:
99
98
  return struct.pack('<I', self.audio_location)
@@ -118,7 +117,6 @@ class VolumeOffsetControlPoint:
118
117
  volume_offset_state: VolumeOffsetState
119
118
 
120
119
  async def on_write(self, connection: Connection, value: bytes) -> None:
121
-
122
120
  opcode = value[0]
123
121
  if opcode != SetVolumeOffsetOpCode.SET_VOLUME_OFFSET:
124
122
  raise ATT_Error(ErrorCode.OPCODE_NOT_SUPPORTED)
@@ -148,7 +146,7 @@ class VolumeOffsetControlPoint:
148
146
  @dataclass
149
147
  class AudioOutputDescription:
150
148
  audio_output_description: str = ''
151
- attribute: Optional[Characteristic] = None
149
+ attribute: Characteristic | None = None
152
150
 
153
151
  @classmethod
154
152
  def from_bytes(cls, data: bytes):
@@ -173,11 +171,10 @@ class VolumeOffsetControlService(TemplateService):
173
171
 
174
172
  def __init__(
175
173
  self,
176
- volume_offset_state: Optional[VolumeOffsetState] = None,
177
- audio_location: Optional[VocsAudioLocation] = None,
178
- audio_output_description: Optional[AudioOutputDescription] = None,
174
+ volume_offset_state: VolumeOffsetState | None = None,
175
+ audio_location: VocsAudioLocation | None = None,
176
+ audio_output_description: AudioOutputDescription | None = None,
179
177
  ) -> None:
180
-
181
178
  self.volume_offset_state = (
182
179
  VolumeOffsetState() if volume_offset_state is None else volume_offset_state
183
180
  )
bumble/rfcomm.py CHANGED
@@ -22,7 +22,8 @@ import collections
22
22
  import dataclasses
23
23
  import enum
24
24
  import logging
25
- from typing import TYPE_CHECKING, Callable, Optional, Union
25
+ from collections.abc import Callable
26
+ from typing import TYPE_CHECKING
26
27
 
27
28
  from typing_extensions import Self
28
29
 
@@ -119,7 +120,7 @@ RFCOMM_DYNAMIC_CHANNEL_NUMBER_END = 30
119
120
 
120
121
  # -----------------------------------------------------------------------------
121
122
  def make_service_sdp_records(
122
- service_record_handle: int, channel: int, uuid: Optional[UUID] = None
123
+ service_record_handle: int, channel: int, uuid: UUID | None = None
123
124
  ) -> list[sdp.ServiceAttribute]:
124
125
  """
125
126
  Create SDP records for an RFComm service given a channel number and an
@@ -186,7 +187,7 @@ async def find_rfcomm_channels(connection: Connection) -> dict[int, list[UUID]]:
186
187
  )
187
188
  for attribute_lists in search_result:
188
189
  service_classes: list[UUID] = []
189
- channel: Optional[int] = None
190
+ channel: int | None = None
190
191
  for attribute in attribute_lists:
191
192
  # The layout is [[L2CAP_PROTOCOL], [RFCOMM_PROTOCOL, RFCOMM_CHANNEL]].
192
193
  if attribute.id == sdp.SDP_PROTOCOL_DESCRIPTOR_LIST_ATTRIBUTE_ID:
@@ -207,7 +208,7 @@ async def find_rfcomm_channels(connection: Connection) -> dict[int, list[UUID]]:
207
208
  # -----------------------------------------------------------------------------
208
209
  async def find_rfcomm_channel_with_uuid(
209
210
  connection: Connection, uuid: str | UUID
210
- ) -> Optional[int]:
211
+ ) -> int | None:
211
212
  """Searches an RFCOMM channel associated with given UUID from service records.
212
213
 
213
214
  Args:
@@ -473,15 +474,15 @@ class DLC(utils.EventEmitter):
473
474
  self.state = DLC.State.INIT
474
475
  self.role = multiplexer.role
475
476
  self.c_r = 1 if self.role == Multiplexer.Role.INITIATOR else 0
476
- self.connection_result: Optional[asyncio.Future] = None
477
- self.disconnection_result: Optional[asyncio.Future] = None
477
+ self.connection_result: asyncio.Future | None = None
478
+ self.disconnection_result: asyncio.Future | None = None
478
479
  self.drained = asyncio.Event()
479
480
  self.drained.set()
480
481
  # Queued packets when sink is not set.
481
482
  self._enqueued_rx_packets: collections.deque[bytes] = collections.deque(
482
483
  maxlen=DEFAULT_RX_QUEUE_SIZE
483
484
  )
484
- self._sink: Optional[Callable[[bytes], None]] = None
485
+ self._sink: Callable[[bytes], None] | None = None
485
486
 
486
487
  # Compute the MTU
487
488
  max_overhead = 4 + 1 # header with 2-byte length + fcs
@@ -490,11 +491,11 @@ class DLC(utils.EventEmitter):
490
491
  )
491
492
 
492
493
  @property
493
- def sink(self) -> Optional[Callable[[bytes], None]]:
494
+ def sink(self) -> Callable[[bytes], None] | None:
494
495
  return self._sink
495
496
 
496
497
  @sink.setter
497
- def sink(self, sink: Optional[Callable[[bytes], None]]) -> None:
498
+ def sink(self, sink: Callable[[bytes], None] | None) -> None:
498
499
  self._sink = sink
499
500
  # Dump queued packets to sink
500
501
  if sink:
@@ -712,7 +713,7 @@ class DLC(utils.EventEmitter):
712
713
  self.drained.set()
713
714
 
714
715
  # Stream protocol
715
- def write(self, data: Union[bytes, str]) -> None:
716
+ def write(self, data: bytes | str) -> None:
716
717
  # We can only send bytes
717
718
  if not isinstance(data, bytes):
718
719
  if isinstance(data, str):
@@ -769,10 +770,10 @@ class Multiplexer(utils.EventEmitter):
769
770
 
770
771
  EVENT_DLC = "dlc"
771
772
 
772
- connection_result: Optional[asyncio.Future]
773
- disconnection_result: Optional[asyncio.Future]
774
- open_result: Optional[asyncio.Future]
775
- acceptor: Optional[Callable[[int], Optional[tuple[int, int]]]]
773
+ connection_result: asyncio.Future | None
774
+ disconnection_result: asyncio.Future | None
775
+ open_result: asyncio.Future | None
776
+ acceptor: Callable[[int], tuple[int, int] | None] | None
776
777
  dlcs: dict[int, DLC]
777
778
 
778
779
  def __init__(self, l2cap_channel: l2cap.ClassicChannel, role: Role) -> None:
@@ -784,7 +785,7 @@ class Multiplexer(utils.EventEmitter):
784
785
  self.connection_result = None
785
786
  self.disconnection_result = None
786
787
  self.open_result = None
787
- self.open_pn: Optional[RFCOMM_MCC_PN] = None
788
+ self.open_pn: RFCOMM_MCC_PN | None = None
788
789
  self.open_rx_max_credits = 0
789
790
  self.acceptor = None
790
791
 
@@ -1031,8 +1032,8 @@ class Multiplexer(utils.EventEmitter):
1031
1032
 
1032
1033
  # -----------------------------------------------------------------------------
1033
1034
  class Client:
1034
- multiplexer: Optional[Multiplexer]
1035
- l2cap_channel: Optional[l2cap.ClassicChannel]
1035
+ multiplexer: Multiplexer | None
1036
+ l2cap_channel: l2cap.ClassicChannel | None
1036
1037
 
1037
1038
  def __init__(
1038
1039
  self, connection: Connection, l2cap_mtu: int = RFCOMM_DEFAULT_L2CAP_MTU
@@ -1145,7 +1146,7 @@ class Server(utils.EventEmitter):
1145
1146
  # Notify
1146
1147
  self.emit(self.EVENT_START, multiplexer)
1147
1148
 
1148
- def accept_dlc(self, channel_number: int) -> Optional[tuple[int, int]]:
1149
+ def accept_dlc(self, channel_number: int) -> tuple[int, int] | None:
1149
1150
  return self.dlc_configs.get(channel_number)
1150
1151
 
1151
1152
  def on_dlc(self, dlc: DLC) -> None:
bumble/sdp.py CHANGED
@@ -20,7 +20,8 @@ from __future__ import annotations
20
20
  import asyncio
21
21
  import logging
22
22
  import struct
23
- from typing import TYPE_CHECKING, Iterable, NewType, Optional, Sequence, Union
23
+ from collections.abc import Iterable, Sequence
24
+ from typing import TYPE_CHECKING, NewType
24
25
 
25
26
  from typing_extensions import Self
26
27
 
@@ -497,7 +498,7 @@ class ServiceAttribute:
497
498
  @staticmethod
498
499
  def find_attribute_in_list(
499
500
  attribute_list: Iterable[ServiceAttribute], attribute_id: int
500
- ) -> Optional[DataElement]:
501
+ ) -> DataElement | None:
501
502
  return next(
502
503
  (
503
504
  attribute.value
@@ -528,7 +529,7 @@ class ServiceAttribute:
528
529
  def to_string(self, with_colors=False):
529
530
  if with_colors:
530
531
  return (
531
- f'Attribute(id={color(self.id_name(self.id),"magenta")},'
532
+ f'Attribute(id={color(self.id_name(self.id), "magenta")},'
532
533
  f'value={self.value})'
533
534
  )
534
535
 
@@ -778,11 +779,11 @@ class SDP_ServiceSearchAttributeResponse(SDP_PDU):
778
779
  class Client:
779
780
  def __init__(self, connection: Connection, mtu: int = 0) -> None:
780
781
  self.connection = connection
781
- self.channel: Optional[l2cap.ClassicChannel] = None
782
+ self.channel: l2cap.ClassicChannel | None = None
782
783
  self.mtu = mtu
783
784
  self.request_semaphore = asyncio.Semaphore(1)
784
- self.pending_request: Optional[SDP_PDU] = None
785
- self.pending_response: Optional[asyncio.futures.Future[SDP_PDU]] = None
785
+ self.pending_request: SDP_PDU | None = None
786
+ self.pending_response: asyncio.futures.Future[SDP_PDU] | None = None
786
787
  self.next_transaction_id = 0
787
788
 
788
789
  async def connect(self) -> None:
@@ -898,7 +899,7 @@ class Client:
898
899
  async def search_attributes(
899
900
  self,
900
901
  uuids: Iterable[core.UUID],
901
- attribute_ids: Iterable[Union[int, tuple[int, int]]],
902
+ attribute_ids: Iterable[int | tuple[int, int]],
902
903
  ) -> list[list[ServiceAttribute]]:
903
904
  """
904
905
  Search for attributes by UUID and attribute IDs.
@@ -970,7 +971,7 @@ class Client:
970
971
  async def get_attributes(
971
972
  self,
972
973
  service_record_handle: int,
973
- attribute_ids: Iterable[Union[int, tuple[int, int]]],
974
+ attribute_ids: Iterable[int | tuple[int, int]],
974
975
  ) -> list[ServiceAttribute]:
975
976
  """
976
977
  Get attributes for a service.
@@ -1042,10 +1043,10 @@ class Client:
1042
1043
  # -----------------------------------------------------------------------------
1043
1044
  class Server:
1044
1045
  CONTINUATION_STATE = bytes([0x01, 0x00])
1045
- channel: Optional[l2cap.ClassicChannel]
1046
+ channel: l2cap.ClassicChannel | None
1046
1047
  Service = NewType('Service', list[ServiceAttribute])
1047
1048
  service_records: dict[int, Service]
1048
- current_response: Union[None, bytes, tuple[int, list[int]]]
1049
+ current_response: None | bytes | tuple[int, list[int]]
1049
1050
 
1050
1051
  def __init__(self, device: Device) -> None:
1051
1052
  self.device = device
@@ -1123,7 +1124,7 @@ class Server:
1123
1124
  self,
1124
1125
  continuation_state: bytes,
1125
1126
  transaction_id: int,
1126
- ) -> Optional[bool]:
1127
+ ) -> bool | None:
1127
1128
  # Check if this is a valid continuation
1128
1129
  if len(continuation_state) > 1:
1129
1130
  if (
bumble/smp.py CHANGED
@@ -27,17 +27,9 @@ from __future__ import annotations
27
27
  import asyncio
28
28
  import enum
29
29
  import logging
30
+ from collections.abc import Awaitable, Callable
30
31
  from dataclasses import dataclass, field
31
- from typing import (
32
- TYPE_CHECKING,
33
- Any,
34
- Awaitable,
35
- Callable,
36
- ClassVar,
37
- Optional,
38
- TypeVar,
39
- cast,
40
- )
32
+ from typing import TYPE_CHECKING, ClassVar, TypeVar, cast
41
33
 
42
34
  from bumble import crypto, utils
43
35
  from bumble.colors import color
@@ -213,10 +205,10 @@ class SMP_Command:
213
205
  fields: ClassVar[Fields]
214
206
  code: int = field(default=0, init=False)
215
207
  name: str = field(default='', init=False)
216
- _payload: Optional[bytes] = field(default=None, init=False)
208
+ _payload: bytes | None = field(default=None, init=False)
217
209
 
218
210
  @classmethod
219
- def from_bytes(cls, pdu: bytes) -> "SMP_Command":
211
+ def from_bytes(cls, pdu: bytes) -> SMP_Command:
220
212
  code = pdu[0]
221
213
 
222
214
  subclass = SMP_Command.smp_classes.get(code)
@@ -554,7 +546,7 @@ class OobContext:
554
546
  r: bytes
555
547
 
556
548
  def __init__(
557
- self, ecc_key: Optional[crypto.EccKey] = None, r: Optional[bytes] = None
549
+ self, ecc_key: crypto.EccKey | None = None, r: bytes | None = None
558
550
  ) -> None:
559
551
  self.ecc_key = crypto.EccKey.generate() if ecc_key is None else ecc_key
560
552
  self.r = crypto.r() if r is None else r
@@ -570,7 +562,7 @@ class OobLegacyContext:
570
562
 
571
563
  tk: bytes
572
564
 
573
- def __init__(self, tk: Optional[bytes] = None) -> None:
565
+ def __init__(self, tk: bytes | None = None) -> None:
574
566
  self.tk = crypto.r() if tk is None else tk
575
567
 
576
568
 
@@ -677,31 +669,31 @@ class Session:
677
669
  self.stk = None
678
670
  self.ltk_ediv = 0
679
671
  self.ltk_rand = bytes(8)
680
- self.link_key: Optional[bytes] = None
672
+ self.link_key: bytes | None = None
681
673
  self.maximum_encryption_key_size: int = 0
682
674
  self.initiator_key_distribution: int = 0
683
675
  self.responder_key_distribution: int = 0
684
- self.peer_random_value: Optional[bytes] = None
676
+ self.peer_random_value: bytes | None = None
685
677
  self.peer_public_key_x: bytes = bytes(32)
686
678
  self.peer_public_key_y = bytes(32)
687
679
  self.peer_ltk = None
688
680
  self.peer_ediv = None
689
- self.peer_rand: Optional[bytes] = None
681
+ self.peer_rand: bytes | None = None
690
682
  self.peer_identity_resolving_key = None
691
- self.peer_bd_addr: Optional[Address] = None
683
+ self.peer_bd_addr: Address | None = None
692
684
  self.peer_signature_key = None
693
685
  self.peer_expected_distributions: list[type[SMP_Command]] = []
694
686
  self.dh_key = b''
695
687
  self.confirm_value = None
696
- self.passkey: Optional[int] = None
688
+ self.passkey: int | None = None
697
689
  self.passkey_ready = asyncio.Event()
698
690
  self.passkey_step = 0
699
691
  self.passkey_display = False
700
692
  self.pairing_method: PairingMethod = PairingMethod.JUST_WORKS
701
693
  self.pairing_config = pairing_config
702
- self.wait_before_continuing: Optional[asyncio.Future[None]] = None
694
+ self.wait_before_continuing: asyncio.Future[None] | None = None
703
695
  self.completed = False
704
- self.ctkd_task: Optional[Awaitable[None]] = None
696
+ self.ctkd_task: Awaitable[None] | None = None
705
697
 
706
698
  # Decide if we're the initiator or the responder
707
699
  self.is_initiator = is_initiator
@@ -720,7 +712,7 @@ class Session:
720
712
 
721
713
  # Create a future that can be used to wait for the session to complete
722
714
  if self.is_initiator:
723
- self.pairing_result: Optional[asyncio.Future[None]] = (
715
+ self.pairing_result: asyncio.Future[None] | None = (
724
716
  asyncio.get_running_loop().create_future()
725
717
  )
726
718
  else:
@@ -828,7 +820,7 @@ class Session:
828
820
  def auth_req(self) -> int:
829
821
  return smp_auth_req(self.bonding, self.mitm, self.sc, self.keypress, self.ct2)
830
822
 
831
- def get_long_term_key(self, rand: bytes, ediv: int) -> Optional[bytes]:
823
+ def get_long_term_key(self, rand: bytes, ediv: int) -> bytes | None:
832
824
  if not self.sc and not self.completed:
833
825
  if rand == self.ltk_rand and ediv == self.ltk_ediv:
834
826
  return self.stk
@@ -939,7 +931,7 @@ class Session:
939
931
  self.pairing_config.delegate.display_number(self.passkey, digits=6)
940
932
  )
941
933
 
942
- def input_passkey(self, next_steps: Optional[Callable[[], None]] = None) -> None:
934
+ def input_passkey(self, next_steps: Callable[[], None] | None = None) -> None:
943
935
  # Prompt the user for the passkey displayed on the peer
944
936
  def after_input(passkey: int) -> None:
945
937
  self.passkey = passkey
@@ -956,7 +948,7 @@ class Session:
956
948
  self.prompt_user_for_number(after_input)
957
949
 
958
950
  def display_or_input_passkey(
959
- self, next_steps: Optional[Callable[[], None]] = None
951
+ self, next_steps: Callable[[], None] | None = None
960
952
  ) -> None:
961
953
  if self.passkey_display:
962
954
 
@@ -1006,7 +998,6 @@ class Session:
1006
998
  self.send_command(response)
1007
999
 
1008
1000
  def send_pairing_confirm_command(self) -> None:
1009
-
1010
1001
  if self.pairing_method != PairingMethod.OOB:
1011
1002
  self.r = crypto.r()
1012
1003
  logger.debug(f'generated random: {self.r.hex()}')
@@ -1842,7 +1833,6 @@ class Session:
1842
1833
  self.send_public_key_command()
1843
1834
 
1844
1835
  def next_steps() -> None:
1845
-
1846
1836
  if self.pairing_method in (
1847
1837
  PairingMethod.JUST_WORKS,
1848
1838
  PairingMethod.NUMERIC_COMPARISON,
@@ -1929,7 +1919,7 @@ class Manager(utils.EventEmitter):
1929
1919
  sessions: dict[int, Session]
1930
1920
  pairing_config_factory: Callable[[Connection], PairingConfig]
1931
1921
  session_proxy: type[Session]
1932
- _ecc_key: Optional[crypto.EccKey]
1922
+ _ecc_key: crypto.EccKey | None
1933
1923
 
1934
1924
  def __init__(
1935
1925
  self,
@@ -2022,7 +2012,7 @@ class Manager(utils.EventEmitter):
2022
2012
  self.device.on_pairing_start(session.connection)
2023
2013
 
2024
2014
  async def on_pairing(
2025
- self, session: Session, identity_address: Optional[Address], keys: PairingKeys
2015
+ self, session: Session, identity_address: Address | None, keys: PairingKeys
2026
2016
  ) -> None:
2027
2017
  # Store the keys in the key store
2028
2018
  if self.device.keystore and identity_address is not None:
@@ -2041,7 +2031,7 @@ class Manager(utils.EventEmitter):
2041
2031
 
2042
2032
  def get_long_term_key(
2043
2033
  self, connection: Connection, rand: bytes, ediv: int
2044
- ) -> Optional[bytes]:
2034
+ ) -> bytes | None:
2045
2035
  if session := self.sessions.get(connection.handle):
2046
2036
  return session.get_long_term_key(rand, ediv)
2047
2037
 
bumble/snoop.py CHANGED
@@ -16,13 +16,14 @@ import datetime
16
16
  import logging
17
17
  import os
18
18
  import struct
19
+ from collections.abc import Generator
19
20
 
20
21
  # -----------------------------------------------------------------------------
21
22
  # Imports
22
23
  # -----------------------------------------------------------------------------
23
24
  from contextlib import contextmanager
24
25
  from enum import IntEnum
25
- from typing import BinaryIO, Generator
26
+ from typing import BinaryIO
26
27
 
27
28
  from bumble import core
28
29
  from bumble.hci import HCI_COMMAND_PACKET, HCI_EVENT_PACKET
@@ -65,7 +66,7 @@ class BtSnooper(Snooper):
65
66
  """
66
67
 
67
68
  IDENTIFICATION_PATTERN = b'btsnoop\0'
68
- TIMESTAMP_ANCHOR = datetime.datetime(2000, 1, 1)
69
+ TIMESTAMP_ANCHOR = datetime.datetime(2000, 1, 1, tzinfo=datetime.timezone.utc)
69
70
  TIMESTAMP_DELTA = 0x00E03AB44A676000
70
71
  ONE_MS = datetime.timedelta(microseconds=1)
71
72
 
@@ -85,7 +86,13 @@ class BtSnooper(Snooper):
85
86
 
86
87
  # Compute the current timestamp
87
88
  timestamp = (
88
- int((datetime.datetime.utcnow() - self.TIMESTAMP_ANCHOR) / self.ONE_MS)
89
+ int(
90
+ (
91
+ datetime.datetime.now(tz=datetime.timezone.utc)
92
+ - self.TIMESTAMP_ANCHOR
93
+ )
94
+ / self.ONE_MS
95
+ )
89
96
  + self.TIMESTAMP_DELTA
90
97
  )
91
98
 
@@ -129,7 +136,7 @@ def create_snooper(spec: str) -> Generator[Snooper, None, None]:
129
136
  records will be written to that file if it can be opened/created.
130
137
  The keyword args that may be referenced by the string pattern are:
131
138
  now: the value of `datetime.now()`
132
- utcnow: the value of `datetime.utcnow()`
139
+ utcnow: the value of `datetime.now(tz=datetime.timezone.utc)`
133
140
  pid: the current process ID.
134
141
  instance: the instance ID in the current process.
135
142
 
@@ -153,7 +160,7 @@ def create_snooper(spec: str) -> Generator[Snooper, None, None]:
153
160
  global _SNOOPER_INSTANCE_COUNT
154
161
  file_path = io_name.format(
155
162
  now=datetime.datetime.now(),
156
- utcnow=datetime.datetime.utcnow(),
163
+ utcnow=datetime.datetime.now(tz=datetime.timezone.utc),
157
164
  pid=os.getpid(),
158
165
  instance=_SNOOPER_INSTANCE_COUNT,
159
166
  )
@@ -27,7 +27,7 @@ import sys
27
27
  import yaml
28
28
 
29
29
  # -----------------------------------------------------------------------------
30
- with open(sys.argv[1], "r") as yaml_file:
30
+ with open(sys.argv[1]) as yaml_file:
31
31
  root = yaml.safe_load(yaml_file)
32
32
  companies = {}
33
33
  for company in root["company_identifiers"]:
@@ -18,7 +18,7 @@ import asyncio
18
18
  # Imports
19
19
  # -----------------------------------------------------------------------------
20
20
  import logging
21
- from typing import Any, Optional
21
+ from typing import Any
22
22
 
23
23
  import click
24
24
 
@@ -47,7 +47,7 @@ def print_device_info(device_info: dict[intel.ValueType, Any]) -> None:
47
47
 
48
48
 
49
49
  # -----------------------------------------------------------------------------
50
- async def get_driver(host: Host, force: bool) -> Optional[intel.Driver]:
50
+ async def get_driver(host: Host, force: bool) -> intel.Driver | None:
51
51
  # Create a driver
52
52
  driver = await intel.Driver.for_host(host, force)
53
53
  if driver is None:
@@ -21,11 +21,11 @@ import urllib.error
21
21
  import urllib.request
22
22
 
23
23
  import click
24
+ from bumble.tools import rtk_util
24
25
 
25
26
  import bumble.logging
26
27
  from bumble.colors import color
27
28
  from bumble.drivers import rtk
28
- from bumble.tools import rtk_util
29
29
 
30
30
  # -----------------------------------------------------------------------------
31
31
  # Logging
bumble/tools/rtk_util.py CHANGED
@@ -21,7 +21,7 @@ import logging
21
21
  import click
22
22
 
23
23
  import bumble.logging
24
- from bumble import company_ids, hci, transport
24
+ from bumble import transport
25
25
  from bumble.drivers import rtk
26
26
  from bumble.host import Host
27
27
 
@@ -18,7 +18,6 @@
18
18
  import logging
19
19
  import os
20
20
  import re
21
- from typing import Optional
22
21
 
23
22
  from bumble import utils
24
23
  from bumble.snoop import create_snooper
@@ -111,7 +110,7 @@ async def open_transport(name: str) -> Transport:
111
110
 
112
111
 
113
112
  # -----------------------------------------------------------------------------
114
- async def _open_transport(scheme: str, spec: Optional[str]) -> Transport:
113
+ async def _open_transport(scheme: str, spec: str | None) -> Transport:
115
114
  # pylint: disable=import-outside-toplevel
116
115
  # pylint: disable=too-many-return-statements
117
116