From ebfaba3e22f56e140366f3c5f45fc58dbc588751 Mon Sep 17 00:00:00 2001 From: guogang Date: Wed, 3 Nov 2021 16:31:35 -0700 Subject: [PATCH] Refactored WiFi LAN implementation based on new WiFi LAN API PiperOrigin-RevId: 407453537 --- cpp/platform/impl/windows/BUILD | 4 + cpp/platform/impl/windows/platform.cc | 4 +- cpp/platform/impl/windows/wifi_lan.h | 320 ++++------ cpp/platform/impl/windows/wifi_lan_medium.cc | 551 +++++++++++------- cpp/platform/impl/windows/wifi_lan_nsd.cc | 511 ---------------- .../impl/windows/wifi_lan_server_socket.cc | 180 ++++++ cpp/platform/impl/windows/wifi_lan_socket.cc | 32 +- 7 files changed, 664 insertions(+), 938 deletions(-) delete mode 100644 cpp/platform/impl/windows/wifi_lan_nsd.cc create mode 100644 cpp/platform/impl/windows/wifi_lan_server_socket.cc diff --git a/cpp/platform/impl/windows/BUILD b/cpp/platform/impl/windows/BUILD index beade04c..726067b4 100644 --- a/cpp/platform/impl/windows/BUILD +++ b/cpp/platform/impl/windows/BUILD @@ -64,6 +64,7 @@ cc_library( "thread_pool.h", "webrtc.h", "wifi.h", + "wifi_lan.h", ], compatible_with = ["//buildenv/target:non_prod"], visibility = ["//visibility:private"], @@ -104,6 +105,9 @@ cc_library( "system_clock.cc", "thread_pool.cc", "utils.cc", + "wifi_lan_medium.cc", + "wifi_lan_server_socket.cc", + "wifi_lan_socket.cc", ], compatible_with = ["//buildenv/target:non_prod"], copts = ["-Ithird_party/nearby_connections/cpp/platform/impl/windows/generated"], diff --git a/cpp/platform/impl/windows/platform.cc b/cpp/platform/impl/windows/platform.cc index e882f12c..5d117cf6 100644 --- a/cpp/platform/impl/windows/platform.cc +++ b/cpp/platform/impl/windows/platform.cc @@ -36,6 +36,7 @@ #include "platform/impl/windows/submittable_executor.h" #include "platform/impl/windows/webrtc.h" #include "platform/impl/windows/wifi.h" +#include "platform/impl/windows/wifi_lan.h" namespace location { namespace nearby { @@ -159,9 +160,8 @@ std::unique_ptr ImplementationPlatform::CreateWifiMedium() { return std::unique_ptr(); } -// TODO(b/184975123): replace with real implementation. std::unique_ptr ImplementationPlatform::CreateWifiLanMedium() { - return std::unique_ptr(); + return absl::make_unique(); } // TODO(b/184975123): replace with real implementation. diff --git a/cpp/platform/impl/windows/wifi_lan.h b/cpp/platform/impl/windows/wifi_lan.h index e8e2a3cf..5485367b 100644 --- a/cpp/platform/impl/windows/wifi_lan.h +++ b/cpp/platform/impl/windows/wifi_lan.h @@ -21,6 +21,7 @@ // Standard C/C++ headers #include +#include #include #include @@ -76,40 +77,13 @@ using winrt::Windows::Storage::Streams::IInputStream; using winrt::Windows::Storage::Streams::InputStreamOptions; using winrt::Windows::Storage::Streams::IOutputStream; -class WifiLanMedium; - -// WifiLanService includes NSD service information and -// related medium information. -class WifiLanService : public api::WifiLanService { - public: - WifiLanService() = default; - explicit WifiLanService(NsdServiceInfo nsd_service_info) - : nsd_service_info_(std::move(nsd_service_info)) {} - ~WifiLanService() override = default; - - NsdServiceInfo GetServiceInfo() const override { return nsd_service_info_; } - - void SetServiceInfo(NsdServiceInfo nsd_service_info) { - nsd_service_info_ = std::move(nsd_service_info); - } - - WifiLanMedium* GetMedium() { return medium_; } - - void SetMedium(WifiLanMedium* medium) { medium_ = medium; } - - private: - NsdServiceInfo nsd_service_info_; - WifiLanMedium* medium_ = nullptr; -}; - // WifiLanSocket wraps the socket functions to read and write stream. // In WiFi LAN, A WifiLanSocket will be passed to StartAcceptingConnections's // call back when StreamSocketListener got connect. When call API to connect to // remote WiFi LAN service, also will return a WifiLanSocket to caller. class WifiLanSocket : public api::WifiLanSocket { public: - explicit WifiLanSocket(api::WifiLanService* wifi_lan_service, - StreamSocket socket); + explicit WifiLanSocket(StreamSocket socket); WifiLanSocket(WifiLanSocket&) = default; WifiLanSocket(WifiLanSocket&&) = default; ~WifiLanSocket() override; @@ -133,22 +107,6 @@ class WifiLanSocket : public api::WifiLanSocket { // Returns Exception::kIo on error, Exception::kSuccess otherwise. Exception Close() override; - // Returns valid WifiLanService pointer if there is a connection, and - // nullptr otherwise. - api::WifiLanService* GetRemoteWifiLanService() override; - - // Sets service id binding to the socket - void SetServiceId(std::string service_id); - - // Sets medium information - void SetMedium(WifiLanMedium* medium); - - // Returns the socket IP address - std::string GetLocalAddress(); - - // Returns the socket port, range is between 49152 and 65535 - int GetLocalPort(); - private: // A simple wrapper to handle input stream of socket class SocketInputStream : public InputStream { @@ -182,45 +140,107 @@ class WifiLanSocket : public api::WifiLanSocket { StreamSocket stream_soket_{nullptr}; SocketInputStream input_stream_{nullptr}; SocketOutputStream output_stream_{nullptr}; - - api::WifiLanService* remote_wifi_lan_service_ = nullptr; - WifiLanMedium* medium_ = nullptr; - std::string service_id_; }; -// WifiLanNsd implements the NSD functions for a specific service ID. -// WifiLan Medium separates NSD functions using WifiLanNsd. WifiLanNsd -// maintians the states of mDNS service. -class WifiLanNsd { +// WifiLanServerSocket provides the support to server socket, this server socket +// accepts connection from clients. +class WifiLanServerSocket : public api::WifiLanServerSocket { public: - explicit WifiLanNsd(WifiLanMedium* medium, const std::string service_id); - WifiLanNsd(WifiLanNsd&&) = default; - WifiLanNsd& operator=(WifiLanNsd&&) = default; - ~WifiLanNsd() = default; + explicit WifiLanServerSocket(int port = 0); + WifiLanServerSocket(WifiLanServerSocket&) = default; + WifiLanServerSocket(WifiLanServerSocket&&) = default; + ~WifiLanServerSocket() override; + WifiLanServerSocket& operator=(const WifiLanServerSocket&) = default; + WifiLanServerSocket& operator=(WifiLanServerSocket&&) = default; - // Implements medium functions based on service id - bool StartAcceptingConnections( - api::WifiLanMedium::AcceptedConnectionCallback callback); - bool StopAcceptingConnections(); - bool StartAdvertising(const NsdServiceInfo& nsd_service_info); - bool StopAdvertising(); - bool StartDiscovery(api::WifiLanMedium::DiscoveredServiceCallback callback); - bool StopDiscovery(); + // Returns ip address. + std::string GetIPAddress() const override; - // In the class, not using ENUM to describe the mDNS states, because a little - // complicate to combine all states based on accepting, advertising and - // discovery. - bool IsIdle() { return nsd_status_ == 0; } + // Returns port. + int GetPort() const override; - bool IsAccepting() { return (nsd_status_ & NSD_STATUS_ACCEPTING) != 0; } + // Sets port + void SetPort(int port) { port_ = port; } - bool IsAdvertising() { return (nsd_status_ & NSD_STATUS_ADVERTISING) != 0; } + StreamSocketListener GetSocketListener() const { + return stream_socket_listener_; + } - bool IsDiscovering() { return (nsd_status_ & NSD_STATUS_DISCOVERING) != 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. + std::unique_ptr Accept() override; - // A pair of IP Address and Port. A remote device can use this information - // to connect to us. This is non-null while IsAccepting is true. - std::pair GetCredentials(); + // Called by the server side of a connection before passing ownership of + // WifiLanServerSocker to user, to track validity of a pointer to this + // server socket. + void SetCloseNotifier(std::function notifier); + + // Returns Exception::kIo on error, Exception::kSuccess otherwise. + Exception Close() override; + + // Binds to local port + bool listen(); + + private: + // The listener is accepting incoming connections + fire_and_forget Listener_ConnectionReceived( + StreamSocketListener listener, + StreamSocketListenerConnectionReceivedEventArgs const& args); + + // Retrieves IP addresses from local machine + std::vector GetIpAddresses(); + + mutable absl::Mutex mutex_; + absl::CondVar cond_; + std::deque pending_sockets_ ABSL_GUARDED_BY(mutex_); + StreamSocketListener stream_socket_listener_{nullptr}; + winrt::event_token listener_event_token_{}; + + // Close notifier + std::function close_notifier_ = nullptr; + + // IP addresses of the computer. mDNS uses them to advertise. + std::vector ip_addresses_{}; + + // Cache socket not be picked by upper layer + int port_ = 0; + bool closed_ = false; +}; + +// Container of operations that can be performed over the WifiLan medium. +class WifiLanMedium : public api::WifiLanMedium { + public: + ~WifiLanMedium() override = default; + + // Starts to advertising + bool StartAdvertising(const NsdServiceInfo& nsd_service_info) override; + + // Stops to advertising + bool StopAdvertising(const NsdServiceInfo& nsd_service_info) override; + + // Starts to discovery + bool StartDiscovery(const std::string& service_type, + DiscoveredServiceCallback callback) override; + + // Returns true once WifiLan discovery for service_type is well and truly + // stopped; after this returns, there must be no more invocations of the + // DiscoveredServiceCallback passed in to StartDiscovery() for service_type. + bool StopDiscovery(const std::string& service_type) override; + + std::unique_ptr ConnectToService( + const NsdServiceInfo& remote_service_info, + CancellationFlag* cancellation_flag) override; + + std::unique_ptr ConnectToService( + const std::string& ip_address, int port, + CancellationFlag* cancellation_flag) override; + + std::unique_ptr ListenForService( + int port = 0) override; // DnsServiceDeRegister is a async process, after operation finish, callback // will call this method to notify the waiting method StopAdvertising to @@ -228,46 +248,38 @@ class WifiLanNsd { void NotifyDnsServiceUnregistered(DWORD status); private: - // Nsd status - static const int NSD_STATUS_IDLE = 0; - static const int NSD_STATUS_ACCEPTING = (1 << 0); - static const int NSD_STATUS_ADVERTISING = (1 << 1); - static const int NSD_STATUS_DISCOVERING = (1 << 2); - - // - // Constants - // - - // Socket listening ports - static const uint16 PORT_MIN = 49152; - static const uint16 PORT_MAX = 65535; - static const uint16 PORT_RANGE = PORT_MAX - PORT_MIN; - // mDNS text attributes static constexpr std::string_view KEY_ENDPOINT_INFO = "n"; // mDNS information for advertising and discovery static constexpr std::wstring_view MDNS_HOST_NAME = L"Windows.local"; - static constexpr std::string_view MDNS_INSTANCE_NAME_FORMAT = - "%s.%s._tcp.local"; + static constexpr std::string_view MDNS_INSTANCE_NAME_FORMAT = "%s.%slocal"; static constexpr std::string_view MDNS_DEVICE_SELECTOR_FORMAT = "System.Devices.AepService.ProtocolId:=\"{4526e8c1-8aac-4153-9b16-" "55e86ada0e54}\" " "AND System.Devices.Dnssd.ServiceName:=\"%s._tcp\" AND " "System.Devices.Dnssd.Domain:=\"local\""; - static const int SERVICE_ID_HASH_LENGTH = 6; - static constexpr std::string_view SERVICE_ID_FORMAT = - "_%02X%02X%02X%02X%02X%02X"; - // - // Private methods - // + // Nsd status + static const int MEDIUM_STATUS_IDLE = 0; + static const int MEDIUM_STATUS_ACCEPTING = (1 << 0); + static const int MEDIUM_STATUS_ADVERTISING = (1 << 1); + static const int MEDIUM_STATUS_DISCOVERING = (1 << 2); - // Generates preferred listening port. If cannot bind to this port, - // NSD will assign a random port for the service. - // TODO: Windows firewall may break the solution, need to further solution to - // resolve the potential issue - uint16 GenerateSocketPort(const std::string& service_id); + // In the class, not using ENUM to describe the mDNS states, because a little + // complicate to combine all states based on accepting, advertising and + // discovery. + bool IsIdle() { return medium_status_ == 0; } + + bool IsAccepting() { return (medium_status_ & MEDIUM_STATUS_ACCEPTING) != 0; } + + bool IsAdvertising() { + return (medium_status_ & MEDIUM_STATUS_ADVERTISING) != 0; + } + + bool IsDiscovering() { + return (medium_status_ & MEDIUM_STATUS_DISCOVERING) != 0; + } // From mDNS device information, to build NsdServiceInfo. // the properties are from DeviceInformation and DeviceInformationUpdate. @@ -277,9 +289,6 @@ class WifiLanNsd { IMapView properties); // mDNS callbacks for advertising and discovery - fire_and_forget Listener_ConnectionReceived( - StreamSocketListener listener, - StreamSocketListenerConnectionReceivedEventArgs const& args); fire_and_forget Watcher_DeviceAdded(DeviceWatcher sender, DeviceInformation deviceInfo); fire_and_forget Watcher_DeviceUpdated( @@ -289,36 +298,14 @@ class WifiLanNsd { static void Advertising_StopCompleted(DWORD Status, PVOID pQueryContext, PDNS_SERVICE_INSTANCE pInstance); - // Retrieves IP addresses from local machine - std::vector GetIpAddresses(); - - std::string GetServiceIdHash(); - - // Manages remote connections - WifiLanService* GetRemoteWifiLanService( - std::string endpoint, std::unique_ptr wifi_lan_service); - void RemoveRemoteWifiLanService(std::string endpoint); - - // Basic information of Nsd - location::nearby::Mutex mutex_{}; - std::string service_id_{}; - // TODO(200421848): NsdServiceInfo should support service type - std::string service_type_{}; - WifiLanMedium* medium_ = nullptr; - WifiLanService wifi_lan_service_{}; - absl::flat_hash_map> - remote_wifi_lan_services_ ABSL_GUARDED_BY(mutex_); - - // NSD Status - int nsd_status_ = NSD_STATUS_IDLE; + // Gets error message from exception pointer + std::string GetErrorMessage(std::exception_ptr eptr); // // Dns-sd related properties // // Advertising properties - winrt::event_token listener_event_token_{}; - StreamSocketListener stream_socket_listener_{nullptr}; DnssdServiceInstance dnssd_service_instance_{nullptr}; DnssdRegistrationResult dnssd_regirstraion_result_{nullptr}; @@ -335,78 +322,17 @@ class WifiLanNsd { winrt::event_token device_watcher_updated_event_token; winrt::event_token device_watcher_removed_event_token; - // callbacks for advertising and discovery - api::WifiLanMedium::AcceptedConnectionCallback accepted_connection_callback_; + // callback for discovery api::WifiLanMedium::DiscoveredServiceCallback discovered_service_callback_; - // IP addresses of the computer. mDNS uses them to advertise. - std::vector ip_addresses_{}; -}; + // Protects to access some members + absl::Mutex mutex_; -// Container of operations that can be performed over the WifiLan medium. -class WifiLanMedium : public api::WifiLanMedium { - public: - ~WifiLanMedium() override = default; + // Medium Status + int medium_status_ = MEDIUM_STATUS_IDLE; - // Starts to advertising - bool StartAdvertising(const std::string& service_id, - const NsdServiceInfo& nsd_service_info) override; - - // Stops to advertising - bool StopAdvertising(const std::string& service_id) override; - - // Starts to discovery - bool StartDiscovery(const std::string& service_id, - DiscoveredServiceCallback callback) override; - - // 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 StopDiscovery(const std::string& service_id) override; - - // Returns true once WifiLan socket connection requests to service_id can be - // accepted. - bool StartAcceptingConnections(const std::string& service_id, - AcceptedConnectionCallback callback) override; - - // Stops to accept connections - bool StopAcceptingConnections(const std::string& service_id) override; - - // Connects to a WifiLan service. - // On success, returns a new WifiLanSocket. - // On error, returns nullptr. - std::unique_ptr Connect( - api::WifiLanService& wifi_lan_service, const std::string& service_id, - CancellationFlag* cancellation_flag) override; - - // Returns WiFi LAN service from local ip address and port information - api::WifiLanService* GetRemoteService(const std::string& ip_address, - int port) override; - - // returns advertising service address - std::pair GetCredentials( - const std::string& service_id) override; - - // for internal to clean closed connection. - void CloseConnection(WifiLanSocket& socket); - - private: - // Accesses NSD by service id - WifiLanNsd* GetNsd(std::string service_id, bool create = false); - bool RemoveNsd(std::string service_id); - - // Gets error message from exception pointer - std::string GetErrorMessage(std::exception_ptr eptr); - - // Protects the access to NSD and connections - location::nearby::Mutex mutex_{}; - - // Tracks of active advertising or discovery - absl::flat_hash_map> - service_to_nsd_map_ ABSL_GUARDED_BY(mutex_); - - // Tracks of active connetions - absl::flat_hash_set wifi_lan_sockets_ ABSL_GUARDED_BY(mutex_); + // Keep the server socket listener pointer + WifiLanServerSocket* server_socket_ptr_ ABSL_GUARDED_BY(mutex_) = nullptr; }; } // namespace windows diff --git a/cpp/platform/impl/windows/wifi_lan_medium.cc b/cpp/platform/impl/windows/wifi_lan_medium.cc index f18262bc..f702fd7b 100644 --- a/cpp/platform/impl/windows/wifi_lan_medium.cc +++ b/cpp/platform/impl/windows/wifi_lan_medium.cc @@ -35,258 +35,415 @@ namespace location { namespace nearby { namespace windows { -bool WifiLanMedium::StartAcceptingConnections( - const std::string& service_id, AcceptedConnectionCallback callback) { - try { - WifiLanNsd* nsd = GetNsd(service_id, true); - return nsd->StartAcceptingConnections(callback); - } catch (...) { - NEARBY_LOGS(ERROR) << "failed to start accepting connections due to " - << GetErrorMessage(std::current_exception()); +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; + } } -bool WifiLanMedium::StopAcceptingConnections(const std::string& service_id) { - try { - WifiLanNsd* nsd = GetNsd(service_id); - if (nsd == nullptr) { - NEARBY_LOGS(WARNING) << "no running accepting connections."; - return false; - } - - if (nsd->StopAcceptingConnections()) { - if (nsd->IsIdle()) { - this->RemoveNsd(service_id); - } - return true; - } - - NEARBY_LOGS(ERROR) << "failed to stop accepting connections."; - return false; - } catch (...) { - NEARBY_LOGS(ERROR) << "failed to stop accepting connections due to " - << GetErrorMessage(std::current_exception()); - return false; +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::StartAdvertising(const std::string& service_id, - const NsdServiceInfo& nsd_service_info) { - try { - WifiLanNsd* nsd = GetNsd(service_id); - if (nsd == nullptr) { - NEARBY_LOGS(WARNING) - << "cannot start advertising without accepting connections."; - return false; - } - return nsd->StartAdvertising(nsd_service_info); - } catch (...) { - NEARBY_LOGS(ERROR) << "failed to start advertising due to " - << GetErrorMessage(std::current_exception()); +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; } -} -bool WifiLanMedium::StopAdvertising(const std::string& service_id) { - try { - WifiLanNsd* nsd = GetNsd(service_id); - if (nsd == nullptr) { - NEARBY_LOGS(WARNING) - << "cannot stop advertising without accepting connections."; - return false; - } - if (nsd->StopAdvertising()) { - if (nsd->IsIdle()) { - this->RemoveNsd(service_id); - } - return true; - } + // 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)); - NEARBY_LOGS(ERROR) << "failed to stop advertising."; - return false; - } catch (...) { - NEARBY_LOGS(ERROR) << "failed to stop advertising due to " - << GetErrorMessage(std::current_exception()); + 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_id, +bool WifiLanMedium::StartDiscovery(const std::string& service_type, DiscoveredServiceCallback callback) { - try { - WifiLanNsd* nsd = GetNsd(service_id, true); - return nsd->StartDiscovery(callback); - } catch (...) { - NEARBY_LOGS(ERROR) << "failed to start discovery due to " - << GetErrorMessage(std::current_exception()); + 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_id) { - try { - WifiLanNsd* nsd = GetNsd(service_id); - if (nsd == nullptr) { - NEARBY_LOGS(WARNING) << "no running discovery to stop."; - return false; - } - - if (nsd->StopDiscovery()) { - if (nsd->IsIdle()) { - this->RemoveNsd(service_id); - } - return true; - } - - NEARBY_LOGS(WARNING) << "failed to stop discovery."; - - return false; - } catch (...) { - NEARBY_LOGS(ERROR) << "failed to stop discovery due to " - << GetErrorMessage(std::current_exception()); +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; } -// Connects to a WifiLan service. -// On success, returns a new WifiLanSocket. -// On error, returns nullptr. -std::unique_ptr WifiLanMedium::Connect( - api::WifiLanService& wifi_lan_service, const std::string& service_id, +std::unique_ptr WifiLanMedium::ConnectToService( + const NsdServiceInfo& remote_service_info, CancellationFlag* cancellation_flag) { - try { - std::string ip_address = wifi_lan_service.GetServiceInfo().GetIPAddress(); - int port = wifi_lan_service.GetServiceInfo().GetPort(); - if (ip_address.empty() || port == 0) { - NEARBY_LOGS(ERROR) << "no valid service address and port to connect."; + 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; } - 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: " - "service_id=" - << service_id; - 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(&wifi_lan_service, socket); - wifi_lan_socket->SetServiceId(service_id); - wifi_lan_socket->SetMedium(this); - { - MutexLock lock(&mutex_); - wifi_lan_sockets_.insert(wifi_lan_socket.get()); - } - - NEARBY_LOGS(INFO) << "connected to remote Wifi LAN service"; - return wifi_lan_socket; - } catch (...) { - NEARBY_LOGS(ERROR) << "failed to connect remote service."; - } - - return nullptr; - } catch (...) { - NEARBY_LOGS(ERROR) << "failed to connect due to " - << GetErrorMessage(std::current_exception()); - return nullptr; + location::nearby::CancellationFlagListener cancellationFlagListener( + cancellation_flag, [socket]() { socket.CancelIOAsync().get(); }); } -} -api::WifiLanService* WifiLanMedium::GetRemoteService( - const std::string& ip_address, int port) { + // connection to the service try { - MutexLock lock(&mutex_); + socket.ConnectAsync(host_name, service_name).get(); + // connected need to keep connection - for (WifiLanSocket* socket : wifi_lan_sockets_) { - if (socket->GetLocalAddress() == ip_address && - socket->GetLocalPort() == port) { - return socket->GetRemoteWifiLanService(); - } - } + std::unique_ptr wifi_lan_socket = + std::make_unique(socket); - return nullptr; + NEARBY_LOGS(INFO) << "connected to remote service " << ip_address << ":" + << port; + return wifi_lan_socket; } catch (...) { - NEARBY_LOGS(ERROR) << "failed to get remove service due to " - << GetErrorMessage(std::current_exception()); + 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; } -std::pair WifiLanMedium::GetCredentials( - const std::string& service_id) { - try { - WifiLanNsd* nsd = GetNsd(service_id, true); - if (nsd == nullptr) { - // no nsd service - NEARBY_LOGS(WARNING) << "no service for service id " << service_id; - return std::pair{"", 0}; +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; } - return nsd->GetCredentials(); - } catch (...) { - NEARBY_LOGS(ERROR) << "failed to get service address due to " - << GetErrorMessage(std::current_exception()); - return {"", 0}; + 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; } -void WifiLanMedium::CloseConnection(WifiLanSocket& socket) { - MutexLock lock(&mutex_); - if (wifi_lan_sockets_.contains(&socket)) { - wifi_lan_sockets_.erase(&socket); +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(); -WifiLanNsd* WifiLanMedium::GetNsd(std::string service_id, bool create) { - MutexLock lock(&mutex_); - - if (!service_to_nsd_map_.contains(service_id)) { - if (create) { - // if no the service id, create a new one - std::unique_ptr nsd = - std::make_unique(this, service_id); - service_to_nsd_map_[service_id] = std::move(nsd); - } - } - - return service_to_nsd_map_[service_id].get(); + 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()); -bool WifiLanMedium::RemoveNsd(std::string service_id) { - MutexLock lock(&mutex_); + NEARBY_LOGS(INFO) << "device removed for service name " + << nsd_service_info.GetServiceName(); - if (!service_to_nsd_map_.contains(service_id)) { - return true; - } - auto nsd = service_to_nsd_map_.find(service_id); - if (nsd == service_to_nsd_map_.end() || nsd->second == nullptr || - !nsd->second->IsIdle()) { - return false; + std::string endpoint = + nsd_service_info.GetTxtRecord(KEY_ENDPOINT_INFO.data()); + if (endpoint.empty()) { + return fire_and_forget{}; } - service_to_nsd_map_.erase(nsd); - return true; + discovered_service_callback_.service_lost_cb(nsd_service_info); + + return fire_and_forget(); } std::string WifiLanMedium::GetErrorMessage(std::exception_ptr eptr) { diff --git a/cpp/platform/impl/windows/wifi_lan_nsd.cc b/cpp/platform/impl/windows/wifi_lan_nsd.cc deleted file mode 100644 index b0da2d8d..00000000 --- a/cpp/platform/impl/windows/wifi_lan_nsd.cc +++ /dev/null @@ -1,511 +0,0 @@ -// 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. - -// Absl headers -#include "absl/strings/str_format.h" - -// Nearby connections headers -#include "platform/impl/windows/utils.h" -#include "platform/impl/windows/wifi_lan.h" -#include "platform/public/logging.h" -#include "platform/public/mutex_lock.h" - -// WinRT headers -#include "platform/impl/windows/generated/winrt/Windows.Foundation.Collections.h" - -namespace location { -namespace nearby { -namespace windows { - -WifiLanNsd::WifiLanNsd(WifiLanMedium* medium, const std::string service_id) { - medium_ = medium; - service_id_ = service_id; - service_type_ = GetServiceIdHash(); -} - -bool WifiLanNsd::StartAcceptingConnections( - api::WifiLanMedium::AcceptedConnectionCallback callback) { - // TODO: check windows version to decide whether support mDNS service - - // Check IP address - ip_addresses_ = GetIpAddresses(); - - if (ip_addresses_.empty()) { - NEARBY_LOGS(WARNING) << "failed to start accepting connection without IP " - "addresses configured on computer."; - return false; - } - - // check current status - if (IsAccepting()) { - NEARBY_LOGS(WARNING) << "accepting connections already started. service id=" - << service_id_; - return false; - } - - int port = GenerateSocketPort(service_id_); - - // Save connection callback - accepted_connection_callback_ = std::move(callback); - stream_socket_listener_ = StreamSocketListener(); - - // Setup callback - listener_event_token_ = stream_socket_listener_.ConnectionReceived( - {this, &WifiLanNsd::Listener_ConnectionReceived}); - - try { - stream_socket_listener_.BindServiceNameAsync(winrt::to_hstring(port)).get(); - nsd_status_ |= NSD_STATUS_ACCEPTING; - return true; - } catch (...) { - // Cannot bind to the preferred port, will let system to assign port. - NEARBY_LOGS(WARNING) << "cannot accept connection on preferred port."; - } - - try { - stream_socket_listener_.BindServiceNameAsync({}).get(); - // need to save the port information - port = std::stoi(stream_socket_listener_.Information().LocalPort().c_str()); - nsd_status_ |= NSD_STATUS_ACCEPTING; - return true; - } catch (...) { - // Cannot bind to the preferred port, will let system to assign port. - NEARBY_LOGS(ERROR) << "cannot bind to any port."; - } - - // clean up - stream_socket_listener_.ConnectionReceived(listener_event_token_); - stream_socket_listener_ = nullptr; - - return false; -} - -bool WifiLanNsd::StopAcceptingConnections() { - if (!IsAccepting()) { - NEARBY_LOGS(WARNING) << "no accepting connections to stop."; - return false; - } - - stream_socket_listener_.ConnectionReceived(listener_event_token_); - stream_socket_listener_ = nullptr; - nsd_status_ &= (~NSD_STATUS_ACCEPTING); - return true; -} - -bool WifiLanNsd::StartAdvertising(const NsdServiceInfo& nsd_service_info) { - 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; - } - - // Setup WiFi LAN service information - int port = - std::stoi(stream_socket_listener_.Information().LocalPort().c_str()); - NsdServiceInfo new_nsd_servcie_info = nsd_service_info; - // TODO: need feature enhancement to support multiple network interfaces - new_nsd_servcie_info.SetIPAddress(ip_addresses_[0]); - new_nsd_servcie_info.SetPort(port); - wifi_lan_service_ = WifiLanService(new_nsd_servcie_info); - wifi_lan_service_.SetMedium(medium_); - - std::string instance_name = absl::StrFormat( - MDNS_INSTANCE_NAME_FORMAT.data(), - wifi_lan_service_.GetServiceInfo().GetServiceName(), service_type_); - - 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)port}; - - // Add TextRecords from NsdServiceInfo - auto text_attributes = dnssd_service_instance_.TextAttributes(); - // TODO(b/200298824): NsdServiceInfo should have function to return all text - // records will be more generic - text_attributes.Insert(string_to_wstring(KEY_ENDPOINT_INFO.data()), - string_to_wstring(nsd_service_info.GetTxtRecord( - KEY_ENDPOINT_INFO.data()))); - - dnssd_regirstraion_result_ = - dnssd_service_instance_ - .RegisterStreamSocketListenerAsync(stream_socket_listener_) - .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(); - return false; - } - - if (dnssd_regirstraion_result_.Status() == DnssdRegistrationStatus::Success) { - NEARBY_LOGS(INFO) << "started to advertising."; - nsd_status_ |= NSD_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 WifiLanNsd::Advertising_StopCompleted(DWORD Status, PVOID pQueryContext, - PDNS_SERVICE_INSTANCE pInstance) { - NEARBY_LOGS(INFO) << "unregister with status=" << Status; - try { - WifiLanNsd* nsd = static_cast(pQueryContext); - nsd->NotifyDnsServiceUnregistered(Status); - } catch (...) { - NEARBY_LOGS(ERROR) << "failed to notify the stop of DNS service instance." - << Status; - } -} - -void WifiLanNsd::NotifyDnsServiceUnregistered(DWORD status) { - if (dns_service_stop_latch_.get() != nullptr) { - dns_service_stop_status_ = status; - dns_service_stop_latch_.get()->CountDown(); - } -} - -bool WifiLanNsd::StopAdvertising() { - // 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(), - wifi_lan_service_.GetServiceInfo().GetServiceName(), service_type_); - int port = wifi_lan_service_.GetServiceInfo().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 = - WifiLanNsd::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 id =" - << service_id_; - 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 id =" - << service_id_; - return false; - } - - NEARBY_LOGS(INFO) << "succeeded to stop mDNS advertising for service id =" - << service_id_; - nsd_status_ &= (~NSD_STATUS_ADVERTISING); - return true; -} - -bool WifiLanNsd::StartDiscovery( - api::WifiLanMedium::DiscoveredServiceCallback callback) { - if (IsDiscovering()) { - NEARBY_LOGS(WARNING) << "discovery already running for service id =" - << service_id_; - 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, &WifiLanNsd::Watcher_DeviceAdded}); - device_watcher_updated_event_token = - device_watcher_.Updated({this, &WifiLanNsd::Watcher_DeviceUpdated}); - device_watcher_removed_event_token = - device_watcher_.Removed({this, &WifiLanNsd::Watcher_DeviceRemoved}); - - device_watcher_.Start(); - discovered_service_callback_ = std::move(callback); - nsd_status_ |= NSD_STATUS_DISCOVERING; - - NEARBY_LOGS(INFO) << "started to discovery."; - - return true; -} - -bool WifiLanNsd::StopDiscovery() { - if (!IsDiscovering()) { - NEARBY_LOGS(WARNING) << "no discovering service to stop."; - return false; - } - // TODO: handle exception - 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); - nsd_status_ &= (~NSD_STATUS_DISCOVERING); - device_watcher_ = nullptr; - return true; -} - -std::pair WifiLanNsd::GetCredentials() { - if (!IsAdvertising()) { - // no advertising is running - NEARBY_LOGS(WARNING) << "no advertising for service id " << service_id_; - return std::pair{"", 0}; - } - - return std::make_pair(wifi_lan_service_.GetServiceInfo().GetIPAddress(), - wifi_lan_service_.GetServiceInfo().GetPort()); -} - -std::vector WifiLanNsd::GetIpAddresses() { - std::vector result{}; - auto host_names = NetworkInformation::GetHostNames(); - for (auto host_name : host_names) { - if (host_name.IPInformation() != nullptr && - host_name.IPInformation().NetworkAdapter() != nullptr) { - result.push_back(wstring_to_string(host_name.ToString().c_str())); - } - } - return result; -} - -fire_and_forget WifiLanNsd::Listener_ConnectionReceived( - StreamSocketListener listener, - StreamSocketListenerConnectionReceivedEventArgs const& args) { - NEARBY_LOGS(INFO) << "got connection message for service " << service_id_; - StreamSocket stream_socket = args.Socket(); - - // Send to callback - std::unique_ptr socket = - std::make_unique(&wifi_lan_service_, - stream_socket); - socket->SetMedium(medium_); - socket->SetServiceId(service_id_); - accepted_connection_callback_.accepted_cb(*socket.get(), service_id_); - return fire_and_forget{}; -} - -NsdServiceInfo WifiLanNsd::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 WifiLanNsd::Watcher_DeviceAdded(DeviceWatcher sender, - DeviceInformation deviceInfo) { - NEARBY_LOGS(INFO) << "device added for service " << service_id_; - - // need to read IP address and port information from deviceInfo - NsdServiceInfo nsd_service_info = - GetNsdServiceInformation(deviceInfo.Properties()); - - std::string endpoint = - nsd_service_info.GetTxtRecord(KEY_ENDPOINT_INFO.data()); - if (endpoint.empty()) { - return fire_and_forget{}; - } - - std::unique_ptr wifi_lan_service = - std::make_unique(nsd_service_info); - WifiLanService* pwifi_lan_service = - GetRemoteWifiLanService(endpoint, std::move(wifi_lan_service)); - discovered_service_callback_.service_discovered_cb(*pwifi_lan_service, - service_id_); - return fire_and_forget(); -} -fire_and_forget WifiLanNsd::Watcher_DeviceUpdated( - DeviceWatcher sender, DeviceInformationUpdate deviceInfoUpdate) { - // TODO(b/200421481): discovery servcie callback needs to support device - // update. - NEARBY_LOGS(INFO) << "device updated for service " << service_id_; - - return fire_and_forget(); -} -fire_and_forget WifiLanNsd::Watcher_DeviceRemoved( - DeviceWatcher sender, DeviceInformationUpdate deviceInfoUpdate) { - NEARBY_LOGS(INFO) << "device removed for service " << service_id_; - // need to read IP address and port information from deviceInfo - NsdServiceInfo nsd_service_info = - GetNsdServiceInformation(deviceInfoUpdate.Properties()); - - std::string endpoint = - nsd_service_info.GetTxtRecord(KEY_ENDPOINT_INFO.data()); - if (endpoint.empty()) { - return fire_and_forget{}; - } - std::unique_ptr wifi_lan_service = - std::make_unique(nsd_service_info); - WifiLanService* pwifi_lan_service = - GetRemoteWifiLanService(endpoint, std::move(wifi_lan_service)); - discovered_service_callback_.service_lost_cb(*pwifi_lan_service, service_id_); - RemoveRemoteWifiLanService(endpoint); - return fire_and_forget(); -} - -uint16 WifiLanNsd::GenerateSocketPort(const std::string& service_id) { - ByteArray service_id_sha = Sha256(service_id, 4); - char* hash = service_id_sha.data(); - int b1 = hash[0] & 0xff; - int b2 = hash[1] & 0xff; - int b3 = hash[2] & 0xff; - int b4 = hash[3] & 0xff; - int hashValue = (b1 << 24) | (b2 << 16) | (b3 << 8) | b4; - return PORT_MIN + (hashValue % PORT_RANGE); -} - -WifiLanService* WifiLanNsd::GetRemoteWifiLanService( - std::string endpoint, std::unique_ptr wifi_lan_service) { - MutexLock lock(&mutex_); - - remote_wifi_lan_services_[endpoint] = std::move(wifi_lan_service); - return remote_wifi_lan_services_[endpoint].get(); -} - -void WifiLanNsd::RemoveRemoteWifiLanService(std::string endpoint) { - MutexLock lock(&mutex_); - if (remote_wifi_lan_services_.contains(endpoint)) { - remote_wifi_lan_services_.erase(endpoint); - } -} - -std::string WifiLanNsd::GetServiceIdHash() { - // Get hashed service id - ByteArray service_id_sha = Sha256(service_id_, SERVICE_ID_HASH_LENGTH); - char* sha_data = service_id_sha.data(); - std::string service_id_hash = absl::StrFormat( - SERVICE_ID_FORMAT.data(), sha_data[0] & 0xFF, sha_data[1] & 0xFF, - sha_data[2] & 0xFF, sha_data[3] & 0xFF, sha_data[4] & 0xFF, - sha_data[5] & 0xFF); - - return service_id_hash; -} - -} // namespace windows -} // namespace nearby -} // namespace location diff --git a/cpp/platform/impl/windows/wifi_lan_server_socket.cc b/cpp/platform/impl/windows/wifi_lan_server_socket.cc new file mode 100644 index 00000000..5d181329 --- /dev/null +++ b/cpp/platform/impl/windows/wifi_lan_server_socket.cc @@ -0,0 +1,180 @@ +// 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/utils.h" +#include "platform/impl/windows/wifi_lan.h" +#include "platform/public/logging.h" + +namespace location { +namespace nearby { +namespace windows { + +WifiLanServerSocket::WifiLanServerSocket(int port) : port_(port) {} + +WifiLanServerSocket::~WifiLanServerSocket() { Close(); } + +// Returns ip address. +std::string WifiLanServerSocket::GetIPAddress() const { + if (stream_socket_listener_ == nullptr) { + return {}; + } + + auto host_names = NetworkInformation::GetHostNames(); + for (auto host_name : host_names) { + if (host_name.IPInformation() != nullptr && + host_name.IPInformation().NetworkAdapter() != nullptr) { + return wstring_to_string(host_name.ToString().c_str()); + } + } + + return {}; +} + +// Returns port. +int WifiLanServerSocket::GetPort() const { + if (stream_socket_listener_ == nullptr) { + return 0; + } + + return std::stoi(stream_socket_listener_.Information().LocalPort().c_str()); +} + +// 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. +std::unique_ptr WifiLanServerSocket::Accept() { + absl::MutexLock lock(&mutex_); + while (!closed_ && pending_sockets_.empty()) { + cond_.Wait(&mutex_); + } + if (closed_) return {}; + + StreamSocket wifi_lan_socket = pending_sockets_.front(); + pending_sockets_.pop_front(); + return std::make_unique(wifi_lan_socket); +} + +void WifiLanServerSocket::SetCloseNotifier(std::function notifier) { + close_notifier_ = std::move(notifier); +} + +// Returns Exception::kIo on error, Exception::kSuccess otherwise. +Exception WifiLanServerSocket::Close() { + try { + absl::MutexLock lock(&mutex_); + if (closed_) { + return {Exception::kSuccess}; + } + if (stream_socket_listener_ != nullptr) { + stream_socket_listener_.ConnectionReceived(listener_event_token_); + stream_socket_listener_.Close(); + stream_socket_listener_ = nullptr; + + if (!pending_sockets_.empty()) { + auto it = pending_sockets_.begin(); + while (it != pending_sockets_.end()) { + it->Close(); + } + } + + cond_.SignalAll(); + } + closed_ = true; + if (close_notifier_ != nullptr) { + close_notifier_(); + } + return {Exception::kSuccess}; + } catch (...) { + return {Exception::kIo}; + } +} + +bool WifiLanServerSocket::listen() { + // Check IP address + ip_addresses_ = GetIpAddresses(); + + if (ip_addresses_.empty()) { + NEARBY_LOGS(WARNING) << "failed to start accepting connection without IP " + "addresses configured on computer."; + return false; + } + + // Save connection callback + stream_socket_listener_ = StreamSocketListener(); + + // Setup callback + listener_event_token_ = stream_socket_listener_.ConnectionReceived( + {this, &WifiLanServerSocket::Listener_ConnectionReceived}); + + try { + stream_socket_listener_.BindServiceNameAsync(winrt::to_hstring(port_)) + .get(); + if (port_ == 0) { + port_ = + std::stoi(stream_socket_listener_.Information().LocalPort().c_str()); + } + + return true; + } catch (...) { + // Cannot bind to the preferred port, will let system to assign port. + NEARBY_LOGS(WARNING) << "cannot accept connection on preferred port."; + } + + try { + stream_socket_listener_.BindServiceNameAsync({}).get(); + // need to save the port information + port_ = + std::stoi(stream_socket_listener_.Information().LocalPort().c_str()); + return true; + } catch (...) { + // Cannot bind to the preferred port, will let system to assign port. + NEARBY_LOGS(ERROR) << "cannot bind to any port."; + } + + return false; +} + +fire_and_forget WifiLanServerSocket::Listener_ConnectionReceived( + StreamSocketListener listener, + StreamSocketListenerConnectionReceivedEventArgs const& args) { + absl::MutexLock lock(&mutex_); + + if (closed_) { + return fire_and_forget{}; + } + + pending_sockets_.push_back(args.Socket()); + cond_.SignalAll(); + return fire_and_forget{}; +} + +// Retrieves IP addresses from local machine +std::vector WifiLanServerSocket::GetIpAddresses() { + std::vector result{}; + auto host_names = NetworkInformation::GetHostNames(); + for (auto host_name : host_names) { + if (host_name.IPInformation() != nullptr && + host_name.IPInformation().NetworkAdapter() != nullptr) { + result.push_back(wstring_to_string(host_name.ToString().c_str())); + } + } + return result; +} + +} // namespace windows +} // namespace nearby +} // namespace location diff --git a/cpp/platform/impl/windows/wifi_lan_socket.cc b/cpp/platform/impl/windows/wifi_lan_socket.cc index 44573f8e..0b8358de 100644 --- a/cpp/platform/impl/windows/wifi_lan_socket.cc +++ b/cpp/platform/impl/windows/wifi_lan_socket.cc @@ -19,9 +19,7 @@ namespace location { namespace nearby { namespace windows { -WifiLanSocket::WifiLanSocket(api::WifiLanService* wifi_lan_service, - StreamSocket socket) { - remote_wifi_lan_service_ = wifi_lan_service; +WifiLanSocket::WifiLanSocket(StreamSocket socket) { stream_soket_ = socket; input_stream_ = SocketInputStream(socket.InputStream()); output_stream_ = SocketOutputStream(socket.OutputStream()); @@ -45,7 +43,6 @@ Exception WifiLanSocket::Close() { try { if (stream_soket_ != nullptr) { stream_soket_.Close(); - medium_->CloseConnection(*this); } return {Exception::kSuccess}; } catch (...) { @@ -53,33 +50,6 @@ Exception WifiLanSocket::Close() { } } -api::WifiLanService* WifiLanSocket::GetRemoteWifiLanService() { - return remote_wifi_lan_service_; -} - -void WifiLanSocket::SetServiceId(std::string service_id) { - service_id_ = service_id; -} - -void WifiLanSocket::SetMedium(WifiLanMedium* medium) { medium_ = medium; } - -std::string WifiLanSocket::GetLocalAddress() { - if (stream_soket_ == nullptr) { - return {}; - } - - return winrt::to_string( - stream_soket_.Information().LocalAddress().ToString()); -} - -int WifiLanSocket::GetLocalPort() { - if (stream_soket_ == nullptr) { - return 0; - } - - return std::stoi(stream_soket_.Information().LocalPort().c_str()); -} - // SocketInputStream WifiLanSocket::SocketInputStream::SocketInputStream(IInputStream input_stream) { input_stream_ = input_stream;