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

Commit 82f68a35 authored by Łukasz Rymanowski's avatar Łukasz Rymanowski
Browse files

leaudio: Verify CIG parameters before creating CIG

This patch does simple CIG check parameter before sending HCI command to
controller.

It allows State Machine to fallback and reconfigure the stream.

Bug: 300216034
Test: atest bluetooth_le_audio_test
Tag: #feature
Change-Id: I2546a0c7db56dc359f1a6a9f37f55813f143ef6c
parent dcfaaaf5
Loading
Loading
Loading
Loading
+11 −5
Original line number Diff line number Diff line
@@ -537,18 +537,24 @@ static uint16_t find_max_transport_latency(const LeAudioDeviceGroup* group,
         ase = leAudioDevice->GetNextActiveAseWithSameDirection(ase)) {
      if (!ase) break;

      if (!max_transport_latency)
      if (max_transport_latency == 0) {
        // first assignment
        max_transport_latency = ase->max_transport_latency;
      else if (ase->max_transport_latency < max_transport_latency)
      } else if (ase->max_transport_latency < max_transport_latency) {
        if (ase->max_transport_latency != 0) {
          max_transport_latency = ase->max_transport_latency;
        } else {
          LOG_WARN("Trying to set latency back to 0, ASE ID %d", ase->id);
        }
      }
    }
  }

  if (max_transport_latency < types::kMaxTransportLatencyMin)
  if (max_transport_latency < types::kMaxTransportLatencyMin) {
    max_transport_latency = types::kMaxTransportLatencyMin;
  else if (max_transport_latency > types::kMaxTransportLatencyMax)
  } else if (max_transport_latency > types::kMaxTransportLatencyMax) {
    max_transport_latency = types::kMaxTransportLatencyMax;
  }

  return max_transport_latency;
}
+17 −0
Original line number Diff line number Diff line
@@ -1248,6 +1248,14 @@ class LeAudioGroupStateMachineImpl : public LeAudioGroupStateMachine {
                                                        ase->direction);
  }

  static bool isIntervalAndLatencyProperlySet(uint32_t sdu_interval_us,
                                              uint16_t max_latency_ms) {
    if (sdu_interval_us == 0) {
      return max_latency_ms == le_audio::types::kMaxTransportLatencyMin;
    }
    return ((1000 * max_latency_ms) > sdu_interval_us);
  }

  bool CigCreate(LeAudioDeviceGroup* group) {
    uint32_t sdu_interval_mtos, sdu_interval_stom;
    uint16_t max_trans_lat_mtos, max_trans_lat_stom;
@@ -1280,6 +1288,15 @@ class LeAudioGroupStateMachineImpl : public LeAudioGroupStateMachine {
    uint8_t phy_stom =
        group->GetPhyBitmask(le_audio::types::kLeAudioDirectionSource);

    if (!isIntervalAndLatencyProperlySet(sdu_interval_mtos,
                                         max_trans_lat_mtos) ||
        !isIntervalAndLatencyProperlySet(sdu_interval_stom,
                                         max_trans_lat_stom)) {
      LOG_ERROR("Latency and interval not properly set");
      group->PrintDebugState();
      return false;
    }

    // Use 1M Phy for the ACK packet from remote device to phone for better
    // sensitivity
    if (max_sdu_size_stom == 0 &&
+115 −0
Original line number Diff line number Diff line
@@ -186,6 +186,9 @@ class StateMachineTestBase : public Test {
  /* Keep ASE in releasing state */
  bool stay_in_releasing_state_;

  /* Use for single test to simulate late ASE notifications */
  bool stop_inject_configured_ase_after_first_ase_configured_;

  virtual void SetUp() override {
    bluetooth::common::InitFlags::Load(test_flags);
    reset_mock_function_count_map();
@@ -198,6 +201,7 @@ class StateMachineTestBase : public Test {
    overwrite_cis_status_ = false;
    do_not_send_cis_establish_event_ = false;
    stay_in_releasing_state_ = false;
    stop_inject_configured_ase_after_first_ase_configured_ = false;
    cis_status_.clear();

    LeAudioGroupStateMachine::Initialize(&mock_callbacks_);
@@ -935,6 +939,10 @@ class StateMachineTestBase : public Test {
            InjectAseStateNotification(ase, device, group,
                                       ascs::kAseStateCodecConfigured,
                                       &codec_configured_state_params);

            if (stop_inject_configured_ase_after_first_ase_configured_) {
              return;
            }
          }
        };
  }
@@ -4554,6 +4562,113 @@ TEST_F(StateMachineTest, StartStreamCachedConfig) {
  ASSERT_EQ(1, get_func_call_count("alarm_cancel"));
}

TEST_F(StateMachineTest, StartStreamCachedConfigReconfigInvalidBehavior) {
  const auto context_type = kContextTypeConversational;
  const auto leaudio_group_id = 6;
  const auto num_devices = 1;
  channel_count_ = kLeAudioCodecChannelCountSingleChannel |
                   kLeAudioCodecChannelCountTwoChannel;

  /* Scenario
   * 1. Start stream and stop stream so ASEs stays in Configured State
   * 2. Reconfigure ASEs localy, so the QoS parameters are zeroed
   * 3. Inject one ASE 2 to be in Releasing state
   * 4. Start stream and Incject ASE 1 to go into Codec Configured state
   * 5. IN such case CIG shall not be created and fallback to Release and
   * Configure stream should happen. Before fix CigCreate with invalid
   * parameters were called */
  ContentControlIdKeeper::GetInstance()->SetCcid(call_context, call_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, 0, true);
  PrepareConfigureQosHandler(group);
  PrepareEnableHandler(group);
  PrepareDisableHandler(group);
  PrepareReceiverStartReady(group);
  PrepareReleaseHandler(group);

  InjectInitialIdleNotification(group);

  // Validate GroupStreamStatus
  EXPECT_CALL(
      mock_callbacks_,
      StatusReportCb(leaudio_group_id,
                     bluetooth::le_audio::GroupStreamStatus::STREAMING));

  EXPECT_CALL(*mock_iso_manager_, CreateCig).Times(1);

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

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

  ASSERT_EQ(1, get_func_call_count("alarm_cancel"));
  reset_mock_function_count_map();

  // Validate GroupStreamStatus
  EXPECT_CALL(
      mock_callbacks_,
      StatusReportCb(leaudio_group_id,
                     bluetooth::le_audio::GroupStreamStatus::RELEASING));

  EXPECT_CALL(
      mock_callbacks_,
      StatusReportCb(
          leaudio_group_id,
          bluetooth::le_audio::GroupStreamStatus::CONFIGURED_AUTONOMOUS));
  // Start the configuration and stream Media content
  LeAudioGroupStateMachine::Get()->StopStream(group);

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

  ASSERT_EQ(1, get_func_call_count("alarm_cancel"));
  reset_mock_function_count_map();

  stop_inject_configured_ase_after_first_ase_configured_ = true;

  auto device = group->GetFirstDevice();
  int i = 0;
  for (auto& ase : device->ases_) {
    if (i++ == 0) continue;

    // Simulate autonomus release for one ASE - this is invalid behaviour
    InjectAseStateNotification(&ase, device, group, ascs::kAseStateReleasing,
                               nullptr);
  }

  // Restart stream and expect it will not be created.
  EXPECT_CALL(mock_callbacks_,
              StatusReportCb(leaudio_group_id,
                             bluetooth::le_audio::GroupStreamStatus::STREAMING))
      .Times(0);
  EXPECT_CALL(mock_callbacks_,
              StatusReportCb(leaudio_group_id,
                             bluetooth::le_audio::GroupStreamStatus::RELEASING))
      .Times(1);

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

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

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

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

TEST_F(StateMachineTest, BoundedHeadphonesConversationalToMediaChannelCount_2) {
  const auto initial_context_type = kContextTypeConversational;
  const auto new_context_type = kContextTypeMedia;