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

Commit 230c03d7 authored by Łukasz Rymanowski's avatar Łukasz Rymanowski
Browse files

leaudio: Fix CIG not remove usecase

CIG could be not removed when
1. QoS Config failed or
2. in race condition during stream release when one device moves it ASES
   to IDLE/CONFIGURE before second gets its ASEs into  RELEASING state.

Bug: 321221128
Test: atest bluetooth_le_audio_test
Flag: Exempt, fixing remote invalid behaviour, regression covered with
unit tests. New unit test added

Change-Id: I293a66ddd42a6476468295735e72c206e42e8960
parent 44f449e0
Loading
Loading
Loading
Loading
+4 −6
Original line number Diff line number Diff line
@@ -2905,12 +2905,8 @@ class LeAudioGroupStateMachineImpl : public LeAudioGroupStateMachine {
    }

    switch (ase->state) {
      case AseState::BTA_LE_AUDIO_ASE_STATE_DISABLING:
      case AseState::BTA_LE_AUDIO_ASE_STATE_CODEC_CONFIGURED:
      case AseState::BTA_LE_AUDIO_ASE_STATE_DISABLING: {
        SetAseState(leAudioDevice, ase,
                    AseState::BTA_LE_AUDIO_ASE_STATE_RELEASING);
        break;
      }
      case AseState::BTA_LE_AUDIO_ASE_STATE_QOS_CONFIGURED:
        SetAseState(leAudioDevice, ase,
                    AseState::BTA_LE_AUDIO_ASE_STATE_RELEASING);
@@ -2918,8 +2914,10 @@ class LeAudioGroupStateMachineImpl : public LeAudioGroupStateMachine {
        if (group->HaveAllActiveDevicesAsesTheSameState(
                AseState::BTA_LE_AUDIO_ASE_STATE_RELEASING)) {
          group->SetState(AseState::BTA_LE_AUDIO_ASE_STATE_RELEASING);
        }

          /* At this point all of the active ASEs within group are released. */
        if (group->cig.GetState() == CigState::CREATED &&
            group->HaveAllCisesDisconnected()) {
          RemoveCigForGroup(group);
        }

+68 −3
Original line number Diff line number Diff line
@@ -239,6 +239,13 @@ class StateMachineTestBase : public Test {

    ContentControlIdKeeper::GetInstance()->Start();

    ON_CALL(mock_callbacks_, StatusReportCb(_, _))
        .WillByDefault(Invoke(
            [](int group_id, bluetooth::le_audio::GroupStreamStatus status) {
              LOG_DEBUG(" [Testing] StatusReportCb: group id: %d, status: %d",
                        group_id, status);
            }));

    MockCsisClient::SetMockInstanceForTesting(&mock_csis_client_module_);
    ON_CALL(mock_csis_client_module_, Get())
        .WillByDefault(Return(&mock_csis_client_module_));
@@ -1741,6 +1748,60 @@ TEST_F(StateMachineTest, testConfigureQosMultiple) {
  ASSERT_EQ(0, get_func_call_count("alarm_cancel"));
}

TEST_F(StateMachineTest, testConfigureQosFailed) {
  const auto context_type = kContextTypeMedia;
  const auto leaudio_group_id = 3;
  const auto num_devices = 2;

  // Check if CIG is properly cleared when QoS failed

  // 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);
  PrepareCtpNotificationError(
      group, client_parser::ascs::kCtpOpcodeQosConfiguration,
      client_parser::ascs::kCtpResponseCodeInvalidConfigurationParameterValue,
      client_parser::ascs::kCtpResponsePhy);
  PrepareReleaseHandler(group);

  auto* leAudioDevice = group->GetFirstDevice();
  auto expected_devices_written = 0;
  while (leAudioDevice) {
    EXPECT_CALL(gatt_queue,
                WriteCharacteristic(leAudioDevice->conn_id_,
                                    leAudioDevice->ctp_hdls_.val_hdl, _,
                                    GATT_WRITE_NO_RSP, _, _))
        .Times(AtLeast(2));
    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(0);
  EXPECT_CALL(*mock_iso_manager_, SetupIsoDataPath(_, _)).Times(0);
  EXPECT_CALL(*mock_iso_manager_, RemoveIsoDataPath(_, _)).Times(0);
  EXPECT_CALL(*mock_iso_manager_, DisconnectCis(_, _)).Times(0);
  EXPECT_CALL(*mock_iso_manager_, RemoveCig(_, _)).Times(1);

  InjectInitialIdleNotification(group);

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

  // Check if group has transitioned to a proper state
  ASSERT_EQ(group->GetState(), types::AseState::BTA_LE_AUDIO_ASE_STATE_IDLE);
  ASSERT_EQ(2, get_func_call_count("alarm_cancel"));

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

TEST_F(StateMachineTest, testStreamCreationError) {
  /* Device is banded headphones with 1x snk + 0x src ase
   * (1xunidirectional CIS) with channel count 2 (for stereo
@@ -5602,19 +5663,23 @@ TEST_F(StateMachineTest, StartStreamCachedConfigReconfigInvalidBehavior) {
  EXPECT_CALL(mock_callbacks_,
              StatusReportCb(leaudio_group_id,
                             bluetooth::le_audio::GroupStreamStatus::RELEASING))
      .Times(1);
      .Times(0);

  EXPECT_CALL(*mock_iso_manager_, CreateCig(_, _)).Times(0);

  // Block the fallback Release which will happen when CreateCig will faile
  // Block the fallback Release which will happen when CreateCig will fail
  stay_in_releasing_state_ = true;

  // Start the configuration and stream Live content
  LeAudioGroupStateMachine::Get()->StartStream(
  bool result = LeAudioGroupStateMachine::Get()->StartStream(
      group, kContextTypeLive,
      {.sink = types::AudioContexts(kContextTypeLive),
       .source = types::AudioContexts(kContextTypeLive)});

  // Group internally in releasing state. StartStrean should faile.

  ASSERT_FALSE(result);

  testing::Mock::VerifyAndClearExpectations(&mock_callbacks_);
  testing::Mock::VerifyAndClearExpectations(&mock_iso_manager_);
}