mirror of
https://github.com/kidfromjupiter/nearby.git
synced 2026-09-16 07:36:10 -04:00
533 lines
16 KiB
C++
533 lines
16 KiB
C++
// Copyright 2024 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/implementation/windows/wifi_hotspot_native.h"
|
|
|
|
// clang-format off
|
|
#include <windows.h>
|
|
#include <winsock2.h>
|
|
#include <wlanapi.h>
|
|
#include <cguid.h>
|
|
// clang-format on
|
|
|
|
#include <cstring>
|
|
#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/platform/count_down_latch.h"
|
|
#include "internal/platform/exception.h"
|
|
#include "internal/platform/implementation/windows/string_utils.h"
|
|
#include "internal/platform/logging.h"
|
|
#include "internal/platform/wifi_credential.h"
|
|
|
|
namespace nearby {
|
|
namespace windows {
|
|
|
|
namespace {
|
|
constexpr absl::Duration kConnectTimeout = absl::Seconds(15);
|
|
constexpr char kProfileTemplate[] =
|
|
R"(<?xml version="1.0"?>
|
|
<WLANProfile xmlns = "http://www.microsoft.com/networking/WLAN/profile/v1" >
|
|
<name>%s</name>
|
|
<SSIDConfig>
|
|
<SSID>
|
|
<name>%s</name>
|
|
</SSID>
|
|
</SSIDConfig>
|
|
<connectionType>ESS</connectionType>
|
|
<connectionMode>auto</connectionMode>
|
|
<MSM>
|
|
<security>
|
|
<authEncryption>
|
|
<authentication>WPA2PSK</authentication>
|
|
<encryption>AES</encryption>
|
|
<useOneX>false</useOneX>
|
|
</authEncryption>
|
|
<sharedKey>
|
|
<keyType>passPhrase</keyType>
|
|
<protected>false</protected>
|
|
<keyMaterial>%s</keyMaterial>
|
|
</sharedKey>
|
|
</security>
|
|
</MSM>
|
|
</WLANProfile>)";
|
|
} // namespace
|
|
|
|
WifiHotspotNative::WifiHotspotNative() {
|
|
// Open WLAN handle
|
|
DWORD negotiated_version;
|
|
DWORD result = WlanOpenHandle(/*dwClientVersion=*/2, /*pReserved=*/nullptr,
|
|
/*pdwNegotiatedVersion=*/&negotiated_version,
|
|
/*phClientHandle=*/&wifi_);
|
|
|
|
if (result != ERROR_SUCCESS) {
|
|
LOG(ERROR) << "Failed to open WLAN handle.";
|
|
return;
|
|
}
|
|
|
|
VLOG(1) << "WifiHotspotNative created successfully.";
|
|
}
|
|
|
|
WifiHotspotNative::~WifiHotspotNative() {
|
|
if (wifi_ != nullptr) {
|
|
WlanCloseHandle(wifi_, nullptr);
|
|
wifi_ = nullptr;
|
|
}
|
|
|
|
VLOG(1) << "WifiHotspotNative destroyed successfully.";
|
|
}
|
|
|
|
bool WifiHotspotNative::ConnectToWifiNetwork(
|
|
HotspotCredentials* hotspot_credentials) {
|
|
absl::MutexLock lock(&mutex_);
|
|
|
|
if (GetInterfaceGuid() == GUID_NULL) {
|
|
LOG(ERROR) << "No available WLAN Interface to use.";
|
|
return false;
|
|
}
|
|
|
|
if (!SetWlanProfile(hotspot_credentials)) {
|
|
LOG(ERROR) << "Failed to set WLAN profile.";
|
|
return false;
|
|
}
|
|
|
|
if (!ConnectToWifiNetworkInternal(created_profile_name_)) {
|
|
return false;
|
|
}
|
|
|
|
LOG(ERROR) << "Connect to Wifi network successfully.";
|
|
return true;
|
|
}
|
|
|
|
bool WifiHotspotNative::ConnectToWifiNetwork(const std::wstring& profile_name) {
|
|
absl::MutexLock lock(&mutex_);
|
|
|
|
if (GetInterfaceGuid() == GUID_NULL) {
|
|
LOG(ERROR) << "No available WLAN Interface to use.";
|
|
return false;
|
|
}
|
|
|
|
if (!ConnectToWifiNetworkInternal(profile_name)) {
|
|
return false;
|
|
}
|
|
|
|
RemoveWlanProfile();
|
|
LOG(ERROR) << "Connect to Wifi network successfully.";
|
|
return true;
|
|
}
|
|
|
|
bool WifiHotspotNative::DisconnectWifiNetwork() {
|
|
absl::MutexLock lock(&mutex_);
|
|
|
|
GUID interface_guid = GetInterfaceGuid();
|
|
if (interface_guid == GUID_NULL) {
|
|
LOG(ERROR) << "No available WLAN Interface to use.";
|
|
return false;
|
|
}
|
|
|
|
std::optional<std::wstring> connected_profile_name =
|
|
GetConnectedProfileNameInternal();
|
|
if (!connected_profile_name.has_value()) {
|
|
LOG(ERROR) << "Not connected to any WLAN network.";
|
|
RemoveWlanProfile();
|
|
return false;
|
|
}
|
|
|
|
DWORD result = WlanDisconnect(
|
|
/*hClientHandle=*/wifi_, /*pInterfaceGuid=*/&interface_guid,
|
|
/*pReserved=*/nullptr);
|
|
if (result != ERROR_SUCCESS) {
|
|
LOG(ERROR) << "Failed to disconnect WLAN profile with error: " << result;
|
|
RemoveWlanProfile();
|
|
return false;
|
|
}
|
|
|
|
RemoveWlanProfile();
|
|
LOG(ERROR) << "Disconnect Wifi hotspot successfully.";
|
|
return true;
|
|
}
|
|
|
|
std::optional<std::wstring> WifiHotspotNative::GetConnectedProfileName() const {
|
|
absl::MutexLock lock(&mutex_);
|
|
return GetConnectedProfileNameInternal();
|
|
}
|
|
|
|
bool WifiHotspotNative::DeleteWifiProfile(const std::wstring& profile_name) {
|
|
absl::MutexLock lock(&mutex_);
|
|
GUID interface_guid = GetInterfaceGuid();
|
|
if (interface_guid == GUID_NULL) {
|
|
LOG(ERROR) << "No available WLAN Interface to use.";
|
|
return false;
|
|
}
|
|
|
|
DWORD result = WlanDeleteProfile(
|
|
/*hClientHandle=*/wifi_,
|
|
/*pInterfaceGuid=*/&interface_guid,
|
|
/*strProfileName=*/profile_name.data(),
|
|
/*pReserved=*/nullptr);
|
|
|
|
if (result != ERROR_SUCCESS) {
|
|
LOG(ERROR) << "Failed to delete WLAN profile "
|
|
<< string_utils::WideStringToString(profile_name)
|
|
<< " with reason" << result;
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool WifiHotspotNative::Scan(absl::string_view ssid) {
|
|
absl::MutexLock lock(&mutex_);
|
|
|
|
GUID interface_guid = GetInterfaceGuid();
|
|
if (interface_guid == GUID_NULL) {
|
|
LOG(ERROR) << "No available WLAN Interface to use.";
|
|
return false;
|
|
}
|
|
|
|
if (!RegisterWlanNotificationCallback()) {
|
|
LOG(ERROR) << "Failed to register WLAN notification callback.";
|
|
return false;
|
|
}
|
|
|
|
if (ssid.length() > DOT11_SSID_MAX_LENGTH) {
|
|
LOG(ERROR) << "Invalid SSID length.";
|
|
return false;
|
|
}
|
|
|
|
DOT11_SSID dot11_ssid;
|
|
dot11_ssid.uSSIDLength = ssid.length();
|
|
memcpy(dot11_ssid.ucSSID, ssid.data(), dot11_ssid.uSSIDLength);
|
|
scan_latch_ = std::make_unique<CountDownLatch>(1);
|
|
scanning_ssid_ = ssid;
|
|
DWORD result = WlanScan(
|
|
/*hClientHandle=*/wifi_, /*pInterfaceGuid=*/&interface_guid,
|
|
/*pDot11Ssid=*/&dot11_ssid, /*pIeData=*/nullptr, /*pReserved=*/nullptr);
|
|
|
|
if (result != ERROR_SUCCESS) {
|
|
LOG(ERROR) << "Failed to scan Wi-Fi network with error " << result;
|
|
UnregisterWlanNotificationCallback();
|
|
return false;
|
|
}
|
|
|
|
ExceptionOr<bool> scan_result = scan_latch_->Await(kConnectTimeout);
|
|
UnregisterWlanNotificationCallback();
|
|
|
|
if (!scan_result.ok() || !scan_result.result()) {
|
|
LOG(ERROR) << "Failed to scan to Wifi network " << ssid;
|
|
return false;
|
|
}
|
|
|
|
scan_latch_ = nullptr;
|
|
return true;
|
|
}
|
|
|
|
void WifiHotspotNative::TriggerConnected(const std::wstring& profile_name) {
|
|
if (connect_latch_ == nullptr) {
|
|
return;
|
|
}
|
|
|
|
if (profile_name != connecting_profile_name_) {
|
|
return;
|
|
}
|
|
|
|
LOG(INFO) << "Connected to expected WLAN network";
|
|
connect_latch_->CountDown();
|
|
}
|
|
|
|
void WifiHotspotNative::TriggerNetworkRefreshed() {
|
|
if (scan_latch_ == nullptr) {
|
|
return;
|
|
}
|
|
|
|
GUID interface_guid = GetInterfaceGuid();
|
|
if (interface_guid == GUID_NULL) {
|
|
LOG(ERROR) << "No available WLAN Interface to use.";
|
|
return;
|
|
}
|
|
|
|
PWLAN_AVAILABLE_NETWORK_LIST list = nullptr;
|
|
DWORD result = WlanGetAvailableNetworkList(
|
|
/*hClientHandle=*/wifi_, /*pInterfaceGuid=*/&interface_guid,
|
|
/*dwFlags=*/WLAN_AVAILABLE_NETWORK_INCLUDE_ALL_ADHOC_PROFILES,
|
|
/*pReserved=*/nullptr, /*ppAvailableNetworkList=*/&list);
|
|
if (result != ERROR_SUCCESS) {
|
|
return;
|
|
}
|
|
|
|
bool found_ssid = false;
|
|
for (int i = 0; i < list->dwNumberOfItems; i++) {
|
|
std::string ssid = std::string((char*)list->Network[i].dot11Ssid.ucSSID,
|
|
list->Network[i].dot11Ssid.uSSIDLength);
|
|
if (ssid == scanning_ssid_) {
|
|
found_ssid = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
WlanFreeMemory(list);
|
|
|
|
if (found_ssid) {
|
|
LOG(INFO) << "Found WLAN network " << scanning_ssid_;
|
|
scan_latch_->CountDown();
|
|
}
|
|
}
|
|
|
|
void WifiHotspotNative::WlanNotificationCallback(
|
|
PWLAN_NOTIFICATION_DATA wlan_notification_data, PVOID context) {
|
|
VLOG(1) << "WlanNotificationCallback is called with notification code "
|
|
<< wlan_notification_data->NotificationCode;
|
|
|
|
if (wlan_notification_data->NotificationCode ==
|
|
wlan_notification_acm_connection_complete) {
|
|
// Make sure the connected WLAN profile is the one we set.
|
|
PWLAN_CONNECTION_NOTIFICATION_DATA data =
|
|
static_cast<PWLAN_CONNECTION_NOTIFICATION_DATA>(
|
|
wlan_notification_data->pData);
|
|
WifiHotspotNative* wifi_hotspot_native =
|
|
static_cast<WifiHotspotNative*>(context);
|
|
std::wstring profile_name = std::wstring(data->strProfileName);
|
|
LOG(INFO) << "Connected to Wifi hotspot "
|
|
<< string_utils::WideStringToString(profile_name);
|
|
wifi_hotspot_native->TriggerConnected(profile_name);
|
|
} else if (wlan_notification_data->NotificationCode ==
|
|
wlan_notification_acm_scan_list_refresh) {
|
|
LOG(INFO) << "Scan list refreshed.";
|
|
WifiHotspotNative* wifi_hotspot_native =
|
|
static_cast<WifiHotspotNative*>(context);
|
|
wifi_hotspot_native->TriggerNetworkRefreshed();
|
|
}
|
|
}
|
|
|
|
std::wstring WifiHotspotNative::BuildWlanProfile(absl::string_view ssid,
|
|
absl::string_view password) {
|
|
std::string profile = absl::StrFormat(kProfileTemplate, ssid, ssid, password);
|
|
return string_utils::StringToWideString(profile);
|
|
}
|
|
|
|
GUID WifiHotspotNative::GetInterfaceGuid() const {
|
|
if (wifi_ == nullptr) {
|
|
return GUID_NULL;
|
|
}
|
|
|
|
// Find WLAN interface, only support one interface for now.
|
|
PWLAN_INTERFACE_INFO_LIST interfaces = nullptr;
|
|
DWORD result =
|
|
WlanEnumInterfaces(/*hClientHandle=*/wifi_, /*pReserved=*/nullptr,
|
|
/*ppInterfaceList=*/&interfaces);
|
|
if (result != ERROR_SUCCESS) {
|
|
LOG(ERROR) << "Failed to enum WLAN interfaces.";
|
|
return GUID_NULL;
|
|
}
|
|
|
|
if (interfaces->dwNumberOfItems == 0) {
|
|
LOG(ERROR) << "No WLAN interfaces found.";
|
|
WlanFreeMemory(interfaces);
|
|
return GUID_NULL;
|
|
}
|
|
|
|
GUID interface_guid = interfaces->InterfaceInfo[0].InterfaceGuid;
|
|
WlanFreeMemory(interfaces);
|
|
return interface_guid;
|
|
}
|
|
|
|
bool WifiHotspotNative::ConnectToWifiNetworkInternal(
|
|
const std::wstring& profile_name) {
|
|
if (profile_name.empty()) {
|
|
LOG(ERROR) << "Profile name is empty.";
|
|
return false;
|
|
}
|
|
|
|
GUID interface_guid = GetInterfaceGuid();
|
|
if (interface_guid == GUID_NULL) {
|
|
LOG(ERROR) << "No available WLAN Interface to use.";
|
|
return false;
|
|
}
|
|
|
|
if (!RegisterWlanNotificationCallback()) {
|
|
LOG(ERROR) << "Failed to register WLAN notification callback.";
|
|
return false;
|
|
}
|
|
|
|
connect_latch_ = std::make_unique<CountDownLatch>(1);
|
|
|
|
WLAN_CONNECTION_PARAMETERS parameters;
|
|
parameters.wlanConnectionMode = wlan_connection_mode_profile;
|
|
parameters.strProfile = profile_name.data();
|
|
parameters.pDot11Ssid = nullptr;
|
|
parameters.pDesiredBssidList = nullptr;
|
|
parameters.dot11BssType = dot11_BSS_type_infrastructure;
|
|
parameters.dwFlags = 0;
|
|
|
|
connecting_profile_name_ = profile_name;
|
|
|
|
DWORD result =
|
|
WlanConnect(/*hClientHandle=*/wifi_, /*pInterfaceGuid=*/&interface_guid,
|
|
/*pConnectionParameters=*/¶meters, /*pReserved=*/nullptr);
|
|
if (result != ERROR_SUCCESS) {
|
|
LOG(ERROR) << "Failed to connect to WLAN profile "
|
|
<< string_utils::WideStringToString(created_profile_name_);
|
|
UnregisterWlanNotificationCallback();
|
|
return false;
|
|
}
|
|
|
|
// Make sure that we connect to the expected profile.
|
|
ExceptionOr<bool> connect_result = connect_latch_->Await(kConnectTimeout);
|
|
UnregisterWlanNotificationCallback();
|
|
|
|
if (!connect_result.ok() || !connect_result.result()) {
|
|
LOG(ERROR) << "Connect to Wifi hotspot timed out.";
|
|
return false;
|
|
}
|
|
|
|
connect_latch_ = nullptr;
|
|
return true;
|
|
}
|
|
|
|
bool WifiHotspotNative::RegisterWlanNotificationCallback() {
|
|
DWORD result = WlanRegisterNotification(
|
|
/*hClientHandle=*/wifi_, /*dwNotifSource=*/WLAN_NOTIFICATION_SOURCE_ACM,
|
|
/*bIgnoreDuplicate=*/TRUE,
|
|
/*funcCallback=*/WifiHotspotNative::WlanNotificationCallback,
|
|
/*pCallbackContext=*/this, /*pReserved=*/nullptr,
|
|
/*pdwPrevNotifSource=*/nullptr);
|
|
if (result != ERROR_SUCCESS) {
|
|
LOG(ERROR) << "Failed to register WLAN notification with error: " << result;
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool WifiHotspotNative::UnregisterWlanNotificationCallback() {
|
|
DWORD result = WlanRegisterNotification(
|
|
/*hClientHandle=*/wifi_, /*dwNotifSource=*/WLAN_NOTIFICATION_SOURCE_NONE,
|
|
/*bIgnoreDuplicate=*/TRUE,
|
|
/*funcCallback=*/nullptr, /*pCallbackContext=*/nullptr,
|
|
/*pReserved=*/nullptr, /*pdwPrevNotifSource=*/nullptr);
|
|
if (result != ERROR_SUCCESS) {
|
|
LOG(ERROR) << "Failed to unregister WLAN notification with error: "
|
|
<< result;
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool WifiHotspotNative::SetWlanProfile(
|
|
HotspotCredentials* hotspot_credentials) {
|
|
DWORD reason = 0;
|
|
|
|
GUID interface_guid = GetInterfaceGuid();
|
|
if (interface_guid == GUID_NULL) {
|
|
LOG(ERROR) << "No available WLAN Interface to use.";
|
|
return false;
|
|
}
|
|
|
|
std::wstring profile = BuildWlanProfile(hotspot_credentials->GetSSID(),
|
|
hotspot_credentials->GetPassword());
|
|
DWORD result = WlanSetProfile(
|
|
/*hClientHandle=*/wifi_, /*pInterfaceGuid=*/&interface_guid,
|
|
/*dwFlags=*/WLAN_PROFILE_USER, /*strProfileXml=*/profile.data(),
|
|
/*strAllUserProfileSecurity=*/nullptr, /*bOverwrite=*/TRUE,
|
|
/*pReserved=*/nullptr, /*pdwReasonCode*/ &reason);
|
|
if (result != ERROR_SUCCESS) {
|
|
LOG(ERROR) << "Failed to set WLAN profile with reason" << result;
|
|
return false;
|
|
}
|
|
|
|
created_profile_name_ =
|
|
string_utils::StringToWideString(hotspot_credentials->GetSSID());
|
|
|
|
LOG(INFO) << "Set WLAN profile "
|
|
<< string_utils::WideStringToString(created_profile_name_)
|
|
<< " successfully.";
|
|
return true;
|
|
}
|
|
|
|
bool WifiHotspotNative::RemoveWlanProfile() {
|
|
if (created_profile_name_.empty()) {
|
|
return false;
|
|
}
|
|
|
|
GUID interface_guid = GetInterfaceGuid();
|
|
if (interface_guid == GUID_NULL) {
|
|
LOG(ERROR) << "No available WLAN Interface to use.";
|
|
return false;
|
|
}
|
|
|
|
DWORD result = WlanDeleteProfile(
|
|
/*hClientHandle=*/wifi_, /*pInterfaceGuid=*/&interface_guid,
|
|
/*strProfileName=*/created_profile_name_.data(), /*pReserved=*/nullptr);
|
|
|
|
if (result != ERROR_SUCCESS) {
|
|
LOG(ERROR) << "Failed to remove WLAN profile "
|
|
<< string_utils::WideStringToString(created_profile_name_)
|
|
<< " with reason" << result;
|
|
return false;
|
|
}
|
|
|
|
LOG(INFO) << "WLAN profile "
|
|
<< string_utils::WideStringToString(created_profile_name_)
|
|
<< " removed successfully.";
|
|
|
|
created_profile_name_ = L"";
|
|
return true;
|
|
}
|
|
|
|
std::optional<std::wstring> WifiHotspotNative::GetConnectedProfileNameInternal()
|
|
const {
|
|
PWLAN_INTERFACE_INFO_LIST interfaces = nullptr;
|
|
DWORD result =
|
|
WlanEnumInterfaces(/*hClientHandle=*/wifi_, /*pReserved=*/nullptr,
|
|
/*ppInterfaceList=*/&interfaces);
|
|
if (result != ERROR_SUCCESS) {
|
|
LOG(ERROR) << "Failed to enum WLAN interfaces with error: " << result;
|
|
return std::nullopt;
|
|
}
|
|
|
|
for (DWORD i = 0; i < interfaces->dwNumberOfItems; i++) {
|
|
WLAN_CONNECTION_ATTRIBUTES* connect_info = nullptr;
|
|
DWORD connect_info_size = sizeof(WLAN_CONNECTION_ATTRIBUTES);
|
|
result = WlanQueryInterface(
|
|
/*hClientHandle=*/wifi_,
|
|
/*pInterfaceGuid=*/&interfaces->InterfaceInfo[i].InterfaceGuid,
|
|
/*OpCode=*/wlan_intf_opcode_current_connection, /*pReserved=*/nullptr,
|
|
/*pdwDataSize=*/&connect_info_size,
|
|
/*ppData=*/(PVOID*)&connect_info, /*pWlanOpcodeValueType=*/nullptr);
|
|
if (result == ERROR_SUCCESS && connect_info != nullptr) {
|
|
if (connect_info->isState == wlan_interface_state_connected) {
|
|
std::wstring profile_name = std::wstring(connect_info->strProfileName);
|
|
WlanFreeMemory(connect_info);
|
|
if (interfaces != nullptr) {
|
|
WlanFreeMemory(interfaces);
|
|
}
|
|
return profile_name;
|
|
}
|
|
WlanFreeMemory(connect_info);
|
|
}
|
|
}
|
|
|
|
WlanFreeMemory(interfaces);
|
|
return std::nullopt;
|
|
}
|
|
|
|
} // namespace windows
|
|
} // namespace nearby
|