From a62f981556e939fd808c9c4a68f2b72f97f36f4f Mon Sep 17 00:00:00 2001 From: khsiao-google Date: Mon, 10 Nov 2025 08:43:18 +0000 Subject: [PATCH] Add remote name request --- bumble/controller.py | 54 ++++++++++++++++++++++++++++++++++++++++++++ bumble/lmp.py | 18 +++++++++++++++ tests/device_test.py | 11 +++++++++ 3 files changed, 83 insertions(+) diff --git a/bumble/controller.py b/bumble/controller.py index 893a6dc2..4f687956 100644 --- a/bumble/controller.py +++ b/bumble/controller.py @@ -730,6 +730,15 @@ class Controller: self.on_classic_role_change_request(sender_address) elif isinstance(packet, (lmp.LmpRemoveScoLinkReq, lmp.LmpRemoveEscoLinkReq)): self.on_classic_sco_disconnected(sender_address, packet.error_code) + elif isinstance(packet, lmp.LmpNameReq): + self.on_classic_remote_name_request(sender_address, packet.name_offset) + elif isinstance(packet, lmp.LmpNameRes): + self.on_classic_remote_name_response( + sender_address, + packet.name_offset, + packet.name_length, + packet.name_fregment, + ) else: logger.error("!!! Unhandled packet: %s", packet) @@ -895,6 +904,33 @@ class Controller: ) ) + def on_classic_remote_name_request( + self, peer_address: hci.Address, name_offset: int + ): + self.send_lmp_packet( + peer_address, + lmp.LmpNameRes( + name_offset=name_offset, + name_length=len(self.local_name), + name_fregment=self.local_name.encode('utf-8'), + ), + ) + + def on_classic_remote_name_response( + self, + peer_address: hci.Address, + name_offset: int, + name_length: int, + name_fregment: bytes, + ): + self.send_hci_packet( + hci.HCI_Remote_Name_Request_Complete_Event( + status=hci.HCI_SUCCESS, + bd_addr=peer_address, + remote_name=name_fregment, + ) + ) + ############################################################ # Advertising support ############################################################ @@ -1120,6 +1156,24 @@ class Controller: self.on_classic_connection_complete(command.bd_addr, hci.HCI_SUCCESS) return None + def on_hci_remote_name_request_command( + self, command: hci.HCI_Remote_Name_Request_Command + ) -> Optional[bytes]: + ''' + See Bluetooth spec Vol 4, Part E - 7.1.19 Remote Name Request command + ''' + self.send_hci_packet( + hci.HCI_Command_Status_Event( + status=hci.HCI_SUCCESS, + num_hci_command_packets=1, + command_opcode=command.op_code, + ) + ) + + self.send_lmp_packet(command.bd_addr, lmp.LmpNameReq(0)) + + return None + def on_hci_enhanced_setup_synchronous_connection_command( self, command: hci.HCI_Enhanced_Setup_Synchronous_Connection_Command ) -> Optional[bytes]: diff --git a/bumble/lmp.py b/bumble/lmp.py index 73013920..c37a57ee 100644 --- a/bumble/lmp.py +++ b/bumble/lmp.py @@ -304,3 +304,21 @@ class LmpSwitchReq(Packet): opcode = Opcode.LMP_SWITCH_REQ switch_instant: int = field(metadata=hci.metadata(4), default=0) + + +@Packet.subclass +@dataclass +class LmpNameReq(Packet): + opcode = Opcode.LMP_NAME_REQ + + name_offset: int = field(metadata=hci.metadata(2)) + + +@Packet.subclass +@dataclass +class LmpNameRes(Packet): + opcode = Opcode.LMP_NAME_RES + + name_offset: int = field(metadata=hci.metadata(2)) + name_length: int = field(metadata=hci.metadata(3)) + name_fregment: bytes = field(metadata=hci.metadata('*')) diff --git a/tests/device_test.py b/tests/device_test.py index d7717651..7eed5b9c 100644 --- a/tests/device_test.py +++ b/tests/device_test.py @@ -789,6 +789,17 @@ async def test_accept_classic_connection(roles: tuple[hci.Role, hci.Role]): assert devices.connections[1].role == roles[1] +# ----------------------------------------------------------------------------- +@pytest.mark.asyncio +async def test_remote_name_request(): + devices = TwoDevices() + devices[0].classic_enabled = True + devices[1].classic_enabled = True + await devices.setup_connection() + remote_name = await devices[0].request_remote_name(devices[1].public_address) + assert remote_name == "Bumble" + + # ----------------------------------------------------------------------------- async def run_test_device(): await test_device_connect_parallel()