| // 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 |