// Copyright 2021 Google LLC // // 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 "connections/implementation/base_pcp_handler.h" #include #include #include #include "gmock/gmock.h" #include "protobuf-matchers/protocol-buffer-matchers.h" #include "gtest/gtest.h" #include "absl/time/time.h" #include "connections/implementation/proto/offline_wire_formats.pb.h" #include "connections/implementation/base_endpoint_channel.h" #include "connections/implementation/bwu_manager.h" #include "connections/implementation/client_proxy.h" #include "connections/implementation/encryption_runner.h" #include "connections/implementation/offline_frames.h" #include "connections/listeners.h" #include "connections/params.h" #include "internal/platform/byte_array.h" #include "internal/platform/exception.h" #include "internal/platform/medium_environment.h" #include "internal/platform/count_down_latch.h" #include "internal/platform/pipe.h" #include "proto/connections_enums.pb.h" namespace location { namespace nearby { namespace connections { namespace { using ::location::nearby::proto::connections::Medium; using ::testing::_; using ::testing::AtLeast; using ::testing::Invoke; using ::testing::MockFunction; using ::testing::Return; using ::testing::StrictMock; constexpr std::array kFakeMacAddress = {'a', 'b', 'c', 'd', 'e', 'f'}; constexpr BooleanMediumSelector kTestCases[] = { BooleanMediumSelector{}, BooleanMediumSelector{ .ble = true, }, BooleanMediumSelector{ .bluetooth = true, }, BooleanMediumSelector{ .wifi_lan = true, }, BooleanMediumSelector{ .bluetooth = true, .ble = true, }, BooleanMediumSelector{ .bluetooth = true, .wifi_lan = true, }, BooleanMediumSelector{ .ble = true, .wifi_lan = true, }, BooleanMediumSelector{ .bluetooth = true, .ble = true, .wifi_lan = true, }, }; class MockEndpointChannel : public BaseEndpointChannel { public: explicit MockEndpointChannel(Pipe* reader, Pipe* writer) : BaseEndpointChannel("service_id", "channel", &reader->GetInputStream(), &writer->GetOutputStream()) {} ExceptionOr DoRead() { return BaseEndpointChannel::Read(); } Exception DoWrite(const ByteArray& data) { if (broken_write_) { return {Exception::kFailed}; } return BaseEndpointChannel::Write(data); } absl::Time DoGetLastReadTimestamp() { return BaseEndpointChannel::GetLastReadTimestamp(); } MOCK_METHOD(ExceptionOr, Read, (), (override)); MOCK_METHOD(Exception, Write, (const ByteArray& data), (override)); MOCK_METHOD(void, CloseImpl, (), (override)); MOCK_METHOD(proto::connections::Medium, GetMedium, (), (const override)); MOCK_METHOD(std::string, GetType, (), (const override)); MOCK_METHOD(std::string, GetName, (), (const override)); MOCK_METHOD(bool, IsPaused, (), (const override)); MOCK_METHOD(void, Pause, (), (override)); MOCK_METHOD(void, Resume, (), (override)); MOCK_METHOD(absl::Time, GetLastReadTimestamp, (), (const override)); bool broken_write_{false}; }; class MockPcpHandler : public BasePcpHandler { public: using DiscoveredEndpoint = BasePcpHandler::DiscoveredEndpoint; MockPcpHandler(Mediums* m, EndpointManager* em, EndpointChannelManager* ecm, BwuManager* bwu) : BasePcpHandler(m, em, ecm, bwu, Pcp::kP2pCluster) {} // Expose protected inner types of a base type for mocking. using BasePcpHandler::ConnectImplResult; using BasePcpHandler::DiscoveredEndpoint; using BasePcpHandler::StartOperationResult; MOCK_METHOD(Strategy, GetStrategy, (), (const override)); MOCK_METHOD(Pcp, GetPcp, (), (const override)); MOCK_METHOD(bool, HasOutgoingConnections, (ClientProxy * client), (const, override)); MOCK_METHOD(bool, HasIncomingConnections, (ClientProxy * client), (const, override)); MOCK_METHOD(bool, CanSendOutgoingConnection, (ClientProxy * client), (const, override)); MOCK_METHOD(bool, CanReceiveIncomingConnection, (ClientProxy * client), (const, override)); MOCK_METHOD(StartOperationResult, StartAdvertisingImpl, (ClientProxy * client, const std::string& service_id, const std::string& local_endpoint_id, const ByteArray& local_endpoint_info, const AdvertisingOptions& advertising_options), (override)); MOCK_METHOD(Status, StopAdvertisingImpl, (ClientProxy * client), (override)); MOCK_METHOD(StartOperationResult, StartDiscoveryImpl, (ClientProxy * client, const std::string& service_id, const DiscoveryOptions& discovery_options), (override)); MOCK_METHOD(Status, StopDiscoveryImpl, (ClientProxy * client), (override)); MOCK_METHOD(Status, InjectEndpointImpl, (ClientProxy * client, const std::string& service_id, const OutOfBandConnectionMetadata& metadata), (override)); MOCK_METHOD(ConnectImplResult, ConnectImpl, (ClientProxy * client, DiscoveredEndpoint* endpoint), (override)); MOCK_METHOD(proto::connections::Medium, GetDefaultUpgradeMedium, (), (override)); std::vector GetConnectionMediumsByPriority() override { return std::vector{ proto::connections::WIFI_LAN, proto::connections::WEB_RTC, proto::connections::BLUETOOTH, proto::connections::BLE}; } // Mock adapters for protected non-virtual methods of a base class. void OnEndpointFound(ClientProxy* client, std::shared_ptr endpoint) ABSL_NO_THREAD_SAFETY_ANALYSIS { BasePcpHandler::OnEndpointFound(client, std::move(endpoint)); } void OnEndpointLost(ClientProxy* client, const DiscoveredEndpoint& endpoint) ABSL_NO_THREAD_SAFETY_ANALYSIS { BasePcpHandler::OnEndpointLost(client, endpoint); } std::vector GetDiscoveredEndpoints( const std::string& endpoint_id) { return BasePcpHandler::GetDiscoveredEndpoints(endpoint_id); } std::vector GetDiscoveryMediums( ClientProxy* client) { auto allowed = client->GetDiscoveryOptions().CompatibleOptions().allowed; return GetMediumsFromSelector(allowed); } std::vector GetMediumsFromSelector( BooleanMediumSelector allowed) { return allowed.GetMediums(true); } }; class MockContext { public: explicit MockContext(std::atomic_int* destroyed = nullptr) : destroyed_{destroyed} {} MockContext(MockContext&& other) { *this = std::move(other); } MockContext& operator=(MockContext&& other) { destroyed_ = other.destroyed_; other.destroyed_ = nullptr; return *this; } ~MockContext() { if (destroyed_) (*destroyed_)++; } private: std::atomic_int* destroyed_; }; struct MockDiscoveredEndpoint : public MockPcpHandler::DiscoveredEndpoint { MockDiscoveredEndpoint(DiscoveredEndpoint endpoint, MockContext context) : DiscoveredEndpoint(std::move(endpoint)), context(std::move(context)) {} MockContext context; }; class BasePcpHandlerTest : public ::testing::TestWithParam { protected: struct MockConnectionListener { StrictMock> initiated_cb; StrictMock> accepted_cb; StrictMock> rejected_cb; StrictMock> disconnected_cb; StrictMock> bandwidth_changed_cb; }; struct MockDiscoveryListener { StrictMock> endpoint_found_cb; StrictMock> endpoint_lost_cb; StrictMock< MockFunction> endpoint_distance_changed_cb; }; void StartAdvertising(ClientProxy* client, MockPcpHandler* pcp_handler, BooleanMediumSelector allowed = GetParam()) { std::string service_id{"service"}; AdvertisingOptions advertising_options{ { Strategy::kP2pCluster, allowed, }, true, // auto_upgrade_bandwidth true, // enforce_topology_constraints }; ConnectionRequestInfo info{ .endpoint_info = ByteArray{"remote_endpoint_name"}, .listener = connection_listener_, }; EXPECT_CALL(*pcp_handler, StartAdvertisingImpl(client, service_id, _, info.endpoint_info, _)) .WillOnce(Return(MockPcpHandler::StartOperationResult{ .status = {Status::kSuccess}, .mediums = pcp_handler->GetMediumsFromSelector(allowed), })); EXPECT_EQ(pcp_handler->StartAdvertising(client, service_id, advertising_options, info), Status{Status::kSuccess}); EXPECT_TRUE(client->IsAdvertising()); } void StartDiscovery(ClientProxy* client, MockPcpHandler* pcp_handler, BooleanMediumSelector allowed = GetParam()) { std::string service_id{"service"}; DiscoveryOptions discovery_options{ { Strategy::kP2pCluster, allowed, }, true, // auto_upgrade_bandwidth true, // enforce_topology_constraints 5000, // keep_alive_interval_millis 3000, // keep_alive_timeout_millis }; EXPECT_CALL(*pcp_handler, StartDiscoveryImpl(client, service_id, _)) .WillOnce(Return(MockPcpHandler::StartOperationResult{ .status = {Status::kSuccess}, .mediums = pcp_handler->GetMediumsFromSelector(allowed), })); EXPECT_EQ(pcp_handler->StartDiscovery(client, service_id, discovery_options, discovery_listener_), Status{Status::kSuccess}); EXPECT_TRUE(client->IsDiscovering()); } std::pair, std::unique_ptr> SetupConnection(Pipe& pipe_a, Pipe& pipe_b, proto::connections::Medium medium) { // NOLINT auto channel_a = std::make_unique(&pipe_b, &pipe_a); auto channel_b = std::make_unique(&pipe_a, &pipe_b); // On initiator (A) side, we drop the first write, since this is a // connection establishment packet, and we don't have the peer entity, just // the peer channel. The rest of the exchange must happen for the benefit of // DH key exchange. EXPECT_CALL(*channel_a, Read()) .WillRepeatedly(Invoke( [channel = channel_a.get()]() { return channel->DoRead(); })); EXPECT_CALL(*channel_a, Write(_)) .WillOnce(Return(Exception{Exception::kSuccess})) .WillRepeatedly( Invoke([channel = channel_a.get()](const ByteArray& data) { return channel->DoWrite(data); })); EXPECT_CALL(*channel_a, GetMedium).WillRepeatedly(Return(medium)); EXPECT_CALL(*channel_a, GetLastReadTimestamp) .WillRepeatedly(Return(absl::Now())); EXPECT_CALL(*channel_a, IsPaused).WillRepeatedly(Return(false)); EXPECT_CALL(*channel_b, Read()) .WillRepeatedly(Invoke( [channel = channel_b.get()]() { return channel->DoRead(); })); EXPECT_CALL(*channel_b, Write(_)) .WillRepeatedly( Invoke([channel = channel_b.get()](const ByteArray& data) { return channel->DoWrite(data); })); EXPECT_CALL(*channel_b, GetMedium).WillRepeatedly(Return(medium)); EXPECT_CALL(*channel_b, GetLastReadTimestamp) .WillRepeatedly(Return(absl::Now())); EXPECT_CALL(*channel_b, IsPaused).WillRepeatedly(Return(false)); return std::make_pair(std::move(channel_a), std::move(channel_b)); } void RequestConnection(const std::string& endpoint_id, std::unique_ptr channel_a, MockEndpointChannel* channel_b, ClientProxy* client, MockPcpHandler* pcp_handler, proto::connections::Medium connect_medium, std::atomic_int* flag = nullptr, Status expected_result = {Status::kSuccess}) { ConnectionRequestInfo info{ .endpoint_info = ByteArray{"ABCD"}, .listener = connection_listener_, }; ConnectionOptions connection_options{ .remote_bluetooth_mac_address = ByteArray{std::string("\x12\x34\x56\x78\x9a\xbc")}, .keep_alive_interval_millis = FeatureFlags::GetInstance().GetFlags().keep_alive_interval_millis, .keep_alive_timeout_millis = FeatureFlags::GetInstance().GetFlags().keep_alive_timeout_millis, }; EXPECT_CALL(mock_discovery_listener_.endpoint_found_cb, Call); EXPECT_CALL(*pcp_handler, CanSendOutgoingConnection) .WillRepeatedly(Return(true)); EXPECT_CALL(*pcp_handler, GetStrategy) .WillRepeatedly(Return(Strategy::kP2pCluster)); if (expected_result == Status{Status::kSuccess}) { EXPECT_CALL(mock_connection_listener_.initiated_cb, Call).Times(1); } // Simulate successful discovery. auto encryption_runner = std::make_unique(); auto allowed_mediums = pcp_handler->GetDiscoveryMediums(client); EXPECT_CALL(*pcp_handler, ConnectImpl) .WillOnce(Invoke([&channel_a, connect_medium]( ClientProxy* client, MockPcpHandler::DiscoveredEndpoint* endpoint) { return MockPcpHandler::ConnectImplResult{ .medium = connect_medium, .status = {Status::kSuccess}, .endpoint_channel = std::move(channel_a), }; })); for (const auto& discovered_medium : allowed_mediums) { pcp_handler->OnEndpointFound( client, std::make_shared(MockDiscoveredEndpoint{ { endpoint_id, info.endpoint_info, "service", discovered_medium, WebRtcState::kUndefined, }, MockContext{flag}, })); } auto other_client = std::make_unique(); // Run peer crypto in advance, if channel_b is provided. // Otherwise stay in not-encrypted state. if (channel_b != nullptr) { encryption_runner->StartServer(other_client.get(), endpoint_id, channel_b, {}); } EXPECT_EQ(pcp_handler->RequestConnection(client, endpoint_id, info, connection_options), expected_result); NEARBY_LOG(INFO, "Stopping Encryption Runner"); } Pipe pipe_a_; Pipe pipe_b_; MockConnectionListener mock_connection_listener_; MockDiscoveryListener mock_discovery_listener_; ConnectionListener connection_listener_{ .initiated_cb = mock_connection_listener_.initiated_cb.AsStdFunction(), .accepted_cb = mock_connection_listener_.accepted_cb.AsStdFunction(), .rejected_cb = mock_connection_listener_.rejected_cb.AsStdFunction(), .disconnected_cb = mock_connection_listener_.disconnected_cb.AsStdFunction(), .bandwidth_changed_cb = mock_connection_listener_.bandwidth_changed_cb.AsStdFunction(), }; DiscoveryListener discovery_listener_{ .endpoint_found_cb = mock_discovery_listener_.endpoint_found_cb.AsStdFunction(), .endpoint_lost_cb = mock_discovery_listener_.endpoint_lost_cb.AsStdFunction(), .endpoint_distance_changed_cb = mock_discovery_listener_.endpoint_distance_changed_cb.AsStdFunction(), }; MediumEnvironment& env_ = MediumEnvironment::Instance(); }; TEST_P(BasePcpHandlerTest, ConstructorDestructorWorks) { env_.Start(); Mediums m; EndpointChannelManager ecm; EndpointManager em(&ecm); BwuManager bwu(m, em, ecm, {}, {}); MockPcpHandler pcp_handler(&m, &em, &ecm, &bwu); SUCCEED(); bwu.Shutdown(); env_.Stop(); } TEST_P(BasePcpHandlerTest, StartAdvertisingChangesState) { env_.Start(); ClientProxy client; Mediums m; EndpointChannelManager ecm; EndpointManager em(&ecm); BwuManager bwu(m, em, ecm, {}, {}); MockPcpHandler pcp_handler(&m, &em, &ecm, &bwu); StartAdvertising(&client, &pcp_handler); bwu.Shutdown(); env_.Stop(); } TEST_P(BasePcpHandlerTest, StopAdvertisingChangesState) { env_.Start(); ClientProxy client; Mediums m; EndpointChannelManager ecm; EndpointManager em(&ecm); BwuManager bwu(m, em, ecm, {}, {}); MockPcpHandler pcp_handler(&m, &em, &ecm, &bwu); StartAdvertising(&client, &pcp_handler); EXPECT_CALL(pcp_handler, StopAdvertisingImpl(&client)).Times(1); EXPECT_TRUE(client.IsAdvertising()); pcp_handler.StopAdvertising(&client); EXPECT_FALSE(client.IsAdvertising()); bwu.Shutdown(); env_.Stop(); } TEST_P(BasePcpHandlerTest, StartDiscoveryChangesState) { env_.Start(); ClientProxy client; Mediums m; EndpointChannelManager ecm; EndpointManager em(&ecm); BwuManager bwu(m, em, ecm, {}, {}); MockPcpHandler pcp_handler(&m, &em, &ecm, &bwu); StartDiscovery(&client, &pcp_handler); bwu.Shutdown(); env_.Stop(); } TEST_P(BasePcpHandlerTest, StopDiscoveryChangesState) { env_.Start(); ClientProxy client; Mediums m; EndpointChannelManager ecm; EndpointManager em(&ecm); BwuManager bwu(m, em, ecm, {}, {}); MockPcpHandler pcp_handler(&m, &em, &ecm, &bwu); StartDiscovery(&client, &pcp_handler); EXPECT_CALL(pcp_handler, StopDiscoveryImpl(&client)).Times(1); EXPECT_TRUE(client.IsDiscovering()); pcp_handler.StopDiscovery(&client); EXPECT_FALSE(client.IsDiscovering()); bwu.Shutdown(); env_.Stop(); } TEST_P(BasePcpHandlerTest, RequestConnectionChangesState) { env_.Start(); std::string endpoint_id{"1234"}; ClientProxy client; Mediums m; EndpointChannelManager ecm; EndpointManager em(&ecm); BwuManager bwu(m, em, ecm, {}, {}); MockPcpHandler pcp_handler(&m, &em, &ecm, &bwu); StartDiscovery(&client, &pcp_handler); auto mediums = pcp_handler.GetDiscoveryMediums(&client); auto connect_medium = mediums[mediums.size() - 1]; auto channel_pair = SetupConnection(pipe_a_, pipe_b_, connect_medium); auto& channel_a = channel_pair.first; auto& channel_b = channel_pair.second; EXPECT_CALL(*channel_a, CloseImpl).Times(1); EXPECT_CALL(*channel_b, CloseImpl).Times(1); EXPECT_CALL(mock_connection_listener_.rejected_cb, Call).Times(AtLeast(0)); RequestConnection(endpoint_id, std::move(channel_a), channel_b.get(), &client, &pcp_handler, connect_medium); NEARBY_LOG(INFO, "RequestConnection complete"); channel_b->Close(); bwu.Shutdown(); pcp_handler.DisconnectFromEndpointManager(); env_.Stop(); } TEST_P(BasePcpHandlerTest, IoError_RequestConnectionFails) { env_.Start(); std::string endpoint_id{"1234"}; ClientProxy client; Mediums m; EndpointChannelManager ecm; EndpointManager em(&ecm); BwuManager bwu(m, em, ecm, {}, {}); MockPcpHandler pcp_handler(&m, &em, &ecm, &bwu); StartDiscovery(&client, &pcp_handler); auto mediums = pcp_handler.GetDiscoveryMediums(&client); auto connect_medium = mediums[mediums.size() - 1]; auto channel_pair = SetupConnection(pipe_a_, pipe_b_, connect_medium); auto& channel_a = channel_pair.first; auto& channel_b = channel_pair.second; EXPECT_CALL(*channel_a, CloseImpl).Times(AtLeast(1)); EXPECT_CALL(*channel_b, CloseImpl).Times(AtLeast(1)); channel_b->broken_write_ = true; EXPECT_CALL(mock_connection_listener_.rejected_cb, Call).Times(AtLeast(0)); RequestConnection(endpoint_id, std::move(channel_a), channel_b.get(), &client, &pcp_handler, connect_medium, nullptr, {Status::kEndpointIoError}); NEARBY_LOG(INFO, "RequestConnection complete"); channel_b->Close(); bwu.Shutdown(); pcp_handler.DisconnectFromEndpointManager(); env_.Stop(); } TEST_P(BasePcpHandlerTest, AcceptConnectionChangesState) { env_.Start(); std::string endpoint_id{"1234"}; ClientProxy client; Mediums m; EndpointChannelManager ecm; EndpointManager em(&ecm); BwuManager bwu(m, em, ecm, {}, {}); MockPcpHandler pcp_handler(&m, &em, &ecm, &bwu); StartDiscovery(&client, &pcp_handler); auto mediums = pcp_handler.GetDiscoveryMediums(&client); auto connect_medium = mediums[mediums.size() - 1]; auto channel_pair = SetupConnection(pipe_a_, pipe_b_, connect_medium); auto& channel_a = channel_pair.first; auto& channel_b = channel_pair.second; EXPECT_CALL(*channel_a, CloseImpl).Times(1); EXPECT_CALL(*channel_b, CloseImpl).Times(1); RequestConnection(endpoint_id, std::move(channel_a), channel_b.get(), &client, &pcp_handler, connect_medium); NEARBY_LOG(INFO, "Attempting to accept connection: id=%s", endpoint_id.c_str()); EXPECT_EQ(pcp_handler.AcceptConnection(&client, endpoint_id, {}), Status{Status::kSuccess}); EXPECT_CALL(mock_connection_listener_.rejected_cb, Call).Times(AtLeast(0)); NEARBY_LOGS(INFO) << "Closing connection: id=" << endpoint_id; channel_b->Close(); bwu.Shutdown(); pcp_handler.DisconnectFromEndpointManager(); env_.Stop(); } TEST_P(BasePcpHandlerTest, RejectConnectionChangesState) { env_.Start(); std::string endpoint_id{"1234"}; ClientProxy client; Mediums m; EndpointChannelManager ecm; EndpointManager em(&ecm); BwuManager bwu(m, em, ecm, {}, {}); MockPcpHandler pcp_handler(&m, &em, &ecm, &bwu); StartDiscovery(&client, &pcp_handler); auto mediums = pcp_handler.GetDiscoveryMediums(&client); auto connect_medium = mediums[mediums.size() - 1]; auto channel_pair = SetupConnection(pipe_a_, pipe_b_, connect_medium); auto& channel_b = channel_pair.second; EXPECT_CALL(mock_connection_listener_.rejected_cb, Call).Times(1); RequestConnection(endpoint_id, std::move(channel_pair.first), channel_b.get(), &client, &pcp_handler, connect_medium); NEARBY_LOGS(INFO) << "Attempting to reject connection: id=" << endpoint_id; EXPECT_EQ(pcp_handler.RejectConnection(&client, endpoint_id), Status{Status::kSuccess}); NEARBY_LOGS(INFO) << "Closing connection: id=" << endpoint_id; channel_b->Close(); bwu.Shutdown(); pcp_handler.DisconnectFromEndpointManager(); env_.Stop(); } TEST_P(BasePcpHandlerTest, OnIncomingFrameChangesState) { env_.Start(); std::string endpoint_id{"1234"}; ClientProxy client; Mediums m; EndpointChannelManager ecm; EndpointManager em(&ecm); BwuManager bwu(m, em, ecm, {}, {}); MockPcpHandler pcp_handler(&m, &em, &ecm, &bwu); StartDiscovery(&client, &pcp_handler); auto mediums = pcp_handler.GetDiscoveryMediums(&client); auto connect_medium = mediums[mediums.size() - 1]; auto channel_pair = SetupConnection(pipe_a_, pipe_b_, connect_medium); auto& channel_a = channel_pair.first; auto& channel_b = channel_pair.second; EXPECT_CALL(*channel_a, CloseImpl).Times(1); EXPECT_CALL(*channel_b, CloseImpl).Times(1); RequestConnection(endpoint_id, std::move(channel_a), channel_b.get(), &client, &pcp_handler, connect_medium); NEARBY_LOGS(INFO) << "Attempting to accept connection: id=" << endpoint_id; EXPECT_CALL(mock_connection_listener_.accepted_cb, Call).Times(1); EXPECT_CALL(mock_connection_listener_.disconnected_cb, Call) .Times(AtLeast(0)); EXPECT_EQ(pcp_handler.AcceptConnection(&client, endpoint_id, {}), Status{Status::kSuccess}); NEARBY_LOG(INFO, "Simulating remote accept: id=%s", endpoint_id.c_str()); auto frame = parser::FromBytes(parser::ForConnectionResponse(Status::kSuccess)); pcp_handler.OnIncomingFrame(frame.result(), endpoint_id, &client, connect_medium); NEARBY_LOGS(INFO) << "Closing connection: id=" << endpoint_id; channel_b->Close(); bwu.Shutdown(); pcp_handler.DisconnectFromEndpointManager(); env_.Stop(); } TEST_P(BasePcpHandlerTest, DestructorIsCalledOnProtocolEndpoint) { env_.Start(); std::atomic_int destroyed_flag = 0; int mediums_count = 0; { std::string endpoint_id{"1234"}; ClientProxy client; Mediums m; EndpointChannelManager ecm; EndpointManager em(&ecm); BwuManager bwu(m, em, ecm, {}, {}); MockPcpHandler pcp_handler(&m, &em, &ecm, &bwu); StartDiscovery(&client, &pcp_handler); auto mediums = pcp_handler.GetDiscoveryMediums(&client); auto connect_medium = mediums[mediums.size() - 1]; auto channel_pair = SetupConnection(pipe_a_, pipe_b_, connect_medium); auto& channel_a = channel_pair.first; auto& channel_b = channel_pair.second; EXPECT_CALL(*channel_a, CloseImpl).Times(1); EXPECT_CALL(*channel_b, CloseImpl).Times(1); RequestConnection(endpoint_id, std::move(channel_a), channel_b.get(), &client, &pcp_handler, connect_medium, &destroyed_flag); mediums_count = mediums.size(); NEARBY_LOG(INFO, "Attempting to accept connection: id=%s", endpoint_id.c_str()); EXPECT_EQ(pcp_handler.AcceptConnection(&client, endpoint_id, {}), Status{Status::kSuccess}); EXPECT_CALL(mock_connection_listener_.rejected_cb, Call).Times(AtLeast(0)); NEARBY_LOG(INFO, "Closing connection: id=%s", endpoint_id.c_str()); channel_b->Close(); bwu.Shutdown(); pcp_handler.DisconnectFromEndpointManager(); } EXPECT_EQ(destroyed_flag.load(), mediums_count); env_.Stop(); } TEST_P(BasePcpHandlerTest, MultipleMediumsProduceSingleEndpointLostEvent) { env_.Start(); BooleanMediumSelector allowed = GetParam(); if (allowed.Count(true) < 2) { // Ignore single-medium test cases, and implicit "all mediums" case. SUCCEED(); return; } std::atomic_int destroyed_flag = 0; int mediums_count = 0; { std::string endpoint_id{"1234"}; ClientProxy client; Mediums m; EndpointChannelManager ecm; EndpointManager em(&ecm); BwuManager bwu(m, em, ecm, {}, {}); MockPcpHandler pcp_handler(&m, &em, &ecm, &bwu); StartDiscovery(&client, &pcp_handler); auto mediums = pcp_handler.GetDiscoveryMediums(&client); auto connect_medium = mediums[mediums.size() - 1]; auto channel_pair = SetupConnection(pipe_a_, pipe_b_, connect_medium); auto& channel_a = channel_pair.first; auto& channel_b = channel_pair.second; EXPECT_CALL(*channel_a, CloseImpl).Times(1); EXPECT_CALL(*channel_b, CloseImpl).Times(1); EXPECT_CALL(mock_discovery_listener_.endpoint_lost_cb, Call).Times(1); RequestConnection(endpoint_id, std::move(channel_a), channel_b.get(), &client, &pcp_handler, connect_medium, &destroyed_flag); auto allowed_mediums = pcp_handler.GetDiscoveryMediums(&client); mediums_count = allowed_mediums.size(); NEARBY_LOG(INFO, "Attempting to accept connection: id=%s", endpoint_id.c_str()); EXPECT_EQ(pcp_handler.AcceptConnection(&client, endpoint_id, {}), Status{Status::kSuccess}); EXPECT_CALL(mock_connection_listener_.rejected_cb, Call).Times(AtLeast(0)); for (const auto* endpoint : pcp_handler.GetDiscoveredEndpoints(endpoint_id)) { pcp_handler.OnEndpointLost(&client, *endpoint); } NEARBY_LOG(INFO, "Closing connection: id=%s", endpoint_id.c_str()); channel_b->Close(); bwu.Shutdown(); pcp_handler.DisconnectFromEndpointManager(); } EXPECT_EQ(destroyed_flag.load(), mediums_count); env_.Stop(); } INSTANTIATE_TEST_SUITE_P(ParameterizedBasePcpHandlerTest, BasePcpHandlerTest, ::testing::ValuesIn(kTestCases)); TEST_F(BasePcpHandlerTest, InjectEndpoint) { env_.Start(); std::string service_id{"service"}; std::string endpoint_id{"ABCD"}; ClientProxy client; Mediums m; EndpointChannelManager ecm; EndpointManager em(&ecm); BwuManager bwu(m, em, ecm, {}, {}); MockPcpHandler pcp_handler(&m, &em, &ecm, &bwu); BooleanMediumSelector allowed{ .bluetooth = true, }; DiscoveryOptions discovery_options{ { Strategy::kP2pPointToPoint, allowed, }, false, // auto_upgrade_bandwidth; false, // enforce_topology_constraints; 0, // keep_alive_interval_millis; 0, // keep_alive_timeout_millis; }; EXPECT_CALL(mock_discovery_listener_.endpoint_found_cb, Call); EXPECT_CALL(pcp_handler, StartDiscoveryImpl(&client, service_id, _)) .WillOnce(Return(MockPcpHandler::StartOperationResult{ .status = {Status::kSuccess}, .mediums = allowed.GetMediums(true), })); EXPECT_EQ(pcp_handler.StartDiscovery(&client, service_id, discovery_options, discovery_listener_), Status{Status::kSuccess}); EXPECT_TRUE(client.IsDiscovering()); EXPECT_CALL(pcp_handler, InjectEndpointImpl(&client, service_id, _)) .WillOnce(Invoke([&pcp_handler, &endpoint_id]( ClientProxy* client, const std::string& service_id, const OutOfBandConnectionMetadata& metadata) { pcp_handler.OnEndpointFound( client, std::make_shared(MockDiscoveredEndpoint{ { endpoint_id, /*endpoint_info=*/ByteArray{"ABCD"}, service_id, Medium::BLUETOOTH, WebRtcState::kUndefined, }, MockContext{nullptr}, })); return Status{Status::kSuccess}; })); pcp_handler.InjectEndpoint( &client, service_id, OutOfBandConnectionMetadata{ .medium = Medium::BLUETOOTH, .remote_bluetooth_mac_address = ByteArray(kFakeMacAddress), }); bwu.Shutdown(); env_.Stop(); } } // namespace } // namespace connections } // namespace nearby } // namespace location