bumble 0.0.218__py3-none-any.whl → 0.0.220__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 +57 -18
- bumble/apps/auracast.py +7 -13
- bumble/audio/io.py +3 -3
- bumble/avctp.py +8 -12
- bumble/avdtp.py +584 -533
- bumble/avrcp.py +20 -20
- bumble/controller.py +993 -507
- bumble/device.py +107 -183
- bumble/drivers/rtk.py +3 -1
- bumble/hci.py +38 -0
- bumble/hid.py +1 -1
- bumble/link.py +68 -165
- bumble/lmp.py +324 -0
- bumble/rfcomm.py +7 -3
- bumble/snoop.py +10 -4
- bumble/transport/common.py +6 -3
- bumble/transport/ws_client.py +2 -2
- bumble/transport/ws_server.py +16 -8
- bumble/utils.py +1 -5
- {bumble-0.0.218.dist-info → bumble-0.0.220.dist-info}/METADATA +3 -3
- {bumble-0.0.218.dist-info → bumble-0.0.220.dist-info}/RECORD +26 -25
- {bumble-0.0.218.dist-info → bumble-0.0.220.dist-info}/WHEEL +0 -0
- {bumble-0.0.218.dist-info → bumble-0.0.220.dist-info}/entry_points.txt +0 -0
- {bumble-0.0.218.dist-info → bumble-0.0.220.dist-info}/licenses/LICENSE +0 -0
- {bumble-0.0.218.dist-info → bumble-0.0.220.dist-info}/top_level.txt +0 -0
bumble/avdtp.py
CHANGED
|
@@ -22,36 +22,27 @@ import enum
|
|
|
22
22
|
import logging
|
|
23
23
|
import time
|
|
24
24
|
import warnings
|
|
25
|
+
from collections.abc import AsyncGenerator, Awaitable, Iterable
|
|
26
|
+
from dataclasses import dataclass, field
|
|
25
27
|
from typing import (
|
|
26
28
|
Any,
|
|
27
|
-
AsyncGenerator,
|
|
28
|
-
Awaitable,
|
|
29
29
|
Callable,
|
|
30
|
-
|
|
30
|
+
ClassVar,
|
|
31
31
|
Optional,
|
|
32
32
|
SupportsBytes,
|
|
33
|
+
TypeVar,
|
|
33
34
|
Union,
|
|
34
35
|
cast,
|
|
35
36
|
)
|
|
36
37
|
|
|
37
|
-
from
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
A2DP_MPEG_2_4_AAC_CODEC_TYPE,
|
|
41
|
-
A2DP_NON_A2DP_CODEC_TYPE,
|
|
42
|
-
A2DP_SBC_CODEC_TYPE,
|
|
43
|
-
A2DP_VENDOR_MEDIA_CODEC_INFORMATION_CLASSES,
|
|
44
|
-
AacMediaCodecInformation,
|
|
45
|
-
SbcMediaCodecInformation,
|
|
46
|
-
VendorSpecificMediaCodecInformation,
|
|
47
|
-
)
|
|
38
|
+
from typing_extensions import override
|
|
39
|
+
|
|
40
|
+
from bumble import a2dp, device, hci, l2cap, sdp, utils
|
|
48
41
|
from bumble.colors import color
|
|
49
42
|
from bumble.core import (
|
|
50
43
|
BT_ADVANCED_AUDIO_DISTRIBUTION_SERVICE,
|
|
51
|
-
InvalidArgumentError,
|
|
52
44
|
InvalidStateError,
|
|
53
45
|
ProtocolError,
|
|
54
|
-
name_or_number,
|
|
55
46
|
)
|
|
56
47
|
from bumble.rtp import MediaPacket
|
|
57
48
|
|
|
@@ -72,147 +63,118 @@ AVDTP_PSM = 0x0019
|
|
|
72
63
|
AVDTP_DEFAULT_RTX_SIG_TIMER = 5 # Seconds
|
|
73
64
|
|
|
74
65
|
# Signal Identifiers (AVDTP spec - 8.5 Signal Command Set)
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
AVDTP_DELAY_REPORTING_SERVICE_CATEGORY = 0x08
|
|
188
|
-
|
|
189
|
-
AVDTP_SERVICE_CATEGORY_NAMES = {
|
|
190
|
-
AVDTP_MEDIA_TRANSPORT_SERVICE_CATEGORY: 'AVDTP_MEDIA_TRANSPORT_SERVICE_CATEGORY',
|
|
191
|
-
AVDTP_REPORTING_SERVICE_CATEGORY: 'AVDTP_REPORTING_SERVICE_CATEGORY',
|
|
192
|
-
AVDTP_RECOVERY_SERVICE_CATEGORY: 'AVDTP_RECOVERY_SERVICE_CATEGORY',
|
|
193
|
-
AVDTP_CONTENT_PROTECTION_SERVICE_CATEGORY: 'AVDTP_CONTENT_PROTECTION_SERVICE_CATEGORY',
|
|
194
|
-
AVDTP_HEADER_COMPRESSION_SERVICE_CATEGORY: 'AVDTP_HEADER_COMPRESSION_SERVICE_CATEGORY',
|
|
195
|
-
AVDTP_MULTIPLEXING_SERVICE_CATEGORY: 'AVDTP_MULTIPLEXING_SERVICE_CATEGORY',
|
|
196
|
-
AVDTP_MEDIA_CODEC_SERVICE_CATEGORY: 'AVDTP_MEDIA_CODEC_SERVICE_CATEGORY',
|
|
197
|
-
AVDTP_DELAY_REPORTING_SERVICE_CATEGORY: 'AVDTP_DELAY_REPORTING_SERVICE_CATEGORY'
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
# States (AVDTP spec - 9.1 State Definitions)
|
|
201
|
-
AVDTP_IDLE_STATE = 0x00
|
|
202
|
-
AVDTP_CONFIGURED_STATE = 0x01
|
|
203
|
-
AVDTP_OPEN_STATE = 0x02
|
|
204
|
-
AVDTP_STREAMING_STATE = 0x03
|
|
205
|
-
AVDTP_CLOSING_STATE = 0x04
|
|
206
|
-
AVDTP_ABORTING_STATE = 0x05
|
|
207
|
-
|
|
208
|
-
AVDTP_STATE_NAMES = {
|
|
209
|
-
AVDTP_IDLE_STATE: 'AVDTP_IDLE_STATE',
|
|
210
|
-
AVDTP_CONFIGURED_STATE: 'AVDTP_CONFIGURED_STATE',
|
|
211
|
-
AVDTP_OPEN_STATE: 'AVDTP_OPEN_STATE',
|
|
212
|
-
AVDTP_STREAMING_STATE: 'AVDTP_STREAMING_STATE',
|
|
213
|
-
AVDTP_CLOSING_STATE: 'AVDTP_CLOSING_STATE',
|
|
214
|
-
AVDTP_ABORTING_STATE: 'AVDTP_ABORTING_STATE'
|
|
215
|
-
}
|
|
66
|
+
class SignalIdentifier(hci.SpecableEnum):
|
|
67
|
+
DISCOVER = 0x01
|
|
68
|
+
GET_CAPABILITIES = 0x02
|
|
69
|
+
SET_CONFIGURATION = 0x03
|
|
70
|
+
GET_CONFIGURATION = 0x04
|
|
71
|
+
RECONFIGURE = 0x05
|
|
72
|
+
OPEN = 0x06
|
|
73
|
+
START = 0x07
|
|
74
|
+
CLOSE = 0x08
|
|
75
|
+
SUSPEND = 0x09
|
|
76
|
+
ABORT = 0x0A
|
|
77
|
+
SECURITY_CONTROL = 0x0B
|
|
78
|
+
GET_ALL_CAPABILITIES = 0x0C
|
|
79
|
+
DELAYREPORT = 0x0D
|
|
80
|
+
|
|
81
|
+
AVDTP_DISCOVER = SignalIdentifier.DISCOVER
|
|
82
|
+
AVDTP_GET_CAPABILITIES = SignalIdentifier.GET_CAPABILITIES
|
|
83
|
+
AVDTP_SET_CONFIGURATION = SignalIdentifier.SET_CONFIGURATION
|
|
84
|
+
AVDTP_GET_CONFIGURATION = SignalIdentifier.GET_CONFIGURATION
|
|
85
|
+
AVDTP_RECONFIGURE = SignalIdentifier.RECONFIGURE
|
|
86
|
+
AVDTP_OPEN = SignalIdentifier.OPEN
|
|
87
|
+
AVDTP_START = SignalIdentifier.START
|
|
88
|
+
AVDTP_CLOSE = SignalIdentifier.CLOSE
|
|
89
|
+
AVDTP_SUSPEND = SignalIdentifier.SUSPEND
|
|
90
|
+
AVDTP_ABORT = SignalIdentifier.ABORT
|
|
91
|
+
AVDTP_SECURITY_CONTROL = SignalIdentifier.SECURITY_CONTROL
|
|
92
|
+
AVDTP_GET_ALL_CAPABILITIES = SignalIdentifier.GET_ALL_CAPABILITIES
|
|
93
|
+
AVDTP_DELAYREPORT = SignalIdentifier.DELAYREPORT
|
|
94
|
+
|
|
95
|
+
class ErrorCode(hci.SpecableEnum):
|
|
96
|
+
'''Error codes (AVDTP spec - 8.20.6.2 ERROR_CODE tables)'''
|
|
97
|
+
BAD_HEADER_FORMAT = 0x01
|
|
98
|
+
BAD_LENGTH = 0x11
|
|
99
|
+
BAD_ACP_SEID = 0x12
|
|
100
|
+
SEP_IN_USE = 0x13
|
|
101
|
+
SEP_NOT_IN_USE = 0x14
|
|
102
|
+
BAD_SERV_CATEGORY = 0x17
|
|
103
|
+
BAD_PAYLOAD_FORMAT = 0x18
|
|
104
|
+
NOT_SUPPORTED_COMMAND = 0x19
|
|
105
|
+
INVALID_CAPABILITIES = 0x1A
|
|
106
|
+
BAD_RECOVERY_TYPE = 0x22
|
|
107
|
+
BAD_MEDIA_TRANSPORT_FORMAT = 0x23
|
|
108
|
+
BAD_RECOVERY_FORMAT = 0x25
|
|
109
|
+
BAD_ROHC_FORMAT = 0x26
|
|
110
|
+
BAD_CP_FORMAT = 0x27
|
|
111
|
+
BAD_MULTIPLEXING_FORMAT = 0x28
|
|
112
|
+
UNSUPPORTED_CONFIGURATION = 0x29
|
|
113
|
+
BAD_STATE = 0x31
|
|
114
|
+
|
|
115
|
+
AVDTP_BAD_HEADER_FORMAT_ERROR = ErrorCode.BAD_HEADER_FORMAT
|
|
116
|
+
AVDTP_BAD_LENGTH_ERROR = ErrorCode.BAD_LENGTH
|
|
117
|
+
AVDTP_BAD_ACP_SEID_ERROR = ErrorCode.BAD_ACP_SEID
|
|
118
|
+
AVDTP_SEP_IN_USE_ERROR = ErrorCode.SEP_IN_USE
|
|
119
|
+
AVDTP_SEP_NOT_IN_USE_ERROR = ErrorCode.SEP_NOT_IN_USE
|
|
120
|
+
AVDTP_BAD_SERV_CATEGORY_ERROR = ErrorCode.BAD_SERV_CATEGORY
|
|
121
|
+
AVDTP_BAD_PAYLOAD_FORMAT_ERROR = ErrorCode.BAD_PAYLOAD_FORMAT
|
|
122
|
+
AVDTP_NOT_SUPPORTED_COMMAND_ERROR = ErrorCode.NOT_SUPPORTED_COMMAND
|
|
123
|
+
AVDTP_INVALID_CAPABILITIES_ERROR = ErrorCode.INVALID_CAPABILITIES
|
|
124
|
+
AVDTP_BAD_RECOVERY_TYPE_ERROR = ErrorCode.BAD_RECOVERY_TYPE
|
|
125
|
+
AVDTP_BAD_MEDIA_TRANSPORT_FORMAT_ERROR = ErrorCode.BAD_MEDIA_TRANSPORT_FORMAT
|
|
126
|
+
AVDTP_BAD_RECOVERY_FORMAT_ERROR = ErrorCode.BAD_RECOVERY_FORMAT
|
|
127
|
+
AVDTP_BAD_ROHC_FORMAT_ERROR = ErrorCode.BAD_ROHC_FORMAT
|
|
128
|
+
AVDTP_BAD_CP_FORMAT_ERROR = ErrorCode.BAD_CP_FORMAT
|
|
129
|
+
AVDTP_BAD_MULTIPLEXING_FORMAT_ERROR = ErrorCode.BAD_MULTIPLEXING_FORMAT
|
|
130
|
+
AVDTP_UNSUPPORTED_CONFIGURATION_ERROR = ErrorCode.UNSUPPORTED_CONFIGURATION
|
|
131
|
+
AVDTP_BAD_STATE_ERROR = ErrorCode.BAD_STATE
|
|
132
|
+
|
|
133
|
+
class MediaType(utils.OpenIntEnum):
|
|
134
|
+
AUDIO = 0x00
|
|
135
|
+
VIDEO = 0x01
|
|
136
|
+
MULTIMEDIA = 0x02
|
|
137
|
+
|
|
138
|
+
AVDTP_AUDIO_MEDIA_TYPE = MediaType.AUDIO
|
|
139
|
+
AVDTP_VIDEO_MEDIA_TYPE = MediaType.VIDEO
|
|
140
|
+
AVDTP_MULTIMEDIA_MEDIA_TYPE = MediaType.MULTIMEDIA
|
|
141
|
+
|
|
142
|
+
class StreamEndPointType(utils.OpenIntEnum):
|
|
143
|
+
'''TSEP (AVDTP spec - 8.20.3 Stream End-point Type, Source or Sink (TSEP)).'''
|
|
144
|
+
SRC = 0x00
|
|
145
|
+
SNK = 0x01
|
|
146
|
+
|
|
147
|
+
AVDTP_TSEP_SRC = StreamEndPointType.SRC
|
|
148
|
+
AVDTP_TSEP_SNK = StreamEndPointType.SNK
|
|
149
|
+
|
|
150
|
+
class ServiceCategory(hci.SpecableEnum):
|
|
151
|
+
'''Service Categories (AVDTP spec - Table 8.47: Service Category information element field values).'''
|
|
152
|
+
MEDIA_TRANSPORT = 0x01
|
|
153
|
+
REPORTING = 0x02
|
|
154
|
+
RECOVERY = 0x03
|
|
155
|
+
CONTENT_PROTECTION = 0x04
|
|
156
|
+
HEADER_COMPRESSION = 0x05
|
|
157
|
+
MULTIPLEXING = 0x06
|
|
158
|
+
MEDIA_CODEC = 0x07
|
|
159
|
+
DELAY_REPORTING = 0x08
|
|
160
|
+
|
|
161
|
+
AVDTP_MEDIA_TRANSPORT_SERVICE_CATEGORY = ServiceCategory.MEDIA_TRANSPORT
|
|
162
|
+
AVDTP_REPORTING_SERVICE_CATEGORY = ServiceCategory.REPORTING
|
|
163
|
+
AVDTP_RECOVERY_SERVICE_CATEGORY = ServiceCategory.RECOVERY
|
|
164
|
+
AVDTP_CONTENT_PROTECTION_SERVICE_CATEGORY = ServiceCategory.CONTENT_PROTECTION
|
|
165
|
+
AVDTP_HEADER_COMPRESSION_SERVICE_CATEGORY = ServiceCategory.HEADER_COMPRESSION
|
|
166
|
+
AVDTP_MULTIPLEXING_SERVICE_CATEGORY = ServiceCategory.MULTIPLEXING
|
|
167
|
+
AVDTP_MEDIA_CODEC_SERVICE_CATEGORY = ServiceCategory.MEDIA_CODEC
|
|
168
|
+
AVDTP_DELAY_REPORTING_SERVICE_CATEGORY = ServiceCategory.DELAY_REPORTING
|
|
169
|
+
|
|
170
|
+
class State(utils.OpenIntEnum):
|
|
171
|
+
'''States (AVDTP spec - 9.1 State Definitions)'''
|
|
172
|
+
IDLE = 0x00
|
|
173
|
+
CONFIGURED = 0x01
|
|
174
|
+
OPEN = 0x02
|
|
175
|
+
STREAMING = 0x03
|
|
176
|
+
CLOSING = 0x04
|
|
177
|
+
ABORTING = 0x05
|
|
216
178
|
|
|
217
179
|
# fmt: on
|
|
218
180
|
# pylint: enable=line-too-long
|
|
@@ -335,6 +297,7 @@ class MediaPacketPump:
|
|
|
335
297
|
# -----------------------------------------------------------------------------
|
|
336
298
|
class MessageAssembler:
|
|
337
299
|
message: Optional[bytes]
|
|
300
|
+
signal_identifier: SignalIdentifier
|
|
338
301
|
|
|
339
302
|
def __init__(self, callback: Callable[[int, Message], Any]) -> None:
|
|
340
303
|
self.callback = callback
|
|
@@ -344,7 +307,7 @@ class MessageAssembler:
|
|
|
344
307
|
self.transaction_label = 0
|
|
345
308
|
self.message = None
|
|
346
309
|
self.message_type = Message.MessageType.COMMAND
|
|
347
|
-
self.signal_identifier = 0
|
|
310
|
+
self.signal_identifier = SignalIdentifier(0)
|
|
348
311
|
self.number_of_signal_packets = 0
|
|
349
312
|
self.packet_count = 0
|
|
350
313
|
|
|
@@ -373,7 +336,7 @@ class MessageAssembler:
|
|
|
373
336
|
self.reset()
|
|
374
337
|
|
|
375
338
|
self.transaction_label = transaction_label
|
|
376
|
-
self.signal_identifier = pdu[1] & 0x3F
|
|
339
|
+
self.signal_identifier = SignalIdentifier(pdu[1] & 0x3F)
|
|
377
340
|
self.message_type = message_type
|
|
378
341
|
|
|
379
342
|
if packet_type == Protocol.PacketType.SINGLE_PACKET:
|
|
@@ -429,7 +392,9 @@ class MessageAssembler:
|
|
|
429
392
|
|
|
430
393
|
def on_message_complete(self) -> None:
|
|
431
394
|
message = Message.create(
|
|
432
|
-
self.signal_identifier,
|
|
395
|
+
self.signal_identifier,
|
|
396
|
+
self.message_type,
|
|
397
|
+
self.message or b'',
|
|
433
398
|
)
|
|
434
399
|
try:
|
|
435
400
|
self.callback(self.transaction_label, message)
|
|
@@ -440,150 +405,126 @@ class MessageAssembler:
|
|
|
440
405
|
|
|
441
406
|
|
|
442
407
|
# -----------------------------------------------------------------------------
|
|
408
|
+
@dataclass
|
|
443
409
|
class ServiceCapabilities:
|
|
444
|
-
|
|
410
|
+
METADATA = hci.metadata(
|
|
411
|
+
{
|
|
412
|
+
'parser': lambda data, offset: (
|
|
413
|
+
len(data),
|
|
414
|
+
ServiceCapabilities.parse_capabilities(data[offset:]),
|
|
415
|
+
),
|
|
416
|
+
'serializer': lambda capabilities: ServiceCapabilities.serialize_capabilities(
|
|
417
|
+
capabilities
|
|
418
|
+
),
|
|
419
|
+
}
|
|
420
|
+
)
|
|
421
|
+
service_category: int
|
|
422
|
+
service_capabilities_bytes: bytes = b''
|
|
423
|
+
|
|
424
|
+
@classmethod
|
|
445
425
|
def create(
|
|
446
|
-
service_category: int, service_capabilities_bytes: bytes
|
|
426
|
+
cls, service_category: int, service_capabilities_bytes: bytes
|
|
447
427
|
) -> ServiceCapabilities:
|
|
448
428
|
# Select the appropriate subclass
|
|
449
|
-
cls: type[ServiceCapabilities]
|
|
450
429
|
if service_category == AVDTP_MEDIA_CODEC_SERVICE_CATEGORY:
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
instance = cls.__new__(cls)
|
|
457
|
-
instance.service_category = service_category
|
|
458
|
-
instance.service_capabilities_bytes = service_capabilities_bytes
|
|
459
|
-
instance.init_from_bytes()
|
|
460
|
-
|
|
461
|
-
return instance
|
|
430
|
+
return MediaCodecCapabilities.from_bytes(service_capabilities_bytes)
|
|
431
|
+
return ServiceCapabilities(
|
|
432
|
+
service_category=service_category,
|
|
433
|
+
service_capabilities_bytes=service_capabilities_bytes,
|
|
434
|
+
)
|
|
462
435
|
|
|
463
|
-
@
|
|
464
|
-
def parse_capabilities(payload: bytes) -> list[ServiceCapabilities]:
|
|
436
|
+
@classmethod
|
|
437
|
+
def parse_capabilities(cls, payload: bytes) -> list[ServiceCapabilities]:
|
|
465
438
|
capabilities = []
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
439
|
+
offset = 0
|
|
440
|
+
while offset < len(payload):
|
|
441
|
+
service_category = payload[offset]
|
|
442
|
+
length_of_service_capabilities = payload[offset + 1]
|
|
443
|
+
service_capabilities_bytes = payload[
|
|
444
|
+
offset + 2 : offset + 2 + length_of_service_capabilities
|
|
445
|
+
]
|
|
470
446
|
capabilities.append(
|
|
471
447
|
ServiceCapabilities.create(service_category, service_capabilities_bytes)
|
|
472
448
|
)
|
|
473
|
-
|
|
474
|
-
payload = payload[2 + length_of_service_capabilities :]
|
|
449
|
+
offset += 2 + length_of_service_capabilities
|
|
475
450
|
|
|
476
451
|
return capabilities
|
|
477
452
|
|
|
478
|
-
@
|
|
479
|
-
def serialize_capabilities(
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
return serialized
|
|
487
|
-
|
|
488
|
-
def init_from_bytes(self) -> None:
|
|
489
|
-
pass
|
|
490
|
-
|
|
491
|
-
def __init__(
|
|
492
|
-
self, service_category: int, service_capabilities_bytes: bytes = b''
|
|
493
|
-
) -> None:
|
|
494
|
-
self.service_category = service_category
|
|
495
|
-
self.service_capabilities_bytes = service_capabilities_bytes
|
|
496
|
-
|
|
497
|
-
def to_string(self, details: Optional[list[str]] = None) -> str:
|
|
498
|
-
attributes = ','.join(
|
|
499
|
-
[name_or_number(AVDTP_SERVICE_CATEGORY_NAMES, self.service_category)]
|
|
500
|
-
+ (details or [])
|
|
453
|
+
@classmethod
|
|
454
|
+
def serialize_capabilities(
|
|
455
|
+
cls, capabilities: Iterable[ServiceCapabilities]
|
|
456
|
+
) -> bytes:
|
|
457
|
+
return b''.join(
|
|
458
|
+
bytes([item.service_category, len(item.service_capabilities_bytes)])
|
|
459
|
+
+ item.service_capabilities_bytes
|
|
460
|
+
for item in capabilities
|
|
501
461
|
)
|
|
502
|
-
return f'ServiceCapabilities({attributes})'
|
|
503
|
-
|
|
504
|
-
def __str__(self) -> str:
|
|
505
|
-
if self.service_capabilities_bytes:
|
|
506
|
-
details = [self.service_capabilities_bytes.hex()]
|
|
507
|
-
else:
|
|
508
|
-
details = []
|
|
509
|
-
return self.to_string(details)
|
|
510
462
|
|
|
511
463
|
|
|
512
464
|
# -----------------------------------------------------------------------------
|
|
465
|
+
@dataclass(init=False)
|
|
513
466
|
class MediaCodecCapabilities(ServiceCapabilities):
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
def init_from_bytes(self) -> None:
|
|
519
|
-
self.media_type = self.service_capabilities_bytes[0]
|
|
520
|
-
self.media_codec_type = self.service_capabilities_bytes[1]
|
|
521
|
-
self.media_codec_information = self.service_capabilities_bytes[2:]
|
|
467
|
+
service_category = AVDTP_MEDIA_CODEC_SERVICE_CATEGORY
|
|
468
|
+
# Redeclare this attribute to suppress inheritance error.
|
|
469
|
+
service_capabilities_bytes: bytes
|
|
522
470
|
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
)
|
|
527
|
-
elif self.media_codec_type == A2DP_MPEG_2_4_AAC_CODEC_TYPE:
|
|
528
|
-
self.media_codec_information = AacMediaCodecInformation.from_bytes(
|
|
529
|
-
self.media_codec_information
|
|
530
|
-
)
|
|
531
|
-
elif self.media_codec_type == A2DP_NON_A2DP_CODEC_TYPE:
|
|
532
|
-
vendor_media_codec_information = (
|
|
533
|
-
VendorSpecificMediaCodecInformation.from_bytes(
|
|
534
|
-
self.media_codec_information
|
|
535
|
-
)
|
|
536
|
-
)
|
|
537
|
-
if (
|
|
538
|
-
vendor_class_map := A2DP_VENDOR_MEDIA_CODEC_INFORMATION_CLASSES.get(
|
|
539
|
-
vendor_media_codec_information.vendor_id
|
|
540
|
-
)
|
|
541
|
-
) and (
|
|
542
|
-
media_codec_information_class := vendor_class_map.get(
|
|
543
|
-
vendor_media_codec_information.codec_id
|
|
544
|
-
)
|
|
545
|
-
):
|
|
546
|
-
self.media_codec_information = media_codec_information_class.from_bytes(
|
|
547
|
-
vendor_media_codec_information.value
|
|
548
|
-
)
|
|
549
|
-
else:
|
|
550
|
-
self.media_codec_information = vendor_media_codec_information
|
|
471
|
+
media_type: MediaType
|
|
472
|
+
media_codec_type: a2dp.CodecType
|
|
473
|
+
media_codec_information: Union[bytes, SupportsBytes]
|
|
551
474
|
|
|
475
|
+
# Override init to allow passing service_capabilities_bytes.
|
|
552
476
|
def __init__(
|
|
553
477
|
self,
|
|
554
|
-
media_type:
|
|
555
|
-
media_codec_type:
|
|
478
|
+
media_type: MediaType,
|
|
479
|
+
media_codec_type: a2dp.CodecType,
|
|
556
480
|
media_codec_information: Union[bytes, SupportsBytes],
|
|
481
|
+
service_capabilities_bytes: Optional[bytes] = None,
|
|
557
482
|
) -> None:
|
|
558
|
-
super().__init__(
|
|
559
|
-
AVDTP_MEDIA_CODEC_SERVICE_CATEGORY,
|
|
560
|
-
bytes([media_type, media_codec_type]) + bytes(media_codec_information),
|
|
561
|
-
)
|
|
562
483
|
self.media_type = media_type
|
|
563
484
|
self.media_codec_type = media_codec_type
|
|
564
|
-
self.media_codec_information = media_codec_information
|
|
565
485
|
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
486
|
+
if isinstance(media_codec_information, bytes):
|
|
487
|
+
self.media_codec_information = a2dp.MediaCodecInformation.create(
|
|
488
|
+
media_codec_type, media_codec_information
|
|
489
|
+
)
|
|
490
|
+
else:
|
|
491
|
+
self.media_codec_information = media_codec_information
|
|
572
492
|
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
493
|
+
if service_capabilities_bytes is not None:
|
|
494
|
+
self.service_capabilities_bytes = service_capabilities_bytes
|
|
495
|
+
else:
|
|
496
|
+
self.service_capabilities_bytes = bytes(
|
|
497
|
+
[self.media_type, self.media_codec_type]
|
|
498
|
+
) + bytes(self.media_codec_information)
|
|
499
|
+
|
|
500
|
+
@classmethod
|
|
501
|
+
def from_bytes(cls, data: bytes) -> ServiceCapabilities:
|
|
502
|
+
media_type = MediaType(data[0])
|
|
503
|
+
media_codec_type = a2dp.CodecType(data[1])
|
|
504
|
+
return cls(
|
|
505
|
+
media_type=media_type,
|
|
506
|
+
media_codec_type=media_codec_type,
|
|
507
|
+
media_codec_information=a2dp.MediaCodecInformation.create(
|
|
508
|
+
media_codec_type, data[2:]
|
|
509
|
+
),
|
|
510
|
+
)
|
|
579
511
|
|
|
580
512
|
|
|
581
513
|
# -----------------------------------------------------------------------------
|
|
514
|
+
@dataclass
|
|
582
515
|
class EndPointInfo:
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
516
|
+
seid: int
|
|
517
|
+
in_use: int
|
|
518
|
+
media_type: MediaType
|
|
519
|
+
tsep: StreamEndPointType
|
|
520
|
+
|
|
521
|
+
@classmethod
|
|
522
|
+
def from_bytes(cls, payload: bytes) -> EndPointInfo:
|
|
523
|
+
return cls(
|
|
524
|
+
seid=payload[0] >> 2,
|
|
525
|
+
in_use=payload[0] >> 1 & 1,
|
|
526
|
+
media_type=MediaType(payload[1] >> 4),
|
|
527
|
+
tsep=StreamEndPointType(payload[1] >> 3 & 1),
|
|
587
528
|
)
|
|
588
529
|
|
|
589
530
|
def __bytes__(self) -> bytes:
|
|
@@ -591,98 +532,84 @@ class EndPointInfo:
|
|
|
591
532
|
[self.seid << 2 | self.in_use << 1, self.media_type << 4 | self.tsep << 3]
|
|
592
533
|
)
|
|
593
534
|
|
|
594
|
-
def __init__(self, seid: int, in_use: int, media_type: int, tsep: int) -> None:
|
|
595
|
-
self.seid = seid
|
|
596
|
-
self.in_use = in_use
|
|
597
|
-
self.media_type = media_type
|
|
598
|
-
self.tsep = tsep
|
|
599
|
-
|
|
600
535
|
|
|
601
536
|
# -----------------------------------------------------------------------------
|
|
602
|
-
class Message:
|
|
537
|
+
class Message:
|
|
603
538
|
class MessageType(enum.IntEnum):
|
|
604
539
|
COMMAND = 0
|
|
605
540
|
GENERAL_REJECT = 1
|
|
606
541
|
RESPONSE_ACCEPT = 2
|
|
607
542
|
RESPONSE_REJECT = 3
|
|
608
543
|
|
|
544
|
+
SEID_METADATA = hci.metadata(
|
|
545
|
+
{
|
|
546
|
+
'serializer': lambda seid: bytes([seid << 2]),
|
|
547
|
+
'parser': lambda data, offset: (offset + 1, data[offset] >> 2),
|
|
548
|
+
}
|
|
549
|
+
)
|
|
550
|
+
|
|
609
551
|
# Subclasses, by signal identifier and message type
|
|
610
|
-
subclasses: dict[int, dict[int, type[Message]]] = {}
|
|
611
|
-
message_type: MessageType
|
|
612
|
-
signal_identifier: int
|
|
552
|
+
subclasses: ClassVar[dict[int, dict[int, type[Message]]]] = {}
|
|
613
553
|
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
if name == 'General_Reject':
|
|
619
|
-
subclass.signal_identifier = 0
|
|
620
|
-
signal_identifier_str = None
|
|
621
|
-
message_type = Message.MessageType.COMMAND
|
|
622
|
-
elif name.endswith('_Command'):
|
|
623
|
-
signal_identifier_str = name[:-8]
|
|
624
|
-
message_type = Message.MessageType.COMMAND
|
|
625
|
-
elif name.endswith('_Response'):
|
|
626
|
-
signal_identifier_str = name[:-9]
|
|
627
|
-
message_type = Message.MessageType.RESPONSE_ACCEPT
|
|
628
|
-
elif name.endswith('_Reject'):
|
|
629
|
-
signal_identifier_str = name[:-7]
|
|
630
|
-
message_type = Message.MessageType.RESPONSE_REJECT
|
|
631
|
-
else:
|
|
632
|
-
raise InvalidArgumentError('invalid class name')
|
|
554
|
+
message_type: MessageType
|
|
555
|
+
signal_identifier: SignalIdentifier
|
|
556
|
+
_payload: Optional[bytes] = None
|
|
557
|
+
fields: ClassVar[hci.Fields] = ()
|
|
633
558
|
|
|
634
|
-
|
|
559
|
+
@property
|
|
560
|
+
def payload(self) -> bytes:
|
|
561
|
+
if self._payload is None:
|
|
562
|
+
self._payload = hci.HCI_Object.dict_to_bytes(self.__dict__, self.fields)
|
|
563
|
+
return self._payload
|
|
635
564
|
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
subclass.signal_identifier = signal_identifier
|
|
640
|
-
break
|
|
565
|
+
@payload.setter
|
|
566
|
+
def payload(self, payload: bytes) -> None:
|
|
567
|
+
self._payload = payload
|
|
641
568
|
|
|
642
|
-
|
|
643
|
-
Message.subclasses.setdefault(subclass.signal_identifier, {})[
|
|
644
|
-
subclass.message_type
|
|
645
|
-
] = subclass
|
|
569
|
+
_Message = TypeVar("_Message", bound="Message")
|
|
646
570
|
|
|
571
|
+
@classmethod
|
|
572
|
+
def subclass(cls, subclass: type[_Message]) -> type[_Message]:
|
|
573
|
+
cls.subclasses.setdefault(subclass.signal_identifier, {})[
|
|
574
|
+
subclass.message_type
|
|
575
|
+
] = subclass
|
|
576
|
+
subclass.fields = hci.HCI_Object.fields_from_dataclass(subclass)
|
|
647
577
|
return subclass
|
|
648
578
|
|
|
649
579
|
# Factory method to create a subclass based on the signal identifier and message
|
|
650
580
|
# type
|
|
651
|
-
@
|
|
581
|
+
@classmethod
|
|
652
582
|
def create(
|
|
653
|
-
|
|
583
|
+
cls,
|
|
584
|
+
signal_identifier: SignalIdentifier,
|
|
585
|
+
message_type: MessageType,
|
|
586
|
+
payload: bytes,
|
|
654
587
|
) -> Message:
|
|
588
|
+
instance: Message
|
|
655
589
|
# Look for a registered subclass
|
|
656
|
-
subclasses
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
590
|
+
if (subclasses := Message.subclasses.get(signal_identifier)) and (
|
|
591
|
+
subclass := subclasses.get(message_type)
|
|
592
|
+
):
|
|
593
|
+
instance = subclass(
|
|
594
|
+
**hci.HCI_Object.dict_from_bytes(payload, 0, subclass.fields),
|
|
595
|
+
)
|
|
596
|
+
instance.payload = payload
|
|
597
|
+
return instance
|
|
664
598
|
|
|
665
599
|
# Instantiate the appropriate class based on the message type
|
|
666
600
|
if message_type == Message.MessageType.RESPONSE_REJECT:
|
|
667
601
|
# Assume a simple reject message
|
|
668
|
-
instance = Simple_Reject(payload)
|
|
669
|
-
instance.init_from_payload()
|
|
602
|
+
instance = Simple_Reject(ErrorCode(payload[0]))
|
|
670
603
|
else:
|
|
671
|
-
instance = Message(
|
|
604
|
+
instance = Message()
|
|
605
|
+
instance.payload = payload
|
|
606
|
+
instance.message_type = message_type
|
|
672
607
|
instance.signal_identifier = signal_identifier
|
|
673
|
-
instance.message_type = message_type
|
|
674
608
|
return instance
|
|
675
609
|
|
|
676
|
-
def init_from_payload(self) -> None:
|
|
677
|
-
pass
|
|
678
|
-
|
|
679
|
-
def __init__(self, payload: bytes = b'') -> None:
|
|
680
|
-
self.payload = payload
|
|
681
|
-
|
|
682
610
|
def to_string(self, details: Union[str, Iterable[str]]) -> str:
|
|
683
611
|
base = color(
|
|
684
|
-
f'{
|
|
685
|
-
f'{self.message_type.name}',
|
|
612
|
+
f'{self.signal_identifier.name}_{self.message_type.name}',
|
|
686
613
|
'yellow',
|
|
687
614
|
)
|
|
688
615
|
|
|
@@ -703,68 +630,84 @@ class Message: # pylint:disable=attribute-defined-outside-init
|
|
|
703
630
|
|
|
704
631
|
|
|
705
632
|
# -----------------------------------------------------------------------------
|
|
633
|
+
@dataclass
|
|
706
634
|
class Simple_Command(Message):
|
|
707
635
|
'''
|
|
708
636
|
Command message with just one seid
|
|
709
637
|
'''
|
|
710
638
|
|
|
711
|
-
|
|
712
|
-
self.acp_seid = self.payload[0] >> 2
|
|
639
|
+
message_type = Message.MessageType.COMMAND
|
|
713
640
|
|
|
714
|
-
|
|
715
|
-
super().__init__(payload=bytes([seid << 2]))
|
|
716
|
-
self.acp_seid = seid
|
|
641
|
+
acp_seid: int = field(metadata=Message.SEID_METADATA)
|
|
717
642
|
|
|
718
643
|
def __str__(self) -> str:
|
|
719
644
|
return self.to_string([f'ACP SEID: {self.acp_seid}'])
|
|
720
645
|
|
|
721
646
|
|
|
722
647
|
# -----------------------------------------------------------------------------
|
|
648
|
+
@dataclass
|
|
723
649
|
class Simple_Reject(Message):
|
|
724
650
|
'''
|
|
725
651
|
Reject messages with just an error code
|
|
726
652
|
'''
|
|
727
653
|
|
|
728
|
-
|
|
729
|
-
self.error_code = self.payload[0]
|
|
654
|
+
message_type = Message.MessageType.RESPONSE_REJECT
|
|
730
655
|
|
|
731
|
-
|
|
732
|
-
super().__init__(payload=bytes([error_code]))
|
|
733
|
-
self.error_code = error_code
|
|
656
|
+
error_code: ErrorCode = field(metadata=ErrorCode.type_metadata(1))
|
|
734
657
|
|
|
735
658
|
def __str__(self) -> str:
|
|
736
|
-
details = [f'error_code: {
|
|
659
|
+
details = [f'error_code: {self.error_code.name}']
|
|
737
660
|
return self.to_string(details)
|
|
738
661
|
|
|
739
662
|
|
|
740
663
|
# -----------------------------------------------------------------------------
|
|
741
664
|
@Message.subclass
|
|
665
|
+
@dataclass
|
|
742
666
|
class Discover_Command(Message):
|
|
743
667
|
'''
|
|
744
668
|
See Bluetooth AVDTP spec - 8.6.1 Stream End Point Discovery Command
|
|
745
669
|
'''
|
|
746
670
|
|
|
671
|
+
signal_identifier = AVDTP_DISCOVER
|
|
672
|
+
message_type = Message.MessageType.COMMAND
|
|
673
|
+
|
|
747
674
|
|
|
748
675
|
# -----------------------------------------------------------------------------
|
|
749
676
|
@Message.subclass
|
|
677
|
+
@dataclass
|
|
750
678
|
class Discover_Response(Message):
|
|
751
679
|
'''
|
|
752
680
|
See Bluetooth AVDTP spec - 8.6.2 Stream End Point Discovery Response
|
|
753
681
|
'''
|
|
754
682
|
|
|
755
|
-
|
|
683
|
+
signal_identifier = AVDTP_DISCOVER
|
|
684
|
+
message_type = Message.MessageType.RESPONSE_ACCEPT
|
|
756
685
|
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
)
|
|
686
|
+
@classmethod
|
|
687
|
+
def parse_endpoints(
|
|
688
|
+
cls, data: bytes, offset: int
|
|
689
|
+
) -> tuple[int, list[EndPointInfo]]:
|
|
690
|
+
return len(data), [
|
|
691
|
+
EndPointInfo.from_bytes(data[i * 2 : (i + 1) * 2])
|
|
692
|
+
for i in range(offset, len(data) // 2)
|
|
693
|
+
]
|
|
764
694
|
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
695
|
+
@classmethod
|
|
696
|
+
def serialize_endpoints(cls, endpoints: Iterable[EndPointInfo]) -> bytes:
|
|
697
|
+
return b''.join([bytes(endpoint) for endpoint in endpoints])
|
|
698
|
+
|
|
699
|
+
endpoints: Iterable[EndPointInfo] = field(
|
|
700
|
+
metadata=hci.metadata(
|
|
701
|
+
{
|
|
702
|
+
'parser': lambda data, offset: Discover_Response.parse_endpoints(
|
|
703
|
+
data, offset
|
|
704
|
+
),
|
|
705
|
+
'serializer': lambda endpoints: Discover_Response.serialize_endpoints(
|
|
706
|
+
endpoints
|
|
707
|
+
),
|
|
708
|
+
}
|
|
709
|
+
)
|
|
710
|
+
)
|
|
768
711
|
|
|
769
712
|
def __str__(self) -> str:
|
|
770
713
|
details = []
|
|
@@ -774,8 +717,8 @@ class Discover_Response(Message):
|
|
|
774
717
|
[
|
|
775
718
|
f'ACP SEID: {endpoint.seid}',
|
|
776
719
|
f' in_use: {endpoint.in_use}',
|
|
777
|
-
f' media_type: {
|
|
778
|
-
f' tsep: {
|
|
720
|
+
f' media_type: {endpoint.media_type.name}',
|
|
721
|
+
f' tsep: {endpoint.tsep.name}',
|
|
779
722
|
]
|
|
780
723
|
)
|
|
781
724
|
return self.to_string(details)
|
|
@@ -783,27 +726,30 @@ class Discover_Response(Message):
|
|
|
783
726
|
|
|
784
727
|
# -----------------------------------------------------------------------------
|
|
785
728
|
@Message.subclass
|
|
729
|
+
@dataclass
|
|
786
730
|
class Get_Capabilities_Command(Simple_Command):
|
|
787
731
|
'''
|
|
788
732
|
See Bluetooth AVDTP spec - 8.7.1 Get Capabilities Command
|
|
789
733
|
'''
|
|
790
734
|
|
|
735
|
+
signal_identifier = AVDTP_GET_CAPABILITIES
|
|
736
|
+
message_type = Message.MessageType.COMMAND
|
|
737
|
+
|
|
791
738
|
|
|
792
739
|
# -----------------------------------------------------------------------------
|
|
793
740
|
@Message.subclass
|
|
741
|
+
@dataclass
|
|
794
742
|
class Get_Capabilities_Response(Message):
|
|
795
743
|
'''
|
|
796
744
|
See Bluetooth AVDTP spec - 8.7.2 Get All Capabilities Response
|
|
797
745
|
'''
|
|
798
746
|
|
|
799
|
-
|
|
800
|
-
|
|
747
|
+
signal_identifier = AVDTP_GET_CAPABILITIES
|
|
748
|
+
message_type = Message.MessageType.RESPONSE_ACCEPT
|
|
801
749
|
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
)
|
|
806
|
-
self.capabilities = capabilities
|
|
750
|
+
capabilities: Iterable[ServiceCapabilities] = field(
|
|
751
|
+
metadata=ServiceCapabilities.METADATA
|
|
752
|
+
)
|
|
807
753
|
|
|
808
754
|
def __str__(self) -> str:
|
|
809
755
|
details = [str(capability) for capability in self.capabilities]
|
|
@@ -812,58 +758,68 @@ class Get_Capabilities_Response(Message):
|
|
|
812
758
|
|
|
813
759
|
# -----------------------------------------------------------------------------
|
|
814
760
|
@Message.subclass
|
|
761
|
+
@dataclass
|
|
815
762
|
class Get_Capabilities_Reject(Simple_Reject):
|
|
816
763
|
'''
|
|
817
764
|
See Bluetooth AVDTP spec - 8.7.3 Get Capabilities Reject
|
|
818
765
|
'''
|
|
819
766
|
|
|
767
|
+
signal_identifier = AVDTP_GET_CAPABILITIES
|
|
768
|
+
message_type = Message.MessageType.RESPONSE_REJECT
|
|
769
|
+
|
|
820
770
|
|
|
821
771
|
# -----------------------------------------------------------------------------
|
|
822
772
|
@Message.subclass
|
|
773
|
+
@dataclass
|
|
823
774
|
class Get_All_Capabilities_Command(Get_Capabilities_Command):
|
|
824
775
|
'''
|
|
825
776
|
See Bluetooth AVDTP spec - 8.8.1 Get All Capabilities Command
|
|
826
777
|
'''
|
|
827
778
|
|
|
779
|
+
signal_identifier = AVDTP_GET_ALL_CAPABILITIES
|
|
780
|
+
message_type = Message.MessageType.COMMAND
|
|
781
|
+
|
|
828
782
|
|
|
829
783
|
# -----------------------------------------------------------------------------
|
|
830
784
|
@Message.subclass
|
|
785
|
+
@dataclass
|
|
831
786
|
class Get_All_Capabilities_Response(Get_Capabilities_Response):
|
|
832
787
|
'''
|
|
833
788
|
See Bluetooth AVDTP spec - 8.8.2 Get All Capabilities Response
|
|
834
789
|
'''
|
|
835
790
|
|
|
791
|
+
signal_identifier = AVDTP_GET_ALL_CAPABILITIES
|
|
792
|
+
message_type = Message.MessageType.RESPONSE_ACCEPT
|
|
793
|
+
|
|
836
794
|
|
|
837
795
|
# -----------------------------------------------------------------------------
|
|
838
796
|
@Message.subclass
|
|
797
|
+
@dataclass
|
|
839
798
|
class Get_All_Capabilities_Reject(Simple_Reject):
|
|
840
799
|
'''
|
|
841
800
|
See Bluetooth AVDTP spec - 8.8.3 Get All Capabilities Reject
|
|
842
801
|
'''
|
|
843
802
|
|
|
803
|
+
signal_identifier = AVDTP_GET_ALL_CAPABILITIES
|
|
804
|
+
message_type = Message.MessageType.RESPONSE_REJECT
|
|
805
|
+
|
|
844
806
|
|
|
845
807
|
# -----------------------------------------------------------------------------
|
|
846
808
|
@Message.subclass
|
|
809
|
+
@dataclass
|
|
847
810
|
class Set_Configuration_Command(Message):
|
|
848
811
|
'''
|
|
849
812
|
See Bluetooth AVDTP spec - 8.9.1 Set Configuration Command
|
|
850
813
|
'''
|
|
851
814
|
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
self.int_seid = self.payload[1] >> 2
|
|
855
|
-
self.capabilities = ServiceCapabilities.parse_capabilities(self.payload[2:])
|
|
815
|
+
signal_identifier = AVDTP_SET_CONFIGURATION
|
|
816
|
+
message_type = Message.MessageType.COMMAND
|
|
856
817
|
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
+ ServiceCapabilities.serialize_capabilities(capabilities)
|
|
863
|
-
)
|
|
864
|
-
self.acp_seid = acp_seid
|
|
865
|
-
self.int_seid = int_seid
|
|
866
|
-
self.capabilities = capabilities
|
|
818
|
+
acp_seid: int = field(metadata=Message.SEID_METADATA)
|
|
819
|
+
int_seid: int = field(metadata=Message.SEID_METADATA)
|
|
820
|
+
capabilities: Iterable[ServiceCapabilities] = field(
|
|
821
|
+
metadata=ServiceCapabilities.METADATA
|
|
822
|
+
)
|
|
867
823
|
|
|
868
824
|
def __str__(self) -> str:
|
|
869
825
|
details = [f'ACP SEID: {self.acp_seid}', f'INT SEID: {self.int_seid}'] + [
|
|
@@ -874,65 +830,68 @@ class Set_Configuration_Command(Message):
|
|
|
874
830
|
|
|
875
831
|
# -----------------------------------------------------------------------------
|
|
876
832
|
@Message.subclass
|
|
833
|
+
@dataclass
|
|
877
834
|
class Set_Configuration_Response(Message):
|
|
878
835
|
'''
|
|
879
836
|
See Bluetooth AVDTP spec - 8.9.2 Set Configuration Response
|
|
880
837
|
'''
|
|
881
838
|
|
|
839
|
+
signal_identifier = AVDTP_SET_CONFIGURATION
|
|
840
|
+
message_type = Message.MessageType.RESPONSE_ACCEPT
|
|
841
|
+
|
|
882
842
|
|
|
883
843
|
# -----------------------------------------------------------------------------
|
|
884
844
|
@Message.subclass
|
|
845
|
+
@dataclass
|
|
885
846
|
class Set_Configuration_Reject(Message):
|
|
886
847
|
'''
|
|
887
848
|
See Bluetooth AVDTP spec - 8.9.3 Set Configuration Reject
|
|
888
849
|
'''
|
|
889
850
|
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
self.error_code = self.payload[1]
|
|
851
|
+
signal_identifier = AVDTP_SET_CONFIGURATION
|
|
852
|
+
message_type = Message.MessageType.RESPONSE_REJECT
|
|
893
853
|
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
854
|
+
service_category: ServiceCategory = field(
|
|
855
|
+
metadata=ServiceCategory.type_metadata(1), default=ServiceCategory(0)
|
|
856
|
+
)
|
|
857
|
+
error_code: ErrorCode = field(
|
|
858
|
+
metadata=ErrorCode.type_metadata(1), default=ErrorCode(0)
|
|
859
|
+
)
|
|
898
860
|
|
|
899
861
|
def __str__(self) -> str:
|
|
900
862
|
details = [
|
|
901
|
-
(
|
|
902
|
-
|
|
903
|
-
f'{name_or_number(AVDTP_SERVICE_CATEGORY_NAMES, self.service_category)}'
|
|
904
|
-
),
|
|
905
|
-
(
|
|
906
|
-
'error_code: '
|
|
907
|
-
f'{name_or_number(AVDTP_ERROR_NAMES, self.error_code)}'
|
|
908
|
-
),
|
|
863
|
+
(f'service_category: {self.service_category.name}'),
|
|
864
|
+
(f'error_code: {self.error_code.name}'),
|
|
909
865
|
]
|
|
910
866
|
return self.to_string(details)
|
|
911
867
|
|
|
912
868
|
|
|
913
869
|
# -----------------------------------------------------------------------------
|
|
914
870
|
@Message.subclass
|
|
871
|
+
@dataclass
|
|
915
872
|
class Get_Configuration_Command(Simple_Command):
|
|
916
873
|
'''
|
|
917
874
|
See Bluetooth AVDTP spec - 8.10.1 Get Configuration Command
|
|
918
875
|
'''
|
|
919
876
|
|
|
877
|
+
signal_identifier = AVDTP_GET_CONFIGURATION
|
|
878
|
+
message_type = Message.MessageType.COMMAND
|
|
879
|
+
|
|
920
880
|
|
|
921
881
|
# -----------------------------------------------------------------------------
|
|
922
882
|
@Message.subclass
|
|
883
|
+
@dataclass
|
|
923
884
|
class Get_Configuration_Response(Message):
|
|
924
885
|
'''
|
|
925
886
|
See Bluetooth AVDTP spec - 8.10.2 Get Configuration Response
|
|
926
887
|
'''
|
|
927
888
|
|
|
928
|
-
|
|
929
|
-
|
|
889
|
+
signal_identifier = AVDTP_GET_CONFIGURATION
|
|
890
|
+
message_type = Message.MessageType.RESPONSE_ACCEPT
|
|
930
891
|
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
)
|
|
935
|
-
self.capabilities = capabilities
|
|
892
|
+
capabilities: Iterable[ServiceCapabilities] = field(
|
|
893
|
+
metadata=ServiceCapabilities.METADATA
|
|
894
|
+
)
|
|
936
895
|
|
|
937
896
|
def __str__(self) -> str:
|
|
938
897
|
details = [str(capability) for capability in self.capabilities]
|
|
@@ -941,23 +900,31 @@ class Get_Configuration_Response(Message):
|
|
|
941
900
|
|
|
942
901
|
# -----------------------------------------------------------------------------
|
|
943
902
|
@Message.subclass
|
|
903
|
+
@dataclass
|
|
944
904
|
class Get_Configuration_Reject(Simple_Reject):
|
|
945
905
|
'''
|
|
946
906
|
See Bluetooth AVDTP spec - 8.10.3 Get Configuration Reject
|
|
947
907
|
'''
|
|
948
908
|
|
|
909
|
+
signal_identifier = AVDTP_GET_CONFIGURATION
|
|
910
|
+
message_type = Message.MessageType.RESPONSE_REJECT
|
|
911
|
+
|
|
949
912
|
|
|
950
913
|
# -----------------------------------------------------------------------------
|
|
951
914
|
@Message.subclass
|
|
915
|
+
@dataclass
|
|
952
916
|
class Reconfigure_Command(Message):
|
|
953
917
|
'''
|
|
954
918
|
See Bluetooth AVDTP spec - 8.11.1 Reconfigure Command
|
|
955
919
|
'''
|
|
956
920
|
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
921
|
+
signal_identifier = AVDTP_RECONFIGURE
|
|
922
|
+
message_type = Message.MessageType.COMMAND
|
|
923
|
+
|
|
924
|
+
acp_seid: int = field(metadata=Message.SEID_METADATA)
|
|
925
|
+
capabilities: Iterable[ServiceCapabilities] = field(
|
|
926
|
+
metadata=ServiceCapabilities.METADATA
|
|
927
|
+
)
|
|
961
928
|
|
|
962
929
|
def __str__(self) -> str:
|
|
963
930
|
details = [
|
|
@@ -968,57 +935,86 @@ class Reconfigure_Command(Message):
|
|
|
968
935
|
|
|
969
936
|
# -----------------------------------------------------------------------------
|
|
970
937
|
@Message.subclass
|
|
938
|
+
@dataclass
|
|
971
939
|
class Reconfigure_Response(Message):
|
|
972
940
|
'''
|
|
973
941
|
See Bluetooth AVDTP spec - 8.11.2 Reconfigure Response
|
|
974
942
|
'''
|
|
975
943
|
|
|
944
|
+
signal_identifier = AVDTP_RECONFIGURE
|
|
945
|
+
message_type = Message.MessageType.RESPONSE_ACCEPT
|
|
946
|
+
|
|
976
947
|
|
|
977
948
|
# -----------------------------------------------------------------------------
|
|
978
949
|
@Message.subclass
|
|
950
|
+
@dataclass
|
|
979
951
|
class Reconfigure_Reject(Set_Configuration_Reject):
|
|
980
952
|
'''
|
|
981
953
|
See Bluetooth AVDTP spec - 8.11.3 Reconfigure Reject
|
|
982
954
|
'''
|
|
983
955
|
|
|
956
|
+
signal_identifier = AVDTP_RECONFIGURE
|
|
957
|
+
message_type = Message.MessageType.RESPONSE_REJECT
|
|
958
|
+
|
|
984
959
|
|
|
985
960
|
# -----------------------------------------------------------------------------
|
|
986
961
|
@Message.subclass
|
|
962
|
+
@dataclass
|
|
987
963
|
class Open_Command(Simple_Command):
|
|
988
964
|
'''
|
|
989
965
|
See Bluetooth AVDTP spec - 8.12.1 Open Stream Command
|
|
990
966
|
'''
|
|
991
967
|
|
|
968
|
+
signal_identifier = AVDTP_OPEN
|
|
969
|
+
message_type = Message.MessageType.COMMAND
|
|
970
|
+
|
|
992
971
|
|
|
993
972
|
# -----------------------------------------------------------------------------
|
|
994
973
|
@Message.subclass
|
|
974
|
+
@dataclass
|
|
995
975
|
class Open_Response(Message):
|
|
996
976
|
'''
|
|
997
977
|
See Bluetooth AVDTP spec - 8.12.2 Open Stream Response
|
|
998
978
|
'''
|
|
999
979
|
|
|
980
|
+
signal_identifier = AVDTP_OPEN
|
|
981
|
+
message_type = Message.MessageType.RESPONSE_ACCEPT
|
|
982
|
+
|
|
1000
983
|
|
|
1001
984
|
# -----------------------------------------------------------------------------
|
|
1002
985
|
@Message.subclass
|
|
986
|
+
@dataclass
|
|
1003
987
|
class Open_Reject(Simple_Reject):
|
|
1004
988
|
'''
|
|
1005
989
|
See Bluetooth AVDTP spec - 8.12.3 Open Stream Reject
|
|
1006
990
|
'''
|
|
1007
991
|
|
|
992
|
+
signal_identifier = AVDTP_OPEN
|
|
993
|
+
message_type = Message.MessageType.RESPONSE_REJECT
|
|
994
|
+
|
|
1008
995
|
|
|
1009
996
|
# -----------------------------------------------------------------------------
|
|
1010
997
|
@Message.subclass
|
|
998
|
+
@dataclass
|
|
1011
999
|
class Start_Command(Message):
|
|
1012
1000
|
'''
|
|
1013
1001
|
See Bluetooth AVDTP spec - 8.13.1 Start Stream Command
|
|
1014
1002
|
'''
|
|
1015
1003
|
|
|
1016
|
-
|
|
1017
|
-
|
|
1004
|
+
signal_identifier = AVDTP_START
|
|
1005
|
+
message_type = Message.MessageType.COMMAND
|
|
1018
1006
|
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1007
|
+
acp_seids: Iterable[int] = field(
|
|
1008
|
+
metadata=hci.metadata(
|
|
1009
|
+
{
|
|
1010
|
+
'serializer': lambda seids: bytes([seid << 2 for seid in seids]),
|
|
1011
|
+
'parser': lambda data, offset: (
|
|
1012
|
+
len(data),
|
|
1013
|
+
[x >> 2 for x in data[offset:]],
|
|
1014
|
+
),
|
|
1015
|
+
}
|
|
1016
|
+
)
|
|
1017
|
+
)
|
|
1022
1018
|
|
|
1023
1019
|
def __str__(self) -> str:
|
|
1024
1020
|
return self.to_string([f'ACP SEIDs: {self.acp_seids}'])
|
|
@@ -1026,154 +1022,216 @@ class Start_Command(Message):
|
|
|
1026
1022
|
|
|
1027
1023
|
# -----------------------------------------------------------------------------
|
|
1028
1024
|
@Message.subclass
|
|
1025
|
+
@dataclass
|
|
1029
1026
|
class Start_Response(Message):
|
|
1030
1027
|
'''
|
|
1031
1028
|
See Bluetooth AVDTP spec - 8.13.2 Start Stream Response
|
|
1032
1029
|
'''
|
|
1033
1030
|
|
|
1031
|
+
signal_identifier = AVDTP_START
|
|
1032
|
+
message_type = Message.MessageType.RESPONSE_ACCEPT
|
|
1033
|
+
|
|
1034
1034
|
|
|
1035
1035
|
# -----------------------------------------------------------------------------
|
|
1036
1036
|
@Message.subclass
|
|
1037
|
+
@dataclass
|
|
1037
1038
|
class Start_Reject(Message):
|
|
1038
1039
|
'''
|
|
1039
1040
|
See Bluetooth AVDTP spec - 8.13.3 Set Configuration Reject
|
|
1040
1041
|
'''
|
|
1041
1042
|
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
self.error_code = self.payload[1]
|
|
1043
|
+
signal_identifier = AVDTP_START
|
|
1044
|
+
message_type = Message.MessageType.RESPONSE_REJECT
|
|
1045
1045
|
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
self.acp_seid = acp_seid
|
|
1049
|
-
self.error_code = error_code
|
|
1046
|
+
acp_seid: int = field(metadata=Message.SEID_METADATA)
|
|
1047
|
+
error_code: ErrorCode = field(metadata=ErrorCode.type_metadata(1))
|
|
1050
1048
|
|
|
1051
1049
|
def __str__(self) -> str:
|
|
1052
1050
|
details = [
|
|
1053
1051
|
f'acp_seid: {self.acp_seid}',
|
|
1054
|
-
f'error_code: {
|
|
1052
|
+
f'error_code: {self.error_code.name}',
|
|
1055
1053
|
]
|
|
1056
1054
|
return self.to_string(details)
|
|
1057
1055
|
|
|
1058
1056
|
|
|
1059
1057
|
# -----------------------------------------------------------------------------
|
|
1060
1058
|
@Message.subclass
|
|
1059
|
+
@dataclass
|
|
1061
1060
|
class Close_Command(Simple_Command):
|
|
1062
1061
|
'''
|
|
1063
1062
|
See Bluetooth AVDTP spec - 8.14.1 Close Stream Command
|
|
1064
1063
|
'''
|
|
1065
1064
|
|
|
1065
|
+
signal_identifier = AVDTP_CLOSE
|
|
1066
|
+
message_type = Message.MessageType.COMMAND
|
|
1067
|
+
|
|
1066
1068
|
|
|
1067
1069
|
# -----------------------------------------------------------------------------
|
|
1068
1070
|
@Message.subclass
|
|
1071
|
+
@dataclass
|
|
1069
1072
|
class Close_Response(Message):
|
|
1070
1073
|
'''
|
|
1071
1074
|
See Bluetooth AVDTP spec - 8.14.2 Close Stream Response
|
|
1072
1075
|
'''
|
|
1073
1076
|
|
|
1077
|
+
signal_identifier = AVDTP_CLOSE
|
|
1078
|
+
message_type = Message.MessageType.RESPONSE_ACCEPT
|
|
1079
|
+
|
|
1074
1080
|
|
|
1075
1081
|
# -----------------------------------------------------------------------------
|
|
1076
1082
|
@Message.subclass
|
|
1083
|
+
@dataclass
|
|
1077
1084
|
class Close_Reject(Simple_Reject):
|
|
1078
1085
|
'''
|
|
1079
1086
|
See Bluetooth AVDTP spec - 8.14.3 Close Stream Reject
|
|
1080
1087
|
'''
|
|
1081
1088
|
|
|
1089
|
+
signal_identifier = AVDTP_CLOSE
|
|
1090
|
+
message_type = Message.MessageType.RESPONSE_REJECT
|
|
1091
|
+
|
|
1082
1092
|
|
|
1083
1093
|
# -----------------------------------------------------------------------------
|
|
1084
1094
|
@Message.subclass
|
|
1095
|
+
@dataclass
|
|
1085
1096
|
class Suspend_Command(Start_Command):
|
|
1086
1097
|
'''
|
|
1087
1098
|
See Bluetooth AVDTP spec - 8.15.1 Suspend Command
|
|
1088
1099
|
'''
|
|
1089
1100
|
|
|
1101
|
+
signal_identifier = AVDTP_SUSPEND
|
|
1102
|
+
message_type = Message.MessageType.COMMAND
|
|
1103
|
+
|
|
1090
1104
|
|
|
1091
1105
|
# -----------------------------------------------------------------------------
|
|
1092
1106
|
@Message.subclass
|
|
1107
|
+
@dataclass
|
|
1093
1108
|
class Suspend_Response(Message):
|
|
1094
1109
|
'''
|
|
1095
1110
|
See Bluetooth AVDTP spec - 8.15.2 Suspend Response
|
|
1096
1111
|
'''
|
|
1097
1112
|
|
|
1113
|
+
signal_identifier = AVDTP_SUSPEND
|
|
1114
|
+
message_type = Message.MessageType.RESPONSE_ACCEPT
|
|
1115
|
+
|
|
1098
1116
|
|
|
1099
1117
|
# -----------------------------------------------------------------------------
|
|
1100
1118
|
@Message.subclass
|
|
1119
|
+
@dataclass
|
|
1101
1120
|
class Suspend_Reject(Start_Reject):
|
|
1102
1121
|
'''
|
|
1103
1122
|
See Bluetooth AVDTP spec - 8.15.3 Suspend Reject
|
|
1104
1123
|
'''
|
|
1105
1124
|
|
|
1125
|
+
signal_identifier = AVDTP_SUSPEND
|
|
1126
|
+
message_type = Message.MessageType.RESPONSE_REJECT
|
|
1127
|
+
|
|
1106
1128
|
|
|
1107
1129
|
# -----------------------------------------------------------------------------
|
|
1108
1130
|
@Message.subclass
|
|
1131
|
+
@dataclass
|
|
1109
1132
|
class Abort_Command(Simple_Command):
|
|
1110
1133
|
'''
|
|
1111
1134
|
See Bluetooth AVDTP spec - 8.16.1 Abort Command
|
|
1112
1135
|
'''
|
|
1113
1136
|
|
|
1137
|
+
signal_identifier = AVDTP_ABORT
|
|
1138
|
+
message_type = Message.MessageType.COMMAND
|
|
1139
|
+
|
|
1114
1140
|
|
|
1115
1141
|
# -----------------------------------------------------------------------------
|
|
1116
1142
|
@Message.subclass
|
|
1143
|
+
@dataclass
|
|
1117
1144
|
class Abort_Response(Message):
|
|
1118
1145
|
'''
|
|
1119
1146
|
See Bluetooth AVDTP spec - 8.16.2 Abort Response
|
|
1120
1147
|
'''
|
|
1121
1148
|
|
|
1149
|
+
signal_identifier = AVDTP_ABORT
|
|
1150
|
+
message_type = Message.MessageType.RESPONSE_ACCEPT
|
|
1151
|
+
|
|
1122
1152
|
|
|
1123
1153
|
# -----------------------------------------------------------------------------
|
|
1124
1154
|
@Message.subclass
|
|
1155
|
+
@dataclass
|
|
1125
1156
|
class Security_Control_Command(Message):
|
|
1126
1157
|
'''
|
|
1127
1158
|
See Bluetooth AVDTP spec - 8.17.1 Security Control Command
|
|
1128
1159
|
'''
|
|
1129
1160
|
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1161
|
+
signal_identifier = AVDTP_SECURITY_CONTROL
|
|
1162
|
+
message_type = Message.MessageType.COMMAND
|
|
1163
|
+
|
|
1164
|
+
acp_seid: int = field(metadata=Message.SEID_METADATA)
|
|
1165
|
+
data: bytes = field(metadata=hci.metadata('*'))
|
|
1134
1166
|
|
|
1135
1167
|
def __str__(self) -> str:
|
|
1136
|
-
return self.to_string(
|
|
1168
|
+
return self.to_string(
|
|
1169
|
+
[f'ACP_SEID: {self.acp_seid}', f'data: {self.data.hex()}']
|
|
1170
|
+
)
|
|
1137
1171
|
|
|
1138
1172
|
|
|
1139
1173
|
# -----------------------------------------------------------------------------
|
|
1140
1174
|
@Message.subclass
|
|
1175
|
+
@dataclass
|
|
1141
1176
|
class Security_Control_Response(Message):
|
|
1142
1177
|
'''
|
|
1143
1178
|
See Bluetooth AVDTP spec - 8.17.2 Security Control Response
|
|
1144
1179
|
'''
|
|
1145
1180
|
|
|
1181
|
+
signal_identifier = AVDTP_SECURITY_CONTROL
|
|
1182
|
+
message_type = Message.MessageType.RESPONSE_ACCEPT
|
|
1183
|
+
|
|
1146
1184
|
|
|
1147
1185
|
# -----------------------------------------------------------------------------
|
|
1148
1186
|
@Message.subclass
|
|
1187
|
+
@dataclass
|
|
1149
1188
|
class Security_Control_Reject(Simple_Reject):
|
|
1150
1189
|
'''
|
|
1151
1190
|
See Bluetooth AVDTP spec - 8.17.3 Security Control Reject
|
|
1152
1191
|
'''
|
|
1153
1192
|
|
|
1193
|
+
signal_identifier = AVDTP_SECURITY_CONTROL
|
|
1194
|
+
message_type = Message.MessageType.RESPONSE_REJECT
|
|
1195
|
+
|
|
1154
1196
|
|
|
1155
1197
|
# -----------------------------------------------------------------------------
|
|
1156
1198
|
@Message.subclass
|
|
1199
|
+
@dataclass
|
|
1157
1200
|
class General_Reject(Message):
|
|
1158
1201
|
'''
|
|
1159
1202
|
See Bluetooth AVDTP spec - 8.18 General Reject
|
|
1160
1203
|
'''
|
|
1161
1204
|
|
|
1205
|
+
signal_identifier = SignalIdentifier(0)
|
|
1206
|
+
message_type = Message.MessageType.GENERAL_REJECT
|
|
1207
|
+
|
|
1162
1208
|
def to_string(self, details):
|
|
1163
1209
|
return color('GENERAL_REJECT', 'yellow')
|
|
1164
1210
|
|
|
1165
1211
|
|
|
1166
1212
|
# -----------------------------------------------------------------------------
|
|
1167
1213
|
@Message.subclass
|
|
1214
|
+
@dataclass
|
|
1168
1215
|
class DelayReport_Command(Message):
|
|
1169
1216
|
'''
|
|
1170
1217
|
See Bluetooth AVDTP spec - 8.19.1 Delay Report Command
|
|
1171
1218
|
'''
|
|
1172
1219
|
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1220
|
+
signal_identifier = AVDTP_DELAYREPORT
|
|
1221
|
+
message_type = Message.MessageType.COMMAND
|
|
1222
|
+
|
|
1223
|
+
DELAY_METADATA = hci.metadata(
|
|
1224
|
+
{
|
|
1225
|
+
'serializer': lambda delay: bytes([delay >> 8, delay & 0xFF]),
|
|
1226
|
+
'parser': lambda data, offset: (
|
|
1227
|
+
offset + 2,
|
|
1228
|
+
(data[offset] << 8) | (data[offset + 1]),
|
|
1229
|
+
),
|
|
1230
|
+
}
|
|
1231
|
+
)
|
|
1232
|
+
|
|
1233
|
+
acp_seid: int = field(metadata=Message.SEID_METADATA)
|
|
1234
|
+
delay: int = field(metadata=DELAY_METADATA)
|
|
1177
1235
|
|
|
1178
1236
|
def __str__(self) -> str:
|
|
1179
1237
|
return self.to_string([f'ACP_SEID: {self.acp_seid}', f'delay: {self.delay}'])
|
|
@@ -1181,19 +1239,27 @@ class DelayReport_Command(Message):
|
|
|
1181
1239
|
|
|
1182
1240
|
# -----------------------------------------------------------------------------
|
|
1183
1241
|
@Message.subclass
|
|
1242
|
+
@dataclass
|
|
1184
1243
|
class DelayReport_Response(Message):
|
|
1185
1244
|
'''
|
|
1186
1245
|
See Bluetooth AVDTP spec - 8.19.2 Delay Report Response
|
|
1187
1246
|
'''
|
|
1188
1247
|
|
|
1248
|
+
signal_identifier = AVDTP_DELAYREPORT
|
|
1249
|
+
message_type = Message.MessageType.RESPONSE_ACCEPT
|
|
1250
|
+
|
|
1189
1251
|
|
|
1190
1252
|
# -----------------------------------------------------------------------------
|
|
1191
1253
|
@Message.subclass
|
|
1254
|
+
@dataclass
|
|
1192
1255
|
class DelayReport_Reject(Simple_Reject):
|
|
1193
1256
|
'''
|
|
1194
1257
|
See Bluetooth AVDTP spec - 8.19.3 Delay Report Reject
|
|
1195
1258
|
'''
|
|
1196
1259
|
|
|
1260
|
+
signal_identifier = AVDTP_DELAYREPORT
|
|
1261
|
+
message_type = Message.MessageType.RESPONSE_REJECT
|
|
1262
|
+
|
|
1197
1263
|
|
|
1198
1264
|
# -----------------------------------------------------------------------------
|
|
1199
1265
|
class Protocol(utils.EventEmitter):
|
|
@@ -1202,6 +1268,7 @@ class Protocol(utils.EventEmitter):
|
|
|
1202
1268
|
streams: dict[int, Stream]
|
|
1203
1269
|
transaction_results: list[Optional[asyncio.Future[Message]]]
|
|
1204
1270
|
channel_connector: Callable[[], Awaitable[l2cap.ClassicChannel]]
|
|
1271
|
+
channel_acceptor: Optional[Stream]
|
|
1205
1272
|
|
|
1206
1273
|
EVENT_OPEN = "open"
|
|
1207
1274
|
EVENT_CLOSE = "close"
|
|
@@ -1212,10 +1279,6 @@ class Protocol(utils.EventEmitter):
|
|
|
1212
1279
|
CONTINUE_PACKET = 2
|
|
1213
1280
|
END_PACKET = 3
|
|
1214
1281
|
|
|
1215
|
-
@staticmethod
|
|
1216
|
-
def packet_type_name(packet_type):
|
|
1217
|
-
return name_or_number(Protocol.PACKET_TYPE_NAMES, packet_type)
|
|
1218
|
-
|
|
1219
1282
|
@staticmethod
|
|
1220
1283
|
async def connect(
|
|
1221
1284
|
connection: device.Connection, version: tuple[int, int] = (1, 3)
|
|
@@ -1348,7 +1411,7 @@ class Protocol(utils.EventEmitter):
|
|
|
1348
1411
|
):
|
|
1349
1412
|
if isinstance(
|
|
1350
1413
|
codec_capabilities.media_codec_information,
|
|
1351
|
-
VendorSpecificMediaCodecInformation,
|
|
1414
|
+
a2dp.VendorSpecificMediaCodecInformation,
|
|
1352
1415
|
):
|
|
1353
1416
|
if (
|
|
1354
1417
|
codec_capabilities.media_codec_information.vendor_id
|
|
@@ -1388,11 +1451,7 @@ class Protocol(utils.EventEmitter):
|
|
|
1388
1451
|
|
|
1389
1452
|
if message.message_type == Message.MessageType.COMMAND:
|
|
1390
1453
|
# Command
|
|
1391
|
-
signal_name = (
|
|
1392
|
-
AVDTP_SIGNAL_NAMES.get(message.signal_identifier, "")
|
|
1393
|
-
.replace("AVDTP_", "")
|
|
1394
|
-
.lower()
|
|
1395
|
-
)
|
|
1454
|
+
signal_name = message.signal_identifier.name.lower()
|
|
1396
1455
|
handler_name = f'on_{signal_name}_command'
|
|
1397
1456
|
handler = getattr(self, handler_name, None)
|
|
1398
1457
|
if handler:
|
|
@@ -1575,11 +1634,11 @@ class Protocol(utils.EventEmitter):
|
|
|
1575
1634
|
) -> Optional[Message]:
|
|
1576
1635
|
endpoint = self.get_local_endpoint_by_seid(command.acp_seid)
|
|
1577
1636
|
if endpoint is None:
|
|
1578
|
-
return Set_Configuration_Reject(AVDTP_BAD_ACP_SEID_ERROR)
|
|
1637
|
+
return Set_Configuration_Reject(error_code=AVDTP_BAD_ACP_SEID_ERROR)
|
|
1579
1638
|
|
|
1580
1639
|
# Check that the local endpoint isn't in use
|
|
1581
1640
|
if endpoint.in_use:
|
|
1582
|
-
return Set_Configuration_Reject(AVDTP_SEP_IN_USE_ERROR)
|
|
1641
|
+
return Set_Configuration_Reject(error_code=AVDTP_SEP_IN_USE_ERROR)
|
|
1583
1642
|
|
|
1584
1643
|
# Create a stream object for the pair of endpoints
|
|
1585
1644
|
stream = Stream(self, endpoint, StreamEndPointProxy(self, command.int_seid))
|
|
@@ -1602,9 +1661,9 @@ class Protocol(utils.EventEmitter):
|
|
|
1602
1661
|
def on_reconfigure_command(self, command: Reconfigure_Command) -> Optional[Message]:
|
|
1603
1662
|
endpoint = self.get_local_endpoint_by_seid(command.acp_seid)
|
|
1604
1663
|
if endpoint is None:
|
|
1605
|
-
return Reconfigure_Reject(
|
|
1664
|
+
return Reconfigure_Reject(error_code=AVDTP_BAD_ACP_SEID_ERROR)
|
|
1606
1665
|
if endpoint.stream is None:
|
|
1607
|
-
return Reconfigure_Reject(
|
|
1666
|
+
return Reconfigure_Reject(error_code=AVDTP_BAD_STATE_ERROR)
|
|
1608
1667
|
|
|
1609
1668
|
result = endpoint.stream.on_reconfigure_command(command.capabilities)
|
|
1610
1669
|
return result or Reconfigure_Response()
|
|
@@ -1768,12 +1827,8 @@ class Stream:
|
|
|
1768
1827
|
|
|
1769
1828
|
rtp_channel: Optional[l2cap.ClassicChannel]
|
|
1770
1829
|
|
|
1771
|
-
|
|
1772
|
-
|
|
1773
|
-
return name_or_number(AVDTP_STATE_NAMES, state)
|
|
1774
|
-
|
|
1775
|
-
def change_state(self, state: int) -> None:
|
|
1776
|
-
logger.debug(f'{self} state change -> {color(self.state_name(state), "cyan")}')
|
|
1830
|
+
def change_state(self, state: State) -> None:
|
|
1831
|
+
logger.debug(f'{self} state change -> {color(state.name, "cyan")}')
|
|
1777
1832
|
self.state = state
|
|
1778
1833
|
|
|
1779
1834
|
def send_media_packet(self, packet: MediaPacket) -> None:
|
|
@@ -1781,22 +1836,22 @@ class Stream:
|
|
|
1781
1836
|
self.rtp_channel.send_pdu(bytes(packet))
|
|
1782
1837
|
|
|
1783
1838
|
async def configure(self) -> None:
|
|
1784
|
-
if self.state !=
|
|
1839
|
+
if self.state != State.IDLE:
|
|
1785
1840
|
raise InvalidStateError('current state is not IDLE')
|
|
1786
1841
|
|
|
1787
1842
|
await self.remote_endpoint.set_configuration(
|
|
1788
1843
|
self.local_endpoint.seid, self.local_endpoint.configuration
|
|
1789
1844
|
)
|
|
1790
|
-
self.change_state(
|
|
1845
|
+
self.change_state(State.CONFIGURED)
|
|
1791
1846
|
|
|
1792
1847
|
async def open(self) -> None:
|
|
1793
|
-
if self.state !=
|
|
1848
|
+
if self.state != State.CONFIGURED:
|
|
1794
1849
|
raise InvalidStateError('current state is not CONFIGURED')
|
|
1795
1850
|
|
|
1796
1851
|
logger.debug('opening remote endpoint')
|
|
1797
1852
|
await self.remote_endpoint.open()
|
|
1798
1853
|
|
|
1799
|
-
self.change_state(
|
|
1854
|
+
self.change_state(State.OPEN)
|
|
1800
1855
|
|
|
1801
1856
|
# Create a channel for RTP packets
|
|
1802
1857
|
self.rtp_channel = (
|
|
@@ -1808,10 +1863,10 @@ class Stream:
|
|
|
1808
1863
|
async def start(self) -> None:
|
|
1809
1864
|
"""[Source] Start streaming."""
|
|
1810
1865
|
# Auto-open if needed
|
|
1811
|
-
if self.state ==
|
|
1866
|
+
if self.state == State.CONFIGURED:
|
|
1812
1867
|
await self.open()
|
|
1813
1868
|
|
|
1814
|
-
if self.state !=
|
|
1869
|
+
if self.state != State.OPEN:
|
|
1815
1870
|
raise InvalidStateError('current state is not OPEN')
|
|
1816
1871
|
|
|
1817
1872
|
logger.debug('starting remote endpoint')
|
|
@@ -1820,11 +1875,11 @@ class Stream:
|
|
|
1820
1875
|
logger.debug('starting local endpoint')
|
|
1821
1876
|
await self.local_endpoint.start()
|
|
1822
1877
|
|
|
1823
|
-
self.change_state(
|
|
1878
|
+
self.change_state(State.STREAMING)
|
|
1824
1879
|
|
|
1825
1880
|
async def stop(self) -> None:
|
|
1826
1881
|
"""[Source] Stop streaming and transit to OPEN state."""
|
|
1827
|
-
if self.state !=
|
|
1882
|
+
if self.state != State.STREAMING:
|
|
1828
1883
|
raise InvalidStateError('current state is not STREAMING')
|
|
1829
1884
|
|
|
1830
1885
|
logger.debug('stopping local endpoint')
|
|
@@ -1833,11 +1888,11 @@ class Stream:
|
|
|
1833
1888
|
logger.debug('stopping remote endpoint')
|
|
1834
1889
|
await self.remote_endpoint.stop()
|
|
1835
1890
|
|
|
1836
|
-
self.change_state(
|
|
1891
|
+
self.change_state(State.OPEN)
|
|
1837
1892
|
|
|
1838
1893
|
async def close(self) -> None:
|
|
1839
1894
|
"""[Source] Close channel and transit to IDLE state."""
|
|
1840
|
-
if self.state not in (
|
|
1895
|
+
if self.state not in (State.OPEN, State.STREAMING):
|
|
1841
1896
|
raise InvalidStateError('current state is not OPEN or STREAMING')
|
|
1842
1897
|
|
|
1843
1898
|
logger.debug('closing local endpoint')
|
|
@@ -1847,7 +1902,7 @@ class Stream:
|
|
|
1847
1902
|
await self.remote_endpoint.close()
|
|
1848
1903
|
|
|
1849
1904
|
# Release any channels we may have created
|
|
1850
|
-
self.change_state(
|
|
1905
|
+
self.change_state(State.CLOSING)
|
|
1851
1906
|
if self.rtp_channel:
|
|
1852
1907
|
await self.rtp_channel.disconnect()
|
|
1853
1908
|
self.rtp_channel = None
|
|
@@ -1855,32 +1910,36 @@ class Stream:
|
|
|
1855
1910
|
# Release the endpoint
|
|
1856
1911
|
self.local_endpoint.in_use = 0
|
|
1857
1912
|
|
|
1858
|
-
self.change_state(
|
|
1913
|
+
self.change_state(State.IDLE)
|
|
1859
1914
|
|
|
1860
|
-
def on_set_configuration_command(
|
|
1861
|
-
|
|
1862
|
-
|
|
1915
|
+
def on_set_configuration_command(
|
|
1916
|
+
self, configuration: Iterable[ServiceCapabilities]
|
|
1917
|
+
) -> Optional[Message]:
|
|
1918
|
+
if self.state != State.IDLE:
|
|
1919
|
+
return Set_Configuration_Reject(error_code=AVDTP_BAD_STATE_ERROR)
|
|
1863
1920
|
|
|
1864
1921
|
result = self.local_endpoint.on_set_configuration_command(configuration)
|
|
1865
1922
|
if result is not None:
|
|
1866
1923
|
return result
|
|
1867
1924
|
|
|
1868
|
-
self.change_state(
|
|
1925
|
+
self.change_state(State.CONFIGURED)
|
|
1869
1926
|
return None
|
|
1870
1927
|
|
|
1871
|
-
def on_get_configuration_command(self):
|
|
1928
|
+
def on_get_configuration_command(self) -> Optional[Message]:
|
|
1872
1929
|
if self.state not in (
|
|
1873
|
-
|
|
1874
|
-
|
|
1875
|
-
|
|
1930
|
+
State.CONFIGURED,
|
|
1931
|
+
State.OPEN,
|
|
1932
|
+
State.STREAMING,
|
|
1876
1933
|
):
|
|
1877
|
-
return Get_Configuration_Reject(AVDTP_BAD_STATE_ERROR)
|
|
1934
|
+
return Get_Configuration_Reject(error_code=AVDTP_BAD_STATE_ERROR)
|
|
1878
1935
|
|
|
1879
1936
|
return self.local_endpoint.on_get_configuration_command()
|
|
1880
1937
|
|
|
1881
|
-
def on_reconfigure_command(
|
|
1882
|
-
|
|
1883
|
-
|
|
1938
|
+
def on_reconfigure_command(
|
|
1939
|
+
self, configuration: Iterable[ServiceCapabilities]
|
|
1940
|
+
) -> Optional[Message]:
|
|
1941
|
+
if self.state != State.OPEN:
|
|
1942
|
+
return Reconfigure_Reject(error_code=AVDTP_BAD_STATE_ERROR)
|
|
1884
1943
|
|
|
1885
1944
|
result = self.local_endpoint.on_reconfigure_command(configuration)
|
|
1886
1945
|
if result is not None:
|
|
@@ -1888,8 +1947,8 @@ class Stream:
|
|
|
1888
1947
|
|
|
1889
1948
|
return None
|
|
1890
1949
|
|
|
1891
|
-
def on_open_command(self):
|
|
1892
|
-
if self.state !=
|
|
1950
|
+
def on_open_command(self) -> Optional[Message]:
|
|
1951
|
+
if self.state != State.CONFIGURED:
|
|
1893
1952
|
return Open_Reject(AVDTP_BAD_STATE_ERROR)
|
|
1894
1953
|
|
|
1895
1954
|
result = self.local_endpoint.on_open_command()
|
|
@@ -1899,11 +1958,11 @@ class Stream:
|
|
|
1899
1958
|
# Register to accept the next channel
|
|
1900
1959
|
self.protocol.channel_acceptor = self
|
|
1901
1960
|
|
|
1902
|
-
self.change_state(
|
|
1961
|
+
self.change_state(State.OPEN)
|
|
1903
1962
|
return None
|
|
1904
1963
|
|
|
1905
|
-
def on_start_command(self):
|
|
1906
|
-
if self.state !=
|
|
1964
|
+
def on_start_command(self) -> Optional[Message]:
|
|
1965
|
+
if self.state != State.OPEN:
|
|
1907
1966
|
return Open_Reject(AVDTP_BAD_STATE_ERROR)
|
|
1908
1967
|
|
|
1909
1968
|
# Check that we have an RTP channel
|
|
@@ -1915,46 +1974,47 @@ class Stream:
|
|
|
1915
1974
|
if result is not None:
|
|
1916
1975
|
return result
|
|
1917
1976
|
|
|
1918
|
-
self.change_state(
|
|
1977
|
+
self.change_state(State.STREAMING)
|
|
1919
1978
|
return None
|
|
1920
1979
|
|
|
1921
|
-
def on_suspend_command(self):
|
|
1922
|
-
if self.state !=
|
|
1980
|
+
def on_suspend_command(self) -> Optional[Message]:
|
|
1981
|
+
if self.state != State.STREAMING:
|
|
1923
1982
|
return Open_Reject(AVDTP_BAD_STATE_ERROR)
|
|
1924
1983
|
|
|
1925
1984
|
result = self.local_endpoint.on_suspend_command()
|
|
1926
1985
|
if result is not None:
|
|
1927
1986
|
return result
|
|
1928
1987
|
|
|
1929
|
-
self.change_state(
|
|
1988
|
+
self.change_state(State.OPEN)
|
|
1930
1989
|
return None
|
|
1931
1990
|
|
|
1932
|
-
def on_close_command(self):
|
|
1933
|
-
if self.state not in (
|
|
1991
|
+
def on_close_command(self) -> Optional[Message]:
|
|
1992
|
+
if self.state not in (State.OPEN, State.STREAMING):
|
|
1934
1993
|
return Open_Reject(AVDTP_BAD_STATE_ERROR)
|
|
1935
1994
|
|
|
1936
1995
|
result = self.local_endpoint.on_close_command()
|
|
1937
1996
|
if result is not None:
|
|
1938
1997
|
return result
|
|
1939
1998
|
|
|
1940
|
-
self.change_state(
|
|
1999
|
+
self.change_state(State.CLOSING)
|
|
1941
2000
|
|
|
1942
2001
|
if self.rtp_channel is None:
|
|
1943
2002
|
# No channel to release, we're done
|
|
1944
|
-
self.change_state(
|
|
2003
|
+
self.change_state(State.IDLE)
|
|
1945
2004
|
else:
|
|
1946
2005
|
# TODO: set a timer as we wait for the RTP channel to be closed
|
|
1947
2006
|
pass
|
|
1948
2007
|
|
|
1949
2008
|
return None
|
|
1950
2009
|
|
|
1951
|
-
def on_abort_command(self):
|
|
2010
|
+
def on_abort_command(self) -> Optional[Message]:
|
|
1952
2011
|
if self.rtp_channel is None:
|
|
1953
2012
|
# No need to wait
|
|
1954
|
-
self.change_state(
|
|
2013
|
+
self.change_state(State.IDLE)
|
|
1955
2014
|
else:
|
|
1956
2015
|
# Wait for the RTP channel to be closed
|
|
1957
|
-
self.change_state(
|
|
2016
|
+
self.change_state(State.ABORTING)
|
|
2017
|
+
return None
|
|
1958
2018
|
|
|
1959
2019
|
def on_l2cap_connection(self, channel: l2cap.ClassicChannel) -> None:
|
|
1960
2020
|
logger.debug(color('<<< stream channel connected', 'magenta'))
|
|
@@ -1975,8 +2035,8 @@ class Stream:
|
|
|
1975
2035
|
self.local_endpoint.in_use = 0
|
|
1976
2036
|
self.rtp_channel = None
|
|
1977
2037
|
|
|
1978
|
-
if self.state in (
|
|
1979
|
-
self.change_state(
|
|
2038
|
+
if self.state in (State.CLOSING, State.ABORTING):
|
|
2039
|
+
self.change_state(State.IDLE)
|
|
1980
2040
|
else:
|
|
1981
2041
|
logger.warning('unexpected channel close while not CLOSING or ABORTING')
|
|
1982
2042
|
|
|
@@ -1994,7 +2054,7 @@ class Stream:
|
|
|
1994
2054
|
self.local_endpoint = local_endpoint
|
|
1995
2055
|
self.remote_endpoint = remote_endpoint
|
|
1996
2056
|
self.rtp_channel = None
|
|
1997
|
-
self.state =
|
|
2057
|
+
self.state = State.IDLE
|
|
1998
2058
|
|
|
1999
2059
|
local_endpoint.stream = self
|
|
2000
2060
|
local_endpoint.in_use = 1
|
|
@@ -2002,42 +2062,18 @@ class Stream:
|
|
|
2002
2062
|
def __str__(self) -> str:
|
|
2003
2063
|
return (
|
|
2004
2064
|
f'Stream({self.local_endpoint.seid} -> '
|
|
2005
|
-
f'{self.remote_endpoint.seid} {self.
|
|
2065
|
+
f'{self.remote_endpoint.seid} {self.state.name})'
|
|
2006
2066
|
)
|
|
2007
2067
|
|
|
2008
2068
|
|
|
2009
2069
|
# -----------------------------------------------------------------------------
|
|
2070
|
+
@dataclass
|
|
2010
2071
|
class StreamEndPoint:
|
|
2011
|
-
|
|
2012
|
-
|
|
2013
|
-
|
|
2014
|
-
|
|
2015
|
-
|
|
2016
|
-
in_use: int,
|
|
2017
|
-
capabilities: Iterable[ServiceCapabilities],
|
|
2018
|
-
) -> None:
|
|
2019
|
-
self.seid = seid
|
|
2020
|
-
self.media_type = media_type
|
|
2021
|
-
self.tsep = tsep
|
|
2022
|
-
self.in_use = in_use
|
|
2023
|
-
self.capabilities = capabilities
|
|
2024
|
-
|
|
2025
|
-
def __str__(self) -> str:
|
|
2026
|
-
media_type = f'{name_or_number(AVDTP_MEDIA_TYPE_NAMES, self.media_type)}'
|
|
2027
|
-
tsep = f'{name_or_number(AVDTP_TSEP_NAMES, self.tsep)}'
|
|
2028
|
-
return '\n'.join(
|
|
2029
|
-
[
|
|
2030
|
-
'SEP(',
|
|
2031
|
-
f' seid={self.seid}',
|
|
2032
|
-
f' media_type={media_type}',
|
|
2033
|
-
f' tsep={tsep}',
|
|
2034
|
-
f' in_use={self.in_use}',
|
|
2035
|
-
' capabilities=[',
|
|
2036
|
-
'\n'.join([f' {x}' for x in self.capabilities]),
|
|
2037
|
-
' ]',
|
|
2038
|
-
')',
|
|
2039
|
-
]
|
|
2040
|
-
)
|
|
2072
|
+
seid: int
|
|
2073
|
+
media_type: MediaType
|
|
2074
|
+
tsep: StreamEndPointType
|
|
2075
|
+
in_use: int
|
|
2076
|
+
capabilities: Iterable[ServiceCapabilities]
|
|
2041
2077
|
|
|
2042
2078
|
|
|
2043
2079
|
# -----------------------------------------------------------------------------
|
|
@@ -2073,8 +2109,8 @@ class DiscoveredStreamEndPoint(StreamEndPoint, StreamEndPointProxy):
|
|
|
2073
2109
|
self,
|
|
2074
2110
|
protocol: Protocol,
|
|
2075
2111
|
seid: int,
|
|
2076
|
-
media_type:
|
|
2077
|
-
tsep:
|
|
2112
|
+
media_type: MediaType,
|
|
2113
|
+
tsep: StreamEndPointType,
|
|
2078
2114
|
in_use: int,
|
|
2079
2115
|
capabilities: Iterable[ServiceCapabilities],
|
|
2080
2116
|
) -> None:
|
|
@@ -2103,8 +2139,8 @@ class LocalStreamEndPoint(StreamEndPoint, utils.EventEmitter):
|
|
|
2103
2139
|
self,
|
|
2104
2140
|
protocol: Protocol,
|
|
2105
2141
|
seid: int,
|
|
2106
|
-
media_type:
|
|
2107
|
-
tsep:
|
|
2142
|
+
media_type: MediaType,
|
|
2143
|
+
tsep: StreamEndPointType,
|
|
2108
2144
|
capabilities: Iterable[ServiceCapabilities],
|
|
2109
2145
|
configuration: Optional[Iterable[ServiceCapabilities]] = None,
|
|
2110
2146
|
):
|
|
@@ -2123,10 +2159,15 @@ class LocalStreamEndPoint(StreamEndPoint, utils.EventEmitter):
|
|
|
2123
2159
|
async def close(self) -> None:
|
|
2124
2160
|
"""[Source Only] Handles when receiving close command."""
|
|
2125
2161
|
|
|
2126
|
-
def on_reconfigure_command(
|
|
2162
|
+
def on_reconfigure_command(
|
|
2163
|
+
self, command: Iterable[ServiceCapabilities]
|
|
2164
|
+
) -> Optional[Message]:
|
|
2165
|
+
del command # unused.
|
|
2127
2166
|
return None
|
|
2128
2167
|
|
|
2129
|
-
def on_set_configuration_command(
|
|
2168
|
+
def on_set_configuration_command(
|
|
2169
|
+
self, configuration: Iterable[ServiceCapabilities]
|
|
2170
|
+
) -> Optional[Message]:
|
|
2130
2171
|
logger.debug(
|
|
2131
2172
|
'<<< received configuration: '
|
|
2132
2173
|
f'{",".join([str(capability) for capability in configuration])}'
|
|
@@ -2182,13 +2223,13 @@ class LocalSource(LocalStreamEndPoint):
|
|
|
2182
2223
|
protocol: Protocol,
|
|
2183
2224
|
seid: int,
|
|
2184
2225
|
codec_capabilities: MediaCodecCapabilities,
|
|
2185
|
-
|
|
2226
|
+
other_capabilities: Iterable[ServiceCapabilities],
|
|
2186
2227
|
packet_pump: MediaPacketPump,
|
|
2187
2228
|
) -> None:
|
|
2188
2229
|
capabilities = [
|
|
2189
2230
|
ServiceCapabilities(AVDTP_MEDIA_TRANSPORT_SERVICE_CATEGORY),
|
|
2190
2231
|
codec_capabilities,
|
|
2191
|
-
] + list(
|
|
2232
|
+
] + list(other_capabilities)
|
|
2192
2233
|
super().__init__(
|
|
2193
2234
|
protocol,
|
|
2194
2235
|
seid,
|
|
@@ -2199,23 +2240,29 @@ class LocalSource(LocalStreamEndPoint):
|
|
|
2199
2240
|
)
|
|
2200
2241
|
self.packet_pump = packet_pump
|
|
2201
2242
|
|
|
2243
|
+
@override
|
|
2202
2244
|
async def start(self) -> None:
|
|
2203
2245
|
if self.packet_pump and self.stream and self.stream.rtp_channel:
|
|
2204
2246
|
return await self.packet_pump.start(self.stream.rtp_channel)
|
|
2205
2247
|
|
|
2206
2248
|
self.emit(self.EVENT_START)
|
|
2207
2249
|
|
|
2250
|
+
@override
|
|
2208
2251
|
async def stop(self) -> None:
|
|
2209
2252
|
if self.packet_pump:
|
|
2210
2253
|
return await self.packet_pump.stop()
|
|
2211
2254
|
|
|
2212
2255
|
self.emit(self.EVENT_STOP)
|
|
2213
2256
|
|
|
2214
|
-
|
|
2257
|
+
@override
|
|
2258
|
+
def on_start_command(self) -> Optional[Message]:
|
|
2215
2259
|
asyncio.create_task(self.start())
|
|
2260
|
+
return None
|
|
2216
2261
|
|
|
2217
|
-
|
|
2262
|
+
@override
|
|
2263
|
+
def on_suspend_command(self) -> Optional[Message]:
|
|
2218
2264
|
asyncio.create_task(self.stop())
|
|
2265
|
+
return None
|
|
2219
2266
|
|
|
2220
2267
|
|
|
2221
2268
|
# -----------------------------------------------------------------------------
|
|
@@ -2235,16 +2282,20 @@ class LocalSink(LocalStreamEndPoint):
|
|
|
2235
2282
|
capabilities,
|
|
2236
2283
|
)
|
|
2237
2284
|
|
|
2238
|
-
def on_rtp_channel_open(self):
|
|
2285
|
+
def on_rtp_channel_open(self) -> None:
|
|
2239
2286
|
logger.debug(color('<<< RTP channel open', 'magenta'))
|
|
2287
|
+
if not self.stream:
|
|
2288
|
+
raise InvalidStateError('Stream is None')
|
|
2289
|
+
if not self.stream.rtp_channel:
|
|
2290
|
+
raise InvalidStateError('RTP channel is None')
|
|
2240
2291
|
self.stream.rtp_channel.sink = self.on_avdtp_packet
|
|
2241
2292
|
super().on_rtp_channel_open()
|
|
2242
2293
|
|
|
2243
|
-
def on_rtp_channel_close(self):
|
|
2294
|
+
def on_rtp_channel_close(self) -> None:
|
|
2244
2295
|
logger.debug(color('<<< RTP channel close', 'magenta'))
|
|
2245
2296
|
super().on_rtp_channel_close()
|
|
2246
2297
|
|
|
2247
|
-
def on_avdtp_packet(self, packet):
|
|
2298
|
+
def on_avdtp_packet(self, packet: bytes) -> None:
|
|
2248
2299
|
rtp_packet = MediaPacket.from_bytes(packet)
|
|
2249
2300
|
logger.debug(
|
|
2250
2301
|
f'{color("<<< RTP Packet:", "green")} '
|