// 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 "platform/impl/windows/wifi_lan.h" // Windows headers #include // Standard C/C++ headers #include #include #include // ABSL headers #include "absl/strings/str_format.h" // Nearby connections headers #include "platform/base/cancellation_flag_listener.h" #include "platform/impl/windows/utils.h" #include "platform/public/logging.h" #include "platform/public/mutex_lock.h" namespace location { namespace nearby { namespace windows { bool WifiLanMedium::StartAdvertising(const NsdServiceInfo& nsd_service_info) { absl::MutexLock lock(&mutex_); if (!IsAccepting()) { NEARBY_LOGS(WARNING) << "cannot start advertising without accepting connetions."; return false; } if (IsAdvertising()) { NEARBY_LOGS(WARNING) << "cannot start advertising again when it is running."; return false; } if (nsd_service_info.GetTxtRecord(KEY_ENDPOINT_INFO.data()).empty()) { NEARBY_LOGS(ERROR) << "cannot start advertising without endpoint info."; return false; } if (nsd_service_info.GetServiceName().empty()) { NEARBY_LOGS(ERROR) << "cannot start advertising without service name."; return false; } std::string instance_name = absl::StrFormat( MDNS_INSTANCE_NAME_FORMAT.data(), nsd_service_info.GetServiceName(), nsd_service_info.GetServiceType()); NEARBY_LOGS(INFO) << "mDNS instance name is " << instance_name; dnssd_service_instance_ = DnssdServiceInstance{ string_to_wstring(instance_name), nullptr, // let windows use default computer's local name (uint16)nsd_service_info.GetPort()}; // Add TextRecords from NsdServiceInfo auto text_attributes = dnssd_service_instance_.TextAttributes(); auto text_records = nsd_service_info.GetTxtRecords(); auto it = text_records.begin(); while (it != text_records.end()) { text_attributes.Insert(string_to_wstring(it->first), string_to_wstring(it->second)); it++; } dnssd_regirstraion_result_ = dnssd_service_instance_ .RegisterStreamSocketListenerAsync( server_socket_ptr_->GetSocketListener()) .get(); if (dnssd_regirstraion_result_.HasInstanceNameChanged()) { NEARBY_LOGS(WARNING) << "advertising instance name was changed due to have " "same name instance was running."; // stop the service and return false StopAdvertising(nsd_service_info); return false; } if (dnssd_regirstraion_result_.Status() == DnssdRegistrationStatus::Success) { NEARBY_LOGS(INFO) << "started to advertising."; medium_status_ |= MEDIUM_STATUS_ADVERTISING; return true; } // Clean up NEARBY_LOGS(ERROR) << "failed to start advertising due to registration failure."; dnssd_service_instance_ = nullptr; dnssd_regirstraion_result_ = nullptr; return false; } // Win32 call only can use globel function or static method in class void WifiLanMedium::Advertising_StopCompleted(DWORD Status, PVOID pQueryContext, PDNS_SERVICE_INSTANCE pInstance) { NEARBY_LOGS(INFO) << "unregister with status=" << Status; try { WifiLanMedium* medium = static_cast(pQueryContext); medium->NotifyDnsServiceUnregistered(Status); } catch (...) { NEARBY_LOGS(ERROR) << "failed to notify the stop of DNS service instance." << Status; } } void WifiLanMedium::NotifyDnsServiceUnregistered(DWORD status) { if (dns_service_stop_latch_.get() != nullptr) { dns_service_stop_status_ = status; dns_service_stop_latch_.get()->CountDown(); } } bool WifiLanMedium::StopAdvertising(const NsdServiceInfo& nsd_service_info) { // Need to use Win32 API to deregister the Dnssd instance if (!IsAdvertising()) { NEARBY_LOGS(WARNING) << "Cannot stop advertising because no advertising is running."; return false; } // Init DNS service instance std::string instance_name = absl::StrFormat( MDNS_INSTANCE_NAME_FORMAT.data(), nsd_service_info.GetServiceName(), nsd_service_info.GetServiceType()); int port = nsd_service_info.GetPort(); dns_service_instance_name_ = std::make_unique(string_to_wstring(instance_name)); dns_service_instance_.pszInstanceName = (LPWSTR)dns_service_instance_name_->c_str(); dns_service_instance_.pszHostName = (LPWSTR)MDNS_HOST_NAME.data(); dns_service_instance_.wPort = port; // Init DNS service register request dns_service_register_request_.Version = DNS_QUERY_REQUEST_VERSION1; dns_service_register_request_.InterfaceIndex = 0; // all interfaces will be considered dns_service_register_request_.unicastEnabled = false; dns_service_register_request_.hCredentials = NULL; dns_service_register_request_.pServiceInstance = &dns_service_instance_; dns_service_register_request_.pQueryContext = this; // callback use it dns_service_register_request_.pRegisterCompletionCallback = WifiLanMedium::Advertising_StopCompleted; dns_service_stop_latch_ = std::make_unique(1); DWORD status = DnsServiceDeRegister(&dns_service_register_request_, nullptr); if (status != DNS_REQUEST_PENDING) { NEARBY_LOGS(ERROR) << "failed to stop mDNS advertising for service type =" << nsd_service_info.GetServiceType(); return false; } // Wait for stop finish dns_service_stop_latch_.get()->Await(); dns_service_stop_latch_ = nullptr; if (dns_service_stop_status_ != 0) { NEARBY_LOGS(INFO) << "failed to stop mDNS advertising for service type =" << nsd_service_info.GetServiceType(); return false; } NEARBY_LOGS(INFO) << "succeeded to stop mDNS advertising for service type =" << nsd_service_info.GetServiceType(); medium_status_ &= (~MEDIUM_STATUS_ADVERTISING); return true; } // Returns true once the WifiLan discovery has been initiated. bool WifiLanMedium::StartDiscovery(const std::string& service_type, DiscoveredServiceCallback callback) { if (IsDiscovering()) { NEARBY_LOGS(WARNING) << "discovery already running for service type =" << service_type; return false; } std::string selector = absl::StrFormat(MDNS_DEVICE_SELECTOR_FORMAT.data(), service_type); std::vector requestedProperties{ L"System.Devices.IpAddress", L"System.Devices.Dnssd.HostName", L"System.Devices.Dnssd.InstanceName", L"System.Devices.Dnssd.PortNumber", L"System.Devices.Dnssd.ServiceName", L"System.Devices.Dnssd.TextAttributes"}; device_watcher_ = DeviceInformation::CreateWatcher( string_to_wstring(selector), requestedProperties, DeviceInformationKind::AssociationEndpointService); device_watcher_added_event_token = device_watcher_.Added({this, &WifiLanMedium::Watcher_DeviceAdded}); device_watcher_updated_event_token = device_watcher_.Updated({this, &WifiLanMedium::Watcher_DeviceUpdated}); device_watcher_removed_event_token = device_watcher_.Removed({this, &WifiLanMedium::Watcher_DeviceRemoved}); device_watcher_.Start(); discovered_service_callback_ = std::move(callback); medium_status_ |= MEDIUM_STATUS_DISCOVERING; NEARBY_LOGS(INFO) << "started to discovery."; return true; } // Returns true once WifiLan discovery for service_id is well and truly // stopped; after this returns, there must be no more invocations of the // DiscoveredServiceCallback passed in to StartDiscovery() for service_id. bool WifiLanMedium::StopDiscovery(const std::string& service_type) { if (!IsDiscovering()) { NEARBY_LOGS(WARNING) << "no discovering service to stop."; return false; } device_watcher_.Stop(); device_watcher_.Added(device_watcher_added_event_token); device_watcher_.Updated(device_watcher_updated_event_token); device_watcher_.Removed(device_watcher_removed_event_token); medium_status_ &= (~MEDIUM_STATUS_DISCOVERING); device_watcher_ = nullptr; return true; } std::unique_ptr WifiLanMedium::ConnectToService( const NsdServiceInfo& remote_service_info, CancellationFlag* cancellation_flag) { NEARBY_LOGS(ERROR) << "connect to service by NSD service info. service type is " << remote_service_info.GetServiceType(); return ConnectToService(remote_service_info.GetIPAddress(), remote_service_info.GetPort(), cancellation_flag); } std::unique_ptr WifiLanMedium::ConnectToService( const std::string& ip_address, int port, CancellationFlag* cancellation_flag) { if (ip_address.empty() || port == 0) { NEARBY_LOGS(ERROR) << "no valid service address and port to connect."; return nullptr; } HostName host_name{string_to_wstring(ip_address)}; winrt::hstring service_name{winrt::to_hstring(port)}; StreamSocket socket{}; // setup cancel listener if (cancellation_flag != nullptr) { if (cancellation_flag->Cancelled()) { NEARBY_LOGS(INFO) << "connect has been cancelled to service " << ip_address << ":" << port; return nullptr; } location::nearby::CancellationFlagListener cancellationFlagListener( cancellation_flag, [socket]() { socket.CancelIOAsync().get(); }); } // connection to the service try { socket.ConnectAsync(host_name, service_name).get(); // connected need to keep connection std::unique_ptr wifi_lan_socket = std::make_unique(socket); NEARBY_LOGS(INFO) << "connected to remote service " << ip_address << ":" << port; return wifi_lan_socket; } catch (...) { NEARBY_LOGS(ERROR) << "failed to connect remote service " << ip_address << ":" << port; } return nullptr; } std::unique_ptr WifiLanMedium::ListenForService( int port) { absl::MutexLock lock(&mutex_); // check current status if (IsAccepting()) { NEARBY_LOGS(WARNING) << "accepting connections already started on port " << server_socket_ptr_->GetPort(); return nullptr; } std::unique_ptr server_socket = std::make_unique(port); server_socket_ptr_ = server_socket.get(); server_socket->SetCloseNotifier([this]() { absl::MutexLock lock(&mutex_); NEARBY_LOGS(INFO) << "server socket was closed on port " << server_socket_ptr_->GetPort(); medium_status_ &= (~MEDIUM_STATUS_ACCEPTING); server_socket_ptr_ = nullptr; }); if (server_socket->listen()) { medium_status_ |= MEDIUM_STATUS_ACCEPTING; NEARBY_LOGS(INFO) << "started to listen serive on port " << port; return server_socket; } NEARBY_LOGS(ERROR) << "Failed to listen service on port " << port; return nullptr; } NsdServiceInfo WifiLanMedium::GetNsdServiceInformation( IMapView properties) { NsdServiceInfo nsd_service_info{}; // Service name information IInspectable inspectable = properties.TryLookup(L"System.Devices.Dnssd.InstanceName"); if (inspectable == nullptr) { NEARBY_LOGS(WARNING) << "no service name information in device information."; return nsd_service_info; } nsd_service_info.SetServiceName(InspectableReader::ReadString(inspectable)); // IP Address information inspectable = properties.TryLookup(L"System.Devices.IPAddress"); if (inspectable == nullptr) { NEARBY_LOGS(WARNING) << "no IP address information in device information."; return nsd_service_info; } auto ipaddresses = InspectableReader::ReadStringArray(inspectable); if (ipaddresses.size() == 0) { NEARBY_LOGS(WARNING) << "no IP address information in device information."; return nsd_service_info; } std::string ip_address = ipaddresses[0]; // read IP port inspectable = properties.TryLookup(L"System.Devices.Dnssd.PortNumber"); if (inspectable == nullptr) { NEARBY_LOGS(WARNING) << "no IP port information in device information."; return nsd_service_info; } int port = InspectableReader::ReadUint16(inspectable); nsd_service_info.SetIPAddress(ip_address); nsd_service_info.SetPort(port); // read text record inspectable = properties.TryLookup(L"System.Devices.Dnssd.TextAttributes"); if (inspectable == nullptr) { NEARBY_LOGS(WARNING) << "no text attributes information in device information."; return nsd_service_info; } auto text_attributes = InspectableReader::ReadStringArray(inspectable); for (auto text_attribute : text_attributes) { // text attribute in format key=value int pos = text_attribute.find("="); if (pos <= 0 || pos == text_attribute.size() - 1) { NEARBY_LOGS(WARNING) << "found invalid text attribute " << text_attribute; continue; } std::string key = text_attribute.substr(0, pos); std::string value = text_attribute.substr(pos + 1); nsd_service_info.SetTxtRecord(key, value); } return nsd_service_info; } fire_and_forget WifiLanMedium::Watcher_DeviceAdded( DeviceWatcher sender, DeviceInformation deviceInfo) { // need to read IP address and port information from deviceInfo NsdServiceInfo nsd_service_info = GetNsdServiceInformation(deviceInfo.Properties()); NEARBY_LOGS(INFO) << "device added for service name " << nsd_service_info.GetServiceName(); std::string endpoint = nsd_service_info.GetTxtRecord(KEY_ENDPOINT_INFO.data()); if (endpoint.empty()) { return fire_and_forget{}; } discovered_service_callback_.service_discovered_cb(nsd_service_info); return fire_and_forget(); } fire_and_forget WifiLanMedium::Watcher_DeviceUpdated( DeviceWatcher sender, DeviceInformationUpdate deviceInfoUpdate) { // TODO(b/200421481): discovery servcie callback needs to support device // update. NsdServiceInfo nsd_service_info = GetNsdServiceInformation(deviceInfoUpdate.Properties()); NEARBY_LOGS(INFO) << "device updated for service name " << nsd_service_info.GetServiceName(); return fire_and_forget(); } fire_and_forget WifiLanMedium::Watcher_DeviceRemoved( DeviceWatcher sender, DeviceInformationUpdate deviceInfoUpdate) { // need to read IP address and port information from deviceInfo NsdServiceInfo nsd_service_info = GetNsdServiceInformation(deviceInfoUpdate.Properties()); NEARBY_LOGS(INFO) << "device removed for service name " << nsd_service_info.GetServiceName(); std::string endpoint = nsd_service_info.GetTxtRecord(KEY_ENDPOINT_INFO.data()); if (endpoint.empty()) { return fire_and_forget{}; } discovered_service_callback_.service_lost_cb(nsd_service_info); return fire_and_forget(); } std::string WifiLanMedium::GetErrorMessage(std::exception_ptr eptr) { try { if (eptr) { std::rethrow_exception(eptr); } else { return ""; } } catch (const std::exception& e) { return e.what(); } } } // namespace windows } // namespace nearby } // namespace location