Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit ec391bf5 authored by Henri Chataing's avatar Henri Chataing Committed by Gerrit Code Review
Browse files

Merge changes I29547ac0,I40fbd61f

* changes:
  RootCanal: Implement LL tests CIS/CEN/*, CIS/PER/*
  RootCanal: Enable Connected Isochronous Stream support
parents aba75aaa 6729c606
Loading
Loading
Loading
Loading
+19 −0
Original line number Diff line number Diff line
@@ -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
// hci_packets.pdl.
genrule {
@@ -285,8 +301,11 @@ python_test_host {
    srcs: [
        ":hci_packets_python3_gen",
        ":link_layer_packets_python3_gen",
        ":llcp_packets_python3_gen",
        "py/bluetooth.py",
        "py/controller.py",
        "test/LL/CIS/CEN/*.py",
        "test/LL/CIS/PER/*.py",
        "test/LL/CON_/CEN/*.py",
        "test/LL/CON_/PER/*.py",
        "test/LL/DDI/ADV/*.py",
+3 −0
Original line number Diff line number Diff line
@@ -14,6 +14,9 @@ message ControllerFeatures {
  optional bool ll_privacy = 3;
  optional bool le_2m_phy = 4;
  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 {
+33 −12
Original line number Diff line number Diff line
@@ -108,13 +108,13 @@ static constexpr uint64_t LlFeatures() {
      LLFeaturesBits::LE_PING,
      LLFeaturesBits::LL_PRIVACY,
      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_PERIODIC_ADVERTISING,

      // TODO: breaks AVD boot tests with LE audio
      // LLFeaturesBits::CONNECTED_ISOCHRONOUS_STREAM_CENTRAL,
      // LLFeaturesBits::CONNECTED_ISOCHRONOUS_STREAM_PERIPHERAL,
      LLFeaturesBits::CONNECTED_ISOCHRONOUS_STREAM_CENTRAL,
      LLFeaturesBits::CONNECTED_ISOCHRONOUS_STREAM_PERIPHERAL,
  };

  uint64_t value = 0;
@@ -377,20 +377,17 @@ static std::array<uint8_t, 64> SupportedCommands() {
      // OpCodeIndex::LE_MODIFY_SLEEP_CLOCK_ACCURACY,
      OpCodeIndex::LE_READ_BUFFER_SIZE_V2,
      // OpCodeIndex::LE_READ_ISO_TX_SYNC,
      // 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_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_CREATE_BIG,
      // OpCodeIndex::LE_CREATE_BIG_TEST,
      // OpCodeIndex::LE_TERMINATE_BIG,
      // OpCodeIndex::LE_BIG_CREATE_SYNC,
      // OpCodeIndex::LE_BIG_TERMINATE_SYNC,
      // OpCodeIndex::LE_REQUEST_PEER_SCA,
      // OpCodeIndex::LE_SETUP_ISO_DATA_PATH,
      // OpCodeIndex::LE_REMOVE_ISO_DATA_PATH,
      OpCodeIndex::LE_SETUP_ISO_DATA_PATH, OpCodeIndex::LE_REMOVE_ISO_DATA_PATH,
      // OpCodeIndex::LE_ISO_TRANSMIT_TEST,
      // OpCodeIndex::LE_ISO_RECEIVE_TEST,
      // OpCodeIndex::LE_ISO_READ_TEST_COUNTERS,
@@ -1794,6 +1791,19 @@ static std::vector<OpCodeIndex> ll_privacy_commands_ = {
    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,
                            bool set) {
  if (set) {
@@ -1865,6 +1875,17 @@ ControllerProperties::ControllerProperties(
      SetLLFeatureBit(le_features, LLFeaturesBits::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.
+199 −1
Original line number Diff line number Diff line
@@ -3,6 +3,7 @@ import collections
import enum
import hci_packets as hci
import link_layer_packets as ll
import llcp_packets as llcp
import py.bluetooth
import sys
import typing
@@ -81,9 +82,11 @@ class Controller:
        self.address = address
        self.evt_queue = collections.deque()
        self.acl_queue = collections.deque()
        self.iso_queue = collections.deque()
        self.ll_queue = collections.deque()
        self.evt_queue_event = asyncio.Event()
        self.acl_queue_event = asyncio.Event()
        self.iso_queue_event = asyncio.Event()
        self.ll_queue_event = asyncio.Event()

    def __del__(self):
@@ -98,8 +101,12 @@ class Controller:
            print(f"<-- received HCI ACL packet data={len(packet)}[..]")
            self.acl_queue.append(packet)
            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:
            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):
        print(f"<-- received LL pdu data={len(packet)}[..]")
@@ -111,12 +118,31 @@ class Controller:
        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)))

    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):
        print(f"--> sending LL pdu {pdu.__class__.__name__}")
        data = 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))

    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 timer():
@@ -138,6 +164,13 @@ class Controller:
                evt.show()
            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:
            for (packet, _) in self.ll_queue:
                pdu = ll.LinkLayerPacket.parse_all(packet)
@@ -150,6 +183,12 @@ class Controller:
            self.evt_queue_event.clear()
        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):
        packet = await self.receive_evt()
        evt = hci.Event.parse_all(packet)
@@ -238,6 +277,18 @@ class ControllerTest(unittest.IsolatedAsyncioTestCase):
        assert evt.num_hci_command_packets == 1
        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,
                        expected_pdus: typing.Union[list, typing.Union[ll.LinkLayerPacket, type]],
                        timeout: int = 3) -> ll.LinkLayerPacket:
@@ -265,5 +316,152 @@ class ControllerTest(unittest.IsolatedAsyncioTestCase):

        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):
        self.controller.stop()
+192 −0

File added.

Preview size limit exceeded, changes collapsed.

Loading