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.
- bumble/_version.py +2 -2
- bumble/a2dp.py +6 -0
- bumble/apps/README.md +0 -3
- bumble/apps/auracast.py +11 -9
- bumble/apps/bench.py +482 -31
- bumble/apps/console.py +5 -5
- bumble/apps/controller_info.py +47 -10
- bumble/apps/controller_loopback.py +7 -3
- bumble/apps/controllers.py +2 -2
- bumble/apps/device_info.py +2 -2
- bumble/apps/gatt_dump.py +2 -2
- bumble/apps/gg_bridge.py +2 -2
- bumble/apps/hci_bridge.py +2 -2
- bumble/apps/l2cap_bridge.py +2 -2
- bumble/apps/lea_unicast/app.py +6 -1
- bumble/apps/pair.py +204 -43
- bumble/apps/pandora_server.py +2 -2
- bumble/apps/rfcomm_bridge.py +1 -1
- bumble/apps/scan.py +2 -2
- bumble/apps/show.py +4 -2
- bumble/apps/speaker/speaker.html +1 -0
- bumble/apps/speaker/speaker.js +113 -62
- bumble/apps/speaker/speaker.py +126 -18
- bumble/at.py +4 -4
- bumble/att.py +15 -18
- bumble/avc.py +7 -7
- bumble/avctp.py +5 -5
- bumble/avdtp.py +138 -88
- bumble/avrcp.py +52 -58
- bumble/colors.py +2 -2
- bumble/controller.py +84 -23
- bumble/core.py +13 -7
- bumble/{crypto.py → crypto/__init__.py} +11 -95
- bumble/crypto/builtin.py +652 -0
- bumble/crypto/cryptography.py +84 -0
- bumble/device.py +688 -345
- bumble/drivers/__init__.py +2 -2
- bumble/drivers/common.py +0 -2
- bumble/drivers/intel.py +40 -40
- bumble/drivers/rtk.py +28 -35
- bumble/gatt.py +7 -9
- bumble/gatt_adapters.py +4 -5
- bumble/gatt_client.py +31 -34
- bumble/gatt_server.py +15 -17
- bumble/hci.py +2635 -2878
- bumble/helpers.py +4 -5
- bumble/hfp.py +76 -57
- bumble/hid.py +24 -12
- bumble/host.py +117 -34
- bumble/keys.py +68 -52
- bumble/l2cap.py +329 -403
- bumble/link.py +6 -270
- bumble/pairing.py +23 -20
- bumble/pandora/__init__.py +1 -1
- bumble/pandora/config.py +2 -2
- bumble/pandora/device.py +6 -6
- bumble/pandora/host.py +38 -39
- bumble/pandora/l2cap.py +4 -4
- bumble/pandora/security.py +73 -57
- bumble/pandora/utils.py +3 -3
- bumble/profiles/aics.py +3 -5
- bumble/profiles/ancs.py +3 -1
- bumble/profiles/ascs.py +143 -136
- bumble/profiles/asha.py +13 -8
- bumble/profiles/bap.py +3 -4
- bumble/profiles/csip.py +3 -5
- bumble/profiles/device_information_service.py +2 -2
- bumble/profiles/gap.py +2 -2
- bumble/profiles/gatt_service.py +1 -3
- bumble/profiles/hap.py +42 -58
- bumble/profiles/le_audio.py +4 -4
- bumble/profiles/mcp.py +16 -13
- bumble/profiles/vcs.py +8 -10
- bumble/profiles/vocs.py +6 -9
- bumble/rfcomm.py +27 -18
- bumble/rtp.py +1 -2
- bumble/sdp.py +2 -2
- bumble/smp.py +71 -69
- bumble/tools/rtk_util.py +2 -2
- bumble/transport/__init__.py +2 -16
- bumble/transport/android_netsim.py +5 -5
- bumble/transport/common.py +4 -4
- bumble/transport/pyusb.py +2 -2
- bumble/utils.py +2 -5
- bumble/vendor/android/hci.py +118 -200
- bumble/vendor/zephyr/hci.py +32 -27
- {bumble-0.0.211.dist-info → bumble-0.0.213.dist-info}/METADATA +5 -5
- {bumble-0.0.211.dist-info → bumble-0.0.213.dist-info}/RECORD +92 -93
- {bumble-0.0.211.dist-info → bumble-0.0.213.dist-info}/WHEEL +1 -1
- {bumble-0.0.211.dist-info → bumble-0.0.213.dist-info}/entry_points.txt +0 -1
- bumble/apps/link_relay/__init__.py +0 -0
- bumble/apps/link_relay/link_relay.py +0 -289
- bumble/apps/link_relay/logging.yml +0 -21
- {bumble-0.0.211.dist-info → bumble-0.0.213.dist-info}/licenses/LICENSE +0 -0
- {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
|
|
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:
|
|
216
|
+
subclasses: dict[Frame.OperationCode, type[CommandFrame]] = {}
|
|
217
217
|
ctype: CommandType
|
|
218
218
|
|
|
219
219
|
@staticmethod
|
|
220
|
-
def parse_operands(operands: bytes) ->
|
|
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:
|
|
254
|
+
subclasses: dict[Frame.OperationCode, type[ResponseFrame]] = {}
|
|
255
255
|
response: ResponseCode
|
|
256
256
|
|
|
257
257
|
@staticmethod
|
|
258
|
-
def parse_operands(operands: bytes) ->
|
|
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) ->
|
|
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) ->
|
|
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,
|
|
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:
|
|
149
|
+
command_handlers: dict[int, CommandHandler] # Command handlers, by PID
|
|
150
150
|
ResponseHandler = Callable[[int, Optional[avc.ResponseFrame]], None]
|
|
151
|
-
response_handlers:
|
|
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(
|
|
170
|
-
l2cap_channel.on(
|
|
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[
|
|
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[
|
|
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:
|
|
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) ->
|
|
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[
|
|
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:
|
|
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:
|
|
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,
|
|
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:
|
|
1198
|
-
remote_endpoints:
|
|
1199
|
-
streams:
|
|
1200
|
-
transaction_results:
|
|
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:
|
|
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:
|
|
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(
|
|
1243
|
-
l2cap_channel.on(
|
|
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(
|
|
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(
|
|
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) ->
|
|
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,
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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
|
-
|
|
1623
|
-
|
|
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
|
-
|
|
1641
|
-
|
|
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(
|
|
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.
|
|
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:
|
|
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:
|
|
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(
|
|
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(
|
|
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(
|
|
1742
|
-
channel.on(
|
|
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
|
|
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(
|
|
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(
|
|
1942
|
-
channel.on(
|
|
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
|
-
|
|
2120
|
+
async def start(self) -> None:
|
|
2121
|
+
"""[Source Only] Handles when receiving start command."""
|
|
2085
2122
|
|
|
2086
|
-
async def stop(self):
|
|
2087
|
-
|
|
2123
|
+
async def stop(self) -> None:
|
|
2124
|
+
"""[Source Only] Handles when receiving stop command."""
|
|
2088
2125
|
|
|
2089
|
-
async def close(self):
|
|
2090
|
-
|
|
2126
|
+
async def close(self) -> None:
|
|
2127
|
+
"""[Source Only] Handles when receiving close command."""
|
|
2091
2128
|
|
|
2092
|
-
def on_reconfigure_command(self, command):
|
|
2093
|
-
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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
|
|
2125
|
-
self.emit(
|
|
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
|
|
2128
|
-
self.emit(
|
|
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(
|
|
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(
|
|
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(
|
|
2256
|
+
self.emit(self.EVENT_RTP_PACKET, rtp_packet)
|