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/avc.py CHANGED
@@ -18,7 +18,7 @@
18
18
  from __future__ import annotations
19
19
  import enum
20
20
  import struct
21
- from typing import Dict, Type, Union, Tuple
21
+ from typing import Union
22
22
 
23
23
  from bumble import core
24
24
  from bumble import utils
@@ -213,11 +213,11 @@ class CommandFrame(Frame):
213
213
  NOTIFY = 0x03
214
214
  GENERAL_INQUIRY = 0x04
215
215
 
216
- subclasses: Dict[Frame.OperationCode, Type[CommandFrame]] = {}
216
+ subclasses: dict[Frame.OperationCode, type[CommandFrame]] = {}
217
217
  ctype: CommandType
218
218
 
219
219
  @staticmethod
220
- def parse_operands(operands: bytes) -> Tuple:
220
+ def parse_operands(operands: bytes) -> tuple:
221
221
  raise NotImplementedError
222
222
 
223
223
  def __init__(
@@ -251,11 +251,11 @@ class ResponseFrame(Frame):
251
251
  CHANGED = 0x0D
252
252
  INTERIM = 0x0F
253
253
 
254
- subclasses: Dict[Frame.OperationCode, Type[ResponseFrame]] = {}
254
+ subclasses: dict[Frame.OperationCode, type[ResponseFrame]] = {}
255
255
  response: ResponseCode
256
256
 
257
257
  @staticmethod
258
- def parse_operands(operands: bytes) -> Tuple:
258
+ def parse_operands(operands: bytes) -> tuple:
259
259
  raise NotImplementedError
260
260
 
261
261
  def __init__(
@@ -282,7 +282,7 @@ class VendorDependentFrame:
282
282
  vendor_dependent_data: bytes
283
283
 
284
284
  @staticmethod
285
- def parse_operands(operands: bytes) -> Tuple:
285
+ def parse_operands(operands: bytes) -> tuple:
286
286
  return (
287
287
  struct.unpack(">I", b"\x00" + operands[:3])[0],
288
288
  operands[3:],
@@ -432,7 +432,7 @@ class PassThroughFrame:
432
432
  operation_data: bytes
433
433
 
434
434
  @staticmethod
435
- def parse_operands(operands: bytes) -> Tuple:
435
+ def parse_operands(operands: bytes) -> tuple:
436
436
  return (
437
437
  PassThroughFrame.StateFlag(operands[0] >> 7),
438
438
  PassThroughFrame.OperationId(operands[0] & 0x7F),
bumble/avctp.py CHANGED
@@ -19,7 +19,7 @@ from __future__ import annotations
19
19
  from enum import IntEnum
20
20
  import logging
21
21
  import struct
22
- from typing import Callable, cast, Dict, Optional
22
+ from typing import Callable, cast, Optional
23
23
 
24
24
  from bumble.colors import color
25
25
  from bumble import avc
@@ -146,9 +146,9 @@ class MessageAssembler:
146
146
  # -----------------------------------------------------------------------------
147
147
  class Protocol:
148
148
  CommandHandler = Callable[[int, avc.CommandFrame], None]
149
- command_handlers: Dict[int, CommandHandler] # Command handlers, by PID
149
+ command_handlers: dict[int, CommandHandler] # Command handlers, by PID
150
150
  ResponseHandler = Callable[[int, Optional[avc.ResponseFrame]], None]
151
- response_handlers: Dict[int, ResponseHandler] # Response handlers, by PID
151
+ response_handlers: dict[int, ResponseHandler] # Response handlers, by PID
152
152
  next_transaction_label: int
153
153
  message_assembler: MessageAssembler
154
154
 
@@ -166,8 +166,8 @@ class Protocol:
166
166
 
167
167
  # Register to receive PDUs from the channel
168
168
  l2cap_channel.sink = self.on_pdu
169
- l2cap_channel.on("open", self.on_l2cap_channel_open)
170
- l2cap_channel.on("close", self.on_l2cap_channel_close)
169
+ l2cap_channel.on(l2cap_channel.EVENT_OPEN, self.on_l2cap_channel_open)
170
+ l2cap_channel.on(l2cap_channel.EVENT_CLOSE, self.on_l2cap_channel_close)
171
171
 
172
172
  def on_l2cap_channel_open(self):
173
173
  logger.debug(color("<<< AVCTP channel open", "magenta"))
bumble/avdtp.py CHANGED
@@ -24,12 +24,8 @@ import warnings
24
24
  from typing import (
25
25
  Any,
26
26
  Awaitable,
27
- Dict,
28
- Type,
29
- Tuple,
30
27
  Optional,
31
28
  Callable,
32
- List,
33
29
  AsyncGenerator,
34
30
  Iterable,
35
31
  Union,
@@ -227,7 +223,7 @@ AVDTP_STATE_NAMES = {
227
223
  # -----------------------------------------------------------------------------
228
224
  async def find_avdtp_service_with_sdp_client(
229
225
  sdp_client: sdp.Client,
230
- ) -> Optional[Tuple[int, int]]:
226
+ ) -> Optional[tuple[int, int]]:
231
227
  '''
232
228
  Find an AVDTP service, using a connected SDP client, and return its version,
233
229
  or None if none is found
@@ -257,7 +253,7 @@ async def find_avdtp_service_with_sdp_client(
257
253
  # -----------------------------------------------------------------------------
258
254
  async def find_avdtp_service_with_connection(
259
255
  connection: device.Connection,
260
- ) -> Optional[Tuple[int, int]]:
256
+ ) -> Optional[tuple[int, int]]:
261
257
  '''
262
258
  Find an AVDTP service, for a connection, and return its version,
263
259
  or None if none is found
@@ -451,7 +447,7 @@ class ServiceCapabilities:
451
447
  service_category: int, service_capabilities_bytes: bytes
452
448
  ) -> ServiceCapabilities:
453
449
  # Select the appropriate subclass
454
- cls: Type[ServiceCapabilities]
450
+ cls: type[ServiceCapabilities]
455
451
  if service_category == AVDTP_MEDIA_CODEC_SERVICE_CATEGORY:
456
452
  cls = MediaCodecCapabilities
457
453
  else:
@@ -466,7 +462,7 @@ class ServiceCapabilities:
466
462
  return instance
467
463
 
468
464
  @staticmethod
469
- def parse_capabilities(payload: bytes) -> List[ServiceCapabilities]:
465
+ def parse_capabilities(payload: bytes) -> list[ServiceCapabilities]:
470
466
  capabilities = []
471
467
  while payload:
472
468
  service_category = payload[0]
@@ -499,7 +495,7 @@ class ServiceCapabilities:
499
495
  self.service_category = service_category
500
496
  self.service_capabilities_bytes = service_capabilities_bytes
501
497
 
502
- def to_string(self, details: Optional[List[str]] = None) -> str:
498
+ def to_string(self, details: Optional[list[str]] = None) -> str:
503
499
  attributes = ','.join(
504
500
  [name_or_number(AVDTP_SERVICE_CATEGORY_NAMES, self.service_category)]
505
501
  + (details or [])
@@ -612,7 +608,7 @@ class Message: # pylint:disable=attribute-defined-outside-init
612
608
  RESPONSE_REJECT = 3
613
609
 
614
610
  # Subclasses, by signal identifier and message type
615
- subclasses: Dict[int, Dict[int, Type[Message]]] = {}
611
+ subclasses: dict[int, dict[int, type[Message]]] = {}
616
612
  message_type: MessageType
617
613
  signal_identifier: int
618
614
 
@@ -757,7 +753,7 @@ class Discover_Response(Message):
757
753
  See Bluetooth AVDTP spec - 8.6.2 Stream End Point Discovery Response
758
754
  '''
759
755
 
760
- endpoints: List[EndPointInfo]
756
+ endpoints: list[EndPointInfo]
761
757
 
762
758
  def init_from_payload(self):
763
759
  self.endpoints = []
@@ -896,7 +892,7 @@ class Set_Configuration_Reject(Message):
896
892
  self.service_category = self.payload[0]
897
893
  self.error_code = self.payload[1]
898
894
 
899
- def __init__(self, service_category, error_code):
895
+ def __init__(self, error_code: int, service_category: int = 0) -> None:
900
896
  super().__init__(payload=bytes([service_category, error_code]))
901
897
  self.service_category = service_category
902
898
  self.error_code = error_code
@@ -1132,6 +1128,14 @@ class Security_Control_Command(Message):
1132
1128
  See Bluetooth AVDTP spec - 8.17.1 Security Control Command
1133
1129
  '''
1134
1130
 
1131
+ def init_from_payload(self):
1132
+ # pylint: disable=attribute-defined-outside-init
1133
+ self.acp_seid = self.payload[0] >> 2
1134
+ self.data = self.payload[1:]
1135
+
1136
+ def __str__(self) -> str:
1137
+ return self.to_string([f'ACP_SEID: {self.acp_seid}', f'data: {self.data}'])
1138
+
1135
1139
 
1136
1140
  # -----------------------------------------------------------------------------
1137
1141
  @Message.subclass
@@ -1194,12 +1198,15 @@ class DelayReport_Reject(Simple_Reject):
1194
1198
 
1195
1199
  # -----------------------------------------------------------------------------
1196
1200
  class Protocol(utils.EventEmitter):
1197
- local_endpoints: List[LocalStreamEndPoint]
1198
- remote_endpoints: Dict[int, DiscoveredStreamEndPoint]
1199
- streams: Dict[int, Stream]
1200
- transaction_results: List[Optional[asyncio.Future[Message]]]
1201
+ local_endpoints: list[LocalStreamEndPoint]
1202
+ remote_endpoints: dict[int, DiscoveredStreamEndPoint]
1203
+ streams: dict[int, Stream]
1204
+ transaction_results: list[Optional[asyncio.Future[Message]]]
1201
1205
  channel_connector: Callable[[], Awaitable[l2cap.ClassicChannel]]
1202
1206
 
1207
+ EVENT_OPEN = "open"
1208
+ EVENT_CLOSE = "close"
1209
+
1203
1210
  class PacketType(enum.IntEnum):
1204
1211
  SINGLE_PACKET = 0
1205
1212
  START_PACKET = 1
@@ -1212,7 +1219,7 @@ class Protocol(utils.EventEmitter):
1212
1219
 
1213
1220
  @staticmethod
1214
1221
  async def connect(
1215
- connection: device.Connection, version: Tuple[int, int] = (1, 3)
1222
+ connection: device.Connection, version: tuple[int, int] = (1, 3)
1216
1223
  ) -> Protocol:
1217
1224
  channel = await connection.create_l2cap_channel(
1218
1225
  spec=l2cap.ClassicChannelSpec(psm=AVDTP_PSM)
@@ -1222,7 +1229,7 @@ class Protocol(utils.EventEmitter):
1222
1229
  return protocol
1223
1230
 
1224
1231
  def __init__(
1225
- self, l2cap_channel: l2cap.ClassicChannel, version: Tuple[int, int] = (1, 3)
1232
+ self, l2cap_channel: l2cap.ClassicChannel, version: tuple[int, int] = (1, 3)
1226
1233
  ) -> None:
1227
1234
  super().__init__()
1228
1235
  self.l2cap_channel = l2cap_channel
@@ -1239,8 +1246,8 @@ class Protocol(utils.EventEmitter):
1239
1246
 
1240
1247
  # Register to receive PDUs from the channel
1241
1248
  l2cap_channel.sink = self.on_pdu
1242
- l2cap_channel.on('open', self.on_l2cap_channel_open)
1243
- l2cap_channel.on('close', self.on_l2cap_channel_close)
1249
+ l2cap_channel.on(l2cap_channel.EVENT_OPEN, self.on_l2cap_channel_open)
1250
+ l2cap_channel.on(l2cap_channel.EVENT_CLOSE, self.on_l2cap_channel_close)
1244
1251
 
1245
1252
  def get_local_endpoint_by_seid(self, seid: int) -> Optional[LocalStreamEndPoint]:
1246
1253
  if 0 < seid <= len(self.local_endpoints):
@@ -1410,20 +1417,20 @@ class Protocol(utils.EventEmitter):
1410
1417
  self.transaction_results[transaction_label] = None
1411
1418
  self.transaction_semaphore.release()
1412
1419
 
1413
- def on_l2cap_connection(self, channel):
1420
+ def on_l2cap_connection(self, channel: l2cap.ClassicChannel) -> None:
1414
1421
  # Forward the channel to the endpoint that's expecting it
1415
1422
  if self.channel_acceptor is None:
1416
1423
  logger.warning(color('!!! l2cap connection with no acceptor', 'red'))
1417
1424
  return
1418
1425
  self.channel_acceptor.on_l2cap_connection(channel)
1419
1426
 
1420
- def on_l2cap_channel_open(self):
1427
+ def on_l2cap_channel_open(self) -> None:
1421
1428
  logger.debug(color('<<< L2CAP channel open', 'magenta'))
1422
- self.emit('open')
1429
+ self.emit(self.EVENT_OPEN)
1423
1430
 
1424
- def on_l2cap_channel_close(self):
1431
+ def on_l2cap_channel_close(self) -> None:
1425
1432
  logger.debug(color('<<< L2CAP channel close', 'magenta'))
1426
- self.emit('close')
1433
+ self.emit(self.EVENT_CLOSE)
1427
1434
 
1428
1435
  def send_message(self, transaction_label: int, message: Message) -> None:
1429
1436
  logger.debug(
@@ -1491,7 +1498,7 @@ class Protocol(utils.EventEmitter):
1491
1498
 
1492
1499
  return response
1493
1500
 
1494
- async def start_transaction(self) -> Tuple[int, asyncio.Future[Message]]:
1501
+ async def start_transaction(self) -> tuple[int, asyncio.Future[Message]]:
1495
1502
  # Wait until we can start a new transaction
1496
1503
  await self.transaction_semaphore.acquire()
1497
1504
 
@@ -1541,28 +1548,34 @@ class Protocol(utils.EventEmitter):
1541
1548
  async def abort(self, seid: int) -> Abort_Response:
1542
1549
  return await self.send_command(Abort_Command(seid))
1543
1550
 
1544
- def on_discover_command(self, _command):
1551
+ def on_discover_command(self, command: Discover_Command) -> Optional[Message]:
1545
1552
  endpoint_infos = [
1546
1553
  EndPointInfo(endpoint.seid, 0, endpoint.media_type, endpoint.tsep)
1547
1554
  for endpoint in self.local_endpoints
1548
1555
  ]
1549
1556
  return Discover_Response(endpoint_infos)
1550
1557
 
1551
- def on_get_capabilities_command(self, command):
1558
+ def on_get_capabilities_command(
1559
+ self, command: Get_Capabilities_Command
1560
+ ) -> Optional[Message]:
1552
1561
  endpoint = self.get_local_endpoint_by_seid(command.acp_seid)
1553
1562
  if endpoint is None:
1554
1563
  return Get_Capabilities_Reject(AVDTP_BAD_ACP_SEID_ERROR)
1555
1564
 
1556
1565
  return Get_Capabilities_Response(endpoint.capabilities)
1557
1566
 
1558
- def on_get_all_capabilities_command(self, command):
1567
+ def on_get_all_capabilities_command(
1568
+ self, command: Get_All_Capabilities_Command
1569
+ ) -> Optional[Message]:
1559
1570
  endpoint = self.get_local_endpoint_by_seid(command.acp_seid)
1560
1571
  if endpoint is None:
1561
1572
  return Get_All_Capabilities_Reject(AVDTP_BAD_ACP_SEID_ERROR)
1562
1573
 
1563
1574
  return Get_All_Capabilities_Response(endpoint.capabilities)
1564
1575
 
1565
- def on_set_configuration_command(self, command):
1576
+ def on_set_configuration_command(
1577
+ self, command: Set_Configuration_Command
1578
+ ) -> Optional[Message]:
1566
1579
  endpoint = self.get_local_endpoint_by_seid(command.acp_seid)
1567
1580
  if endpoint is None:
1568
1581
  return Set_Configuration_Reject(AVDTP_BAD_ACP_SEID_ERROR)
@@ -1578,7 +1591,9 @@ class Protocol(utils.EventEmitter):
1578
1591
  result = stream.on_set_configuration_command(command.capabilities)
1579
1592
  return result or Set_Configuration_Response()
1580
1593
 
1581
- def on_get_configuration_command(self, command):
1594
+ def on_get_configuration_command(
1595
+ self, command: Get_Configuration_Command
1596
+ ) -> Optional[Message]:
1582
1597
  endpoint = self.get_local_endpoint_by_seid(command.acp_seid)
1583
1598
  if endpoint is None:
1584
1599
  return Get_Configuration_Reject(AVDTP_BAD_ACP_SEID_ERROR)
@@ -1587,7 +1602,7 @@ class Protocol(utils.EventEmitter):
1587
1602
 
1588
1603
  return endpoint.stream.on_get_configuration_command()
1589
1604
 
1590
- def on_reconfigure_command(self, command):
1605
+ def on_reconfigure_command(self, command: Reconfigure_Command) -> Optional[Message]:
1591
1606
  endpoint = self.get_local_endpoint_by_seid(command.acp_seid)
1592
1607
  if endpoint is None:
1593
1608
  return Reconfigure_Reject(0, AVDTP_BAD_ACP_SEID_ERROR)
@@ -1597,7 +1612,7 @@ class Protocol(utils.EventEmitter):
1597
1612
  result = endpoint.stream.on_reconfigure_command(command.capabilities)
1598
1613
  return result or Reconfigure_Response()
1599
1614
 
1600
- def on_open_command(self, command):
1615
+ def on_open_command(self, command: Open_Command) -> Optional[Message]:
1601
1616
  endpoint = self.get_local_endpoint_by_seid(command.acp_seid)
1602
1617
  if endpoint is None:
1603
1618
  return Open_Reject(AVDTP_BAD_ACP_SEID_ERROR)
@@ -1607,25 +1622,26 @@ class Protocol(utils.EventEmitter):
1607
1622
  result = endpoint.stream.on_open_command()
1608
1623
  return result or Open_Response()
1609
1624
 
1610
- def on_start_command(self, command):
1625
+ def on_start_command(self, command: Start_Command) -> Optional[Message]:
1611
1626
  for seid in command.acp_seids:
1612
1627
  endpoint = self.get_local_endpoint_by_seid(seid)
1613
1628
  if endpoint is None:
1614
1629
  return Start_Reject(seid, AVDTP_BAD_ACP_SEID_ERROR)
1615
1630
  if endpoint.stream is None:
1616
- return Start_Reject(AVDTP_BAD_STATE_ERROR)
1631
+ return Start_Reject(seid, AVDTP_BAD_STATE_ERROR)
1617
1632
 
1618
1633
  # Start all streams
1619
1634
  # TODO: deal with partial failures
1620
1635
  for seid in command.acp_seids:
1621
1636
  endpoint = self.get_local_endpoint_by_seid(seid)
1622
- result = endpoint.stream.on_start_command()
1623
- if result is not None:
1637
+ if not endpoint or not endpoint.stream:
1638
+ raise InvalidStateError("Should already be checked!")
1639
+ if (result := endpoint.stream.on_start_command()) is not None:
1624
1640
  return result
1625
1641
 
1626
1642
  return Start_Response()
1627
1643
 
1628
- def on_suspend_command(self, command):
1644
+ def on_suspend_command(self, command: Suspend_Command) -> Optional[Message]:
1629
1645
  for seid in command.acp_seids:
1630
1646
  endpoint = self.get_local_endpoint_by_seid(seid)
1631
1647
  if endpoint is None:
@@ -1637,13 +1653,14 @@ class Protocol(utils.EventEmitter):
1637
1653
  # TODO: deal with partial failures
1638
1654
  for seid in command.acp_seids:
1639
1655
  endpoint = self.get_local_endpoint_by_seid(seid)
1640
- result = endpoint.stream.on_suspend_command()
1641
- if result is not None:
1656
+ if not endpoint or not endpoint.stream:
1657
+ raise InvalidStateError("Should already be checked!")
1658
+ if (result := endpoint.stream.on_suspend_command()) is not None:
1642
1659
  return result
1643
1660
 
1644
1661
  return Suspend_Response()
1645
1662
 
1646
- def on_close_command(self, command):
1663
+ def on_close_command(self, command: Close_Command) -> Optional[Message]:
1647
1664
  endpoint = self.get_local_endpoint_by_seid(command.acp_seid)
1648
1665
  if endpoint is None:
1649
1666
  return Close_Reject(AVDTP_BAD_ACP_SEID_ERROR)
@@ -1653,7 +1670,7 @@ class Protocol(utils.EventEmitter):
1653
1670
  result = endpoint.stream.on_close_command()
1654
1671
  return result or Close_Response()
1655
1672
 
1656
- def on_abort_command(self, command):
1673
+ def on_abort_command(self, command: Abort_Command) -> Optional[Message]:
1657
1674
  endpoint = self.get_local_endpoint_by_seid(command.acp_seid)
1658
1675
  if endpoint is None or endpoint.stream is None:
1659
1676
  return Abort_Response()
@@ -1661,15 +1678,17 @@ class Protocol(utils.EventEmitter):
1661
1678
  endpoint.stream.on_abort_command()
1662
1679
  return Abort_Response()
1663
1680
 
1664
- def on_security_control_command(self, command):
1681
+ def on_security_control_command(
1682
+ self, command: Security_Control_Command
1683
+ ) -> Optional[Message]:
1665
1684
  endpoint = self.get_local_endpoint_by_seid(command.acp_seid)
1666
1685
  if endpoint is None:
1667
1686
  return Security_Control_Reject(AVDTP_BAD_ACP_SEID_ERROR)
1668
1687
 
1669
- result = endpoint.on_security_control_command(command.payload)
1688
+ result = endpoint.on_security_control_command(command.data)
1670
1689
  return result or Security_Control_Response()
1671
1690
 
1672
- def on_delayreport_command(self, command):
1691
+ def on_delayreport_command(self, command: DelayReport_Command) -> Optional[Message]:
1673
1692
  endpoint = self.get_local_endpoint_by_seid(command.acp_seid)
1674
1693
  if endpoint is None:
1675
1694
  return DelayReport_Reject(AVDTP_BAD_ACP_SEID_ERROR)
@@ -1680,7 +1699,9 @@ class Protocol(utils.EventEmitter):
1680
1699
 
1681
1700
  # -----------------------------------------------------------------------------
1682
1701
  class Listener(utils.EventEmitter):
1683
- servers: Dict[int, Protocol]
1702
+ servers: dict[int, Protocol]
1703
+
1704
+ EVENT_CONNECTION = "connection"
1684
1705
 
1685
1706
  @staticmethod
1686
1707
  def create_registrar(device: device.Device):
@@ -1710,13 +1731,13 @@ class Listener(utils.EventEmitter):
1710
1731
 
1711
1732
  @classmethod
1712
1733
  def for_device(
1713
- cls, device: device.Device, version: Tuple[int, int] = (1, 3)
1734
+ cls, device: device.Device, version: tuple[int, int] = (1, 3)
1714
1735
  ) -> Listener:
1715
1736
  listener = Listener(registrar=None, version=version)
1716
1737
  l2cap_server = device.create_l2cap_server(
1717
1738
  spec=l2cap.ClassicChannelSpec(psm=AVDTP_PSM)
1718
1739
  )
1719
- l2cap_server.on('connection', listener.on_l2cap_connection)
1740
+ l2cap_server.on(l2cap_server.EVENT_CONNECTION, listener.on_l2cap_connection)
1720
1741
  return listener
1721
1742
 
1722
1743
  def on_l2cap_connection(self, channel: l2cap.ClassicChannel) -> None:
@@ -1732,14 +1753,14 @@ class Listener(utils.EventEmitter):
1732
1753
  logger.debug('setting up new Protocol for the connection')
1733
1754
  server = Protocol(channel, self.version)
1734
1755
  self.set_server(channel.connection, server)
1735
- self.emit('connection', server)
1756
+ self.emit(self.EVENT_CONNECTION, server)
1736
1757
 
1737
1758
  def on_channel_close():
1738
1759
  logger.debug('removing Protocol for the connection')
1739
1760
  self.remove_server(channel.connection)
1740
1761
 
1741
- channel.on('open', on_channel_open)
1742
- channel.on('close', on_channel_close)
1762
+ channel.on(channel.EVENT_OPEN, on_channel_open)
1763
+ channel.on(channel.EVENT_CLOSE, on_channel_close)
1743
1764
 
1744
1765
 
1745
1766
  # -----------------------------------------------------------------------------
@@ -1788,6 +1809,7 @@ class Stream:
1788
1809
  )
1789
1810
 
1790
1811
  async def start(self) -> None:
1812
+ """[Source] Start streaming."""
1791
1813
  # Auto-open if needed
1792
1814
  if self.state == AVDTP_CONFIGURED_STATE:
1793
1815
  await self.open()
@@ -1804,6 +1826,7 @@ class Stream:
1804
1826
  self.change_state(AVDTP_STREAMING_STATE)
1805
1827
 
1806
1828
  async def stop(self) -> None:
1829
+ """[Source] Stop streaming and transit to OPEN state."""
1807
1830
  if self.state != AVDTP_STREAMING_STATE:
1808
1831
  raise InvalidStateError('current state is not STREAMING')
1809
1832
 
@@ -1816,6 +1839,7 @@ class Stream:
1816
1839
  self.change_state(AVDTP_OPEN_STATE)
1817
1840
 
1818
1841
  async def close(self) -> None:
1842
+ """[Source] Close channel and transit to IDLE state."""
1819
1843
  if self.state not in (AVDTP_OPEN_STATE, AVDTP_STREAMING_STATE):
1820
1844
  raise InvalidStateError('current state is not OPEN or STREAMING')
1821
1845
 
@@ -1847,7 +1871,7 @@ class Stream:
1847
1871
  self.change_state(AVDTP_CONFIGURED_STATE)
1848
1872
  return None
1849
1873
 
1850
- def on_get_configuration_command(self, configuration):
1874
+ def on_get_configuration_command(self):
1851
1875
  if self.state not in (
1852
1876
  AVDTP_CONFIGURED_STATE,
1853
1877
  AVDTP_OPEN_STATE,
@@ -1855,7 +1879,7 @@ class Stream:
1855
1879
  ):
1856
1880
  return Get_Configuration_Reject(AVDTP_BAD_STATE_ERROR)
1857
1881
 
1858
- return self.local_endpoint.on_get_configuration_command(configuration)
1882
+ return self.local_endpoint.on_get_configuration_command()
1859
1883
 
1860
1884
  def on_reconfigure_command(self, configuration):
1861
1885
  if self.state != AVDTP_OPEN_STATE:
@@ -1935,20 +1959,20 @@ class Stream:
1935
1959
  # Wait for the RTP channel to be closed
1936
1960
  self.change_state(AVDTP_ABORTING_STATE)
1937
1961
 
1938
- def on_l2cap_connection(self, channel):
1962
+ def on_l2cap_connection(self, channel: l2cap.ClassicChannel) -> None:
1939
1963
  logger.debug(color('<<< stream channel connected', 'magenta'))
1940
1964
  self.rtp_channel = channel
1941
- channel.on('open', self.on_l2cap_channel_open)
1942
- channel.on('close', self.on_l2cap_channel_close)
1965
+ channel.on(channel.EVENT_OPEN, self.on_l2cap_channel_open)
1966
+ channel.on(channel.EVENT_CLOSE, self.on_l2cap_channel_close)
1943
1967
 
1944
1968
  # We don't need more channels
1945
1969
  self.protocol.channel_acceptor = None
1946
1970
 
1947
- def on_l2cap_channel_open(self):
1971
+ def on_l2cap_channel_open(self) -> None:
1948
1972
  logger.debug(color('<<< stream channel open', 'magenta'))
1949
1973
  self.local_endpoint.on_rtp_channel_open()
1950
1974
 
1951
- def on_l2cap_channel_close(self):
1975
+ def on_l2cap_channel_close(self) -> None:
1952
1976
  logger.debug(color('<<< stream channel closed', 'magenta'))
1953
1977
  self.local_endpoint.on_rtp_channel_close()
1954
1978
  self.local_endpoint.in_use = 0
@@ -2065,6 +2089,19 @@ class DiscoveredStreamEndPoint(StreamEndPoint, StreamEndPointProxy):
2065
2089
  class LocalStreamEndPoint(StreamEndPoint, utils.EventEmitter):
2066
2090
  stream: Optional[Stream]
2067
2091
 
2092
+ EVENT_CONFIGURATION = "configuration"
2093
+ EVENT_OPEN = "open"
2094
+ EVENT_START = "start"
2095
+ EVENT_STOP = "stop"
2096
+ EVENT_RTP_PACKET = "rtp_packet"
2097
+ EVENT_SUSPEND = "suspend"
2098
+ EVENT_CLOSE = "close"
2099
+ EVENT_ABORT = "abort"
2100
+ EVENT_DELAY_REPORT = "delay_report"
2101
+ EVENT_SECURITY_CONTROL = "security_control"
2102
+ EVENT_RTP_CHANNEL_OPEN = "rtp_channel_open"
2103
+ EVENT_RTP_CHANNEL_CLOSE = "rtp_channel_close"
2104
+
2068
2105
  def __init__(
2069
2106
  self,
2070
2107
  protocol: Protocol,
@@ -2080,52 +2117,65 @@ class LocalStreamEndPoint(StreamEndPoint, utils.EventEmitter):
2080
2117
  self.configuration = configuration if configuration is not None else []
2081
2118
  self.stream = None
2082
2119
 
2083
- async def start(self):
2084
- pass
2120
+ async def start(self) -> None:
2121
+ """[Source Only] Handles when receiving start command."""
2085
2122
 
2086
- async def stop(self):
2087
- pass
2123
+ async def stop(self) -> None:
2124
+ """[Source Only] Handles when receiving stop command."""
2088
2125
 
2089
- async def close(self):
2090
- pass
2126
+ async def close(self) -> None:
2127
+ """[Source Only] Handles when receiving close command."""
2091
2128
 
2092
- def on_reconfigure_command(self, command):
2093
- pass
2129
+ def on_reconfigure_command(self, command) -> Optional[Message]:
2130
+ return None
2094
2131
 
2095
- def on_set_configuration_command(self, configuration):
2132
+ def on_set_configuration_command(self, configuration) -> Optional[Message]:
2096
2133
  logger.debug(
2097
2134
  '<<< received configuration: '
2098
2135
  f'{",".join([str(capability) for capability in configuration])}'
2099
2136
  )
2100
2137
  self.configuration = configuration
2101
- self.emit('configuration')
2138
+ self.emit(self.EVENT_CONFIGURATION)
2139
+ return None
2102
2140
 
2103
- def on_get_configuration_command(self):
2141
+ def on_get_configuration_command(self) -> Optional[Message]:
2104
2142
  return Get_Configuration_Response(self.configuration)
2105
2143
 
2106
- def on_open_command(self):
2107
- self.emit('open')
2144
+ def on_open_command(self) -> Optional[Message]:
2145
+ self.emit(self.EVENT_OPEN)
2146
+ return None
2108
2147
 
2109
- def on_start_command(self):
2110
- self.emit('start')
2148
+ def on_start_command(self) -> Optional[Message]:
2149
+ self.emit(self.EVENT_START)
2150
+ return None
2111
2151
 
2112
- def on_suspend_command(self):
2113
- self.emit('suspend')
2152
+ def on_suspend_command(self) -> Optional[Message]:
2153
+ self.emit(self.EVENT_SUSPEND)
2154
+ return None
2114
2155
 
2115
- def on_close_command(self):
2116
- self.emit('close')
2156
+ def on_close_command(self) -> Optional[Message]:
2157
+ self.emit(self.EVENT_CLOSE)
2158
+ return None
2117
2159
 
2118
- def on_abort_command(self):
2119
- self.emit('abort')
2160
+ def on_abort_command(self) -> Optional[Message]:
2161
+ self.emit(self.EVENT_ABORT)
2162
+ return None
2120
2163
 
2121
- def on_delayreport_command(self, delay: int):
2122
- self.emit('delay_report', delay)
2164
+ def on_delayreport_command(self, delay: int) -> Optional[Message]:
2165
+ self.emit(self.EVENT_DELAY_REPORT, delay)
2166
+ return None
2123
2167
 
2124
- def on_rtp_channel_open(self):
2125
- self.emit('rtp_channel_open')
2168
+ def on_security_control_command(self, data: bytes) -> Optional[Message]:
2169
+ self.emit(self.EVENT_SECURITY_CONTROL, data)
2170
+ return None
2126
2171
 
2127
- def on_rtp_channel_close(self):
2128
- self.emit('rtp_channel_close')
2172
+ def on_rtp_channel_open(self) -> None:
2173
+ self.emit(self.EVENT_RTP_CHANNEL_OPEN)
2174
+ return None
2175
+
2176
+ def on_rtp_channel_close(self) -> None:
2177
+ self.emit(self.EVENT_RTP_CHANNEL_CLOSE)
2178
+ return None
2129
2179
 
2130
2180
 
2131
2181
  # -----------------------------------------------------------------------------
@@ -2156,13 +2206,13 @@ class LocalSource(LocalStreamEndPoint):
2156
2206
  if self.packet_pump and self.stream and self.stream.rtp_channel:
2157
2207
  return await self.packet_pump.start(self.stream.rtp_channel)
2158
2208
 
2159
- self.emit('start')
2209
+ self.emit(self.EVENT_START)
2160
2210
 
2161
2211
  async def stop(self) -> None:
2162
2212
  if self.packet_pump:
2163
2213
  return await self.packet_pump.stop()
2164
2214
 
2165
- self.emit('stop')
2215
+ self.emit(self.EVENT_STOP)
2166
2216
 
2167
2217
  def on_start_command(self):
2168
2218
  asyncio.create_task(self.start())
@@ -2203,4 +2253,4 @@ class LocalSink(LocalStreamEndPoint):
2203
2253
  f'{color("<<< RTP Packet:", "green")} '
2204
2254
  f'{rtp_packet} {rtp_packet.payload[:16].hex()}'
2205
2255
  )
2206
- self.emit('rtp_packet', rtp_packet)
2256
+ self.emit(self.EVENT_RTP_PACKET, rtp_packet)