Replace long if-else with match-case

This commit is contained in:
Josh Wu
2026-02-05 15:56:38 +08:00
parent d810d93aaf
commit 3d5648cdc3
11 changed files with 451 additions and 432 deletions

View File

@@ -267,11 +267,12 @@ class MediaCodecInformation:
def create( def create(
cls, media_codec_type: int, data: bytes cls, media_codec_type: int, data: bytes
) -> MediaCodecInformation | bytes: ) -> MediaCodecInformation | bytes:
if media_codec_type == CodecType.SBC: match media_codec_type:
case CodecType.SBC:
return SbcMediaCodecInformation.from_bytes(data) return SbcMediaCodecInformation.from_bytes(data)
elif media_codec_type == CodecType.MPEG_2_4_AAC: case CodecType.MPEG_2_4_AAC:
return AacMediaCodecInformation.from_bytes(data) return AacMediaCodecInformation.from_bytes(data)
elif media_codec_type == CodecType.NON_A2DP: case CodecType.NON_A2DP:
vendor_media_codec_information = ( vendor_media_codec_information = (
VendorSpecificMediaCodecInformation.from_bytes(data) VendorSpecificMediaCodecInformation.from_bytes(data)
) )

View File

@@ -27,7 +27,7 @@ def tokenize_parameters(buffer: bytes) -> list[bytes]:
are ignored [..], unless they are embedded in numeric or string constants" are ignored [..], unless they are embedded in numeric or string constants"
Raises AtParsingError in case of invalid input string.""" Raises AtParsingError in case of invalid input string."""
tokens = [] tokens: list[bytearray] = []
in_quotes = False in_quotes = False
token = bytearray() token = bytearray()
for b in buffer: for b in buffer:
@@ -40,22 +40,23 @@ def tokenize_parameters(buffer: bytes) -> list[bytes]:
tokens.append(token[1:-1]) tokens.append(token[1:-1])
token = bytearray() token = bytearray()
else: else:
if char == b' ': match char:
case b' ':
pass pass
elif char == b',' or char == b')': case b',' | b')':
tokens.append(token) tokens.append(token)
tokens.append(char) tokens.append(char)
token = bytearray() token = bytearray()
elif char == b'(': case b'(':
if len(token) > 0: if len(token) > 0:
raise AtParsingError("open_paren following regular character") raise AtParsingError("open_paren following regular character")
tokens.append(char) tokens.append(char)
elif char == b'"': case b'"':
if len(token) > 0: if len(token) > 0:
raise AtParsingError("quote following regular character") raise AtParsingError("quote following regular character")
in_quotes = True in_quotes = True
token.extend(char) token.extend(char)
else: case _:
token.extend(char) token.extend(char)
tokens.append(token) tokens.append(token)
@@ -71,17 +72,18 @@ def parse_parameters(buffer: bytes) -> list[bytes | list]:
current: bytes | list = b'' current: bytes | list = b''
for token in tokens: for token in tokens:
if token == b',': match token:
case b',':
accumulator[-1].append(current) accumulator[-1].append(current)
current = b'' current = b''
elif token == b'(': case b'(':
accumulator.append([]) accumulator.append([])
elif token == b')': case b')':
if len(accumulator) < 2: if len(accumulator) < 2:
raise AtParsingError("close_paren without matching open_paren") raise AtParsingError("close_paren without matching open_paren")
accumulator[-1].append(current) accumulator[-1].append(current)
current = accumulator.pop() current = accumulator.pop()
else: case _:
current = token current = token
accumulator[-1].append(current) accumulator[-1].append(current)

View File

@@ -954,11 +954,12 @@ class Attribute(utils.EventEmitter, Generic[_T]):
self.permissions = permissions self.permissions = permissions
# Convert the type to a UUID object if it isn't already # Convert the type to a UUID object if it isn't already
if isinstance(attribute_type, str): match attribute_type:
case str():
self.type = UUID(attribute_type) self.type = UUID(attribute_type)
elif isinstance(attribute_type, bytes): case bytes():
self.type = UUID.from_bytes(attribute_type) self.type = UUID.from_bytes(attribute_type)
else: case _:
self.type = attribute_type self.type = attribute_type
self.value = value self.value = value
@@ -994,7 +995,8 @@ class Attribute(utils.EventEmitter, Generic[_T]):
) )
value: _T | None value: _T | None
if isinstance(self.value, AttributeValue): match self.value:
case AttributeValue():
try: try:
read_value = self.value.read(connection) read_value = self.value.read(connection)
if inspect.isawaitable(read_value): if inspect.isawaitable(read_value):
@@ -1005,7 +1007,7 @@ class Attribute(utils.EventEmitter, Generic[_T]):
raise ATT_Error( raise ATT_Error(
error_code=error.error_code, att_handle=self.handle error_code=error.error_code, att_handle=self.handle
) from error ) from error
elif isinstance(self.value, AttributeValueV2): case AttributeValueV2():
try: try:
read_value = self.value.read(bearer) read_value = self.value.read(bearer)
if inspect.isawaitable(read_value): if inspect.isawaitable(read_value):
@@ -1016,7 +1018,7 @@ class Attribute(utils.EventEmitter, Generic[_T]):
raise ATT_Error( raise ATT_Error(
error_code=error.error_code, att_handle=self.handle error_code=error.error_code, att_handle=self.handle
) from error ) from error
else: case _:
value = self.value value = self.value
self.emit(self.EVENT_READ, connection, b'' if value is None else value) self.emit(self.EVENT_READ, connection, b'' if value is None else value)
@@ -1049,7 +1051,8 @@ class Attribute(utils.EventEmitter, Generic[_T]):
decoded_value = self.decode_value(value) decoded_value = self.decode_value(value)
if isinstance(self.value, AttributeValue): match self.value:
case AttributeValue():
try: try:
result = self.value.write(connection, decoded_value) result = self.value.write(connection, decoded_value)
if inspect.isawaitable(result): if inspect.isawaitable(result):
@@ -1058,7 +1061,7 @@ class Attribute(utils.EventEmitter, Generic[_T]):
raise ATT_Error( raise ATT_Error(
error_code=error.error_code, att_handle=self.handle error_code=error.error_code, att_handle=self.handle
) from error ) from error
elif isinstance(self.value, AttributeValueV2): case AttributeValueV2():
try: try:
result = self.value.write(bearer, decoded_value) result = self.value.write(bearer, decoded_value)
if inspect.isawaitable(result): if inspect.isawaitable(result):
@@ -1067,7 +1070,7 @@ class Attribute(utils.EventEmitter, Generic[_T]):
raise ATT_Error( raise ATT_Error(
error_code=error.error_code, att_handle=self.handle error_code=error.error_code, att_handle=self.handle
) from error ) from error
else: case _:
self.value = decoded_value self.value = decoded_value
self.emit(self.EVENT_WRITE, connection, decoded_value) self.emit(self.EVENT_WRITE, connection, decoded_value)

View File

@@ -403,13 +403,14 @@ class Controller:
) )
# If the packet is a command, invoke the handler for this packet # If the packet is a command, invoke the handler for this packet
if isinstance(packet, hci.HCI_Command): match packet:
case hci.HCI_Command():
self.on_hci_command_packet(packet) self.on_hci_command_packet(packet)
elif isinstance(packet, hci.HCI_AclDataPacket): case hci.HCI_AclDataPacket():
self.on_hci_acl_data_packet(packet) self.on_hci_acl_data_packet(packet)
elif isinstance(packet, hci.HCI_Event): case hci.HCI_Event():
self.on_hci_event_packet(packet) self.on_hci_event_packet(packet)
else: case _:
logger.warning(f'!!! unknown packet type {packet.hci_packet_type}') logger.warning(f'!!! unknown packet type {packet.hci_packet_type}')
def on_hci_command_packet(self, command: hci.HCI_Command) -> None: def on_hci_command_packet(self, command: hci.HCI_Command) -> None:
@@ -517,25 +518,27 @@ class Controller:
logger.error("Cannot find a connection for %s", sender_address) logger.error("Cannot find a connection for %s", sender_address)
return return
if isinstance(packet, ll.TerminateInd): match packet:
case ll.TerminateInd():
self.on_le_disconnected(connection, packet.error_code) self.on_le_disconnected(connection, packet.error_code)
elif isinstance(packet, ll.CisReq): case ll.CisReq():
self.on_le_cis_request(connection, packet.cig_id, packet.cis_id) self.on_le_cis_request(connection, packet.cig_id, packet.cis_id)
elif isinstance(packet, ll.CisRsp): case ll.CisRsp():
self.on_le_cis_established(packet.cig_id, packet.cis_id) self.on_le_cis_established(packet.cig_id, packet.cis_id)
connection.send_ll_control_pdu(ll.CisInd(packet.cig_id, packet.cis_id)) connection.send_ll_control_pdu(ll.CisInd(packet.cig_id, packet.cis_id))
elif isinstance(packet, ll.CisInd): case ll.CisInd():
self.on_le_cis_established(packet.cig_id, packet.cis_id) self.on_le_cis_established(packet.cig_id, packet.cis_id)
elif isinstance(packet, ll.CisTerminateInd): case ll.CisTerminateInd():
self.on_le_cis_disconnected(packet.cig_id, packet.cis_id) self.on_le_cis_disconnected(packet.cig_id, packet.cis_id)
elif isinstance(packet, ll.EncReq): case ll.EncReq():
self.on_le_encrypted(connection) self.on_le_encrypted(connection)
def on_ll_advertising_pdu(self, packet: ll.AdvertisingPdu) -> None: def on_ll_advertising_pdu(self, packet: ll.AdvertisingPdu) -> None:
logger.debug("[%s] <<< Advertising PDU: %s", self.name, packet) logger.debug("[%s] <<< Advertising PDU: %s", self.name, packet)
if isinstance(packet, ll.ConnectInd): match packet:
case ll.ConnectInd():
self.on_le_connect_ind(packet) self.on_le_connect_ind(packet)
elif isinstance(packet, (ll.AdvInd, ll.AdvExtInd)): case ll.AdvInd() | ll.AdvExtInd():
self.on_advertising_pdu(packet) self.on_advertising_pdu(packet)
def on_le_connect_ind(self, packet: ll.ConnectInd) -> None: def on_le_connect_ind(self, packet: ll.ConnectInd) -> None:
@@ -894,50 +897,51 @@ class Controller:
return future return future
def on_lmp_packet(self, sender_address: hci.Address, packet: lmp.Packet): def on_lmp_packet(self, sender_address: hci.Address, packet: lmp.Packet):
if isinstance(packet, (lmp.LmpAccepted, lmp.LmpAcceptedExt)): match packet:
case lmp.LmpAccepted() | lmp.LmpAcceptedExt():
if future := self.classic_pending_commands.setdefault( if future := self.classic_pending_commands.setdefault(
sender_address, {} sender_address, {}
).get(packet.response_opcode): ).get(packet.response_opcode):
future.set_result(hci.HCI_SUCCESS) future.set_result(hci.HCI_SUCCESS)
else: else:
logger.error("!!! Unhandled packet: %s", packet) logger.error("!!! Unhandled packet: %s", packet)
elif isinstance(packet, (lmp.LmpNotAccepted, lmp.LmpNotAcceptedExt)): case lmp.LmpNotAccepted() | lmp.LmpNotAcceptedExt():
if future := self.classic_pending_commands.setdefault( if future := self.classic_pending_commands.setdefault(
sender_address, {} sender_address, {}
).get(packet.response_opcode): ).get(packet.response_opcode):
future.set_result(packet.error_code) future.set_result(packet.error_code)
else: else:
logger.error("!!! Unhandled packet: %s", packet) logger.error("!!! Unhandled packet: %s", packet)
elif isinstance(packet, (lmp.LmpHostConnectionReq)): case lmp.LmpHostConnectionReq():
self.on_classic_connection_request( self.on_classic_connection_request(
sender_address, hci.HCI_Connection_Complete_Event.LinkType.ACL sender_address, hci.HCI_Connection_Complete_Event.LinkType.ACL
) )
elif isinstance(packet, (lmp.LmpScoLinkReq)): case lmp.LmpScoLinkReq():
self.on_classic_connection_request( self.on_classic_connection_request(
sender_address, hci.HCI_Connection_Complete_Event.LinkType.SCO sender_address, hci.HCI_Connection_Complete_Event.LinkType.SCO
) )
elif isinstance(packet, (lmp.LmpEscoLinkReq)): case lmp.LmpEscoLinkReq():
self.on_classic_connection_request( self.on_classic_connection_request(
sender_address, hci.HCI_Connection_Complete_Event.LinkType.ESCO sender_address, hci.HCI_Connection_Complete_Event.LinkType.ESCO
) )
elif isinstance(packet, (lmp.LmpDetach)): case lmp.LmpDetach():
self.on_classic_disconnected( self.on_classic_disconnected(
sender_address, hci.HCI_REMOTE_USER_TERMINATED_CONNECTION_ERROR sender_address, hci.HCI_REMOTE_USER_TERMINATED_CONNECTION_ERROR
) )
elif isinstance(packet, (lmp.LmpSwitchReq)): case lmp.LmpSwitchReq():
self.on_classic_role_change_request(sender_address) self.on_classic_role_change_request(sender_address)
elif isinstance(packet, (lmp.LmpRemoveScoLinkReq, lmp.LmpRemoveEscoLinkReq)): case lmp.LmpRemoveScoLinkReq() | lmp.LmpRemoveEscoLinkReq():
self.on_classic_sco_disconnected(sender_address, packet.error_code) self.on_classic_sco_disconnected(sender_address, packet.error_code)
elif isinstance(packet, lmp.LmpNameReq): case lmp.LmpNameReq():
self.on_classic_remote_name_request(sender_address, packet.name_offset) self.on_classic_remote_name_request(sender_address, packet.name_offset)
elif isinstance(packet, lmp.LmpNameRes): case lmp.LmpNameRes():
self.on_classic_remote_name_response( self.on_classic_remote_name_response(
sender_address, sender_address,
packet.name_offset, packet.name_offset,
packet.name_length, packet.name_length,
packet.name_fregment, packet.name_fregment,
) )
else: case _:
logger.error("!!! Unhandled packet: %s", packet) logger.error("!!! Unhandled packet: %s", packet)
def on_classic_connection_request( def on_classic_connection_request(

View File

@@ -280,13 +280,14 @@ class UUID:
if not force_128: if not force_128:
return self.uuid_bytes return self.uuid_bytes
if len(self.uuid_bytes) == 2: match len(self.uuid_bytes):
case 2:
return self.BASE_UUID + self.uuid_bytes + bytes([0, 0]) return self.BASE_UUID + self.uuid_bytes + bytes([0, 0])
elif len(self.uuid_bytes) == 4: case 4:
return self.BASE_UUID + self.uuid_bytes return self.BASE_UUID + self.uuid_bytes
elif len(self.uuid_bytes) == 16: case 16:
return self.uuid_bytes return self.uuid_bytes
else: case _:
assert False, "unreachable" assert False, "unreachable"
def to_pdu_bytes(self) -> bytes: def to_pdu_bytes(self) -> bytes:
@@ -1769,64 +1770,69 @@ class AdvertisingData:
@classmethod @classmethod
def ad_data_to_string(cls, ad_type: int, ad_data: bytes) -> str: def ad_data_to_string(cls, ad_type: int, ad_data: bytes) -> str:
if ad_type == AdvertisingData.FLAGS: match ad_type:
case AdvertisingData.FLAGS:
ad_type_str = 'Flags' ad_type_str = 'Flags'
ad_data_str = AdvertisingData.flags_to_string(ad_data[0], short=True) ad_data_str = AdvertisingData.flags_to_string(ad_data[0], short=True)
elif ad_type == AdvertisingData.COMPLETE_LIST_OF_16_BIT_SERVICE_CLASS_UUIDS: case AdvertisingData.COMPLETE_LIST_OF_16_BIT_SERVICE_CLASS_UUIDS:
ad_type_str = 'Complete List of 16-bit Service Class UUIDs' ad_type_str = 'Complete List of 16-bit Service Class UUIDs'
ad_data_str = AdvertisingData.uuid_list_to_string(ad_data, 2) ad_data_str = AdvertisingData.uuid_list_to_string(ad_data, 2)
elif ad_type == AdvertisingData.INCOMPLETE_LIST_OF_16_BIT_SERVICE_CLASS_UUIDS: case AdvertisingData.INCOMPLETE_LIST_OF_16_BIT_SERVICE_CLASS_UUIDS:
ad_type_str = 'Incomplete List of 16-bit Service Class UUIDs' ad_type_str = 'Incomplete List of 16-bit Service Class UUIDs'
ad_data_str = AdvertisingData.uuid_list_to_string(ad_data, 2) ad_data_str = AdvertisingData.uuid_list_to_string(ad_data, 2)
elif ad_type == AdvertisingData.COMPLETE_LIST_OF_32_BIT_SERVICE_CLASS_UUIDS: case AdvertisingData.COMPLETE_LIST_OF_32_BIT_SERVICE_CLASS_UUIDS:
ad_type_str = 'Complete List of 32-bit Service Class UUIDs' ad_type_str = 'Complete List of 32-bit Service Class UUIDs'
ad_data_str = AdvertisingData.uuid_list_to_string(ad_data, 4) ad_data_str = AdvertisingData.uuid_list_to_string(ad_data, 4)
elif ad_type == AdvertisingData.INCOMPLETE_LIST_OF_32_BIT_SERVICE_CLASS_UUIDS: case AdvertisingData.INCOMPLETE_LIST_OF_32_BIT_SERVICE_CLASS_UUIDS:
ad_type_str = 'Incomplete List of 32-bit Service Class UUIDs' ad_type_str = 'Incomplete List of 32-bit Service Class UUIDs'
ad_data_str = AdvertisingData.uuid_list_to_string(ad_data, 4) ad_data_str = AdvertisingData.uuid_list_to_string(ad_data, 4)
elif ad_type == AdvertisingData.COMPLETE_LIST_OF_128_BIT_SERVICE_CLASS_UUIDS: case AdvertisingData.COMPLETE_LIST_OF_128_BIT_SERVICE_CLASS_UUIDS:
ad_type_str = 'Complete List of 128-bit Service Class UUIDs' ad_type_str = 'Complete List of 128-bit Service Class UUIDs'
ad_data_str = AdvertisingData.uuid_list_to_string(ad_data, 16) ad_data_str = AdvertisingData.uuid_list_to_string(ad_data, 16)
elif ad_type == AdvertisingData.INCOMPLETE_LIST_OF_128_BIT_SERVICE_CLASS_UUIDS: case AdvertisingData.INCOMPLETE_LIST_OF_128_BIT_SERVICE_CLASS_UUIDS:
ad_type_str = 'Incomplete List of 128-bit Service Class UUIDs' ad_type_str = 'Incomplete List of 128-bit Service Class UUIDs'
ad_data_str = AdvertisingData.uuid_list_to_string(ad_data, 16) ad_data_str = AdvertisingData.uuid_list_to_string(ad_data, 16)
elif ad_type == AdvertisingData.SERVICE_DATA_16_BIT_UUID: case AdvertisingData.SERVICE_DATA_16_BIT_UUID:
ad_type_str = 'Service Data' ad_type_str = 'Service Data'
uuid = UUID.from_bytes(ad_data[:2]) uuid = UUID.from_bytes(ad_data[:2])
ad_data_str = f'service={uuid}, data={ad_data[2:].hex()}' ad_data_str = f'service={uuid}, data={ad_data[2:].hex()}'
elif ad_type == AdvertisingData.SERVICE_DATA_32_BIT_UUID: case AdvertisingData.SERVICE_DATA_32_BIT_UUID:
ad_type_str = 'Service Data' ad_type_str = 'Service Data'
uuid = UUID.from_bytes(ad_data[:4]) uuid = UUID.from_bytes(ad_data[:4])
ad_data_str = f'service={uuid}, data={ad_data[4:].hex()}' ad_data_str = f'service={uuid}, data={ad_data[4:].hex()}'
elif ad_type == AdvertisingData.SERVICE_DATA_128_BIT_UUID: case AdvertisingData.SERVICE_DATA_128_BIT_UUID:
ad_type_str = 'Service Data' ad_type_str = 'Service Data'
uuid = UUID.from_bytes(ad_data[:16]) uuid = UUID.from_bytes(ad_data[:16])
ad_data_str = f'service={uuid}, data={ad_data[16:].hex()}' ad_data_str = f'service={uuid}, data={ad_data[16:].hex()}'
elif ad_type == AdvertisingData.SHORTENED_LOCAL_NAME: case AdvertisingData.SHORTENED_LOCAL_NAME:
ad_type_str = 'Shortened Local Name' ad_type_str = 'Shortened Local Name'
ad_data_str = f'"{ad_data.decode("utf-8")}"' ad_data_str = f'"{ad_data.decode("utf-8")}"'
elif ad_type == AdvertisingData.COMPLETE_LOCAL_NAME: case AdvertisingData.COMPLETE_LOCAL_NAME:
ad_type_str = 'Complete Local Name' ad_type_str = 'Complete Local Name'
try: try:
ad_data_str = f'"{ad_data.decode("utf-8")}"' ad_data_str = f'"{ad_data.decode("utf-8")}"'
except UnicodeDecodeError: except UnicodeDecodeError:
ad_data_str = ad_data.hex() ad_data_str = ad_data.hex()
elif ad_type == AdvertisingData.TX_POWER_LEVEL: case AdvertisingData.TX_POWER_LEVEL:
ad_type_str = 'TX Power Level' ad_type_str = 'TX Power Level'
ad_data_str = str(ad_data[0]) ad_data_str = str(ad_data[0])
elif ad_type == AdvertisingData.MANUFACTURER_SPECIFIC_DATA: case AdvertisingData.MANUFACTURER_SPECIFIC_DATA:
ad_type_str = 'Manufacturer Specific Data' ad_type_str = 'Manufacturer Specific Data'
company_id = struct.unpack_from('<H', ad_data, 0)[0] company_id = struct.unpack_from('<H', ad_data, 0)[0]
company_name = COMPANY_IDENTIFIERS.get(company_id, f'0x{company_id:04X}') company_name = COMPANY_IDENTIFIERS.get(
company_id, f'0x{company_id:04X}'
)
ad_data_str = f'company={company_name}, data={ad_data[2:].hex()}' ad_data_str = f'company={company_name}, data={ad_data[2:].hex()}'
elif ad_type == AdvertisingData.APPEARANCE: case AdvertisingData.APPEARANCE:
ad_type_str = 'Appearance' ad_type_str = 'Appearance'
appearance = Appearance.from_int(struct.unpack_from('<H', ad_data, 0)[0]) appearance = Appearance.from_int(
struct.unpack_from('<H', ad_data, 0)[0]
)
ad_data_str = str(appearance) ad_data_str = str(appearance)
elif ad_type == AdvertisingData.BROADCAST_NAME: case AdvertisingData.BROADCAST_NAME:
ad_type_str = 'Broadcast Name' ad_type_str = 'Broadcast Name'
ad_data_str = ad_data.decode('utf-8') ad_data_str = ad_data.decode('utf-8')
else: case _:
ad_type_str = AdvertisingData.Type(ad_type).name ad_type_str = AdvertisingData.Type(ad_type).name
ad_data_str = ad_data.hex() ad_data_str = ad_data.hex()

View File

@@ -201,10 +201,11 @@ def _parse_tlv(data: bytes) -> list[tuple[ValueType, Any]]:
value = data[2 : 2 + value_length] value = data[2 : 2 + value_length]
typed_value: Any typed_value: Any
if value_type == ValueType.END: match value_type:
case ValueType.END:
break break
if value_type in (ValueType.CNVI, ValueType.CNVR): case ValueType.CNVI | ValueType.CNVR:
(v,) = struct.unpack("<I", value) (v,) = struct.unpack("<I", value)
typed_value = ( typed_value = (
(((v >> 0) & 0xF) << 12) (((v >> 0) & 0xF) << 12)
@@ -212,38 +213,38 @@ def _parse_tlv(data: bytes) -> list[tuple[ValueType, Any]]:
| (((v >> 8) & 0xF) << 4) | (((v >> 8) & 0xF) << 4)
| (((v >> 24) & 0xF) << 8) | (((v >> 24) & 0xF) << 8)
) )
elif value_type == ValueType.HARDWARE_INFO: case ValueType.HARDWARE_INFO:
(v,) = struct.unpack("<I", value) (v,) = struct.unpack("<I", value)
typed_value = HardwareInfo( typed_value = HardwareInfo(
HardwarePlatform((v >> 8) & 0xFF), HardwareVariant((v >> 16) & 0x3F) HardwarePlatform((v >> 8) & 0xFF), HardwareVariant((v >> 16) & 0x3F)
) )
elif value_type in ( case (
ValueType.USB_VENDOR_ID, ValueType.USB_VENDOR_ID
ValueType.USB_PRODUCT_ID, | ValueType.USB_PRODUCT_ID
ValueType.DEVICE_REVISION, | ValueType.DEVICE_REVISION
): ):
(typed_value,) = struct.unpack("<H", value) (typed_value,) = struct.unpack("<H", value)
elif value_type == ValueType.CURRENT_MODE_OF_OPERATION: case ValueType.CURRENT_MODE_OF_OPERATION:
typed_value = ModeOfOperation(value[0]) typed_value = ModeOfOperation(value[0])
elif value_type in ( case (
ValueType.BUILD_TYPE, ValueType.BUILD_TYPE
ValueType.BUILD_NUMBER, | ValueType.BUILD_NUMBER
ValueType.SECURE_BOOT, | ValueType.SECURE_BOOT
ValueType.OTP_LOCK, | ValueType.OTP_LOCK
ValueType.API_LOCK, | ValueType.API_LOCK
ValueType.DEBUG_LOCK, | ValueType.DEBUG_LOCK
ValueType.SECURE_BOOT_ENGINE_TYPE, | ValueType.SECURE_BOOT_ENGINE_TYPE
): ):
typed_value = value[0] typed_value = value[0]
elif value_type == ValueType.TIMESTAMP: case ValueType.TIMESTAMP:
typed_value = Timestamp(value[0], value[1]) typed_value = Timestamp(value[0], value[1])
elif value_type == ValueType.FIRMWARE_BUILD: case ValueType.FIRMWARE_BUILD:
typed_value = FirmwareBuild(value[0], Timestamp(value[1], value[2])) typed_value = FirmwareBuild(value[0], Timestamp(value[1], value[2]))
elif value_type == ValueType.BLUETOOTH_ADDRESS: case ValueType.BLUETOOTH_ADDRESS:
typed_value = hci.Address( typed_value = hci.Address(
value, address_type=hci.Address.PUBLIC_DEVICE_ADDRESS value, address_type=hci.Address.PUBLIC_DEVICE_ADDRESS
) )
else: case _:
typed_value = value typed_value = value
result.append((value_type, typed_value)) result.append((value_type, typed_value))

View File

@@ -31,6 +31,7 @@ from typing import (
ClassVar, ClassVar,
Generic, Generic,
Literal, Literal,
SupportsBytes,
TypeVar, TypeVar,
cast, cast,
) )
@@ -1860,44 +1861,46 @@ class HCI_Object:
field_type = field_type['parser'] field_type = field_type['parser']
# Parse the field # Parse the field
if field_type == '*': match field_type:
case '*':
# The rest of the bytes # The rest of the bytes
field_value = data[offset:] field_value = data[offset:]
return (field_value, len(field_value)) return (field_value, len(field_value))
if field_type == 'v': case 'v':
# Variable-length bytes field, with 1-byte length at the beginning # Variable-length bytes field, with 1-byte length at the beginning
field_length = data[offset] field_length = data[offset]
offset += 1 offset += 1
field_value = data[offset : offset + field_length] field_value = data[offset : offset + field_length]
return (field_value, field_length + 1) return (field_value, field_length + 1)
if field_type == 1: case 1:
# 8-bit unsigned # 8-bit unsigned
return (data[offset], 1) return (data[offset], 1)
if field_type == -1: case -1:
# 8-bit signed # 8-bit signed
return (struct.unpack_from('b', data, offset)[0], 1) return (struct.unpack_from('b', data, offset)[0], 1)
if field_type == 2: case 2:
# 16-bit unsigned # 16-bit unsigned
return (struct.unpack_from('<H', data, offset)[0], 2) return (struct.unpack_from('<H', data, offset)[0], 2)
if field_type == '>2': case '>2':
# 16-bit unsigned big-endian # 16-bit unsigned big-endian
return (struct.unpack_from('>H', data, offset)[0], 2) return (struct.unpack_from('>H', data, offset)[0], 2)
if field_type == -2: case -2:
# 16-bit signed # 16-bit signed
return (struct.unpack_from('<h', data, offset)[0], 2) return (struct.unpack_from('<h', data, offset)[0], 2)
if field_type == 3: case 3:
# 24-bit unsigned # 24-bit unsigned
padded = data[offset : offset + 3] + bytes([0]) padded = data[offset : offset + 3] + bytes([0])
return (struct.unpack('<I', padded)[0], 3) return (struct.unpack('<I', padded)[0], 3)
if field_type == 4: case 4:
# 32-bit unsigned # 32-bit unsigned
return (struct.unpack_from('<I', data, offset)[0], 4) return (struct.unpack_from('<I', data, offset)[0], 4)
if field_type == '>4': case '>4':
# 32-bit unsigned big-endian # 32-bit unsigned big-endian
return (struct.unpack_from('>I', data, offset)[0], 4) return (struct.unpack_from('>I', data, offset)[0], 4)
if isinstance(field_type, int) and 4 < field_type <= 256: case int() if 4 < field_type <= 256:
# Byte array (from 5 up to 256 bytes) # Byte array (from 5 up to 256 bytes)
return (data[offset : offset + field_type], field_type) return (data[offset : offset + field_type], field_type)
if callable(field_type): if callable(field_type):
new_offset, field_value = field_type(data, offset) new_offset, field_value = field_type(data, offset)
return (field_value, new_offset - offset) return (field_value, new_offset - offset)
@@ -1954,61 +1957,59 @@ class HCI_Object:
# Serialize the field # Serialize the field
if serializer: if serializer:
field_bytes = serializer(field_value) return serializer(field_value)
elif field_type == 1: match field_type:
case 1:
# 8-bit unsigned # 8-bit unsigned
field_bytes = bytes([field_value]) return bytes([field_value])
elif field_type == -1: case -1:
# 8-bit signed # 8-bit signed
field_bytes = struct.pack('b', field_value) return struct.pack('b', field_value)
elif field_type == 2: case 2:
# 16-bit unsigned # 16-bit unsigned
field_bytes = struct.pack('<H', field_value) return struct.pack('<H', field_value)
elif field_type == '>2': case '>2':
# 16-bit unsigned big-endian # 16-bit unsigned big-endian
field_bytes = struct.pack('>H', field_value) return struct.pack('>H', field_value)
elif field_type == -2: case -2:
# 16-bit signed # 16-bit signed
field_bytes = struct.pack('<h', field_value) return struct.pack('<h', field_value)
elif field_type == 3: case 3:
# 24-bit unsigned # 24-bit unsigned
field_bytes = struct.pack('<I', field_value)[0:3] return struct.pack('<I', field_value)[0:3]
elif field_type == 4: case 4:
# 32-bit unsigned # 32-bit unsigned
field_bytes = struct.pack('<I', field_value) return struct.pack('<I', field_value)
elif field_type == '>4': case '>4':
# 32-bit unsigned big-endian # 32-bit unsigned big-endian
field_bytes = struct.pack('>I', field_value) return struct.pack('>I', field_value)
elif field_type == '*': case '*':
if isinstance(field_value, int): if isinstance(field_value, int):
if 0 <= field_value <= 255: if 0 <= field_value <= 255:
field_bytes = bytes([field_value]) return bytes([field_value])
else: else:
raise InvalidArgumentError('value too large for *-typed field') raise InvalidArgumentError('value too large for *-typed field')
else: else:
field_bytes = bytes(field_value) return bytes(field_value)
elif field_type == 'v': case 'v':
# Variable-length bytes field, with 1-byte length at the beginning # Variable-length bytes field, with 1-byte length at the beginning
field_bytes = bytes(field_value) field_bytes = bytes(field_value)
field_length = len(field_bytes) field_length = len(field_bytes)
field_bytes = bytes([field_length]) + field_bytes return bytes([field_length]) + field_bytes
elif isinstance(field_value, (bytes, bytearray)) or hasattr( if isinstance(field_value, (bytes, bytearray, SupportsBytes)):
field_value, '__bytes__'
):
field_bytes = bytes(field_value) field_bytes = bytes(field_value)
if isinstance(field_type, int) and 4 < field_type <= 256: if isinstance(field_type, int) and 4 < field_type <= 256:
# Truncate or pad with zeros if the field is too long or too short # Truncate or pad with zeros if the field is too long or too short
if len(field_bytes) < field_type: if len(field_bytes) < field_type:
field_bytes += bytes(field_type - len(field_bytes)) return field_bytes + bytes(field_type - len(field_bytes))
elif len(field_bytes) > field_type: elif len(field_bytes) > field_type:
field_bytes = field_bytes[:field_type] return field_bytes[:field_type]
else: return field_bytes
raise InvalidArgumentError( raise InvalidArgumentError(
f"don't know how to serialize type {type(field_value)}" f"don't know how to serialize type {type(field_value)}"
) )
return field_bytes
@staticmethod @staticmethod
def dict_to_bytes(hci_object, object_fields): def dict_to_bytes(hci_object, object_fields):
result = bytearray() result = bytearray()

View File

@@ -22,7 +22,7 @@ import collections
import dataclasses import dataclasses
import logging import logging
from collections.abc import Awaitable, Callable from collections.abc import Awaitable, Callable
from typing import TYPE_CHECKING, Any, TypeVar, cast, overload from typing import TYPE_CHECKING, Any, TypeVar, overload
from bumble import drivers, hci, utils from bumble import drivers, hci, utils
from bumble.colors import color from bumble.colors import color
@@ -1002,17 +1002,18 @@ class Host(utils.EventEmitter):
self.snooper.snoop(bytes(packet), Snooper.Direction.CONTROLLER_TO_HOST) self.snooper.snoop(bytes(packet), Snooper.Direction.CONTROLLER_TO_HOST)
# If the packet is a command, invoke the handler for this packet # If the packet is a command, invoke the handler for this packet
if packet.hci_packet_type == hci.HCI_COMMAND_PACKET: match packet:
self.on_hci_command_packet(cast(hci.HCI_Command, packet)) case hci.HCI_Command():
elif packet.hci_packet_type == hci.HCI_EVENT_PACKET: self.on_hci_command_packet(packet)
self.on_hci_event_packet(cast(hci.HCI_Event, packet)) case hci.HCI_Event():
elif packet.hci_packet_type == hci.HCI_ACL_DATA_PACKET: self.on_hci_event_packet(packet)
self.on_hci_acl_data_packet(cast(hci.HCI_AclDataPacket, packet)) case hci.HCI_AclDataPacket():
elif packet.hci_packet_type == hci.HCI_SYNCHRONOUS_DATA_PACKET: self.on_hci_acl_data_packet(packet)
self.on_hci_sco_data_packet(cast(hci.HCI_SynchronousDataPacket, packet)) case hci.HCI_SynchronousDataPacket():
elif packet.hci_packet_type == hci.HCI_ISO_DATA_PACKET: self.on_hci_sco_data_packet(packet)
self.on_hci_iso_data_packet(cast(hci.HCI_IsoDataPacket, packet)) case hci.HCI_IsoDataPacket():
else: self.on_hci_iso_data_packet(packet)
case _:
logger.warning(f'!!! unknown packet type {packet.hci_packet_type}') logger.warning(f'!!! unknown packet type {packet.hci_packet_type}')
def on_hci_command_packet(self, command: hci.HCI_Command) -> None: def on_hci_command_packet(self, command: hci.HCI_Command) -> None:

View File

@@ -664,7 +664,8 @@ class AudioStreamControlService(gatt.TemplateService):
responses = [] responses = []
logger.debug(f'*** ASCS Write {operation} ***') logger.debug(f'*** ASCS Write {operation} ***')
if isinstance(operation, ASE_Config_Codec): match operation:
case ASE_Config_Codec():
for ase_id, *args in zip( for ase_id, *args in zip(
operation.ase_id, operation.ase_id,
operation.target_latency, operation.target_latency,
@@ -673,7 +674,7 @@ class AudioStreamControlService(gatt.TemplateService):
operation.codec_specific_configuration, operation.codec_specific_configuration,
): ):
responses.append(self.on_operation(operation.op_code, ase_id, args)) responses.append(self.on_operation(operation.op_code, ase_id, args))
elif isinstance(operation, ASE_Config_QOS): case ASE_Config_QOS():
for ase_id, *args in zip( for ase_id, *args in zip(
operation.ase_id, operation.ase_id,
operation.cig_id, operation.cig_id,
@@ -687,20 +688,17 @@ class AudioStreamControlService(gatt.TemplateService):
operation.presentation_delay, operation.presentation_delay,
): ):
responses.append(self.on_operation(operation.op_code, ase_id, args)) responses.append(self.on_operation(operation.op_code, ase_id, args))
elif isinstance(operation, (ASE_Enable, ASE_Update_Metadata)): case ASE_Enable() | ASE_Update_Metadata():
for ase_id, *args in zip( for ase_id, *args in zip(
operation.ase_id, operation.ase_id,
operation.metadata, operation.metadata,
): ):
responses.append(self.on_operation(operation.op_code, ase_id, args)) responses.append(self.on_operation(operation.op_code, ase_id, args))
elif isinstance( case (
operation, ASE_Receiver_Start_Ready()
( | ASE_Disable()
ASE_Receiver_Start_Ready, | ASE_Receiver_Stop_Ready()
ASE_Disable, | ASE_Release()
ASE_Receiver_Stop_Ready,
ASE_Release,
),
): ):
for ase_id in operation.ase_id: for ase_id in operation.ase_id:
responses.append(self.on_operation(operation.op_code, ase_id, [])) responses.append(self.on_operation(operation.op_code, ase_id, []))

View File

@@ -333,16 +333,17 @@ class CodecSpecificCapabilities:
value = int.from_bytes(data[offset : offset + length - 1], 'little') value = int.from_bytes(data[offset : offset + length - 1], 'little')
offset += length - 1 offset += length - 1
if type == CodecSpecificCapabilities.Type.SAMPLING_FREQUENCY: match type:
case CodecSpecificCapabilities.Type.SAMPLING_FREQUENCY:
supported_sampling_frequencies = SupportedSamplingFrequency(value) supported_sampling_frequencies = SupportedSamplingFrequency(value)
elif type == CodecSpecificCapabilities.Type.FRAME_DURATION: case CodecSpecificCapabilities.Type.FRAME_DURATION:
supported_frame_durations = SupportedFrameDuration(value) supported_frame_durations = SupportedFrameDuration(value)
elif type == CodecSpecificCapabilities.Type.AUDIO_CHANNEL_COUNT: case CodecSpecificCapabilities.Type.AUDIO_CHANNEL_COUNT:
supported_audio_channel_count = bits_to_channel_counts(value) supported_audio_channel_count = bits_to_channel_counts(value)
elif type == CodecSpecificCapabilities.Type.OCTETS_PER_FRAME: case CodecSpecificCapabilities.Type.OCTETS_PER_FRAME:
min_octets_per_sample = value & 0xFFFF min_octets_per_sample = value & 0xFFFF
max_octets_per_sample = value >> 16 max_octets_per_sample = value >> 16
elif type == CodecSpecificCapabilities.Type.CODEC_FRAMES_PER_SDU: case CodecSpecificCapabilities.Type.CODEC_FRAMES_PER_SDU:
supported_max_codec_frames_per_sdu = value supported_max_codec_frames_per_sdu = value
# It is expected here that if some fields are missing, an error should be raised. # It is expected here that if some fields are missing, an error should be raised.

View File

@@ -55,13 +55,14 @@ class GenericAccessService(TemplateService):
def __init__( def __init__(
self, device_name: str, appearance: Appearance | tuple[int, int] | int = 0 self, device_name: str, appearance: Appearance | tuple[int, int] | int = 0
): ):
if isinstance(appearance, int): match appearance:
case int():
appearance_int = appearance appearance_int = appearance
elif isinstance(appearance, tuple): case tuple():
appearance_int = (appearance[0] << 6) | appearance[1] appearance_int = (appearance[0] << 6) | appearance[1]
elif isinstance(appearance, Appearance): case Appearance():
appearance_int = int(appearance) appearance_int = int(appearance)
else: case _:
raise TypeError() raise TypeError()
self.device_name_characteristic = Characteristic( self.device_name_characteristic = Characteristic(