From 43a165e418927442b2fac348353dcf96aa6bb9bd Mon Sep 17 00:00:00 2001 From: hai007 Date: Fri, 8 Apr 2022 13:16:43 -0700 Subject: [PATCH] WIFI Hotspot implementation (1) Part 1: Internal common code PiperOrigin-RevId: 440437253 --- internal/platform/BUILD | 5 + internal/platform/implementation/BUILD | 1 + .../platform/implementation/g3/platform.cc | 5 + .../ios/Source/Internal/platform.mm | 4 + internal/platform/implementation/platform.h | 2 + .../platform/implementation/wifi_hotspot.h | 114 ++++++++++ .../implementation/windows/platform.cc | 5 + internal/platform/wifi_hotspot.cc | 32 +++ internal/platform/wifi_hotspot.h | 210 ++++++++++++++++++ internal/platform/wifi_hotspot_credential.h | 86 +++++++ internal/platform/wifi_hotspot_test.cc | 75 +++++++ 11 files changed, 539 insertions(+) create mode 100644 internal/platform/implementation/wifi_hotspot.h create mode 100644 internal/platform/wifi_hotspot.cc create mode 100644 internal/platform/wifi_hotspot.h create mode 100644 internal/platform/wifi_hotspot_credential.h create mode 100644 internal/platform/wifi_hotspot_test.cc diff --git a/internal/platform/BUILD b/internal/platform/BUILD index 0c8adc30..08e3c774 100644 --- a/internal/platform/BUILD +++ b/internal/platform/BUILD @@ -42,6 +42,7 @@ cc_library( "runnable.h", "socket.h", "types.h", + "wifi_hotspot_credential.h", ], copts = ["-DCORE_ADAPTER_DLL"], defines = ["NO_WEBRTC"], @@ -56,6 +57,7 @@ cc_library( "//third_party/nearby/presence:__subpackages__", ], deps = [ + "//proto:connections_enums_cc_proto", "@com_google_absl//absl/container:flat_hash_map", "@com_google_absl//absl/meta:type_traits", "@com_google_absl//absl/strings", @@ -310,6 +312,7 @@ cc_library( "ble_v2.cc", "bluetooth_classic.cc", "file.cc", + "wifi_hotspot.cc", "wifi_lan.cc", ], hdrs = [ @@ -317,6 +320,7 @@ cc_library( "ble_v2.h", "bluetooth_adapter.h", "bluetooth_classic.h", + "wifi_hotspot.h", "wifi_lan.h", ], copts = ["-DCORE_ADAPTER_DLL"], @@ -361,6 +365,7 @@ cc_test( "pipe_test.cc", "scheduled_executor_test.cc", "single_thread_executor_test.cc", + "wifi_hotspot_test.cc", "wifi_lan_test.cc", ], copts = ["-DCORE_ADAPTER_DLL"], diff --git a/internal/platform/implementation/BUILD b/internal/platform/implementation/BUILD index 95d4d64e..7d7ce422 100644 --- a/internal/platform/implementation/BUILD +++ b/internal/platform/implementation/BUILD @@ -57,6 +57,7 @@ cc_library( "bluetooth_classic.h", "server_sync.h", "wifi.h", + "wifi_hotspot.h", "wifi_lan.h", ], defines = ["NO_WEBRTC"], diff --git a/internal/platform/implementation/g3/platform.cc b/internal/platform/implementation/g3/platform.cc index cb6046f1..7525e2e7 100644 --- a/internal/platform/implementation/g3/platform.cc +++ b/internal/platform/implementation/g3/platform.cc @@ -210,6 +210,11 @@ std::unique_ptr ImplementationPlatform::CreateWifiLanMedium() { return std::make_unique(); } +std::unique_ptr +ImplementationPlatform::CreateWifiHotspotMedium() { + return nullptr; +} + #ifndef NO_WEBRTC std::unique_ptr ImplementationPlatform::CreateWebRtcMedium() { if (MediumEnvironment::Instance().GetEnvironmentConfig().webrtc_enabled) { diff --git a/internal/platform/implementation/ios/Source/Internal/platform.mm b/internal/platform/implementation/ios/Source/Internal/platform.mm index 51148bc6..d47a62cb 100644 --- a/internal/platform/implementation/ios/Source/Internal/platform.mm +++ b/internal/platform/implementation/ios/Source/Internal/platform.mm @@ -163,6 +163,10 @@ std::unique_ptr ImplementationPlatform::CreateWifiLanMedium() { return std::make_unique(); } +std::unique_ptr ImplementationPlatform::CreateWifiHotspotMedium() { + return nullptr; +} + #ifndef NO_WEBRTC std::unique_ptr ImplementationPlatform::CreateWebRtcMedium() { return nullptr; } #endif diff --git a/internal/platform/implementation/platform.h b/internal/platform/implementation/platform.h index c7929a6c..d447267d 100644 --- a/internal/platform/implementation/platform.h +++ b/internal/platform/implementation/platform.h @@ -42,6 +42,7 @@ #include "internal/platform/implementation/webrtc.h" #endif #include "internal/platform/implementation/wifi.h" +#include "internal/platform/implementation/wifi_hotspot.h" #include "internal/platform/implementation/wifi_lan.h" #include "internal/platform/os_name.h" #include "internal/platform/payload_id.h" @@ -114,6 +115,7 @@ class ImplementationPlatform { static std::unique_ptr CreateServerSyncMedium(); static std::unique_ptr CreateWifiMedium(); static std::unique_ptr CreateWifiLanMedium(); + static std::unique_ptr CreateWifiHotspotMedium(); #ifndef NO_WEBRTC static std::unique_ptr CreateWebRtcMedium(); #endif diff --git a/internal/platform/implementation/wifi_hotspot.h b/internal/platform/implementation/wifi_hotspot.h new file mode 100644 index 00000000..bcc1de6c --- /dev/null +++ b/internal/platform/implementation/wifi_hotspot.h @@ -0,0 +1,114 @@ +// 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. + +#ifndef PLATFORM_API_WIFI_HOTSPOT_H_ +#define PLATFORM_API_WIFI_HOTSPOT_H_ + +#include + +#include "internal/platform/cancellation_flag.h" +#include "internal/platform/input_stream.h" +#include "internal/platform/listeners.h" +#include "internal/platform/output_stream.h" +#include "internal/platform/wifi_hotspot_credential.h" + +namespace location { +namespace nearby { +namespace api { + +class WifiHotspotSocket { + public: + virtual ~WifiHotspotSocket() = default; + + // Returns the InputStream of the WifiHotspotSocket. + // On error, returned stream will report Exception::kIo on any operation. + // + // The returned object is not owned by the caller, and can be invalidated once + // the WifiHotspotSocket object is destroyed. + virtual InputStream& GetInputStream() = 0; + + // Returns the OutputStream of the WifiHotspotSocket. + // On error, returned stream will report Exception::kIo on any operation. + // + // The returned object is not owned by the caller, and can be invalidated once + // the WifiHotspotSocket object is destroyed. + virtual OutputStream& GetOutputStream() = 0; + + // Returns Exception::kIo on error, Exception::kSuccess otherwise. + virtual Exception Close() = 0; +}; + +class WifiHotspotServerSocket { + public: + virtual ~WifiHotspotServerSocket() = default; + + virtual std::string GetIPAddress() const = 0; + + virtual int GetPort() const = 0; + + // Blocks until either: + // - at least one incoming connection request is available, or + // - ServerSocket is closed. + // On success, returns connected socket, ready to exchange data. + // Returns nullptr on error. + // Once error is reported, it is permanent, and ServerSocket has to be closed. + virtual std::unique_ptr Accept() = 0; + + // Returns Exception::kIo on error, Exception::kSuccess otherwise. + virtual Exception Close() = 0; +}; + +// Container of operations that can be performed over the WifiHotspot medium. +class WifiHotspotMedium { + public: + virtual ~WifiHotspotMedium() = default; + + // Connects to a WifiHotspot service by ip address and port. + // On success, returns a new WifiHotspotSocket. + // On error, returns nullptr. + virtual std::unique_ptr ConnectToService( + absl::string_view ip_address, int port, + CancellationFlag* cancellation_flag) = 0; + + // Listens for incoming connection. + // + // port - A port number. + // 0 : use a random port. + // 1~65536 : open a server socket on that exact port. + // On success, returns a new WifiHotspotServerSocket. + // On error, returns nullptr. + virtual std::unique_ptr ListenForService( + int port) = 0; + + // Start a softAP as Hotspot with platform dependent APIs and set the + // SSID/password pair back to the credentials. BWU module will retieve these + // credentials and send to the client device through established channel and + // then client may connect to this Hotspot with these credentials. + virtual bool StartWifiHotspot(HotspotCredentials* hotspot_credentials) = 0; + virtual bool StopWifiHotspot() = 0; + + // Client device connect to a softAP with specified credential. + virtual bool ConnectWifiHotspot(HotspotCredentials* hotspot_credentials) = 0; + virtual bool DisconnectWifiHotspot() = 0; + + // Returns the port range as a pair of min and max port. + virtual absl::optional> + GetDynamicPortRange() = 0; +}; + +} // namespace api +} // namespace nearby +} // namespace location + +#endif // PLATFORM_API_WIFI_HOTSPOT_H_ diff --git a/internal/platform/implementation/windows/platform.cc b/internal/platform/implementation/windows/platform.cc index ae3a38fa..dfa48acd 100644 --- a/internal/platform/implementation/windows/platform.cc +++ b/internal/platform/implementation/windows/platform.cc @@ -232,6 +232,11 @@ std::unique_ptr ImplementationPlatform::CreateWifiLanMedium() { return absl::make_unique(); } +std::unique_ptr +ImplementationPlatform::CreateWifiHotspotMedium() { + return nullptr; +} + // TODO(b/184975123): replace with real implementation. std::unique_ptr ImplementationPlatform::CreateWebRtcMedium() { return absl::make_unique(); diff --git a/internal/platform/wifi_hotspot.cc b/internal/platform/wifi_hotspot.cc new file mode 100644 index 00000000..ce5574b7 --- /dev/null +++ b/internal/platform/wifi_hotspot.cc @@ -0,0 +1,32 @@ +// 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 "internal/platform/wifi_hotspot.h" + +#include "internal/platform/mutex_lock.h" + +namespace location { +namespace nearby { + +WifiHotspotSocket WifiHotspotMedium::ConnectToService( + absl::string_view ip_address, int port, + CancellationFlag* cancellation_flag) { + NEARBY_LOGS(INFO) << "WifiHotspotMedium::ConnectToService: ip address=" + << ip_address << ", port=" << port; + return WifiHotspotSocket( + impl_->ConnectToService(ip_address, port, cancellation_flag)); +} + +} // namespace nearby +} // namespace location diff --git a/internal/platform/wifi_hotspot.h b/internal/platform/wifi_hotspot.h new file mode 100644 index 00000000..c13ec257 --- /dev/null +++ b/internal/platform/wifi_hotspot.h @@ -0,0 +1,210 @@ +// 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. + +#ifndef PLATFORM_PUBLIC_WIFI_HOTSPOT_H_ +#define PLATFORM_PUBLIC_WIFI_HOTSPOT_H_ + +#include +#include +#include + +#include "absl/container/flat_hash_map.h" +#include "internal/platform/byte_array.h" +#include "internal/platform/cancellation_flag.h" +#include "internal/platform/implementation/platform.h" +#include "internal/platform/implementation/wifi_hotspot.h" +#include "internal/platform/input_stream.h" +#include "internal/platform/logging.h" +#include "internal/platform/mutex.h" +#include "internal/platform/output_stream.h" +#include "internal/platform/wifi_hotspot_credential.h" + +namespace location { +namespace nearby { + +// Socket class for both SoftAP ( created through accept connection from STA) +// and STA side (created through connect request to SoftAP side) +// Class WifiHotspotSocket is copyable & movable +class WifiHotspotSocket final { + public: + WifiHotspotSocket() = default; + WifiHotspotSocket(const WifiHotspotSocket&) = default; + WifiHotspotSocket& operator=(const WifiHotspotSocket&) = default; + WifiHotspotSocket(WifiHotspotSocket&&) = default; + WifiHotspotSocket& operator=(WifiHotspotSocket&&) = default; + + explicit WifiHotspotSocket(std::unique_ptr socket) + : impl_(std::move(socket)) {} + + // Returns the InputStream of the WifiHotspotSocket. + // On error, returned stream will report Exception::kIo on any operation. + // + // The returned object is not owned by the caller, and can be invalidated once + // the WifiHotspotSocket object is destroyed. + InputStream& GetInputStream() { + CHECK(impl_); + return impl_->GetInputStream(); + } + + // Returns the OutputStream of the WifiHotspotSocket. + // On error, returned stream will report Exception::kIo on any operation. + // + // The returned object is not owned by the caller, and can be invalidated once + // the WifiHotspotSocket object is destroyed. + OutputStream& GetOutputStream() { + CHECK(impl_); + return impl_->GetOutputStream(); + } + + // Returns Exception::kIo on error, Exception::kSuccess otherwise. + Exception Close() { + CHECK(impl_); + return impl_->Close(); + } + + // Returns true if a socket is usable. If this method returns false, + // it is not safe to call any other method. + // NOTE(socket validity): + // Socket created by a default public constructor is not valid, because + // it is missing platform implementation. + // The only way to obtain a valid socket is through connection, such as + // an object returned by WifiHotspotMedium::Connect + // These methods may also return an invalid socket if connection failed for + // any reason. + bool IsValid() const { return impl_ != nullptr; } + + // Returns reference to platform implementation. + // This is used to communicate with platform code, and for debugging purposes. + // Returned reference will remain valid for while WifiHotspotSocket object is + // itself valid. Typically WifiHotspotSocket lifetime matches duration of the + // connection, and is controlled by end user, since they hold the instance. + api::WifiHotspotSocket& GetImpl() { return *impl_; } + + private: + std::shared_ptr impl_; +}; + +// Server socket class for SoftAP when it listen to connection request +class WifiHotspotServerSocket final { + public: + WifiHotspotServerSocket() = default; + WifiHotspotServerSocket(const WifiHotspotServerSocket&) = default; + WifiHotspotServerSocket& operator=(const WifiHotspotServerSocket&) = default; + + explicit WifiHotspotServerSocket( + std::unique_ptr socket) + : impl_(std::move(socket)) {} + + // Returns ip address. + std::string GetIPAddress() const { + CHECK(impl_); + return impl_->GetIPAddress(); + } + + // Returns port. + int GetPort() const { + CHECK(impl_); + return impl_->GetPort(); + } + + // Blocks until either: + // - at least one incoming connection request is available, or + // - ServerSocket is closed. + // On success, returns connected socket, ready to exchange data. + // On error, "impl_" will be nullptr and the caller will check it by calling + // member function "IsValid()" + // Once error is reported, it is permanent, and + // ServerSocket has to be closed by caller. + WifiHotspotSocket Accept() { + std::unique_ptr socket = impl_->Accept(); + if (!socket) { + NEARBY_LOGS(INFO) + << "WifiHotspotServerSocket Accept() failed on server socket: "; + } + return WifiHotspotSocket(std::move(socket)); + } + + // Returns Exception::kIo on error, Exception::kSuccess otherwise. + Exception Close() { + NEARBY_LOGS(INFO) << "WifiHotspotServerSocket Closing:: " << this; + return impl_->Close(); + } + + bool IsValid() const { return impl_ != nullptr; } + api::WifiHotspotServerSocket& GetImpl() { + CHECK(impl_); + return *impl_; + } + + private: + std::shared_ptr impl_; +}; + +// Container of operations that can be performed over the WifiHotspot medium. +class WifiHotspotMedium { + public: + using Platform = api::ImplementationPlatform; + + WifiHotspotMedium() : impl_(Platform::CreateWifiHotspotMedium()) {} + + // Returns a new WifiHotspotSocket by ip address and port. + // On Success, WifiHotspotSocket::IsValid()returns true. + WifiHotspotSocket ConnectToService(absl::string_view ip_address, int port, + CancellationFlag* cancellation_flag); + + // Returns a new WifiHotspotServerSocket. + // On Success, WifiHotspotServerSocket::IsValid() returns true. + WifiHotspotServerSocket ListenForService(int port = 0) { + return WifiHotspotServerSocket(impl_->ListenForService(port)); + } + + // Returns the port range as a pair of min and max port. + absl::optional> GetDynamicPortRange() { + return impl_->GetDynamicPortRange(); + } + + bool StartWifiHotspot() { + return impl_->StartWifiHotspot(&hotspot_credentials_); + } + bool StopWifiHotspot() { return impl_->StopWifiHotspot(); } + + bool ConnectWifiHotspot(const std::string& ssid, + const std::string& password) { + hotspot_credentials_.SetSSID(ssid); + hotspot_credentials_.SetPassword(password); + return impl_->ConnectWifiHotspot(&hotspot_credentials_); + } + bool DisconnectWifiHotspot() { return impl_->DisconnectWifiHotspot(); } + + HotspotCredentials* GetCredential() { return &hotspot_credentials_; } + + bool IsValid() const { return impl_ != nullptr; } + + // since impl_ could be nullptr if platform hasn't implemented this Medium, + // call IsValid() to check the availability before call this method + api::WifiHotspotMedium& GetImpl() { + CHECK(impl_); + return *impl_; + } + + private: + Mutex mutex_; + std::unique_ptr impl_; + HotspotCredentials hotspot_credentials_; +}; + +} // namespace nearby +} // namespace location + +#endif // PLATFORM_PUBLIC_WIFI_HOTSPOT_H_ diff --git a/internal/platform/wifi_hotspot_credential.h b/internal/platform/wifi_hotspot_credential.h new file mode 100644 index 00000000..85c82cba --- /dev/null +++ b/internal/platform/wifi_hotspot_credential.h @@ -0,0 +1,86 @@ +// 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. + +#ifndef PLATFORM_BASE_WIFI_HOTSPOT_CREDENTIAL_H_ +#define PLATFORM_BASE_WIFI_HOTSPOT_CREDENTIAL_H_ + +#include + +#include "absl/strings/str_format.h" +#include "internal/platform/prng.h" +#include "proto/connections_enums.pb.h" + +namespace location { +namespace nearby { + +// Credentials for the currently-hosted Wifi hotspot (if any) +// Class HotspotCredentials is copyable & movable +class HotspotCredentials { + public: + HotspotCredentials() = default; + HotspotCredentials(const HotspotCredentials&) = default; + HotspotCredentials& operator=(const HotspotCredentials&) = default; + HotspotCredentials(HotspotCredentials&&) = default; + HotspotCredentials& operator=(HotspotCredentials&&) = default; + ~HotspotCredentials() = default; + + // Returns the SSID (Service Set Identifier) which the hotspot will + // periodically beacon. STA can start a wifi scan for this SSID to find the + // hotspot + std::string GetSSID() const { return ssid_; } + void SetSSID(const std::string& ssid) { ssid_ = ssid; } + + std::string GetPassword() const { return password_; } + void SetPassword(const std::string& password) { password_ = password; } + + // Gets IP Address, which is in byte sequence, in network order. For example, + // for "192.168.1.1", it'll be byte(129)+byte(168)+byte(1)+byte(1). Now only + // ipv4 is supported. + std::string GetIPAddress() const { return ip_address_; } + void SetIPAddress(const std::string& ip_address) { ip_address_ = ip_address; } + + std::string GetGateway() const { return gateway_; } + void SetGateway(const std::string& gateway) { gateway_ = gateway; } + + // Gets the Port number + int GetPort() const { return port_; } + // Set port_ + void SetPort(const int port) { port_ = port; } + + // Gets the Frequency + int GetFrequency() const { return frequency_; } + + // Gets the Band + proto::connections::ConnectionBand GetBand() const { return band_; } + + // Gets the Technology + proto::connections::ConnectionTechnology GetTechnology() const { + return technology_; + } + + private: + std::string ssid_; + std::string password_; + std::string ip_address_; + std::string gateway_ = "0.0.0.0"; + int port_ = 0; + int frequency_ = -1; + proto::connections::ConnectionBand band_; + proto::connections::ConnectionTechnology technology_; +}; + +} // namespace nearby +} // namespace location + +#endif // PLATFORM_BASE_WIFI_HOTSPOT_CREDENTIAL_H_ diff --git a/internal/platform/wifi_hotspot_test.cc b/internal/platform/wifi_hotspot_test.cc new file mode 100644 index 00000000..ac18379d --- /dev/null +++ b/internal/platform/wifi_hotspot_test.cc @@ -0,0 +1,75 @@ +// 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 "internal/platform/wifi_hotspot.h" + +#include + +#include "gtest/gtest.h" +#include "internal/platform/count_down_latch.h" +#include "internal/platform/wifi_hotspot_credential.h" + +namespace location { +namespace nearby { +namespace { + +constexpr absl::string_view kSsid{"Direct-357a2d8c"}; +constexpr absl::string_view kPassword{"b592f7d3"}; + +TEST(HotspotCredentialsTest, SetGetSsid) { + std::string ssid(kSsid); + HotspotCredentials hotspot_credentials; + hotspot_credentials.SetSSID(ssid); + + EXPECT_EQ(kSsid, hotspot_credentials.GetSSID()); +} + +TEST(HotspotCredentialsTest, SetGetPassword) { + std::string password(kPassword); + HotspotCredentials hotspot_credentials; + hotspot_credentials.SetPassword(password); + + EXPECT_EQ(kPassword, hotspot_credentials.GetPassword()); +} + +TEST(WifiHotspotMediumTest, ConstructorDestructorWorks) { + WifiHotspotMedium wifi_hotspot_a; + WifiHotspotMedium wifi_hotspot_b; + + if (wifi_hotspot_a.IsValid() && wifi_hotspot_b.IsValid()) { + // Make sure we can create 2 distinct mediums. + EXPECT_NE(&wifi_hotspot_a.GetImpl(), &wifi_hotspot_b.GetImpl()); + } + // TODO(b/227482970): Add test coverage for wifi_hotspot.cc +} + +TEST(WifiHotspotMediumTest, CanConnectToService) { + CancellationFlag flag; + WifiHotspotMedium wifi_hotspot_a; + WifiHotspotMedium wifi_hotspot_b; + + WifiHotspotSocket socket_a; + WifiHotspotServerSocket socket_b; + EXPECT_FALSE(socket_a.IsValid()); + // TODO(b/227482970): Add test coverage for wifi_hotspot.cc +} + +TEST(WifiHotspotMediumTest, CanSartHotspot) { + WifiHotspotMedium wifi_hotspot; + // TODO(b/227482970): Add test coverage for wifi_hotspot.cc +} + +} // namespace +} // namespace nearby +} // namespace location