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

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

leaudio: Fix handling remote invalid ASE state

Initial ASE state should be either Idle or Codec Configured, however
Android should handle also invalid states. This patch fixes it.

This patch also adds some more logs on invalid remote behaviour.

Bug: 318746835
Test: atest bluetooth_le_audio_test
Tag: feature
Flag: Exempt, fixing remote issue, regressions tested with unit test,
new test cases added to simulate invalid remote behavior

Change-Id: Ib0ce731fa43a1a1803cc20e63fe3d8a6aac213d6
parent b0ceadb8
Loading
Loading
Loading
Loading
+73 −32
Original line number Diff line number Diff line
@@ -407,6 +407,12 @@ class LeAudioGroupStateMachineImpl : public LeAudioGroupStateMachine {

    ParseAseStatusHeader(arh, len, value);

    if (ase->id == 0x00) {
      /* Initial state of Ase - update id */
      LOG_INFO(", discovered ase id: %d", arh.id);
      ase->id = arh.id;
    }

    auto state = static_cast<AseState>(arh.state);

    LOG_INFO(" %s , ASE id: %d, state changed %s -> %s ",
@@ -1649,13 +1655,6 @@ class LeAudioGroupStateMachineImpl : public LeAudioGroupStateMachine {
    switch (ase->state) {
      case AseState::BTA_LE_AUDIO_ASE_STATE_IDLE:
      case AseState::BTA_LE_AUDIO_ASE_STATE_CODEC_CONFIGURED:
      case AseState::BTA_LE_AUDIO_ASE_STATE_QOS_CONFIGURED:
        if (ase->id == 0x00) {
          /* Initial state of Ase - update id */
          LOG(INFO) << __func__
                    << ", discovered ase id: " << static_cast<int>(arh.id);
          ase->id = arh.id;
        }
        break;
      case AseState::BTA_LE_AUDIO_ASE_STATE_RELEASING: {
        SetAseState(leAudioDevice, ase, AseState::BTA_LE_AUDIO_ASE_STATE_IDLE);
@@ -1709,10 +1708,25 @@ class LeAudioGroupStateMachineImpl : public LeAudioGroupStateMachine {

        break;
      }
      default:
        LOG(ERROR) << __func__ << ", invalid state transition, from: "
                   << static_cast<int>(ase->state) << ", to: "
                   << static_cast<int>(AseState::BTA_LE_AUDIO_ASE_STATE_IDLE);
      case AseState::BTA_LE_AUDIO_ASE_STATE_QOS_CONFIGURED:
      case AseState::BTA_LE_AUDIO_ASE_STATE_DISABLING:
        LOG_ERROR(
            "Ignore invalid attempt of state transition from  %s to %s, %s, "
            "ase_id: %d",
            ToString(ase->state).c_str(),
            ToString(AseState::BTA_LE_AUDIO_ASE_STATE_IDLE).c_str(),
            ADDRESS_TO_LOGGABLE_CSTR(leAudioDevice->address_), ase->id);
        group->PrintDebugState();
        break;
      case AseState::BTA_LE_AUDIO_ASE_STATE_ENABLING:
      case AseState::BTA_LE_AUDIO_ASE_STATE_STREAMING:
        LOG_ERROR(
            "Invalid state transition from %s to %s, %s, ase_id: "
            "%d. Stopping the stream.",
            ToString(ase->state).c_str(),
            ToString(AseState::BTA_LE_AUDIO_ASE_STATE_IDLE).c_str(),
            ADDRESS_TO_LOGGABLE_CSTR(leAudioDevice->address_), ase->id);
        group->PrintDebugState();
        StopStream(group);
        break;
    }
@@ -1812,13 +1826,6 @@ class LeAudioGroupStateMachineImpl : public LeAudioGroupStateMachine {
    /* ase contain current ASE state. New state is in "arh" */
    switch (ase->state) {
      case AseState::BTA_LE_AUDIO_ASE_STATE_IDLE: {
        if (ase->id == 0x00) {
          /* Initial state of Ase - update id */
          LOG(INFO) << __func__
                    << ", discovered ase id: " << static_cast<int>(arh.id);
          ase->id = arh.id;
        }

        struct le_audio::client_parser::ascs::ase_codec_configured_state_params
            rsp;

@@ -2060,7 +2067,18 @@ class LeAudioGroupStateMachineImpl : public LeAudioGroupStateMachine {
        break;
      }
      case AseState::BTA_LE_AUDIO_ASE_STATE_QOS_CONFIGURED:
        /* TODO: Config Codec */
        SetAseState(leAudioDevice, ase,
                    AseState::BTA_LE_AUDIO_ASE_STATE_CODEC_CONFIGURED);
        group->PrintDebugState();
        break;
      case AseState::BTA_LE_AUDIO_ASE_STATE_DISABLING:
        LOG_ERROR(
            "Ignore invalid attempt of state transition from %s to %s, %s, "
            "ase_id: %d",
            ToString(ase->state).c_str(),
            ToString(AseState::BTA_LE_AUDIO_ASE_STATE_CODEC_CONFIGURED).c_str(),
            ADDRESS_TO_LOGGABLE_CSTR(leAudioDevice->address_), ase->id);
        group->PrintDebugState();
        break;
      case AseState::BTA_LE_AUDIO_ASE_STATE_RELEASING:
        SetAseState(leAudioDevice, ase,
@@ -2118,11 +2136,15 @@ class LeAudioGroupStateMachineImpl : public LeAudioGroupStateMachine {
        state_machine_callbacks_->StatusReportCb(
            group->group_id_, GroupStreamStatus::CONFIGURED_AUTONOMOUS);
        break;
      default:
        LOG(ERROR) << __func__ << ", invalid state transition, from: "
                   << static_cast<int>(ase->state) << ", to: "
                   << static_cast<int>(
                          AseState::BTA_LE_AUDIO_ASE_STATE_QOS_CONFIGURED);
      case AseState::BTA_LE_AUDIO_ASE_STATE_STREAMING:
      case AseState::BTA_LE_AUDIO_ASE_STATE_ENABLING:
        LOG_ERROR(
            "Invalid state transition from %s to %s, %s, ase_id: %d. Stopping "
            "the stream",
            ToString(ase->state).c_str(),
            ToString(AseState::BTA_LE_AUDIO_ASE_STATE_CODEC_CONFIGURED).c_str(),
            ADDRESS_TO_LOGGABLE_CSTR(leAudioDevice->address_), ase->id);
        group->PrintDebugState();
        StopStream(group);
        break;
    }
@@ -2165,9 +2187,6 @@ class LeAudioGroupStateMachineImpl : public LeAudioGroupStateMachine {

        break;
      }
      case AseState::BTA_LE_AUDIO_ASE_STATE_QOS_CONFIGURED:
        /* TODO: Config Codec error/Config Qos/Config QoS error/Enable error */
        break;
      case AseState::BTA_LE_AUDIO_ASE_STATE_STREAMING:
        if (ase->direction == le_audio::types::kLeAudioDirectionSource) {
          /* Source ASE cannot go from Streaming to QoS Configured state */
@@ -2223,11 +2242,33 @@ class LeAudioGroupStateMachineImpl : public LeAudioGroupStateMachine {
        }
        break;
      }
      default:
        LOG(ERROR) << __func__ << ", invalid state transition, from: "
                   << static_cast<int>(ase->state) << ", to: "
                   << static_cast<int>(
                          AseState::BTA_LE_AUDIO_ASE_STATE_QOS_CONFIGURED);

      case AseState::BTA_LE_AUDIO_ASE_STATE_QOS_CONFIGURED:
        LOG_INFO(
            "Unexpected state transition from %s to %s, %s, ase_id: %d",
            ToString(ase->state).c_str(),
            ToString(AseState::BTA_LE_AUDIO_ASE_STATE_QOS_CONFIGURED).c_str(),
            ADDRESS_TO_LOGGABLE_CSTR(leAudioDevice->address_), ase->id);
        group->PrintDebugState();
        break;
      case AseState::BTA_LE_AUDIO_ASE_STATE_IDLE:
      case AseState::BTA_LE_AUDIO_ASE_STATE_RELEASING:
        // Do nothing here, just print an error message
        LOG_ERROR(
            "Ignore invalid attempt of state transition from %s to %s, %s, "
            "ase_id: %d",
            ToString(ase->state).c_str(),
            ToString(AseState::BTA_LE_AUDIO_ASE_STATE_QOS_CONFIGURED).c_str(),
            ADDRESS_TO_LOGGABLE_CSTR(leAudioDevice->address_), ase->id);
        group->PrintDebugState();
        break;
      case AseState::BTA_LE_AUDIO_ASE_STATE_ENABLING:
        LOG_ERROR(
            "Invalid state transition from %s to %s, %s, ase_id: "
            "%d. Stopping the stream.",
            ToString(ase->state).c_str(),
            ToString(AseState::BTA_LE_AUDIO_ASE_STATE_QOS_CONFIGURED).c_str(),
            ADDRESS_TO_LOGGABLE_CSTR(leAudioDevice->address_), ase->id);
        StopStream(group);
        break;
    }
+91 −2
Original line number Diff line number Diff line
@@ -695,7 +695,9 @@ class StateMachineTestBase : public Test {
        auto* p = notif_value.data();

        // Prepare header
        UINT8_TO_STREAM(p, ase->id);
        UINT8_TO_STREAM(p, ase->id == types::ase::kAseIdInvalid
                               ? ++ase_id_last_assigned
                               : ase->id);
        UINT8_TO_STREAM(p, new_state);

        UINT8_TO_STREAM(p, conf->cig_id);
@@ -726,7 +728,10 @@ class StateMachineTestBase : public Test {
        auto* p = notif_value.data();

        // Prepare header
        UINT8_TO_STREAM(p, ase->id);
        UINT8_TO_STREAM(p, ase->id == types::ase::kAseIdInvalid
                               ? ++ase_id_last_assigned
                               : ase->id);

        UINT8_TO_STREAM(p, new_state);

        UINT8_TO_STREAM(p, group->group_id_);
@@ -828,6 +833,27 @@ class StateMachineTestBase : public Test {
    }
  }

  void InjectInitialInvalidNotification(LeAudioDeviceGroup* group) {
    for (auto* device = group->GetFirstDevice(); device != nullptr;
         device = group->GetNextDevice(device)) {
      int i = 0;
      for (auto& ase : device->ases_) {
        if (i % 2 == 1) {
          client_parser::ascs::ase_qos_configured_state_params
              qos_configured_state_params;
          InjectAseStateNotification(&ase, device, group,
                                     ascs::kAseStateQoSConfigured,
                                     &qos_configured_state_params);
        } else {
          client_parser::ascs::ase_transient_state_params enable_params;
          InjectAseStateNotification(&ase, device, group,
                                     ascs::kAseStateEnabling, &enable_params);
        }
        i++;
      }
    }
  }

  void MultipleTestDevicePrepare(int leaudio_group_id,
                                 LeAudioContextType context_type,
                                 uint16_t device_cnt,
@@ -3929,6 +3955,69 @@ TEST_F(StateMachineTest, testHandlingAutonomousCodecConfigStateOnConnection) {
  testing::Mock::VerifyAndClearExpectations(&mock_callbacks_);
}

TEST_F(StateMachineTest, testHandlingInvalidRemoteAseStateHandling) {
  /* Scenario
   * 1. After connection remote device has different ASE configurations
   * 2. Try to start stream and make sure it is configured well.
   */

  const auto context_type = kContextTypeConversational;
  const int leaudio_group_id = 4;
  const int num_of_devices = 2;

  // Prepare fake connected device group
  auto* group = PrepareSingleTestDeviceGroup(leaudio_group_id, context_type,
                                             num_of_devices);

  auto* firstDevice = group->GetFirstDevice();
  auto* secondDevice = group->GetNextDevice(firstDevice);

  /* Since we prepared device with Conversional context in mind, Sink and Source
   * ASEs should have been configured.
   */
  PrepareConfigureCodecHandler(group, 0, true);
  PrepareConfigureQosHandler(group);
  PrepareEnableHandler(group);
  PrepareDisableHandler(group);
  PrepareReceiverStartReadyHandler(group);
  PrepareReceiverStopReady(group);

  /* Number of control point calls
   * 1. Codec Config
   * 2. QoS Config
   * 3. Enable
   * 4. Receiver Start Ready
   */
  EXPECT_CALL(gatt_queue, WriteCharacteristic(firstDevice->conn_id_,
                                              firstDevice->ctp_hdls_.val_hdl, _,
                                              GATT_WRITE_NO_RSP, _, _))
      .Times(4);

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

  /* Inject invalid states*/
  InjectInitialInvalidNotification(group);

  ASSERT_FALSE(group->IsInTransition());

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

  // 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)}));

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

TEST_F(StateMachineTest, testHandlingCachedCodecConfig2Devices) {
  const auto context_type = kContextTypeConversational;
  const int leaudio_group_id = 4;