blob: cfe3bf3ac6cd97d22bf88ac1d547d9af8a96844b [file] [log] [blame]
// Copyright 2023 The Pigweed Authors
//
// Licensed under the Apache License, Version 2.0 (the "License"); you may not
// use this file except in compliance with the License. You may obtain a copy of
// the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
// License for the specific language governing permissions and limitations under
// the License.
#include "pw_bluetooth_sapphire/internal/host/l2cap/a2dp_offload_manager.h"
#include <memory>
#include "pw_bluetooth_sapphire/internal/host/common/host_error.h"
#include "pw_bluetooth_sapphire/internal/host/testing/controller_test.h"
#include "pw_bluetooth_sapphire/internal/host/testing/mock_controller.h"
#include "pw_bluetooth_sapphire/internal/host/testing/test_packets.h"
namespace bt::l2cap {
namespace {
namespace hci_android = bt::hci_spec::vendor::android;
using namespace bt::testing;
constexpr hci_spec::ConnectionHandle kTestHandle1 = 0x0001;
constexpr ChannelId kLocalId = 0x0040;
constexpr ChannelId kRemoteId = 0x9042;
A2dpOffloadManager::Configuration BuildConfiguration(
hci_android::A2dpCodecType codec = hci_android::A2dpCodecType::kSbc) {
hci_android::A2dpScmsTEnable scms_t_enable;
scms_t_enable.enabled = pw::bluetooth::emboss::GenericEnableParam::DISABLE;
scms_t_enable.header = 0x00;
hci_android::A2dpOffloadCodecInformation codec_information;
switch (codec) {
case hci_android::A2dpCodecType::kSbc:
codec_information.sbc.blocklen_subbands_alloc_method = 0x00;
codec_information.sbc.min_bitpool_value = 0x00;
codec_information.sbc.max_bitpool_value = 0xFF;
memset(codec_information.sbc.reserved,
0,
sizeof(codec_information.sbc.reserved));
break;
case hci_android::A2dpCodecType::kAac:
codec_information.aac.object_type = 0x00;
codec_information.aac.variable_bit_rate =
hci_android::A2dpAacEnableVariableBitRate::kDisable;
memset(codec_information.aac.reserved,
0,
sizeof(codec_information.aac.reserved));
break;
case hci_android::A2dpCodecType::kLdac:
codec_information.ldac.vendor_id = 0x0000012D;
codec_information.ldac.codec_id = 0x00AA;
codec_information.ldac.bitrate_index =
hci_android::A2dpBitrateIndex::kLow;
codec_information.ldac.ldac_channel_mode =
hci_android::A2dpLdacChannelMode::kStereo;
memset(codec_information.ldac.reserved,
0,
sizeof(codec_information.ldac.reserved));
break;
default:
memset(codec_information.aptx.reserved,
0,
sizeof(codec_information.aptx.reserved));
break;
}
A2dpOffloadManager::Configuration config;
config.codec = codec;
config.max_latency = 0xFFFF;
config.scms_t_enable = scms_t_enable;
config.sampling_frequency = hci_android::A2dpSamplingFrequency::k44100Hz;
config.bits_per_sample = hci_android::A2dpBitsPerSample::k16BitsPerSample;
config.channel_mode = hci_android::A2dpChannelMode::kMono;
config.encoded_audio_bit_rate = 0x0;
config.codec_information = codec_information;
return config;
}
using TestingBase = FakeDispatcherControllerTest<MockController>;
class A2dpOffloadTest : public TestingBase {
public:
A2dpOffloadTest() = default;
~A2dpOffloadTest() override = default;
void SetUp() override {
TestingBase::SetUp();
offload_mgr_ =
std::make_unique<A2dpOffloadManager>(cmd_channel()->AsWeakPtr());
}
void TearDown() override { TestingBase::TearDown(); }
A2dpOffloadManager* offload_mgr() const { return offload_mgr_.get(); }
private:
std::unique_ptr<A2dpOffloadManager> offload_mgr_;
BT_DISALLOW_COPY_AND_ASSIGN_ALLOW_MOVE(A2dpOffloadTest);
};
class StartA2dpOffloadTest
: public A2dpOffloadTest,
public ::testing::WithParamInterface<hci_android::A2dpCodecType> {};
TEST_P(StartA2dpOffloadTest, StartA2dpOffloadSuccess) {
const hci_android::A2dpCodecType codec = GetParam();
A2dpOffloadManager::Configuration config = BuildConfiguration(codec);
const auto command_complete =
CommandCompletePacket(hci_android::kA2dpOffloadCommand,
pw::bluetooth::emboss::StatusCode::SUCCESS);
EXPECT_CMD_PACKET_OUT(
test_device(),
StartA2dpOffloadRequest(config, kTestHandle1, kRemoteId, kMaxMTU),
&command_complete);
std::optional<hci::Result<>> start_result;
offload_mgr()->StartA2dpOffload(
config,
kLocalId,
kRemoteId,
kTestHandle1,
kMaxMTU,
[&start_result](auto res) {
EXPECT_EQ(ToResult(pw::bluetooth::emboss::StatusCode::SUCCESS), res);
start_result = res;
});
RunUntilIdle();
EXPECT_TRUE(offload_mgr()->IsChannelOffloaded(kLocalId, kTestHandle1));
EXPECT_TRUE(test_device()->AllExpectedCommandPacketsSent());
ASSERT_TRUE(start_result.has_value());
EXPECT_TRUE(start_result->is_ok());
}
const std::vector<hci_android::A2dpCodecType> kA2dpCodecTypeParams = {
hci_android::A2dpCodecType::kSbc,
hci_android::A2dpCodecType::kAac,
hci_android::A2dpCodecType::kLdac,
hci_android::A2dpCodecType::kAptx};
INSTANTIATE_TEST_SUITE_P(ChannelManagerTest,
StartA2dpOffloadTest,
::testing::ValuesIn(kA2dpCodecTypeParams));
TEST_F(A2dpOffloadTest, StartA2dpOffloadInvalidConfiguration) {
A2dpOffloadManager::Configuration config = BuildConfiguration();
const auto command_complete = CommandCompletePacket(
hci_android::kA2dpOffloadCommand,
pw::bluetooth::emboss::StatusCode::INVALID_HCI_COMMAND_PARAMETERS);
EXPECT_CMD_PACKET_OUT(
test_device(),
StartA2dpOffloadRequest(config, kTestHandle1, kRemoteId, kMaxMTU),
&command_complete);
std::optional<hci::Result<>> start_result;
offload_mgr()->StartA2dpOffload(
config,
kLocalId,
kRemoteId,
kTestHandle1,
kMaxMTU,
[&start_result](auto res) {
EXPECT_EQ(ToResult(pw::bluetooth::emboss::StatusCode::
INVALID_HCI_COMMAND_PARAMETERS),
res);
start_result = res;
});
RunUntilIdle();
EXPECT_TRUE(test_device()->AllExpectedCommandPacketsSent());
ASSERT_TRUE(start_result.has_value());
EXPECT_TRUE(start_result->is_error());
}
TEST_F(A2dpOffloadTest, StartAndStopA2dpOffloadSuccess) {
A2dpOffloadManager::Configuration config = BuildConfiguration();
const auto command_complete =
CommandCompletePacket(hci_android::kA2dpOffloadCommand,
pw::bluetooth::emboss::StatusCode::SUCCESS);
EXPECT_CMD_PACKET_OUT(
test_device(),
StartA2dpOffloadRequest(config, kTestHandle1, kRemoteId, kMaxMTU),
&command_complete);
std::optional<hci::Result<>> start_result;
offload_mgr()->StartA2dpOffload(
config,
kLocalId,
kRemoteId,
kTestHandle1,
kMaxMTU,
[&start_result](auto res) {
EXPECT_EQ(ToResult(pw::bluetooth::emboss::StatusCode::SUCCESS), res);
start_result = res;
});
RunUntilIdle();
EXPECT_TRUE(offload_mgr()->IsChannelOffloaded(kLocalId, kTestHandle1));
EXPECT_TRUE(test_device()->AllExpectedCommandPacketsSent());
ASSERT_TRUE(start_result.has_value());
EXPECT_TRUE(start_result->is_ok());
EXPECT_CMD_PACKET_OUT(
test_device(), StopA2dpOffloadRequest(), &command_complete);
std::optional<hci::Result<>> stop_result;
offload_mgr()->RequestStopA2dpOffload(
kLocalId, kTestHandle1, [&stop_result](auto res) {
EXPECT_EQ(ToResult(pw::bluetooth::emboss::StatusCode::SUCCESS), res);
stop_result = res;
});
RunUntilIdle();
EXPECT_FALSE(offload_mgr()->IsChannelOffloaded(kLocalId, kTestHandle1));
EXPECT_TRUE(test_device()->AllExpectedCommandPacketsSent());
ASSERT_TRUE(stop_result.has_value());
EXPECT_TRUE(stop_result->is_ok());
}
TEST_F(A2dpOffloadTest, StartA2dpOffloadAlreadyStarted) {
A2dpOffloadManager::Configuration config = BuildConfiguration();
const auto command_complete =
CommandCompletePacket(hci_android::kA2dpOffloadCommand,
pw::bluetooth::emboss::StatusCode::SUCCESS);
EXPECT_CMD_PACKET_OUT(
test_device(),
StartA2dpOffloadRequest(config, kTestHandle1, kRemoteId, kMaxMTU),
&command_complete);
std::optional<hci::Result<>> start_result;
offload_mgr()->StartA2dpOffload(
config,
kLocalId,
kRemoteId,
kTestHandle1,
kMaxMTU,
[&start_result](auto res) {
EXPECT_EQ(ToResult(pw::bluetooth::emboss::StatusCode::SUCCESS), res);
start_result = res;
});
RunUntilIdle();
EXPECT_TRUE(offload_mgr()->IsChannelOffloaded(kLocalId, kTestHandle1));
EXPECT_TRUE(test_device()->AllExpectedCommandPacketsSent());
ASSERT_TRUE(start_result.has_value());
EXPECT_TRUE(start_result->is_ok());
start_result.reset();
offload_mgr()->StartA2dpOffload(config,
kLocalId,
kRemoteId,
kTestHandle1,
kMaxMTU,
[&start_result](auto res) {
EXPECT_EQ(ToResult(HostError::kInProgress),
res);
start_result = res;
});
RunUntilIdle();
EXPECT_TRUE(offload_mgr()->IsChannelOffloaded(kLocalId, kTestHandle1));
ASSERT_TRUE(start_result.has_value());
EXPECT_TRUE(start_result->is_error());
}
TEST_F(A2dpOffloadTest, StartA2dpOffloadStillStarting) {
A2dpOffloadManager::Configuration config = BuildConfiguration();
const auto command_complete =
CommandCompletePacket(hci_android::kA2dpOffloadCommand,
pw::bluetooth::emboss::StatusCode::SUCCESS);
EXPECT_CMD_PACKET_OUT(
test_device(),
StartA2dpOffloadRequest(config, kTestHandle1, kRemoteId, kMaxMTU),
&command_complete);
std::optional<hci::Result<>> start_result;
offload_mgr()->StartA2dpOffload(
config,
kLocalId,
kRemoteId,
kTestHandle1,
kMaxMTU,
[&start_result](auto res) {
EXPECT_EQ(ToResult(pw::bluetooth::emboss::StatusCode::SUCCESS), res);
start_result = res;
});
EXPECT_FALSE(start_result.has_value());
offload_mgr()->StartA2dpOffload(config,
kLocalId,
kRemoteId,
kTestHandle1,
kMaxMTU,
[&start_result](auto res) {
EXPECT_EQ(ToResult(HostError::kInProgress),
res);
start_result = res;
});
RunUntilIdle();
EXPECT_TRUE(offload_mgr()->IsChannelOffloaded(kLocalId, kTestHandle1));
EXPECT_TRUE(test_device()->AllExpectedCommandPacketsSent());
ASSERT_TRUE(start_result.has_value());
EXPECT_TRUE(start_result->is_ok());
}
TEST_F(A2dpOffloadTest, StartA2dpOffloadStillStopping) {
A2dpOffloadManager::Configuration config = BuildConfiguration();
const auto command_complete =
CommandCompletePacket(hci_android::kA2dpOffloadCommand,
pw::bluetooth::emboss::StatusCode::SUCCESS);
EXPECT_CMD_PACKET_OUT(
test_device(),
StartA2dpOffloadRequest(config, kTestHandle1, kRemoteId, kMaxMTU),
&command_complete);
std::optional<hci::Result<>> start_result;
offload_mgr()->StartA2dpOffload(
config,
kLocalId,
kRemoteId,
kTestHandle1,
kMaxMTU,
[&start_result](auto res) {
EXPECT_EQ(ToResult(pw::bluetooth::emboss::StatusCode::SUCCESS), res);
start_result = res;
});
RunUntilIdle();
EXPECT_TRUE(offload_mgr()->IsChannelOffloaded(kLocalId, kTestHandle1));
EXPECT_TRUE(test_device()->AllExpectedCommandPacketsSent());
ASSERT_TRUE(start_result.has_value());
EXPECT_TRUE(start_result->is_ok());
EXPECT_CMD_PACKET_OUT(
test_device(), StopA2dpOffloadRequest(), &command_complete);
std::optional<hci::Result<>> stop_result;
offload_mgr()->RequestStopA2dpOffload(
kLocalId, kTestHandle1, [&stop_result](auto res) {
EXPECT_EQ(ToResult(pw::bluetooth::emboss::StatusCode::SUCCESS), res);
stop_result = res;
});
EXPECT_FALSE(stop_result.has_value());
start_result.reset();
offload_mgr()->StartA2dpOffload(config,
kLocalId,
kRemoteId,
kTestHandle1,
kMaxMTU,
[&start_result](auto res) {
EXPECT_EQ(ToResult(HostError::kInProgress),
res);
start_result = res;
});
RunUntilIdle();
EXPECT_FALSE(offload_mgr()->IsChannelOffloaded(kLocalId, kTestHandle1));
EXPECT_TRUE(test_device()->AllExpectedCommandPacketsSent());
ASSERT_TRUE(start_result.has_value());
EXPECT_TRUE(start_result->is_error());
ASSERT_TRUE(stop_result.has_value());
EXPECT_TRUE(stop_result->is_ok());
}
TEST_F(A2dpOffloadTest, StopA2dpOffloadStillStarting) {
A2dpOffloadManager::Configuration config = BuildConfiguration();
const auto command_complete =
CommandCompletePacket(hci_android::kA2dpOffloadCommand,
pw::bluetooth::emboss::StatusCode::SUCCESS);
EXPECT_CMD_PACKET_OUT(
test_device(),
StartA2dpOffloadRequest(config, kTestHandle1, kRemoteId, kMaxMTU),
&command_complete);
std::optional<hci::Result<>> start_result;
offload_mgr()->StartA2dpOffload(
config,
kLocalId,
kRemoteId,
kTestHandle1,
kMaxMTU,
[&start_result](auto res) {
EXPECT_EQ(ToResult(pw::bluetooth::emboss::StatusCode::SUCCESS), res);
start_result = res;
});
EXPECT_FALSE(start_result.has_value());
EXPECT_CMD_PACKET_OUT(
test_device(), StopA2dpOffloadRequest(), &command_complete);
std::optional<hci::Result<>> stop_result;
offload_mgr()->RequestStopA2dpOffload(
kLocalId, kTestHandle1, [&stop_result](auto res) {
EXPECT_EQ(ToResult(pw::bluetooth::emboss::StatusCode::SUCCESS), res);
stop_result = res;
});
RunUntilIdle();
EXPECT_FALSE(offload_mgr()->IsChannelOffloaded(kLocalId, kTestHandle1));
EXPECT_TRUE(test_device()->AllExpectedCommandPacketsSent());
ASSERT_TRUE(start_result.has_value());
EXPECT_TRUE(start_result->is_ok());
ASSERT_TRUE(stop_result.has_value());
EXPECT_TRUE(stop_result->is_ok());
}
TEST_F(A2dpOffloadTest, StopA2dpOffloadStillStopping) {
A2dpOffloadManager::Configuration config = BuildConfiguration();
const auto command_complete =
CommandCompletePacket(hci_android::kA2dpOffloadCommand,
pw::bluetooth::emboss::StatusCode::SUCCESS);
EXPECT_CMD_PACKET_OUT(
test_device(),
StartA2dpOffloadRequest(config, kTestHandle1, kRemoteId, kMaxMTU),
&command_complete);
std::optional<hci::Result<>> start_result;
offload_mgr()->StartA2dpOffload(
config,
kLocalId,
kRemoteId,
kTestHandle1,
kMaxMTU,
[&start_result](auto res) {
EXPECT_EQ(ToResult(pw::bluetooth::emboss::StatusCode::SUCCESS), res);
start_result = res;
});
RunUntilIdle();
EXPECT_TRUE(offload_mgr()->IsChannelOffloaded(kLocalId, kTestHandle1));
EXPECT_TRUE(test_device()->AllExpectedCommandPacketsSent());
ASSERT_TRUE(start_result.has_value());
EXPECT_TRUE(start_result->is_ok());
EXPECT_CMD_PACKET_OUT(
test_device(), StopA2dpOffloadRequest(), &command_complete);
std::optional<hci::Result<>> stop_result;
offload_mgr()->RequestStopA2dpOffload(
kLocalId, kTestHandle1, [&stop_result](auto res) {
EXPECT_EQ(ToResult(pw::bluetooth::emboss::StatusCode::SUCCESS), res);
stop_result = res;
});
EXPECT_FALSE(stop_result.has_value());
offload_mgr()->RequestStopA2dpOffload(
kLocalId, kTestHandle1, [&stop_result](auto res) {
EXPECT_EQ(ToResult(HostError::kInProgress), res);
stop_result = res;
});
RunUntilIdle();
EXPECT_FALSE(offload_mgr()->IsChannelOffloaded(kLocalId, kTestHandle1));
EXPECT_TRUE(test_device()->AllExpectedCommandPacketsSent());
ASSERT_TRUE(stop_result.has_value());
EXPECT_TRUE(stop_result->is_ok());
}
TEST_F(A2dpOffloadTest, StopA2dpOffloadAlreadyStopped) {
std::optional<hci::Result<>> stop_result;
offload_mgr()->RequestStopA2dpOffload(
kLocalId, kTestHandle1, [&stop_result](auto res) {
EXPECT_EQ(ToResult(pw::bluetooth::emboss::StatusCode::SUCCESS), res);
stop_result = res;
});
RunUntilIdle();
EXPECT_FALSE(offload_mgr()->IsChannelOffloaded(kLocalId, kTestHandle1));
ASSERT_TRUE(stop_result.has_value());
EXPECT_TRUE(stop_result->is_ok());
}
TEST_F(A2dpOffloadTest, A2dpOffloadOnlyOneChannel) {
A2dpOffloadManager::Configuration config = BuildConfiguration();
const auto command_complete =
CommandCompletePacket(hci_android::kA2dpOffloadCommand,
pw::bluetooth::emboss::StatusCode::SUCCESS);
EXPECT_CMD_PACKET_OUT(
test_device(),
StartA2dpOffloadRequest(config, kTestHandle1, kRemoteId, kMaxMTU),
&command_complete);
std::optional<hci::Result<>> start_result_0;
offload_mgr()->StartA2dpOffload(
config,
kLocalId,
kRemoteId,
kTestHandle1,
kMaxMTU,
[&start_result_0](auto res) {
EXPECT_EQ(ToResult(pw::bluetooth::emboss::StatusCode::SUCCESS), res);
start_result_0 = res;
});
RunUntilIdle();
EXPECT_TRUE(offload_mgr()->IsChannelOffloaded(kLocalId, kTestHandle1));
EXPECT_TRUE(test_device()->AllExpectedCommandPacketsSent());
ASSERT_TRUE(start_result_0.has_value());
EXPECT_TRUE(start_result_0->is_ok());
std::optional<hci::Result<>> start_result_1;
offload_mgr()->StartA2dpOffload(config,
kLocalId + 1,
kRemoteId + 1,
kTestHandle1,
kMaxMTU,
[&start_result_1](auto res) {
EXPECT_EQ(ToResult(HostError::kInProgress),
res);
start_result_1 = res;
});
RunUntilIdle();
EXPECT_TRUE(offload_mgr()->IsChannelOffloaded(kLocalId, kTestHandle1));
EXPECT_FALSE(offload_mgr()->IsChannelOffloaded(kLocalId + 1, kTestHandle1));
ASSERT_TRUE(start_result_1.has_value());
EXPECT_TRUE(start_result_1->is_error());
}
TEST_F(A2dpOffloadTest, DifferentChannelCannotStopA2dpOffloading) {
A2dpOffloadManager::Configuration config = BuildConfiguration();
const auto command_complete =
CommandCompletePacket(hci_android::kA2dpOffloadCommand,
pw::bluetooth::emboss::StatusCode::SUCCESS);
EXPECT_CMD_PACKET_OUT(
test_device(),
StartA2dpOffloadRequest(config, kTestHandle1, kRemoteId, kMaxMTU),
&command_complete);
std::optional<hci::Result<>> start_result;
offload_mgr()->StartA2dpOffload(
config,
kLocalId,
kRemoteId,
kTestHandle1,
kMaxMTU,
[&start_result](auto res) {
EXPECT_EQ(ToResult(pw::bluetooth::emboss::StatusCode::SUCCESS), res);
start_result = res;
});
RunUntilIdle();
EXPECT_TRUE(offload_mgr()->IsChannelOffloaded(kLocalId, kTestHandle1));
EXPECT_TRUE(test_device()->AllExpectedCommandPacketsSent());
ASSERT_TRUE(start_result.has_value());
EXPECT_TRUE(start_result->is_ok());
std::optional<hci::Result<>> stop_result;
offload_mgr()->RequestStopA2dpOffload(
kLocalId + 1, kTestHandle1 + 1, [&stop_result](auto res) {
EXPECT_EQ(ToResult(pw::bluetooth::emboss::StatusCode::SUCCESS), res);
stop_result = res;
});
RunUntilIdle();
EXPECT_TRUE(offload_mgr()->IsChannelOffloaded(kLocalId, kTestHandle1));
ASSERT_TRUE(stop_result.has_value());
EXPECT_TRUE(stop_result->is_ok());
EXPECT_CMD_PACKET_OUT(
test_device(), StopA2dpOffloadRequest(), &command_complete);
// Can still stop it from the correct one.
stop_result = std::nullopt;
offload_mgr()->RequestStopA2dpOffload(
kLocalId, kTestHandle1, [&stop_result](auto res) {
EXPECT_EQ(ToResult(pw::bluetooth::emboss::StatusCode::SUCCESS), res);
stop_result = res;
});
RunUntilIdle();
EXPECT_FALSE(offload_mgr()->IsChannelOffloaded(kLocalId, kTestHandle1));
ASSERT_TRUE(stop_result.has_value());
EXPECT_TRUE(stop_result->is_ok());
}
} // namespace
} // namespace bt::l2cap