From fa7c8a2ec8204d9ef4ef5e4946dafb36279491c5 Mon Sep 17 00:00:00 2001 From: hai007 Date: Mon, 5 Dec 2022 17:12:22 -0800 Subject: [PATCH] WIFI Direct implementation (2) Part 2: Connection common code for WIFI Direct PiperOrigin-RevId: 493153750 --- Package.swift | 1 + connections/implementation/mediums/BUILD | 3 + connections/implementation/mediums/mediums.cc | 2 + connections/implementation/mediums/mediums.h | 5 + .../implementation/mediums/wifi_direct.cc | 282 ++++++++++++++++++ .../implementation/mediums/wifi_direct.h | 127 ++++++++ .../mediums/wifi_direct_test.cc | 189 ++++++++++++ 7 files changed, 609 insertions(+) create mode 100644 connections/implementation/mediums/wifi_direct.cc create mode 100644 connections/implementation/mediums/wifi_direct.h create mode 100644 connections/implementation/mediums/wifi_direct_test.cc diff --git a/Package.swift b/Package.swift index d517a10a..cb180fb4 100644 --- a/Package.swift +++ b/Package.swift @@ -449,6 +449,7 @@ let package = Package( "connections/implementation/mediums/webrtc_test.cc", "connections/implementation/mediums/lost_entity_tracker_test.cc", "connections/implementation/mediums/bluetooth_radio_test.cc", + "connections/implementation/mediums/wifi_direct_test.cc", "connections/implementation/mediums/wifi_hotspot_test.cc", "connections/implementation/mediums/wifi_test.cc", "connections/implementation/endpoint_channel_manager_test.cc", diff --git a/connections/implementation/mediums/BUILD b/connections/implementation/mediums/BUILD index 021d488d..44c2c9ec 100644 --- a/connections/implementation/mediums/BUILD +++ b/connections/implementation/mediums/BUILD @@ -22,6 +22,7 @@ cc_library( "bluetooth_radio.cc", "mediums.cc", "webrtc_stub.cc", + "wifi_direct.cc", "wifi_hotspot.cc", "wifi_lan.cc", ], @@ -33,6 +34,7 @@ cc_library( "mediums.h", "webrtc_stub.h", "wifi.h", + "wifi_direct.h", "wifi_hotspot.h", "wifi_lan.h", ], @@ -99,6 +101,7 @@ cc_test( "bluetooth_classic_test.cc", "bluetooth_radio_test.cc", "lost_entity_tracker_test.cc", + "wifi_direct_test.cc", "wifi_hotspot_test.cc", "wifi_lan_test.cc", "wifi_test.cc", diff --git a/connections/implementation/mediums/mediums.cc b/connections/implementation/mediums/mediums.cc index bc686c1b..30f52dc6 100644 --- a/connections/implementation/mediums/mediums.cc +++ b/connections/implementation/mediums/mediums.cc @@ -32,6 +32,8 @@ WifiLan& Mediums::GetWifiLan() { return wifi_lan_; } WifiHotspot& Mediums::GetWifiHotspot() { return wifi_hotspot_; } +WifiDirect& Mediums::GetWifiDirect() { return wifi_direct_; } + mediums::WebRtc& Mediums::GetWebRtc() { return webrtc_; } } // namespace connections diff --git a/connections/implementation/mediums/mediums.h b/connections/implementation/mediums/mediums.h index aa4743c8..a7c3aecc 100644 --- a/connections/implementation/mediums/mediums.h +++ b/connections/implementation/mediums/mediums.h @@ -26,6 +26,7 @@ #endif #include "connections/implementation/mediums/wifi.h" #include "connections/implementation/mediums/wifi_hotspot.h" +#include "connections/implementation/mediums/wifi_direct.h" #include "connections/implementation/mediums/wifi_lan.h" namespace location { @@ -59,6 +60,9 @@ class Mediums { // Returns a handle to the Wifi-Hotspot medium. WifiHotspot& GetWifiHotspot(); + // Returns a handle to the Wifi-Direct medium. + WifiDirect& GetWifiDirect(); + // Returns a handle to the WebRtc medium. mediums::WebRtc& GetWebRtc(); @@ -78,6 +82,7 @@ class Mediums { Wifi wifi_; WifiLan wifi_lan_; WifiHotspot wifi_hotspot_; + WifiDirect wifi_direct_; mediums::WebRtc webrtc_; }; diff --git a/connections/implementation/mediums/wifi_direct.cc b/connections/implementation/mediums/wifi_direct.cc new file mode 100644 index 00000000..c5755d9d --- /dev/null +++ b/connections/implementation/mediums/wifi_direct.cc @@ -0,0 +1,282 @@ +// Copyright 2022 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/mediums/wifi_direct.h" + +#include +#include + +#include "internal/platform/logging.h" + +namespace location { +namespace nearby { +namespace connections { + +WifiDirect::~WifiDirect() { + while (!server_sockets_.empty()) { + StopAcceptingConnections(server_sockets_.begin()->first); + } + if (is_go_started_) StopWifiDirect(); + if (is_connected_to_go_) DisconnectWifiDirect(); + + // All the AcceptLoopRunnable objects in here should already have gotten an + // opportunity to shut themselves down cleanly in the calls to + // StopAcceptingConnections() above. + accept_loops_runner_.Shutdown(); +} + +bool WifiDirect::IsGOAvailable() const { + MutexLock lock(&mutex_); + + return IsGOAvailableLocked(); +} + +bool WifiDirect::IsGOAvailableLocked() const { + if (medium_.IsValid()) return medium_.IsInterfaceValid(); + return false; +} + +bool WifiDirect::IsGCAvailable() const { + MutexLock lock(&mutex_); + + return IsGCAvailableLocked(); +} + +bool WifiDirect::IsGCAvailableLocked() const { return medium_.IsValid(); } + +bool WifiDirect::IsGOStarted() { + MutexLock lock(&mutex_); + + return is_go_started_; +} + +// Call the Medium to start a softAP and send beacon for Client to scan and +// connect +bool WifiDirect::StartWifiDirect() { + MutexLock lock(&mutex_); + if (is_go_started_) { + NEARBY_LOGS(INFO) << "No need to start GO because it is already started."; + return true; + } + is_go_started_ = medium_.StartWifiDirect(); + return is_go_started_; +} + +bool WifiDirect::StopWifiDirect() { + MutexLock lock(&mutex_); + if (!is_go_started_) { + NEARBY_LOGS(INFO) << "No need to stop GO because it is not started."; + return true; + } + is_go_started_ = false; + medium_.StopWifiDirect(); + return true; +} + +bool WifiDirect::IsConnectedToGO() { + MutexLock lock(&mutex_); + + return is_connected_to_go_; +} + +bool WifiDirect::ConnectWifiDirect(const std::string& ssid, + const std::string& password) { + MutexLock lock(&mutex_); + if (is_connected_to_go_) { + NEARBY_LOGS(INFO) + << "No need to connect to GO because it is already connected."; + return true; + } + is_connected_to_go_ = medium_.ConnectWifiDirect(ssid, password); + return is_connected_to_go_; +} + +bool WifiDirect::DisconnectWifiDirect() { + MutexLock lock(&mutex_); + if (!is_connected_to_go_) { + NEARBY_LOGS(INFO) + << "No need to disconnect to GO because it is not connected."; + return true; + } + is_connected_to_go_ = false; + return medium_.DisconnectWifiDirect(); +} + +HotspotCredentials* WifiDirect::GetCredentials(absl::string_view service_id) { + MutexLock lock(&mutex_); + HotspotCredentials* crendential = medium_.GetCredential(); + CHECK(crendential); + + const auto& it = server_sockets_.find(service_id); + if (it == server_sockets_.end()) { + NEARBY_LOGS(INFO) << "No server socket found for service_id:" << service_id + << ". Use default credentials"; + return crendential; + } + crendential->SetGateway(it->second.GetIPAddress()); + crendential->SetIPAddress(it->second.GetIPAddress()); + crendential->SetPort(it->second.GetPort()); + + return crendential; +} + +bool WifiDirect::StartAcceptingConnections( + const std::string& service_id, AcceptedConnectionCallback callback) { + MutexLock lock(&mutex_); + + if (service_id.empty()) { + NEARBY_LOGS(INFO) + << "Can not to start accepting WifiDirect GC's connections; " + "service_id is empty."; + return false; + } + + if (!IsGOAvailableLocked()) { + NEARBY_LOGS(INFO) + << "Can't start accepting WifiDirect GC's connections [service_id=" + << service_id << "]; WifiDirct GO is not available."; + return false; + } + + if (IsAcceptingConnectionsLocked(service_id)) { + NEARBY_LOGS(INFO) + << "Refusing to start accepting WifiDirect GC's connections [service=" + << service_id + << "]; WifiDirect GO server is already in-progress with the same name."; + return false; + } + + // "port=0" to let the platform to select an available port for the socket + WifiHotspotServerSocket server_socket = medium_.ListenForService(/*port=*/0); + if (!server_socket.IsValid()) { + NEARBY_LOGS(INFO) + << "Failed to start to listen on WifiDirect GO server for service_id=" + << service_id; + return false; + } + + // Mark the fact that there's an in-progress WifiDirect server accepting + // connections. + auto owned_server_socket = + server_sockets_.insert({service_id, std::move(server_socket)}) + .first->second; + + // Start the accept loop on a dedicated thread - this stays alive and + // listening for new incoming connections until StopAcceptingConnections() is + // invoked. + accept_loops_runner_.Execute( + "wifi-direct-accept", + [callback = std::move(callback), + server_socket = std::move(owned_server_socket), service_id]() mutable { + while (true) { + WifiHotspotSocket client_socket = server_socket.Accept(); + if (!client_socket.IsValid()) { + server_socket.Close(); + break; + } + callback.accepted_cb(service_id, std::move(client_socket)); + } + }); + + return true; +} + +bool WifiDirect::StopAcceptingConnections(const std::string& service_id) { + MutexLock lock(&mutex_); + + if (service_id.empty()) { + NEARBY_LOGS(INFO) + << "Unable to stop accepting WifiDirect GC's connections because " + "the service_id is empty."; + return false; + } + + const auto& it = server_sockets_.find(service_id); + if (it == server_sockets_.end()) { + NEARBY_LOGS(INFO) << "Can't stop accepting WifiDirect GC's connections for " + << service_id << " because it was never started."; + return false; + } + + // Closing the WifiDirectServerSocket will kick off the suicide of the thread + // in accept_loops_thread_pool_ that block on WifiDirectServerSocket.accept() + // That may take some time to complete, but there's no particular reason to + // wait around for it. + auto item = server_sockets_.extract(it); + + // Store a handle to the WifiDirectServerSocket, so we can use it after + // removing the entry from server_sockets_; making it scoped + // is a bonus that takes care of deallocation before we leave this method. + WifiHotspotServerSocket& listening_socket = item.mapped(); + + // Regardless of whether or not we fail to close the existing + // WifiDirectServerSocket, remove it from server_sockets_ so that it + // frees up this service for another round. + + // Finally, close the WifiDirectServerSocket. + if (!listening_socket.Close().Ok()) { + NEARBY_LOGS(INFO) + << "Failed to close WifiDirect server socket for service_id:" + << service_id; + return false; + } + + return true; +} + +bool WifiDirect::IsAcceptingConnections(const std::string& service_id) { + MutexLock lock(&mutex_); + return IsAcceptingConnectionsLocked(service_id); +} + +bool WifiDirect::IsAcceptingConnectionsLocked(const std::string& service_id) { + return server_sockets_.find(service_id) != server_sockets_.end(); +} + +WifiHotspotSocket WifiDirect::Connect(const std::string& service_id, + const std::string& ip_address, int port, + CancellationFlag* cancellation_flag) { + MutexLock lock(&mutex_); + // Socket to return. To allow for NRVO to work, it has to be a single object. + WifiHotspotSocket socket; + + if (service_id.empty()) { + NEARBY_LOGS(INFO) << "Refusing to create client WifiDirect socket because " + "service_id is empty."; + return socket; + } + + if (!IsGCAvailableLocked()) { + NEARBY_LOGS(INFO) << "Can't create WifiDirect client socket [service_id=" + << service_id << "]; WifiDirect GC isn't available."; + return socket; + } + + if (cancellation_flag->Cancelled()) { + NEARBY_LOGS(INFO) << "Can't create WifiDirect client socket due to cancel"; + return socket; + } + + socket = medium_.ConnectToService(ip_address, port, cancellation_flag); + if (!socket.IsValid()) { + NEARBY_LOGS(INFO) << "Failed to Connect via WifiDirect Server [service_id=" + << service_id << "]"; + } + + return socket; +} + +} // namespace connections +} // namespace nearby +} // namespace location diff --git a/connections/implementation/mediums/wifi_direct.h b/connections/implementation/mediums/wifi_direct.h new file mode 100644 index 00000000..ebb95ca7 --- /dev/null +++ b/connections/implementation/mediums/wifi_direct.h @@ -0,0 +1,127 @@ +// Copyright 2022 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. + +#ifndef CORE_INTERNAL_MEDIUMS_WIFI_DIRECT_H_ +#define CORE_INTERNAL_MEDIUMS_WIFI_DIRECT_H_ + +#include +#include +#include + +#include "internal/platform/multi_thread_executor.h" +#include "internal/platform/mutex.h" +#include "internal/platform/wifi_direct.h" +#include "internal/platform/wifi_hotspot.h" + +namespace location { +namespace nearby { +namespace connections { + +class WifiDirect { + public: + // Callback that is invoked when a new connection is accepted. + struct AcceptedConnectionCallback { + std::function + accepted_cb = DefaultCallback(); + }; + + WifiDirect() : is_go_started_(false), is_connected_to_go_(false) {} + ~WifiDirect(); + // Not copyable or movable + WifiDirect(const WifiDirect&) = delete; + WifiDirect& operator=(const WifiDirect&) = delete; + WifiDirect(WifiDirect&&) = delete; + WifiDirect& operator=(WifiDirect&&) = delete; + + // Returns true, if WifiDirect Group Owner is supported by a platform. + bool IsGOAvailable() const ABSL_LOCKS_EXCLUDED(mutex_); + // Returns true, if WifiDirect Group Client is supported by a platform. + bool IsGCAvailable() const ABSL_LOCKS_EXCLUDED(mutex_); + + // If WifiDirect Group Owner started + bool IsGOStarted() ABSL_LOCKS_EXCLUDED(mutex_); + // Start WifiDirect Group Owner. Returns true if AutoGO is successfully + // started. + bool StartWifiDirect() ABSL_LOCKS_EXCLUDED(mutex_); + // Stop WifiDirect Group Owner + bool StopWifiDirect() ABSL_LOCKS_EXCLUDED(mutex_); + + // If WifiDirect Group Client connects to Group Owner + bool IsConnectedToGO() ABSL_LOCKS_EXCLUDED(mutex_); + // WifiDirect Group Client request to connect to the Group Owner + bool ConnectWifiDirect(const std::string& ssid, const std::string& password) + ABSL_LOCKS_EXCLUDED(mutex_); + // WifiDirect Group Client request to disconnect from the Group Owner + bool DisconnectWifiDirect() ABSL_LOCKS_EXCLUDED(mutex_); + + // Starts a worker thread, creates a WifiDirect socket, associates it with a + // service id. + bool StartAcceptingConnections(const std::string& service_id, + AcceptedConnectionCallback callback) + ABSL_LOCKS_EXCLUDED(mutex_); + + // Closes socket corresponding to a service id. + bool StopAcceptingConnections(const std::string& service_id) + ABSL_LOCKS_EXCLUDED(mutex_); + + bool IsAcceptingConnections(const std::string& service_id) + ABSL_LOCKS_EXCLUDED(mutex_); + + // Establishes connection to WifiDirect service by ip address and port for + // bandwidth upgradation. + // Returns socket instance. On success, WifiDirectSocket.IsValid() return + // true. + WifiHotspotSocket Connect(const std::string& service_id, + const std::string& ip_address, int port, + CancellationFlag* cancellation_flag) + ABSL_LOCKS_EXCLUDED(mutex_); + + // Gets SoftAP ssid + password + ip address + gateway + port etc for remote + // services on the network to identify and connect to this service. + // Credential is for the currently-hosted WiFi SoftAP ServerSocket (if any). + HotspotCredentials* GetCredentials(absl::string_view service_id) + ABSL_LOCKS_EXCLUDED(mutex_); + + private: + mutable Mutex mutex_; + static constexpr int kMaxConcurrentAcceptLoops = 5; + + // Same as IsAvailable(), but must be called with mutex_ held. + bool IsGOAvailableLocked() const ABSL_EXCLUSIVE_LOCKS_REQUIRED(mutex_); + bool IsGCAvailableLocked() const ABSL_EXCLUSIVE_LOCKS_REQUIRED(mutex_); + // Same as IsAcceptingConnections(), but must be called with mutex_ held. + bool IsAcceptingConnectionsLocked(const std::string& service_id) + ABSL_EXCLUSIVE_LOCKS_REQUIRED(mutex_); + + bool is_go_started_ ABSL_GUARDED_BY(mutex_); + bool is_connected_to_go_ ABSL_GUARDED_BY(mutex_); + WifiDirectMedium medium_ ABSL_GUARDED_BY(mutex_); + + // A thread pool dedicated to running all the accept loops from + // StartAcceptingConnections(). + MultiThreadExecutor accept_loops_runner_{kMaxConcurrentAcceptLoops}; + + // A map of service_id -> ServerSocket. If map is non-empty, we + // are currently listening for incoming connections. + // WifiDirectServerSocket instances are used from accept_loops_runner_, + // and thus require pointer stability. + absl::flat_hash_map server_sockets_ + ABSL_GUARDED_BY(mutex_); +}; + +} // namespace connections +} // namespace nearby +} // namespace location + +#endif // CORE_INTERNAL_MEDIUMS_WIFI_DIRECT_H_ diff --git a/connections/implementation/mediums/wifi_direct_test.cc b/connections/implementation/mediums/wifi_direct_test.cc new file mode 100644 index 00000000..8b953132 --- /dev/null +++ b/connections/implementation/mediums/wifi_direct_test.cc @@ -0,0 +1,189 @@ + +// Copyright 2020 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/mediums/wifi_direct.h" + +#include + +#include "gtest/gtest.h" +#include "absl/time/clock.h" +#include "internal/platform/medium_environment.h" +#include "internal/platform/wifi_direct.h" +#include "internal/platform/wifi_hotspot_credential.h" + +namespace location { +namespace nearby { +namespace connections { +namespace { + +using FeatureFlags = FeatureFlags::Flags; + +constexpr FeatureFlags kTestCases[] = { + FeatureFlags{ + .enable_cancellation_flag = true, + }, + FeatureFlags{ + .enable_cancellation_flag = false, + }, +}; + +constexpr absl::string_view kServiceID{"com.google.location.nearby.apps.test"}; +constexpr absl::string_view kSsid{"Direct-357a2d8c"}; +constexpr absl::string_view kPassword{"12345678"}; +constexpr absl::string_view kIp = "123.234.23.1"; +constexpr const size_t kPort = 20; + +class WifiDirectTest : public testing::TestWithParam { + protected: + WifiDirectTest() { + env_.Stop(); + env_.Start(); + } + ~WifiDirectTest() override { env_.Stop(); } + + MediumEnvironment& env_{MediumEnvironment::Instance()}; +}; + +INSTANTIATE_TEST_SUITE_P(ParametrisedWifiDirectMediumTest, WifiDirectTest, + testing::ValuesIn(kTestCases)); + +TEST_F(WifiDirectTest, ConstructorDestructorWorks) { + WifiDirect wifi_direct_a, wifi_direct_b; + + EXPECT_NE(&wifi_direct_a, &wifi_direct_b); + EXPECT_TRUE(wifi_direct_a.IsGCAvailable()); + EXPECT_TRUE(wifi_direct_b.IsGCAvailable()); +} +TEST_F(WifiDirectTest, CanStartStopGO) { + std::string service_id(kServiceID); + WifiDirect wifi_direct_a; + + if (wifi_direct_a.IsGOAvailable()) { + EXPECT_FALSE(wifi_direct_a.IsGOStarted()); + EXPECT_TRUE(wifi_direct_a.StartWifiDirect()); + EXPECT_TRUE(wifi_direct_a.StartAcceptingConnections(service_id, {})); + EXPECT_TRUE(wifi_direct_a.IsGOStarted()); + EXPECT_TRUE(wifi_direct_a.StopWifiDirect()); + EXPECT_FALSE(wifi_direct_a.IsGOStarted()); + } else { + EXPECT_FALSE(wifi_direct_a.StartWifiDirect()); + } +} + +TEST_F(WifiDirectTest, GCCanConnectDisconnectGO) { + std::string ssid(kSsid); + std::string password(kPassword); + WifiDirect wifi_direct_a; + + EXPECT_FALSE(wifi_direct_a.ConnectWifiDirect(ssid, password)); + EXPECT_TRUE(wifi_direct_a.DisconnectWifiDirect()); +} + +TEST_P(WifiDirectTest, CanStartGOThatOtherConnect) { + FeatureFlags feature_flags = GetParam(); + env_.SetFeatureFlags(feature_flags); + + std::string service_id(kServiceID); + std::string ip(kIp); + WifiDirect wifi_direct_a, wifi_direct_b; + + EXPECT_FALSE(wifi_direct_b.IsConnectedToGO()); + EXPECT_TRUE(wifi_direct_a.StartWifiDirect()); + if (!wifi_direct_a.IsAcceptingConnections(service_id)) { + EXPECT_TRUE(wifi_direct_a.StartAcceptingConnections(service_id, {})); + } + + HotspotCredentials* wifi_direct_credentials = + wifi_direct_a.GetCredentials(service_id); + + EXPECT_TRUE( + wifi_direct_b.ConnectWifiDirect(wifi_direct_credentials->GetSSID(), + wifi_direct_credentials->GetPassword())); + EXPECT_TRUE(wifi_direct_b.IsConnectedToGO()); + + WifiHotspotSocket socket_client; + EXPECT_FALSE(socket_client.IsValid()); + + CancellationFlag flag; + socket_client = wifi_direct_b.Connect(service_id, ip, kPort, &flag); + EXPECT_FALSE(socket_client.IsValid()); + + socket_client = + wifi_direct_b.Connect(service_id, wifi_direct_credentials->GetGateway(), + wifi_direct_credentials->GetPort(), &flag); + EXPECT_TRUE(socket_client.IsValid()); + + EXPECT_TRUE(wifi_direct_b.DisconnectWifiDirect()); + EXPECT_FALSE(wifi_direct_b.IsConnectedToGO()); + EXPECT_TRUE(wifi_direct_a.StopWifiDirect()); +} + +TEST_P(WifiDirectTest, CanStartGOThatOtherCanCancelConnect) { + FeatureFlags feature_flags = GetParam(); + env_.SetFeatureFlags(feature_flags); + + std::string service_id(kServiceID); + std::string ip(kIp); + WifiDirect wifi_direct_a, wifi_direct_b; + + EXPECT_TRUE(wifi_direct_a.StartWifiDirect()); + if (!wifi_direct_a.IsAcceptingConnections(service_id)) { + EXPECT_TRUE(wifi_direct_a.StartAcceptingConnections(service_id, {})); + } + + HotspotCredentials* wifi_direct_credentials = + wifi_direct_a.GetCredentials(service_id); + + EXPECT_TRUE( + wifi_direct_b.ConnectWifiDirect(wifi_direct_credentials->GetSSID(), + wifi_direct_credentials->GetPassword())); + + WifiHotspotSocket socket_client; + EXPECT_FALSE(socket_client.IsValid()); + + CancellationFlag flag(true); + socket_client = + wifi_direct_b.Connect(service_id, wifi_direct_credentials->GetGateway(), + wifi_direct_credentials->GetPort(), &flag); + + // If FeatureFlag is disabled, Cancelled is false as no-op. + if (!feature_flags.enable_cancellation_flag) { + EXPECT_TRUE(socket_client.IsValid()); + EXPECT_TRUE(wifi_direct_b.DisconnectWifiDirect()); + EXPECT_TRUE(wifi_direct_a.StopWifiDirect()); + } else { + EXPECT_FALSE(socket_client.IsValid()); + EXPECT_TRUE(wifi_direct_b.DisconnectWifiDirect()); + EXPECT_TRUE(wifi_direct_a.StopWifiDirect()); + } +} + +TEST_F(WifiDirectTest, CanStartGOTheOtherFailConnect) { + WifiDirect wifi_direct_a, wifi_direct_b; + + EXPECT_TRUE(wifi_direct_a.StartWifiDirect()); + + std::string ssid(kSsid); + std::string password(kPassword); + + EXPECT_FALSE(wifi_direct_b.ConnectWifiDirect(ssid, password)); + EXPECT_TRUE(wifi_direct_b.DisconnectWifiDirect()); + EXPECT_TRUE(wifi_direct_a.StopWifiDirect()); +} + +} // namespace +} // namespace connections +} // namespace nearby +} // namespace location