blob: 6368832e4337f19a626c6b7f332f4e2bfd77517d [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/sco/sco_connection_manager.h"
#include <optional>
#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::sco {
namespace {
using OpenConnectionResult = ScoConnectionManager::OpenConnectionResult;
using AcceptConnectionResult = ScoConnectionManager::AcceptConnectionResult;
constexpr hci_spec::ConnectionHandle kAclConnectionHandle = 0x40;
constexpr hci_spec::ConnectionHandle kScoConnectionHandle = 0x41;
const DeviceAddress kLocalAddress(DeviceAddress::Type::kBREDR,
{0x00, 0x00, 0x00, 0x00, 0x00, 0x01});
const DeviceAddress kPeerAddress(DeviceAddress::Type::kBREDR,
{0x00, 0x00, 0x00, 0x00, 0x00, 0x02});
bt::StaticPacket<pw::bluetooth::emboss::SynchronousConnectionParametersWriter>
kConnectionParams;
bt::StaticPacket<pw::bluetooth::emboss::SynchronousConnectionParametersWriter>
InitializeConnectionParams() {
bt::StaticPacket<pw::bluetooth::emboss::SynchronousConnectionParametersWriter>
out;
auto view = out.view();
view.transmit_bandwidth().Write(1);
view.receive_bandwidth().Write(2);
view.transmit_coding_format().coding_format().Write(
pw::bluetooth::emboss::CodingFormat::MSBC);
view.transmit_coding_format().company_id().Write(3);
view.transmit_coding_format().vendor_codec_id().Write(4);
view.receive_coding_format().coding_format().Write(
pw::bluetooth::emboss::CodingFormat::CVSD);
view.receive_coding_format().company_id().Write(5);
view.receive_coding_format().vendor_codec_id().Write(6);
view.transmit_codec_frame_size_bytes().Write(7);
view.receive_codec_frame_size_bytes().Write(8);
view.input_bandwidth().Write(9);
view.output_bandwidth().Write(10);
view.input_coding_format().coding_format().Write(
pw::bluetooth::emboss::CodingFormat::A_LAW);
view.input_coding_format().company_id().Write(11);
view.input_coding_format().vendor_codec_id().Write(12);
view.output_coding_format().coding_format().Write(
pw::bluetooth::emboss::CodingFormat::LINEAR_PCM);
view.output_coding_format().company_id().Write(13);
view.output_coding_format().vendor_codec_id().Write(14);
view.input_coded_data_size_bits().Write(15);
view.output_coded_data_size_bits().Write(16);
view.input_pcm_data_format().Write(
pw::bluetooth::emboss::PcmDataFormat::ONES_COMPLEMENT);
view.output_pcm_data_format().Write(
pw::bluetooth::emboss::PcmDataFormat::TWOS_COMPLEMENT);
view.input_pcm_sample_payload_msb_position().Write(17);
view.output_pcm_sample_payload_msb_position().Write(18);
view.input_data_path().Write(
pw::bluetooth::emboss::ScoDataPath::AUDIO_TEST_MODE);
view.output_data_path().Write(pw::bluetooth::emboss::ScoDataPath::HCI);
view.input_transport_unit_size_bits().Write(19);
view.output_transport_unit_size_bits().Write(20);
view.max_latency_ms().Write(21);
view.packet_types().BackingStorage().WriteUInt(0x003F); // All packet types
view.retransmission_effort().Write(
pw::bluetooth::emboss::SynchronousConnectionParameters::
ScoRetransmissionEffort::QUALITY_OPTIMIZED);
return out;
}
bt::StaticPacket<pw::bluetooth::emboss::SynchronousConnectionParametersWriter>
ScoConnectionParams() {
auto params = kConnectionParams;
params.view().packet_types().BackingStorage().WriteUInt(0);
params.view().packet_types().hv3().Write(true);
return params;
}
bt::StaticPacket<pw::bluetooth::emboss::SynchronousConnectionParametersWriter>
EscoConnectionParams() {
auto params = kConnectionParams;
params.view().packet_types().BackingStorage().WriteUInt(0);
params.view().packet_types().ev3().Write(true);
return params;
}
using TestingBase =
bt::testing::FakeDispatcherControllerTest<bt::testing::MockController>;
// Activate a SCO connection and set the close handler to call Deactivate()
void activate_connection(OpenConnectionResult& result) {
if (result.is_ok()) {
result.value()->Activate(
/*rx_callback=*/[]() {},
/*closed_callback=*/[result] { result.value()->Deactivate(); });
};
}
class ScoConnectionManagerTest : public TestingBase {
public:
ScoConnectionManagerTest() = default;
~ScoConnectionManagerTest() override = default;
void SetUp() override {
TestingBase::SetUp();
InitializeACLDataChannel();
InitializeScoDataChannel();
manager_ =
std::make_unique<ScoConnectionManager>(PeerId(1),
kAclConnectionHandle,
kPeerAddress,
kLocalAddress,
transport()->GetWeakPtr());
kConnectionParams = InitializeConnectionParams();
}
void TearDown() override {
manager_.reset();
RunUntilIdle();
TestingBase::TearDown();
}
void DestroyManager() { manager_.reset(); }
ScoConnectionManager* manager() const { return manager_.get(); }
private:
std::unique_ptr<ScoConnectionManager> manager_;
BT_DISALLOW_COPY_AND_ASSIGN_ALLOW_MOVE(ScoConnectionManagerTest);
};
TEST_F(ScoConnectionManagerTest, OpenConnectionSuccess) {
auto setup_status_packet = testing::CommandStatusPacket(
hci_spec::kEnhancedSetupSynchronousConnection,
pw::bluetooth::emboss::StatusCode::SUCCESS);
auto conn_complete_packet = testing::SynchronousConnectionCompletePacket(
kScoConnectionHandle,
kPeerAddress,
hci_spec::LinkType::kExtendedSCO,
pw::bluetooth::emboss::StatusCode::SUCCESS);
EXPECT_CMD_PACKET_OUT(test_device(),
testing::EnhancedSetupSynchronousConnectionPacket(
kAclConnectionHandle, kConnectionParams),
&setup_status_packet,
&conn_complete_packet);
std::optional<OpenConnectionResult> conn_result;
auto conn_cb = [&conn_result](auto result) {
activate_connection(result);
conn_result = std::move(result);
};
auto req_handle =
manager()->OpenConnection(kConnectionParams, std::move(conn_cb));
RunUntilIdle();
ASSERT_TRUE(conn_result.has_value());
ASSERT_TRUE(conn_result->is_ok());
ASSERT_TRUE(conn_result->value().is_alive());
EXPECT_EQ(conn_result->value()->handle(), kScoConnectionHandle);
EXPECT_CMD_PACKET_OUT(test_device(),
testing::DisconnectPacket(kScoConnectionHandle));
conn_result->value()->Close();
RunUntilIdle();
}
TEST_F(ScoConnectionManagerTest, OpenConnectionAndReceiveFailureStatusEvent) {
auto setup_status_packet = testing::CommandStatusPacket(
hci_spec::kEnhancedSetupSynchronousConnection,
pw::bluetooth::emboss::StatusCode::CONNECTION_LIMIT_EXCEEDED);
EXPECT_CMD_PACKET_OUT(test_device(),
testing::EnhancedSetupSynchronousConnectionPacket(
kAclConnectionHandle, kConnectionParams),
&setup_status_packet);
std::optional<OpenConnectionResult> conn;
auto conn_cb = [&conn](auto cb_result) {
activate_connection(cb_result);
conn = std::move(cb_result);
};
auto req_handle =
manager()->OpenConnection(kConnectionParams, std::move(conn_cb));
RunUntilIdle();
ASSERT_TRUE(conn.has_value());
ASSERT_TRUE(conn->is_error());
EXPECT_EQ(conn->error_value(), HostError::kFailed);
}
TEST_F(ScoConnectionManagerTest, OpenConnectionAndReceiveFailureCompleteEvent) {
auto setup_status_packet = testing::CommandStatusPacket(
hci_spec::kEnhancedSetupSynchronousConnection,
pw::bluetooth::emboss::StatusCode::SUCCESS);
auto conn_complete_packet = testing::SynchronousConnectionCompletePacket(
kScoConnectionHandle,
kPeerAddress,
hci_spec::LinkType::kExtendedSCO,
pw::bluetooth::emboss::StatusCode::CONNECTION_FAILED_TO_BE_ESTABLISHED);
EXPECT_CMD_PACKET_OUT(test_device(),
testing::EnhancedSetupSynchronousConnectionPacket(
kAclConnectionHandle, kConnectionParams),
&setup_status_packet,
&conn_complete_packet);
std::optional<OpenConnectionResult> conn;
auto conn_cb = [&conn](auto cb_result) {
activate_connection(cb_result);
conn = std::move(cb_result);
};
auto req_handle =
manager()->OpenConnection(kConnectionParams, std::move(conn_cb));
RunUntilIdle();
ASSERT_TRUE(conn.has_value());
ASSERT_TRUE(conn->is_error());
EXPECT_EQ(conn->error_value(), HostError::kFailed);
}
TEST_F(
ScoConnectionManagerTest,
AcceptConnectionCompleteEventErrorAndResultCallbackDestroysRequestHandle) {
std::optional<AcceptConnectionResult> conn;
std::optional<ScoConnectionManager::RequestHandle> req_handle;
auto conn_cb = [&conn, &req_handle](auto cb_result) {
req_handle.reset();
conn = std::move(cb_result);
};
req_handle =
manager()->AcceptConnection({kConnectionParams}, std::move(conn_cb));
auto conn_req_packet =
testing::ConnectionRequestPacket(kPeerAddress, hci_spec::LinkType::kSCO);
test_device()->SendCommandChannelPacket(conn_req_packet);
auto accept_status_packet = testing::CommandStatusPacket(
hci_spec::kEnhancedAcceptSynchronousConnectionRequest,
pw::bluetooth::emboss::StatusCode::UNSPECIFIED_ERROR);
auto conn_complete_packet = testing::SynchronousConnectionCompletePacket(
kScoConnectionHandle,
kPeerAddress,
hci_spec::LinkType::kExtendedSCO,
pw::bluetooth::emboss::StatusCode::CONNECTION_ACCEPT_TIMEOUT_EXCEEDED);
EXPECT_CMD_PACKET_OUT(
test_device(),
testing::EnhancedAcceptSynchronousConnectionRequestPacket(
kPeerAddress, kConnectionParams),
&accept_status_packet,
&conn_complete_packet);
RunUntilIdle();
ASSERT_TRUE(conn.has_value());
ASSERT_TRUE(conn->is_error());
EXPECT_EQ(conn->error_value(), HostError::kParametersRejected);
}
TEST_F(ScoConnectionManagerTest, IgnoreWrongAddressInConnectionComplete) {
auto setup_status_packet = testing::CommandStatusPacket(
hci_spec::kEnhancedSetupSynchronousConnection,
pw::bluetooth::emboss::StatusCode::SUCCESS);
const DeviceAddress kWrongPeerAddress(DeviceAddress::Type::kBREDR,
{0x00, 0x00, 0x00, 0x00, 0x00, 0x05});
auto conn_complete_packet_wrong =
testing::SynchronousConnectionCompletePacket(
kScoConnectionHandle,
kWrongPeerAddress,
hci_spec::LinkType::kExtendedSCO,
pw::bluetooth::emboss::StatusCode::SUCCESS);
EXPECT_CMD_PACKET_OUT(test_device(),
testing::EnhancedSetupSynchronousConnectionPacket(
kAclConnectionHandle, kConnectionParams),
&setup_status_packet,
&conn_complete_packet_wrong);
std::optional<OpenConnectionResult> conn_result;
auto conn_cb = [&conn_result](auto result) {
activate_connection(result);
conn_result = std::move(result);
};
auto req_handle =
manager()->OpenConnection(kConnectionParams, std::move(conn_cb));
RunUntilIdle();
EXPECT_FALSE(conn_result.has_value());
// Ensure subsequent correct complete packet completes request.
auto conn_complete_packet = testing::SynchronousConnectionCompletePacket(
kScoConnectionHandle,
kPeerAddress,
hci_spec::LinkType::kExtendedSCO,
pw::bluetooth::emboss::StatusCode::SUCCESS);
test_device()->SendCommandChannelPacket(conn_complete_packet);
RunUntilIdle();
ASSERT_TRUE(conn_result.has_value());
ASSERT_TRUE(conn_result->is_ok());
EXPECT_CMD_PACKET_OUT(test_device(),
testing::DisconnectPacket(kScoConnectionHandle));
}
TEST_F(ScoConnectionManagerTest,
UnexpectedConnectionCompleteDisconnectsConnection) {
auto conn_complete_packet = testing::SynchronousConnectionCompletePacket(
kScoConnectionHandle,
kPeerAddress,
hci_spec::LinkType::kExtendedSCO,
pw::bluetooth::emboss::StatusCode::SUCCESS);
EXPECT_CMD_PACKET_OUT(test_device(),
testing::DisconnectPacket(kScoConnectionHandle));
test_device()->SendCommandChannelPacket(conn_complete_packet);
RunUntilIdle();
}
TEST_F(ScoConnectionManagerTest, DestroyingManagerClosesConnections) {
auto setup_status_packet = testing::CommandStatusPacket(
hci_spec::kEnhancedSetupSynchronousConnection,
pw::bluetooth::emboss::StatusCode::SUCCESS);
auto conn_complete_packet = testing::SynchronousConnectionCompletePacket(
kScoConnectionHandle,
kPeerAddress,
hci_spec::LinkType::kExtendedSCO,
pw::bluetooth::emboss::StatusCode::SUCCESS);
EXPECT_CMD_PACKET_OUT(test_device(),
testing::EnhancedSetupSynchronousConnectionPacket(
kAclConnectionHandle, kConnectionParams),
&setup_status_packet,
&conn_complete_packet);
std::optional<OpenConnectionResult> conn_result;
auto conn_cb = [&conn_result](OpenConnectionResult result) {
activate_connection(result);
conn_result = std::move(result);
};
auto req_handle =
manager()->OpenConnection(kConnectionParams, std::move(conn_cb));
RunUntilIdle();
ASSERT_TRUE(conn_result.has_value());
ASSERT_TRUE(conn_result->is_ok());
EXPECT_TRUE(conn_result->value().is_alive());
EXPECT_CMD_PACKET_OUT(test_device(),
testing::DisconnectPacket(kScoConnectionHandle));
DestroyManager();
RunUntilIdle();
// WeakPtr should become invalid.
EXPECT_FALSE(conn_result->value().is_alive());
}
TEST_F(ScoConnectionManagerTest, QueueThreeRequestsCancelsSecond) {
const hci_spec::ConnectionHandle handle_0 = kScoConnectionHandle;
const hci_spec::ConnectionHandle handle_1 = handle_0 + 1;
const hci_spec::ConnectionHandle handle_2 = handle_1 + 1;
auto setup_status_packet = testing::CommandStatusPacket(
hci_spec::kEnhancedSetupSynchronousConnection,
pw::bluetooth::emboss::StatusCode::SUCCESS);
EXPECT_CMD_PACKET_OUT(test_device(),
testing::EnhancedSetupSynchronousConnectionPacket(
kAclConnectionHandle, kConnectionParams),
&setup_status_packet);
std::optional<OpenConnectionResult> conn_result_0;
auto conn_cb_0 = [&conn_result_0](auto cb_conn) {
// No need to activate the connection here since Deactivate is called
// manually.
conn_result_0 = std::move(cb_conn);
};
auto req_handle_0 =
manager()->OpenConnection(kConnectionParams, std::move(conn_cb_0));
std::optional<OpenConnectionResult> conn_result_1;
auto conn_cb_1 = [&conn_result_1](auto cb_result) {
activate_connection(cb_result);
conn_result_1 = std::move(cb_result);
};
auto req_handle_1 =
manager()->OpenConnection(kConnectionParams, std::move(conn_cb_1));
std::optional<OpenConnectionResult> conn_result_2;
auto conn_cb_2 = [&conn_result_2](auto cb_conn) {
// No need to activate the connection here since Deactivate is called
// manually.
conn_result_2 = std::move(cb_conn);
};
auto req_handle_2 =
manager()->OpenConnection(kConnectionParams, std::move(conn_cb_2));
RunUntilIdle();
EXPECT_FALSE(conn_result_0.has_value());
EXPECT_FALSE(conn_result_2.has_value());
ASSERT_TRUE(conn_result_1.has_value());
ASSERT_TRUE(conn_result_1->is_error());
EXPECT_EQ(conn_result_1->error_value(), HostError::kCanceled);
EXPECT_CMD_PACKET_OUT(test_device(),
testing::EnhancedSetupSynchronousConnectionPacket(
kAclConnectionHandle, kConnectionParams),
&setup_status_packet);
auto conn_complete_packet_0 = testing::SynchronousConnectionCompletePacket(
handle_0,
kPeerAddress,
hci_spec::LinkType::kExtendedSCO,
pw::bluetooth::emboss::StatusCode::SUCCESS);
test_device()->SendCommandChannelPacket(conn_complete_packet_0);
RunUntilIdle();
ASSERT_TRUE(conn_result_0.has_value());
ASSERT_TRUE(conn_result_0->is_ok());
EXPECT_FALSE(conn_result_2.has_value());
auto conn_complete_packet_2 = testing::SynchronousConnectionCompletePacket(
handle_2,
kPeerAddress,
hci_spec::LinkType::kExtendedSCO,
pw::bluetooth::emboss::StatusCode::SUCCESS);
test_device()->SendCommandChannelPacket(conn_complete_packet_2);
RunUntilIdle();
ASSERT_TRUE(conn_result_2.has_value());
ASSERT_TRUE(conn_result_2->is_ok());
// Send status and complete events so second disconnect command isn't queued
// in CommandChannel.
auto disconn_status_packet_0 = testing::CommandStatusPacket(
hci_spec::kDisconnect, pw::bluetooth::emboss::StatusCode::SUCCESS);
auto disconn_complete_0 = testing::DisconnectionCompletePacket(handle_0);
EXPECT_CMD_PACKET_OUT(test_device(),
testing::DisconnectPacket(handle_0),
&disconn_status_packet_0,
&disconn_complete_0);
conn_result_0.value()->Deactivate();
EXPECT_CMD_PACKET_OUT(test_device(), testing::DisconnectPacket(handle_2));
conn_result_2.value()->Deactivate();
RunUntilIdle();
}
TEST_F(ScoConnectionManagerTest, HandleReuse) {
auto setup_status_packet = testing::CommandStatusPacket(
hci_spec::kEnhancedSetupSynchronousConnection,
pw::bluetooth::emboss::StatusCode::SUCCESS);
auto conn_complete_packet = testing::SynchronousConnectionCompletePacket(
kScoConnectionHandle,
kPeerAddress,
hci_spec::LinkType::kExtendedSCO,
pw::bluetooth::emboss::StatusCode::SUCCESS);
EXPECT_CMD_PACKET_OUT(test_device(),
testing::EnhancedSetupSynchronousConnectionPacket(
kAclConnectionHandle, kConnectionParams),
&setup_status_packet,
&conn_complete_packet);
std::optional<OpenConnectionResult> conn_result;
auto conn_cb = [&conn_result](auto cb_conn) {
// No need to activate the connection here since Deactivate is called
// manually.
conn_result = std::move(cb_conn);
};
auto req_handle_0 = manager()->OpenConnection(kConnectionParams, conn_cb);
RunUntilIdle();
ASSERT_TRUE(conn_result.has_value());
ASSERT_TRUE(conn_result->is_ok());
ScoConnection::WeakPtr conn = conn_result->value();
EXPECT_EQ(conn->handle(), kScoConnectionHandle);
auto disconn_status_packet = testing::CommandStatusPacket(
hci_spec::kDisconnect, pw::bluetooth::emboss::StatusCode::SUCCESS);
auto disconn_complete =
testing::DisconnectionCompletePacket(kScoConnectionHandle);
EXPECT_CMD_PACKET_OUT(test_device(),
testing::DisconnectPacket(kScoConnectionHandle),
&disconn_status_packet,
&disconn_complete);
conn->Deactivate();
conn_result.reset();
EXPECT_CMD_PACKET_OUT(test_device(),
testing::EnhancedSetupSynchronousConnectionPacket(
kAclConnectionHandle, kConnectionParams),
&setup_status_packet,
&conn_complete_packet);
auto req_handle_1 = manager()->OpenConnection(kConnectionParams, conn_cb);
RunUntilIdle();
ASSERT_TRUE(conn_result.has_value());
ASSERT_TRUE(conn_result->is_ok());
EXPECT_EQ(conn_result->value()->handle(), kScoConnectionHandle);
EXPECT_CMD_PACKET_OUT(test_device(),
testing::DisconnectPacket(kScoConnectionHandle));
}
TEST_F(ScoConnectionManagerTest, AcceptConnectionSuccess) {
std::optional<AcceptConnectionResult> conn_result;
auto conn_cb = [&conn_result](auto cb_conn) {
EXPECT_TRUE(cb_conn.is_ok());
conn_result = std::move(cb_conn);
};
auto req_handle =
manager()->AcceptConnection({kConnectionParams}, std::move(conn_cb));
auto conn_req_packet =
testing::ConnectionRequestPacket(kPeerAddress, hci_spec::LinkType::kSCO);
test_device()->SendCommandChannelPacket(conn_req_packet);
auto accept_status_packet = testing::CommandStatusPacket(
hci_spec::kEnhancedAcceptSynchronousConnectionRequest,
pw::bluetooth::emboss::StatusCode::SUCCESS);
EXPECT_CMD_PACKET_OUT(
test_device(),
testing::EnhancedAcceptSynchronousConnectionRequestPacket(
kPeerAddress, kConnectionParams),
&accept_status_packet);
RunUntilIdle();
EXPECT_FALSE(conn_result.has_value());
test_device()->SendCommandChannelPacket(
testing::SynchronousConnectionCompletePacket(
kScoConnectionHandle,
kPeerAddress,
hci_spec::LinkType::kSCO,
pw::bluetooth::emboss::StatusCode::SUCCESS));
RunUntilIdle();
ASSERT_TRUE(conn_result.has_value());
ASSERT_TRUE(conn_result->is_ok());
EXPECT_EQ(conn_result->value().first->handle(), kScoConnectionHandle);
EXPECT_CMD_PACKET_OUT(test_device(),
testing::DisconnectPacket(kScoConnectionHandle));
}
TEST_F(ScoConnectionManagerTest,
AcceptConnectionAndReceiveStatusAndCompleteEventWithErrors) {
std::optional<AcceptConnectionResult> conn_result;
auto conn_cb = [&conn_result](auto cb_conn) {
conn_result = std::move(cb_conn);
};
auto req_handle =
manager()->AcceptConnection({kConnectionParams}, std::move(conn_cb));
auto conn_req_packet =
testing::ConnectionRequestPacket(kPeerAddress, hci_spec::LinkType::kSCO);
test_device()->SendCommandChannelPacket(conn_req_packet);
auto accept_status_packet = testing::CommandStatusPacket(
hci_spec::kEnhancedAcceptSynchronousConnectionRequest,
pw::bluetooth::emboss::StatusCode::INVALID_HCI_COMMAND_PARAMETERS);
EXPECT_CMD_PACKET_OUT(
test_device(),
testing::EnhancedAcceptSynchronousConnectionRequestPacket(
kPeerAddress, kConnectionParams),
&accept_status_packet);
RunUntilIdle();
EXPECT_FALSE(conn_result.has_value());
test_device()->SendCommandChannelPacket(
testing::SynchronousConnectionCompletePacket(
kScoConnectionHandle,
kPeerAddress,
hci_spec::LinkType::kSCO,
pw::bluetooth::emboss::StatusCode::
CONNECTION_ACCEPT_TIMEOUT_EXCEEDED));
RunUntilIdle();
ASSERT_TRUE(conn_result.has_value());
ASSERT_TRUE(conn_result->is_error());
EXPECT_EQ(conn_result->error_value(), HostError::kParametersRejected);
}
TEST_F(ScoConnectionManagerTest,
AcceptConnectionAndReceiveCompleteEventWithFailureStatus) {
std::optional<AcceptConnectionResult> conn_result;
auto conn_cb = [&conn_result](auto cb_conn) {
conn_result = std::move(cb_conn);
};
auto req_handle =
manager()->AcceptConnection({kConnectionParams}, std::move(conn_cb));
auto conn_req_packet =
testing::ConnectionRequestPacket(kPeerAddress, hci_spec::LinkType::kSCO);
test_device()->SendCommandChannelPacket(conn_req_packet);
auto accept_status_packet = testing::CommandStatusPacket(
hci_spec::kEnhancedAcceptSynchronousConnectionRequest,
pw::bluetooth::emboss::StatusCode::SUCCESS);
EXPECT_CMD_PACKET_OUT(
test_device(),
testing::EnhancedAcceptSynchronousConnectionRequestPacket(
kPeerAddress, kConnectionParams),
&accept_status_packet);
RunUntilIdle();
EXPECT_FALSE(conn_result.has_value());
test_device()->SendCommandChannelPacket(
testing::SynchronousConnectionCompletePacket(
kScoConnectionHandle,
kPeerAddress,
hci_spec::LinkType::kSCO,
pw::bluetooth::emboss::StatusCode::
CONNECTION_FAILED_TO_BE_ESTABLISHED));
RunUntilIdle();
ASSERT_TRUE(conn_result.has_value());
ASSERT_TRUE(conn_result->is_error());
EXPECT_EQ(conn_result->error_value(), HostError::kParametersRejected);
}
TEST_F(ScoConnectionManagerTest,
RejectInboundRequestWhileInitiatorRequestPending) {
size_t conn_cb_count = 0;
auto conn_cb = [&conn_cb_count](auto cb_conn) {
activate_connection(cb_conn);
conn_cb_count++;
};
auto setup_status_packet = testing::CommandStatusPacket(
hci_spec::kEnhancedSetupSynchronousConnection,
pw::bluetooth::emboss::StatusCode::SUCCESS);
EXPECT_CMD_PACKET_OUT(test_device(),
testing::EnhancedSetupSynchronousConnectionPacket(
kAclConnectionHandle, kConnectionParams),
&setup_status_packet);
auto req_handle =
manager()->OpenConnection(kConnectionParams, std::move(conn_cb));
auto conn_req_packet =
testing::ConnectionRequestPacket(kPeerAddress, hci_spec::LinkType::kSCO);
test_device()->SendCommandChannelPacket(conn_req_packet);
auto reject_status_packet = testing::CommandStatusPacket(
hci_spec::kRejectSynchronousConnectionRequest,
pw::bluetooth::emboss::StatusCode::SUCCESS);
EXPECT_CMD_PACKET_OUT(
test_device(),
testing::RejectSynchronousConnectionRequest(
kPeerAddress,
pw::bluetooth::emboss::StatusCode::CONNECTION_REJECTED_BAD_BD_ADDR),
&reject_status_packet);
RunUntilIdle();
EXPECT_EQ(conn_cb_count, 0u);
// Destroy manager so that callback gets called before callback reference is
// invalid.
DestroyManager();
EXPECT_EQ(conn_cb_count, 1u);
}
TEST_F(ScoConnectionManagerTest, RejectInboundRequestWhenNoRequestsPending) {
auto conn_req_packet =
testing::ConnectionRequestPacket(kPeerAddress, hci_spec::LinkType::kSCO);
test_device()->SendCommandChannelPacket(conn_req_packet);
auto reject_status_packet = testing::CommandStatusPacket(
hci_spec::kRejectSynchronousConnectionRequest,
pw::bluetooth::emboss::StatusCode::SUCCESS);
EXPECT_CMD_PACKET_OUT(
test_device(),
testing::RejectSynchronousConnectionRequest(
kPeerAddress,
pw::bluetooth::emboss::StatusCode::CONNECTION_REJECTED_BAD_BD_ADDR),
&reject_status_packet);
RunUntilIdle();
}
TEST_F(ScoConnectionManagerTest, IgnoreInboundAclRequest) {
auto conn_req_packet =
testing::ConnectionRequestPacket(kPeerAddress, hci_spec::LinkType::kACL);
test_device()->SendCommandChannelPacket(conn_req_packet);
RunUntilIdle();
}
TEST_F(ScoConnectionManagerTest, IgnoreInboundRequestWrongPeerAddress) {
const DeviceAddress address(DeviceAddress::Type::kBREDR,
{0x00, 0x00, 0x00, 0x00, 0x00, 0x05});
auto conn_req_packet =
testing::ConnectionRequestPacket(address, hci_spec::LinkType::kACL);
test_device()->SendCommandChannelPacket(conn_req_packet);
RunUntilIdle();
}
TEST_F(ScoConnectionManagerTest,
QueueTwoAcceptConnectionRequestsCancelsFirstRequest) {
std::optional<AcceptConnectionResult> conn_result_0;
auto conn_cb = [&conn_result_0](auto cb_conn) {
conn_result_0 = std::move(cb_conn);
};
auto req_handle_0 = manager()->AcceptConnection({kConnectionParams}, conn_cb);
auto second_conn_params = kConnectionParams;
second_conn_params.view().transmit_bandwidth().Write(99);
std::optional<AcceptConnectionResult> conn_result_1;
auto req_handle_1 = manager()->AcceptConnection(
{second_conn_params},
[&conn_result_1](auto cb_conn) { conn_result_1 = std::move(cb_conn); });
ASSERT_TRUE(conn_result_0.has_value());
ASSERT_TRUE(conn_result_0->is_error());
EXPECT_EQ(conn_result_0->error_value(), HostError::kCanceled);
auto conn_req_packet =
testing::ConnectionRequestPacket(kPeerAddress, hci_spec::LinkType::kSCO);
test_device()->SendCommandChannelPacket(conn_req_packet);
auto accept_status_packet = testing::CommandStatusPacket(
hci_spec::kEnhancedAcceptSynchronousConnectionRequest,
pw::bluetooth::emboss::StatusCode::SUCCESS);
EXPECT_CMD_PACKET_OUT(
test_device(),
testing::EnhancedAcceptSynchronousConnectionRequestPacket(
kPeerAddress, second_conn_params),
&accept_status_packet);
RunUntilIdle();
EXPECT_FALSE(conn_result_1.has_value());
test_device()->SendCommandChannelPacket(
testing::SynchronousConnectionCompletePacket(
kScoConnectionHandle,
kPeerAddress,
hci_spec::LinkType::kSCO,
pw::bluetooth::emboss::StatusCode::SUCCESS));
RunUntilIdle();
ASSERT_TRUE(conn_result_1.has_value());
ASSERT_TRUE(conn_result_1->is_ok());
EXPECT_EQ(conn_result_1->value().first->handle(), kScoConnectionHandle);
EXPECT_CMD_PACKET_OUT(test_device(),
testing::DisconnectPacket(kScoConnectionHandle));
}
TEST_F(
ScoConnectionManagerTest,
QueueTwoAcceptConnectionRequestsCancelsFirstRequestAndFirstRequestCallbackDestroysRequestHandle) {
std::optional<ScoConnectionManager::RequestHandle> req_handle_0;
std::optional<AcceptConnectionResult> conn_result_0;
auto conn_cb = [&conn_result_0, &req_handle_0](auto cb_conn) {
conn_result_0 = std::move(cb_conn);
req_handle_0.reset();
};
req_handle_0 = manager()->AcceptConnection({kConnectionParams}, conn_cb);
auto second_conn_params = kConnectionParams;
second_conn_params.view().transmit_bandwidth().Write(99);
std::optional<AcceptConnectionResult> conn_result_1;
auto req_handle_1 = manager()->AcceptConnection(
{second_conn_params},
[&conn_result_1](auto cb_conn) { conn_result_1 = std::move(cb_conn); });
ASSERT_TRUE(conn_result_0.has_value());
ASSERT_TRUE(conn_result_0->is_error());
EXPECT_EQ(conn_result_0->error_value(), HostError::kCanceled);
auto conn_req_packet =
testing::ConnectionRequestPacket(kPeerAddress, hci_spec::LinkType::kSCO);
test_device()->SendCommandChannelPacket(conn_req_packet);
auto accept_status_packet = testing::CommandStatusPacket(
hci_spec::kEnhancedAcceptSynchronousConnectionRequest,
pw::bluetooth::emboss::StatusCode::SUCCESS);
EXPECT_CMD_PACKET_OUT(
test_device(),
testing::EnhancedAcceptSynchronousConnectionRequestPacket(
kPeerAddress, second_conn_params),
&accept_status_packet);
RunUntilIdle();
EXPECT_FALSE(conn_result_1.has_value());
test_device()->SendCommandChannelPacket(
testing::SynchronousConnectionCompletePacket(
kScoConnectionHandle,
kPeerAddress,
hci_spec::LinkType::kSCO,
pw::bluetooth::emboss::StatusCode::SUCCESS));
RunUntilIdle();
ASSERT_TRUE(conn_result_1.has_value());
ASSERT_TRUE(conn_result_1->is_ok());
EXPECT_EQ(conn_result_1->value().first->handle(), kScoConnectionHandle);
EXPECT_CMD_PACKET_OUT(test_device(),
testing::DisconnectPacket(kScoConnectionHandle));
}
TEST_F(ScoConnectionManagerTest,
QueueSecondAcceptRequestAfterFirstRequestReceivesEvent) {
std::optional<AcceptConnectionResult> conn_result_0;
auto req_handle_0 = manager()->AcceptConnection(
{kConnectionParams},
[&conn_result_0](auto cb_conn) { conn_result_0 = std::move(cb_conn); });
auto conn_req_packet =
testing::ConnectionRequestPacket(kPeerAddress, hci_spec::LinkType::kSCO);
test_device()->SendCommandChannelPacket(conn_req_packet);
EXPECT_CMD_PACKET_OUT(
test_device(),
testing::EnhancedAcceptSynchronousConnectionRequestPacket(
kPeerAddress, kConnectionParams));
RunUntilIdle();
std::optional<AcceptConnectionResult> conn_result_1;
auto req_handle_1 = manager()->AcceptConnection(
{kConnectionParams},
[&conn_result_1](auto cb_conn) { conn_result_1 = std::move(cb_conn); });
// First request should not be cancelled because a request event was received.
EXPECT_FALSE(conn_result_0.has_value());
// Send failure events to fail first request.
test_device()->SendCommandChannelPacket(testing::CommandStatusPacket(
hci_spec::kEnhancedAcceptSynchronousConnectionRequest,
pw::bluetooth::emboss::StatusCode::COMMAND_DISALLOWED));
test_device()->SendCommandChannelPacket(
testing::SynchronousConnectionCompletePacket(
kScoConnectionHandle,
kPeerAddress,
hci_spec::LinkType::kSCO,
pw::bluetooth::emboss::StatusCode::
CONNECTION_ACCEPT_TIMEOUT_EXCEEDED));
RunUntilIdle();
ASSERT_TRUE(conn_result_0.has_value());
ASSERT_TRUE(conn_result_0->is_error());
EXPECT_EQ(conn_result_0->error_value(), HostError::kParametersRejected);
// Second request should now be in progress.
test_device()->SendCommandChannelPacket(conn_req_packet);
auto accept_status_packet = testing::CommandStatusPacket(
hci_spec::kEnhancedAcceptSynchronousConnectionRequest,
pw::bluetooth::emboss::StatusCode::SUCCESS);
EXPECT_CMD_PACKET_OUT(
test_device(),
testing::EnhancedAcceptSynchronousConnectionRequestPacket(
kPeerAddress, kConnectionParams),
&accept_status_packet);
RunUntilIdle();
EXPECT_FALSE(conn_result_1.has_value());
test_device()->SendCommandChannelPacket(
testing::SynchronousConnectionCompletePacket(
kScoConnectionHandle,
kPeerAddress,
hci_spec::LinkType::kSCO,
pw::bluetooth::emboss::StatusCode::SUCCESS));
RunUntilIdle();
ASSERT_TRUE(conn_result_1.has_value());
ASSERT_TRUE(conn_result_1->is_ok());
EXPECT_EQ(conn_result_1->value().first->handle(), kScoConnectionHandle);
EXPECT_CMD_PACKET_OUT(test_device(),
testing::DisconnectPacket(kScoConnectionHandle));
}
TEST_F(ScoConnectionManagerTest, RequestsCancelledOnManagerDestruction) {
auto setup_status_packet = testing::CommandStatusPacket(
hci_spec::kEnhancedSetupSynchronousConnection,
pw::bluetooth::emboss::StatusCode::SUCCESS);
EXPECT_CMD_PACKET_OUT(test_device(),
testing::EnhancedSetupSynchronousConnectionPacket(
kAclConnectionHandle, kConnectionParams),
&setup_status_packet);
std::optional<OpenConnectionResult> conn_result_0;
auto conn_cb_0 = [&conn_result_0](auto cb_conn) {
activate_connection(cb_conn);
conn_result_0 = std::move(cb_conn);
};
auto req_handle_0 =
manager()->OpenConnection(kConnectionParams, std::move(conn_cb_0));
std::optional<OpenConnectionResult> conn_result_1;
auto conn_cb_1 = [&conn_result_1](auto cb_conn) {
activate_connection(cb_conn);
conn_result_1 = std::move(cb_conn);
};
auto req_handle_1 =
manager()->OpenConnection(kConnectionParams, std::move(conn_cb_1));
RunUntilIdle();
DestroyManager();
ASSERT_TRUE(conn_result_0.has_value());
ASSERT_TRUE(conn_result_0->is_error());
EXPECT_EQ(conn_result_0->error_value(), HostError::kCanceled);
ASSERT_TRUE(conn_result_1.has_value());
ASSERT_TRUE(conn_result_1->is_error());
EXPECT_EQ(conn_result_1->error_value(), HostError::kCanceled);
}
TEST_F(ScoConnectionManagerTest, AcceptConnectionExplicitlyCancelledByClient) {
std::optional<AcceptConnectionResult> conn_result;
auto conn_cb = [&conn_result](auto cb_conn) {
conn_result = std::move(cb_conn);
};
auto req_handle =
manager()->AcceptConnection({kConnectionParams}, std::move(conn_cb));
req_handle.Cancel();
ASSERT_TRUE(conn_result.has_value());
ASSERT_TRUE(conn_result->is_error());
EXPECT_EQ(conn_result->error_value(), HostError::kCanceled);
auto conn_req_packet =
testing::ConnectionRequestPacket(kPeerAddress, hci_spec::LinkType::kSCO);
test_device()->SendCommandChannelPacket(conn_req_packet);
auto reject_status_packet = testing::CommandStatusPacket(
hci_spec::kRejectSynchronousConnectionRequest,
pw::bluetooth::emboss::StatusCode::SUCCESS);
EXPECT_CMD_PACKET_OUT(
test_device(),
testing::RejectSynchronousConnectionRequest(
kPeerAddress,
pw::bluetooth::emboss::StatusCode::CONNECTION_REJECTED_BAD_BD_ADDR),
&reject_status_packet);
RunUntilIdle();
ASSERT_TRUE(conn_result->is_error());
EXPECT_EQ(conn_result->error_value(), HostError::kCanceled);
}
TEST_F(ScoConnectionManagerTest,
AcceptConnectionCancelledByClientDestroyingHandle) {
std::optional<AcceptConnectionResult> conn_result;
auto conn_cb = [&conn_result](auto cb_conn) {
conn_result = std::move(cb_conn);
};
// req_handle destroyed at end of scope
{
auto req_handle =
manager()->AcceptConnection({kConnectionParams}, std::move(conn_cb));
}
ASSERT_TRUE(conn_result.has_value());
ASSERT_TRUE(conn_result->is_error());
EXPECT_EQ(conn_result->error_value(), HostError::kCanceled);
auto conn_req_packet =
testing::ConnectionRequestPacket(kPeerAddress, hci_spec::LinkType::kSCO);
test_device()->SendCommandChannelPacket(conn_req_packet);
auto reject_status_packet = testing::CommandStatusPacket(
hci_spec::kRejectSynchronousConnectionRequest,
pw::bluetooth::emboss::StatusCode::SUCCESS);
EXPECT_CMD_PACKET_OUT(
test_device(),
testing::RejectSynchronousConnectionRequest(
kPeerAddress,
pw::bluetooth::emboss::StatusCode::CONNECTION_REJECTED_BAD_BD_ADDR),
&reject_status_packet);
RunUntilIdle();
ASSERT_TRUE(conn_result->is_error());
EXPECT_EQ(conn_result->error_value(), HostError::kCanceled);
}
TEST_F(ScoConnectionManagerTest, OpenConnectionCantBeCancelledOnceInProgress) {
auto setup_status_packet = testing::CommandStatusPacket(
hci_spec::kEnhancedSetupSynchronousConnection,
pw::bluetooth::emboss::StatusCode::SUCCESS);
auto conn_complete_packet = testing::SynchronousConnectionCompletePacket(
kScoConnectionHandle,
kPeerAddress,
hci_spec::LinkType::kExtendedSCO,
pw::bluetooth::emboss::StatusCode::SUCCESS);
EXPECT_CMD_PACKET_OUT(test_device(),
testing::EnhancedSetupSynchronousConnectionPacket(
kAclConnectionHandle, kConnectionParams),
&setup_status_packet);
std::optional<OpenConnectionResult> conn_result;
auto conn_cb = [&conn_result](auto cb_conn) {
activate_connection(cb_conn);
conn_result = std::move(cb_conn);
};
auto req_handle =
manager()->OpenConnection(kConnectionParams, std::move(conn_cb));
req_handle.Cancel();
RunUntilIdle();
EXPECT_FALSE(conn_result.has_value());
test_device()->SendCommandChannelPacket(conn_complete_packet);
RunUntilIdle();
ASSERT_TRUE(conn_result.has_value());
ASSERT_TRUE(conn_result->is_ok());
EXPECT_EQ(conn_result->value()->handle(), kScoConnectionHandle);
EXPECT_CMD_PACKET_OUT(test_device(),
testing::DisconnectPacket(kScoConnectionHandle));
conn_result.value()->Close();
RunUntilIdle();
}
TEST_F(ScoConnectionManagerTest, QueueTwoRequestsAndCancelSecond) {
const hci_spec::ConnectionHandle handle_0 = kScoConnectionHandle;
auto setup_status_packet = testing::CommandStatusPacket(
hci_spec::kEnhancedSetupSynchronousConnection,
pw::bluetooth::emboss::StatusCode::SUCCESS);
EXPECT_CMD_PACKET_OUT(test_device(),
testing::EnhancedSetupSynchronousConnectionPacket(
kAclConnectionHandle, kConnectionParams),
&setup_status_packet);
std::optional<OpenConnectionResult> conn_result_0;
auto conn_cb_0 = [&conn_result_0](auto cb_conn) {
// No need to activate the connection here since Deactivate is called
// manually.
conn_result_0 = std::move(cb_conn);
};
auto req_handle_0 =
manager()->OpenConnection(kConnectionParams, std::move(conn_cb_0));
size_t cb_count_1 = 0;
std::optional<OpenConnectionResult> conn_result_1;
auto conn_cb_1 = [&cb_count_1, &conn_result_1](auto cb_conn) {
activate_connection(cb_conn);
cb_count_1++;
conn_result_1 = std::move(cb_conn);
};
auto req_handle_1 =
manager()->OpenConnection(kConnectionParams, std::move(conn_cb_1));
RunUntilIdle();
EXPECT_FALSE(conn_result_0.has_value());
req_handle_1.Cancel();
EXPECT_EQ(cb_count_1, 1u);
ASSERT_TRUE(conn_result_1.has_value());
ASSERT_TRUE(conn_result_1->is_error());
EXPECT_EQ(conn_result_1->error_value(), HostError::kCanceled);
auto conn_complete_packet_0 = testing::SynchronousConnectionCompletePacket(
handle_0,
kPeerAddress,
hci_spec::LinkType::kExtendedSCO,
pw::bluetooth::emboss::StatusCode::SUCCESS);
test_device()->SendCommandChannelPacket(conn_complete_packet_0);
RunUntilIdle();
ASSERT_TRUE(conn_result_0.has_value());
ASSERT_TRUE(conn_result_0->is_ok());
EXPECT_EQ(cb_count_1, 1u);
auto disconn_status_packet_0 = testing::CommandStatusPacket(
hci_spec::kDisconnect, pw::bluetooth::emboss::StatusCode::SUCCESS);
auto disconn_complete_0 = testing::DisconnectionCompletePacket(handle_0);
EXPECT_CMD_PACKET_OUT(test_device(),
testing::DisconnectPacket(handle_0),
&disconn_status_packet_0,
&disconn_complete_0);
conn_result_0.value()->Deactivate();
RunUntilIdle();
}
TEST_F(
ScoConnectionManagerTest,
QueueingThreeRequestsCancelsSecondAndRequestHandleDestroyedInResultCallback) {
auto setup_status_packet = testing::CommandStatusPacket(
hci_spec::kEnhancedSetupSynchronousConnection,
pw::bluetooth::emboss::StatusCode::SUCCESS);
EXPECT_CMD_PACKET_OUT(test_device(),
testing::EnhancedSetupSynchronousConnectionPacket(
kAclConnectionHandle, kConnectionParams),
&setup_status_packet);
std::optional<OpenConnectionResult> conn_result_0;
auto conn_cb_0 = [&conn_result_0](auto cb_conn) {
activate_connection(cb_conn);
conn_result_0 = std::move(cb_conn);
};
auto req_handle_0 =
manager()->OpenConnection(kConnectionParams, std::move(conn_cb_0));
std::optional<ScoConnectionManager::RequestHandle> req_handle_1;
std::optional<OpenConnectionResult> conn_result_1;
auto conn_cb_1 = [&conn_result_1, &req_handle_1](auto cb_conn) {
activate_connection(cb_conn);
req_handle_1.reset();
conn_result_1 = std::move(cb_conn);
};
req_handle_1 =
manager()->OpenConnection(kConnectionParams, std::move(conn_cb_1));
std::optional<OpenConnectionResult> conn_result_2;
auto conn_cb_2 = [&conn_result_2](auto cb_conn) {
activate_connection(cb_conn);
conn_result_2 = std::move(cb_conn);
};
auto req_handle_2 =
manager()->OpenConnection(kConnectionParams, std::move(conn_cb_2));
RunUntilIdle();
EXPECT_FALSE(conn_result_0.has_value());
EXPECT_FALSE(conn_result_2.has_value());
ASSERT_TRUE(conn_result_1.has_value());
ASSERT_TRUE(conn_result_1->is_error());
EXPECT_EQ(conn_result_1->error_value(), HostError::kCanceled);
DestroyManager();
RunUntilIdle();
}
TEST_F(
ScoConnectionManagerTest,
OpenConnectionFollowedByPeerDisconnectAndSecondOpenConnectonWithHandleReuse) {
auto setup_status_packet = testing::CommandStatusPacket(
hci_spec::kEnhancedSetupSynchronousConnection,
pw::bluetooth::emboss::StatusCode::SUCCESS);
auto conn_complete_packet = testing::SynchronousConnectionCompletePacket(
kScoConnectionHandle,
kPeerAddress,
hci_spec::LinkType::kExtendedSCO,
pw::bluetooth::emboss::StatusCode::SUCCESS);
EXPECT_CMD_PACKET_OUT(test_device(),
testing::EnhancedSetupSynchronousConnectionPacket(
kAclConnectionHandle, kConnectionParams),
&setup_status_packet,
&conn_complete_packet);
std::optional<OpenConnectionResult> conn_result_0;
auto conn_cb_0 = [&conn_result_0](auto result) {
activate_connection(result);
conn_result_0 = std::move(result);
};
auto req_handle_0 =
manager()->OpenConnection(kConnectionParams, std::move(conn_cb_0));
RunUntilIdle();
ASSERT_TRUE(conn_result_0.has_value());
ASSERT_TRUE(conn_result_0->is_ok());
ASSERT_TRUE(conn_result_0->value().is_alive());
EXPECT_EQ(conn_result_0->value()->handle(), kScoConnectionHandle);
test_device()->SendCommandChannelPacket(testing::DisconnectionCompletePacket(
kScoConnectionHandle,
pw::bluetooth::emboss::StatusCode::REMOTE_USER_TERMINATED_CONNECTION));
RunUntilIdle();
EXPECT_CMD_PACKET_OUT(test_device(),
testing::EnhancedSetupSynchronousConnectionPacket(
kAclConnectionHandle, kConnectionParams),
&setup_status_packet,
&conn_complete_packet);
std::optional<OpenConnectionResult> conn_result_1;
auto conn_cb_1 = [&conn_result_1](auto result) {
activate_connection(result);
conn_result_1 = std::move(result);
};
auto req_handle_1 =
manager()->OpenConnection(kConnectionParams, std::move(conn_cb_1));
RunUntilIdle();
ASSERT_TRUE(conn_result_1.has_value());
ASSERT_TRUE(conn_result_1->is_ok());
ASSERT_TRUE(conn_result_1->value().is_alive());
EXPECT_EQ(conn_result_1->value()->handle(), kScoConnectionHandle);
EXPECT_CMD_PACKET_OUT(test_device(),
testing::DisconnectPacket(kScoConnectionHandle));
conn_result_1.value()->Close();
RunUntilIdle();
}
TEST_F(
ScoConnectionManagerTest,
DestroyManagerWhileResponderRequestInProgressAndDestroyRequestHandleInResultCallback) {
std::optional<AcceptConnectionResult> conn;
std::optional<ScoConnectionManager::RequestHandle> req_handle;
auto conn_cb = [&conn, &req_handle](auto cb_result) {
req_handle.reset();
conn = std::move(cb_result);
};
req_handle =
manager()->AcceptConnection({kConnectionParams}, std::move(conn_cb));
RunUntilIdle();
DestroyManager();
RunUntilIdle();
ASSERT_TRUE(conn.has_value());
ASSERT_TRUE(conn->is_error());
EXPECT_EQ(conn->error_value(), HostError::kCanceled);
}
TEST_F(
ScoConnectionManagerTest,
DestroyManagerWhileInitiatorRequestQueuedAndDestroyRequestHandleInResultCallback) {
auto setup_status_packet = testing::CommandStatusPacket(
hci_spec::kEnhancedSetupSynchronousConnection,
pw::bluetooth::emboss::StatusCode::SUCCESS);
EXPECT_CMD_PACKET_OUT(test_device(),
testing::EnhancedSetupSynchronousConnectionPacket(
kAclConnectionHandle, kConnectionParams),
&setup_status_packet);
std::optional<OpenConnectionResult> conn_result_0;
auto conn_cb_0 = [&conn_result_0](auto cb_conn) {
activate_connection(cb_conn);
conn_result_0 = std::move(cb_conn);
};
auto req_handle_0 =
manager()->OpenConnection(kConnectionParams, std::move(conn_cb_0));
std::optional<ScoConnectionManager::RequestHandle> req_handle_1;
std::optional<OpenConnectionResult> conn_result_1;
auto conn_cb_1 = [&conn_result_1, &req_handle_1](auto cb_conn) {
activate_connection(cb_conn);
req_handle_1.reset();
conn_result_1 = std::move(cb_conn);
};
req_handle_1 =
manager()->OpenConnection(kConnectionParams, std::move(conn_cb_1));
RunUntilIdle();
EXPECT_FALSE(conn_result_0.has_value());
DestroyManager();
ASSERT_TRUE(conn_result_0.has_value());
ASSERT_TRUE(conn_result_0->is_error());
EXPECT_EQ(conn_result_0->error_value(), HostError::kCanceled);
ASSERT_TRUE(conn_result_1.has_value());
ASSERT_TRUE(conn_result_1->is_error());
EXPECT_EQ(conn_result_1->error_value(), HostError::kCanceled);
}
TEST_F(ScoConnectionManagerTest,
AcceptConnectionFirstParametersRejectedSecondParametersAccepted) {
bt::StaticPacket<pw::bluetooth::emboss::SynchronousConnectionParametersWriter>
esco_params_0 = kConnectionParams;
esco_params_0.view().packet_types().ev3().Write(true);
bt::StaticPacket<pw::bluetooth::emboss::SynchronousConnectionParametersWriter>
esco_params_1 = kConnectionParams;
esco_params_1.view().packet_types().ev4().Write(true);
std::optional<AcceptConnectionResult> conn_result;
auto conn_cb = [&conn_result](auto cb_conn) {
conn_result = std::move(cb_conn);
};
auto req_handle = manager()->AcceptConnection({esco_params_0, esco_params_1},
std::move(conn_cb));
auto conn_req_packet_0 = testing::ConnectionRequestPacket(
kPeerAddress, hci_spec::LinkType::kExtendedSCO);
test_device()->SendCommandChannelPacket(conn_req_packet_0);
auto accept_status_packet_0 = testing::CommandStatusPacket(
hci_spec::kEnhancedAcceptSynchronousConnectionRequest,
pw::bluetooth::emboss::StatusCode::SUCCESS);
EXPECT_CMD_PACKET_OUT(
test_device(),
testing::EnhancedAcceptSynchronousConnectionRequestPacket(kPeerAddress,
esco_params_0),
&accept_status_packet_0);
RunUntilIdle();
EXPECT_FALSE(conn_result.has_value());
test_device()->SendCommandChannelPacket(
testing::SynchronousConnectionCompletePacket(
kScoConnectionHandle,
kPeerAddress,
hci_spec::LinkType::kExtendedSCO,
pw::bluetooth::emboss::StatusCode::UNSUPPORTED_FEATURE_OR_PARAMETER));
RunUntilIdle();
EXPECT_FALSE(conn_result.has_value());
auto conn_req_packet_1 = testing::ConnectionRequestPacket(
kPeerAddress, hci_spec::LinkType::kExtendedSCO);
test_device()->SendCommandChannelPacket(conn_req_packet_1);
auto accept_status_packet_1 = testing::CommandStatusPacket(
hci_spec::kEnhancedAcceptSynchronousConnectionRequest,
pw::bluetooth::emboss::StatusCode::SUCCESS);
EXPECT_CMD_PACKET_OUT(
test_device(),
testing::EnhancedAcceptSynchronousConnectionRequestPacket(kPeerAddress,
esco_params_1),
&accept_status_packet_1);
RunUntilIdle();
EXPECT_FALSE(conn_result.has_value());
test_device()->SendCommandChannelPacket(
testing::SynchronousConnectionCompletePacket(
kScoConnectionHandle,
kPeerAddress,
hci_spec::LinkType::kExtendedSCO,
pw::bluetooth::emboss::StatusCode::SUCCESS));
RunUntilIdle();
ASSERT_TRUE(conn_result.has_value());
ASSERT_TRUE(conn_result->is_ok());
EXPECT_EQ(conn_result->value().first->handle(), kScoConnectionHandle);
size_t result_parameter_index = conn_result->value().second;
EXPECT_EQ(result_parameter_index, 1u);
EXPECT_CMD_PACKET_OUT(test_device(),
testing::DisconnectPacket(kScoConnectionHandle));
}
TEST_F(
ScoConnectionManagerTest,
AcceptScoConnectionWithFirstParametersEscoPacketTypeAndSecondScoPacketTypeSkipsToSecondParameters) {
std::optional<AcceptConnectionResult> conn_result;
auto conn_cb = [&conn_result](auto cb_conn) {
conn_result = std::move(cb_conn);
};
auto req_handle = manager()->AcceptConnection(
{EscoConnectionParams(), ScoConnectionParams()}, std::move(conn_cb));
auto conn_req_packet =
testing::ConnectionRequestPacket(kPeerAddress, hci_spec::LinkType::kSCO);
test_device()->SendCommandChannelPacket(conn_req_packet);
auto accept_status_packet = testing::CommandStatusPacket(
hci_spec::kEnhancedAcceptSynchronousConnectionRequest,
pw::bluetooth::emboss::StatusCode::SUCCESS);
EXPECT_CMD_PACKET_OUT(
test_device(),
testing::EnhancedAcceptSynchronousConnectionRequestPacket(
kPeerAddress, ScoConnectionParams()),
&accept_status_packet);
RunUntilIdle();
EXPECT_FALSE(conn_result.has_value());
test_device()->SendCommandChannelPacket(
testing::SynchronousConnectionCompletePacket(
kScoConnectionHandle,
kPeerAddress,
hci_spec::LinkType::kSCO,
pw::bluetooth::emboss::StatusCode::SUCCESS));
RunUntilIdle();
ASSERT_TRUE(conn_result.has_value());
ASSERT_TRUE(conn_result->is_ok());
EXPECT_EQ(conn_result->value().first->handle(), kScoConnectionHandle);
size_t result_parameter_index = conn_result->value().second;
EXPECT_EQ(result_parameter_index, 1u);
// Verify that the correct parameters were given to the ScoConnection.
ASSERT_TRUE(
conn_result->value().first->parameters().view().packet_types().Equals(
ScoConnectionParams().view().packet_types()));
EXPECT_CMD_PACKET_OUT(test_device(),
testing::DisconnectPacket(kScoConnectionHandle));
}
TEST_F(
ScoConnectionManagerTest,
AcceptEscoConnectionWithFirstParametersScoPacketTypeAndSecondEscoPacketTypeSkipsToSecondParameters) {
std::optional<AcceptConnectionResult> conn_result;
auto conn_cb = [&conn_result](auto cb_conn) {
conn_result = std::move(cb_conn);
};
auto req_handle = manager()->AcceptConnection(
{ScoConnectionParams(), EscoConnectionParams()}, std::move(conn_cb));
auto conn_req_packet = testing::ConnectionRequestPacket(
kPeerAddress, hci_spec::LinkType::kExtendedSCO);
test_device()->SendCommandChannelPacket(conn_req_packet);
auto accept_status_packet = testing::CommandStatusPacket(
hci_spec::kEnhancedAcceptSynchronousConnectionRequest,
pw::bluetooth::emboss::StatusCode::SUCCESS);
EXPECT_CMD_PACKET_OUT(
test_device(),
testing::EnhancedAcceptSynchronousConnectionRequestPacket(
kPeerAddress, EscoConnectionParams()),
&accept_status_packet);
RunUntilIdle();
EXPECT_FALSE(conn_result.has_value());
test_device()->SendCommandChannelPacket(
testing::SynchronousConnectionCompletePacket(
kScoConnectionHandle,
kPeerAddress,
hci_spec::LinkType::kExtendedSCO,
pw::bluetooth::emboss::StatusCode::SUCCESS));
RunUntilIdle();
ASSERT_TRUE(conn_result.has_value());
ASSERT_TRUE(conn_result->is_ok());
EXPECT_EQ(conn_result->value().first->handle(), kScoConnectionHandle);
size_t result_parameter_index = conn_result->value().second;
EXPECT_EQ(result_parameter_index, 1u);
EXPECT_CMD_PACKET_OUT(test_device(),
testing::DisconnectPacket(kScoConnectionHandle));
}
TEST_F(ScoConnectionManagerTest,
AcceptScoConnectionWithEscoParametersFailsAndSendsRejectCommand) {
std::optional<AcceptConnectionResult> conn_result;
auto conn_cb = [&conn_result](auto cb_conn) {
conn_result = std::move(cb_conn);
};
auto req_handle =
manager()->AcceptConnection({EscoConnectionParams()}, std::move(conn_cb));
auto conn_req_packet =
testing::ConnectionRequestPacket(kPeerAddress, hci_spec::LinkType::kSCO);
test_device()->SendCommandChannelPacket(conn_req_packet);
auto reject_status_packet = testing::CommandStatusPacket(
hci_spec::kRejectSynchronousConnectionRequest,
pw::bluetooth::emboss::StatusCode::SUCCESS);
auto conn_complete_packet = testing::SynchronousConnectionCompletePacket(
/*conn=*/0,
kPeerAddress,
hci_spec::LinkType::kExtendedSCO,
pw::bluetooth::emboss::StatusCode::CONNECTION_REJECTED_LIMITED_RESOURCES);
EXPECT_CMD_PACKET_OUT(test_device(),
testing::RejectSynchronousConnectionRequest(
kPeerAddress,
pw::bluetooth::emboss::StatusCode::
CONNECTION_REJECTED_LIMITED_RESOURCES),
&reject_status_packet);
RunUntilIdle();
// The AcceptConnection request should not be completed until the connection
// complete event is received.
EXPECT_FALSE(conn_result.has_value());
test_device()->SendCommandChannelPacket(conn_complete_packet);
RunUntilIdle();
ASSERT_TRUE(conn_result.has_value());
ASSERT_TRUE(conn_result->is_error());
EXPECT_EQ(conn_result->error_value(), HostError::kParametersRejected);
}
TEST_F(ScoConnectionManagerTest, AcceptScoConnectionWithEmptyParametersFails) {
std::optional<AcceptConnectionResult> conn_result;
auto conn_cb = [&conn_result](auto cb_conn) {
conn_result = std::move(cb_conn);
};
auto req_handle =
manager()->AcceptConnection(/*parameters=*/{}, std::move(conn_cb));
ASSERT_TRUE(conn_result.has_value());
ASSERT_TRUE(conn_result->is_error());
EXPECT_EQ(conn_result->error_value(), HostError::kInvalidParameters);
}
TEST_F(
ScoConnectionManagerTest,
QueuedRequestAfterAcceptConnectionCommandCancelsNextAcceptConnectionParameterAttemptWhenThereAreMultipleParameters) {
std::optional<AcceptConnectionResult> conn_result_0;
auto conn_cb_0 = [&conn_result_0](auto cb_conn) {
conn_result_0 = std::move(cb_conn);
};
// Queue an accept request with 2 parameters. The first parameters should fail
// and the second should never be used due to the second request canceling the
// first request.
auto req_handle_0 = manager()->AcceptConnection(
{kConnectionParams, kConnectionParams}, std::move(conn_cb_0));
auto conn_req_packet_0 = testing::ConnectionRequestPacket(
kPeerAddress, hci_spec::LinkType::kExtendedSCO);
test_device()->SendCommandChannelPacket(conn_req_packet_0);
auto accept_status_packet_0 = testing::CommandStatusPacket(
hci_spec::kEnhancedAcceptSynchronousConnectionRequest,
pw::bluetooth::emboss::StatusCode::SUCCESS);
EXPECT_CMD_PACKET_OUT(
test_device(),
testing::EnhancedAcceptSynchronousConnectionRequestPacket(
kPeerAddress, kConnectionParams),
&accept_status_packet_0);
RunUntilIdle();
EXPECT_FALSE(conn_result_0.has_value());
// Second request should cancel first request when connection complete event
// is received.
std::optional<AcceptConnectionResult> conn_result_1;
auto conn_cb_1 = [&conn_result_1](auto cb_conn) {
conn_result_1 = std::move(cb_conn);
};
auto req_handle_1 =
manager()->AcceptConnection({kConnectionParams}, std::move(conn_cb_1));
EXPECT_FALSE(conn_result_0.has_value());
test_device()->SendCommandChannelPacket(
testing::SynchronousConnectionCompletePacket(
kScoConnectionHandle,
kPeerAddress,
hci_spec::LinkType::kExtendedSCO,
pw::bluetooth::emboss::StatusCode::UNSUPPORTED_FEATURE_OR_PARAMETER));
RunUntilIdle();
ASSERT_TRUE(conn_result_0.has_value());
ASSERT_TRUE(conn_result_0->is_error());
EXPECT_EQ(conn_result_0->error_value(), HostError::kCanceled);
EXPECT_FALSE(conn_result_1.has_value());
// Complete the second accept request with an incoming connection.
auto conn_req_packet_1 = testing::ConnectionRequestPacket(
kPeerAddress, hci_spec::LinkType::kExtendedSCO);
test_device()->SendCommandChannelPacket(conn_req_packet_1);
auto accept_status_packet_1 = testing::CommandStatusPacket(
hci_spec::kEnhancedAcceptSynchronousConnectionRequest,
pw::bluetooth::emboss::StatusCode::SUCCESS);
EXPECT_CMD_PACKET_OUT(
test_device(),
testing::EnhancedAcceptSynchronousConnectionRequestPacket(
kPeerAddress, kConnectionParams),
&accept_status_packet_1);
RunUntilIdle();
EXPECT_FALSE(conn_result_1.has_value());
test_device()->SendCommandChannelPacket(
testing::SynchronousConnectionCompletePacket(
kScoConnectionHandle,
kPeerAddress,
hci_spec::LinkType::kExtendedSCO,
pw::bluetooth::emboss::StatusCode::SUCCESS));
RunUntilIdle();
ASSERT_TRUE(conn_result_1.has_value());
ASSERT_TRUE(conn_result_1->is_ok());
EXPECT_EQ(conn_result_1->value().first->handle(), kScoConnectionHandle);
size_t result_parameter_index = conn_result_1->value().second;
EXPECT_EQ(result_parameter_index, 0u);
EXPECT_CMD_PACKET_OUT(test_device(),
testing::DisconnectPacket(kScoConnectionHandle));
}
} // namespace
} // namespace bt::sco