Files
nearby/internal/platform/implementation/windows/wifi_hotspot_medium.cc
T
2026-05-07 22:18:07 -07:00

497 lines
18 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// 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 <cstdint>
#include <exception>
#include <memory>
#include <optional>
#include <string>
#include "absl/strings/str_format.h"
#include "absl/strings/string_view.h"
#include "absl/synchronization/mutex.h"
#include "absl/time/time.h"
#include "internal/base/masker.h"
#include "internal/flags/nearby_flags.h"
#include "internal/platform/byte_array.h"
#include "internal/platform/cancellation_flag.h"
#include "internal/platform/cancellation_flag_listener.h"
#include "internal/platform/exception.h"
#include "internal/platform/feature_flags.h"
#include "internal/platform/flags/nearby_platform_feature_flags.h"
#include "internal/platform/implementation/input_file.h"
#include "internal/platform/implementation/output_file.h"
#include "internal/platform/implementation/platform.h"
#include "internal/platform/implementation/system_clock.h"
#include "internal/platform/implementation/wifi_hotspot.h"
#include "internal/platform/implementation/wifi_utils.h"
#include "internal/platform/implementation/windows/generated/winrt/Windows.Devices.Enumeration.h"
#include "internal/platform/implementation/windows/generated/winrt/Windows.Security.Credentials.h"
#include "internal/platform/implementation/windows/socket_address.h"
#include "internal/platform/implementation/windows/string_utils.h"
#include "internal/platform/implementation/windows/utils.h"
#include "internal/platform/implementation/windows/wifi_hotspot.h"
#include "internal/platform/implementation/windows/wifi_hotspot_server_socket.h"
#include "internal/platform/implementation/windows/wifi_hotspot_socket.h"
#include "internal/platform/implementation/windows/wifi_intel.h"
#include "internal/platform/logging.h"
#include "internal/platform/prng.h"
#include "internal/platform/wifi_credential.h"
namespace nearby::windows {
namespace {
using ::absl::Milliseconds;
using ::winrt::Windows::Devices::WiFiDirect::
WiFiDirectAdvertisementPublisherStatus;
using ::winrt::Windows::Devices::WiFiDirect::WiFiDirectConnectionRequest;
using ::winrt::Windows::Security::Credentials::PasswordCredential;
// Wifi connection metrics show P90 latency is just under 600ms.
// Assuming the hotspot connection latency is similar to the wifi
// connection latency.
constexpr absl::Duration kConnectTimeout = absl::Milliseconds(700);
} // namespace
WifiHotspotMedium::~WifiHotspotMedium() {
StopWifiHotspot();
DisconnectWifiHotspot();
}
bool WifiHotspotMedium::IsInterfaceValid() const {
HANDLE wifi_direct_handle = nullptr;
DWORD negotiated_version = 0;
DWORD result = 0;
result =
WFDOpenHandle(WFD_API_VERSION, &negotiated_version, &wifi_direct_handle);
if (result == ERROR_SUCCESS) {
LOG(INFO) << "WiFi can support Hotspot";
WFDCloseHandle(wifi_direct_handle);
return true;
}
LOG(ERROR) << "WiFi can't support Hotspot";
return false;
}
std::unique_ptr<api::WifiHotspotSocket> WifiHotspotMedium::ConnectToService(
const ServiceAddress& service_address,
CancellationFlag* cancellation_flag) {
LOG(WARNING) << __func__ << " : Connect to remote service.";
if (service_address.address.empty() || service_address.port == 0) {
LOG(ERROR) << "no valid service address and port to connect.";
return nullptr;
}
SocketAddress server_address;
if (!server_address.FromServiceAddress(server_address, service_address)) {
LOG(ERROR) << "no valid service address and port to connect.";
return nullptr;
}
if (server_address.IsV6LinkLocal()) {
// Link local address need to be scoped to the wifi interface.
std::optional<uint32_t> wifi_interface_index =
wifi_hotspot_native_.GetWifiInterfaceIndex();
if (!wifi_interface_index.has_value()) {
LOG(ERROR)
<< "Wifi interface index is not available, skip link local address.";
return nullptr;
}
server_address.SetScopeId(wifi_interface_index.value());
}
VLOG(1) << "ConnectToService address: " << server_address.ToString();
LOG(INFO) << "Connecting to service.";
auto wifi_hotspot_socket = std::make_unique<WifiHotspotSocket>();
// setup cancel listener
std::unique_ptr<CancellationFlagListener> connection_cancellation_listener =
nullptr;
if (cancellation_flag != nullptr) {
connection_cancellation_listener =
std::make_unique<nearby::CancellationFlagListener>(
cancellation_flag, [socket = wifi_hotspot_socket.get()]() {
LOG(WARNING) << "connect is closed due to it is cancelled.";
socket->Close();
});
}
bool result = wifi_hotspot_socket->Connect(server_address, kConnectTimeout);
if (!result) {
LOG(ERROR) << "Failed to connect to service.";
return nullptr;
}
LOG(INFO) << "connected to remote service.";
return wifi_hotspot_socket;
}
std::unique_ptr<api::WifiHotspotServerSocket>
WifiHotspotMedium::ListenForService(int port) {
absl::MutexLock lock(mutex_);
LOG(INFO) << __func__
<< " :Start to listen connection from WiFi Hotspot client.";
// check current status
if (IsAccepting()) {
LOG(WARNING) << "accepting connections already started on port "
<< server_socket_ptr_->GetPort();
return nullptr;
}
auto server_socket = std::make_unique<WifiHotspotServerSocket>();
server_socket_ptr_ = server_socket.get();
if (server_socket->Listen(port)) {
medium_status_ |= kMediumStatusAccepting;
// Setup close notifier after listen started.
server_socket->SetCloseNotifier([this]() {
absl::MutexLock lock(mutex_);
LOG(INFO) << "Server socket was closed.";
medium_status_ &= (~kMediumStatusAccepting);
server_socket_ptr_ = nullptr;
});
LOG(INFO) << "Started to listen serive on port "
<< server_socket_ptr_->GetPort();
return server_socket;
}
LOG(ERROR) << "Failed to listen service on port " << port;
return nullptr;
}
bool WifiHotspotMedium::StartWifiHotspot(
HotspotCredentials* hotspot_credentials) {
absl::MutexLock lock(mutex_);
VLOG(1) << __func__ << ": Start to create WiFi Hotspot.";
if (IsBeaconing()) {
LOG(WARNING) << "Cannot create WiFi Hotspot again when it is running.";
return true;
}
try {
publisher_ = WiFiDirectAdvertisementPublisher();
publisher_status_changed_token_ =
publisher_.StatusChanged({this, &WifiHotspotMedium::OnStatusChanged});
listener_ = WiFiDirectConnectionListener();
connection_requested_token_ = listener_.ConnectionRequested(
{this, &WifiHotspotMedium::OnConnectionRequested});
// Enable Autonomous GO mode
publisher_.Advertisement().IsAutonomousGroupOwnerEnabled(true);
// Using WIFIDirect legacy mode to create a softAP. AP means "access point".
Prng prng;
publisher_.Advertisement().LegacySettings().IsEnabled(true);
std::string password = absl::StrFormat("%08x", prng.NextUint32());
hotspot_credentials->SetPassword(password);
PasswordCredential creds;
creds.Password(winrt::to_hstring(password));
publisher_.Advertisement().LegacySettings().Passphrase(creds);
std::string ssid = "DIRECT-" + std::to_string(prng.NextUint32());
hotspot_credentials->SetSSID(ssid);
publisher_.Advertisement().LegacySettings().Ssid(winrt::to_hstring(ssid));
publisher_.Start();
if (publisher_.Status() ==
WiFiDirectAdvertisementPublisherStatus::Started) {
LOG(INFO) << __func__ << ": WiFi Hotspot created and started.";
medium_status_ |= kMediumStatusBeaconing;
if (NearbyFlags::GetInstance().GetBoolFlag(
platform::config_package_nearby::nearby_platform_feature::
kEnableIntelPieSdk)) {
WifiIntel& intel_wifi{WifiIntel::GetInstance()};
if (intel_wifi.Start()) {
int GO_channel = intel_wifi.GetGOChannel();
LOG(INFO) << "Intel PIE enabled, Hotspot is running on channel: "
<< GO_channel;
intel_wifi.Stop();
hotspot_credentials->SetFrequency(
WifiUtils::ConvertChannelToFrequencyMhz(GO_channel,
WifiBandType::kUnknown));
}
} else {
LOG(INFO) << "Intel PIE disabled, Can't extract Hotspot channel info!";
hotspot_credentials->SetFrequency(-1);
}
return true;
}
// Clean up when fail
LOG(ERROR) << "Windows WiFi Hotspot fails to start";
publisher_.StatusChanged(publisher_status_changed_token_);
listener_.ConnectionRequested(connection_requested_token_);
listener_ = nullptr;
publisher_ = nullptr;
return false;
} catch (std::exception exception) {
LOG(ERROR) << __func__
<< ": Cannot start Hotspot. Exception: " << exception.what();
} catch (const winrt::hresult_error& error) {
LOG(ERROR) << __func__
<< ": Cannot start Hotspot. WinRT exception: " << error.code()
<< ": " << winrt::to_string(error.message());
} catch (...) {
LOG(ERROR) << __func__ << ": Unknown exeption.";
}
return false;
}
bool WifiHotspotMedium::StopWifiHotspot() {
// Need to use Win32 API to deregister the Dnssd instance
absl::MutexLock lock(mutex_);
if (!IsBeaconing()) {
LOG(WARNING) << "Cannot stop SoftAP because no SoftAP is started.";
return true;
}
try {
if (publisher_) {
publisher_.Stop();
listener_.ConnectionRequested(connection_requested_token_);
publisher_.StatusChanged(publisher_status_changed_token_);
wifi_direct_devices_.clear();
listener_ = nullptr;
publisher_ = nullptr;
LOG(INFO) << "succeeded to stop WiFi Hotspot";
}
medium_status_ &= (~kMediumStatusBeaconing);
return true;
} catch (std::exception exception) {
LOG(ERROR) << __func__
<< ": Stop Hotspot failed. Exception: " << exception.what();
} catch (const winrt::hresult_error& error) {
LOG(ERROR) << __func__
<< ": Stop Hotspot failed. WinRT exception: " << error.code()
<< ": " << winrt::to_string(error.message());
} catch (...) {
LOG(ERROR) << __func__ << ": Unknown exeption.";
}
return false;
}
fire_and_forget WifiHotspotMedium::OnStatusChanged(
WiFiDirectAdvertisementPublisher sender,
WiFiDirectAdvertisementPublisherStatusChangedEventArgs event) {
if (event.Status() == WiFiDirectAdvertisementPublisherStatus::Started) {
if (sender.Advertisement().LegacySettings().IsEnabled()) {
LOG(INFO) << "WiFi SoftAP SSID: "
<< winrt::to_string(
publisher_.Advertisement().LegacySettings().Ssid());
VLOG(1) << "WiFi SoftAP password: "
<< masker::Mask(winrt::to_string(publisher_.Advertisement()
.LegacySettings()
.Passphrase()
.Password()));
}
return winrt::fire_and_forget();
} else if (event.Status() ==
WiFiDirectAdvertisementPublisherStatus::Created) {
LOG(INFO) << "Receive WiFi direct/SoftAP Created event.";
return winrt::fire_and_forget();
} else if (event.Status() ==
WiFiDirectAdvertisementPublisherStatus::Stopped) {
LOG(INFO) << "Receive WiFi direct/SoftAP Stopped event.";
} else if (event.Status() ==
WiFiDirectAdvertisementPublisherStatus::Aborted) {
LOG(INFO) << "Receive WiFi direct/SoftAP Aborted event.";
}
// Publisher is stopped. Need to clean up the publisher.
{
absl::MutexLock lock(mutex_);
if (publisher_ != nullptr) {
LOG(ERROR) << "Windows WiFi Hotspot cleanup.";
listener_.ConnectionRequested(connection_requested_token_);
publisher_.StatusChanged(publisher_status_changed_token_);
wifi_direct_devices_.clear();
listener_ = nullptr;
publisher_ = nullptr;
}
}
return winrt::fire_and_forget();
}
fire_and_forget WifiHotspotMedium::OnConnectionRequested(
WiFiDirectConnectionListener const& sender,
WiFiDirectConnectionRequestedEventArgs const& event) {
WiFiDirectConnectionRequest connection_request = event.GetConnectionRequest();
winrt::hstring device_name = connection_request.DeviceInformation().Name();
LOG(INFO) << "Receive connection request from: "
<< winrt::to_string(device_name);
try {
// This is to solve b/236805122.
// Problem: [Microsoft-Windows-WLAN-AutoConfig] issues a disconnection to
// Hotspot Client every 2 minutes and stopped Hotspot eventually.
// Solution: Creating a WiFiDirectDevice for Clients connection request can
// solve the problem. Guess when this object is created,
// [Microsoft-Windows-WLAN-AutoConfig] will recognise it as a valid device
// and won't kick it away.
// We found new connection request comes in during hotspot transfer. In this
// case, we should create a new WiFiDirectDevice for it. It will cause
// transfer failure if replace the old WiFiDirectDevice with it.
auto wifi_direct_device = WiFiDirectDevice::FromIdAsync(
connection_request.DeviceInformation().Id())
.get();
{
absl::MutexLock lock(mutex_);
wifi_direct_devices_.push_back(wifi_direct_device);
}
LOG(INFO) << "Registered the device " << winrt::to_string(device_name)
<< " in WLAN-AutoConfig";
} catch (...) {
LOG(ERROR) << "Failed to registered the device "
<< winrt::to_string(device_name) << " in WLAN-AutoConfig";
connection_request.Close();
}
return winrt::fire_and_forget();
}
bool WifiHotspotMedium::ConnectWifiHotspot(
const HotspotCredentials& hotspot_credentials) {
absl::MutexLock lock(mutex_);
std::string ssid = hotspot_credentials.GetSSID();
std::string password = hotspot_credentials.GetPassword();
try {
if (IsConnected()) {
LOG(WARNING) << "Already connected to Hotspot, disconnect first.";
wifi_hotspot_native_.DisconnectWifiNetwork();
}
// Initialize Intel PIE scan if it is installed.
bool intel_wifi_started = false;
if (NearbyFlags::GetInstance().GetBoolFlag(
platform::config_package_nearby::nearby_platform_feature::
kEnableIntelPieSdk)) {
auto channel = WifiUtils::ConvertFrequencyMhzToChannel(
hotspot_credentials.GetFrequency());
WifiIntel& intel_wifi{WifiIntel::GetInstance()};
intel_wifi_started = intel_wifi.Start();
if (intel_wifi_started) {
intel_wifi.SetScanFilter(channel);
}
}
bool connected =
wifi_hotspot_native_.ConnectToWifiNetwork(ssid, password);
if (intel_wifi_started) {
WifiIntel& intel_wifi{WifiIntel::GetInstance()};
intel_wifi.ResetScanFilter();
intel_wifi.Stop();
}
if (!connected) {
LOG(INFO) << "Failed to connect to Hotspot.";
wifi_hotspot_native_.RestoreWifiProfile();
return false;
}
LOG(INFO) << "Connected to Hotspot successfully.";
// Make sure IP address is ready.
bool has_address = false;
bool has_ipv6_candidates = false;
for (const auto& address_candidate :
hotspot_credentials.GetAddressCandidates()) {
if (address_candidate.address.size() == 16) {
has_ipv6_candidates = true;
break;
}
}
int64_t ip_address_max_retries = NearbyFlags::GetInstance().GetInt64Flag(
platform::config_package_nearby::nearby_platform_feature::
kWifiHotspotCheckIpMaxRetries);
int64_t ip_address_retry_interval_millis =
NearbyFlags::GetInstance().GetInt64Flag(
platform::config_package_nearby::nearby_platform_feature::
kWifiHotspotCheckIpIntervalMillis);
absl::Duration connection_timeout =
Milliseconds(ip_address_retry_interval_millis) * ip_address_max_retries;
VLOG(1) << "maximum IP check retries=" << ip_address_max_retries
<< ", IP check interval=" << ip_address_retry_interval_millis
<< "ms, timeout=" << connection_timeout;
absl::Time start_time = SystemClock::ElapsedRealtime();;
for (int i = 0; i < ip_address_max_retries; i++) {
LOG(INFO) << "Check IP address at attempt " << i;
if (wifi_hotspot_native_.HasAssignedAddress(has_ipv6_candidates)) {
has_address = true;
break;
}
// Keep track of time spent waiting for connection as RenewIpv4Address()
// can take a while and only relying on retry count can increase the time
// spent waiting significantly.
if (SystemClock::ElapsedRealtime() - start_time > connection_timeout) {
LOG(WARNING) << "Timeout getting IP address from hotspot.";
break;
}
// IP address is assigned, check for address right away.
if (!wifi_hotspot_native_.RenewIpv4Address()) {
Sleep(ip_address_retry_interval_millis);
}
}
if (!has_address) {
LOG(WARNING) << "Failed to get IP address from hotspot.";
wifi_hotspot_native_.RestoreWifiProfile();
return false;
}
medium_status_ |= kMediumStatusConnected;
LOG(INFO) << "Connected to hotspot: " << ssid;
return true;
} catch (std::exception exception) {
LOG(ERROR) << __func__
<< ": Cannot connet to Hotspot. Exception: " << exception.what();
} catch (...) {
LOG(ERROR) << __func__ << ": Unknown exception.";
}
return false;
}
bool WifiHotspotMedium::DisconnectWifiHotspot() {
absl::MutexLock lock(mutex_);
if (!IsConnected()) {
LOG(WARNING) << "Cannot disconnect SoftAP because it is not connected.";
return true;
}
wifi_hotspot_native_.RestoreWifiProfile();
medium_status_ &= (~kMediumStatusConnected);
LOG(INFO) << __func__ << ": Disconnected to hotspot successfully.";
return true;
}
std::string WifiHotspotMedium::GetErrorMessage(std::exception_ptr eptr) {
try {
if (eptr) {
std::rethrow_exception(eptr);
} else {
return "";
}
} catch (const std::exception& e) {
return e.what();
}
}
} // namespace nearby::windows