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

Commit bbf3802a authored by Łukasz Rymanowski's avatar Łukasz Rymanowski
Browse files

leaudio: Fix for "Unable to get free Uni-Directional Sink CIS ID"

This could happen in the event, when CIS is not properly cleared after
device disconnection and to trigger it, CIS must be in assigned state.

Bug: 351314630
Flag: Exempt, regression tested with unit tests, new test added
Test: atest bluetooth_le_audio_test bluetooth_le_audio_client_test

Change-Id: Ib13b0c511bf8f159020df050211ae9219684dc18
parent 762061d2
Loading
Loading
Loading
Loading
+7 −5
Original line number Diff line number Diff line
@@ -1313,14 +1313,15 @@ void LeAudioDeviceGroup::AssignCisConnHandlesToAses(void) {
  }
}

void LeAudioDeviceGroup::CigConfiguration::UnassignCis(LeAudioDevice* leAudioDevice) {
void LeAudioDeviceGroup::CigConfiguration::UnassignCis(LeAudioDevice* leAudioDevice,
                                                       uint16_t conn_handle) {
  log::assert_that(leAudioDevice, "Invalid device");

  log::info("Group {}, group_id {}, device: {}", fmt::ptr(group_), group_->group_id_,
            leAudioDevice->address_);
  log::info("Group {}, group_id {}, device: {}, conn_handle: {:#x}", fmt::ptr(group_),
            group_->group_id_, leAudioDevice->address_, conn_handle);

  for (struct bluetooth::le_audio::types::cis& cis_entry : cises) {
    if (cis_entry.addr == leAudioDevice->address_) {
    if (cis_entry.conn_handle == conn_handle && cis_entry.addr == leAudioDevice->address_) {
      cis_entry.addr = RawAddress::kEmpty;
    }
  }
@@ -1707,6 +1708,7 @@ void LeAudioDeviceGroup::RemoveCisFromStreamIfNeeded(LeAudioDevice* leAudioDevic
  log::info("CIS Connection Handle: {}", cis_conn_hdl);

  if (!IsCisPartOfCurrentStream(cis_conn_hdl)) {
    cig.UnassignCis(leAudioDevice, cis_conn_hdl);
    return;
  }

@@ -1763,7 +1765,7 @@ void LeAudioDeviceGroup::RemoveCisFromStreamIfNeeded(LeAudioDevice* leAudioDevic
            bluetooth::le_audio::types::kLeAudioDirectionSource);
  }

  cig.UnassignCis(leAudioDevice);
  cig.UnassignCis(leAudioDevice, cis_conn_hdl);
}

bool LeAudioDeviceGroup::IsPendingConfiguration(void) const {
+1 −1
Original line number Diff line number Diff line
@@ -64,7 +64,7 @@ public:
    void GenerateCisIds(types::LeAudioContextType context_type);
    bool AssignCisIds(LeAudioDevice* leAudioDevice);
    void AssignCisConnHandles(const std::vector<uint16_t>& conn_handles);
    void UnassignCis(LeAudioDevice* leAudioDevice);
    void UnassignCis(LeAudioDevice* leAudioDevice, uint16_t conn_handle);

    std::vector<struct types::cis> cises;

+4 −2
Original line number Diff line number Diff line
@@ -2000,7 +2000,8 @@ TEST_P(LeAudioAseConfigurationTest, test_reconnection_media) {
  left->DeactivateAllAses();

  /* Unassign from the group*/
  group_->cig.UnassignCis(left);
  group_->cig.UnassignCis(left, 0x0012);
  group_->cig.UnassignCis(left, 0x0013);

  TestAsesInactivated(left);

@@ -2137,7 +2138,8 @@ TEST_P(LeAudioAseConfigurationTest, test_reactivation_conversational) {
  TestActiveAses();

  /* Simulate stopping stream with caching codec configuration in ASEs */
  group_->cig.UnassignCis(tws_headset);
  group_->cig.UnassignCis(tws_headset, 0x0012);
  group_->cig.UnassignCis(tws_headset, 0x0013);
  SetAsesToCachedConfiguration(tws_headset, LeAudioContextType::CONVERSATIONAL,
                               kLeAudioDirectionSink | kLeAudioDirectionSource);

+3 −3
Original line number Diff line number Diff line
@@ -1283,7 +1283,7 @@ protected:
                        stream_conf->stream_params.source.stream_locations.end());
              }

              group->cig.UnassignCis(leAudioDevice);
              group->cig.UnassignCis(leAudioDevice, event->cis_conn_hdl);
            });

    ON_CALL(mock_state_machine_, StopStream(_)).WillByDefault([this](LeAudioDeviceGroup* group) {
@@ -1344,9 +1344,9 @@ protected:
                  stream_conf->stream_params.source.stream_locations.end());
        }

        group->cig.UnassignCis(device);

        for (auto& ase : device->ases_) {
          group->cig.UnassignCis(device, ase.cis_conn_hdl);

          ase.cis_state = types::CisState::IDLE;
          ase.data_path_state = types::DataPathState::IDLE;
          ase.active = false;
+137 −0
Original line number Diff line number Diff line
@@ -5906,6 +5906,143 @@ TEST_F(StateMachineTest, testAttachDeviceToTheStream_autonomusQoSConfiguredState
  ASSERT_NE(ase->qos_config.retrans_nb, 0);
}

TEST_F(StateMachineTest, testAttachDeviceToTheStream_remoteDoesNotResponseOnCodecConfig) {
  const auto context_type = kContextTypeMedia;
  const auto leaudio_group_id = 6;
  const auto num_devices = 2;

  ContentControlIdKeeper::GetInstance()->SetCcid(media_context, media_ccid);

  // Prepare multiple fake connected devices in a group
  auto* group = PrepareSingleTestDeviceGroup(leaudio_group_id, context_type, num_devices);
  ASSERT_EQ(group->Size(), num_devices);

  PrepareConfigureCodecHandler(group);
  PrepareConfigureQosHandler(group);
  PrepareEnableHandler(group);
  PrepareDisableHandler(group);
  PrepareReleaseHandler(group);

  auto* leAudioDevice = group->GetFirstDevice();
  LeAudioDevice* lastDevice;
  LeAudioDevice* fistDevice = leAudioDevice;

  auto expected_devices_written = 0;
  while (leAudioDevice) {
    /* Three Writes:
     * 1: Codec Config
     * 2: Codec QoS
     * 3: Enabling
     */
    lastDevice = leAudioDevice;
    EXPECT_CALL(gatt_queue,
                WriteCharacteristic(leAudioDevice->conn_id_, leAudioDevice->ctp_hdls_.val_hdl, _,
                                    GATT_WRITE_NO_RSP, _, _))
            .Times(AtLeast(3));
    expected_devices_written++;
    leAudioDevice = group->GetNextDevice(leAudioDevice);
  }
  ASSERT_EQ(expected_devices_written, num_devices);

  EXPECT_CALL(*mock_iso_manager_, CreateCig(_, _)).Times(1);
  EXPECT_CALL(*mock_iso_manager_, EstablishCis(_)).Times(1);
  EXPECT_CALL(*mock_iso_manager_, SetupIsoDataPath(_, _)).Times(2);

  InjectInitialIdleNotification(group);

  // Start the configuration and stream Media content
  LeAudioGroupStateMachine::Get()->StartStream(group, context_type,
                                               {.sink = types::AudioContexts(context_type),
                                                .source = types::AudioContexts(context_type)},
                                               {.sink = std::vector<uint8_t>(1, media_ccid),
                                                .source = std::vector<uint8_t>(1, media_ccid)});

  // Check if group has transitioned to a proper state
  ASSERT_EQ(group->GetState(), types::AseState::BTA_LE_AUDIO_ASE_STATE_STREAMING);
  testing::Mock::VerifyAndClearExpectations(mock_iso_manager_);

  log::info(" Inject ACL disconnection of last device {} ", lastDevice->address_);
  uint16_t conn_id = lastDevice->conn_id_;

  InjectAclDisconnected(group, lastDevice);

  log::info("Check if group keeps streaming");

  ASSERT_EQ(group->GetState(), types::AseState::BTA_LE_AUDIO_ASE_STATE_STREAMING);

  log::info("Make sure ASE with disconnected CIS are not left in STREAMING");

  ASSERT_EQ(lastDevice->GetFirstAseWithState(::bluetooth::le_audio::types::kLeAudioDirectionSink,
                                             types::AseState::BTA_LE_AUDIO_ASE_STATE_STREAMING),
            nullptr);
  ASSERT_EQ(lastDevice->GetFirstAseWithState(::bluetooth::le_audio::types::kLeAudioDirectionSource,
                                             types::AseState::BTA_LE_AUDIO_ASE_STATE_STREAMING),
            nullptr);

  log::info(
          "Now, group is not yet in the streaming state. Let's simulated the other device got "
          "connected");

  lastDevice->conn_id_ = conn_id;
  lastDevice->SetConnectionState(DeviceConnectState::CONNECTED);

  EXPECT_CALL(gatt_queue, WriteCharacteristic(lastDevice->conn_id_, lastDevice->ctp_hdls_.val_hdl,
                                              _, GATT_WRITE_NO_RSP, _, _))
          .Times(1);

  EXPECT_CALL(*mock_iso_manager_, EstablishCis(_)).Times(0);
  EXPECT_CALL(*mock_iso_manager_, SetupIsoDataPath(_, _)).Times(0);

  log::info(" Block configured state");
  PrepareConfigureCodecHandler(group, 0, false, false);

  LeAudioGroupStateMachine::Get()->AttachToStream(group, lastDevice,
                                                  {.sink = {media_ccid}, .source = {}});

  // Check if group keeps streaming
  ASSERT_EQ(group->GetState(), types::AseState::BTA_LE_AUDIO_ASE_STATE_STREAMING);

  testing::Mock::VerifyAndClearExpectations(mock_iso_manager_);
  testing::Mock::VerifyAndClearExpectations(&gatt_queue);

  log::info("Inject ACL disconnect and reconnect again");
  InjectAclDisconnected(group, lastDevice);
  lastDevice->conn_id_ = conn_id;
  lastDevice->SetConnectionState(DeviceConnectState::CONNECTED);

  log::info("allow codec configured state");
  PrepareConfigureCodecHandler(group);

  EXPECT_CALL(gatt_queue, WriteCharacteristic(lastDevice->conn_id_, lastDevice->ctp_hdls_.val_hdl,
                                              _, GATT_WRITE_NO_RSP, _, _))
          .Times(3);

  EXPECT_CALL(*mock_iso_manager_, EstablishCis(_)).Times(1);
  EXPECT_CALL(*mock_iso_manager_, SetupIsoDataPath(_, _)).Times(1);

  LeAudioGroupStateMachine::Get()->AttachToStream(group, lastDevice,
                                                  {.sink = {media_ccid}, .source = {}});

  testing::Mock::VerifyAndClearExpectations(mock_iso_manager_);
  testing::Mock::VerifyAndClearExpectations(&gatt_queue);

  // Verify that the joining device receives the right CCID list
  auto lastMeta = lastDevice->GetFirstActiveAse()->metadata;
  bool parsedOk = false;
  auto ltv = bluetooth::le_audio::types::LeAudioLtvMap::Parse(lastMeta.data(), lastMeta.size(),
                                                              parsedOk);
  ASSERT_TRUE(parsedOk);

  auto ccids = ltv.Find(bluetooth::le_audio::types::kLeAudioMetadataTypeCcidList);
  ASSERT_TRUE(ccids.has_value());
  ASSERT_NE(std::find(ccids->begin(), ccids->end(), media_ccid), ccids->end());

  /* Verify that ASE of first device are still good*/
  auto ase = fistDevice->GetFirstActiveAse();
  ASSERT_NE(ase->qos_config.max_transport_latency, 0);
  ASSERT_NE(ase->qos_config.retrans_nb, 0);
}

TEST_F(StateMachineTest, testAttachDeviceToTheStreamDoNotAttach) {
  const auto context_type = kContextTypeMedia;
  const auto leaudio_group_id = 6;