diff --git a/cpp/platform/impl/windows/BUILD b/cpp/platform/impl/windows/BUILD index 0528ff0b..f0554a0c 100644 --- a/cpp/platform/impl/windows/BUILD +++ b/cpp/platform/impl/windows/BUILD @@ -107,6 +107,9 @@ cc_library( "system_clock.cc", "thread_pool.cc", "utils.cc", + "wifi_lan_medium.cc", + "wifi_lan_nsd.cc", + "wifi_lan_socket.cc", ], hdrs = [ "bluetooth_classic.h", @@ -121,9 +124,12 @@ cc_library( "scheduled_executor.h", "submittable_executor.h", "thread_pool.h", + "wifi_lan.h", ], compatible_with = ["//buildenv/target:non_prod"], - copts = ["-Ithird_party/nearby_connections/cpp/platform/impl/windows/generated"], + copts = [ + "-Ithird_party/nearby_connections/cpp/platform/impl/windows/generated", + ], visibility = [ "//third_party/nearby_connections/windows:__subpackages__", ], diff --git a/cpp/platform/impl/windows/generated/BUILD b/cpp/platform/impl/windows/generated/BUILD index 66124c3a..bdd07fa8 100644 --- a/cpp/platform/impl/windows/generated/BUILD +++ b/cpp/platform/impl/windows/generated/BUILD @@ -32,6 +32,7 @@ cc_library( "winspool.lib", "comsuppwd.lib", "setupapi.lib", + "dnsapi.lib", ], textual_hdrs = glob(["**/*.h"]), visibility = [ diff --git a/cpp/platform/impl/windows/utils.cc b/cpp/platform/impl/windows/utils.cc index db9bff65..aba74eb9 100644 --- a/cpp/platform/impl/windows/utils.cc +++ b/cpp/platform/impl/windows/utils.cc @@ -1,36 +1,127 @@ -// Copyright 2020 Google LLC -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// https://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#include "platform/impl/windows/utils.h" - -#include "absl/strings/ascii.h" -#include "absl/strings/str_format.h" - -namespace location { -namespace nearby { -namespace windows { - -std::string uint64_to_mac_address_string(uint64_t bluetoothAddress) { - std::string buffer = absl::StrFormat( - "%2llx:%2llx:%2llx:%2llx:%2llx:%2llx", bluetoothAddress >> 40, - (bluetoothAddress >> 32) & 0xff, (bluetoothAddress >> 24) & 0xff, - (bluetoothAddress >> 16) & 0xff, (bluetoothAddress >> 8) & 0xff, - bluetoothAddress & 0xff); - - return absl::AsciiStrToUpper(buffer); -} - -} // namespace windows -} // namespace nearby -} // namespace location +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "platform/impl/windows/utils.h" + +// Windows headers +#include + +// Standard C/C++ headers +#include +#include +#include + +// Third party headers +#include "absl/strings/ascii.h" +#include "absl/strings/str_format.h" + +// Nearby connections headers +#include "platform/api/crypto.h" + +namespace location { +namespace nearby { +namespace windows { + +std::string uint64_to_mac_address_string(uint64_t bluetoothAddress) { + std::string buffer = absl::StrFormat( + "%2llx:%2llx:%2llx:%2llx:%2llx:%2llx", bluetoothAddress >> 40, + (bluetoothAddress >> 32) & 0xff, (bluetoothAddress >> 24) & 0xff, + (bluetoothAddress >> 16) & 0xff, (bluetoothAddress >> 8) & 0xff, + bluetoothAddress & 0xff); + + return absl::AsciiStrToUpper(buffer); +} + +std::wstring string_to_wstring(std::string str) { + std::wstring_convert> converter; + return converter.from_bytes(str); +} + +std::string wstring_to_string(std::wstring wstr) { + std::wstring_convert> converter; + return converter.to_bytes(wstr); +} + +ByteArray Sha256(absl::string_view input, size_t size) { + ByteArray hash = location::nearby::Crypto::Sha256(input); + return ByteArray{hash.data(), size}; +} + +uint16 InspectableReader::ReadUint16(IInspectable inspectable) { + auto property_value = + inspectable.try_as(); + if (property_value == nullptr) { + throw std::invalid_argument("no property value interface."); + } + if (property_value.Type() != + winrt::Windows::Foundation::PropertyType::UInt16) { + throw std::invalid_argument("not uin16 data type."); + } + + return property_value.GetUInt16(); +} + +uint32 InspectableReader::ReadUint32(IInspectable inspectable) { + auto property_value = + inspectable.try_as(); + if (property_value == nullptr) { + throw std::invalid_argument("no property value interface."); + } + if (property_value.Type() != + winrt::Windows::Foundation::PropertyType::UInt32) { + throw std::invalid_argument("not uin32 data type."); + } + + return property_value.GetUInt32(); +} + +std::string InspectableReader::ReadString(IInspectable inspectable) { + auto property_value = + inspectable.try_as(); + if (property_value == nullptr) { + throw std::invalid_argument("no property value interface."); + } + if (property_value.Type() != + winrt::Windows::Foundation::PropertyType::String) { + throw std::invalid_argument("not string data type."); + } + + return wstring_to_string(property_value.GetString().c_str()); +} + +std::vector InspectableReader::ReadStringArray( + IInspectable inspectable) { + std::vector result; + auto property_value = + inspectable.try_as(); + if (property_value == nullptr) { + throw std::invalid_argument("no property value interface."); + } + if (property_value.Type() != + winrt::Windows::Foundation::PropertyType::StringArray) { + throw std::invalid_argument("not string array data type."); + } + + winrt::com_array strings; + property_value.GetStringArray(strings); + + for (winrt::hstring str : strings) { + result.push_back(winrt::to_string(str)); + } + return result; +} + +} // namespace windows +} // namespace nearby +} // namespace location diff --git a/cpp/platform/impl/windows/utils.h b/cpp/platform/impl/windows/utils.h index f633048d..054678da 100644 --- a/cpp/platform/impl/windows/utils.h +++ b/cpp/platform/impl/windows/utils.h @@ -1,45 +1,65 @@ -// Copyright 2020 Google LLC -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// https://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#ifndef PLATFORM_IMPL_WINDOWS_UTILS_H_ -#define PLATFORM_IMPL_WINDOWS_UTILS_H_ - -#include -#include - -#include - -namespace location { -namespace nearby { -namespace windows { - -std::string uint64_to_mac_address_string(uint64_t bluetoothAddress); - -namespace Constants { -// The Id of the Service Name SDP attribute -const uint16_t SdpServiceNameAttributeId = 0x100; - -// The SDP Type of the Service Name SDP attribute. -// The first byte in the SDP Attribute encodes the SDP Attribute Type as -// follows: -// - the Attribute Type size in the least significant 3 bits, -// - the SDP Attribute Type value in the most significant 5 bits. -const char SdpServiceNameAttributeType = (4 << 3) | 5; -} // namespace Constants - -} // namespace windows -} // namespace nearby -} // namespace location - -#endif // PLATFORM_IMPL_WINDOWS_UTILS_H_ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef PLATFORM_IMPL_WINDOWS_UTILS_H_ +#define PLATFORM_IMPL_WINDOWS_UTILS_H_ + +#include +#include + +#include + +#include "absl/strings/string_view.h" +#include "platform/base/byte_array.h" +#include "platform/impl/windows/generated/winrt/Windows.Foundation.h" +#include "platform/impl/windows/generated/winrt/base.h" + +namespace location { +namespace nearby { +namespace windows { + +using winrt::Windows::Foundation::IInspectable; + +std::string uint64_to_mac_address_string(uint64_t bluetoothAddress); + +// Helpers to windows platform +std::wstring string_to_wstring(std::string str); +std::string wstring_to_string(std::wstring wstr); +ByteArray Sha256(absl::string_view input, size_t size); + +namespace Constants { +// The Id of the Service Name SDP attribute +const uint16_t SdpServiceNameAttributeId = 0x100; + +// The SDP Type of the Service Name SDP attribute. +// The first byte in the SDP Attribute encodes the SDP Attribute Type as +// follows: +// - the Attribute Type size in the least significant 3 bits, +// - the SDP Attribute Type value in the most significant 5 bits. +const char SdpServiceNameAttributeType = (4 << 3) | 5; +} // namespace Constants + +class InspectableReader { + public: + static uint16 ReadUint16(IInspectable inspectable); + static uint32 ReadUint32(IInspectable inspectable); + static std::string ReadString(IInspectable inspectable); + static std::vector ReadStringArray(IInspectable inspectable); +}; + +} // namespace windows +} // namespace nearby +} // namespace location + +#endif // PLATFORM_IMPL_WINDOWS_UTILS_H_ diff --git a/cpp/platform/impl/windows/wifi_lan.h b/cpp/platform/impl/windows/wifi_lan.h index 84f5c283..85aedf6e 100644 --- a/cpp/platform/impl/windows/wifi_lan.h +++ b/cpp/platform/impl/windows/wifi_lan.h @@ -15,33 +15,100 @@ #ifndef PLATFORM_IMPL_WINDOWS_WIFI_LAN_H_ #define PLATFORM_IMPL_WINDOWS_WIFI_LAN_H_ +// Windows headers +#include // NOLINT +#include // NOLINT + +// Standard C/C++ headers +#include +#include +#include + +// Nearby connections headers +#include "absl/base/thread_annotations.h" +#include "absl/container/flat_hash_map.h" +#include "absl/container/flat_hash_set.h" +#include "absl/synchronization/mutex.h" #include "platform/api/wifi_lan.h" #include "platform/base/exception.h" #include "platform/base/input_stream.h" #include "platform/base/output_stream.h" +#include "platform/public/count_down_latch.h" +#include "platform/public/mutex.h" + +// WinRT headers +#include "platform/impl/windows/generated/winrt/Windows.Devices.Enumeration.h" +#include "platform/impl/windows/generated/winrt/Windows.Foundation.Collections.h" +#include "platform/impl/windows/generated/winrt/Windows.Foundation.h" +#include "platform/impl/windows/generated/winrt/Windows.Networking.Connectivity.h" +#include "platform/impl/windows/generated/winrt/Windows.Networking.ServiceDiscovery.Dnssd.h" +#include "platform/impl/windows/generated/winrt/Windows.Networking.Sockets.h" +#include "platform/impl/windows/generated/winrt/Windows.Storage.Streams.h" +#include "platform/impl/windows/generated/winrt/base.h" namespace location { namespace nearby { namespace windows { -// Opaque wrapper over a WifiLan service which contains |NsdServiceInfo|. +using winrt::fire_and_forget; +using winrt::Windows::Devices::Enumeration::DeviceInformation; +using winrt::Windows::Devices::Enumeration::DeviceInformationKind; +using winrt::Windows::Devices::Enumeration::DeviceInformationUpdate; +using winrt::Windows::Devices::Enumeration::DeviceWatcher; +using winrt::Windows::Foundation::IInspectable; +using winrt::Windows::Foundation::Collections::IMapView; +using winrt::Windows::Networking::HostName; +using winrt::Windows::Networking::Connectivity::NetworkInformation; +using winrt::Windows::Networking::ServiceDiscovery::Dnssd:: + DnssdRegistrationResult; +using winrt::Windows::Networking::ServiceDiscovery::Dnssd:: + DnssdRegistrationStatus; +using winrt::Windows::Networking::ServiceDiscovery::Dnssd::DnssdServiceInstance; +using winrt::Windows::Networking::Sockets::StreamSocket; +using winrt::Windows::Networking::Sockets::StreamSocketListener; +using winrt::Windows::Networking::Sockets:: + StreamSocketListenerConnectionReceivedEventArgs; +using winrt::Windows::Networking::Sockets::StreamSocketListenerInformation; +using winrt::Windows::Storage::Streams::Buffer; +using winrt::Windows::Storage::Streams::DataReader; +using winrt::Windows::Storage::Streams::IBuffer; +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: - // TODO(b/184975123): replace with real implementation. + WifiLanService() = default; + explicit WifiLanService(NsdServiceInfo nsd_service_info) + : nsd_service_info_(std::move(nsd_service_info)) {} ~WifiLanService() override = default; - // Returns the |NsdServiceInfo| which contains the packed string of - // |WifiLanServiceInfo| and the endpoint info with named key in a TXTRecord - // map. - // The details refer to - // https://developer.android.com/reference/android/net/nsd/NsdServiceInfo.html. - // TODO(b/184975123): replace with real implementation. - NsdServiceInfo GetServiceInfo() const override { return NsdServiceInfo{}; } + 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: - // TODO(b/184975123): replace with real implementation. + WifiLanSocket(StreamSocket socket); ~WifiLanSocket() override; // Returns the InputStream of the WifiLanSocket. @@ -49,108 +116,296 @@ class WifiLanSocket : public api::WifiLanSocket { // // The returned object is not owned by the caller, and can be invalidated once // the WifiLanSocket object is destroyed. - // TODO(b/184975123): replace with real implementation. - InputStream& GetInputStream() override { return fake_input_stream_; } + InputStream& GetInputStream() override; // Returns the OutputStream of the WifiLanSocket. // On error, returned stream will report Exception::kIo on any operation. // // The returned object is not owned by the caller, and can be invalidated once // the WifiLanSocket object is destroyed. - // TODO(b/184975123): replace with real implementation. - OutputStream& GetOutputStream() override { return fake_output_stream_; } + OutputStream& GetOutputStream() override; // Returns Exception::kIo on error, Exception::kSuccess otherwise. - // TODO(b/184975123): replace with real implementation. - Exception Close() override { return Exception{}; } + Exception Close() override; // Returns valid WifiLanService pointer if there is a connection, and // nullptr otherwise. - // TODO(b/184975123): replace with real implementation. - WifiLanService* GetRemoteWifiLanService() override { return nullptr; } + api::WifiLanService* GetRemoteWifiLanService() override; + + // When connect to remove WiFi LAN servie, need to save remove WiFi LAN + // information, so that can return it based on ip address and port query + void SetRemoteWifiLanService(api::WifiLanService* wifi_lan_service); + + // 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: - // TODO(b/184975123): replace with real implementation. - class FakeInputStream : public InputStream { - ~FakeInputStream() override = default; - ExceptionOr Read(std::int64_t size) override { - return ExceptionOr(Exception::kFailed); - } - Exception Close() override { return {.value = Exception::kFailed}; } - }; - class FakeOutputStream : public OutputStream { - ~FakeOutputStream() override = default; + // A simple wrapper to handle input stream of socket + class SocketInputStream : public InputStream { + public: + SocketInputStream(IInputStream input_stream); + ~SocketInputStream() = default; - Exception Write(const ByteArray& data) override { - return {.value = Exception::kFailed}; - } - Exception Flush() override { return {.value = Exception::kFailed}; } - Exception Close() override { return {.value = Exception::kFailed}; } + ExceptionOr Read(std::int64_t size) override; + ExceptionOr Skip(size_t offset) override; + Exception Close() override; + + private: + IInputStream input_stream_{nullptr}; }; - FakeInputStream fake_input_stream_; - FakeOutputStream fake_output_stream_; + + // A simple wrapper to handle output stream of socket + class SocketOutputStream : public OutputStream { + public: + SocketOutputStream(IOutputStream output_stream); + ~SocketOutputStream() = default; + + Exception Write(const ByteArray& data) override; + Exception Flush() override; + Exception Close() override; + + private: + IOutputStream output_stream_; + }; + + // Internal properties + StreamSocket stream_soket_{nullptr}; + std::unique_ptr input_stream_{nullptr}; + std::unique_ptr 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 { + public: + explicit WifiLanNsd(WifiLanMedium* medium, const std::string service_id); + WifiLanNsd(WifiLanNsd&&) = default; + WifiLanNsd& operator=(WifiLanNsd&&) = default; + ~WifiLanNsd() = 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(); + + // 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; } + + bool IsAccepting() { return (nsd_status_ & NSD_STATUS_ACCEPTING) != 0; } + + bool IsAdvertising() { return (nsd_status_ & NSD_STATUS_ADVERTISING) != 0; } + + bool IsDiscovering() { return (nsd_status_ & NSD_STATUS_DISCOVERING) != 0; } + + // 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 GetServiceAddress(); + + // DnsServiceDeRegister is a async process, after operation finish, callback + // will call this method to notify the waiting method StopAdvertising to + // continue. + 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_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 + // + + // Generates prefered 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 futher solution to + // resolve the potential issue + uint16 GenerateSocketPort(const std::string& service_id); + + // From mDNS device information, to build NsdServiceInfo. + // the properties are from DeviceInformation and DeviceInformationUpdate. + // The API gets IP addresses, service name and text attributes of mDNS + // from these properties, + NsdServiceInfo GetNsdServiceInformation( + 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( + DeviceWatcher sender, DeviceInformationUpdate deviceInfoUpdate); + fire_and_forget Watcher_DeviceRemoved( + DeviceWatcher sender, DeviceInformationUpdate deviceInfoUpdate); + 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_{}; + + // NSD Status + int nsd_status_ = NSD_STATUS_IDLE; + + // + // 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}; + + // Stop advertising properties + DNS_SERVICE_INSTANCE dns_service_instance_{nullptr}; + DNS_SERVICE_REGISTER_REQUEST dns_service_register_request_; + std::unique_ptr dns_service_instance_name_{nullptr}; + std::unique_ptr dns_service_stop_latch_; + DWORD dns_service_stop_status_; + + // Discovery properties + DeviceWatcher device_watcher_{nullptr}; + winrt::event_token device_watcher_added_event_token; + 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_; + api::WifiLanMedium::DiscoveredServiceCallback discovered_service_callback_; + + // IP addresses of the computer. mDNS uses them to advertise. + std::vector ip_addresses_{}; }; // Container of operations that can be performed over the WifiLan medium. class WifiLanMedium : public api::WifiLanMedium { public: - // TODO(b/184975123): replace with real implementation. ~WifiLanMedium() override = default; - // TODO(b/184975123): replace with real implementation. + // Starts to advertising bool StartAdvertising(const std::string& service_id, - const NsdServiceInfo& nsd_service_info) override { - return false; - } - // TODO(b/184975123): replace with real implementation. - bool StopAdvertising(const std::string& service_id) override { return false; } + const NsdServiceInfo& nsd_service_info) override; - // Returns true once the WifiLan discovery has been initiated. - // TODO(b/184975123): replace with real implementation. + // Stops to advertising + bool StopAdvertising(const std::string& service_id) override; + + // Starts to discovery bool StartDiscovery(const std::string& service_id, - DiscoveredServiceCallback callback) override { - return false; - } + 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. - // TODO(b/184975123): replace with real implementation. - bool StopDiscovery(const std::string& service_id) override { return false; } + bool StopDiscovery(const std::string& service_id) override; // Returns true once WifiLan socket connection requests to service_id can be // accepted. - // TODO(b/184975123): replace with real implementation. bool StartAcceptingConnections(const std::string& service_id, - AcceptedConnectionCallback callback) override { - return false; - } - // TODO(b/184975123): replace with real implementation. - bool StopAcceptingConnections(const std::string& service_id) override { - return false; - } + 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. - // TODO(b/184975123): replace with real implementation. std::unique_ptr Connect( api::WifiLanService& wifi_lan_service, const std::string& service_id, - CancellationFlag* cancellation_flag) override { - return nullptr; - } + CancellationFlag* cancellation_flag) override; - // TODO(b/184975123): replace with real implementation. - WifiLanService* GetRemoteService(const std::string& ip_address, - int port) override { - return nullptr; - } + // Returns WiFi LAN service from local ip address and port information + api::WifiLanService* GetRemoteService(const std::string& ip_address, + int port) override; - // TODO(b/184975123): replace with real implementation. + // returns advertising service address std::pair GetServiceAddress( - const std::string& service_id) override { - return std::pair{"Un-implemented", 0}; - } + 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_); }; } // namespace windows diff --git a/cpp/platform/impl/windows/wifi_lan_medium.cc b/cpp/platform/impl/windows/wifi_lan_medium.cc new file mode 100644 index 00000000..ddde5426 --- /dev/null +++ b/cpp/platform/impl/windows/wifi_lan_medium.cc @@ -0,0 +1,305 @@ +// 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::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()); + return false; + } +} + +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; + } +} + +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()); + 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; + } + + NEARBY_LOGS(ERROR) << "failed to stop advertising."; + return false; + } catch (...) { + NEARBY_LOGS(ERROR) << "failed to stop advertising due to " + << GetErrorMessage(std::current_exception()); + return false; + } +} + +// Returns true once the WifiLan discovery has been initiated. +bool WifiLanMedium::StartDiscovery(const std::string& service_id, + 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()); + return false; + } +} + +// 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()); + return false; + } +} + +// 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, + CancellationFlag* cancellation_flag) { + try { + auto address = wifi_lan_service.GetServiceInfo().GetServiceAddress(); + if (address.first.empty() || address.second == 0) { + NEARBY_LOGS(ERROR) << "no valid service address and port to connect."; + return nullptr; + } + + HostName host_name{string_to_wstring(address.first)}; + winrt::hstring service_name{winrt::to_hstring(address.second)}; + + 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(std::move(socket)); + wifi_lan_socket->SetServiceId(service_id); + wifi_lan_socket->SetMedium(this); + wifi_lan_socket->SetRemoteWifiLanService(&wifi_lan_service); + { + 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; + } +} + +api::WifiLanService* WifiLanMedium::GetRemoteService( + const std::string& ip_address, int port) { + try { + MutexLock lock(&mutex_); + + for (WifiLanSocket* socket : wifi_lan_sockets_) { + if (socket->GetLocalAddress() == ip_address && + socket->GetLocalPort() == port) { + return socket->GetRemoteWifiLanService(); + } + } + + return nullptr; + } catch (...) { + NEARBY_LOGS(ERROR) << "failed to get remove service due to " + << GetErrorMessage(std::current_exception()); + return nullptr; + } +} + +std::pair WifiLanMedium::GetServiceAddress( + 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}; + } + + return nsd->GetServiceAddress(); + } catch (...) { + NEARBY_LOGS(ERROR) << "failed to get service address due to " + << GetErrorMessage(std::current_exception()); + return {"", 0}; + } +} + +void WifiLanMedium::CloseConnection(WifiLanSocket& socket) { + MutexLock lock(&mutex_); + if (wifi_lan_sockets_.contains(&socket)) { + wifi_lan_sockets_.erase(&socket); + } +} + +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(); +} + +bool WifiLanMedium::RemoveNsd(std::string service_id) { + MutexLock lock(&mutex_); + + 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; + } + + service_to_nsd_map_.erase(nsd); + return true; +} + +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 diff --git a/cpp/platform/impl/windows/wifi_lan_nsd.cc b/cpp/platform/impl/windows/wifi_lan_nsd.cc new file mode 100644 index 00000000..37b07fe2 --- /dev/null +++ b/cpp/platform/impl/windows/wifi_lan_nsd.cc @@ -0,0 +1,506 @@ +// 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 " + "addreses 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.GetServiceInfoName().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.SetServiceAddress(ip_addresses_[0], 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().GetServiceInfoName(), 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().GetServiceInfoName(), service_type_); + int port = wifi_lan_service_.GetServiceInfo().GetServiceAddress().second; + 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::GetServiceAddress() { + if (!IsAdvertising()) { + // no advertising is running + NEARBY_LOGS(WARNING) << "no advertising for service id " << service_id_; + return std::pair{"", 0}; + } + + return wifi_lan_service_.GetServiceInfo().GetServiceAddress(); +} + +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 + WifiLanSocket socket{stream_socket}; + socket.SetMedium(medium_); + socket.SetServiceId(service_id_); + accepted_connection_callback_.accepted_cb(socket, 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.SetServiceInfoName( + 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.SetServiceAddress(ip_address, 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 informaiton 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 informaiton 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_socket.cc b/cpp/platform/impl/windows/wifi_lan_socket.cc new file mode 100644 index 00000000..5c028d6b --- /dev/null +++ b/cpp/platform/impl/windows/wifi_lan_socket.cc @@ -0,0 +1,171 @@ +// 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" +#include "platform/public/logging.h" + +namespace location { +namespace nearby { +namespace windows { + +WifiLanSocket::WifiLanSocket(StreamSocket socket) { + stream_soket_ = socket; + input_stream_ = std::make_unique(socket.InputStream()); + output_stream_ = std::make_unique(socket.OutputStream()); +} + +WifiLanSocket::~WifiLanSocket() { + if (stream_soket_ != nullptr) { + try { + Close(); + } catch (...) { + NEARBY_LOGS(ERROR) << "Failed to destructor class WifiLanSocket."; + } + } +} + +InputStream& WifiLanSocket::GetInputStream() { return *input_stream_.get(); } + +OutputStream& WifiLanSocket::GetOutputStream() { return *output_stream_.get(); } + +Exception WifiLanSocket::Close() { + try { + if (stream_soket_ != nullptr) { + stream_soket_.Close(); + stream_soket_ = nullptr; + input_stream_ = nullptr; + output_stream_ = nullptr; + medium_->CloseConnection(*this); + } + return {Exception::kSuccess}; + } catch (...) { + return {Exception::kIo}; + } +} + +api::WifiLanService* WifiLanSocket::GetRemoteWifiLanService() { + return remote_wifi_lan_service_; +} + +void WifiLanSocket::SetRemoteWifiLanService( + api::WifiLanService* wifi_lan_service) { + remote_wifi_lan_service_ = 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; +} + +ExceptionOr WifiLanSocket::SocketInputStream::Read( + std::int64_t size) { + try { + Buffer buffer = Buffer(size); + + auto ibuffer = + input_stream_.ReadAsync(buffer, size, InputStreamOptions::None).get(); + ByteArray data((char*)ibuffer.data(), ibuffer.Length()); + return ExceptionOr(data); + } catch (...) { + return Exception{Exception::kIo}; + } +} + +ExceptionOr WifiLanSocket::SocketInputStream::Skip(size_t offset) { + try { + Buffer buffer = Buffer(offset); + + auto ibuffer = + input_stream_.ReadAsync(buffer, offset, InputStreamOptions::None).get(); + return ExceptionOr((size_t)ibuffer.Length()); + } catch (...) { + return Exception{Exception::kIo}; + } +} + +Exception WifiLanSocket::SocketInputStream::Close() { + try { + input_stream_.Close(); + } catch (std::exception exception) { + return {Exception::kIo}; + } + + return {Exception::kSuccess}; +} + +// SocketOutputStream +WifiLanSocket::SocketOutputStream::SocketOutputStream( + IOutputStream output_stream) { + output_stream_ = output_stream; +} + +Exception WifiLanSocket::SocketOutputStream::Write(const ByteArray& data) { + Buffer buffer = Buffer(data.size()); + std::memcpy(buffer.data(), data.data(), data.size()); + + try { + output_stream_.WriteAsync(buffer); + } catch (std::exception exception) { + return {Exception::kIo}; + } + + return {Exception::kSuccess}; +} + +Exception WifiLanSocket::SocketOutputStream::Flush() { + try { + output_stream_.FlushAsync().get(); + } catch (std::exception exception) { + return {Exception::kIo}; + } + + return {Exception::kSuccess}; +} + +Exception WifiLanSocket::SocketOutputStream::Close() { + try { + output_stream_.Close(); + } catch (std::exception exception) { + return {Exception::kIo}; + } + + return {Exception::kSuccess}; +} + +} // namespace windows +} // namespace nearby +} // namespace location