mirror of
https://github.com/google/bumble.git
synced 2026-04-16 00:25:31 +00:00
Migrate AVDTP enums
This commit is contained in:
356
bumble/avdtp.py
356
bumble/avdtp.py
@@ -41,7 +41,6 @@ from bumble.core import (
|
||||
BT_ADVANCED_AUDIO_DISTRIBUTION_SERVICE,
|
||||
InvalidStateError,
|
||||
ProtocolError,
|
||||
name_or_number,
|
||||
)
|
||||
from bumble.rtp import MediaPacket
|
||||
|
||||
@@ -62,74 +61,72 @@ AVDTP_PSM = 0x0019
|
||||
AVDTP_DEFAULT_RTX_SIG_TIMER = 5 # Seconds
|
||||
|
||||
# Signal Identifiers (AVDTP spec - 8.5 Signal Command Set)
|
||||
AVDTP_DISCOVER = 0x01
|
||||
AVDTP_GET_CAPABILITIES = 0x02
|
||||
AVDTP_SET_CONFIGURATION = 0x03
|
||||
AVDTP_GET_CONFIGURATION = 0x04
|
||||
AVDTP_RECONFIGURE = 0x05
|
||||
AVDTP_OPEN = 0x06
|
||||
AVDTP_START = 0x07
|
||||
AVDTP_CLOSE = 0x08
|
||||
AVDTP_SUSPEND = 0x09
|
||||
AVDTP_ABORT = 0x0A
|
||||
AVDTP_SECURITY_CONTROL = 0x0B
|
||||
AVDTP_GET_ALL_CAPABILITIES = 0x0C
|
||||
AVDTP_DELAYREPORT = 0x0D
|
||||
class SignalIdentifier(hci.SpecableEnum):
|
||||
DISCOVER = 0x01
|
||||
GET_CAPABILITIES = 0x02
|
||||
SET_CONFIGURATION = 0x03
|
||||
GET_CONFIGURATION = 0x04
|
||||
RECONFIGURE = 0x05
|
||||
OPEN = 0x06
|
||||
START = 0x07
|
||||
CLOSE = 0x08
|
||||
SUSPEND = 0x09
|
||||
ABORT = 0x0A
|
||||
SECURITY_CONTROL = 0x0B
|
||||
GET_ALL_CAPABILITIES = 0x0C
|
||||
DELAYREPORT = 0x0D
|
||||
|
||||
AVDTP_SIGNAL_NAMES = {
|
||||
AVDTP_DISCOVER: 'AVDTP_DISCOVER',
|
||||
AVDTP_GET_CAPABILITIES: 'AVDTP_GET_CAPABILITIES',
|
||||
AVDTP_SET_CONFIGURATION: 'AVDTP_SET_CONFIGURATION',
|
||||
AVDTP_GET_CONFIGURATION: 'AVDTP_GET_CONFIGURATION',
|
||||
AVDTP_RECONFIGURE: 'AVDTP_RECONFIGURE',
|
||||
AVDTP_OPEN: 'AVDTP_OPEN',
|
||||
AVDTP_START: 'AVDTP_START',
|
||||
AVDTP_CLOSE: 'AVDTP_CLOSE',
|
||||
AVDTP_SUSPEND: 'AVDTP_SUSPEND',
|
||||
AVDTP_ABORT: 'AVDTP_ABORT',
|
||||
AVDTP_SECURITY_CONTROL: 'AVDTP_SECURITY_CONTROL',
|
||||
AVDTP_GET_ALL_CAPABILITIES: 'AVDTP_GET_ALL_CAPABILITIES',
|
||||
AVDTP_DELAYREPORT: 'AVDTP_DELAYREPORT'
|
||||
}
|
||||
AVDTP_DISCOVER = SignalIdentifier.DISCOVER
|
||||
AVDTP_GET_CAPABILITIES = SignalIdentifier.GET_CAPABILITIES
|
||||
AVDTP_SET_CONFIGURATION = SignalIdentifier.SET_CONFIGURATION
|
||||
AVDTP_GET_CONFIGURATION = SignalIdentifier.GET_CONFIGURATION
|
||||
AVDTP_RECONFIGURE = SignalIdentifier.RECONFIGURE
|
||||
AVDTP_OPEN = SignalIdentifier.OPEN
|
||||
AVDTP_START = SignalIdentifier.START
|
||||
AVDTP_CLOSE = SignalIdentifier.CLOSE
|
||||
AVDTP_SUSPEND = SignalIdentifier.SUSPEND
|
||||
AVDTP_ABORT = SignalIdentifier.ABORT
|
||||
AVDTP_SECURITY_CONTROL = SignalIdentifier.SECURITY_CONTROL
|
||||
AVDTP_GET_ALL_CAPABILITIES = SignalIdentifier.GET_ALL_CAPABILITIES
|
||||
AVDTP_DELAYREPORT = SignalIdentifier.DELAYREPORT
|
||||
|
||||
# Error codes (AVDTP spec - 8.20.6.2 ERROR_CODE tables)
|
||||
AVDTP_BAD_HEADER_FORMAT_ERROR = 0x01
|
||||
AVDTP_BAD_LENGTH_ERROR = 0x11
|
||||
AVDTP_BAD_ACP_SEID_ERROR = 0x12
|
||||
AVDTP_SEP_IN_USE_ERROR = 0x13
|
||||
AVDTP_SEP_NOT_IN_USE_ERROR = 0x14
|
||||
AVDTP_BAD_SERV_CATEGORY_ERROR = 0x17
|
||||
AVDTP_BAD_PAYLOAD_FORMAT_ERROR = 0x18
|
||||
AVDTP_NOT_SUPPORTED_COMMAND_ERROR = 0x19
|
||||
AVDTP_INVALID_CAPABILITIES_ERROR = 0x1A
|
||||
AVDTP_BAD_RECOVERY_TYPE_ERROR = 0x22
|
||||
AVDTP_BAD_MEDIA_TRANSPORT_FORMAT_ERROR = 0x23
|
||||
AVDTP_BAD_RECOVERY_FORMAT_ERROR = 0x25
|
||||
AVDTP_BAD_ROHC_FORMAT_ERROR = 0x26
|
||||
AVDTP_BAD_CP_FORMAT_ERROR = 0x27
|
||||
AVDTP_BAD_MULTIPLEXING_FORMAT_ERROR = 0x28
|
||||
AVDTP_UNSUPPORTED_CONFIGURATION_ERROR = 0x29
|
||||
AVDTP_BAD_STATE_ERROR = 0x31
|
||||
class ErrorCode(hci.SpecableEnum):
|
||||
'''Error codes (AVDTP spec - 8.20.6.2 ERROR_CODE tables)'''
|
||||
BAD_HEADER_FORMAT = 0x01
|
||||
BAD_LENGTH = 0x11
|
||||
BAD_ACP_SEID = 0x12
|
||||
SEP_IN_USE = 0x13
|
||||
SEP_NOT_IN_USE = 0x14
|
||||
BAD_SERV_CATEGORY = 0x17
|
||||
BAD_PAYLOAD_FORMAT = 0x18
|
||||
NOT_SUPPORTED_COMMAND = 0x19
|
||||
INVALID_CAPABILITIES = 0x1A
|
||||
BAD_RECOVERY_TYPE = 0x22
|
||||
BAD_MEDIA_TRANSPORT_FORMAT = 0x23
|
||||
BAD_RECOVERY_FORMAT = 0x25
|
||||
BAD_ROHC_FORMAT = 0x26
|
||||
BAD_CP_FORMAT = 0x27
|
||||
BAD_MULTIPLEXING_FORMAT = 0x28
|
||||
UNSUPPORTED_CONFIGURATION = 0x29
|
||||
BAD_STATE = 0x31
|
||||
|
||||
AVDTP_ERROR_NAMES = {
|
||||
AVDTP_BAD_HEADER_FORMAT_ERROR: 'AVDTP_BAD_HEADER_FORMAT_ERROR',
|
||||
AVDTP_BAD_LENGTH_ERROR: 'AVDTP_BAD_LENGTH_ERROR',
|
||||
AVDTP_BAD_ACP_SEID_ERROR: 'AVDTP_BAD_ACP_SEID_ERROR',
|
||||
AVDTP_SEP_IN_USE_ERROR: 'AVDTP_SEP_IN_USE_ERROR',
|
||||
AVDTP_SEP_NOT_IN_USE_ERROR: 'AVDTP_SEP_NOT_IN_USE_ERROR',
|
||||
AVDTP_BAD_SERV_CATEGORY_ERROR: 'AVDTP_BAD_SERV_CATEGORY_ERROR',
|
||||
AVDTP_BAD_PAYLOAD_FORMAT_ERROR: 'AVDTP_BAD_PAYLOAD_FORMAT_ERROR',
|
||||
AVDTP_NOT_SUPPORTED_COMMAND_ERROR: 'AVDTP_NOT_SUPPORTED_COMMAND_ERROR',
|
||||
AVDTP_INVALID_CAPABILITIES_ERROR: 'AVDTP_INVALID_CAPABILITIES_ERROR',
|
||||
AVDTP_BAD_RECOVERY_TYPE_ERROR: 'AVDTP_BAD_RECOVERY_TYPE_ERROR',
|
||||
AVDTP_BAD_MEDIA_TRANSPORT_FORMAT_ERROR: 'AVDTP_BAD_MEDIA_TRANSPORT_FORMAT_ERROR',
|
||||
AVDTP_BAD_RECOVERY_FORMAT_ERROR: 'AVDTP_BAD_RECOVERY_FORMAT_ERROR',
|
||||
AVDTP_BAD_ROHC_FORMAT_ERROR: 'AVDTP_BAD_ROHC_FORMAT_ERROR',
|
||||
AVDTP_BAD_CP_FORMAT_ERROR: 'AVDTP_BAD_CP_FORMAT_ERROR',
|
||||
AVDTP_BAD_MULTIPLEXING_FORMAT_ERROR: 'AVDTP_BAD_MULTIPLEXING_FORMAT_ERROR',
|
||||
AVDTP_UNSUPPORTED_CONFIGURATION_ERROR: 'AVDTP_UNSUPPORTED_CONFIGURATION_ERROR',
|
||||
AVDTP_BAD_STATE_ERROR: 'AVDTP_BAD_STATE_ERROR'
|
||||
}
|
||||
AVDTP_BAD_HEADER_FORMAT_ERROR = ErrorCode.BAD_HEADER_FORMAT
|
||||
AVDTP_BAD_LENGTH_ERROR = ErrorCode.BAD_LENGTH
|
||||
AVDTP_BAD_ACP_SEID_ERROR = ErrorCode.BAD_ACP_SEID
|
||||
AVDTP_SEP_IN_USE_ERROR = ErrorCode.SEP_IN_USE
|
||||
AVDTP_SEP_NOT_IN_USE_ERROR = ErrorCode.SEP_NOT_IN_USE
|
||||
AVDTP_BAD_SERV_CATEGORY_ERROR = ErrorCode.BAD_SERV_CATEGORY
|
||||
AVDTP_BAD_PAYLOAD_FORMAT_ERROR = ErrorCode.BAD_PAYLOAD_FORMAT
|
||||
AVDTP_NOT_SUPPORTED_COMMAND_ERROR = ErrorCode.NOT_SUPPORTED_COMMAND
|
||||
AVDTP_INVALID_CAPABILITIES_ERROR = ErrorCode.INVALID_CAPABILITIES
|
||||
AVDTP_BAD_RECOVERY_TYPE_ERROR = ErrorCode.BAD_RECOVERY_TYPE
|
||||
AVDTP_BAD_MEDIA_TRANSPORT_FORMAT_ERROR = ErrorCode.BAD_MEDIA_TRANSPORT_FORMAT
|
||||
AVDTP_BAD_RECOVERY_FORMAT_ERROR = ErrorCode.BAD_RECOVERY_FORMAT
|
||||
AVDTP_BAD_ROHC_FORMAT_ERROR = ErrorCode.BAD_ROHC_FORMAT
|
||||
AVDTP_BAD_CP_FORMAT_ERROR = ErrorCode.BAD_CP_FORMAT
|
||||
AVDTP_BAD_MULTIPLEXING_FORMAT_ERROR = ErrorCode.BAD_MULTIPLEXING_FORMAT
|
||||
AVDTP_UNSUPPORTED_CONFIGURATION_ERROR = ErrorCode.UNSUPPORTED_CONFIGURATION
|
||||
AVDTP_BAD_STATE_ERROR = ErrorCode.BAD_STATE
|
||||
|
||||
class MediaType(utils.OpenIntEnum):
|
||||
AUDIO = 0x00
|
||||
@@ -140,52 +137,42 @@ AVDTP_AUDIO_MEDIA_TYPE = MediaType.AUDIO
|
||||
AVDTP_VIDEO_MEDIA_TYPE = MediaType.VIDEO
|
||||
AVDTP_MULTIMEDIA_MEDIA_TYPE = MediaType.MULTIMEDIA
|
||||
|
||||
# TSEP (AVDTP spec - 8.20.3 Stream End-point Type, Source or Sink (TSEP))
|
||||
AVDTP_TSEP_SRC = 0x00
|
||||
AVDTP_TSEP_SNK = 0x01
|
||||
class StreamEndPointType(utils.OpenIntEnum):
|
||||
'''TSEP (AVDTP spec - 8.20.3 Stream End-point Type, Source or Sink (TSEP)).'''
|
||||
SRC = 0x00
|
||||
SNK = 0x01
|
||||
|
||||
AVDTP_TSEP_NAMES = {
|
||||
AVDTP_TSEP_SRC: 'AVDTP_TSEP_SRC',
|
||||
AVDTP_TSEP_SNK: 'AVDTP_TSEP_SNK'
|
||||
}
|
||||
AVDTP_TSEP_SRC = StreamEndPointType.SRC
|
||||
AVDTP_TSEP_SNK = StreamEndPointType.SNK
|
||||
|
||||
# Service Categories (AVDTP spec - Table 8.47: Service Category information element field values)
|
||||
AVDTP_MEDIA_TRANSPORT_SERVICE_CATEGORY = 0x01
|
||||
AVDTP_REPORTING_SERVICE_CATEGORY = 0x02
|
||||
AVDTP_RECOVERY_SERVICE_CATEGORY = 0x03
|
||||
AVDTP_CONTENT_PROTECTION_SERVICE_CATEGORY = 0x04
|
||||
AVDTP_HEADER_COMPRESSION_SERVICE_CATEGORY = 0x05
|
||||
AVDTP_MULTIPLEXING_SERVICE_CATEGORY = 0x06
|
||||
AVDTP_MEDIA_CODEC_SERVICE_CATEGORY = 0x07
|
||||
AVDTP_DELAY_REPORTING_SERVICE_CATEGORY = 0x08
|
||||
class ServiceCategory(hci.SpecableEnum):
|
||||
'''Service Categories (AVDTP spec - Table 8.47: Service Category information element field values).'''
|
||||
MEDIA_TRANSPORT = 0x01
|
||||
REPORTING = 0x02
|
||||
RECOVERY = 0x03
|
||||
CONTENT_PROTECTION = 0x04
|
||||
HEADER_COMPRESSION = 0x05
|
||||
MULTIPLEXING = 0x06
|
||||
MEDIA_CODEC = 0x07
|
||||
DELAY_REPORTING = 0x08
|
||||
|
||||
AVDTP_SERVICE_CATEGORY_NAMES = {
|
||||
AVDTP_MEDIA_TRANSPORT_SERVICE_CATEGORY: 'AVDTP_MEDIA_TRANSPORT_SERVICE_CATEGORY',
|
||||
AVDTP_REPORTING_SERVICE_CATEGORY: 'AVDTP_REPORTING_SERVICE_CATEGORY',
|
||||
AVDTP_RECOVERY_SERVICE_CATEGORY: 'AVDTP_RECOVERY_SERVICE_CATEGORY',
|
||||
AVDTP_CONTENT_PROTECTION_SERVICE_CATEGORY: 'AVDTP_CONTENT_PROTECTION_SERVICE_CATEGORY',
|
||||
AVDTP_HEADER_COMPRESSION_SERVICE_CATEGORY: 'AVDTP_HEADER_COMPRESSION_SERVICE_CATEGORY',
|
||||
AVDTP_MULTIPLEXING_SERVICE_CATEGORY: 'AVDTP_MULTIPLEXING_SERVICE_CATEGORY',
|
||||
AVDTP_MEDIA_CODEC_SERVICE_CATEGORY: 'AVDTP_MEDIA_CODEC_SERVICE_CATEGORY',
|
||||
AVDTP_DELAY_REPORTING_SERVICE_CATEGORY: 'AVDTP_DELAY_REPORTING_SERVICE_CATEGORY'
|
||||
}
|
||||
AVDTP_MEDIA_TRANSPORT_SERVICE_CATEGORY = ServiceCategory.MEDIA_TRANSPORT
|
||||
AVDTP_REPORTING_SERVICE_CATEGORY = ServiceCategory.REPORTING
|
||||
AVDTP_RECOVERY_SERVICE_CATEGORY = ServiceCategory.RECOVERY
|
||||
AVDTP_CONTENT_PROTECTION_SERVICE_CATEGORY = ServiceCategory.CONTENT_PROTECTION
|
||||
AVDTP_HEADER_COMPRESSION_SERVICE_CATEGORY = ServiceCategory.HEADER_COMPRESSION
|
||||
AVDTP_MULTIPLEXING_SERVICE_CATEGORY = ServiceCategory.MULTIPLEXING
|
||||
AVDTP_MEDIA_CODEC_SERVICE_CATEGORY = ServiceCategory.MEDIA_CODEC
|
||||
AVDTP_DELAY_REPORTING_SERVICE_CATEGORY = ServiceCategory.DELAY_REPORTING
|
||||
|
||||
# States (AVDTP spec - 9.1 State Definitions)
|
||||
AVDTP_IDLE_STATE = 0x00
|
||||
AVDTP_CONFIGURED_STATE = 0x01
|
||||
AVDTP_OPEN_STATE = 0x02
|
||||
AVDTP_STREAMING_STATE = 0x03
|
||||
AVDTP_CLOSING_STATE = 0x04
|
||||
AVDTP_ABORTING_STATE = 0x05
|
||||
|
||||
AVDTP_STATE_NAMES = {
|
||||
AVDTP_IDLE_STATE: 'AVDTP_IDLE_STATE',
|
||||
AVDTP_CONFIGURED_STATE: 'AVDTP_CONFIGURED_STATE',
|
||||
AVDTP_OPEN_STATE: 'AVDTP_OPEN_STATE',
|
||||
AVDTP_STREAMING_STATE: 'AVDTP_STREAMING_STATE',
|
||||
AVDTP_CLOSING_STATE: 'AVDTP_CLOSING_STATE',
|
||||
AVDTP_ABORTING_STATE: 'AVDTP_ABORTING_STATE'
|
||||
}
|
||||
class State(utils.OpenIntEnum):
|
||||
'''States (AVDTP spec - 9.1 State Definitions)'''
|
||||
IDLE = 0x00
|
||||
CONFIGURED = 0x01
|
||||
OPEN = 0x02
|
||||
STREAMING = 0x03
|
||||
CLOSING = 0x04
|
||||
ABORTING = 0x05
|
||||
|
||||
# fmt: on
|
||||
# pylint: enable=line-too-long
|
||||
@@ -308,6 +295,7 @@ class MediaPacketPump:
|
||||
# -----------------------------------------------------------------------------
|
||||
class MessageAssembler:
|
||||
message: Optional[bytes]
|
||||
signal_identifier: SignalIdentifier
|
||||
|
||||
def __init__(self, callback: Callable[[int, Message], Any]) -> None:
|
||||
self.callback = callback
|
||||
@@ -317,7 +305,7 @@ class MessageAssembler:
|
||||
self.transaction_label = 0
|
||||
self.message = None
|
||||
self.message_type = Message.MessageType.COMMAND
|
||||
self.signal_identifier = 0
|
||||
self.signal_identifier = SignalIdentifier(0)
|
||||
self.number_of_signal_packets = 0
|
||||
self.packet_count = 0
|
||||
|
||||
@@ -346,7 +334,7 @@ class MessageAssembler:
|
||||
self.reset()
|
||||
|
||||
self.transaction_label = transaction_label
|
||||
self.signal_identifier = pdu[1] & 0x3F
|
||||
self.signal_identifier = SignalIdentifier(pdu[1] & 0x3F)
|
||||
self.message_type = message_type
|
||||
|
||||
if packet_type == Protocol.PacketType.SINGLE_PACKET:
|
||||
@@ -402,7 +390,9 @@ class MessageAssembler:
|
||||
|
||||
def on_message_complete(self) -> None:
|
||||
message = Message.create(
|
||||
self.signal_identifier, self.message_type, self.message or b''
|
||||
self.signal_identifier,
|
||||
self.message_type,
|
||||
self.message or b'',
|
||||
)
|
||||
try:
|
||||
self.callback(self.transaction_label, message)
|
||||
@@ -524,7 +514,7 @@ class EndPointInfo:
|
||||
seid: int
|
||||
in_use: int
|
||||
media_type: MediaType
|
||||
tsep: int
|
||||
tsep: StreamEndPointType
|
||||
|
||||
@classmethod
|
||||
def from_bytes(cls, payload: bytes) -> EndPointInfo:
|
||||
@@ -532,7 +522,7 @@ class EndPointInfo:
|
||||
seid=payload[0] >> 2,
|
||||
in_use=payload[0] >> 1 & 1,
|
||||
media_type=MediaType(payload[1] >> 4),
|
||||
tsep=payload[1] >> 3 & 1,
|
||||
tsep=StreamEndPointType(payload[1] >> 3 & 1),
|
||||
)
|
||||
|
||||
def __bytes__(self) -> bytes:
|
||||
@@ -560,7 +550,7 @@ class Message:
|
||||
subclasses: ClassVar[dict[int, dict[int, type[Message]]]] = {}
|
||||
|
||||
message_type: MessageType
|
||||
signal_identifier: int
|
||||
signal_identifier: SignalIdentifier
|
||||
_payload: Optional[bytes] = None
|
||||
fields: ClassVar[hci.Fields] = ()
|
||||
|
||||
@@ -588,7 +578,10 @@ class Message:
|
||||
# type
|
||||
@classmethod
|
||||
def create(
|
||||
cls, signal_identifier: int, message_type: MessageType, payload: bytes
|
||||
cls,
|
||||
signal_identifier: SignalIdentifier,
|
||||
message_type: MessageType,
|
||||
payload: bytes,
|
||||
) -> Message:
|
||||
instance: Message
|
||||
# Look for a registered subclass
|
||||
@@ -604,7 +597,7 @@ class Message:
|
||||
# Instantiate the appropriate class based on the message type
|
||||
if message_type == Message.MessageType.RESPONSE_REJECT:
|
||||
# Assume a simple reject message
|
||||
instance = Simple_Reject(payload[0])
|
||||
instance = Simple_Reject(ErrorCode(payload[0]))
|
||||
else:
|
||||
instance = Message()
|
||||
instance.payload = payload
|
||||
@@ -614,8 +607,7 @@ class Message:
|
||||
|
||||
def to_string(self, details: Union[str, Iterable[str]]) -> str:
|
||||
base = color(
|
||||
f'{name_or_number(AVDTP_SIGNAL_NAMES, self.signal_identifier)}_'
|
||||
f'{self.message_type.name}',
|
||||
f'{self.signal_identifier.name}_{self.message_type.name}',
|
||||
'yellow',
|
||||
)
|
||||
|
||||
@@ -659,10 +651,10 @@ class Simple_Reject(Message):
|
||||
|
||||
message_type = Message.MessageType.RESPONSE_REJECT
|
||||
|
||||
error_code: int = field(metadata=hci.metadata(1))
|
||||
error_code: ErrorCode = field(metadata=ErrorCode.type_metadata(1))
|
||||
|
||||
def __str__(self) -> str:
|
||||
details = [f'error_code: {name_or_number(AVDTP_ERROR_NAMES, self.error_code)}']
|
||||
details = [f'error_code: {self.error_code.name}']
|
||||
return self.to_string(details)
|
||||
|
||||
|
||||
@@ -724,7 +716,7 @@ class Discover_Response(Message):
|
||||
f'ACP SEID: {endpoint.seid}',
|
||||
f' in_use: {endpoint.in_use}',
|
||||
f' media_type: {endpoint.media_type.name}',
|
||||
f' tsep: {name_or_number(AVDTP_TSEP_NAMES, endpoint.tsep)}',
|
||||
f' tsep: {endpoint.tsep.name}',
|
||||
]
|
||||
)
|
||||
return self.to_string(details)
|
||||
@@ -857,19 +849,17 @@ class Set_Configuration_Reject(Message):
|
||||
signal_identifier = AVDTP_SET_CONFIGURATION
|
||||
message_type = Message.MessageType.RESPONSE_REJECT
|
||||
|
||||
service_category: int = field(metadata=hci.metadata(1), default=0)
|
||||
error_code: int = field(metadata=hci.metadata(1), default=0)
|
||||
service_category: ServiceCategory = field(
|
||||
metadata=ServiceCategory.type_metadata(1), default=ServiceCategory(0)
|
||||
)
|
||||
error_code: ErrorCode = field(
|
||||
metadata=ErrorCode.type_metadata(1), default=ErrorCode(0)
|
||||
)
|
||||
|
||||
def __str__(self) -> str:
|
||||
details = [
|
||||
(
|
||||
'service_category: '
|
||||
f'{name_or_number(AVDTP_SERVICE_CATEGORY_NAMES, self.service_category)}'
|
||||
),
|
||||
(
|
||||
'error_code: '
|
||||
f'{name_or_number(AVDTP_ERROR_NAMES, self.error_code)}'
|
||||
),
|
||||
(f'service_category: {self.service_category.name}'),
|
||||
(f'error_code: {self.error_code.name}'),
|
||||
]
|
||||
return self.to_string(details)
|
||||
|
||||
@@ -1052,12 +1042,12 @@ class Start_Reject(Message):
|
||||
message_type = Message.MessageType.RESPONSE_REJECT
|
||||
|
||||
acp_seid: int = field(metadata=Message.SEID_METADATA)
|
||||
error_code: int = field(metadata=hci.metadata(1))
|
||||
error_code: ErrorCode = field(metadata=ErrorCode.type_metadata(1))
|
||||
|
||||
def __str__(self) -> str:
|
||||
details = [
|
||||
f'acp_seid: {self.acp_seid}',
|
||||
f'error_code: {name_or_number(AVDTP_ERROR_NAMES, self.error_code)}',
|
||||
f'error_code: {self.error_code.name}',
|
||||
]
|
||||
return self.to_string(details)
|
||||
|
||||
@@ -1210,7 +1200,7 @@ class General_Reject(Message):
|
||||
See Bluetooth AVDTP spec - 8.18 General Reject
|
||||
'''
|
||||
|
||||
signal_identifier = 0
|
||||
signal_identifier = SignalIdentifier(0)
|
||||
message_type = Message.MessageType.GENERAL_REJECT
|
||||
|
||||
def to_string(self, details):
|
||||
@@ -1286,10 +1276,6 @@ class Protocol(utils.EventEmitter):
|
||||
CONTINUE_PACKET = 2
|
||||
END_PACKET = 3
|
||||
|
||||
@staticmethod
|
||||
def packet_type_name(packet_type):
|
||||
return name_or_number(Protocol.PACKET_TYPE_NAMES, packet_type)
|
||||
|
||||
@staticmethod
|
||||
async def connect(
|
||||
connection: device.Connection, version: tuple[int, int] = (1, 3)
|
||||
@@ -1462,11 +1448,7 @@ class Protocol(utils.EventEmitter):
|
||||
|
||||
if message.message_type == Message.MessageType.COMMAND:
|
||||
# Command
|
||||
signal_name = (
|
||||
AVDTP_SIGNAL_NAMES.get(message.signal_identifier, "")
|
||||
.replace("AVDTP_", "")
|
||||
.lower()
|
||||
)
|
||||
signal_name = message.signal_identifier.name.lower()
|
||||
handler_name = f'on_{signal_name}_command'
|
||||
handler = getattr(self, handler_name, None)
|
||||
if handler:
|
||||
@@ -1649,11 +1631,11 @@ class Protocol(utils.EventEmitter):
|
||||
) -> Optional[Message]:
|
||||
endpoint = self.get_local_endpoint_by_seid(command.acp_seid)
|
||||
if endpoint is None:
|
||||
return Set_Configuration_Reject(AVDTP_BAD_ACP_SEID_ERROR)
|
||||
return Set_Configuration_Reject(error_code=AVDTP_BAD_ACP_SEID_ERROR)
|
||||
|
||||
# Check that the local endpoint isn't in use
|
||||
if endpoint.in_use:
|
||||
return Set_Configuration_Reject(AVDTP_SEP_IN_USE_ERROR)
|
||||
return Set_Configuration_Reject(error_code=AVDTP_SEP_IN_USE_ERROR)
|
||||
|
||||
# Create a stream object for the pair of endpoints
|
||||
stream = Stream(self, endpoint, StreamEndPointProxy(self, command.int_seid))
|
||||
@@ -1676,9 +1658,9 @@ class Protocol(utils.EventEmitter):
|
||||
def on_reconfigure_command(self, command: Reconfigure_Command) -> Optional[Message]:
|
||||
endpoint = self.get_local_endpoint_by_seid(command.acp_seid)
|
||||
if endpoint is None:
|
||||
return Reconfigure_Reject(0, AVDTP_BAD_ACP_SEID_ERROR)
|
||||
return Reconfigure_Reject(error_code=AVDTP_BAD_ACP_SEID_ERROR)
|
||||
if endpoint.stream is None:
|
||||
return Reconfigure_Reject(0, AVDTP_BAD_STATE_ERROR)
|
||||
return Reconfigure_Reject(error_code=AVDTP_BAD_STATE_ERROR)
|
||||
|
||||
result = endpoint.stream.on_reconfigure_command(command.capabilities)
|
||||
return result or Reconfigure_Response()
|
||||
@@ -1842,12 +1824,8 @@ class Stream:
|
||||
|
||||
rtp_channel: Optional[l2cap.ClassicChannel]
|
||||
|
||||
@staticmethod
|
||||
def state_name(state: int) -> str:
|
||||
return name_or_number(AVDTP_STATE_NAMES, state)
|
||||
|
||||
def change_state(self, state: int) -> None:
|
||||
logger.debug(f'{self} state change -> {color(self.state_name(state), "cyan")}')
|
||||
def change_state(self, state: State) -> None:
|
||||
logger.debug(f'{self} state change -> {color(state.name, "cyan")}')
|
||||
self.state = state
|
||||
|
||||
def send_media_packet(self, packet: MediaPacket) -> None:
|
||||
@@ -1855,22 +1833,22 @@ class Stream:
|
||||
self.rtp_channel.send_pdu(bytes(packet))
|
||||
|
||||
async def configure(self) -> None:
|
||||
if self.state != AVDTP_IDLE_STATE:
|
||||
if self.state != State.IDLE:
|
||||
raise InvalidStateError('current state is not IDLE')
|
||||
|
||||
await self.remote_endpoint.set_configuration(
|
||||
self.local_endpoint.seid, self.local_endpoint.configuration
|
||||
)
|
||||
self.change_state(AVDTP_CONFIGURED_STATE)
|
||||
self.change_state(State.CONFIGURED)
|
||||
|
||||
async def open(self) -> None:
|
||||
if self.state != AVDTP_CONFIGURED_STATE:
|
||||
if self.state != State.CONFIGURED:
|
||||
raise InvalidStateError('current state is not CONFIGURED')
|
||||
|
||||
logger.debug('opening remote endpoint')
|
||||
await self.remote_endpoint.open()
|
||||
|
||||
self.change_state(AVDTP_OPEN_STATE)
|
||||
self.change_state(State.OPEN)
|
||||
|
||||
# Create a channel for RTP packets
|
||||
self.rtp_channel = (
|
||||
@@ -1882,10 +1860,10 @@ class Stream:
|
||||
async def start(self) -> None:
|
||||
"""[Source] Start streaming."""
|
||||
# Auto-open if needed
|
||||
if self.state == AVDTP_CONFIGURED_STATE:
|
||||
if self.state == State.CONFIGURED:
|
||||
await self.open()
|
||||
|
||||
if self.state != AVDTP_OPEN_STATE:
|
||||
if self.state != State.OPEN:
|
||||
raise InvalidStateError('current state is not OPEN')
|
||||
|
||||
logger.debug('starting remote endpoint')
|
||||
@@ -1894,11 +1872,11 @@ class Stream:
|
||||
logger.debug('starting local endpoint')
|
||||
await self.local_endpoint.start()
|
||||
|
||||
self.change_state(AVDTP_STREAMING_STATE)
|
||||
self.change_state(State.STREAMING)
|
||||
|
||||
async def stop(self) -> None:
|
||||
"""[Source] Stop streaming and transit to OPEN state."""
|
||||
if self.state != AVDTP_STREAMING_STATE:
|
||||
if self.state != State.STREAMING:
|
||||
raise InvalidStateError('current state is not STREAMING')
|
||||
|
||||
logger.debug('stopping local endpoint')
|
||||
@@ -1907,11 +1885,11 @@ class Stream:
|
||||
logger.debug('stopping remote endpoint')
|
||||
await self.remote_endpoint.stop()
|
||||
|
||||
self.change_state(AVDTP_OPEN_STATE)
|
||||
self.change_state(State.OPEN)
|
||||
|
||||
async def close(self) -> None:
|
||||
"""[Source] Close channel and transit to IDLE state."""
|
||||
if self.state not in (AVDTP_OPEN_STATE, AVDTP_STREAMING_STATE):
|
||||
if self.state not in (State.OPEN, State.STREAMING):
|
||||
raise InvalidStateError('current state is not OPEN or STREAMING')
|
||||
|
||||
logger.debug('closing local endpoint')
|
||||
@@ -1921,7 +1899,7 @@ class Stream:
|
||||
await self.remote_endpoint.close()
|
||||
|
||||
# Release any channels we may have created
|
||||
self.change_state(AVDTP_CLOSING_STATE)
|
||||
self.change_state(State.CLOSING)
|
||||
if self.rtp_channel:
|
||||
await self.rtp_channel.disconnect()
|
||||
self.rtp_channel = None
|
||||
@@ -1929,31 +1907,31 @@ class Stream:
|
||||
# Release the endpoint
|
||||
self.local_endpoint.in_use = 0
|
||||
|
||||
self.change_state(AVDTP_IDLE_STATE)
|
||||
self.change_state(State.IDLE)
|
||||
|
||||
def on_set_configuration_command(self, configuration):
|
||||
if self.state != AVDTP_IDLE_STATE:
|
||||
if self.state != State.IDLE:
|
||||
return Set_Configuration_Reject(AVDTP_BAD_STATE_ERROR)
|
||||
|
||||
result = self.local_endpoint.on_set_configuration_command(configuration)
|
||||
if result is not None:
|
||||
return result
|
||||
|
||||
self.change_state(AVDTP_CONFIGURED_STATE)
|
||||
self.change_state(State.CONFIGURED)
|
||||
return None
|
||||
|
||||
def on_get_configuration_command(self):
|
||||
if self.state not in (
|
||||
AVDTP_CONFIGURED_STATE,
|
||||
AVDTP_OPEN_STATE,
|
||||
AVDTP_STREAMING_STATE,
|
||||
State.CONFIGURED,
|
||||
State.OPEN,
|
||||
State.STREAMING,
|
||||
):
|
||||
return Get_Configuration_Reject(AVDTP_BAD_STATE_ERROR)
|
||||
|
||||
return self.local_endpoint.on_get_configuration_command()
|
||||
|
||||
def on_reconfigure_command(self, configuration):
|
||||
if self.state != AVDTP_OPEN_STATE:
|
||||
if self.state != State.OPEN:
|
||||
return Reconfigure_Reject(AVDTP_BAD_STATE_ERROR)
|
||||
|
||||
result = self.local_endpoint.on_reconfigure_command(configuration)
|
||||
@@ -1963,7 +1941,7 @@ class Stream:
|
||||
return None
|
||||
|
||||
def on_open_command(self):
|
||||
if self.state != AVDTP_CONFIGURED_STATE:
|
||||
if self.state != State.CONFIGURED:
|
||||
return Open_Reject(AVDTP_BAD_STATE_ERROR)
|
||||
|
||||
result = self.local_endpoint.on_open_command()
|
||||
@@ -1973,11 +1951,11 @@ class Stream:
|
||||
# Register to accept the next channel
|
||||
self.protocol.channel_acceptor = self
|
||||
|
||||
self.change_state(AVDTP_OPEN_STATE)
|
||||
self.change_state(State.OPEN)
|
||||
return None
|
||||
|
||||
def on_start_command(self):
|
||||
if self.state != AVDTP_OPEN_STATE:
|
||||
if self.state != State.OPEN:
|
||||
return Open_Reject(AVDTP_BAD_STATE_ERROR)
|
||||
|
||||
# Check that we have an RTP channel
|
||||
@@ -1989,33 +1967,33 @@ class Stream:
|
||||
if result is not None:
|
||||
return result
|
||||
|
||||
self.change_state(AVDTP_STREAMING_STATE)
|
||||
self.change_state(State.STREAMING)
|
||||
return None
|
||||
|
||||
def on_suspend_command(self):
|
||||
if self.state != AVDTP_STREAMING_STATE:
|
||||
if self.state != State.STREAMING:
|
||||
return Open_Reject(AVDTP_BAD_STATE_ERROR)
|
||||
|
||||
result = self.local_endpoint.on_suspend_command()
|
||||
if result is not None:
|
||||
return result
|
||||
|
||||
self.change_state(AVDTP_OPEN_STATE)
|
||||
self.change_state(State.OPEN)
|
||||
return None
|
||||
|
||||
def on_close_command(self):
|
||||
if self.state not in (AVDTP_OPEN_STATE, AVDTP_STREAMING_STATE):
|
||||
if self.state not in (State.OPEN, State.STREAMING):
|
||||
return Open_Reject(AVDTP_BAD_STATE_ERROR)
|
||||
|
||||
result = self.local_endpoint.on_close_command()
|
||||
if result is not None:
|
||||
return result
|
||||
|
||||
self.change_state(AVDTP_CLOSING_STATE)
|
||||
self.change_state(State.CLOSING)
|
||||
|
||||
if self.rtp_channel is None:
|
||||
# No channel to release, we're done
|
||||
self.change_state(AVDTP_IDLE_STATE)
|
||||
self.change_state(State.IDLE)
|
||||
else:
|
||||
# TODO: set a timer as we wait for the RTP channel to be closed
|
||||
pass
|
||||
@@ -2025,10 +2003,10 @@ class Stream:
|
||||
def on_abort_command(self):
|
||||
if self.rtp_channel is None:
|
||||
# No need to wait
|
||||
self.change_state(AVDTP_IDLE_STATE)
|
||||
self.change_state(State.IDLE)
|
||||
else:
|
||||
# Wait for the RTP channel to be closed
|
||||
self.change_state(AVDTP_ABORTING_STATE)
|
||||
self.change_state(State.ABORTING)
|
||||
|
||||
def on_l2cap_connection(self, channel: l2cap.ClassicChannel) -> None:
|
||||
logger.debug(color('<<< stream channel connected', 'magenta'))
|
||||
@@ -2049,8 +2027,8 @@ class Stream:
|
||||
self.local_endpoint.in_use = 0
|
||||
self.rtp_channel = None
|
||||
|
||||
if self.state in (AVDTP_CLOSING_STATE, AVDTP_ABORTING_STATE):
|
||||
self.change_state(AVDTP_IDLE_STATE)
|
||||
if self.state in (State.CLOSING, State.ABORTING):
|
||||
self.change_state(State.IDLE)
|
||||
else:
|
||||
logger.warning('unexpected channel close while not CLOSING or ABORTING')
|
||||
|
||||
@@ -2068,7 +2046,7 @@ class Stream:
|
||||
self.local_endpoint = local_endpoint
|
||||
self.remote_endpoint = remote_endpoint
|
||||
self.rtp_channel = None
|
||||
self.state = AVDTP_IDLE_STATE
|
||||
self.state = State.IDLE
|
||||
|
||||
local_endpoint.stream = self
|
||||
local_endpoint.in_use = 1
|
||||
@@ -2076,7 +2054,7 @@ class Stream:
|
||||
def __str__(self) -> str:
|
||||
return (
|
||||
f'Stream({self.local_endpoint.seid} -> '
|
||||
f'{self.remote_endpoint.seid} {self.state_name(self.state)})'
|
||||
f'{self.remote_endpoint.seid} {self.state.name})'
|
||||
)
|
||||
|
||||
|
||||
@@ -2085,7 +2063,7 @@ class Stream:
|
||||
class StreamEndPoint:
|
||||
seid: int
|
||||
media_type: MediaType
|
||||
tsep: int
|
||||
tsep: StreamEndPointType
|
||||
in_use: int
|
||||
capabilities: Iterable[ServiceCapabilities]
|
||||
|
||||
@@ -2124,7 +2102,7 @@ class DiscoveredStreamEndPoint(StreamEndPoint, StreamEndPointProxy):
|
||||
protocol: Protocol,
|
||||
seid: int,
|
||||
media_type: MediaType,
|
||||
tsep: int,
|
||||
tsep: StreamEndPointType,
|
||||
in_use: int,
|
||||
capabilities: Iterable[ServiceCapabilities],
|
||||
) -> None:
|
||||
@@ -2154,7 +2132,7 @@ class LocalStreamEndPoint(StreamEndPoint, utils.EventEmitter):
|
||||
protocol: Protocol,
|
||||
seid: int,
|
||||
media_type: MediaType,
|
||||
tsep: int,
|
||||
tsep: StreamEndPointType,
|
||||
capabilities: Iterable[ServiceCapabilities],
|
||||
configuration: Optional[Iterable[ServiceCapabilities]] = None,
|
||||
):
|
||||
|
||||
@@ -23,17 +23,7 @@ from typing import Awaitable
|
||||
|
||||
import pytest
|
||||
|
||||
from bumble import a2dp
|
||||
from bumble.avdtp import (
|
||||
AVDTP_AUDIO_MEDIA_TYPE,
|
||||
AVDTP_IDLE_STATE,
|
||||
AVDTP_STREAMING_STATE,
|
||||
AVDTP_TSEP_SNK,
|
||||
Listener,
|
||||
MediaCodecCapabilities,
|
||||
MediaPacketPump,
|
||||
Protocol,
|
||||
)
|
||||
from bumble import a2dp, avdtp
|
||||
from bumble.controller import Controller
|
||||
from bumble.core import PhysicalTransport
|
||||
from bumble.device import Device
|
||||
@@ -134,8 +124,8 @@ async def test_self_connection():
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
def source_codec_capabilities():
|
||||
return MediaCodecCapabilities(
|
||||
media_type=AVDTP_AUDIO_MEDIA_TYPE,
|
||||
return avdtp.MediaCodecCapabilities(
|
||||
media_type=avdtp.MediaType.AUDIO,
|
||||
media_codec_type=a2dp.CodecType.SBC,
|
||||
media_codec_information=a2dp.SbcMediaCodecInformation(
|
||||
sampling_frequency=a2dp.SbcMediaCodecInformation.SamplingFrequency.SF_44100,
|
||||
@@ -151,8 +141,8 @@ def source_codec_capabilities():
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
def sink_codec_capabilities():
|
||||
return MediaCodecCapabilities(
|
||||
media_type=AVDTP_AUDIO_MEDIA_TYPE,
|
||||
return avdtp.MediaCodecCapabilities(
|
||||
media_type=avdtp.MediaType.AUDIO,
|
||||
media_codec_type=a2dp.CodecType.SBC,
|
||||
media_codec_information=a2dp.SbcMediaCodecInformation(
|
||||
sampling_frequency=a2dp.SbcMediaCodecInformation.SamplingFrequency.SF_48000
|
||||
@@ -200,7 +190,7 @@ async def test_source_sink_1():
|
||||
sink.on('rtp_packet', on_rtp_packet)
|
||||
|
||||
# Create a listener to wait for AVDTP connections
|
||||
listener = Listener.for_device(two_devices.devices[1])
|
||||
listener = avdtp.Listener.for_device(two_devices.devices[1])
|
||||
listener.on('connection', on_avdtp_connection)
|
||||
|
||||
async def make_connection():
|
||||
@@ -213,13 +203,13 @@ async def test_source_sink_1():
|
||||
return connections[0]
|
||||
|
||||
connection = await make_connection()
|
||||
client = await Protocol.connect(connection)
|
||||
client = await avdtp.Protocol.connect(connection)
|
||||
endpoints = await client.discover_remote_endpoints()
|
||||
assert len(endpoints) == 1
|
||||
remote_sink = list(endpoints)[0]
|
||||
assert remote_sink.in_use == 0
|
||||
assert remote_sink.media_type == AVDTP_AUDIO_MEDIA_TYPE
|
||||
assert remote_sink.tsep == AVDTP_TSEP_SNK
|
||||
assert remote_sink.media_type == avdtp.MediaType.AUDIO
|
||||
assert remote_sink.tsep == avdtp.StreamEndPointType.SNK
|
||||
|
||||
async def generate_packets(packet_count):
|
||||
sequence_number = 0
|
||||
@@ -238,24 +228,24 @@ async def test_source_sink_1():
|
||||
rtp_packets_fully_received = asyncio.get_running_loop().create_future()
|
||||
rtp_packets_expected = 3
|
||||
rtp_packets = []
|
||||
pump = MediaPacketPump(generate_packets(3))
|
||||
pump = avdtp.MediaPacketPump(generate_packets(3))
|
||||
source = client.add_source(source_codec_capabilities(), pump)
|
||||
stream = await client.create_stream(source, remote_sink)
|
||||
await stream.start()
|
||||
assert stream.state == AVDTP_STREAMING_STATE
|
||||
assert stream.state == avdtp.State.STREAMING
|
||||
assert stream.local_endpoint.in_use == 1
|
||||
assert stream.rtp_channel is not None
|
||||
assert sink.in_use == 1
|
||||
assert sink.stream is not None
|
||||
assert sink.stream.state == AVDTP_STREAMING_STATE
|
||||
assert sink.stream.state == avdtp.State.STREAMING
|
||||
await rtp_packets_fully_received
|
||||
|
||||
await stream.close()
|
||||
assert stream.rtp_channel is None
|
||||
assert source.in_use == 0
|
||||
assert source.stream.state == AVDTP_IDLE_STATE
|
||||
assert source.stream.state == avdtp.State.IDLE
|
||||
assert sink.in_use == 0
|
||||
assert sink.stream.state == AVDTP_IDLE_STATE
|
||||
assert sink.stream.state == avdtp.State.IDLE
|
||||
|
||||
# Send packets manually
|
||||
rtp_packets_fully_received = asyncio.get_running_loop().create_future()
|
||||
@@ -267,12 +257,12 @@ async def test_source_sink_1():
|
||||
source = client.add_source(source_codec_capabilities(), None)
|
||||
stream = await client.create_stream(source, remote_sink)
|
||||
await stream.start()
|
||||
assert stream.state == AVDTP_STREAMING_STATE
|
||||
assert stream.state == avdtp.State.STREAMING
|
||||
assert stream.local_endpoint.in_use == 1
|
||||
assert stream.rtp_channel is not None
|
||||
assert sink.in_use == 1
|
||||
assert sink.stream is not None
|
||||
assert sink.stream.state == AVDTP_STREAMING_STATE
|
||||
assert sink.stream.state == avdtp.State.STREAMING
|
||||
|
||||
stream.send_media_packet(source_packets[0])
|
||||
stream.send_media_packet(source_packets[1])
|
||||
@@ -282,9 +272,9 @@ async def test_source_sink_1():
|
||||
assert stream.rtp_channel is None
|
||||
assert len(rtp_packets) == 3
|
||||
assert source.in_use == 0
|
||||
assert source.stream.state == AVDTP_IDLE_STATE
|
||||
assert source.stream.state == avdtp.State.IDLE
|
||||
assert sink.in_use == 0
|
||||
assert sink.stream.state == AVDTP_IDLE_STATE
|
||||
assert sink.stream.state == avdtp.State.IDLE
|
||||
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
|
||||
@@ -30,7 +30,10 @@ from bumble.rtp import MediaPacket
|
||||
avdtp.Discover_Response(
|
||||
endpoints=[
|
||||
avdtp.EndPointInfo(
|
||||
seid=1, in_use=1, media_type=avdtp.MediaType.AUDIO, tsep=1
|
||||
seid=1,
|
||||
in_use=1,
|
||||
media_type=avdtp.MediaType.AUDIO,
|
||||
tsep=avdtp.StreamEndPointType.SNK,
|
||||
)
|
||||
]
|
||||
),
|
||||
|
||||
Reference in New Issue
Block a user