mirror of
https://github.com/google/bumble.git
synced 2026-04-16 00:25:31 +00:00
Merge pull request #900 from zxzxwu/lmp-feat
Add read classic remote features support
This commit is contained in:
@@ -238,9 +238,12 @@ class Controller:
|
||||
hci_revision: int = 0
|
||||
lmp_version: int = hci.HCI_VERSION_BLUETOOTH_CORE_5_0
|
||||
lmp_subversion: int = 0
|
||||
lmp_features: bytes = bytes.fromhex(
|
||||
'0000000060000000'
|
||||
) # BR/EDR Not Supported, LE Supported (Controller)
|
||||
lmp_features: hci.LmpFeatureMask = (
|
||||
hci.LmpFeatureMask.LE_SUPPORTED_CONTROLLER
|
||||
| hci.LmpFeatureMask.BR_EDR_NOT_SUPPORTED
|
||||
| hci.LmpFeatureMask.EXTENDED_FEATURES
|
||||
)
|
||||
lmp_features_max_page_number: int = 3
|
||||
manufacturer_company_identifier: int = 0xFFFF
|
||||
acl_data_packet_length: int = 27
|
||||
total_num_acl_data_packets: int = 64
|
||||
@@ -250,10 +253,78 @@ class Controller:
|
||||
total_num_iso_data_packets: int = 64
|
||||
event_mask: int = 0
|
||||
event_mask_page_2: int = 0
|
||||
supported_commands: bytes = bytes.fromhex(
|
||||
'2000800000c000000000e4000000a822000000000000040000f7ffff7f000000'
|
||||
'30f0f9ff01008004002000000000000000000000000000000000000000000000'
|
||||
)
|
||||
supported_commands: set[int] = {
|
||||
hci.HCI_DISCONNECT_COMMAND,
|
||||
hci.HCI_READ_REMOTE_VERSION_INFORMATION_COMMAND,
|
||||
hci.HCI_READ_CLOCK_OFFSET_COMMAND,
|
||||
hci.HCI_READ_LMP_HANDLE_COMMAND,
|
||||
hci.HCI_SET_EVENT_MASK_COMMAND,
|
||||
hci.HCI_RESET_COMMAND,
|
||||
hci.HCI_READ_TRANSMIT_POWER_LEVEL_COMMAND,
|
||||
hci.HCI_SET_CONTROLLER_TO_HOST_FLOW_CONTROL_COMMAND,
|
||||
hci.HCI_HOST_BUFFER_SIZE_COMMAND,
|
||||
hci.HCI_HOST_NUMBER_OF_COMPLETED_PACKETS_COMMAND,
|
||||
hci.HCI_READ_LOCAL_VERSION_INFORMATION_COMMAND,
|
||||
hci.HCI_READ_LOCAL_SUPPORTED_FEATURES_COMMAND,
|
||||
hci.HCI_READ_BUFFER_SIZE_COMMAND,
|
||||
hci.HCI_READ_BD_ADDR_COMMAND,
|
||||
hci.HCI_READ_RSSI_COMMAND,
|
||||
hci.HCI_SET_EVENT_MASK_PAGE_2_COMMAND,
|
||||
hci.HCI_LE_SET_EVENT_MASK_COMMAND,
|
||||
hci.HCI_LE_READ_BUFFER_SIZE_COMMAND,
|
||||
hci.HCI_LE_READ_LOCAL_SUPPORTED_FEATURES_COMMAND,
|
||||
hci.HCI_LE_SET_RANDOM_ADDRESS_COMMAND,
|
||||
hci.HCI_LE_SET_ADVERTISING_PARAMETERS_COMMAND,
|
||||
hci.HCI_LE_READ_ADVERTISING_PHYSICAL_CHANNEL_TX_POWER_COMMAND,
|
||||
hci.HCI_LE_SET_ADVERTISING_DATA_COMMAND,
|
||||
hci.HCI_LE_SET_SCAN_RESPONSE_DATA_COMMAND,
|
||||
hci.HCI_LE_SET_ADVERTISING_ENABLE_COMMAND,
|
||||
hci.HCI_LE_SET_SCAN_PARAMETERS_COMMAND,
|
||||
hci.HCI_LE_SET_SCAN_ENABLE_COMMAND,
|
||||
hci.HCI_LE_CREATE_CONNECTION_COMMAND,
|
||||
hci.HCI_LE_CREATE_CONNECTION_CANCEL_COMMAND,
|
||||
hci.HCI_LE_READ_FILTER_ACCEPT_LIST_SIZE_COMMAND,
|
||||
hci.HCI_LE_CLEAR_FILTER_ACCEPT_LIST_COMMAND,
|
||||
hci.HCI_LE_ADD_DEVICE_TO_FILTER_ACCEPT_LIST_COMMAND,
|
||||
hci.HCI_LE_REMOVE_DEVICE_FROM_FILTER_ACCEPT_LIST_COMMAND,
|
||||
hci.HCI_LE_CONNECTION_UPDATE_COMMAND,
|
||||
hci.HCI_LE_SET_HOST_CHANNEL_CLASSIFICATION_COMMAND,
|
||||
hci.HCI_LE_READ_CHANNEL_MAP_COMMAND,
|
||||
hci.HCI_LE_READ_REMOTE_FEATURES_COMMAND,
|
||||
hci.HCI_LE_ENCRYPT_COMMAND,
|
||||
hci.HCI_LE_RAND_COMMAND,
|
||||
hci.HCI_LE_ENABLE_ENCRYPTION_COMMAND,
|
||||
hci.HCI_LE_LONG_TERM_KEY_REQUEST_REPLY_COMMAND,
|
||||
hci.HCI_LE_LONG_TERM_KEY_REQUEST_NEGATIVE_REPLY_COMMAND,
|
||||
hci.HCI_LE_READ_SUPPORTED_STATES_COMMAND,
|
||||
hci.HCI_LE_RECEIVER_TEST_COMMAND,
|
||||
hci.HCI_LE_TRANSMITTER_TEST_COMMAND,
|
||||
hci.HCI_LE_TEST_END_COMMAND,
|
||||
hci.HCI_READ_AUTHENTICATED_PAYLOAD_TIMEOUT_COMMAND,
|
||||
hci.HCI_WRITE_AUTHENTICATED_PAYLOAD_TIMEOUT_COMMAND,
|
||||
hci.HCI_LE_REMOTE_CONNECTION_PARAMETER_REQUEST_REPLY_COMMAND,
|
||||
hci.HCI_LE_REMOTE_CONNECTION_PARAMETER_REQUEST_NEGATIVE_REPLY_COMMAND,
|
||||
hci.HCI_LE_SET_DATA_LENGTH_COMMAND,
|
||||
hci.HCI_LE_READ_SUGGESTED_DEFAULT_DATA_LENGTH_COMMAND,
|
||||
hci.HCI_LE_WRITE_SUGGESTED_DEFAULT_DATA_LENGTH_COMMAND,
|
||||
hci.HCI_LE_ADD_DEVICE_TO_RESOLVING_LIST_COMMAND,
|
||||
hci.HCI_LE_REMOVE_DEVICE_FROM_RESOLVING_LIST_COMMAND,
|
||||
hci.HCI_LE_CLEAR_RESOLVING_LIST_COMMAND,
|
||||
hci.HCI_LE_READ_RESOLVING_LIST_SIZE_COMMAND,
|
||||
hci.HCI_LE_READ_PEER_RESOLVABLE_ADDRESS_COMMAND,
|
||||
hci.HCI_LE_READ_LOCAL_RESOLVABLE_ADDRESS_COMMAND,
|
||||
hci.HCI_LE_SET_ADDRESS_RESOLUTION_ENABLE_COMMAND,
|
||||
hci.HCI_LE_SET_RESOLVABLE_PRIVATE_ADDRESS_TIMEOUT_COMMAND,
|
||||
hci.HCI_LE_READ_MAXIMUM_DATA_LENGTH_COMMAND,
|
||||
hci.HCI_LE_READ_PHY_COMMAND,
|
||||
hci.HCI_LE_SET_DEFAULT_PHY_COMMAND,
|
||||
hci.HCI_LE_SET_PHY_COMMAND,
|
||||
hci.HCI_LE_RECEIVER_TEST_V2_COMMAND,
|
||||
hci.HCI_LE_TRANSMITTER_TEST_V2_COMMAND,
|
||||
hci.HCI_LE_READ_TRANSMIT_POWER_COMMAND,
|
||||
hci.HCI_LE_SET_PRIVACY_MODE_COMMAND,
|
||||
hci.HCI_LE_READ_BUFFER_SIZE_V2_COMMAND,
|
||||
}
|
||||
le_event_mask: int = 0
|
||||
le_features: hci.LeFeatureMask = (
|
||||
hci.LeFeatureMask.LE_ENCRYPTION
|
||||
@@ -392,6 +463,12 @@ class Controller:
|
||||
if self.link:
|
||||
self.link.on_address_changed(self)
|
||||
|
||||
@property
|
||||
def lmp_features_bytes(self) -> bytes:
|
||||
return self.lmp_features.to_bytes(
|
||||
(self.lmp_features_max_page_number + 1) * 8, 'little'
|
||||
)
|
||||
|
||||
# Packet Sink protocol (packets coming from the host via HCI)
|
||||
def on_packet(self, packet: bytes) -> None:
|
||||
self.on_hci_packet(hci.HCI_Packet.from_bytes(packet))
|
||||
@@ -968,6 +1045,51 @@ class Controller:
|
||||
packet.name_length,
|
||||
packet.name_fregment,
|
||||
)
|
||||
case lmp.LmpFeaturesReq(features):
|
||||
self.send_lmp_packet(
|
||||
sender_address,
|
||||
lmp.LmpFeaturesRes(features=self.lmp_features_bytes[:8]),
|
||||
)
|
||||
case lmp.LmpFeaturesRes(features):
|
||||
if connection := self.classic_connections.get(sender_address):
|
||||
self.send_hci_packet(
|
||||
hci.HCI_Read_Remote_Supported_Features_Complete_Event(
|
||||
status=hci.HCI_ErrorCode.SUCCESS,
|
||||
connection_handle=connection.handle,
|
||||
lmp_features=features,
|
||||
)
|
||||
)
|
||||
case lmp.LmpFeaturesReqExt(features_page, features):
|
||||
# Calculate start/end of page
|
||||
page_start = features_page * 8
|
||||
page_end = page_start + 8
|
||||
features_bytes = self.lmp_features_bytes
|
||||
if page_start < len(features_bytes):
|
||||
page_features = features_bytes[page_start:page_end].ljust(
|
||||
8, b'\x00'
|
||||
)
|
||||
else:
|
||||
page_features = b'\x00' * 8
|
||||
|
||||
self.send_lmp_packet(
|
||||
sender_address,
|
||||
lmp.LmpFeaturesResExt(
|
||||
features_page=features_page,
|
||||
max_features_page=len(features_bytes) // 8 - 1,
|
||||
features=page_features,
|
||||
),
|
||||
)
|
||||
case lmp.LmpFeaturesResExt(features_page, max_features_page, features):
|
||||
if connection := self.classic_connections.get(sender_address):
|
||||
self.send_hci_packet(
|
||||
hci.HCI_Read_Remote_Extended_Features_Complete_Event(
|
||||
status=hci.HCI_ErrorCode.SUCCESS,
|
||||
connection_handle=connection.handle,
|
||||
page_number=features_page,
|
||||
maximum_page_number=max_features_page,
|
||||
extended_lmp_features=features,
|
||||
)
|
||||
)
|
||||
case _:
|
||||
logger.error("!!! Unhandled packet: %s", packet)
|
||||
|
||||
@@ -1349,6 +1471,53 @@ class Controller:
|
||||
|
||||
return None
|
||||
|
||||
def on_hci_read_remote_supported_features_command(
|
||||
self, command: hci.HCI_Read_Remote_Supported_Features_Command
|
||||
) -> None:
|
||||
'''
|
||||
See Bluetooth spec Vol 4, Part E - 7.1.20 Read Remote Supported Features command
|
||||
'''
|
||||
handle = command.connection_handle
|
||||
if not (connection := self.find_classic_connection_by_handle(handle)):
|
||||
self._send_hci_command_status(
|
||||
hci.HCI_ErrorCode.UNKNOWN_CONNECTION_IDENTIFIER_ERROR, command.op_code
|
||||
)
|
||||
return None
|
||||
|
||||
self._send_hci_command_status(hci.HCI_COMMAND_STATUS_PENDING, command.op_code)
|
||||
self.send_lmp_packet(
|
||||
connection.peer_address,
|
||||
lmp.LmpFeaturesReq(self.lmp_features_bytes[:8]),
|
||||
)
|
||||
|
||||
return None
|
||||
|
||||
def on_hci_read_remote_extended_features_command(
|
||||
self, command: hci.HCI_Read_Remote_Extended_Features_Command
|
||||
) -> None:
|
||||
'''
|
||||
See Bluetooth spec Vol 4, Part E - 7.1.21 Read Remote Extended Features command
|
||||
'''
|
||||
handle = command.connection_handle
|
||||
if not (connection := self.find_classic_connection_by_handle(handle)):
|
||||
self._send_hci_command_status(
|
||||
hci.HCI_ErrorCode.UNKNOWN_CONNECTION_IDENTIFIER_ERROR, command.op_code
|
||||
)
|
||||
return None
|
||||
|
||||
self._send_hci_command_status(hci.HCI_COMMAND_STATUS_PENDING, command.op_code)
|
||||
self.send_lmp_packet(
|
||||
connection.peer_address,
|
||||
lmp.LmpFeaturesReqExt(
|
||||
features_page=command.page_number,
|
||||
features=self.lmp_features_bytes[
|
||||
command.page_number * 8 : (command.page_number + 1) * 8
|
||||
],
|
||||
),
|
||||
)
|
||||
|
||||
return None
|
||||
|
||||
def on_hci_enhanced_setup_synchronous_connection_command(
|
||||
self, command: hci.HCI_Enhanced_Setup_Synchronous_Connection_Command
|
||||
) -> None:
|
||||
@@ -1645,11 +1814,15 @@ class Controller:
|
||||
return hci.HCI_StatusReturnParameters(hci.HCI_ErrorCode.SUCCESS)
|
||||
|
||||
def on_hci_write_simple_pairing_mode_command(
|
||||
self, _command: hci.HCI_Write_Simple_Pairing_Mode_Command
|
||||
self, command: hci.HCI_Write_Simple_Pairing_Mode_Command
|
||||
) -> hci.HCI_StatusReturnParameters:
|
||||
'''
|
||||
See Bluetooth spec Vol 4, Part E - 7.3.59 Write Simple Pairing Mode Command
|
||||
'''
|
||||
if command.simple_pairing_mode:
|
||||
self.lmp_features |= hci.LmpFeatureMask.SECURE_SIMPLE_PAIRING_HOST_SUPPORT
|
||||
else:
|
||||
self.lmp_features &= ~hci.LmpFeatureMask.SECURE_SIMPLE_PAIRING_HOST_SUPPORT
|
||||
return hci.HCI_StatusReturnParameters(hci.HCI_ErrorCode.SUCCESS)
|
||||
|
||||
def on_hci_set_event_mask_page_2_command(
|
||||
@@ -1670,16 +1843,23 @@ class Controller:
|
||||
See Bluetooth spec Vol 4, Part E - 7.3.78 Write LE Host Support Command
|
||||
'''
|
||||
return hci.HCI_Read_LE_Host_Support_ReturnParameters(
|
||||
status=hci.HCI_ErrorCode.SUCCESS, le_supported_host=1, unused=0
|
||||
status=hci.HCI_ErrorCode.SUCCESS,
|
||||
le_supported_host=(
|
||||
1 if self.lmp_features & hci.LmpFeatureMask.LE_SUPPORTED_HOST else 0
|
||||
),
|
||||
unused=0,
|
||||
)
|
||||
|
||||
def on_hci_write_le_host_support_command(
|
||||
self, _command: hci.HCI_Write_LE_Host_Support_Command
|
||||
self, command: hci.HCI_Write_LE_Host_Support_Command
|
||||
) -> hci.HCI_StatusReturnParameters:
|
||||
'''
|
||||
See Bluetooth spec Vol 4, Part E - 7.3.79 Write LE Host Support Command
|
||||
'''
|
||||
# TODO / Just ignore for now
|
||||
if command.le_supported_host:
|
||||
self.lmp_features |= hci.LmpFeatureMask.LE_SUPPORTED_HOST
|
||||
else:
|
||||
self.lmp_features &= ~hci.LmpFeatureMask.LE_SUPPORTED_HOST
|
||||
return hci.HCI_StatusReturnParameters(hci.HCI_ErrorCode.SUCCESS)
|
||||
|
||||
def on_hci_write_authenticated_payload_timeout_command(
|
||||
@@ -1716,7 +1896,11 @@ class Controller:
|
||||
See Bluetooth spec Vol 4, Part E - 7.4.2 Read Local Supported Commands Command
|
||||
'''
|
||||
return hci.HCI_Read_Local_Supported_Commands_ReturnParameters(
|
||||
hci.HCI_ErrorCode.SUCCESS, supported_commands=self.supported_commands
|
||||
hci.HCI_ErrorCode.SUCCESS,
|
||||
supported_commands=sum(
|
||||
hci.HCI_SUPPORTED_COMMANDS_MASKS.get(opcode, 0)
|
||||
for opcode in self.supported_commands
|
||||
).to_bytes(64, 'little'),
|
||||
)
|
||||
|
||||
def on_hci_read_local_supported_features_command(
|
||||
@@ -1726,7 +1910,8 @@ class Controller:
|
||||
See Bluetooth spec Vol 4, Part E - 7.4.3 Read Local Supported Features Command
|
||||
'''
|
||||
return hci.HCI_Read_Local_Supported_Features_ReturnParameters(
|
||||
hci.HCI_ErrorCode.SUCCESS, lmp_features=self.lmp_features[:8]
|
||||
hci.HCI_ErrorCode.SUCCESS,
|
||||
lmp_features=self.lmp_features_bytes[:8],
|
||||
)
|
||||
|
||||
def on_hci_read_local_extended_features_command(
|
||||
@@ -1735,18 +1920,19 @@ class Controller:
|
||||
'''
|
||||
See Bluetooth spec Vol 4, Part E - 7.4.4 Read Local Extended Features Command
|
||||
'''
|
||||
if command.page_number * 8 > len(self.lmp_features):
|
||||
feature_bytes = self.lmp_features_bytes
|
||||
if command.page_number * 8 > len(feature_bytes):
|
||||
return hci.HCI_Read_Local_Extended_Features_ReturnParameters(
|
||||
status=hci.HCI_ErrorCode.INVALID_COMMAND_PARAMETERS_ERROR,
|
||||
page_number=command.page_number,
|
||||
maximum_page_number=len(self.lmp_features) // 8 - 1,
|
||||
maximum_page_number=len(feature_bytes) // 8 - 1,
|
||||
extended_lmp_features=bytes(8),
|
||||
)
|
||||
return hci.HCI_Read_Local_Extended_Features_ReturnParameters(
|
||||
status=hci.HCI_ErrorCode.SUCCESS,
|
||||
page_number=command.page_number,
|
||||
maximum_page_number=len(self.lmp_features) // 8 - 1,
|
||||
extended_lmp_features=self.lmp_features[
|
||||
maximum_page_number=len(feature_bytes) // 8 - 1,
|
||||
extended_lmp_features=feature_bytes[
|
||||
command.page_number * 8 : (command.page_number + 1) * 8
|
||||
],
|
||||
)
|
||||
|
||||
@@ -1837,6 +1837,7 @@ class Connection(utils.CompositeEventEmitter):
|
||||
self.pairing_peer_io_capability = None
|
||||
self.pairing_peer_authentication_requirements = None
|
||||
self.peer_le_features = hci.LeFeatureMask(0)
|
||||
self.peer_classic_features = hci.LmpFeatureMask(0)
|
||||
self.cs_configs = {}
|
||||
self.cs_procedures = {}
|
||||
|
||||
@@ -2054,6 +2055,15 @@ class Connection(utils.CompositeEventEmitter):
|
||||
self.peer_le_features = await self.device.get_remote_le_features(self)
|
||||
return self.peer_le_features
|
||||
|
||||
async def get_remote_classic_features(self) -> hci.LmpFeatureMask:
|
||||
"""[Classic Only] Reads remote LMP supported features.
|
||||
|
||||
Returns:
|
||||
LMP features supported by the remote device.
|
||||
"""
|
||||
self.peer_classic_features = await self.device.get_remote_classic_features(self)
|
||||
return self.peer_classic_features
|
||||
|
||||
def on_att_mtu_update(self, mtu: int):
|
||||
logger.debug(
|
||||
f'*** Connection ATT MTU Update: [0x{self.handle:04X}] '
|
||||
@@ -5281,6 +5291,77 @@ class Device(utils.CompositeEventEmitter):
|
||||
)
|
||||
return await read_feature_future
|
||||
|
||||
async def get_remote_classic_features(
|
||||
self, connection: Connection
|
||||
) -> hci.LmpFeatureMask:
|
||||
"""[Classic Only] Reads remote LE supported features.
|
||||
|
||||
Args:
|
||||
handle: connection handle to read LMP features.
|
||||
|
||||
Returns:
|
||||
LMP features supported by the remote device.
|
||||
"""
|
||||
with closing(utils.EventWatcher()) as watcher:
|
||||
read_feature_future: asyncio.Future[tuple[int, int]] = (
|
||||
asyncio.get_running_loop().create_future()
|
||||
)
|
||||
read_features = hci.LmpFeatureMask(0)
|
||||
current_page_number = 0
|
||||
|
||||
@watcher.on(self.host, 'classic_remote_features')
|
||||
def on_classic_remote_features(
|
||||
handle: int,
|
||||
status: int,
|
||||
features: int,
|
||||
page_number: int,
|
||||
max_page_number: int,
|
||||
) -> None:
|
||||
if handle != connection.handle:
|
||||
logger.warning(
|
||||
"Received classic_remote_features for wrong handle, expected=0x%04X, got=0x%04X",
|
||||
connection.handle,
|
||||
handle,
|
||||
)
|
||||
return
|
||||
if page_number != current_page_number:
|
||||
logger.warning(
|
||||
"Received classic_remote_features for wrong page, expected=%d, got=%d",
|
||||
current_page_number,
|
||||
page_number,
|
||||
)
|
||||
return
|
||||
|
||||
if status == hci.HCI_ErrorCode.SUCCESS:
|
||||
read_feature_future.set_result((features, max_page_number))
|
||||
else:
|
||||
read_feature_future.set_exception(hci.HCI_Error(status))
|
||||
|
||||
await self.send_async_command(
|
||||
hci.HCI_Read_Remote_Supported_Features_Command(
|
||||
connection_handle=connection.handle
|
||||
)
|
||||
)
|
||||
|
||||
new_features, max_page_number = await read_feature_future
|
||||
read_features |= new_features
|
||||
if not (read_features & hci.LmpFeatureMask.EXTENDED_FEATURES):
|
||||
return read_features
|
||||
|
||||
while current_page_number <= max_page_number:
|
||||
read_feature_future = asyncio.get_running_loop().create_future()
|
||||
await self.send_async_command(
|
||||
hci.HCI_Read_Remote_Extended_Features_Command(
|
||||
connection_handle=connection.handle,
|
||||
page_number=current_page_number,
|
||||
)
|
||||
)
|
||||
new_features, max_page_number = await read_feature_future
|
||||
read_features |= new_features << (current_page_number * 64)
|
||||
current_page_number += 1
|
||||
|
||||
return read_features
|
||||
|
||||
@utils.experimental('Only for testing.')
|
||||
async def get_remote_cs_capabilities(
|
||||
self, connection: Connection
|
||||
|
||||
@@ -1660,6 +1660,19 @@ class Host(utils.EventEmitter):
|
||||
'connection_encryption_failure', event.connection_handle, event.status
|
||||
)
|
||||
|
||||
def on_hci_read_remote_supported_features_complete_event(
|
||||
self, event: hci.HCI_Read_Remote_Supported_Features_Complete_Event
|
||||
) -> None:
|
||||
# Notify the client
|
||||
self.emit(
|
||||
'classic_remote_features',
|
||||
event.connection_handle,
|
||||
event.status,
|
||||
int.from_bytes(event.lmp_features, 'little'),
|
||||
0, # page number
|
||||
0, # max page number
|
||||
)
|
||||
|
||||
def on_hci_encryption_change_v2_event(
|
||||
self, event: hci.HCI_Encryption_Change_V2_Event
|
||||
):
|
||||
@@ -1816,6 +1829,18 @@ class Host(utils.EventEmitter):
|
||||
rssi,
|
||||
)
|
||||
|
||||
def on_hci_read_remote_extended_features_complete_event(
|
||||
self, event: hci.HCI_Read_Remote_Extended_Features_Complete_Event
|
||||
):
|
||||
self.emit(
|
||||
'classic_remote_features',
|
||||
event.connection_handle,
|
||||
event.status,
|
||||
int.from_bytes(event.extended_lmp_features, 'little'),
|
||||
event.page_number,
|
||||
event.maximum_page_number,
|
||||
)
|
||||
|
||||
def on_hci_extended_inquiry_result_event(
|
||||
self, event: hci.HCI_Extended_Inquiry_Result_Event
|
||||
):
|
||||
|
||||
@@ -322,3 +322,38 @@ class LmpNameRes(Packet):
|
||||
name_offset: int = field(metadata=hci.metadata(2))
|
||||
name_length: int = field(metadata=hci.metadata(3))
|
||||
name_fregment: bytes = field(metadata=hci.metadata('*'))
|
||||
|
||||
|
||||
@Packet.subclass
|
||||
@dataclass
|
||||
class LmpFeaturesReq(Packet):
|
||||
opcode = Opcode.LMP_FEATURES_REQ
|
||||
|
||||
features: bytes = field(metadata=hci.metadata(8))
|
||||
|
||||
|
||||
@Packet.subclass
|
||||
@dataclass
|
||||
class LmpFeaturesRes(Packet):
|
||||
opcode = Opcode.LMP_FEATURES_RES
|
||||
|
||||
features: bytes = field(metadata=hci.metadata(8))
|
||||
|
||||
|
||||
@Packet.subclass
|
||||
@dataclass
|
||||
class LmpFeaturesReqExt(Packet):
|
||||
opcode = Opcode.LMP_FEATURES_REQ_EXT
|
||||
|
||||
features_page: int = field(metadata=hci.metadata(1))
|
||||
features: bytes = field(metadata=hci.metadata(8))
|
||||
|
||||
|
||||
@Packet.subclass
|
||||
@dataclass
|
||||
class LmpFeaturesResExt(Packet):
|
||||
opcode = Opcode.LMP_FEATURES_RES_EXT
|
||||
|
||||
features_page: int = field(metadata=hci.metadata(1))
|
||||
max_features_page: int = field(metadata=hci.metadata(1))
|
||||
features: bytes = field(metadata=hci.metadata(8))
|
||||
|
||||
@@ -826,6 +826,22 @@ async def test_remote_name_request():
|
||||
assert actual_name == expected_name
|
||||
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
@pytest.mark.asyncio
|
||||
async def test_get_remote_classic_features():
|
||||
devices = TwoDevices()
|
||||
devices[0].classic_enabled = True
|
||||
devices[1].classic_enabled = True
|
||||
await devices[0].power_on()
|
||||
await devices[1].power_on()
|
||||
connection = await devices[0].connect_classic(devices[1].public_address)
|
||||
|
||||
assert (
|
||||
await asyncio.wait_for(connection.get_remote_classic_features(), _TIMEOUT)
|
||||
== devices.controllers[1].lmp_features
|
||||
)
|
||||
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
async def run_test_device():
|
||||
await test_device_connect_parallel()
|
||||
|
||||
@@ -22,6 +22,7 @@ import unittest.mock
|
||||
|
||||
import pytest
|
||||
|
||||
from bumble import controller, hci
|
||||
from bumble.controller import Controller
|
||||
from bumble.hci import (
|
||||
HCI_AclDataPacket,
|
||||
@@ -49,34 +50,27 @@ logger = logging.getLogger(__name__)
|
||||
# -----------------------------------------------------------------------------
|
||||
@pytest.mark.asyncio
|
||||
@pytest.mark.parametrize(
|
||||
'supported_commands, lmp_features',
|
||||
'supported_commands, max_lmp_features_page_number',
|
||||
[
|
||||
(
|
||||
# Default commands
|
||||
'2000800000c000000000e4000000a822000000000000040000f7ffff7f000000'
|
||||
'30f0f9ff01008004000000000000000000000000000000000000000000000000',
|
||||
# Only LE LMP feature
|
||||
'0000000060000000',
|
||||
),
|
||||
(controller.Controller.supported_commands, 0),
|
||||
(
|
||||
# All commands
|
||||
'ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff'
|
||||
'ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff',
|
||||
set(hci.HCI_Command.command_names.keys()),
|
||||
# 3 pages of LMP features
|
||||
'000102030405060708090A0B0C0D0E0F011112131415161718191A1B1C1D1E1F',
|
||||
2,
|
||||
),
|
||||
],
|
||||
)
|
||||
async def test_reset(supported_commands: str, lmp_features: str):
|
||||
async def test_reset(supported_commands: set[int], max_lmp_features_page_number: int):
|
||||
controller = Controller('C')
|
||||
controller.supported_commands = bytes.fromhex(supported_commands)
|
||||
controller.lmp_features = bytes.fromhex(lmp_features)
|
||||
controller.supported_commands = supported_commands
|
||||
controller.lmp_features_max_page_number = max_lmp_features_page_number
|
||||
host = Host(controller, AsyncPipeSink(controller))
|
||||
|
||||
await host.reset()
|
||||
|
||||
assert host.local_lmp_features == int.from_bytes(
|
||||
bytes.fromhex(lmp_features), 'little'
|
||||
assert host.local_lmp_features == (
|
||||
controller.lmp_features & ~(1 << (64 * max_lmp_features_page_number + 1))
|
||||
)
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user