| // Copyright 2020 The Fuchsia Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| #include "sco_connection_manager.h" |
| |
| #include <optional> |
| |
| #include "src/connectivity/bluetooth/core/bt-host/testing/controller_test.h" |
| #include "src/connectivity/bluetooth/core/bt-host/testing/mock_controller.h" |
| #include "src/connectivity/bluetooth/core/bt-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}); |
| constexpr hci_spec::SynchronousConnectionParameters kConnectionParams{ |
| .transmit_bandwidth = 1, |
| .receive_bandwidth = 2, |
| .transmit_coding_format = |
| hci_spec::VendorCodingFormat{ |
| .coding_format = hci_spec::CodingFormat::kMSbc, |
| .company_id = 3, |
| .vendor_codec_id = 4, |
| }, |
| .receive_coding_format = |
| hci_spec::VendorCodingFormat{ |
| .coding_format = hci_spec::CodingFormat::kCvsd, |
| .company_id = 5, |
| .vendor_codec_id = 6, |
| }, |
| .transmit_codec_frame_size_bytes = 7, |
| .receive_codec_frame_size_bytes = 8, |
| .input_bandwidth = 9, |
| .output_bandwidth = 10, |
| .input_coding_format = |
| hci_spec::VendorCodingFormat{ |
| .coding_format = hci_spec::CodingFormat::kALaw, |
| .company_id = 11, |
| .vendor_codec_id = 12, |
| }, |
| .output_coding_format = |
| hci_spec::VendorCodingFormat{ |
| .coding_format = hci_spec::CodingFormat::kLinearPcm, |
| .company_id = 13, |
| .vendor_codec_id = 14, |
| }, |
| .input_coded_data_size_bits = 15, |
| .output_coded_data_size_bits = 16, |
| .input_pcm_data_format = hci_spec::PcmDataFormat::k1sComplement, |
| .output_pcm_data_format = hci_spec::PcmDataFormat::k2sComplement, |
| .input_pcm_sample_payload_msb_position = 17, |
| .output_pcm_sample_payload_msb_position = 18, |
| .input_data_path = hci_spec::ScoDataPath::kAudioTestMode, |
| .output_data_path = hci_spec::ScoDataPath::kHci, |
| .input_transport_unit_size_bits = 19, |
| .output_transport_unit_size_bits = 20, |
| .max_latency_ms = 21, |
| .packet_types = 0x003F, // All packet types |
| .retransmission_effort = hci_spec::ScoRetransmissionEffort::kQualityOptimized, |
| }; |
| |
| constexpr hci_spec::SynchronousConnectionParameters ScoConnectionParams() { |
| auto params = kConnectionParams; |
| params.packet_types = params.packet_types = |
| static_cast<uint16_t>(hci_spec::ScoPacketTypeBits::kHv3); |
| return params; |
| } |
| |
| constexpr hci_spec::SynchronousConnectionParameters EscoConnectionParams() { |
| auto params = kConnectionParams; |
| params.packet_types = params.packet_types = |
| static_cast<uint16_t>(hci_spec::ScoPacketTypeBits::kEv3); |
| return params; |
| } |
| |
| using TestingBase = bt::testing::ControllerTest<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(); |
| StartTestDevice(); |
| |
| manager_ = std::make_unique<ScoConnectionManager>(PeerId(1), kAclConnectionHandle, kPeerAddress, |
| kLocalAddress, transport()->WeakPtr()); |
| } |
| |
| void TearDown() override { |
| manager_.reset(); |
| RunLoopUntilIdle(); |
| TestingBase::TearDown(); |
| } |
| |
| void DestroyManager() { manager_.reset(); } |
| |
| ScoConnectionManager* manager() const { return manager_.get(); } |
| |
| private: |
| std::unique_ptr<ScoConnectionManager> manager_; |
| |
| DISALLOW_COPY_AND_ASSIGN_ALLOW_MOVE(ScoConnectionManagerTest); |
| }; |
| |
| TEST_F(ScoConnectionManagerTest, OpenConnectionSuccess) { |
| auto setup_status_packet = testing::CommandStatusPacket( |
| hci_spec::kEnhancedSetupSynchronousConnection, hci_spec::StatusCode::kSuccess); |
| auto conn_complete_packet = testing::SynchronousConnectionCompletePacket( |
| kScoConnectionHandle, kPeerAddress, hci_spec::LinkType::kExtendedSCO, |
| hci_spec::StatusCode::kSuccess); |
| 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)); |
| |
| RunLoopUntilIdle(); |
| ASSERT_TRUE(conn_result.has_value()); |
| ASSERT_TRUE(conn_result->is_ok()); |
| ASSERT_TRUE(conn_result->value()); |
| EXPECT_EQ(conn_result->value()->handle(), kScoConnectionHandle); |
| |
| EXPECT_CMD_PACKET_OUT(test_device(), testing::DisconnectPacket(kScoConnectionHandle)); |
| conn_result->value()->Close(); |
| RunLoopUntilIdle(); |
| } |
| |
| TEST_F(ScoConnectionManagerTest, OpenConnectionAndReceiveFailureStatusEvent) { |
| auto setup_status_packet = |
| testing::CommandStatusPacket(hci_spec::kEnhancedSetupSynchronousConnection, |
| hci_spec::StatusCode::kConnectionLimitExceeded); |
| 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)); |
| |
| RunLoopUntilIdle(); |
| 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, hci_spec::StatusCode::kSuccess); |
| auto conn_complete_packet = testing::SynchronousConnectionCompletePacket( |
| kScoConnectionHandle, kPeerAddress, hci_spec::LinkType::kExtendedSCO, |
| hci_spec::StatusCode::kConnectionFailedToBeEstablished); |
| 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)); |
| |
| RunLoopUntilIdle(); |
| 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, |
| hci_spec::StatusCode::kUnspecifiedError); |
| auto conn_complete_packet = testing::SynchronousConnectionCompletePacket( |
| kScoConnectionHandle, kPeerAddress, hci_spec::LinkType::kExtendedSCO, |
| hci_spec::StatusCode::kConnectionAcceptTimeoutExceeded); |
| EXPECT_CMD_PACKET_OUT( |
| test_device(), |
| testing::EnhancedAcceptSynchronousConnectionRequestPacket(kPeerAddress, kConnectionParams), |
| &accept_status_packet, &conn_complete_packet); |
| |
| RunLoopUntilIdle(); |
| 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, hci_spec::StatusCode::kSuccess); |
| 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, |
| hci_spec::StatusCode::kSuccess); |
| 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)); |
| |
| RunLoopUntilIdle(); |
| 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, |
| hci_spec::StatusCode::kSuccess); |
| test_device()->SendCommandChannelPacket(conn_complete_packet); |
| RunLoopUntilIdle(); |
| 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, |
| hci_spec::StatusCode::kSuccess); |
| EXPECT_CMD_PACKET_OUT(test_device(), testing::DisconnectPacket(kScoConnectionHandle)); |
| test_device()->SendCommandChannelPacket(conn_complete_packet); |
| RunLoopUntilIdle(); |
| } |
| |
| TEST_F(ScoConnectionManagerTest, DestroyingManagerClosesConnections) { |
| auto setup_status_packet = testing::CommandStatusPacket( |
| hci_spec::kEnhancedSetupSynchronousConnection, hci_spec::StatusCode::kSuccess); |
| auto conn_complete_packet = testing::SynchronousConnectionCompletePacket( |
| kScoConnectionHandle, kPeerAddress, hci_spec::LinkType::kExtendedSCO, |
| hci_spec::StatusCode::kSuccess); |
| 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)); |
| |
| RunLoopUntilIdle(); |
| ASSERT_TRUE(conn_result.has_value()); |
| ASSERT_TRUE(conn_result->is_ok()); |
| EXPECT_TRUE(conn_result->value()); |
| |
| EXPECT_CMD_PACKET_OUT(test_device(), testing::DisconnectPacket(kScoConnectionHandle)); |
| DestroyManager(); |
| RunLoopUntilIdle(); |
| // WeakPtr should become invalid. |
| EXPECT_FALSE(conn_result->value()); |
| } |
| |
| 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, hci_spec::StatusCode::kSuccess); |
| 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)); |
| |
| RunLoopUntilIdle(); |
| 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, hci_spec::StatusCode::kSuccess); |
| test_device()->SendCommandChannelPacket(conn_complete_packet_0); |
| RunLoopUntilIdle(); |
| 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, hci_spec::StatusCode::kSuccess); |
| test_device()->SendCommandChannelPacket(conn_complete_packet_2); |
| RunLoopUntilIdle(); |
| 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, hci_spec::StatusCode::kSuccess); |
| 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(); |
| RunLoopUntilIdle(); |
| } |
| |
| TEST_F(ScoConnectionManagerTest, HandleReuse) { |
| auto setup_status_packet = testing::CommandStatusPacket( |
| hci_spec::kEnhancedSetupSynchronousConnection, hci_spec::StatusCode::kSuccess); |
| auto conn_complete_packet = testing::SynchronousConnectionCompletePacket( |
| kScoConnectionHandle, kPeerAddress, hci_spec::LinkType::kExtendedSCO, |
| hci_spec::StatusCode::kSuccess); |
| 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); |
| |
| RunLoopUntilIdle(); |
| ASSERT_TRUE(conn_result.has_value()); |
| ASSERT_TRUE(conn_result->is_ok()); |
| fxl::WeakPtr<ScoConnection> conn = conn_result->value(); |
| EXPECT_EQ(conn->handle(), kScoConnectionHandle); |
| |
| auto disconn_status_packet = |
| testing::CommandStatusPacket(hci_spec::kDisconnect, hci_spec::StatusCode::kSuccess); |
| 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); |
| |
| RunLoopUntilIdle(); |
| 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, hci_spec::StatusCode::kSuccess); |
| EXPECT_CMD_PACKET_OUT( |
| test_device(), |
| testing::EnhancedAcceptSynchronousConnectionRequestPacket(kPeerAddress, kConnectionParams), |
| &accept_status_packet); |
| RunLoopUntilIdle(); |
| EXPECT_FALSE(conn_result.has_value()); |
| |
| test_device()->SendCommandChannelPacket(testing::SynchronousConnectionCompletePacket( |
| kScoConnectionHandle, kPeerAddress, hci_spec::LinkType::kSCO, |
| hci_spec::StatusCode::kSuccess)); |
| |
| RunLoopUntilIdle(); |
| 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, |
| hci_spec::StatusCode::kInvalidHCICommandParameters); |
| EXPECT_CMD_PACKET_OUT( |
| test_device(), |
| testing::EnhancedAcceptSynchronousConnectionRequestPacket(kPeerAddress, kConnectionParams), |
| &accept_status_packet); |
| RunLoopUntilIdle(); |
| EXPECT_FALSE(conn_result.has_value()); |
| |
| test_device()->SendCommandChannelPacket(testing::SynchronousConnectionCompletePacket( |
| kScoConnectionHandle, kPeerAddress, hci_spec::LinkType::kSCO, |
| hci_spec::StatusCode::kConnectionAcceptTimeoutExceeded)); |
| |
| RunLoopUntilIdle(); |
| 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, hci_spec::StatusCode::kSuccess); |
| EXPECT_CMD_PACKET_OUT( |
| test_device(), |
| testing::EnhancedAcceptSynchronousConnectionRequestPacket(kPeerAddress, kConnectionParams), |
| &accept_status_packet); |
| RunLoopUntilIdle(); |
| EXPECT_FALSE(conn_result.has_value()); |
| |
| test_device()->SendCommandChannelPacket(testing::SynchronousConnectionCompletePacket( |
| kScoConnectionHandle, kPeerAddress, hci_spec::LinkType::kSCO, |
| hci_spec::StatusCode::kConnectionFailedToBeEstablished)); |
| |
| RunLoopUntilIdle(); |
| 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, hci_spec::StatusCode::kSuccess); |
| 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, hci_spec::StatusCode::kSuccess); |
| EXPECT_CMD_PACKET_OUT(test_device(), |
| testing::RejectSynchronousConnectionRequest( |
| kPeerAddress, hci_spec::StatusCode::kConnectionRejectedBadBdAddr), |
| &reject_status_packet); |
| RunLoopUntilIdle(); |
| 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, hci_spec::StatusCode::kSuccess); |
| EXPECT_CMD_PACKET_OUT(test_device(), |
| testing::RejectSynchronousConnectionRequest( |
| kPeerAddress, hci_spec::StatusCode::kConnectionRejectedBadBdAddr), |
| &reject_status_packet); |
| RunLoopUntilIdle(); |
| } |
| |
| TEST_F(ScoConnectionManagerTest, IgnoreInboundAclRequest) { |
| auto conn_req_packet = testing::ConnectionRequestPacket(kPeerAddress, hci_spec::LinkType::kACL); |
| test_device()->SendCommandChannelPacket(conn_req_packet); |
| RunLoopUntilIdle(); |
| } |
| |
| 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); |
| RunLoopUntilIdle(); |
| } |
| |
| 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.transmit_bandwidth = 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, hci_spec::StatusCode::kSuccess); |
| EXPECT_CMD_PACKET_OUT( |
| test_device(), |
| testing::EnhancedAcceptSynchronousConnectionRequestPacket(kPeerAddress, second_conn_params), |
| &accept_status_packet); |
| RunLoopUntilIdle(); |
| EXPECT_FALSE(conn_result_1.has_value()); |
| |
| test_device()->SendCommandChannelPacket(testing::SynchronousConnectionCompletePacket( |
| kScoConnectionHandle, kPeerAddress, hci_spec::LinkType::kSCO, |
| hci_spec::StatusCode::kSuccess)); |
| |
| RunLoopUntilIdle(); |
| 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.transmit_bandwidth = 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, hci_spec::StatusCode::kSuccess); |
| EXPECT_CMD_PACKET_OUT( |
| test_device(), |
| testing::EnhancedAcceptSynchronousConnectionRequestPacket(kPeerAddress, second_conn_params), |
| &accept_status_packet); |
| RunLoopUntilIdle(); |
| EXPECT_FALSE(conn_result_1.has_value()); |
| |
| test_device()->SendCommandChannelPacket(testing::SynchronousConnectionCompletePacket( |
| kScoConnectionHandle, kPeerAddress, hci_spec::LinkType::kSCO, |
| hci_spec::StatusCode::kSuccess)); |
| |
| RunLoopUntilIdle(); |
| 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)); |
| RunLoopUntilIdle(); |
| |
| 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, |
| hci_spec::StatusCode::kCommandDisallowed)); |
| test_device()->SendCommandChannelPacket(testing::SynchronousConnectionCompletePacket( |
| kScoConnectionHandle, kPeerAddress, hci_spec::LinkType::kSCO, |
| hci_spec::StatusCode::kConnectionAcceptTimeoutExceeded)); |
| RunLoopUntilIdle(); |
| 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, hci_spec::StatusCode::kSuccess); |
| EXPECT_CMD_PACKET_OUT( |
| test_device(), |
| testing::EnhancedAcceptSynchronousConnectionRequestPacket(kPeerAddress, kConnectionParams), |
| &accept_status_packet); |
| RunLoopUntilIdle(); |
| EXPECT_FALSE(conn_result_1.has_value()); |
| |
| test_device()->SendCommandChannelPacket(testing::SynchronousConnectionCompletePacket( |
| kScoConnectionHandle, kPeerAddress, hci_spec::LinkType::kSCO, |
| hci_spec::StatusCode::kSuccess)); |
| |
| RunLoopUntilIdle(); |
| 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, hci_spec::StatusCode::kSuccess); |
| 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)); |
| |
| RunLoopUntilIdle(); |
| |
| 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, hci_spec::StatusCode::kSuccess); |
| EXPECT_CMD_PACKET_OUT(test_device(), |
| testing::RejectSynchronousConnectionRequest( |
| kPeerAddress, hci_spec::StatusCode::kConnectionRejectedBadBdAddr), |
| &reject_status_packet); |
| RunLoopUntilIdle(); |
| 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, hci_spec::StatusCode::kSuccess); |
| EXPECT_CMD_PACKET_OUT(test_device(), |
| testing::RejectSynchronousConnectionRequest( |
| kPeerAddress, hci_spec::StatusCode::kConnectionRejectedBadBdAddr), |
| &reject_status_packet); |
| RunLoopUntilIdle(); |
| 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, hci_spec::StatusCode::kSuccess); |
| auto conn_complete_packet = testing::SynchronousConnectionCompletePacket( |
| kScoConnectionHandle, kPeerAddress, hci_spec::LinkType::kExtendedSCO, |
| hci_spec::StatusCode::kSuccess); |
| 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(); |
| |
| RunLoopUntilIdle(); |
| EXPECT_FALSE(conn_result.has_value()); |
| test_device()->SendCommandChannelPacket(conn_complete_packet); |
| |
| RunLoopUntilIdle(); |
| 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(); |
| RunLoopUntilIdle(); |
| } |
| |
| TEST_F(ScoConnectionManagerTest, QueueTwoRequestsAndCancelSecond) { |
| const hci_spec::ConnectionHandle handle_0 = kScoConnectionHandle; |
| |
| auto setup_status_packet = testing::CommandStatusPacket( |
| hci_spec::kEnhancedSetupSynchronousConnection, hci_spec::StatusCode::kSuccess); |
| 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)); |
| |
| RunLoopUntilIdle(); |
| 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, hci_spec::StatusCode::kSuccess); |
| test_device()->SendCommandChannelPacket(conn_complete_packet_0); |
| RunLoopUntilIdle(); |
| 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, hci_spec::StatusCode::kSuccess); |
| 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(); |
| RunLoopUntilIdle(); |
| } |
| |
| TEST_F(ScoConnectionManagerTest, |
| QueueingThreeRequestsCancelsSecondAndRequestHandleDestroyedInResultCallback) { |
| auto setup_status_packet = testing::CommandStatusPacket( |
| hci_spec::kEnhancedSetupSynchronousConnection, hci_spec::StatusCode::kSuccess); |
| 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)); |
| |
| RunLoopUntilIdle(); |
| 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(); |
| RunLoopUntilIdle(); |
| } |
| |
| TEST_F(ScoConnectionManagerTest, |
| OpenConnectionFollowedByPeerDisconnectAndSecondOpenConnectonWithHandleReuse) { |
| auto setup_status_packet = testing::CommandStatusPacket( |
| hci_spec::kEnhancedSetupSynchronousConnection, hci_spec::StatusCode::kSuccess); |
| auto conn_complete_packet = testing::SynchronousConnectionCompletePacket( |
| kScoConnectionHandle, kPeerAddress, hci_spec::LinkType::kExtendedSCO, |
| hci_spec::StatusCode::kSuccess); |
| 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)); |
| |
| RunLoopUntilIdle(); |
| ASSERT_TRUE(conn_result_0.has_value()); |
| ASSERT_TRUE(conn_result_0->is_ok()); |
| ASSERT_TRUE(conn_result_0->value()); |
| EXPECT_EQ(conn_result_0->value()->handle(), kScoConnectionHandle); |
| |
| test_device()->SendCommandChannelPacket(testing::DisconnectionCompletePacket( |
| kScoConnectionHandle, hci_spec::StatusCode::kRemoteUserTerminatedConnection)); |
| RunLoopUntilIdle(); |
| |
| 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)); |
| |
| RunLoopUntilIdle(); |
| ASSERT_TRUE(conn_result_1.has_value()); |
| ASSERT_TRUE(conn_result_1->is_ok()); |
| ASSERT_TRUE(conn_result_1->value()); |
| EXPECT_EQ(conn_result_1->value()->handle(), kScoConnectionHandle); |
| |
| EXPECT_CMD_PACKET_OUT(test_device(), testing::DisconnectPacket(kScoConnectionHandle)); |
| conn_result_1.value()->Close(); |
| RunLoopUntilIdle(); |
| } |
| |
| 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)); |
| RunLoopUntilIdle(); |
| |
| DestroyManager(); |
| RunLoopUntilIdle(); |
| |
| 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, hci_spec::StatusCode::kSuccess); |
| 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)); |
| |
| RunLoopUntilIdle(); |
| 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) { |
| hci_spec::SynchronousConnectionParameters esco_params_0 = kConnectionParams; |
| esco_params_0.packet_types = static_cast<uint16_t>(hci_spec::ScoPacketTypeBits::kEv3); |
| hci_spec::SynchronousConnectionParameters esco_params_1 = kConnectionParams; |
| esco_params_1.packet_types = static_cast<uint16_t>(hci_spec::ScoPacketTypeBits::kEv4); |
| |
| 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, hci_spec::StatusCode::kSuccess); |
| EXPECT_CMD_PACKET_OUT( |
| test_device(), |
| testing::EnhancedAcceptSynchronousConnectionRequestPacket(kPeerAddress, esco_params_0), |
| &accept_status_packet_0); |
| RunLoopUntilIdle(); |
| EXPECT_FALSE(conn_result.has_value()); |
| |
| test_device()->SendCommandChannelPacket(testing::SynchronousConnectionCompletePacket( |
| kScoConnectionHandle, kPeerAddress, hci_spec::LinkType::kExtendedSCO, |
| hci_spec::StatusCode::kUnsupportedFeatureOrParameter)); |
| |
| RunLoopUntilIdle(); |
| 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, hci_spec::StatusCode::kSuccess); |
| EXPECT_CMD_PACKET_OUT( |
| test_device(), |
| testing::EnhancedAcceptSynchronousConnectionRequestPacket(kPeerAddress, esco_params_1), |
| &accept_status_packet_1); |
| RunLoopUntilIdle(); |
| EXPECT_FALSE(conn_result.has_value()); |
| |
| test_device()->SendCommandChannelPacket(testing::SynchronousConnectionCompletePacket( |
| kScoConnectionHandle, kPeerAddress, hci_spec::LinkType::kExtendedSCO, |
| hci_spec::StatusCode::kSuccess)); |
| |
| RunLoopUntilIdle(); |
| 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, hci_spec::StatusCode::kSuccess); |
| EXPECT_CMD_PACKET_OUT(test_device(), |
| testing::EnhancedAcceptSynchronousConnectionRequestPacket( |
| kPeerAddress, ScoConnectionParams()), |
| &accept_status_packet); |
| RunLoopUntilIdle(); |
| EXPECT_FALSE(conn_result.has_value()); |
| |
| test_device()->SendCommandChannelPacket(testing::SynchronousConnectionCompletePacket( |
| kScoConnectionHandle, kPeerAddress, hci_spec::LinkType::kSCO, |
| hci_spec::StatusCode::kSuccess)); |
| |
| RunLoopUntilIdle(); |
| 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. |
| EXPECT_EQ(conn_result->value().first->parameters().packet_types, |
| ScoConnectionParams().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, hci_spec::StatusCode::kSuccess); |
| EXPECT_CMD_PACKET_OUT(test_device(), |
| testing::EnhancedAcceptSynchronousConnectionRequestPacket( |
| kPeerAddress, EscoConnectionParams()), |
| &accept_status_packet); |
| RunLoopUntilIdle(); |
| EXPECT_FALSE(conn_result.has_value()); |
| |
| test_device()->SendCommandChannelPacket(testing::SynchronousConnectionCompletePacket( |
| kScoConnectionHandle, kPeerAddress, hci_spec::LinkType::kExtendedSCO, |
| hci_spec::StatusCode::kSuccess)); |
| |
| RunLoopUntilIdle(); |
| 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, hci_spec::StatusCode::kSuccess); |
| |
| auto conn_complete_packet = testing::SynchronousConnectionCompletePacket( |
| /*conn=*/0, kPeerAddress, hci_spec::LinkType::kExtendedSCO, |
| hci_spec::StatusCode::kConnectionRejectedLimitedResources); |
| |
| EXPECT_CMD_PACKET_OUT( |
| test_device(), |
| testing::RejectSynchronousConnectionRequest( |
| kPeerAddress, hci_spec::StatusCode::kConnectionRejectedLimitedResources), |
| &reject_status_packet); |
| RunLoopUntilIdle(); |
| // 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); |
| RunLoopUntilIdle(); |
| 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, hci_spec::StatusCode::kSuccess); |
| EXPECT_CMD_PACKET_OUT( |
| test_device(), |
| testing::EnhancedAcceptSynchronousConnectionRequestPacket(kPeerAddress, kConnectionParams), |
| &accept_status_packet_0); |
| RunLoopUntilIdle(); |
| 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, |
| hci_spec::StatusCode::kUnsupportedFeatureOrParameter)); |
| |
| RunLoopUntilIdle(); |
| 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, hci_spec::StatusCode::kSuccess); |
| EXPECT_CMD_PACKET_OUT( |
| test_device(), |
| testing::EnhancedAcceptSynchronousConnectionRequestPacket(kPeerAddress, kConnectionParams), |
| &accept_status_packet_1); |
| RunLoopUntilIdle(); |
| EXPECT_FALSE(conn_result_1.has_value()); |
| |
| test_device()->SendCommandChannelPacket(testing::SynchronousConnectionCompletePacket( |
| kScoConnectionHandle, kPeerAddress, hci_spec::LinkType::kExtendedSCO, |
| hci_spec::StatusCode::kSuccess)); |
| |
| RunLoopUntilIdle(); |
| 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 |