Loading tools/rootcanal/Android.bp +19 −0 Original line number Original line Diff line number Diff line Loading @@ -197,6 +197,22 @@ genrule { ], ], } } // Generate the python parser+serializer backend for // rust/llcp_packets.pdl. genrule { name: "llcp_packets_python3_gen", defaults: ["pdl_python_generator_defaults"], cmd: "$(location :pdlc) $(in) |" + " $(location :pdl_python_generator)" + " --output $(out) --custom-type-location py.bluetooth", srcs: [ "rust/llcp_packets.pdl", ], out: [ "llcp_packets.py", ], } // Generate the python parser+serializer backend for // Generate the python parser+serializer backend for // hci_packets.pdl. // hci_packets.pdl. genrule { genrule { Loading Loading @@ -285,8 +301,11 @@ python_test_host { srcs: [ srcs: [ ":hci_packets_python3_gen", ":hci_packets_python3_gen", ":link_layer_packets_python3_gen", ":link_layer_packets_python3_gen", ":llcp_packets_python3_gen", "py/bluetooth.py", "py/bluetooth.py", "py/controller.py", "py/controller.py", "test/LL/CIS/CEN/*.py", "test/LL/CIS/PER/*.py", "test/LL/CON_/CEN/*.py", "test/LL/CON_/CEN/*.py", "test/LL/CON_/PER/*.py", "test/LL/CON_/PER/*.py", "test/LL/DDI/ADV/*.py", "test/LL/DDI/ADV/*.py", Loading tools/rootcanal/config.proto +3 −0 Original line number Original line Diff line number Diff line Loading @@ -14,6 +14,9 @@ message ControllerFeatures { optional bool ll_privacy = 3; optional bool ll_privacy = 3; optional bool le_2m_phy = 4; optional bool le_2m_phy = 4; optional bool le_coded_phy = 5; optional bool le_coded_phy = 5; // Enable the support for both LL Connected Isochronous Stream Central // and LL Connected Isochronous Stream Peripheral. optional bool le_connected_isochronous_stream = 6; } } message ControllerQuirks { message ControllerQuirks { Loading tools/rootcanal/model/controller/controller_properties.cc +33 −12 Original line number Original line Diff line number Diff line Loading @@ -108,13 +108,13 @@ static constexpr uint64_t LlFeatures() { LLFeaturesBits::LE_PING, LLFeaturesBits::LE_PING, LLFeaturesBits::LL_PRIVACY, LLFeaturesBits::LL_PRIVACY, LLFeaturesBits::EXTENDED_SCANNER_FILTER_POLICIES, LLFeaturesBits::EXTENDED_SCANNER_FILTER_POLICIES, LLFeaturesBits::LE_2M_PHY, LLFeaturesBits::LE_CODED_PHY, LLFeaturesBits::LE_2M_PHY, LLFeaturesBits::LE_CODED_PHY, LLFeaturesBits::LE_EXTENDED_ADVERTISING, LLFeaturesBits::LE_EXTENDED_ADVERTISING, LLFeaturesBits::LE_PERIODIC_ADVERTISING, LLFeaturesBits::LE_PERIODIC_ADVERTISING, // TODO: breaks AVD boot tests with LE audio LLFeaturesBits::CONNECTED_ISOCHRONOUS_STREAM_CENTRAL, // LLFeaturesBits::CONNECTED_ISOCHRONOUS_STREAM_CENTRAL, LLFeaturesBits::CONNECTED_ISOCHRONOUS_STREAM_PERIPHERAL, // LLFeaturesBits::CONNECTED_ISOCHRONOUS_STREAM_PERIPHERAL, }; }; uint64_t value = 0; uint64_t value = 0; Loading Loading @@ -377,20 +377,17 @@ static std::array<uint8_t, 64> SupportedCommands() { // OpCodeIndex::LE_MODIFY_SLEEP_CLOCK_ACCURACY, // OpCodeIndex::LE_MODIFY_SLEEP_CLOCK_ACCURACY, OpCodeIndex::LE_READ_BUFFER_SIZE_V2, OpCodeIndex::LE_READ_BUFFER_SIZE_V2, // OpCodeIndex::LE_READ_ISO_TX_SYNC, // OpCodeIndex::LE_READ_ISO_TX_SYNC, // OpCodeIndex::LE_SET_CIG_PARAMETERS, OpCodeIndex::LE_SET_CIG_PARAMETERS, // OpCodeIndex::LE_SET_CIG_PARAMETERS_TEST, OpCodeIndex::LE_SET_CIG_PARAMETERS_TEST, OpCodeIndex::LE_CREATE_CIS, // OpCodeIndex::LE_CREATE_CIS, OpCodeIndex::LE_REMOVE_CIG, OpCodeIndex::LE_ACCEPT_CIS_REQUEST, // OpCodeIndex::LE_REMOVE_CIG, OpCodeIndex::LE_REJECT_CIS_REQUEST, // OpCodeIndex::LE_ACCEPT_CIS_REQUEST, // OpCodeIndex::LE_REJECT_CIS_REQUEST, // OpCodeIndex::LE_CREATE_BIG, // OpCodeIndex::LE_CREATE_BIG, // OpCodeIndex::LE_CREATE_BIG_TEST, // OpCodeIndex::LE_CREATE_BIG_TEST, // OpCodeIndex::LE_TERMINATE_BIG, // OpCodeIndex::LE_TERMINATE_BIG, // OpCodeIndex::LE_BIG_CREATE_SYNC, // OpCodeIndex::LE_BIG_CREATE_SYNC, // OpCodeIndex::LE_BIG_TERMINATE_SYNC, // OpCodeIndex::LE_BIG_TERMINATE_SYNC, // OpCodeIndex::LE_REQUEST_PEER_SCA, // OpCodeIndex::LE_REQUEST_PEER_SCA, // OpCodeIndex::LE_SETUP_ISO_DATA_PATH, OpCodeIndex::LE_SETUP_ISO_DATA_PATH, OpCodeIndex::LE_REMOVE_ISO_DATA_PATH, // OpCodeIndex::LE_REMOVE_ISO_DATA_PATH, // OpCodeIndex::LE_ISO_TRANSMIT_TEST, // OpCodeIndex::LE_ISO_TRANSMIT_TEST, // OpCodeIndex::LE_ISO_RECEIVE_TEST, // OpCodeIndex::LE_ISO_RECEIVE_TEST, // OpCodeIndex::LE_ISO_READ_TEST_COUNTERS, // OpCodeIndex::LE_ISO_READ_TEST_COUNTERS, Loading Loading @@ -1794,6 +1791,19 @@ static std::vector<OpCodeIndex> ll_privacy_commands_ = { OpCodeIndex::LE_SET_RESOLVABLE_PRIVATE_ADDRESS_TIMEOUT, OpCodeIndex::LE_SET_RESOLVABLE_PRIVATE_ADDRESS_TIMEOUT, }; }; // Commands enabled by the LL Connected Isochronous Stream feature bit. // Central and Peripheral support bits are enabled together. static std::vector<OpCodeIndex> ll_connected_isochronous_stream_commands_ = { OpCodeIndex::LE_SET_CIG_PARAMETERS, OpCodeIndex::LE_SET_CIG_PARAMETERS_TEST, OpCodeIndex::LE_CREATE_CIS, OpCodeIndex::LE_REMOVE_CIG, OpCodeIndex::LE_ACCEPT_CIS_REQUEST, OpCodeIndex::LE_REJECT_CIS_REQUEST, OpCodeIndex::LE_SETUP_ISO_DATA_PATH, OpCodeIndex::LE_REMOVE_ISO_DATA_PATH, }; static void SetLLFeatureBit(uint64_t& le_features, LLFeaturesBits bit, static void SetLLFeatureBit(uint64_t& le_features, LLFeaturesBits bit, bool set) { bool set) { if (set) { if (set) { Loading Loading @@ -1865,6 +1875,17 @@ ControllerProperties::ControllerProperties( SetLLFeatureBit(le_features, LLFeaturesBits::LE_CODED_PHY, SetLLFeatureBit(le_features, LLFeaturesBits::LE_CODED_PHY, features.le_coded_phy()); features.le_coded_phy()); } } if (features.has_le_connected_isochronous_stream()) { SetLLFeatureBit(le_features, LLFeaturesBits::CONNECTED_ISOCHRONOUS_STREAM_CENTRAL, features.le_connected_isochronous_stream()); SetLLFeatureBit(le_features, LLFeaturesBits::CONNECTED_ISOCHRONOUS_STREAM_PERIPHERAL, features.le_connected_isochronous_stream()); SetSupportedCommandBits(supported_commands, ll_connected_isochronous_stream_commands_, features.le_connected_isochronous_stream()); } } } // Apply selected quirks. // Apply selected quirks. Loading tools/rootcanal/py/controller.py +199 −1 Original line number Original line Diff line number Diff line Loading @@ -3,6 +3,7 @@ import collections import enum import enum import hci_packets as hci import hci_packets as hci import link_layer_packets as ll import link_layer_packets as ll import llcp_packets as llcp import py.bluetooth import py.bluetooth import sys import sys import typing import typing Loading Loading @@ -81,9 +82,11 @@ class Controller: self.address = address self.address = address self.evt_queue = collections.deque() self.evt_queue = collections.deque() self.acl_queue = collections.deque() self.acl_queue = collections.deque() self.iso_queue = collections.deque() self.ll_queue = collections.deque() self.ll_queue = collections.deque() self.evt_queue_event = asyncio.Event() self.evt_queue_event = asyncio.Event() self.acl_queue_event = asyncio.Event() self.acl_queue_event = asyncio.Event() self.iso_queue_event = asyncio.Event() self.ll_queue_event = asyncio.Event() self.ll_queue_event = asyncio.Event() def __del__(self): def __del__(self): Loading @@ -98,8 +101,12 @@ class Controller: print(f"<-- received HCI ACL packet data={len(packet)}[..]") print(f"<-- received HCI ACL packet data={len(packet)}[..]") self.acl_queue.append(packet) self.acl_queue.append(packet) self.acl_queue_event.set() self.acl_queue_event.set() elif idc == Idc.Iso: print(f"<-- received HCI ISO packet data={len(packet)}[..]") self.iso_queue.append(packet) self.iso_queue_event.set() else: else: print(f"ignoring HCI packet typ={typ}") print(f"ignoring HCI packet typ={idc}") def receive_ll_(self, packet: bytes, phy: int, tx_power: int): def receive_ll_(self, packet: bytes, phy: int, tx_power: int): print(f"<-- received LL pdu data={len(packet)}[..]") print(f"<-- received LL pdu data={len(packet)}[..]") Loading @@ -111,12 +118,31 @@ class Controller: data = cmd.serialize() data = cmd.serialize() rootcanal.ffi_controller_receive_hci(c_void_p(self.instance), c_int(Idc.Cmd), c_char_p(data), c_int(len(data))) rootcanal.ffi_controller_receive_hci(c_void_p(self.instance), c_int(Idc.Cmd), c_char_p(data), c_int(len(data))) def send_iso(self, iso: hci.Iso): print(f"--> sending HCI iso pdu data={len(iso.payload)}[..]") data = iso.serialize() rootcanal.ffi_controller_receive_hci(c_void_p(self.instance), c_int(Idc.Iso), c_char_p(data), c_int(len(data))) def send_ll(self, pdu: ll.LinkLayerPacket, phy: Phy = Phy.LowEnergy, rssi: int = -90): def send_ll(self, pdu: ll.LinkLayerPacket, phy: Phy = Phy.LowEnergy, rssi: int = -90): print(f"--> sending LL pdu {pdu.__class__.__name__}") print(f"--> sending LL pdu {pdu.__class__.__name__}") data = pdu.serialize() data = pdu.serialize() rootcanal.ffi_controller_receive_ll(c_void_p(self.instance), c_char_p(data), c_int(len(data)), c_int(phy), rootcanal.ffi_controller_receive_ll(c_void_p(self.instance), c_char_p(data), c_int(len(data)), c_int(phy), c_int(rssi)) c_int(rssi)) def send_llcp(self, source_address: hci.Address, destination_address: hci.Address, pdu: llcp.LlcpPacket, phy: Phy = Phy.LowEnergy, rssi: int = -90): print(f"--> sending LLCP pdu {pdu.__class__.__name__}") ll_pdu = ll.Llcp(source_address=source_address, destination_address=destination_address, payload=pdu.serialize()) data = ll_pdu.serialize() rootcanal.ffi_controller_receive_ll(c_void_p(self.instance), c_char_p(data), c_int(len(data)), c_int(phy), c_int(rssi)) async def start(self): async def start(self): async def timer(): async def timer(): Loading @@ -138,6 +164,13 @@ class Controller: evt.show() evt.show() raise Exception("evt queue not empty at stop()") raise Exception("evt queue not empty at stop()") if self.iso_queue: print("iso queue not empty at stop():") for packet in self.iso_queue: iso = hci.Iso.parse_all(packet) iso.show() raise Exception("ll queue not empty at stop()") if self.ll_queue: if self.ll_queue: for (packet, _) in self.ll_queue: for (packet, _) in self.ll_queue: pdu = ll.LinkLayerPacket.parse_all(packet) pdu = ll.LinkLayerPacket.parse_all(packet) Loading @@ -150,6 +183,12 @@ class Controller: self.evt_queue_event.clear() self.evt_queue_event.clear() return self.evt_queue.popleft() return self.evt_queue.popleft() async def receive_iso(self): while not self.iso_queue: await self.iso_queue_event.wait() self.iso_queue_event.clear() return self.iso_queue.popleft() async def expect_evt(self, expected_evt: hci.Event): async def expect_evt(self, expected_evt: hci.Event): packet = await self.receive_evt() packet = await self.receive_evt() evt = hci.Event.parse_all(packet) evt = hci.Event.parse_all(packet) Loading Loading @@ -238,6 +277,18 @@ class ControllerTest(unittest.IsolatedAsyncioTestCase): assert evt.num_hci_command_packets == 1 assert evt.num_hci_command_packets == 1 return evt return evt async def expect_iso(self, expected_iso: hci.Iso, timeout: int = 3): packet = await asyncio.wait_for(self.controller.receive_iso(), timeout=timeout) iso = hci.Iso.parse_all(packet) if iso != expected_iso: print("received unexpected iso packet") print("expected packet:") expected_iso.show() print("received packet:") iso.show() self.assertTrue(False) async def expect_ll(self, async def expect_ll(self, expected_pdus: typing.Union[list, typing.Union[ll.LinkLayerPacket, type]], expected_pdus: typing.Union[list, typing.Union[ll.LinkLayerPacket, type]], timeout: int = 3) -> ll.LinkLayerPacket: timeout: int = 3) -> ll.LinkLayerPacket: Loading Loading @@ -265,5 +316,152 @@ class ControllerTest(unittest.IsolatedAsyncioTestCase): self.assertTrue(False) self.assertTrue(False) async def expect_llcp(self, source_address: hci.Address, destination_address: hci.Address, expected_pdu: llcp.LlcpPacket, timeout: int = 3) -> llcp.LlcpPacket: packet = await asyncio.wait_for(self.controller.receive_ll(), timeout=timeout) pdu = ll.LinkLayerPacket.parse_all(packet) if (pdu.type != ll.PacketType.LLCP or pdu.source_address != source_address or pdu.destination_address != destination_address): print("received unexpected pdu:") pdu.show() print(f"expected pdu: {source_address} -> {destination_address}") expected_pdu.show() self.assertTrue(False) pdu = llcp.LlcpPacket.parse_all(pdu.payload) if pdu != expected_pdu: print("received unexpected pdu:") pdu.show() print("expected pdu:") expected_pdu.show() self.assertTrue(False) return pdu async def enable_connected_isochronous_stream_host_support(self): """Enable Connected Isochronous Stream Host Support in the LE Feature mask.""" self.controller.send_cmd( hci.LeSetHostFeature(bit_number=hci.LeHostFeatureBits.CONNECTED_ISO_STREAM_HOST_SUPPORT, bit_value=hci.Enable.ENABLED)) await self.expect_evt(hci.LeSetHostFeatureComplete(status=ErrorCode.SUCCESS, num_hci_command_packets=1)) async def establish_le_connection_central(self, peer_address: hci.Address) -> int: """Establish a connection with the selected peer as Central. Returns the ACL connection handle for the opened link.""" self.controller.send_cmd( hci.LeExtendedCreateConnection(initiator_filter_policy=hci.InitiatorFilterPolicy.USE_PEER_ADDRESS, own_address_type=hci.OwnAddressType.PUBLIC_DEVICE_ADDRESS, peer_address_type=hci.AddressType.PUBLIC_DEVICE_ADDRESS, peer_address=peer_address, initiating_phys=0x1, phy_scan_parameters=[ hci.LeCreateConnPhyScanParameters( scan_interval=0x200, scan_window=0x100, conn_interval_min=0x200, conn_interval_max=0x200, conn_latency=0x6, supervision_timeout=0xc80, min_ce_length=0, max_ce_length=0, ) ])) await self.expect_evt(hci.LeExtendedCreateConnectionStatus(status=ErrorCode.SUCCESS, num_hci_command_packets=1)) self.controller.send_ll(ll.LeLegacyAdvertisingPdu(source_address=peer_address, advertising_address_type=ll.AddressType.PUBLIC, advertising_type=ll.LegacyAdvertisingType.ADV_IND, advertising_data=[]), rssi=-16) await self.expect_ll( ll.LeConnect(source_address=self.controller.address, destination_address=peer_address, initiating_address_type=ll.AddressType.PUBLIC, advertising_address_type=ll.AddressType.PUBLIC, conn_interval=0x200, conn_peripheral_latency=0x6, conn_supervision_timeout=0xc80)) self.controller.send_ll( ll.LeConnectComplete(source_address=peer_address, destination_address=self.controller.address, initiating_address_type=ll.AddressType.PUBLIC, advertising_address_type=ll.AddressType.PUBLIC, conn_interval=0x200, conn_peripheral_latency=0x6, conn_supervision_timeout=0xc80)) connection_complete = await self.expect_evt( hci.LeEnhancedConnectionComplete(status=ErrorCode.SUCCESS, connection_handle=self.Any, role=hci.Role.CENTRAL, peer_address_type=hci.AddressType.PUBLIC_DEVICE_ADDRESS, peer_address=peer_address, conn_interval=0x200, conn_latency=0x6, supervision_timeout=0xc80, central_clock_accuracy=hci.ClockAccuracy.PPM_500)) acl_connection_handle = connection_complete.connection_handle await self.expect_evt( hci.LeChannelSelectionAlgorithm(connection_handle=acl_connection_handle, channel_selection_algorithm=hci.ChannelSelectionAlgorithm.ALGORITHM_1)) return acl_connection_handle async def establish_le_connection_peripheral(self, peer_address: hci.Address) -> int: """Establish a connection with the selected peer as Peripheral. Returns the ACL connection handle for the opened link.""" self.controller.send_cmd( hci.LeSetAdvertisingParameters(advertising_interval_min=0x200, advertising_interval_max=0x200, advertising_type=hci.AdvertisingType.ADV_IND, own_address_type=hci.OwnAddressType.PUBLIC_DEVICE_ADDRESS, advertising_channel_map=0x7, advertising_filter_policy=hci.AdvertisingFilterPolicy.ALL_DEVICES)) await self.expect_evt( hci.LeSetAdvertisingParametersComplete(status=ErrorCode.SUCCESS, num_hci_command_packets=1)) self.controller.send_cmd(hci.LeSetAdvertisingEnable(advertising_enable=True)) await self.expect_evt(hci.LeSetAdvertisingEnableComplete(status=ErrorCode.SUCCESS, num_hci_command_packets=1)) self.controller.send_ll(ll.LeConnect(source_address=peer_address, destination_address=self.controller.address, initiating_address_type=ll.AddressType.PUBLIC, advertising_address_type=ll.AddressType.PUBLIC, conn_interval=0x200, conn_peripheral_latency=0x200, conn_supervision_timeout=0x200), rssi=-16) await self.expect_ll( ll.LeConnectComplete(source_address=self.controller.address, destination_address=peer_address, conn_interval=0x200, conn_peripheral_latency=0x200, conn_supervision_timeout=0x200)) connection_complete = await self.expect_evt( hci.LeEnhancedConnectionComplete(status=ErrorCode.SUCCESS, connection_handle=self.Any, role=hci.Role.PERIPHERAL, peer_address_type=hci.AddressType.PUBLIC_DEVICE_ADDRESS, peer_address=peer_address, conn_interval=0x200, conn_latency=0x200, supervision_timeout=0x200, central_clock_accuracy=hci.ClockAccuracy.PPM_500)) return connection_complete.connection_handle def tearDown(self): def tearDown(self): self.controller.stop() self.controller.stop() Loading
tools/rootcanal/Android.bp +19 −0 Original line number Original line Diff line number Diff line Loading @@ -197,6 +197,22 @@ genrule { ], ], } } // Generate the python parser+serializer backend for // rust/llcp_packets.pdl. genrule { name: "llcp_packets_python3_gen", defaults: ["pdl_python_generator_defaults"], cmd: "$(location :pdlc) $(in) |" + " $(location :pdl_python_generator)" + " --output $(out) --custom-type-location py.bluetooth", srcs: [ "rust/llcp_packets.pdl", ], out: [ "llcp_packets.py", ], } // Generate the python parser+serializer backend for // Generate the python parser+serializer backend for // hci_packets.pdl. // hci_packets.pdl. genrule { genrule { Loading Loading @@ -285,8 +301,11 @@ python_test_host { srcs: [ srcs: [ ":hci_packets_python3_gen", ":hci_packets_python3_gen", ":link_layer_packets_python3_gen", ":link_layer_packets_python3_gen", ":llcp_packets_python3_gen", "py/bluetooth.py", "py/bluetooth.py", "py/controller.py", "py/controller.py", "test/LL/CIS/CEN/*.py", "test/LL/CIS/PER/*.py", "test/LL/CON_/CEN/*.py", "test/LL/CON_/CEN/*.py", "test/LL/CON_/PER/*.py", "test/LL/CON_/PER/*.py", "test/LL/DDI/ADV/*.py", "test/LL/DDI/ADV/*.py", Loading
tools/rootcanal/config.proto +3 −0 Original line number Original line Diff line number Diff line Loading @@ -14,6 +14,9 @@ message ControllerFeatures { optional bool ll_privacy = 3; optional bool ll_privacy = 3; optional bool le_2m_phy = 4; optional bool le_2m_phy = 4; optional bool le_coded_phy = 5; optional bool le_coded_phy = 5; // Enable the support for both LL Connected Isochronous Stream Central // and LL Connected Isochronous Stream Peripheral. optional bool le_connected_isochronous_stream = 6; } } message ControllerQuirks { message ControllerQuirks { Loading
tools/rootcanal/model/controller/controller_properties.cc +33 −12 Original line number Original line Diff line number Diff line Loading @@ -108,13 +108,13 @@ static constexpr uint64_t LlFeatures() { LLFeaturesBits::LE_PING, LLFeaturesBits::LE_PING, LLFeaturesBits::LL_PRIVACY, LLFeaturesBits::LL_PRIVACY, LLFeaturesBits::EXTENDED_SCANNER_FILTER_POLICIES, LLFeaturesBits::EXTENDED_SCANNER_FILTER_POLICIES, LLFeaturesBits::LE_2M_PHY, LLFeaturesBits::LE_CODED_PHY, LLFeaturesBits::LE_2M_PHY, LLFeaturesBits::LE_CODED_PHY, LLFeaturesBits::LE_EXTENDED_ADVERTISING, LLFeaturesBits::LE_EXTENDED_ADVERTISING, LLFeaturesBits::LE_PERIODIC_ADVERTISING, LLFeaturesBits::LE_PERIODIC_ADVERTISING, // TODO: breaks AVD boot tests with LE audio LLFeaturesBits::CONNECTED_ISOCHRONOUS_STREAM_CENTRAL, // LLFeaturesBits::CONNECTED_ISOCHRONOUS_STREAM_CENTRAL, LLFeaturesBits::CONNECTED_ISOCHRONOUS_STREAM_PERIPHERAL, // LLFeaturesBits::CONNECTED_ISOCHRONOUS_STREAM_PERIPHERAL, }; }; uint64_t value = 0; uint64_t value = 0; Loading Loading @@ -377,20 +377,17 @@ static std::array<uint8_t, 64> SupportedCommands() { // OpCodeIndex::LE_MODIFY_SLEEP_CLOCK_ACCURACY, // OpCodeIndex::LE_MODIFY_SLEEP_CLOCK_ACCURACY, OpCodeIndex::LE_READ_BUFFER_SIZE_V2, OpCodeIndex::LE_READ_BUFFER_SIZE_V2, // OpCodeIndex::LE_READ_ISO_TX_SYNC, // OpCodeIndex::LE_READ_ISO_TX_SYNC, // OpCodeIndex::LE_SET_CIG_PARAMETERS, OpCodeIndex::LE_SET_CIG_PARAMETERS, // OpCodeIndex::LE_SET_CIG_PARAMETERS_TEST, OpCodeIndex::LE_SET_CIG_PARAMETERS_TEST, OpCodeIndex::LE_CREATE_CIS, // OpCodeIndex::LE_CREATE_CIS, OpCodeIndex::LE_REMOVE_CIG, OpCodeIndex::LE_ACCEPT_CIS_REQUEST, // OpCodeIndex::LE_REMOVE_CIG, OpCodeIndex::LE_REJECT_CIS_REQUEST, // OpCodeIndex::LE_ACCEPT_CIS_REQUEST, // OpCodeIndex::LE_REJECT_CIS_REQUEST, // OpCodeIndex::LE_CREATE_BIG, // OpCodeIndex::LE_CREATE_BIG, // OpCodeIndex::LE_CREATE_BIG_TEST, // OpCodeIndex::LE_CREATE_BIG_TEST, // OpCodeIndex::LE_TERMINATE_BIG, // OpCodeIndex::LE_TERMINATE_BIG, // OpCodeIndex::LE_BIG_CREATE_SYNC, // OpCodeIndex::LE_BIG_CREATE_SYNC, // OpCodeIndex::LE_BIG_TERMINATE_SYNC, // OpCodeIndex::LE_BIG_TERMINATE_SYNC, // OpCodeIndex::LE_REQUEST_PEER_SCA, // OpCodeIndex::LE_REQUEST_PEER_SCA, // OpCodeIndex::LE_SETUP_ISO_DATA_PATH, OpCodeIndex::LE_SETUP_ISO_DATA_PATH, OpCodeIndex::LE_REMOVE_ISO_DATA_PATH, // OpCodeIndex::LE_REMOVE_ISO_DATA_PATH, // OpCodeIndex::LE_ISO_TRANSMIT_TEST, // OpCodeIndex::LE_ISO_TRANSMIT_TEST, // OpCodeIndex::LE_ISO_RECEIVE_TEST, // OpCodeIndex::LE_ISO_RECEIVE_TEST, // OpCodeIndex::LE_ISO_READ_TEST_COUNTERS, // OpCodeIndex::LE_ISO_READ_TEST_COUNTERS, Loading Loading @@ -1794,6 +1791,19 @@ static std::vector<OpCodeIndex> ll_privacy_commands_ = { OpCodeIndex::LE_SET_RESOLVABLE_PRIVATE_ADDRESS_TIMEOUT, OpCodeIndex::LE_SET_RESOLVABLE_PRIVATE_ADDRESS_TIMEOUT, }; }; // Commands enabled by the LL Connected Isochronous Stream feature bit. // Central and Peripheral support bits are enabled together. static std::vector<OpCodeIndex> ll_connected_isochronous_stream_commands_ = { OpCodeIndex::LE_SET_CIG_PARAMETERS, OpCodeIndex::LE_SET_CIG_PARAMETERS_TEST, OpCodeIndex::LE_CREATE_CIS, OpCodeIndex::LE_REMOVE_CIG, OpCodeIndex::LE_ACCEPT_CIS_REQUEST, OpCodeIndex::LE_REJECT_CIS_REQUEST, OpCodeIndex::LE_SETUP_ISO_DATA_PATH, OpCodeIndex::LE_REMOVE_ISO_DATA_PATH, }; static void SetLLFeatureBit(uint64_t& le_features, LLFeaturesBits bit, static void SetLLFeatureBit(uint64_t& le_features, LLFeaturesBits bit, bool set) { bool set) { if (set) { if (set) { Loading Loading @@ -1865,6 +1875,17 @@ ControllerProperties::ControllerProperties( SetLLFeatureBit(le_features, LLFeaturesBits::LE_CODED_PHY, SetLLFeatureBit(le_features, LLFeaturesBits::LE_CODED_PHY, features.le_coded_phy()); features.le_coded_phy()); } } if (features.has_le_connected_isochronous_stream()) { SetLLFeatureBit(le_features, LLFeaturesBits::CONNECTED_ISOCHRONOUS_STREAM_CENTRAL, features.le_connected_isochronous_stream()); SetLLFeatureBit(le_features, LLFeaturesBits::CONNECTED_ISOCHRONOUS_STREAM_PERIPHERAL, features.le_connected_isochronous_stream()); SetSupportedCommandBits(supported_commands, ll_connected_isochronous_stream_commands_, features.le_connected_isochronous_stream()); } } } // Apply selected quirks. // Apply selected quirks. Loading
tools/rootcanal/py/controller.py +199 −1 Original line number Original line Diff line number Diff line Loading @@ -3,6 +3,7 @@ import collections import enum import enum import hci_packets as hci import hci_packets as hci import link_layer_packets as ll import link_layer_packets as ll import llcp_packets as llcp import py.bluetooth import py.bluetooth import sys import sys import typing import typing Loading Loading @@ -81,9 +82,11 @@ class Controller: self.address = address self.address = address self.evt_queue = collections.deque() self.evt_queue = collections.deque() self.acl_queue = collections.deque() self.acl_queue = collections.deque() self.iso_queue = collections.deque() self.ll_queue = collections.deque() self.ll_queue = collections.deque() self.evt_queue_event = asyncio.Event() self.evt_queue_event = asyncio.Event() self.acl_queue_event = asyncio.Event() self.acl_queue_event = asyncio.Event() self.iso_queue_event = asyncio.Event() self.ll_queue_event = asyncio.Event() self.ll_queue_event = asyncio.Event() def __del__(self): def __del__(self): Loading @@ -98,8 +101,12 @@ class Controller: print(f"<-- received HCI ACL packet data={len(packet)}[..]") print(f"<-- received HCI ACL packet data={len(packet)}[..]") self.acl_queue.append(packet) self.acl_queue.append(packet) self.acl_queue_event.set() self.acl_queue_event.set() elif idc == Idc.Iso: print(f"<-- received HCI ISO packet data={len(packet)}[..]") self.iso_queue.append(packet) self.iso_queue_event.set() else: else: print(f"ignoring HCI packet typ={typ}") print(f"ignoring HCI packet typ={idc}") def receive_ll_(self, packet: bytes, phy: int, tx_power: int): def receive_ll_(self, packet: bytes, phy: int, tx_power: int): print(f"<-- received LL pdu data={len(packet)}[..]") print(f"<-- received LL pdu data={len(packet)}[..]") Loading @@ -111,12 +118,31 @@ class Controller: data = cmd.serialize() data = cmd.serialize() rootcanal.ffi_controller_receive_hci(c_void_p(self.instance), c_int(Idc.Cmd), c_char_p(data), c_int(len(data))) rootcanal.ffi_controller_receive_hci(c_void_p(self.instance), c_int(Idc.Cmd), c_char_p(data), c_int(len(data))) def send_iso(self, iso: hci.Iso): print(f"--> sending HCI iso pdu data={len(iso.payload)}[..]") data = iso.serialize() rootcanal.ffi_controller_receive_hci(c_void_p(self.instance), c_int(Idc.Iso), c_char_p(data), c_int(len(data))) def send_ll(self, pdu: ll.LinkLayerPacket, phy: Phy = Phy.LowEnergy, rssi: int = -90): def send_ll(self, pdu: ll.LinkLayerPacket, phy: Phy = Phy.LowEnergy, rssi: int = -90): print(f"--> sending LL pdu {pdu.__class__.__name__}") print(f"--> sending LL pdu {pdu.__class__.__name__}") data = pdu.serialize() data = pdu.serialize() rootcanal.ffi_controller_receive_ll(c_void_p(self.instance), c_char_p(data), c_int(len(data)), c_int(phy), rootcanal.ffi_controller_receive_ll(c_void_p(self.instance), c_char_p(data), c_int(len(data)), c_int(phy), c_int(rssi)) c_int(rssi)) def send_llcp(self, source_address: hci.Address, destination_address: hci.Address, pdu: llcp.LlcpPacket, phy: Phy = Phy.LowEnergy, rssi: int = -90): print(f"--> sending LLCP pdu {pdu.__class__.__name__}") ll_pdu = ll.Llcp(source_address=source_address, destination_address=destination_address, payload=pdu.serialize()) data = ll_pdu.serialize() rootcanal.ffi_controller_receive_ll(c_void_p(self.instance), c_char_p(data), c_int(len(data)), c_int(phy), c_int(rssi)) async def start(self): async def start(self): async def timer(): async def timer(): Loading @@ -138,6 +164,13 @@ class Controller: evt.show() evt.show() raise Exception("evt queue not empty at stop()") raise Exception("evt queue not empty at stop()") if self.iso_queue: print("iso queue not empty at stop():") for packet in self.iso_queue: iso = hci.Iso.parse_all(packet) iso.show() raise Exception("ll queue not empty at stop()") if self.ll_queue: if self.ll_queue: for (packet, _) in self.ll_queue: for (packet, _) in self.ll_queue: pdu = ll.LinkLayerPacket.parse_all(packet) pdu = ll.LinkLayerPacket.parse_all(packet) Loading @@ -150,6 +183,12 @@ class Controller: self.evt_queue_event.clear() self.evt_queue_event.clear() return self.evt_queue.popleft() return self.evt_queue.popleft() async def receive_iso(self): while not self.iso_queue: await self.iso_queue_event.wait() self.iso_queue_event.clear() return self.iso_queue.popleft() async def expect_evt(self, expected_evt: hci.Event): async def expect_evt(self, expected_evt: hci.Event): packet = await self.receive_evt() packet = await self.receive_evt() evt = hci.Event.parse_all(packet) evt = hci.Event.parse_all(packet) Loading Loading @@ -238,6 +277,18 @@ class ControllerTest(unittest.IsolatedAsyncioTestCase): assert evt.num_hci_command_packets == 1 assert evt.num_hci_command_packets == 1 return evt return evt async def expect_iso(self, expected_iso: hci.Iso, timeout: int = 3): packet = await asyncio.wait_for(self.controller.receive_iso(), timeout=timeout) iso = hci.Iso.parse_all(packet) if iso != expected_iso: print("received unexpected iso packet") print("expected packet:") expected_iso.show() print("received packet:") iso.show() self.assertTrue(False) async def expect_ll(self, async def expect_ll(self, expected_pdus: typing.Union[list, typing.Union[ll.LinkLayerPacket, type]], expected_pdus: typing.Union[list, typing.Union[ll.LinkLayerPacket, type]], timeout: int = 3) -> ll.LinkLayerPacket: timeout: int = 3) -> ll.LinkLayerPacket: Loading Loading @@ -265,5 +316,152 @@ class ControllerTest(unittest.IsolatedAsyncioTestCase): self.assertTrue(False) self.assertTrue(False) async def expect_llcp(self, source_address: hci.Address, destination_address: hci.Address, expected_pdu: llcp.LlcpPacket, timeout: int = 3) -> llcp.LlcpPacket: packet = await asyncio.wait_for(self.controller.receive_ll(), timeout=timeout) pdu = ll.LinkLayerPacket.parse_all(packet) if (pdu.type != ll.PacketType.LLCP or pdu.source_address != source_address or pdu.destination_address != destination_address): print("received unexpected pdu:") pdu.show() print(f"expected pdu: {source_address} -> {destination_address}") expected_pdu.show() self.assertTrue(False) pdu = llcp.LlcpPacket.parse_all(pdu.payload) if pdu != expected_pdu: print("received unexpected pdu:") pdu.show() print("expected pdu:") expected_pdu.show() self.assertTrue(False) return pdu async def enable_connected_isochronous_stream_host_support(self): """Enable Connected Isochronous Stream Host Support in the LE Feature mask.""" self.controller.send_cmd( hci.LeSetHostFeature(bit_number=hci.LeHostFeatureBits.CONNECTED_ISO_STREAM_HOST_SUPPORT, bit_value=hci.Enable.ENABLED)) await self.expect_evt(hci.LeSetHostFeatureComplete(status=ErrorCode.SUCCESS, num_hci_command_packets=1)) async def establish_le_connection_central(self, peer_address: hci.Address) -> int: """Establish a connection with the selected peer as Central. Returns the ACL connection handle for the opened link.""" self.controller.send_cmd( hci.LeExtendedCreateConnection(initiator_filter_policy=hci.InitiatorFilterPolicy.USE_PEER_ADDRESS, own_address_type=hci.OwnAddressType.PUBLIC_DEVICE_ADDRESS, peer_address_type=hci.AddressType.PUBLIC_DEVICE_ADDRESS, peer_address=peer_address, initiating_phys=0x1, phy_scan_parameters=[ hci.LeCreateConnPhyScanParameters( scan_interval=0x200, scan_window=0x100, conn_interval_min=0x200, conn_interval_max=0x200, conn_latency=0x6, supervision_timeout=0xc80, min_ce_length=0, max_ce_length=0, ) ])) await self.expect_evt(hci.LeExtendedCreateConnectionStatus(status=ErrorCode.SUCCESS, num_hci_command_packets=1)) self.controller.send_ll(ll.LeLegacyAdvertisingPdu(source_address=peer_address, advertising_address_type=ll.AddressType.PUBLIC, advertising_type=ll.LegacyAdvertisingType.ADV_IND, advertising_data=[]), rssi=-16) await self.expect_ll( ll.LeConnect(source_address=self.controller.address, destination_address=peer_address, initiating_address_type=ll.AddressType.PUBLIC, advertising_address_type=ll.AddressType.PUBLIC, conn_interval=0x200, conn_peripheral_latency=0x6, conn_supervision_timeout=0xc80)) self.controller.send_ll( ll.LeConnectComplete(source_address=peer_address, destination_address=self.controller.address, initiating_address_type=ll.AddressType.PUBLIC, advertising_address_type=ll.AddressType.PUBLIC, conn_interval=0x200, conn_peripheral_latency=0x6, conn_supervision_timeout=0xc80)) connection_complete = await self.expect_evt( hci.LeEnhancedConnectionComplete(status=ErrorCode.SUCCESS, connection_handle=self.Any, role=hci.Role.CENTRAL, peer_address_type=hci.AddressType.PUBLIC_DEVICE_ADDRESS, peer_address=peer_address, conn_interval=0x200, conn_latency=0x6, supervision_timeout=0xc80, central_clock_accuracy=hci.ClockAccuracy.PPM_500)) acl_connection_handle = connection_complete.connection_handle await self.expect_evt( hci.LeChannelSelectionAlgorithm(connection_handle=acl_connection_handle, channel_selection_algorithm=hci.ChannelSelectionAlgorithm.ALGORITHM_1)) return acl_connection_handle async def establish_le_connection_peripheral(self, peer_address: hci.Address) -> int: """Establish a connection with the selected peer as Peripheral. Returns the ACL connection handle for the opened link.""" self.controller.send_cmd( hci.LeSetAdvertisingParameters(advertising_interval_min=0x200, advertising_interval_max=0x200, advertising_type=hci.AdvertisingType.ADV_IND, own_address_type=hci.OwnAddressType.PUBLIC_DEVICE_ADDRESS, advertising_channel_map=0x7, advertising_filter_policy=hci.AdvertisingFilterPolicy.ALL_DEVICES)) await self.expect_evt( hci.LeSetAdvertisingParametersComplete(status=ErrorCode.SUCCESS, num_hci_command_packets=1)) self.controller.send_cmd(hci.LeSetAdvertisingEnable(advertising_enable=True)) await self.expect_evt(hci.LeSetAdvertisingEnableComplete(status=ErrorCode.SUCCESS, num_hci_command_packets=1)) self.controller.send_ll(ll.LeConnect(source_address=peer_address, destination_address=self.controller.address, initiating_address_type=ll.AddressType.PUBLIC, advertising_address_type=ll.AddressType.PUBLIC, conn_interval=0x200, conn_peripheral_latency=0x200, conn_supervision_timeout=0x200), rssi=-16) await self.expect_ll( ll.LeConnectComplete(source_address=self.controller.address, destination_address=peer_address, conn_interval=0x200, conn_peripheral_latency=0x200, conn_supervision_timeout=0x200)) connection_complete = await self.expect_evt( hci.LeEnhancedConnectionComplete(status=ErrorCode.SUCCESS, connection_handle=self.Any, role=hci.Role.PERIPHERAL, peer_address_type=hci.AddressType.PUBLIC_DEVICE_ADDRESS, peer_address=peer_address, conn_interval=0x200, conn_latency=0x200, supervision_timeout=0x200, central_clock_accuracy=hci.ClockAccuracy.PPM_500)) return connection_complete.connection_handle def tearDown(self): def tearDown(self): self.controller.stop() self.controller.stop()