diff --git a/internal/platform/implementation/g3/wifi_direct.cc b/internal/platform/implementation/g3/wifi_direct.cc index a0e8b4e6..c05adcba 100644 --- a/internal/platform/implementation/g3/wifi_direct.cc +++ b/internal/platform/implementation/g3/wifi_direct.cc @@ -23,42 +23,168 @@ #include "absl/strings/str_format.h" #include "absl/synchronization/mutex.h" -#include "internal/platform/cancellation_flag_listener.h" #include "internal/platform/implementation/g3/wifi_hotspot.h" #include "internal/platform/implementation/wifi_direct.h" #include "internal/platform/implementation/wifi_hotspot.h" #include "internal/platform/logging.h" +#include "internal/platform/medium_environment.h" namespace location { namespace nearby { namespace g3 { + +// Code for WifiDirectMedium +WifiDirectMedium::WifiDirectMedium() { + auto& env = MediumEnvironment::Instance(); + env.RegisterWifiDirectMedium(*this); +} + +WifiDirectMedium::~WifiDirectMedium() { + auto& env = MediumEnvironment::Instance(); + env.UnregisterWifiDirectMedium(*this); +} + bool WifiDirectMedium::StartWifiDirect( HotspotCredentials* wifi_direct_credentials) { + absl::MutexLock lock(&mutex_); + + std::string ssid = absl::StrCat("DIRECT-", Prng().NextUint32()); + wifi_direct_credentials->SetSSID(ssid); + std::string password = absl::StrFormat("%08x", Prng().NextUint32()); + wifi_direct_credentials->SetPassword(password); + + NEARBY_LOGS(INFO) << "G3 StartWifiDirect GO: ssid=" << ssid + << ", password:" << password; + + auto& env = MediumEnvironment::Instance(); + env.UpdateWifiDirectMediumForStartOrConnect(*this, wifi_direct_credentials, + /*is_go=*/true, /*enabled=*/true); + return true; } bool WifiDirectMedium::StopWifiDirect() { absl::MutexLock lock(&mutex_); + NEARBY_LOGS(INFO) << "G3 StopWifiDirect GO"; + + auto& env = MediumEnvironment::Instance(); + env.UpdateWifiDirectMediumForStartOrConnect(*this, /*credentials*/ nullptr, + /*is_go=*/true, + /*enabled=*/false); return true; } bool WifiDirectMedium::ConnectWifiDirect( HotspotCredentials* wifi_direct_credentials) { + absl::MutexLock lock(&mutex_); + + NEARBY_LOGS(INFO) << "G3 ConnectWifiDirect : ssid=" + << wifi_direct_credentials->GetSSID() + << ", password:" << wifi_direct_credentials->GetPassword(); + + auto& env = MediumEnvironment::Instance(); + auto* remote_medium = static_cast( + env.GetWifiDirectMedium(wifi_direct_credentials->GetSSID(), {})); + if (!remote_medium) { + env.UpdateWifiDirectMediumForStartOrConnect(*this, wifi_direct_credentials, + /*is_go=*/false, + /*enabled=*/false); + return false; + } + + env.UpdateWifiDirectMediumForStartOrConnect(*this, wifi_direct_credentials, + /*is_go=*/false, + /*enabled=*/true); return true; } -bool WifiDirectMedium::DisconnectWifiDirect() { return true; } +bool WifiDirectMedium::DisconnectWifiDirect() { + absl::MutexLock lock(&mutex_); + + NEARBY_LOGS(INFO) << "G3 DisconnectWifiDirect"; + + auto& env = MediumEnvironment::Instance(); + env.UpdateWifiDirectMediumForStartOrConnect(*this, /*credentials*/ nullptr, + /*is_go=*/false, + /*enabled=*/false); + return true; +} std::unique_ptr WifiDirectMedium::ConnectToService( absl::string_view ip_address, int port, CancellationFlag* cancellation_flag) { - return nullptr; + std::string socket_name = WifiHotspotServerSocket::GetName(ip_address, port); + NEARBY_LOGS(INFO) << "G3 WifiDirect ConnectToService [self]: medium=" << this + << ", ip address + port=" << socket_name; + // First, find an instance of remote medium, that exposed this service. + auto& env = MediumEnvironment::Instance(); + auto* remote_medium = + static_cast(env.GetWifiDirectMedium({}, ip_address)); + if (remote_medium == nullptr) { + return nullptr; + } + + WifiHotspotServerSocket* server_socket = nullptr; + NEARBY_LOGS(INFO) << "G3 WifiDirect ConnectToService [peer]: medium=" + << remote_medium + << ", remote ip address + port=" << socket_name; + // Then, find our server socket context in this medium. + { + absl::MutexLock medium_lock(&remote_medium->mutex_); + auto item = remote_medium->server_sockets_.find(socket_name); + server_socket = item != server_sockets_.end() ? item->second : nullptr; + if (server_socket == nullptr) { + NEARBY_LOGS(ERROR) << "G3 WifiDirect Failed to find WifiDirect Server " + "socket: socket_name=" + << socket_name; + return nullptr; + } + } + + if (cancellation_flag->Cancelled()) { + NEARBY_LOGS(ERROR) + << "G3 WifiDirect Connect: Has been cancelled: socket_name=" + << socket_name; + return nullptr; + } + + auto socket = std::make_unique(); + // Finally, Request to connect to this socket. + + server_socket->Connect(*socket); + NEARBY_LOGS(INFO) << "G3 WifiHotspot GC ConnectToService: connected: socket=" + << socket.get(); + return socket; } std::unique_ptr WifiDirectMedium::ListenForService(int port) { - return nullptr; + auto& env = MediumEnvironment::Instance(); + auto server_socket = std::make_unique(); + + std::string dot_decimal_ip; + std::string ip_address = env.GetFakeIPAddress(); + + for (auto byte : ip_address) { + absl::StrAppend(&dot_decimal_ip, absl::StrFormat("%d", byte), "."); + } + dot_decimal_ip.pop_back(); + + server_socket->SetIPAddress(dot_decimal_ip); + server_socket->SetPort(port == 0 ? env.GetFakePort() : port); + std::string socket_name = WifiHotspotServerSocket::GetName( + server_socket->GetIPAddress(), server_socket->GetPort()); + server_socket->SetCloseNotifier([this, socket_name]() { + absl::MutexLock lock(&mutex_); + server_sockets_.erase(socket_name); + }); + NEARBY_LOGS(INFO) << "G3 WifiDirect GO Adding server socket: medium=" << this + << ", socket_name=" << socket_name; + absl::MutexLock lock(&mutex_); + server_sockets_.insert({socket_name, server_socket.get()}); + return server_socket; } + } // namespace g3 } // namespace nearby } // namespace location diff --git a/internal/platform/implementation/g3/wifi_direct.h b/internal/platform/implementation/g3/wifi_direct.h index 7733326d..2b6c095d 100644 --- a/internal/platform/implementation/g3/wifi_direct.h +++ b/internal/platform/implementation/g3/wifi_direct.h @@ -40,8 +40,8 @@ class WifiDirectMedium; // Container of operations that can be performed over the WifiDirect medium. class WifiDirectMedium : public api::WifiDirectMedium { public: - WifiDirectMedium() = default; - ~WifiDirectMedium() override = default; + WifiDirectMedium(); + ~WifiDirectMedium() override; WifiDirectMedium(const WifiDirectMedium&) = delete; WifiDirectMedium(WifiDirectMedium&&) = delete; diff --git a/internal/platform/medium_environment.cc b/internal/platform/medium_environment.cc index 10218f3c..4906c927 100644 --- a/internal/platform/medium_environment.cc +++ b/internal/platform/medium_environment.cc @@ -71,6 +71,7 @@ void MediumEnvironment::Reset() { wifi_lan_mediums_.clear(); { MutexLock lock(&mutex_); + wifi_direct_mediums_.clear(); wifi_hotspot_mediums_.clear(); } use_valid_peer_connection_ = true; @@ -927,6 +928,96 @@ api::WifiLanMedium* MediumEnvironment::GetWifiLanMedium( return nullptr; } +void MediumEnvironment::RegisterWifiDirectMedium( + api::WifiDirectMedium& medium) { + if (!enabled_) return; + RunOnMediumEnvironmentThread([this, &medium]() { + MutexLock lock(&mutex_); + wifi_direct_mediums_.insert({&medium, WifiDirectMediumContext{}}); + NEARBY_LOG(INFO, "Registered: medium=%p", &medium); + }); +} + +api::WifiDirectMedium* MediumEnvironment::GetWifiDirectMedium( + absl::string_view ssid, absl::string_view ip_address) { + MutexLock lock(&mutex_); + for (auto& medium_info : wifi_direct_mediums_) { + auto* medium_found = medium_info.first; + auto& info = medium_info.second; + if (info.is_go && info.is_active) { + if ((info.wifi_direct_credentials->GetSSID() == ssid) || + (!ip_address.empty() && + (info.wifi_direct_credentials->GetIPAddress() == ip_address))) { + NEARBY_LOGS(INFO) << "Found Remote WifiDirect medium=" << medium_found; + return medium_found; + } + } + } + + NEARBY_LOGS(INFO) << "Can't find WifiDirect medium!"; + return nullptr; +} + +void MediumEnvironment::UpdateWifiDirectMediumForStartOrConnect( + api::WifiDirectMedium& medium, + const HotspotCredentials* wifi_direct_credentials, bool is_go, + bool enabled) { + if (!enabled_) return; + + CountDownLatch latch(1); + RunOnMediumEnvironmentThread( + [this, &medium, wifi_direct_credentials = wifi_direct_credentials, is_go, + enabled, &latch]() { + std::string role_status = absl::StrFormat( + "; %s is %s", is_go ? "Group Owner" : "Group Client", + is_go ? (enabled ? "Started" : "Stopped") + : (enabled ? "Connected" : "Disconneced")); + + if (wifi_direct_credentials) { + NEARBY_LOGS(INFO) + << "Update WifiDirect medium for GO: this=" << this + << "; medium=" << &medium << role_status + << "; ssid=" << wifi_direct_credentials->GetSSID() + << "; password=" << wifi_direct_credentials->GetPassword(); + } else { + NEARBY_LOGS(INFO) << "Reset WifiDirect medium for GO: this=" << this + << "; medium=" << &medium << role_status; + } + + MutexLock lock(&mutex_); + for (auto& medium_info : wifi_direct_mediums_) { + auto& local_medium = medium_info.first; + auto& info = medium_info.second; + if (local_medium == &medium) { + NEARBY_LOGS(INFO) << "Found WifiDirect medium=" << &medium; + info.is_active = enabled; + info.is_go = is_go; + if (enabled) { + info.wifi_direct_credentials = wifi_direct_credentials; + } + continue; + } + } + latch.CountDown(); + }); + latch.Await(); +} + +bool MediumEnvironment::IsWifiDirectMediumsEmpty() { + MutexLock lock(&mutex_); + return wifi_direct_mediums_.empty(); +} + +void MediumEnvironment::UnregisterWifiDirectMedium( + api::WifiDirectMedium& medium) { + if (!enabled_) return; + RunOnMediumEnvironmentThread([this, &medium]() { + MutexLock lock(&mutex_); + wifi_direct_mediums_.extract(&medium); + NEARBY_LOGS(INFO) << "Unregistered WifiDirect medium"; + }); +} + void MediumEnvironment::RegisterWifiHotspotMedium( api::WifiHotspotMedium& medium) { if (!enabled_) return; diff --git a/internal/platform/medium_environment.h b/internal/platform/medium_environment.h index d7f42409..81796656 100644 --- a/internal/platform/medium_environment.h +++ b/internal/platform/medium_environment.h @@ -20,6 +20,7 @@ #include #include #include +#include #include "absl/container/flat_hash_map.h" #include "absl/container/flat_hash_set.h" @@ -35,6 +36,7 @@ #endif #include "internal/platform/byte_array.h" #include "internal/platform/feature_flags.h" +#include "internal/platform/implementation/wifi_direct.h" #include "internal/platform/implementation/wifi_hotspot.h" #include "internal/platform/implementation/wifi_lan.h" #include "internal/platform/mutex.h" @@ -309,6 +311,28 @@ class MediumEnvironment { // port, or nullptr. api::WifiLanMedium* GetWifiLanMedium(const std::string& ip_address, int port); + // Adds medium-related info to allow for start/connect WifiDirect to work. + void RegisterWifiDirectMedium(api::WifiDirectMedium& medium); + + // Returns WifiDirect medium that matches ssid or IP address with the role of + // the Medium. Returns nullptr if not found. + api::WifiDirectMedium* GetWifiDirectMedium(absl::string_view ssid, + absl::string_view ip_address); + + // Updates credential and Medium role(GO or GC) to indicate the current + // medium is exposing Start WifiDirect event. + void UpdateWifiDirectMediumForStartOrConnect( + api::WifiDirectMedium& medium, + const HotspotCredentials* wifi_direct_credentials, bool is_go, + bool enabled); + + // For unit test only + bool IsWifiDirectMediumsEmpty(); + + // Removes medium-related info. This should correspond to device being stopped + // or disconnected. + void UnregisterWifiDirectMedium(api::WifiDirectMedium& medium); + // Adds medium-related info to allow for start/connect Hotspot to work. // This provides access to this medium from other mediums, when protocol // expects they should communicate. @@ -367,6 +391,14 @@ class MediumEnvironment { absl::flat_hash_map discovered_services; }; + struct WifiDirectMediumContext { + // Set to "true" for Medium act as WifiDirect GO role; "false" for GC role + bool is_go = false; + // Set "true" when GO is started or GC is connected + bool is_active = false; + const HotspotCredentials* wifi_direct_credentials; + }; + struct WifiHotspotMediumContext { // Set to "true" for Medium act as SoftAP role; "false" for STA role bool is_ap = true; @@ -441,6 +473,9 @@ class MediumEnvironment { wifi_lan_mediums_; Mutex mutex_; + absl::flat_hash_map + wifi_direct_mediums_ ABSL_GUARDED_BY(mutex_); + absl::flat_hash_map wifi_hotspot_mediums_ ABSL_GUARDED_BY(mutex_); diff --git a/internal/platform/wifi_direct_test.cc b/internal/platform/wifi_direct_test.cc index 32274b17..b0e8ca51 100644 --- a/internal/platform/wifi_direct_test.cc +++ b/internal/platform/wifi_direct_test.cc @@ -18,23 +18,57 @@ #include #include "gtest/gtest.h" +#include "absl/strings/match.h" +#include "internal/platform/medium_environment.h" #include "internal/platform/wifi_hotspot.h" #include "internal/platform/wifi_hotspot_credential.h" namespace location { namespace nearby { namespace { + +using FeatureFlags = FeatureFlags::Flags; + +constexpr FeatureFlags kTestCases[] = { + FeatureFlags{ + .enable_cancellation_flag = true, + }, + FeatureFlags{ + .enable_cancellation_flag = false, + }, +}; + constexpr absl::string_view kSsid = "Direct-357a2d8c"; constexpr absl::string_view kPassword = "b592f7d3"; constexpr absl::string_view kIp = "123.234.23.1"; constexpr const size_t kPort = 20; +constexpr absl::string_view kData = "ABCD"; +constexpr const size_t kChunkSize = 10; +constexpr absl::Duration kWaitDuration = absl::Milliseconds(100); -class WifiDirectMediumTest : public ::testing::Test {}; +class WifiDirectMediumTest : public testing::TestWithParam { + protected: + WifiDirectMediumTest() { + env_.Stop(); + env_.Start(); + } + ~WifiDirectMediumTest() override { + absl::SleepFor(kWaitDuration); + EXPECT_TRUE(env_.IsWifiDirectMediumsEmpty()); + env_.Stop(); + } + + MediumEnvironment& env_{MediumEnvironment::Instance()}; +}; + +INSTANTIATE_TEST_SUITE_P(ParametrisedWifiDirectMediumTest, WifiDirectMediumTest, + testing::ValuesIn(kTestCases)); TEST_F(WifiDirectMediumTest, ConstructorDestructorWorks) { - auto wifi_direct_a = WifiDirectMedium(); - auto wifi_direct_b = WifiDirectMedium(); + WifiDirectMedium wifi_direct_a; + WifiDirectMedium wifi_direct_b; + // Make sure we can create functional mediums. ASSERT_TRUE(wifi_direct_a.IsInterfaceValid()); ASSERT_TRUE(wifi_direct_b.IsInterfaceValid()); @@ -43,29 +77,173 @@ TEST_F(WifiDirectMediumTest, ConstructorDestructorWorks) { } TEST_F(WifiDirectMediumTest, CanStartStopDirect) { - auto wifi_direct_a = WifiDirectMedium(); + WifiDirectMedium wifi_direct_a; + ASSERT_TRUE(wifi_direct_a.IsInterfaceValid()); EXPECT_TRUE(wifi_direct_a.StartWifiDirect()); EXPECT_EQ(wifi_direct_a.GetDynamicPortRange(), std::nullopt); - WifiHotspotServerSocket server_socket = wifi_direct_a.ListenForService(); - EXPECT_FALSE(server_socket.IsValid()); + EXPECT_TRUE(server_socket.IsValid()); + server_socket.Close(); EXPECT_TRUE(wifi_direct_a.StopWifiDirect()); } TEST_F(WifiDirectMediumTest, CanConnectDisconnectDirect) { - auto wifi_direct_a = WifiDirectMedium(); + WifiDirectMedium wifi_direct_a; ASSERT_TRUE(wifi_direct_a.IsInterfaceValid()); - EXPECT_TRUE(wifi_direct_a.ConnectWifiDirect(kSsid, kPassword)); + EXPECT_FALSE(wifi_direct_a.ConnectWifiDirect(kSsid, kPassword)); EXPECT_TRUE(wifi_direct_a.DisconnectWifiDirect()); } -TEST_F(WifiDirectMediumTest, CanConnectToService) { - auto wifi_direct = WifiDirectMedium(); - CancellationFlag flag; - EXPECT_FALSE(wifi_direct.ConnectToService(kIp, kPort, &flag).IsValid()); +TEST_P(WifiDirectMediumTest, CanStartDirectGOThatOtherCanConnect) { + FeatureFlags feature_flags = GetParam(); + env_.SetFeatureFlags(feature_flags); + WifiDirectMedium wifi_direct_a; + WifiDirectMedium wifi_direct_b; + + ASSERT_TRUE(wifi_direct_a.IsInterfaceValid()); + ASSERT_TRUE(wifi_direct_b.IsInterfaceValid()); + EXPECT_TRUE(wifi_direct_a.StartWifiDirect()); + HotspotCredentials* wifi_direct_credentials = wifi_direct_a.GetCredential(); + auto* medium_a = + env_.GetWifiDirectMedium(wifi_direct_credentials->GetSSID(), {}); + EXPECT_NE(medium_a, nullptr); + EXPECT_TRUE( + wifi_direct_b.ConnectWifiDirect(wifi_direct_credentials->GetSSID(), + wifi_direct_credentials->GetPassword())); + + WifiHotspotServerSocket server_socket = wifi_direct_a.ListenForService(); + EXPECT_TRUE(server_socket.IsValid()); + auto ip_addr = server_socket.GetIPAddress(); + EXPECT_FALSE(absl::EndsWith(ip_addr, ".")); + wifi_direct_credentials->SetIPAddress(ip_addr); + + WifiHotspotSocket socket_a; + WifiHotspotSocket socket_b; + EXPECT_FALSE(socket_a.IsValid()); + EXPECT_FALSE(socket_b.IsValid()); + + { + CancellationFlag flag; + SingleThreadExecutor server_executor; + SingleThreadExecutor client_executor; + client_executor.Execute( + [&wifi_direct_b, &socket_b, &server_socket, &flag]() { + socket_b = wifi_direct_b.ConnectToService(kIp, kPort, &flag); + EXPECT_FALSE(socket_b.IsValid()); + socket_b = wifi_direct_b.ConnectToService( + server_socket.GetIPAddress(), kPort, &flag); + EXPECT_FALSE(socket_b.IsValid()); + socket_b = wifi_direct_b.ConnectToService( + server_socket.GetIPAddress(), server_socket.GetPort(), &flag); + if (!socket_b.IsValid()) { + server_socket.Close(); + } + }); + server_executor.Execute([&socket_a, &server_socket]() { + socket_a = server_socket.Accept(); + if (!socket_a.IsValid()) { + server_socket.Close(); + } + }); + } + EXPECT_TRUE(socket_a.IsValid()); + EXPECT_TRUE(socket_b.IsValid()); + InputStream& in_stream = socket_a.GetInputStream(); + OutputStream& out_stream = socket_b.GetOutputStream(); + std::string data(kData); + EXPECT_TRUE(out_stream.Write(ByteArray(data)).Ok()); + ExceptionOr read_data = in_stream.Read(kChunkSize); + EXPECT_TRUE(read_data.ok()); + EXPECT_EQ(std::string(read_data.result()), data); + + socket_a.Close(); + socket_b.Close(); + EXPECT_FALSE(out_stream.Write(ByteArray(data)).Ok()); + read_data = in_stream.Read(kChunkSize); + EXPECT_FALSE(read_data.ok()); + + server_socket.Close(); + EXPECT_TRUE(wifi_direct_b.DisconnectWifiDirect()); + EXPECT_TRUE(wifi_direct_a.StopWifiDirect()); + auto* medium_b = + env_.GetWifiDirectMedium(wifi_direct_credentials->GetSSID(), {}); + EXPECT_EQ(medium_b, nullptr); } + +TEST_P(WifiDirectMediumTest, CanStartDirectGOThatOtherCanCancelConnect) { + FeatureFlags feature_flags = GetParam(); + env_.SetFeatureFlags(feature_flags); + WifiDirectMedium wifi_direct_a; + WifiDirectMedium wifi_direct_b; + + ASSERT_TRUE(wifi_direct_a.IsInterfaceValid()); + ASSERT_TRUE(wifi_direct_b.IsInterfaceValid()); + EXPECT_TRUE(wifi_direct_a.StartWifiDirect()); + HotspotCredentials* wifi_direct_credentials = wifi_direct_a.GetCredential(); + EXPECT_TRUE( + wifi_direct_b.ConnectWifiDirect(wifi_direct_credentials->GetSSID(), + wifi_direct_credentials->GetPassword())); + + WifiHotspotServerSocket server_socket = wifi_direct_a.ListenForService(); + EXPECT_TRUE(server_socket.IsValid()); + wifi_direct_credentials->SetIPAddress(server_socket.GetIPAddress()); + + WifiHotspotSocket socket_a; + WifiHotspotSocket socket_b; + EXPECT_FALSE(socket_a.IsValid()); + EXPECT_FALSE(socket_b.IsValid()); + + { + CancellationFlag flag(true); + SingleThreadExecutor server_executor; + SingleThreadExecutor client_executor; + client_executor.Execute( + [&wifi_direct_b, &socket_b, &server_socket, &flag]() { + socket_b = wifi_direct_b.ConnectToService(kIp, kPort, &flag); + EXPECT_FALSE(socket_b.IsValid()); + socket_b = wifi_direct_b.ConnectToService( + server_socket.GetIPAddress(), server_socket.GetPort(), &flag); + if (!socket_b.IsValid()) { + server_socket.Close(); + } + }); + server_executor.Execute([&socket_a, &server_socket]() { + socket_a = server_socket.Accept(); + if (!socket_a.IsValid()) { + server_socket.Close(); + } + }); + } + + if (!feature_flags.enable_cancellation_flag) { + EXPECT_TRUE(socket_a.IsValid()); + EXPECT_TRUE(socket_b.IsValid()); + } else { + EXPECT_FALSE(socket_a.IsValid()); + EXPECT_FALSE(socket_b.IsValid()); + } + + server_socket.Close(); + EXPECT_TRUE(wifi_direct_b.DisconnectWifiDirect()); + EXPECT_TRUE(wifi_direct_a.StopWifiDirect()); +} + +TEST_F(WifiDirectMediumTest, CanStartDirectGOThatOtherFailConnect) { + WifiDirectMedium wifi_direct_a; + WifiDirectMedium wifi_direct_b; + + ASSERT_TRUE(wifi_direct_a.IsInterfaceValid()); + ASSERT_TRUE(wifi_direct_b.IsInterfaceValid()); + EXPECT_TRUE(wifi_direct_a.StartWifiDirect()); + + EXPECT_FALSE(wifi_direct_b.ConnectWifiDirect(kSsid, kPassword)); + EXPECT_TRUE(wifi_direct_b.DisconnectWifiDirect()); + + EXPECT_TRUE(wifi_direct_a.StopWifiDirect()); +} + } // namespace } // namespace nearby } // namespace location