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/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
- Iterable,
30
+ ClassVar,
31
31
  Optional,
32
32
  SupportsBytes,
33
+ TypeVar,
33
34
  Union,
34
35
  cast,
35
36
  )
36
37
 
37
- from bumble import device, l2cap, sdp, utils
38
- from bumble.a2dp import (
39
- A2DP_CODEC_TYPE_NAMES,
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
- AVDTP_DISCOVER = 0x01
76
- AVDTP_GET_CAPABILITIES = 0x02
77
- AVDTP_SET_CONFIGURATION = 0x03
78
- AVDTP_GET_CONFIGURATION = 0x04
79
- AVDTP_RECONFIGURE = 0x05
80
- AVDTP_OPEN = 0x06
81
- AVDTP_START = 0x07
82
- AVDTP_CLOSE = 0x08
83
- AVDTP_SUSPEND = 0x09
84
- AVDTP_ABORT = 0x0A
85
- AVDTP_SECURITY_CONTROL = 0x0B
86
- AVDTP_GET_ALL_CAPABILITIES = 0x0C
87
- AVDTP_DELAYREPORT = 0x0D
88
-
89
- AVDTP_SIGNAL_NAMES = {
90
- AVDTP_DISCOVER: 'AVDTP_DISCOVER',
91
- AVDTP_GET_CAPABILITIES: 'AVDTP_GET_CAPABILITIES',
92
- AVDTP_SET_CONFIGURATION: 'AVDTP_SET_CONFIGURATION',
93
- AVDTP_GET_CONFIGURATION: 'AVDTP_GET_CONFIGURATION',
94
- AVDTP_RECONFIGURE: 'AVDTP_RECONFIGURE',
95
- AVDTP_OPEN: 'AVDTP_OPEN',
96
- AVDTP_START: 'AVDTP_START',
97
- AVDTP_CLOSE: 'AVDTP_CLOSE',
98
- AVDTP_SUSPEND: 'AVDTP_SUSPEND',
99
- AVDTP_ABORT: 'AVDTP_ABORT',
100
- AVDTP_SECURITY_CONTROL: 'AVDTP_SECURITY_CONTROL',
101
- AVDTP_GET_ALL_CAPABILITIES: 'AVDTP_GET_ALL_CAPABILITIES',
102
- AVDTP_DELAYREPORT: 'AVDTP_DELAYREPORT'
103
- }
104
-
105
- AVDTP_SIGNAL_IDENTIFIERS = {
106
- 'AVDTP_DISCOVER': AVDTP_DISCOVER,
107
- 'AVDTP_GET_CAPABILITIES': AVDTP_GET_CAPABILITIES,
108
- 'AVDTP_SET_CONFIGURATION': AVDTP_SET_CONFIGURATION,
109
- 'AVDTP_GET_CONFIGURATION': AVDTP_GET_CONFIGURATION,
110
- 'AVDTP_RECONFIGURE': AVDTP_RECONFIGURE,
111
- 'AVDTP_OPEN': AVDTP_OPEN,
112
- 'AVDTP_START': AVDTP_START,
113
- 'AVDTP_CLOSE': AVDTP_CLOSE,
114
- 'AVDTP_SUSPEND': AVDTP_SUSPEND,
115
- 'AVDTP_ABORT': AVDTP_ABORT,
116
- 'AVDTP_SECURITY_CONTROL': AVDTP_SECURITY_CONTROL,
117
- 'AVDTP_GET_ALL_CAPABILITIES': AVDTP_GET_ALL_CAPABILITIES,
118
- 'AVDTP_DELAYREPORT': AVDTP_DELAYREPORT
119
- }
120
-
121
- # Error codes (AVDTP spec - 8.20.6.2 ERROR_CODE tables)
122
- AVDTP_BAD_HEADER_FORMAT_ERROR = 0x01
123
- AVDTP_BAD_LENGTH_ERROR = 0x11
124
- AVDTP_BAD_ACP_SEID_ERROR = 0x12
125
- AVDTP_SEP_IN_USE_ERROR = 0x13
126
- AVDTP_SEP_NOT_IN_USE_ERROR = 0x14
127
- AVDTP_BAD_SERV_CATEGORY_ERROR = 0x17
128
- AVDTP_BAD_PAYLOAD_FORMAT_ERROR = 0x18
129
- AVDTP_NOT_SUPPORTED_COMMAND_ERROR = 0x19
130
- AVDTP_INVALID_CAPABILITIES_ERROR = 0x1A
131
- AVDTP_BAD_RECOVERY_TYPE_ERROR = 0x22
132
- AVDTP_BAD_MEDIA_TRANSPORT_FORMAT_ERROR = 0x23
133
- AVDTP_BAD_RECOVERY_FORMAT_ERROR = 0x25
134
- AVDTP_BAD_ROHC_FORMAT_ERROR = 0x26
135
- AVDTP_BAD_CP_FORMAT_ERROR = 0x27
136
- AVDTP_BAD_MULTIPLEXING_FORMAT_ERROR = 0x28
137
- AVDTP_UNSUPPORTED_CONFIGURATION_ERROR = 0x29
138
- AVDTP_BAD_STATE_ERROR = 0x31
139
-
140
- AVDTP_ERROR_NAMES = {
141
- AVDTP_BAD_HEADER_FORMAT_ERROR: 'AVDTP_BAD_HEADER_FORMAT_ERROR',
142
- AVDTP_BAD_LENGTH_ERROR: 'AVDTP_BAD_LENGTH_ERROR',
143
- AVDTP_BAD_ACP_SEID_ERROR: 'AVDTP_BAD_ACP_SEID_ERROR',
144
- AVDTP_SEP_IN_USE_ERROR: 'AVDTP_SEP_IN_USE_ERROR',
145
- AVDTP_SEP_NOT_IN_USE_ERROR: 'AVDTP_SEP_NOT_IN_USE_ERROR',
146
- AVDTP_BAD_SERV_CATEGORY_ERROR: 'AVDTP_BAD_SERV_CATEGORY_ERROR',
147
- AVDTP_BAD_PAYLOAD_FORMAT_ERROR: 'AVDTP_BAD_PAYLOAD_FORMAT_ERROR',
148
- AVDTP_NOT_SUPPORTED_COMMAND_ERROR: 'AVDTP_NOT_SUPPORTED_COMMAND_ERROR',
149
- AVDTP_INVALID_CAPABILITIES_ERROR: 'AVDTP_INVALID_CAPABILITIES_ERROR',
150
- AVDTP_BAD_RECOVERY_TYPE_ERROR: 'AVDTP_BAD_RECOVERY_TYPE_ERROR',
151
- AVDTP_BAD_MEDIA_TRANSPORT_FORMAT_ERROR: 'AVDTP_BAD_MEDIA_TRANSPORT_FORMAT_ERROR',
152
- AVDTP_BAD_RECOVERY_FORMAT_ERROR: 'AVDTP_BAD_RECOVERY_FORMAT_ERROR',
153
- AVDTP_BAD_ROHC_FORMAT_ERROR: 'AVDTP_BAD_ROHC_FORMAT_ERROR',
154
- AVDTP_BAD_CP_FORMAT_ERROR: 'AVDTP_BAD_CP_FORMAT_ERROR',
155
- AVDTP_BAD_MULTIPLEXING_FORMAT_ERROR: 'AVDTP_BAD_MULTIPLEXING_FORMAT_ERROR',
156
- AVDTP_UNSUPPORTED_CONFIGURATION_ERROR: 'AVDTP_UNSUPPORTED_CONFIGURATION_ERROR',
157
- AVDTP_BAD_STATE_ERROR: 'AVDTP_BAD_STATE_ERROR'
158
- }
159
-
160
- AVDTP_AUDIO_MEDIA_TYPE = 0x00
161
- AVDTP_VIDEO_MEDIA_TYPE = 0x01
162
- AVDTP_MULTIMEDIA_MEDIA_TYPE = 0x02
163
-
164
- AVDTP_MEDIA_TYPE_NAMES = {
165
- AVDTP_AUDIO_MEDIA_TYPE: 'AVDTP_AUDIO_MEDIA_TYPE',
166
- AVDTP_VIDEO_MEDIA_TYPE: 'AVDTP_VIDEO_MEDIA_TYPE',
167
- AVDTP_MULTIMEDIA_MEDIA_TYPE: 'AVDTP_MULTIMEDIA_MEDIA_TYPE'
168
- }
169
-
170
- # TSEP (AVDTP spec - 8.20.3 Stream End-point Type, Source or Sink (TSEP))
171
- AVDTP_TSEP_SRC = 0x00
172
- AVDTP_TSEP_SNK = 0x01
173
-
174
- AVDTP_TSEP_NAMES = {
175
- AVDTP_TSEP_SRC: 'AVDTP_TSEP_SRC',
176
- AVDTP_TSEP_SNK: 'AVDTP_TSEP_SNK'
177
- }
178
-
179
- # Service Categories (AVDTP spec - Table 8.47: Service Category information element field values)
180
- AVDTP_MEDIA_TRANSPORT_SERVICE_CATEGORY = 0x01
181
- AVDTP_REPORTING_SERVICE_CATEGORY = 0x02
182
- AVDTP_RECOVERY_SERVICE_CATEGORY = 0x03
183
- AVDTP_CONTENT_PROTECTION_SERVICE_CATEGORY = 0x04
184
- AVDTP_HEADER_COMPRESSION_SERVICE_CATEGORY = 0x05
185
- AVDTP_MULTIPLEXING_SERVICE_CATEGORY = 0x06
186
- AVDTP_MEDIA_CODEC_SERVICE_CATEGORY = 0x07
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, self.message_type, self.message or b''
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
- @staticmethod
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
- cls = MediaCodecCapabilities
452
- else:
453
- cls = ServiceCapabilities
454
-
455
- # Create an instance and initialize it
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
- @staticmethod
464
- def parse_capabilities(payload: bytes) -> list[ServiceCapabilities]:
436
+ @classmethod
437
+ def parse_capabilities(cls, payload: bytes) -> list[ServiceCapabilities]:
465
438
  capabilities = []
466
- while payload:
467
- service_category = payload[0]
468
- length_of_service_capabilities = payload[1]
469
- service_capabilities_bytes = payload[2 : 2 + length_of_service_capabilities]
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
- @staticmethod
479
- def serialize_capabilities(capabilities: Iterable[ServiceCapabilities]) -> bytes:
480
- serialized = b''
481
- for item in capabilities:
482
- serialized += (
483
- bytes([item.service_category, len(item.service_capabilities_bytes)])
484
- + item.service_capabilities_bytes
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
- media_codec_information: Union[bytes, SupportsBytes]
515
- media_type: int
516
- media_codec_type: int
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
- if self.media_codec_type == A2DP_SBC_CODEC_TYPE:
524
- self.media_codec_information = SbcMediaCodecInformation.from_bytes(
525
- self.media_codec_information
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: int,
555
- media_codec_type: int,
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
- def __str__(self) -> str:
567
- codec_info = (
568
- self.media_codec_information.hex()
569
- if isinstance(self.media_codec_information, bytes)
570
- else str(self.media_codec_information)
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
- details = [
574
- f'media_type={name_or_number(AVDTP_MEDIA_TYPE_NAMES, self.media_type)}',
575
- f'codec={name_or_number(A2DP_CODEC_TYPE_NAMES, self.media_codec_type)}',
576
- f'codec_info={codec_info}',
577
- ]
578
- return self.to_string(details)
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
- @staticmethod
584
- def from_bytes(payload: bytes) -> EndPointInfo:
585
- return EndPointInfo(
586
- payload[0] >> 2, payload[0] >> 1 & 1, payload[1] >> 4, payload[1] >> 3 & 1
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: # pylint:disable=attribute-defined-outside-init
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
- @staticmethod
615
- def subclass(subclass):
616
- # Infer the signal identifier and message subtype from the class name
617
- name = subclass.__name__
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
- subclass.message_type = message_type
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
- if signal_identifier_str is not None:
637
- for name, signal_identifier in AVDTP_SIGNAL_IDENTIFIERS.items():
638
- if name.lower().endswith(signal_identifier_str.lower()):
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
- # Register the subclass
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
- @staticmethod
581
+ @classmethod
652
582
  def create(
653
- signal_identifier: int, message_type: MessageType, payload: bytes
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 = Message.subclasses.get(signal_identifier)
657
- if subclasses:
658
- subclass = subclasses.get(message_type)
659
- if subclass:
660
- instance = subclass.__new__(subclass)
661
- instance.payload = payload
662
- instance.init_from_payload()
663
- return instance
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(payload)
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'{name_or_number(AVDTP_SIGNAL_NAMES, self.signal_identifier)}_'
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
- def init_from_payload(self):
712
- self.acp_seid = self.payload[0] >> 2
639
+ message_type = Message.MessageType.COMMAND
713
640
 
714
- def __init__(self, seid):
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
- def init_from_payload(self):
729
- self.error_code = self.payload[0]
654
+ message_type = Message.MessageType.RESPONSE_REJECT
730
655
 
731
- def __init__(self, error_code):
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: {name_or_number(AVDTP_ERROR_NAMES, self.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
- endpoints: list[EndPointInfo]
683
+ signal_identifier = AVDTP_DISCOVER
684
+ message_type = Message.MessageType.RESPONSE_ACCEPT
756
685
 
757
- def init_from_payload(self):
758
- self.endpoints = []
759
- endpoint_count = len(self.payload) // 2
760
- for i in range(endpoint_count):
761
- self.endpoints.append(
762
- EndPointInfo.from_bytes(self.payload[i * 2 : (i + 1) * 2])
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
- def __init__(self, endpoints):
766
- super().__init__(payload=b''.join([bytes(endpoint) for endpoint in endpoints]))
767
- self.endpoints = endpoints
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: {name_or_number(AVDTP_MEDIA_TYPE_NAMES, endpoint.media_type)}',
778
- f' tsep: {name_or_number(AVDTP_TSEP_NAMES, endpoint.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
- def init_from_payload(self):
800
- self.capabilities = ServiceCapabilities.parse_capabilities(self.payload)
747
+ signal_identifier = AVDTP_GET_CAPABILITIES
748
+ message_type = Message.MessageType.RESPONSE_ACCEPT
801
749
 
802
- def __init__(self, capabilities):
803
- super().__init__(
804
- payload=ServiceCapabilities.serialize_capabilities(capabilities)
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
- def init_from_payload(self):
853
- self.acp_seid = self.payload[0] >> 2
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
- def __init__(
858
- self, acp_seid: int, int_seid: int, capabilities: Iterable[ServiceCapabilities]
859
- ) -> None:
860
- super().__init__(
861
- payload=bytes([acp_seid << 2, int_seid << 2])
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
- def init_from_payload(self):
891
- self.service_category = self.payload[0]
892
- self.error_code = self.payload[1]
851
+ signal_identifier = AVDTP_SET_CONFIGURATION
852
+ message_type = Message.MessageType.RESPONSE_REJECT
893
853
 
894
- def __init__(self, error_code: int, service_category: int = 0) -> None:
895
- super().__init__(payload=bytes([service_category, error_code]))
896
- self.service_category = service_category
897
- self.error_code = error_code
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
- 'service_category: '
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
- def init_from_payload(self):
929
- self.capabilities = ServiceCapabilities.parse_capabilities(self.payload)
889
+ signal_identifier = AVDTP_GET_CONFIGURATION
890
+ message_type = Message.MessageType.RESPONSE_ACCEPT
930
891
 
931
- def __init__(self, capabilities: Iterable[ServiceCapabilities]) -> None:
932
- super().__init__(
933
- payload=ServiceCapabilities.serialize_capabilities(capabilities)
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
- def init_from_payload(self):
958
- # pylint: disable=attribute-defined-outside-init
959
- self.acp_seid = self.payload[0] >> 2
960
- self.capabilities = ServiceCapabilities.parse_capabilities(self.payload[1:])
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
- def init_from_payload(self):
1017
- self.acp_seids = [x >> 2 for x in self.payload]
1004
+ signal_identifier = AVDTP_START
1005
+ message_type = Message.MessageType.COMMAND
1018
1006
 
1019
- def __init__(self, seids: Iterable[int]) -> None:
1020
- super().__init__(payload=bytes([seid << 2 for seid in seids]))
1021
- self.acp_seids = seids
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
- def init_from_payload(self):
1043
- self.acp_seid = self.payload[0] >> 2
1044
- self.error_code = self.payload[1]
1043
+ signal_identifier = AVDTP_START
1044
+ message_type = Message.MessageType.RESPONSE_REJECT
1045
1045
 
1046
- def __init__(self, acp_seid, error_code):
1047
- super().__init__(payload=bytes([acp_seid << 2, error_code]))
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: {name_or_number(AVDTP_ERROR_NAMES, self.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
- def init_from_payload(self):
1131
- # pylint: disable=attribute-defined-outside-init
1132
- self.acp_seid = self.payload[0] >> 2
1133
- self.data = self.payload[1:]
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([f'ACP_SEID: {self.acp_seid}', f'data: {self.data}'])
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
- def init_from_payload(self):
1174
- # pylint: disable=attribute-defined-outside-init
1175
- self.acp_seid = self.payload[0] >> 2
1176
- self.delay = (self.payload[1] << 8) | (self.payload[2])
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(0, AVDTP_BAD_ACP_SEID_ERROR)
1664
+ return Reconfigure_Reject(error_code=AVDTP_BAD_ACP_SEID_ERROR)
1606
1665
  if endpoint.stream is None:
1607
- return Reconfigure_Reject(0, AVDTP_BAD_STATE_ERROR)
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
- @staticmethod
1772
- def state_name(state: int) -> str:
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 != AVDTP_IDLE_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(AVDTP_CONFIGURED_STATE)
1845
+ self.change_state(State.CONFIGURED)
1791
1846
 
1792
1847
  async def open(self) -> None:
1793
- if self.state != AVDTP_CONFIGURED_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(AVDTP_OPEN_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 == AVDTP_CONFIGURED_STATE:
1866
+ if self.state == State.CONFIGURED:
1812
1867
  await self.open()
1813
1868
 
1814
- if self.state != AVDTP_OPEN_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(AVDTP_STREAMING_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 != AVDTP_STREAMING_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(AVDTP_OPEN_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 (AVDTP_OPEN_STATE, AVDTP_STREAMING_STATE):
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(AVDTP_CLOSING_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(AVDTP_IDLE_STATE)
1913
+ self.change_state(State.IDLE)
1859
1914
 
1860
- def on_set_configuration_command(self, configuration):
1861
- if self.state != AVDTP_IDLE_STATE:
1862
- return Set_Configuration_Reject(AVDTP_BAD_STATE_ERROR)
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(AVDTP_CONFIGURED_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
- AVDTP_CONFIGURED_STATE,
1874
- AVDTP_OPEN_STATE,
1875
- AVDTP_STREAMING_STATE,
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(self, configuration):
1882
- if self.state != AVDTP_OPEN_STATE:
1883
- return Reconfigure_Reject(AVDTP_BAD_STATE_ERROR)
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 != AVDTP_CONFIGURED_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(AVDTP_OPEN_STATE)
1961
+ self.change_state(State.OPEN)
1903
1962
  return None
1904
1963
 
1905
- def on_start_command(self):
1906
- if self.state != AVDTP_OPEN_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(AVDTP_STREAMING_STATE)
1977
+ self.change_state(State.STREAMING)
1919
1978
  return None
1920
1979
 
1921
- def on_suspend_command(self):
1922
- if self.state != AVDTP_STREAMING_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(AVDTP_OPEN_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 (AVDTP_OPEN_STATE, AVDTP_STREAMING_STATE):
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(AVDTP_CLOSING_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(AVDTP_IDLE_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(AVDTP_IDLE_STATE)
2013
+ self.change_state(State.IDLE)
1955
2014
  else:
1956
2015
  # Wait for the RTP channel to be closed
1957
- self.change_state(AVDTP_ABORTING_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 (AVDTP_CLOSING_STATE, AVDTP_ABORTING_STATE):
1979
- self.change_state(AVDTP_IDLE_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 = AVDTP_IDLE_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.state_name(self.state)})'
2065
+ f'{self.remote_endpoint.seid} {self.state.name})'
2006
2066
  )
2007
2067
 
2008
2068
 
2009
2069
  # -----------------------------------------------------------------------------
2070
+ @dataclass
2010
2071
  class StreamEndPoint:
2011
- def __init__(
2012
- self,
2013
- seid: int,
2014
- media_type: int,
2015
- tsep: int,
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: int,
2077
- tsep: int,
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: int,
2107
- tsep: int,
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(self, command) -> Optional[Message]:
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(self, configuration) -> Optional[Message]:
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
- other_capabilitiles: Iterable[ServiceCapabilities],
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(other_capabilitiles)
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
- def on_start_command(self):
2257
+ @override
2258
+ def on_start_command(self) -> Optional[Message]:
2215
2259
  asyncio.create_task(self.start())
2260
+ return None
2216
2261
 
2217
- def on_suspend_command(self):
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")} '