diff --git a/include/wil/network.h b/include/wil/network.h new file mode 100644 index 000000000..4ba12ae42 --- /dev/null +++ b/include/wil/network.h @@ -0,0 +1,1328 @@ +//********************************************************* +// +// Copyright (c) Microsoft. All rights reserved. +// This code is licensed under the MIT License. +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF +// ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED +// TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A +// PARTICULAR PURPOSE AND NONINFRINGEMENT. +// +//********************************************************* +//! @file +//! Helpers for using BSD sockets and Winsock functions and structures. +//! Does not require the use of the STL or C++ exceptions (see _nothrow functions) +#ifndef __WIL_NETWORK_INCLUDED +#define __WIL_NETWORK_INCLUDED + +#ifdef _KERNEL_MODE +#error This header is not supported in kernel-mode. +#endif + +//! define WIN32_LEAN_AND_MEAN at the project level to avoid windows.h including older winsock 1.1 headers from winsock.h +//! as including both the Winsock 1.1 header 'winsock.h' and the Winsock 2 header 'winsock2.h' will create compiler errors +//! alternatively, including winsock2.h before windows.h to prevent inclusion of the Winsock 1.1 winsock.h header from within windows.h +//! note: winsock2.h will include windows.h if not already included +//! +//! define _SECURE_SOCKET_TYPES_DEFINED_ at the project level to have access to SocketSecurity* functions in ws2tcpip.h +//! +//! define INCL_WINSOCK_API_TYPEDEFS at the project level to make function typedefs available across various networking headers +//! note, winsock2.h defaults is to not include function typedefs - but these can be necessary when supporting multiple OS versions +//! +//! Link libs for functions referenced in this file: ws2_32.lib, ntdll.lib, and Fwpuclnt.lib (for secure socket functions) + +#if !defined(_WINSOCK2API_) && defined(_WINSOCKAPI_) +#error The Winsock 1.1 winsock.h header was included before the Winsock 2 winsock2.h header - this will cause compilation errors - define WIN32_LEAN_AND_MEAN to avoid winsock.h included by windows.h, or include winsock2.h before windows.h +#endif + +//! Including Winsock and networking headers in the below specific sequence +//! These headers have many intra-header dependencies, creating difficulties when needing access to various functions and types +//! This specific sequence should compile correctly to give access to all available functions and types +#include +#include +#include +#include +#include +#include +#include +#include + +// required wil headers +#include "wistd_type_traits.h" +#include "resource.h" + +namespace wil +{ +//! Functions and classes that support networking operations and structures +namespace network +{ + //! A type that calls WSACleanup on destruction (or reset()). + //! WSAStartup must be called for the lifetime of all Winsock APIs (synchronous and asynchronous) + //! WSACleanup will unload the full Winsock catalog - all the libraries - with the final reference + //! which can lead to crashes if socket APIs are still being used after the final WSACleanup is called + using unique_wsacleanup_call = ::wil::unique_call; + + //! Calls WSAStartup; returns an RAII object that reverts, the RAII object will resolve to bool 'false' if failed + WI_NODISCARD inline ::wil::network::unique_wsacleanup_call WSAStartup_nothrow() WI_NOEXCEPT + { + WSADATA unused_data{}; + const auto error{::WSAStartup(WINSOCK_VERSION, &unused_data)}; + LOG_IF_WIN32_ERROR(error); + + ::wil::network::unique_wsacleanup_call return_cleanup{}; + if (error != 0) + { + // internally set m_call to false + // so the caller can check the return object against its operator bool + // to determine if WSAStartup succeeded + return_cleanup.release(); + } + return return_cleanup; + } + + //! Calls WSAStartup and fail-fasts on error; returns an RAII object that reverts + WI_NODISCARD inline ::wil::network::unique_wsacleanup_call WSAStartup_failfast() WI_NOEXCEPT + { + WSADATA unused_data{}; + FAIL_FAST_IF_WIN32_ERROR(::WSAStartup(WINSOCK_VERSION, &unused_data)); + return {}; + } + +#if defined(WIL_ENABLE_EXCEPTIONS) + //! Calls WSAStartup and throws on error; returns an RAII object that reverts + WI_NODISCARD inline ::wil::network::unique_wsacleanup_call WSAStartup() + { + WSADATA unused_data{}; + THROW_IF_WIN32_ERROR(::WSAStartup(WINSOCK_VERSION, &unused_data)); + return {}; + } +#endif + + // + // utility functions to compare inaddr types + // + [[nodiscard]] inline bool equals(const ::in_addr& lhs, const ::in_addr& rhs) WI_NOEXCEPT + { + return lhs.s_addr == rhs.s_addr; + } + + [[nodiscard]] inline bool not_equals(const ::in_addr& lhs, const ::in_addr& rhs) WI_NOEXCEPT + { + return !::wil::network::equals(lhs, rhs); + } + + [[nodiscard]] inline bool equals(const ::in6_addr& lhs, const ::in6_addr& rhs) WI_NOEXCEPT + { + return 0 == ::memcmp(&lhs, &rhs, sizeof(::in6_addr)); + } + + [[nodiscard]] inline bool not_equals(const ::in6_addr& lhs, const ::in6_addr& rhs) WI_NOEXCEPT + { + return !::wil::network::equals(lhs, rhs); + } + + // + //! encapsulates working with the sockaddr datatype + //! + //! sockaddr is a generic type - similar to a base class, but designed for C with BSD sockets (1983-ish) + //! 'derived' structures are cast back to sockaddr* (so the initial struct members must be aligned) + //! + //! this data type was built to be 'extensible' so new network types could create their own address structures + //! - appending fields to the initial fields of the sockaddr + //! + //! note that the address and port fields of TCPIP sockaddr* types were designed to be encoded in network-byte order + //! - hence the common use of "host-to-network" and "network-to-host" APIs, e.g. htons(), htonl(), ntohs(), ntohl() + //! + //! Socket APIs that accept a socket address will accept 2 fields: + //! - the sockaddr* (the address of the derived sockaddr type, cast down to a sockaddr*) + //! - the length of the 'derived' socket address structure referenced by the sockaddr* + //! + //! Commonly used sockaddr* types that are using with TCPIP networking: + //! + //! sockaddr_storage / SOCKADDR_STORAGE + //! - a sockaddr* derived type that is guaranteed to be large enough to hold any possible socket address (not just TCPIP related) + //! sockaddr_in / SOCKADDR_IN + //! - a sockaddr* derived type designed to contain an IPv4 address and port number + //! sockaddr_in6 / SOCKADDR_IN6 + //! - a sockaddr* derived type designed to contain an IPv6 address, port, scope id, and flow info + //! SOCKADDR_INET + //! - a union of sockaddr_in and sockaddr_in6 -- i.e., large enough to contain any TCPIP IPv4 or IPV6 address + //! in_addr / IN_ADDR + //! - the raw address portion of a sockaddr_in + //! in6_addr / IN6_ADDR + //! - the raw address portion of a sockaddr_in6 + //! + //! SOCKET_ADDRESS + //! - not a derived sockaddr* type + //! - a structure containing both a sockaddr* and its length fields, returned from some networking functions + //! + + // declaring char-arrays large enough for any IPv4 or IPv6 address + optional fields + static_assert(INET6_ADDRSTRLEN > INET_ADDRSTRLEN); + typedef WCHAR socket_address_wstring[INET6_ADDRSTRLEN]; + typedef CHAR socket_address_string[INET6_ADDRSTRLEN]; + + class socket_address final + { + public: + constexpr socket_address() WI_NOEXCEPT = default; + explicit socket_address(ADDRESS_FAMILY family) WI_NOEXCEPT; + template + explicit socket_address(_In_reads_bytes_(addr_size) const SOCKADDR* addr, T addr_size) WI_NOEXCEPT; + explicit socket_address(const SOCKADDR_IN*) WI_NOEXCEPT; + explicit socket_address(const SOCKADDR_IN6*) WI_NOEXCEPT; + explicit socket_address(const SOCKADDR_INET*) WI_NOEXCEPT; + explicit socket_address(const SOCKET_ADDRESS*) WI_NOEXCEPT; + explicit socket_address(const IN_ADDR*, unsigned short port = 0) WI_NOEXCEPT; + explicit socket_address(const IN6_ADDR*, unsigned short port = 0) WI_NOEXCEPT; +#if defined(WIL_ENABLE_EXCEPTIONS) + explicit socket_address(PCWSTR, unsigned short port = 0); +#endif + + ~socket_address() = default; + socket_address(const socket_address&) WI_NOEXCEPT = default; + socket_address& operator=(const socket_address&) WI_NOEXCEPT = default; + socket_address(socket_address&&) WI_NOEXCEPT = default; + socket_address& operator=(socket_address&&) WI_NOEXCEPT = default; + + bool operator==(const socket_address&) const WI_NOEXCEPT; + bool operator!=(const socket_address&) const WI_NOEXCEPT; + bool operator<(const socket_address&) const WI_NOEXCEPT; + bool operator>(const socket_address&) const WI_NOEXCEPT; + + void swap(socket_address&) WI_NOEXCEPT; + void reset(ADDRESS_FAMILY family = AF_UNSPEC) WI_NOEXCEPT; + + // set_sockaddr overwrites the entire sockaddr in the object (including address family) + template + void set_sockaddr(_In_reads_bytes_(addr_size) const SOCKADDR* addr, T addr_size) WI_NOEXCEPT; + void set_sockaddr(const SOCKADDR_IN*) WI_NOEXCEPT; + void set_sockaddr(const SOCKADDR_IN6*) WI_NOEXCEPT; + void set_sockaddr(const SOCKADDR_INET*) WI_NOEXCEPT; + void set_sockaddr(const SOCKET_ADDRESS*) WI_NOEXCEPT; +#if defined(WIL_ENABLE_EXCEPTIONS) + void set_sockaddr(SOCKET); + void set_sockaddr(PCWSTR); + void set_sockaddr(PCSTR); +#endif + [[nodiscard]] HRESULT set_sockaddr_nothrow(SOCKET) WI_NOEXCEPT; + [[nodiscard]] HRESULT set_sockaddr_nothrow(PCWSTR) WI_NOEXCEPT; + [[nodiscard]] HRESULT set_sockaddr_nothrow(PCSTR) WI_NOEXCEPT; + + // set_address* preserves the existing port set on the address + // - so that one can call set_port() and set_address() in any order with expected results + // the address family is preserved unless it is specified (or inferred) as an argument + void set_address_any() WI_NOEXCEPT; + void set_address_any(ADDRESS_FAMILY family) WI_NOEXCEPT; + void set_address_loopback() WI_NOEXCEPT; + void set_address_loopback(ADDRESS_FAMILY family) WI_NOEXCEPT; + void set_address(const IN_ADDR*) WI_NOEXCEPT; + void set_address(const IN6_ADDR*) WI_NOEXCEPT; + + void set_port(USHORT) WI_NOEXCEPT; + void set_scope_id(ULONG) WI_NOEXCEPT; + void set_flow_info(ULONG) WI_NOEXCEPT; + + // write_address prints the IP address, not the scope id or port + HRESULT write_address_nothrow(socket_address_wstring& address) const WI_NOEXCEPT; + HRESULT write_address_nothrow(socket_address_string& address) const WI_NOEXCEPT; + + // write_complete_address_nothrow() prints the IP address as well as the scope id and port values + HRESULT write_complete_address_nothrow(socket_address_wstring& address) const WI_NOEXCEPT; + HRESULT write_complete_address_nothrow(socket_address_string& address) const WI_NOEXCEPT; + +#if defined(WIL_ENABLE_EXCEPTIONS) && (defined(_STRING_) || defined(WIL_DOXYGEN)) + [[nodiscard]] ::std::wstring write_address() const; + [[nodiscard]] ::std::wstring write_complete_address() const; +#endif + + // type: NlatUnspecified ('any'), NlatUnicast, NlatAnycast, NlatMulticast, NlatBroadcast, NlatInvalid + [[nodiscard]] NL_ADDRESS_TYPE address_type() const WI_NOEXCEPT; + [[nodiscard]] bool is_address_linklocal() const WI_NOEXCEPT; + [[nodiscard]] bool is_address_loopback() const WI_NOEXCEPT; + + [[nodiscard]] ADDRESS_FAMILY family() const WI_NOEXCEPT; + [[nodiscard]] USHORT port() const WI_NOEXCEPT; + [[nodiscard]] ULONG flow_info() const WI_NOEXCEPT; + [[nodiscard]] ULONG scope_id() const WI_NOEXCEPT; + + [[nodiscard]] SOCKADDR* sockaddr() WI_NOEXCEPT; + [[nodiscard]] SOCKADDR_IN* sockaddr_in() WI_NOEXCEPT; + [[nodiscard]] SOCKADDR_IN6* sockaddr_in6() WI_NOEXCEPT; + [[nodiscard]] SOCKADDR_INET* sockaddr_inet() WI_NOEXCEPT; + [[nodiscard]] IN_ADDR* in_addr() WI_NOEXCEPT; + [[nodiscard]] IN6_ADDR* in6_addr() WI_NOEXCEPT; + + [[nodiscard]] const SOCKADDR* sockaddr() const WI_NOEXCEPT; + [[nodiscard]] const SOCKADDR_IN* sockaddr_in() const WI_NOEXCEPT; + [[nodiscard]] const SOCKADDR_IN6* sockaddr_in6() const WI_NOEXCEPT; + [[nodiscard]] const SOCKADDR_INET* sockaddr_inet() const WI_NOEXCEPT; + [[nodiscard]] const IN_ADDR* in_addr() const WI_NOEXCEPT; + [[nodiscard]] const IN6_ADDR* in6_addr() const WI_NOEXCEPT; + + [[nodiscard]] SOCKADDR_STORAGE sockaddr_storage() const WI_NOEXCEPT; + + [[nodiscard]] constexpr int length() const + { + return sizeof(m_sockaddr); + } + + private: + SOCKADDR_INET m_sockaddr{}; + }; + + // When using dual-mode sockets, one might need to connect to a target IPv4 address. + // Since dual-mode socket types are IPv6, one must map that IPv4 address to its 'mapped' IPv6 address + inline ::wil::network::socket_address map_dual_mode_4to6(const ::wil::network::socket_address& ipv4_address) WI_NOEXCEPT + { + constexpr IN6_ADDR ipv4MappedPrefix{{IN6ADDR_V4MAPPEDPREFIX_INIT}}; + + ::wil::network::socket_address return_ipv6_address{&ipv4MappedPrefix, ipv4_address.port()}; + + auto* const return_in6_addr{return_ipv6_address.in6_addr()}; + const auto* const ipv4_inaddr{ipv4_address.in_addr()}; + return_in6_addr->u.Byte[12] = ipv4_inaddr->S_un.S_un_b.s_b1; + return_in6_addr->u.Byte[13] = ipv4_inaddr->S_un.S_un_b.s_b2; + return_in6_addr->u.Byte[14] = ipv4_inaddr->S_un.S_un_b.s_b3; + return_in6_addr->u.Byte[15] = ipv4_inaddr->S_un.S_un_b.s_b4; + + return return_ipv6_address; + } + + //! non-member swap + inline void swap(::wil::network::socket_address& lhs, ::wil::network::socket_address& rhs) WI_NOEXCEPT + { + lhs.swap(rhs); + } + + //! class addr_info encapsulates the ADDRINFO-related structures returned from the socket functions + //! getaddrinfo, GetAddrInfoW, GetAddrInfoWEx + //! iterator semantics are supported to safely access these addresses + // ! template T supports pointers to the 3 address structures: ADDRINFOA*, ADDRINFOW*, ADDRINFOEXW* + template + class addr_info_iterator_t + { + public: + static constexpr addr_info_iterator_t end() WI_NOEXCEPT + { + return addr_info_iterator_t{}; + } + + // defining iterator_traits allows STL functions to be used with this iterator class. + // Notice this is a forward_iterator + // - does not support random-access (e.g. vector::iterator) + // - does not support bidirectional access (e.g. list::iterator) +#if defined(_ITERATOR_) || defined(WIL_DOXYGEN) + using iterator_category = ::std::forward_iterator_tag; +#endif + using value_type = ::wil::network::socket_address; + using difference_type = size_t; + using distance_type = size_t; + using pointer = ::wil::network::socket_address*; + using reference = ::wil::network::socket_address&; + + constexpr addr_info_iterator_t() WI_NOEXCEPT = default; + + explicit addr_info_iterator_t(const T* addrinfo) WI_NOEXCEPT : + // must const cast so we can re-use this pointer as we walk the list + m_addrinfo_ptr(const_cast(addrinfo)) + { + if (m_addrinfo_ptr) + { + m_socket_address.set_sockaddr(m_addrinfo_ptr->ai_addr, m_addrinfo_ptr->ai_addrlen); + } + } + + ~addr_info_iterator_t() WI_NOEXCEPT = default; + addr_info_iterator_t(const addr_info_iterator_t&) WI_NOEXCEPT = default; + addr_info_iterator_t& operator=(const addr_info_iterator_t&) WI_NOEXCEPT = default; + addr_info_iterator_t(addr_info_iterator_t&&) WI_NOEXCEPT = default; + addr_info_iterator_t& operator=(addr_info_iterator_t&&) WI_NOEXCEPT = default; + + const ::wil::network::socket_address& operator*() const WI_NOEXCEPT + { + return m_socket_address; + } + + const ::wil::network::socket_address& operator*() WI_NOEXCEPT + { + return m_socket_address; + } + + const ::wil::network::socket_address* operator->() const WI_NOEXCEPT + { + return &m_socket_address; + } + + const ::wil::network::socket_address* operator->() WI_NOEXCEPT + { + return &m_socket_address; + } + + [[nodiscard]] bool operator==(const addr_info_iterator_t& rhs) const WI_NOEXCEPT + { + return m_addrinfo_ptr == rhs.m_addrinfo_ptr; + } + + [[nodiscard]] bool operator!=(const addr_info_iterator_t& rhs) const WI_NOEXCEPT + { + return !(*this == rhs); + } + + addr_info_iterator_t& operator++() WI_NOEXCEPT + { + this->operator+=(1); + return *this; + } + + addr_info_iterator_t operator++(int) WI_NOEXCEPT + { + auto return_value = *this; + this->operator+=(1); + return return_value; + } + + addr_info_iterator_t& operator+=(size_t offset) WI_NOEXCEPT + { + for (size_t count = 0; count < offset; ++count) + { + WI_ASSERT(m_addrinfo_ptr); + if (m_addrinfo_ptr) + { + m_addrinfo_ptr = m_addrinfo_ptr->ai_next; + + if (m_addrinfo_ptr) + { + m_socket_address.set_sockaddr(m_addrinfo_ptr->ai_addr, m_addrinfo_ptr->ai_addrlen); + } + else + { + m_socket_address.reset(); + } + } + } + + return *this; + } + + private: + // non-ownership of this pointer - the parent class must outlive the iterator + T* m_addrinfo_ptr{nullptr}; + ::wil::network::socket_address m_socket_address{}; + }; // class addr_info_iterator_t + + using addr_info_ansi_iterator = addr_info_iterator_t; + using addr_info_iterator = addr_info_iterator_t; + // not defining a type for ADDRINFOEXA as that type is formally __declspec(deprecated) + using addr_infoex_iterator = addr_info_iterator_t; + +#if defined(WIL_ENABLE_EXCEPTIONS) + //! wil function to capture resolving IP addresses assigned to the local machine, throwing on error + //! returning an RAII object containing the results + inline ::wil::unique_addrinfo resolve_local_addresses() WI_NOEXCEPT + { + constexpr auto* local_address_name_string = L""; + ADDRINFOW* addrResult{}; + if (::GetAddrInfoW(local_address_name_string, nullptr, nullptr, &addrResult) != 0) + { + THROW_WIN32(::WSAGetLastError()); + } + + return ::wil::unique_addrinfo{addrResult}; + } + + //! wil function to capture resolving the local-host (loopback) addresses, throwing on error + //! returning an RAII object containing the results + inline ::wil::unique_addrinfo resolve_localhost_addresses() WI_NOEXCEPT + { + constexpr auto* localhost_address_name_string = L"localhost"; + ADDRINFOW* addrResult{}; + if (::GetAddrInfoW(localhost_address_name_string, nullptr, nullptr, &addrResult) != 0) + { + THROW_WIN32(::WSAGetLastError()); + } + + return ::wil::unique_addrinfo{addrResult}; + } +#endif + + struct WINSOCK_EXTENSION_FUNCTION_TABLE + { + LPFN_ACCEPTEX AcceptEx{nullptr}; + LPFN_CONNECTEX ConnectEx{nullptr}; + LPFN_DISCONNECTEX DisconnectEx{nullptr}; + LPFN_GETACCEPTEXSOCKADDRS GetAcceptExSockaddrs{nullptr}; + LPFN_TRANSMITFILE TransmitFile{nullptr}; + LPFN_TRANSMITPACKETS TransmitPackets{nullptr}; + LPFN_WSARECVMSG WSARecvMsg{nullptr}; + LPFN_WSASENDMSG WSASendMsg{nullptr}; + }; + + template + struct socket_extension_function_table_t; + using winsock_extension_function_table = socket_extension_function_table_t; + using rio_extension_function_table = socket_extension_function_table_t; + + template + struct socket_extension_function_table_t + { + static socket_extension_function_table_t load() WI_NOEXCEPT; + + ~socket_extension_function_table_t() WI_NOEXCEPT = default; + + // can copy, but the new object needs its own WSA reference count + // (getting a WSA reference count should be no-fail once the first reference it taken) + // + // IF the target could not get a WSA reference count + // OR + // IF we couldn't get our own WSA reference count + // (this should never happen the caller has a reference, but we failed to get a WSA reference) + // THEN + // this object cannot carry forward any function pointers - it must show successfully loaded == false + socket_extension_function_table_t(const socket_extension_function_table_t& rhs) WI_NOEXCEPT : + wsa_reference_count{WSAStartup_nothrow()} + { + if (!wsa_reference_count || !rhs.wsa_reference_count) + { + ::memset(&f, 0, sizeof(f)); + } + else + { + ::memcpy_s(&f, sizeof(f), &rhs.f, sizeof(rhs.f)); + } + } + + socket_extension_function_table_t& operator=(const socket_extension_function_table_t& rhs) WI_NOEXCEPT + { + if (!wsa_reference_count || !rhs.wsa_reference_count) + { + ::memset(&f, 0, sizeof(f)); + } + else + { + ::memcpy_s(&f, sizeof(f), &rhs.f, sizeof(rhs.f)); + } + + return *this; + } + + //! Returns true if all functions were loaded, holding a WSAStartup reference + WI_NODISCARD explicit operator bool() const WI_NOEXCEPT; + + F f{}; + + private: + // constructed via load() + socket_extension_function_table_t() WI_NOEXCEPT : + wsa_reference_count{WSAStartup_nothrow()} + { + } + + // must guarantee Winsock does not unload while we have dynamically loaded function pointers + const ::wil::network::unique_wsacleanup_call wsa_reference_count; + }; + + // + // explicit specializations for socket_extension_function_table_t + // + template <> + inline socket_extension_function_table_t::operator bool() const WI_NOEXCEPT + { + return f.AcceptEx != nullptr; + } + + template <> + inline socket_extension_function_table_t::operator bool() const WI_NOEXCEPT + { + return f.RIOReceive != nullptr; + } + + template <> + inline winsock_extension_function_table socket_extension_function_table_t< + WINSOCK_EXTENSION_FUNCTION_TABLE>::load() WI_NOEXCEPT + { + winsock_extension_function_table table; + // if WSAStartup failed, immediately exit + if (!table.wsa_reference_count) + { + return table; + } + + // we need a temporary socket for the IOCTL to load the functions + const ::wil::unique_socket localSocket{::socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP)}; + if (INVALID_SOCKET == localSocket.get()) + { + return table; + } + + const auto load_function_pointer = [](SOCKET lambdaSocket, GUID extensionGuid, void* functionPtr) WI_NOEXCEPT { + constexpr DWORD controlCode{SIO_GET_EXTENSION_FUNCTION_POINTER}; + constexpr DWORD bytes{sizeof(functionPtr)}; + DWORD unused_bytes{}; + const auto error{::WSAIoctl( + lambdaSocket, + controlCode, + &extensionGuid, + DWORD{sizeof(extensionGuid)}, + functionPtr, + bytes, + &unused_bytes, + nullptr, + nullptr)}; + return error == 0 ? S_OK : HRESULT_FROM_WIN32(::WSAGetLastError()); + }; + + if (FAILED_LOG(load_function_pointer(localSocket.get(), WSAID_ACCEPTEX, &table.f.AcceptEx)) || + FAILED_LOG(load_function_pointer(localSocket.get(), WSAID_CONNECTEX, &table.f.ConnectEx)) || + FAILED_LOG(load_function_pointer(localSocket.get(), WSAID_DISCONNECTEX, &table.f.DisconnectEx)) || + FAILED_LOG(load_function_pointer(localSocket.get(), WSAID_GETACCEPTEXSOCKADDRS, &table.f.GetAcceptExSockaddrs)) || + FAILED_LOG(load_function_pointer(localSocket.get(), WSAID_TRANSMITFILE, &table.f.TransmitFile)) || + FAILED_LOG(load_function_pointer(localSocket.get(), WSAID_TRANSMITPACKETS, &table.f.TransmitPackets)) || + FAILED_LOG(load_function_pointer(localSocket.get(), WSAID_WSARECVMSG, &table.f.WSARecvMsg)) || + FAILED_LOG(load_function_pointer(localSocket.get(), WSAID_WSASENDMSG, &table.f.WSASendMsg))) + { + // if any failed to be found, something is very broken + // all should load, or all should fail + ::memset(&table.f, 0, sizeof(table.f)); + } + + return table; + } + + template <> + inline rio_extension_function_table socket_extension_function_table_t::load() WI_NOEXCEPT + { + rio_extension_function_table table{}; + // if WSAStartup failed, immediately exit + if (!table.wsa_reference_count) + { + return table; + } + + // we need a temporary socket for the IOCTL to load the functions + const ::wil::unique_socket localSocket{::socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP)}; + if (INVALID_SOCKET == localSocket.get()) + { + return table; + } + + constexpr DWORD controlCode{SIO_GET_MULTIPLE_EXTENSION_FUNCTION_POINTER}; + constexpr DWORD bytes{sizeof(table.f)}; + GUID rioGuid = WSAID_MULTIPLE_RIO; + + ::memset(&table.f, 0, bytes); + table.f.cbSize = bytes; + + DWORD unused_bytes{}; + if (::WSAIoctl( + localSocket.get(), + controlCode, + &rioGuid, + DWORD{sizeof(rioGuid)}, + &table.f, + bytes, + &unused_bytes, + nullptr, + nullptr) != 0) + { + LOG_IF_WIN32_ERROR(::WSAGetLastError()); + + ::memset(&table.f, 0, bytes); + } + return table; + } + + // + // socket_address definitions + // + inline socket_address::socket_address(ADDRESS_FAMILY family) WI_NOEXCEPT + { + reset(family); + } + + template + socket_address::socket_address(_In_reads_bytes_(addr_size) const SOCKADDR* addr, T addr_size) WI_NOEXCEPT + { + set_sockaddr(addr, addr_size); + } + + inline socket_address::socket_address(const SOCKADDR_IN* addr) WI_NOEXCEPT + { + set_sockaddr(addr); + } + + inline socket_address::socket_address(const SOCKADDR_IN6* addr) WI_NOEXCEPT + { + set_sockaddr(addr); + } + + inline socket_address::socket_address(const SOCKADDR_INET* addr) WI_NOEXCEPT + { + set_sockaddr(addr); + } + + inline socket_address::socket_address(const SOCKET_ADDRESS* addr) WI_NOEXCEPT + { + set_sockaddr(addr); + } + + inline socket_address::socket_address(const IN_ADDR* addr, unsigned short port) WI_NOEXCEPT + { + reset(AF_INET); + set_address(addr); + set_port(port); + } + + inline socket_address::socket_address(const IN6_ADDR* addr, unsigned short port) WI_NOEXCEPT + { + reset(AF_INET6); + set_address(addr); + set_port(port); + } + +#if defined(WIL_ENABLE_EXCEPTIONS) + inline socket_address::socket_address(PCWSTR addr, unsigned short port) + { + set_sockaddr(addr); + set_port(port); + } +#endif + inline bool socket_address::operator==(const ::wil::network::socket_address& rhs) const WI_NOEXCEPT + { + const auto& lhs{*this}; + + // Follows the same documented comparison logic as GetTcpTable2 and GetTcp6Table2 + if (lhs.family() != rhs.family()) + { + return false; + } + + if (lhs.family() == AF_INET) + { + // don't compare the padding at the end of the SOCKADDR_IN + return ::memcmp(&lhs.m_sockaddr.Ipv4, &rhs.m_sockaddr.Ipv4, sizeof(SOCKADDR_IN) - sizeof(SOCKADDR_IN::sin_zero)) == 0; + } + + return ::memcmp(&lhs.m_sockaddr.Ipv6, &rhs.m_sockaddr.Ipv6, sizeof(SOCKADDR_IN6)) == 0; + } + + inline bool socket_address::operator!=(const ::wil::network::socket_address& rhs) const WI_NOEXCEPT + { + return !(*this == rhs); + } + + inline bool socket_address::operator<(const ::wil::network::socket_address& rhs) const WI_NOEXCEPT + { + const auto& lhs{*this}; + + if (lhs.family() != rhs.family()) + { + return lhs.family() < rhs.family(); + } + + // for operator<, we cannot just memcmp the raw sockaddr values - as they are in network-byte order + // we have to first convert back to host-byte order to do comparisons + // else the user will see odd behavior, like 1.1.1.1 < 0.0.0.0 (which won't make senses) + switch (lhs.family()) + { + case AF_INET: + { + // compare the address first + auto comparison{::memcmp(lhs.in_addr(), rhs.in_addr(), sizeof(IN_ADDR))}; + if (comparison != 0) + { + return comparison < 0; + } + + // then compare the port (host-byte-order) + // only resolve the ntohs() once + const auto lhs_port{lhs.port()}; + const auto rhs_port{rhs.port()}; + if (lhs_port != rhs_port) + { + return lhs_port < rhs_port; + } + + return true; + } + + case AF_INET6: + { + // compare the address first + auto comparison{::memcmp(lhs.in6_addr(), rhs.in6_addr(), sizeof(IN6_ADDR))}; + if (comparison != 0) + { + return comparison < 0; + } + + // then compare the port (host-byte-order) + // only resolve the ntohs() once + const auto lhs_port{lhs.port()}; + const auto rhs_port{rhs.port()}; + if (lhs_port != rhs_port) + { + return lhs_port < rhs_port; + } + + // then compare the scope_id of the address + const auto lhs_scope_id{lhs.scope_id()}; + const auto rhs_scope_id{rhs.scope_id()}; + if (lhs_scope_id != rhs_scope_id) + { + return lhs_scope_id < rhs_scope_id; + } + + // then compare flow_info + const auto lhs_flow_info{lhs.flow_info()}; + const auto rhs_flow_info{rhs.flow_info()}; + if (lhs_flow_info != rhs_flow_info) + { + return lhs_flow_info < rhs_flow_info; + } + + return true; + } + + default: + // if not AF_INET or AF_INET6, and families don't match + // then just raw memcmp the largest field of the union (v6) + return ::memcmp(&lhs.m_sockaddr.Ipv6, &rhs.m_sockaddr.Ipv6, sizeof(SOCKADDR_IN6)) < 0; + } + } + + inline bool socket_address::operator>(const ::wil::network::socket_address& rhs) const WI_NOEXCEPT + { + return !(*this < rhs); + } + + inline void socket_address::swap(socket_address& addr) WI_NOEXCEPT + { + SOCKADDR_INET tempAddr{}; + ::memcpy_s(&tempAddr, sizeof(tempAddr), &addr.m_sockaddr, sizeof(addr.m_sockaddr)); + ::memcpy_s(&addr.m_sockaddr, sizeof(addr.m_sockaddr), &m_sockaddr, sizeof(m_sockaddr)); + ::memcpy_s(&m_sockaddr, sizeof(m_sockaddr), &tempAddr, sizeof(tempAddr)); + } + + inline void socket_address::reset(ADDRESS_FAMILY family) WI_NOEXCEPT + { +#if (!defined(WI_NETWORK_TEST)) + WI_ASSERT(family == AF_UNSPEC || family == AF_INET || family == AF_INET6); +#endif + ::memset(&m_sockaddr, 0, length()); + m_sockaddr.si_family = family; + } + + template + void socket_address::set_sockaddr(_In_reads_bytes_(addr_size) const SOCKADDR* addr, T addr_size) WI_NOEXCEPT + { + WI_ASSERT(static_cast(addr_size) <= static_cast(length())); + + ::memset(&m_sockaddr, 0, length()); + if (addr) + { + ::memcpy_s(&m_sockaddr, sizeof(m_sockaddr), addr, addr_size); + } + } + + inline void socket_address::set_sockaddr(const SOCKADDR_IN* addr) WI_NOEXCEPT + { + ::memset(&m_sockaddr, 0, length()); + ::memcpy_s(&m_sockaddr.Ipv4, sizeof(m_sockaddr.Ipv4), addr, sizeof(*addr)); + } + + inline void socket_address::set_sockaddr(const SOCKADDR_IN6* addr) WI_NOEXCEPT + { + ::memset(&m_sockaddr, 0, length()); + ::memcpy_s(&m_sockaddr.Ipv6, sizeof(m_sockaddr.Ipv6), addr, sizeof(*addr)); + } + + inline void socket_address::set_sockaddr(const SOCKADDR_INET* addr) WI_NOEXCEPT + { + ::memset(&m_sockaddr, 0, length()); + ::memcpy_s(&m_sockaddr, sizeof(m_sockaddr), addr, sizeof(*addr)); + } + + inline void socket_address::set_sockaddr(const SOCKET_ADDRESS* addr) WI_NOEXCEPT + { + FAIL_FAST_IF_MSG( + addr->lpSockaddr && addr->iSockaddrLength > length(), + "SOCKET_ADDRESS contains an unsupported sockaddr type - larger than an IPv4 or IPv6 address (%d)", + addr->iSockaddrLength); + + ::memset(&m_sockaddr, 0, length()); + if (addr->lpSockaddr) + { + ::memcpy_s(&m_sockaddr, sizeof(m_sockaddr), addr->lpSockaddr, addr->iSockaddrLength); + } + } + + inline bool socket_address::is_address_linklocal() const WI_NOEXCEPT + { + switch (family()) + { + case AF_UNSPEC: + return false; + + case AF_INET: + return ::IN4_IS_ADDR_LINKLOCAL(in_addr()); + + case AF_INET6: + return ::IN6_IS_ADDR_LINKLOCAL(in6_addr()); + + default: + WI_ASSERT_MSG(false, "Unknown address family"); + return false; + } + } + + inline bool socket_address::is_address_loopback() const WI_NOEXCEPT + { + switch (family()) + { + case AF_UNSPEC: + return false; + + case AF_INET: + return ::IN4_IS_ADDR_LOOPBACK(in_addr()); + + case AF_INET6: + return ::IN6_IS_ADDR_LOOPBACK(in6_addr()); + + default: + WI_ASSERT_MSG(false, "Unknown address family"); + return false; + } + } + + inline NL_ADDRESS_TYPE socket_address::address_type() const WI_NOEXCEPT + { + switch (family()) + { + case AF_UNSPEC: + return ::NlatUnspecified; + + case AF_INET: + return ::Ipv4AddressType(reinterpret_cast(in_addr())); + + case AF_INET6: + return ::Ipv6AddressType(reinterpret_cast(in6_addr())); + + default: + WI_ASSERT_MSG(false, "Unknown address family"); + return ::NlatInvalid; + } + } + + inline void socket_address::set_port(USHORT port) WI_NOEXCEPT + { + WI_ASSERT(family() == AF_INET || family() == AF_INET6); + + // the port value is at the exact same offset with both the IPv4 and IPv6 unions + static_assert(FIELD_OFFSET(SOCKADDR_INET, Ipv4.sin_port) == FIELD_OFFSET(SOCKADDR_INET, Ipv6.sin6_port)); + + // port values in a sockaddr are always in network-byte order + m_sockaddr.Ipv4.sin_port = ::htons(port); + } + + inline void socket_address::set_scope_id(ULONG scopeId) WI_NOEXCEPT + { + WI_ASSERT(family() == AF_INET6); + + if (family() == AF_INET6) + { + m_sockaddr.Ipv6.sin6_scope_id = ::htonl(scopeId); + } + } + + inline void socket_address::set_flow_info(ULONG flowInfo) WI_NOEXCEPT + { + WI_ASSERT(family() == AF_INET6); + + if (family() == AF_INET6) + { + m_sockaddr.Ipv6.sin6_flowinfo = ::htonl(flowInfo); + } + } + + inline void socket_address::set_address_any() WI_NOEXCEPT + { + set_address_any(family()); + } + + inline void socket_address::set_address_any(ADDRESS_FAMILY family) WI_NOEXCEPT + { + WI_ASSERT(family == AF_INET || family == AF_INET6); + + // the port value is at the exact same offset with both the IPv4 and IPv6 unions + static_assert(FIELD_OFFSET(SOCKADDR_INET, Ipv4.sin_port) == FIELD_OFFSET(SOCKADDR_INET, Ipv6.sin6_port)); + + const auto original_port{m_sockaddr.Ipv4.sin_port}; + reset(family); + m_sockaddr.Ipv4.sin_port = original_port; + } + + inline void socket_address::set_address_loopback() WI_NOEXCEPT + { + set_address_loopback(family()); + } + + inline void socket_address::set_address_loopback(ADDRESS_FAMILY family) WI_NOEXCEPT + { + // the port value is at the exact same offset with both the IPv4 and IPv6 unions + static_assert(FIELD_OFFSET(SOCKADDR_INET, Ipv4.sin_port) == FIELD_OFFSET(SOCKADDR_INET, Ipv6.sin6_port)); + + const auto original_port{m_sockaddr.Ipv4.sin_port}; + reset(family); + switch (family) + { + case AF_INET: + m_sockaddr.Ipv4.sin_addr.s_addr = ::htonl(INADDR_LOOPBACK); + break; + case AF_INET6: + m_sockaddr.Ipv6.sin6_addr = {{IN6ADDR_LOOPBACK_INIT}}; + break; + default: + WI_ASSERT_MSG(false, "Unknown address family"); + } + m_sockaddr.Ipv4.sin_port = original_port; + } + + inline void socket_address::set_address(const IN_ADDR* addr) WI_NOEXCEPT + { + WI_ASSERT(family() == AF_INET); + + const auto original_port{m_sockaddr.Ipv4.sin_port}; + reset(AF_INET); + m_sockaddr.Ipv4.sin_addr.s_addr = addr->s_addr; + m_sockaddr.Ipv4.sin_port = original_port; + } + + inline void socket_address::set_address(const IN6_ADDR* addr) WI_NOEXCEPT + { + WI_ASSERT(family() == AF_INET6); + + const auto original_port{m_sockaddr.Ipv6.sin6_port}; + reset(AF_INET6); + m_sockaddr.Ipv6.sin6_addr = *addr; + m_sockaddr.Ipv6.sin6_port = original_port; + } + +#if defined(WIL_ENABLE_EXCEPTIONS) + inline void socket_address::set_sockaddr(SOCKET s) + { + THROW_IF_FAILED(set_sockaddr_nothrow(s)); + } + + inline void socket_address::set_sockaddr(PCWSTR address) + { + THROW_IF_FAILED(set_sockaddr_nothrow(address)); + } + + inline void socket_address::set_sockaddr(PCSTR address) + { + THROW_IF_FAILED(set_sockaddr_nothrow(address)); + } +#endif + + inline HRESULT socket_address::set_sockaddr_nothrow(SOCKET s) WI_NOEXCEPT + { + reset(AF_UNSPEC); + + auto nameLength{length()}; + auto error{::getsockname(s, sockaddr(), &nameLength)}; + if (error != 0) + { + error = ::WSAGetLastError(); + RETURN_WIN32(error); + } + return S_OK; + } + + inline HRESULT socket_address::set_sockaddr_nothrow(PCWSTR address) WI_NOEXCEPT + { + PCWSTR terminator_unused; + + reset(AF_INET); + constexpr BOOLEAN strict_string{TRUE}; + if (RtlIpv4StringToAddressW(address, strict_string, &terminator_unused, in_addr()) == 0) + { + m_sockaddr.si_family = AF_INET; + return S_OK; + } + + reset(AF_INET6); + if (RtlIpv6StringToAddressW(address, &terminator_unused, in6_addr()) == 0) + { + m_sockaddr.si_family = AF_INET6; + return S_OK; + } + + reset(AF_UNSPEC); + return E_INVALIDARG; + } + + inline HRESULT socket_address::set_sockaddr_nothrow(PCSTR address) WI_NOEXCEPT + { + PCSTR terminator_unused; + + reset(AF_INET); + constexpr BOOLEAN strict_string{TRUE}; + if (RtlIpv4StringToAddressA(address, strict_string, &terminator_unused, in_addr()) == 0) + { + m_sockaddr.si_family = AF_INET; + return S_OK; + } + + reset(AF_INET6); + if (RtlIpv6StringToAddressA(address, &terminator_unused, in6_addr()) == 0) + { + m_sockaddr.si_family = AF_INET6; + return S_OK; + } + + reset(AF_UNSPEC); + return E_INVALIDARG; + } + +#if defined(WIL_ENABLE_EXCEPTIONS) && (defined(_STRING_) || defined(WIL_DOXYGEN)) + inline ::std::wstring socket_address::write_address() const + { + ::wil::network::socket_address_wstring returnString{}; + THROW_IF_FAILED(write_address_nothrow(returnString)); + returnString[INET6_ADDRSTRLEN - 1] = L'\0'; + return returnString; + } +#endif + + inline HRESULT socket_address::write_address_nothrow(socket_address_wstring& address) const WI_NOEXCEPT + { + ::memset(address, 0, sizeof(socket_address_wstring)); + + // socket_address will return an empty string for AF_UNSPEC (the default family type when constructed) + // as to not fail if just default constructed + if (family() == AF_UNSPEC) + { + return S_OK; + } + + const void* const pAddr{ + family() == AF_INET + ? static_cast(&m_sockaddr.Ipv4.sin_addr) + : static_cast(&m_sockaddr.Ipv6.sin6_addr)}; + + // the last param to InetNtopW is # of characters, not bytes + const auto* error_value{::InetNtopW(family(), pAddr, address, INET6_ADDRSTRLEN)}; + if (error_value == nullptr) + { + RETURN_WIN32(::WSAGetLastError()); + } + return S_OK; + } + + inline HRESULT socket_address::write_address_nothrow(socket_address_string& address) const WI_NOEXCEPT + { + ::memset(address, 0, sizeof(socket_address_string)); + + // socket_address will return an empty string for AF_UNSPEC (the default family type when constructed) + // as to not fail if just default constructed + if (family() == AF_UNSPEC) + { + return S_OK; + } + + const void* const pAddr{ + family() == AF_INET + ? static_cast(&m_sockaddr.Ipv4.sin_addr) + : static_cast(&m_sockaddr.Ipv6.sin6_addr)}; + + // the last param to InetNtopA is # of characters, not bytes + const auto* error_value{::InetNtopA(family(), pAddr, address, INET6_ADDRSTRLEN)}; + if (error_value == nullptr) + { + RETURN_WIN32(::WSAGetLastError()); + } + return S_OK; + } + +#if defined(WIL_ENABLE_EXCEPTIONS) && (defined(_STRING_) || defined(WIL_DOXYGEN)) + inline ::std::wstring socket_address::write_complete_address() const + { + ::wil::network::socket_address_wstring returnString{}; + THROW_IF_FAILED(write_complete_address_nothrow(returnString)); + returnString[INET6_ADDRSTRLEN - 1] = L'\0'; + return returnString; + } +#endif + + inline HRESULT socket_address::write_complete_address_nothrow(socket_address_wstring& address) const WI_NOEXCEPT + { + ::memset(address, 0, sizeof(socket_address_wstring)); + + // socket_address will return an empty string for AF_UNSPEC (the default family type when constructed) + // as to not fail if just default constructed + if (family() == AF_UNSPEC) + { + return S_OK; + } + + // addressLength == # of chars, not bytes + DWORD addressLength{INET6_ADDRSTRLEN}; + if (::WSAAddressToStringW(const_cast(sockaddr()), length(), nullptr, address, &addressLength) != 0) + { + RETURN_WIN32(::WSAGetLastError()); + } + return S_OK; + } + + // the Winsock headers require having set this #define to access ANSI-string versions of the Winsock API +#if defined(_WINSOCK_DEPRECATED_NO_WARNINGS) + inline HRESULT socket_address::write_complete_address_nothrow(socket_address_string& address) const WI_NOEXCEPT + { + ::memset(address, 0, sizeof(socket_address_string)); + + // socket_address will return an empty string for AF_UNSPEC (the default family type when constructed) + // as to not fail if just default constructed + if (family() == AF_UNSPEC) + { + return S_OK; + } + + DWORD addressLength{INET6_ADDRSTRLEN}; + if (::WSAAddressToStringA(const_cast(sockaddr()), length(), nullptr, address, &addressLength) != 0) + { + RETURN_WIN32(::WSAGetLastError()); + } + return S_OK; + } +#endif + + inline ADDRESS_FAMILY socket_address::family() const WI_NOEXCEPT + { + return m_sockaddr.si_family; + } + + inline USHORT socket_address::port() const WI_NOEXCEPT + { + switch (family()) + { + case AF_UNSPEC: + return 0; + case AF_INET: + return ::ntohs(m_sockaddr.Ipv4.sin_port); + case AF_INET6: + return ::ntohs(m_sockaddr.Ipv6.sin6_port); + default: + WI_ASSERT_MSG(false, "Unknown address family"); + return 0; + } + } + + inline ULONG socket_address::flow_info() const WI_NOEXCEPT + { + switch (family()) + { + case AF_UNSPEC: + // fallthrough + case AF_INET: + return 0; + case AF_INET6: + return ::ntohl(m_sockaddr.Ipv6.sin6_flowinfo); + default: + WI_ASSERT_MSG(false, "Unknown address family"); + return 0; + } + } + + inline ULONG socket_address::scope_id() const WI_NOEXCEPT + { + switch (family()) + { + case AF_UNSPEC: + // fallthrough + case AF_INET: + return 0; + case AF_INET6: + return ::ntohl(m_sockaddr.Ipv6.sin6_scope_id); + default: + WI_ASSERT_MSG(false, "Unknown address family"); + return 0; + } + } + + inline SOCKADDR* socket_address::sockaddr() WI_NOEXCEPT + { + return reinterpret_cast(&m_sockaddr); + } + + inline SOCKADDR_IN* socket_address::sockaddr_in() WI_NOEXCEPT + { + WI_ASSERT(family() == AF_INET); + return &m_sockaddr.Ipv4; + } + + inline SOCKADDR_IN6* socket_address::sockaddr_in6() WI_NOEXCEPT + { + WI_ASSERT(family() == AF_INET6); + return &m_sockaddr.Ipv6; + } + + inline SOCKADDR_INET* socket_address::sockaddr_inet() WI_NOEXCEPT + { + return &m_sockaddr; + } + + inline IN_ADDR* socket_address::in_addr() WI_NOEXCEPT + { + WI_ASSERT(family() == AF_INET); + return &m_sockaddr.Ipv4.sin_addr; + } + + inline IN6_ADDR* socket_address::in6_addr() WI_NOEXCEPT + { + WI_ASSERT(family() == AF_INET6); + return &m_sockaddr.Ipv6.sin6_addr; + } + + inline const SOCKADDR* socket_address::sockaddr() const WI_NOEXCEPT + { + return reinterpret_cast(&m_sockaddr); + } + + inline const SOCKADDR_IN* socket_address::sockaddr_in() const WI_NOEXCEPT + { + WI_ASSERT(family() == AF_INET); + return &m_sockaddr.Ipv4; + } + + inline const SOCKADDR_IN6* socket_address::sockaddr_in6() const WI_NOEXCEPT + { + WI_ASSERT(family() == AF_INET6); + return &m_sockaddr.Ipv6; + } + + inline const SOCKADDR_INET* socket_address::sockaddr_inet() const WI_NOEXCEPT + { + return &m_sockaddr; + } + + inline const IN_ADDR* socket_address::in_addr() const WI_NOEXCEPT + { + WI_ASSERT(family() == AF_INET); + return &m_sockaddr.Ipv4.sin_addr; + } + + inline const IN6_ADDR* socket_address::in6_addr() const WI_NOEXCEPT + { + WI_ASSERT(family() == AF_INET6); + return &m_sockaddr.Ipv6.sin6_addr; + } + + inline SOCKADDR_STORAGE socket_address::sockaddr_storage() const noexcept + { + SOCKADDR_STORAGE return_sockaddr{}; + memcpy_s(&return_sockaddr, sizeof(return_sockaddr), &m_sockaddr, sizeof(m_sockaddr)); + return return_sockaddr; + } +} // namespace network +} // namespace wil + +#endif // __WIL_NETWORK_INCLUDED \ No newline at end of file diff --git a/include/wil/resource.h b/include/wil/resource.h index 32fcc5864..fb0d692dc 100644 --- a/include/wil/resource.h +++ b/include/wil/resource.h @@ -5081,6 +5081,27 @@ typedef shared_any shared_socket; typedef weak_any weak_socket; #endif // __WIL_WINSOCKAPI_STL +#if (defined(_WS2TCPIP_H_) && !defined(__WIL_WS2TCPIP_H_)) || defined(WIL_DOXYGEN) +/// @cond +#define __WIL_WS2TCPIP_H_ +/// @endcond +typedef unique_any unique_addrinfo_ansi; +typedef unique_any unique_addrinfo; +// not defining a type for FreeAddrInfoEx(PADDRINFOEXA) as that API is formally __declspec(deprecated) +typedef unique_any unique_addrinfoex; +#endif // __WIL_WS2TCPIP_H_ +#if (defined(__WIL_WS2TCPIP_H_) && !defined(__WIL_WS2TCPIP_H_STL) && defined(WIL_RESOURCE_STL)) || defined(WIL_DOXYGEN) +/// @cond +#define __WIL_WS2TCPIP_H_STL +/// @endcond +typedef shared_any shared_addrinfo_ansi; +typedef weak_any weak_addrinfo_ansi; +typedef shared_any shared_addrinfo; +typedef weak_any weak_addrinfo; +typedef shared_any shared_addrinfoex; +typedef weak_any weak_addrinfoex; +#endif // __WIL_WS2TCPIP_H_STL + #if (defined(_WINGDI_) && !defined(__WIL_WINGDI_) && WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_DESKTOP) && !defined(NOGDI) && !defined(WIL_KERNEL_MODE)) || \ defined(WIL_DOXYGEN) /// @cond diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index ee8a87f8b..e4e737cb9 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -88,7 +88,7 @@ find_package(Catch2 CONFIG REQUIRED) include_directories(${DETOURS_INCLUDE_DIRS}) add_definitions(-DNOMINMAX) -link_libraries(${DETOURS_LIBRARY} Catch2::Catch2WithMain) +link_libraries(${DETOURS_LIBRARY} Catch2::Catch2WithMain ws2_32.lib ntdll.lib) add_subdirectory(app) add_subdirectory(cpplatest) diff --git a/tests/NetworkingTests.cpp b/tests/NetworkingTests.cpp new file mode 100644 index 000000000..9c37df0f1 --- /dev/null +++ b/tests/NetworkingTests.cpp @@ -0,0 +1,2394 @@ +#include "pch.h" + +#include +#include +#include +#include +#include + +// set this to give access to all functions +#define _WINSOCK_DEPRECATED_NO_WARNINGS +#define WI_NETWORK_TEST +#include + +#include "common.h" + +constexpr char Test_in_addr_char_string[] = "1.1.1.1"; +constexpr wchar_t Test_in_addr_string[] = L"1.1.1.1"; +static in_addr Test_in_addr{}; +constexpr char Test_in_addr_char_string2[] = "1.1.1.2"; +constexpr wchar_t Test_in_addr_string2[] = L"1.1.1.2"; +static in_addr Test_in_addr2{}; +constexpr char Test_in6_addr_char_string[] = "2001::1:1:1:1"; +constexpr wchar_t Test_in6_addr_string[] = L"2001::1:1:1:1"; +static in6_addr Test_in6_addr{}; +constexpr char Test_in6_addr_char_string2[] = "2001::1:1:1:2"; +constexpr wchar_t Test_in6_addr_string2[] = L"2001::1:1:1:2"; +static in6_addr Test_in6_addr2{}; + +constexpr wchar_t Test_linklocal_in_addr_string[] = L"169.254.111.222"; +static in_addr Test_linklocal_in_addr{}; +constexpr wchar_t Test_linklocal_in6_addr_string[] = L"fe80::1:1:1111:2222"; +static in6_addr Test_linklocal_in6_addr{}; + +constexpr char Test_any_in_addr_char_string[] = "0.0.0.0"; +constexpr wchar_t Test_any_in_addr_string[] = L"0.0.0.0"; +constexpr char Test_any_in_addr_char_string_with_port[] = "0.0.0.0:12345"; +constexpr wchar_t Test_any_in_addr_string_with_port[] = L"0.0.0.0:12345"; +static in_addr Test_any_in_addr{}; +constexpr char Test_any_in6_addr_char_string[] = "::"; +constexpr wchar_t Test_any_in6_addr_string[] = L"::"; +constexpr char Test_any_in6_addr_char_string_with_port[] = "[::]:12345"; +constexpr wchar_t Test_any_in6_addr_string_with_port[] = L"[::]:12345"; +static in6_addr Test_any_in6_addr{}; + +constexpr wchar_t Test_loopback_in_addr_string[] = L"127.0.0.1"; +constexpr wchar_t Test_loopback_in_addr_string_with_port[] = L"127.0.0.1:12345"; +static in_addr Test_loopback_in_addr{}; +constexpr wchar_t Test_loopback_in6_addr_string[] = L"::1"; +constexpr wchar_t Test_loopback_in6_addr_string_with_port[] = L"[::1]:12345"; +static in6_addr Test_loopback_in6_addr{}; + +constexpr uint16_t TestPort = 12345; + +static INIT_ONCE SocketTestInit = INIT_ONCE_STATIC_INIT; + +static void InitTestAddresses() +{ + InitOnceExecuteOnce( + &SocketTestInit, + [](PINIT_ONCE, PVOID, PVOID*) -> BOOL { + LPCWSTR terminator{}; + auto error = RtlIpv4StringToAddressW(Test_in_addr_string, TRUE, &terminator, &Test_in_addr); + if (error != ERROR_SUCCESS) + { + return FALSE; + } + error = RtlIpv4StringToAddressW(Test_in_addr_string2, TRUE, &terminator, &Test_in_addr2); + if (error != ERROR_SUCCESS) + { + return FALSE; + } + + error = RtlIpv6StringToAddressW(Test_in6_addr_string, &terminator, &Test_in6_addr); + if (error != ERROR_SUCCESS) + { + return FALSE; + } + error = RtlIpv6StringToAddressW(Test_in6_addr_string2, &terminator, &Test_in6_addr2); + if (error != ERROR_SUCCESS) + { + return FALSE; + } + + error = RtlIpv4StringToAddressW(Test_linklocal_in_addr_string, TRUE, &terminator, &Test_linklocal_in_addr); + if (error != ERROR_SUCCESS) + { + return FALSE; + } + + error = RtlIpv6StringToAddressW(Test_linklocal_in6_addr_string, &terminator, &Test_linklocal_in6_addr); + if (error != ERROR_SUCCESS) + { + return FALSE; + } + + error = RtlIpv4StringToAddressW(Test_any_in_addr_string, TRUE, &terminator, &Test_any_in_addr); + if (error != ERROR_SUCCESS) + { + return FALSE; + } + + error = RtlIpv6StringToAddressW(Test_any_in6_addr_string, &terminator, &Test_any_in6_addr); + if (error != ERROR_SUCCESS) + { + return FALSE; + } + + error = RtlIpv4StringToAddressW(Test_loopback_in_addr_string, TRUE, &terminator, &Test_loopback_in_addr); + if (error != ERROR_SUCCESS) + { + return FALSE; + } + + error = RtlIpv6StringToAddressW(Test_loopback_in6_addr_string, &terminator, &Test_loopback_in6_addr); + if (error != ERROR_SUCCESS) + { + return FALSE; + } + + return TRUE; + }, + nullptr, + nullptr); +} + +TEST_CASE("NetworkingTests::Verifying_wsastartup_cleanup", "[networking]") +{ + // verify socket APIs fail without having called WSAStartup + // i.e., WSAStartup was not called elsewhere in the test app + // since that would break the preconditions of this test case + const auto verify_socket_test = ::socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); + const auto verify_gle = ::WSAGetLastError(); + REQUIRE(verify_socket_test == INVALID_SOCKET); + REQUIRE(verify_gle == WSANOTINITIALISED); + + SECTION("Verifying _nothrow") + { + const auto cleanup = wil::network::WSAStartup_nothrow(); + const bool succeeded = !!cleanup; + REQUIRE(succeeded); + const auto socket_test = ::socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); + REQUIRE(socket_test != INVALID_SOCKET); + ::closesocket(socket_test); + } + + SECTION("Verifying _failfast") + { + const auto cleanup = wil::network::WSAStartup_failfast(); + const auto socket_test = ::socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); + REQUIRE(socket_test != INVALID_SOCKET); + ::closesocket(socket_test); + } + +#ifdef WIL_ENABLE_EXCEPTIONS + SECTION("Verifying throwing") + { + const auto cleanup = wil::network::WSAStartup(); + const auto socket_test = ::socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); + REQUIRE(socket_test != INVALID_SOCKET); + ::closesocket(socket_test); + } +#endif +} + +TEST_CASE("NetworkingTests::Verifying_constructors", "[networking]") +{ + using wil::network::socket_address; + InitTestAddresses(); + + SECTION("socket_address(ADDRESS_FAMILY)") + { + socket_address default_addr; + REQUIRE(default_addr.family() == AF_UNSPEC); + REQUIRE(default_addr.address_type() == NlatUnspecified); + REQUIRE(!default_addr.is_address_linklocal()); + REQUIRE(!default_addr.is_address_loopback()); + + socket_address v4_addr{AF_INET}; + REQUIRE(v4_addr.family() == AF_INET); + REQUIRE(v4_addr.address_type() == NlatUnspecified); + REQUIRE(!v4_addr.is_address_linklocal()); + REQUIRE(!v4_addr.is_address_loopback()); + REQUIRE(v4_addr.port() == 0); +#ifdef WIL_ENABLE_EXCEPTIONS + REQUIRE(Test_any_in_addr_string == v4_addr.write_address()); +#endif + + SOCKADDR_STORAGE v4_addr_storage = v4_addr.sockaddr_storage(); + REQUIRE(v4_addr_storage.ss_family == AF_INET); + REQUIRE(0 == memcmp(&v4_addr_storage, v4_addr.sockaddr_in(), sizeof(SOCKADDR_IN))); + + socket_address v6_addr{AF_INET6}; + REQUIRE(v6_addr.family() == AF_INET6); + REQUIRE(v6_addr.address_type() == NlatUnspecified); + REQUIRE(!v6_addr.is_address_linklocal()); + REQUIRE(!v6_addr.is_address_loopback()); + REQUIRE(v6_addr.port() == 0); +#ifdef WIL_ENABLE_EXCEPTIONS + REQUIRE(Test_any_in6_addr_string == v6_addr.write_address()); +#endif + + SOCKADDR_STORAGE v6_addr_storage = v6_addr.sockaddr_storage(); + REQUIRE(v6_addr_storage.ss_family == AF_INET6); + REQUIRE(0 == memcmp(&v6_addr_storage, v6_addr.sockaddr_in6(), sizeof(SOCKADDR_IN6))); + } + + SECTION("socket_address(const SOCKADDR*, T)") + { + SOCKADDR_IN v4_test_sockaddr{}; + v4_test_sockaddr.sin_family = AF_INET; + v4_test_sockaddr.sin_port = htons(TestPort); // the raw value is in network byte order + memcpy(&v4_test_sockaddr.sin_addr, &Test_linklocal_in_addr, sizeof(in_addr)); + + socket_address v4_addr{reinterpret_cast(&v4_test_sockaddr), sizeof(v4_test_sockaddr)}; + REQUIRE(v4_addr.family() == AF_INET); + REQUIRE(v4_addr.address_type() == NlatUnicast); + REQUIRE(v4_addr.is_address_linklocal()); + REQUIRE(!v4_addr.is_address_loopback()); + REQUIRE(v4_addr.port() == TestPort); +#ifdef WIL_ENABLE_EXCEPTIONS + REQUIRE(Test_linklocal_in_addr_string == v4_addr.write_address()); +#endif + + SOCKADDR_STORAGE v4_addr_storage = v4_addr.sockaddr_storage(); + REQUIRE(v4_addr_storage.ss_family == AF_INET); + REQUIRE(0 == memcmp(&v4_addr_storage, v4_addr.sockaddr_in(), sizeof(SOCKADDR_IN))); + + SOCKADDR_IN6 v6_test_sockaddr{}; + v6_test_sockaddr.sin6_family = AF_INET6; + // these raw value are in network byte order + v6_test_sockaddr.sin6_port = htons(TestPort); + v6_test_sockaddr.sin6_flowinfo = htonl(ULONG_MAX - 1); + v6_test_sockaddr.sin6_scope_id = htonl(ULONG_MAX - 1); + memcpy(&v6_test_sockaddr.sin6_addr, &Test_linklocal_in6_addr, sizeof(in6_addr)); + + socket_address v6_addr{reinterpret_cast(&v6_test_sockaddr), sizeof(v6_test_sockaddr)}; + REQUIRE(v6_addr.family() == AF_INET6); + REQUIRE(v6_addr.address_type() == NlatUnicast); + REQUIRE(v6_addr.is_address_linklocal()); + REQUIRE(!v6_addr.is_address_loopback()); + REQUIRE(v6_addr.port() == TestPort); + REQUIRE(v6_addr.flow_info() == ULONG_MAX - 1); + REQUIRE(v6_addr.scope_id() == ULONG_MAX - 1); +#ifdef WIL_ENABLE_EXCEPTIONS + REQUIRE(Test_linklocal_in6_addr_string == v6_addr.write_address()); +#endif + + SOCKADDR_STORAGE v6_addr_storage = v6_addr.sockaddr_storage(); + REQUIRE(v6_addr_storage.ss_family == AF_INET6); + REQUIRE(0 == memcmp(&v6_addr_storage, v6_addr.sockaddr_in6(), sizeof(SOCKADDR_IN6))); + } + + SECTION("socket_address(const SOCKADDR_IN*)") + { + SOCKADDR_IN v4_test_sockaddr{}; + v4_test_sockaddr.sin_family = AF_INET; + v4_test_sockaddr.sin_port = htons(TestPort); // the raw value is in network byte order + memcpy(&v4_test_sockaddr.sin_addr, &Test_linklocal_in_addr, sizeof(in_addr)); + + socket_address v4_addr{&v4_test_sockaddr}; + REQUIRE(v4_addr.family() == AF_INET); + REQUIRE(v4_addr.address_type() == NlatUnicast); + REQUIRE(v4_addr.is_address_linklocal()); + REQUIRE(!v4_addr.is_address_loopback()); + REQUIRE(v4_addr.port() == TestPort); +#ifdef WIL_ENABLE_EXCEPTIONS + REQUIRE(Test_linklocal_in_addr_string == v4_addr.write_address()); +#endif + + SOCKADDR_STORAGE v4_addr_storage = v4_addr.sockaddr_storage(); + REQUIRE(v4_addr_storage.ss_family == AF_INET); + REQUIRE(0 == memcmp(&v4_addr_storage, v4_addr.sockaddr_in(), sizeof(SOCKADDR_IN))); + } + + SECTION("socket_address(const SOCKADDR_IN6*)") + { + SOCKADDR_IN6 v6_test_sockaddr{}; + v6_test_sockaddr.sin6_family = AF_INET6; + // these raw value are in network byte order + v6_test_sockaddr.sin6_port = htons(TestPort); + v6_test_sockaddr.sin6_flowinfo = htonl(ULONG_MAX - 1); + v6_test_sockaddr.sin6_scope_id = htonl(ULONG_MAX - 1); + memcpy(&v6_test_sockaddr.sin6_addr, &Test_linklocal_in6_addr, sizeof(in6_addr)); + + socket_address v6_addr{&v6_test_sockaddr}; + REQUIRE(v6_addr.family() == AF_INET6); + REQUIRE(v6_addr.address_type() == NlatUnicast); + REQUIRE(v6_addr.is_address_linklocal()); + REQUIRE(!v6_addr.is_address_loopback()); + REQUIRE(v6_addr.port() == TestPort); + REQUIRE(v6_addr.flow_info() == ULONG_MAX - 1); + REQUIRE(v6_addr.scope_id() == ULONG_MAX - 1); +#ifdef WIL_ENABLE_EXCEPTIONS + REQUIRE(Test_linklocal_in6_addr_string == v6_addr.write_address()); +#endif + + SOCKADDR_STORAGE v6_addr_storage = v6_addr.sockaddr_storage(); + REQUIRE(v6_addr_storage.ss_family == AF_INET6); + REQUIRE(0 == memcmp(&v6_addr_storage, v6_addr.sockaddr_in6(), sizeof(SOCKADDR_IN6))); + } + + SECTION("socket_address(const SOCKADDR_INET*)") + { + SOCKADDR_IN v4_test_sockaddr{}; + v4_test_sockaddr.sin_family = AF_INET; + v4_test_sockaddr.sin_port = htons(TestPort); // the raw value is in network byte order + memcpy(&v4_test_sockaddr.sin_addr, &Test_linklocal_in_addr, sizeof(in_addr)); + + SOCKADDR_INET v4_inet_addr{}; + memcpy(&v4_inet_addr.Ipv4, &v4_test_sockaddr, sizeof(SOCKADDR_IN)); + + socket_address v4_addr{&v4_inet_addr}; + REQUIRE(v4_addr.family() == AF_INET); + REQUIRE(v4_addr.address_type() == NlatUnicast); + REQUIRE(v4_addr.is_address_linklocal()); + REQUIRE(!v4_addr.is_address_loopback()); + REQUIRE(v4_addr.port() == TestPort); +#ifdef WIL_ENABLE_EXCEPTIONS + REQUIRE(Test_linklocal_in_addr_string == v4_addr.write_address()); +#endif + REQUIRE(0 == memcmp(&v4_inet_addr, v4_addr.sockaddr_inet(), sizeof(SOCKADDR_INET))); + + SOCKADDR_STORAGE v4_addr_storage = v4_addr.sockaddr_storage(); + REQUIRE(v4_addr_storage.ss_family == AF_INET); + REQUIRE(0 == memcmp(&v4_addr_storage, v4_addr.sockaddr_in(), sizeof(SOCKADDR_IN))); + + SOCKADDR_IN6 v6_test_sockaddr{}; + v6_test_sockaddr.sin6_family = AF_INET6; + // these raw value are in network byte order + v6_test_sockaddr.sin6_port = htons(TestPort); + v6_test_sockaddr.sin6_flowinfo = htonl(ULONG_MAX - 1); + v6_test_sockaddr.sin6_scope_id = htonl(ULONG_MAX - 1); + memcpy(&v6_test_sockaddr.sin6_addr, &Test_linklocal_in6_addr, sizeof(in6_addr)); + + SOCKADDR_INET v6_inet_addr{}; + memcpy(&v6_inet_addr.Ipv6, &v6_test_sockaddr, sizeof(SOCKADDR_IN6)); + + socket_address v6_addr{&v6_inet_addr}; + REQUIRE(v6_addr.family() == AF_INET6); + REQUIRE(v6_addr.address_type() == NlatUnicast); + REQUIRE(v6_addr.is_address_linklocal()); + REQUIRE(!v6_addr.is_address_loopback()); + REQUIRE(v6_addr.port() == TestPort); + REQUIRE(v6_addr.flow_info() == ULONG_MAX - 1); + REQUIRE(v6_addr.scope_id() == ULONG_MAX - 1); +#ifdef WIL_ENABLE_EXCEPTIONS + REQUIRE(Test_linklocal_in6_addr_string == v6_addr.write_address()); +#endif + REQUIRE(0 == memcmp(&v6_inet_addr, v6_addr.sockaddr_inet(), sizeof(SOCKADDR_INET))); + + SOCKADDR_STORAGE v6_addr_storage = v6_addr.sockaddr_storage(); + REQUIRE(v6_addr_storage.ss_family == AF_INET6); + REQUIRE(0 == memcmp(&v6_addr_storage, v6_addr.sockaddr_in6(), sizeof(SOCKADDR_IN6))); + } + + SECTION("socket_address(const SOCKET_ADDRESS*)") + { + SOCKET_ADDRESS default_socketaddress{}; + socket_address default_addr{&default_socketaddress}; + REQUIRE(default_addr.family() == AF_UNSPEC); + REQUIRE(default_addr.address_type() == NlatUnspecified); + REQUIRE(!default_addr.is_address_linklocal()); + REQUIRE(!default_addr.is_address_loopback()); + + SOCKADDR_IN v4_test_sockaddr{}; + v4_test_sockaddr.sin_family = AF_INET; + v4_test_sockaddr.sin_port = htons(TestPort); // the raw value is in network byte order + memcpy(&v4_test_sockaddr.sin_addr, &Test_linklocal_in_addr, sizeof(in_addr)); + + SOCKET_ADDRESS v4_socketaddress{}; + v4_socketaddress.lpSockaddr = reinterpret_cast(&v4_test_sockaddr); + v4_socketaddress.iSockaddrLength = sizeof(v4_test_sockaddr); + + socket_address v4_addr{&v4_socketaddress}; + REQUIRE(v4_addr.family() == AF_INET); + REQUIRE(v4_addr.address_type() == NlatUnicast); + REQUIRE(v4_addr.is_address_linklocal()); + REQUIRE(!v4_addr.is_address_loopback()); + REQUIRE(v4_addr.port() == TestPort); +#ifdef WIL_ENABLE_EXCEPTIONS + REQUIRE(Test_linklocal_in_addr_string == v4_addr.write_address()); +#endif + + SOCKADDR_IN6 v6_test_sockaddr{}; + v6_test_sockaddr.sin6_family = AF_INET6; + // these raw value are in network byte order + v6_test_sockaddr.sin6_port = htons(TestPort); + v6_test_sockaddr.sin6_flowinfo = htonl(ULONG_MAX - 1); + v6_test_sockaddr.sin6_scope_id = htonl(ULONG_MAX - 1); + memcpy(&v6_test_sockaddr.sin6_addr, &Test_linklocal_in6_addr, sizeof(in6_addr)); + + SOCKET_ADDRESS v6_socketaddress{}; + v6_socketaddress.lpSockaddr = reinterpret_cast(&v6_test_sockaddr); + v6_socketaddress.iSockaddrLength = sizeof(v6_test_sockaddr); + + socket_address v6_addr{&v6_socketaddress}; + REQUIRE(v6_addr.family() == AF_INET6); + REQUIRE(v6_addr.address_type() == NlatUnicast); + REQUIRE(v6_addr.is_address_linklocal()); + REQUIRE(!v6_addr.is_address_loopback()); + REQUIRE(v6_addr.port() == TestPort); + REQUIRE(v6_addr.flow_info() == ULONG_MAX - 1); + REQUIRE(v6_addr.scope_id() == ULONG_MAX - 1); +#ifdef WIL_ENABLE_EXCEPTIONS + REQUIRE(Test_linklocal_in6_addr_string == v6_addr.write_address()); +#endif + } +} + +TEST_CASE("NetworkingTests::Verifying_in_addr_interactions", "[networking]") +{ + InitTestAddresses(); + + wil::network::socket_address default_addr{}; + REQUIRE(default_addr.length() == sizeof(SOCKADDR_INET)); + + wil::network::socket_address test_v4_addr{&Test_in_addr}; + wil::network::socket_address test_v4_addr2{&Test_in_addr2}; + wil::network::socket_address test_v4_addr_with_port{&Test_in_addr, TestPort}; + + wil::network::socket_address test_v6_addr{&Test_in6_addr}; + wil::network::socket_address test_v6_addr2{&Test_in6_addr2}; + wil::network::socket_address test_v6_addr_with_port{&Test_in6_addr, TestPort}; + + wil::network::socket_address test_v4_linklocal_addr{&Test_linklocal_in_addr}; + wil::network::socket_address test_v4_linklocal_addr_with_port{&Test_linklocal_in_addr, TestPort}; + + wil::network::socket_address test_v6_linklocal_addr{&Test_linklocal_in6_addr}; + wil::network::socket_address test_v6_linklocal_addr_with_port{&Test_linklocal_in6_addr, TestPort}; + + wil::network::socket_address test_v4_any_addr{&Test_any_in_addr}; + wil::network::socket_address test_v4_any_addr_with_port{&Test_any_in_addr, TestPort}; + + wil::network::socket_address test_v6_any_addr{&Test_any_in6_addr}; + wil::network::socket_address test_v6_any_addr_with_port{&Test_any_in6_addr, TestPort}; + + using wil::network::equals; + + SECTION("IPv4 in_addr properties") + { + REQUIRE(test_v4_addr.family() == AF_INET); + REQUIRE(test_v4_addr.address_type() == NlatUnicast); + REQUIRE(!test_v4_addr.is_address_linklocal()); + REQUIRE(!test_v4_addr.is_address_loopback()); + REQUIRE(NlatUnicast == test_v4_addr.address_type()); + REQUIRE(NlatUnicast == test_v4_addr2.address_type()); + + REQUIRE(equals(*test_v4_addr.in_addr(), Test_in_addr)); + REQUIRE(equals(*test_v4_addr2.in_addr(), Test_in_addr2)); + REQUIRE(test_v4_addr.port() == 0); + REQUIRE(test_v4_addr.scope_id() == 0); + REQUIRE(test_v4_addr.flow_info() == 0); + + REQUIRE(test_v4_addr == test_v4_addr); + REQUIRE(test_v4_addr != test_v4_addr2); + REQUIRE(test_v4_addr < test_v4_addr2); + REQUIRE(test_v4_addr2 > test_v4_addr); + REQUIRE(test_v4_addr != default_addr); + REQUIRE(test_v4_addr > default_addr); + } + + SECTION("IPv4 in_addr with port properties") + { + REQUIRE(test_v4_addr_with_port.family() == AF_INET); + REQUIRE(test_v4_addr_with_port.address_type() == NlatUnicast); + REQUIRE(!test_v4_addr_with_port.is_address_linklocal()); + REQUIRE(!test_v4_addr_with_port.is_address_loopback()); + REQUIRE(NlatUnicast == test_v4_addr_with_port.address_type()); + + REQUIRE(equals(*test_v4_addr_with_port.in_addr(), Test_in_addr)); + REQUIRE(equals(*test_v4_addr_with_port.in_addr(), *test_v4_addr.in_addr())); + REQUIRE(test_v4_addr_with_port.port() == TestPort); + REQUIRE(test_v4_addr_with_port.scope_id() == 0); + REQUIRE(test_v4_addr_with_port.flow_info() == 0); + + REQUIRE(test_v4_addr_with_port == test_v4_addr_with_port); + REQUIRE(test_v4_addr_with_port != default_addr); + REQUIRE(test_v4_addr_with_port != test_v4_addr); + REQUIRE(test_v4_addr_with_port > test_v4_addr); + REQUIRE(test_v4_addr_with_port < test_v4_addr2); + REQUIRE(test_v4_addr_with_port > default_addr); + } + + SECTION("IPv6 in6_addr properties") + { + REQUIRE(test_v6_addr.family() == AF_INET6); + REQUIRE(test_v6_addr.address_type() == NlatUnicast); + REQUIRE(!test_v6_addr.is_address_linklocal()); + REQUIRE(!test_v6_addr.is_address_loopback()); + REQUIRE(NlatUnicast == test_v6_addr2.address_type()); + + REQUIRE(equals(*test_v6_addr.in6_addr(), Test_in6_addr)); + REQUIRE(equals(*test_v6_addr2.in6_addr(), Test_in6_addr2)); + REQUIRE(test_v6_addr.port() == 0); + REQUIRE(test_v6_addr.scope_id() == 0); + REQUIRE(test_v6_addr.flow_info() == 0); + + REQUIRE(test_v6_addr == test_v6_addr); + REQUIRE(test_v6_addr != test_v6_addr2); + REQUIRE(test_v6_addr < test_v6_addr2); + REQUIRE(test_v6_addr2 > test_v6_addr); + REQUIRE(test_v6_addr != test_v4_addr); + REQUIRE(test_v6_addr > test_v4_addr); + REQUIRE(test_v6_addr != default_addr); + REQUIRE(test_v6_addr > default_addr); + } + + SECTION("IPv6 in6_addr with port properties") + { + REQUIRE(test_v6_addr_with_port.family() == AF_INET6); + REQUIRE(test_v6_addr_with_port.address_type() == NlatUnicast); + REQUIRE(!test_v6_addr_with_port.is_address_linklocal()); + REQUIRE(!test_v6_addr_with_port.is_address_loopback()); + REQUIRE(NlatUnicast == test_v6_addr_with_port.address_type()); + + REQUIRE(equals(*test_v6_addr_with_port.in6_addr(), Test_in6_addr)); + REQUIRE(equals(*test_v6_addr_with_port.in6_addr(), *test_v6_addr.in6_addr())); + REQUIRE(test_v6_addr_with_port.port() == TestPort); + REQUIRE(test_v6_addr_with_port.scope_id() == 0); + REQUIRE(test_v6_addr_with_port.flow_info() == 0); + + REQUIRE(test_v6_addr_with_port == test_v6_addr_with_port); + REQUIRE(test_v6_addr_with_port != test_v4_addr); + REQUIRE(test_v6_addr_with_port != test_v4_addr_with_port); + REQUIRE(test_v6_addr_with_port != test_v6_addr); + REQUIRE(test_v6_addr_with_port != test_v6_addr2); + REQUIRE(test_v6_addr_with_port > test_v6_addr); + REQUIRE(test_v6_addr_with_port < test_v6_addr2); + REQUIRE(test_v6_addr_with_port != default_addr); + REQUIRE(test_v6_addr_with_port > default_addr); + } + + SECTION("IPv4 link-local in_addr properties") + { + REQUIRE(test_v4_linklocal_addr.family() == AF_INET); + REQUIRE(test_v4_linklocal_addr.address_type() == NlatUnicast); + REQUIRE(test_v4_linklocal_addr.is_address_linklocal()); + REQUIRE(!test_v4_linklocal_addr.is_address_loopback()); + + REQUIRE(equals(*test_v4_linklocal_addr.in_addr(), Test_linklocal_in_addr)); + REQUIRE(test_v4_linklocal_addr.port() == 0); + REQUIRE(test_v4_linklocal_addr.scope_id() == 0); + REQUIRE(test_v4_linklocal_addr.flow_info() == 0); + + REQUIRE(test_v4_linklocal_addr == test_v4_linklocal_addr); + REQUIRE(test_v4_linklocal_addr != default_addr); + REQUIRE(test_v4_linklocal_addr != test_v4_addr); + REQUIRE(test_v4_linklocal_addr != test_v4_addr_with_port); + REQUIRE(test_v4_linklocal_addr != test_v6_addr); + REQUIRE(test_v4_linklocal_addr != test_v6_addr_with_port); + } + + SECTION("IPv4 link-local in_addr with port properties") + { + REQUIRE(test_v4_linklocal_addr_with_port.family() == AF_INET); + REQUIRE(test_v4_linklocal_addr_with_port.address_type() == NlatUnicast); + REQUIRE(test_v4_linklocal_addr_with_port.is_address_linklocal()); + REQUIRE(!test_v4_linklocal_addr_with_port.is_address_loopback()); + + REQUIRE(equals(*test_v4_linklocal_addr_with_port.in_addr(), Test_linklocal_in_addr)); + REQUIRE(equals(*test_v4_linklocal_addr_with_port.in_addr(), *test_v4_linklocal_addr.in_addr())); + REQUIRE(test_v4_linklocal_addr_with_port.port() == TestPort); + REQUIRE(test_v4_linklocal_addr_with_port.scope_id() == 0); + REQUIRE(test_v4_linklocal_addr_with_port.flow_info() == 0); + + REQUIRE(test_v4_linklocal_addr_with_port == test_v4_linklocal_addr_with_port); + REQUIRE(test_v4_linklocal_addr_with_port != default_addr); + REQUIRE(test_v4_linklocal_addr_with_port != test_v4_addr); + REQUIRE(test_v4_linklocal_addr_with_port != test_v4_addr_with_port); + REQUIRE(test_v4_linklocal_addr_with_port != test_v6_addr); + REQUIRE(test_v4_linklocal_addr_with_port != test_v6_addr_with_port); + REQUIRE(test_v4_linklocal_addr_with_port != test_v4_linklocal_addr); + } + + SECTION("IPv6 link-local in6_addr properties") + { + REQUIRE(test_v6_linklocal_addr.family() == AF_INET6); + REQUIRE(test_v6_linklocal_addr.address_type() == NlatUnicast); + REQUIRE(test_v6_linklocal_addr.is_address_linklocal()); + REQUIRE(!test_v6_linklocal_addr.is_address_loopback()); + + REQUIRE(equals(*test_v6_linklocal_addr.in6_addr(), Test_linklocal_in6_addr)); + REQUIRE(test_v6_linklocal_addr.port() == 0); + REQUIRE(test_v6_linklocal_addr.scope_id() == 0); + REQUIRE(test_v6_linklocal_addr.flow_info() == 0); + + REQUIRE(test_v6_linklocal_addr == test_v6_linklocal_addr); + REQUIRE(test_v6_linklocal_addr != default_addr); + REQUIRE(test_v6_linklocal_addr != test_v4_addr); + REQUIRE(test_v6_linklocal_addr != test_v4_addr_with_port); + REQUIRE(test_v6_linklocal_addr != test_v6_addr); + REQUIRE(test_v6_linklocal_addr != test_v6_addr_with_port); + REQUIRE(test_v6_linklocal_addr != test_v4_linklocal_addr); + REQUIRE(test_v6_linklocal_addr != test_v4_linklocal_addr_with_port); + } + + SECTION("IPv6 link-local in6_addr with port properties") + { + REQUIRE(test_v6_linklocal_addr_with_port.family() == AF_INET6); + REQUIRE(test_v6_linklocal_addr_with_port.address_type() == NlatUnicast); + REQUIRE(test_v6_linklocal_addr_with_port.is_address_linklocal()); + REQUIRE(!test_v6_linklocal_addr_with_port.is_address_loopback()); + + REQUIRE(equals(*test_v6_linklocal_addr_with_port.in6_addr(), Test_linklocal_in6_addr)); + REQUIRE(equals(*test_v6_linklocal_addr_with_port.in6_addr(), *test_v6_linklocal_addr.in6_addr())); + REQUIRE(test_v6_linklocal_addr_with_port.port() == TestPort); + REQUIRE(test_v6_linklocal_addr_with_port.scope_id() == 0); + REQUIRE(test_v6_linklocal_addr_with_port.flow_info() == 0); + + REQUIRE(test_v6_linklocal_addr_with_port == test_v6_linklocal_addr_with_port); + REQUIRE(test_v6_linklocal_addr_with_port != default_addr); + REQUIRE(test_v6_linklocal_addr_with_port != test_v4_addr); + REQUIRE(test_v6_linklocal_addr_with_port != test_v4_addr_with_port); + REQUIRE(test_v6_linklocal_addr_with_port != test_v6_addr); + REQUIRE(test_v6_linklocal_addr_with_port != test_v6_addr_with_port); + REQUIRE(test_v6_linklocal_addr_with_port != test_v4_linklocal_addr); + REQUIRE(test_v6_linklocal_addr_with_port != test_v4_linklocal_addr_with_port); + REQUIRE(test_v6_linklocal_addr_with_port != test_v6_linklocal_addr); + } + + SECTION("IPv4 any-addr in_addr properties") + { + REQUIRE(test_v4_any_addr.family() == AF_INET); + REQUIRE(test_v4_any_addr.address_type() == NlatUnspecified); + REQUIRE(!test_v4_any_addr.is_address_linklocal()); + REQUIRE(!test_v4_any_addr.is_address_loopback()); + + REQUIRE(equals(*test_v4_any_addr.in_addr(), Test_any_in_addr)); + REQUIRE(test_v4_any_addr.port() == 0); + REQUIRE(test_v4_any_addr.scope_id() == 0); + REQUIRE(test_v4_any_addr.flow_info() == 0); + + REQUIRE(test_v4_any_addr == test_v4_any_addr); + REQUIRE(test_v4_any_addr != default_addr); + REQUIRE(test_v4_any_addr != test_v4_addr); + REQUIRE(test_v4_any_addr != test_v4_addr_with_port); + REQUIRE(test_v4_any_addr != test_v6_addr); + REQUIRE(test_v4_any_addr != test_v6_addr_with_port); + REQUIRE(test_v4_any_addr != test_v4_linklocal_addr); + REQUIRE(test_v4_any_addr != test_v4_linklocal_addr_with_port); + REQUIRE(test_v4_any_addr != test_v6_linklocal_addr); + REQUIRE(test_v4_any_addr != test_v6_linklocal_addr_with_port); + } + + SECTION("IPv4 any-addr in_addr with port properties") + { + REQUIRE(test_v4_any_addr_with_port.family() == AF_INET); + REQUIRE(test_v4_any_addr_with_port.address_type() == NlatUnspecified); + REQUIRE(!test_v4_any_addr_with_port.is_address_linklocal()); + REQUIRE(!test_v4_any_addr_with_port.is_address_loopback()); + + REQUIRE(equals(*test_v4_any_addr_with_port.in_addr(), Test_any_in_addr)); + REQUIRE(equals(*test_v4_any_addr_with_port.in_addr(), *test_v4_any_addr.in_addr())); + REQUIRE(test_v4_any_addr_with_port.port() == TestPort); + REQUIRE(test_v4_any_addr_with_port.scope_id() == 0); + REQUIRE(test_v4_any_addr_with_port.flow_info() == 0); + + REQUIRE(test_v4_any_addr_with_port == test_v4_any_addr_with_port); + REQUIRE(test_v4_any_addr_with_port != default_addr); + REQUIRE(test_v4_any_addr_with_port != test_v4_addr); + REQUIRE(test_v4_any_addr_with_port != test_v4_addr_with_port); + REQUIRE(test_v4_any_addr_with_port != test_v6_addr); + REQUIRE(test_v4_any_addr_with_port != test_v6_addr_with_port); + REQUIRE(test_v4_any_addr_with_port != test_v4_linklocal_addr); + REQUIRE(test_v4_any_addr_with_port != test_v4_linklocal_addr_with_port); + REQUIRE(test_v4_any_addr_with_port != test_v6_linklocal_addr); + REQUIRE(test_v4_any_addr_with_port != test_v6_linklocal_addr_with_port); + REQUIRE(test_v4_any_addr_with_port != test_v4_any_addr); + } + + SECTION("IPv6 any-addr in6_addr properties") + { + REQUIRE(test_v6_any_addr.family() == AF_INET6); + REQUIRE(test_v6_any_addr.address_type() == NlatUnspecified); + REQUIRE(!test_v6_any_addr.is_address_linklocal()); + REQUIRE(!test_v6_any_addr.is_address_loopback()); + + REQUIRE(equals(*test_v6_any_addr.in6_addr(), Test_any_in6_addr)); + REQUIRE(test_v6_any_addr.port() == 0); + REQUIRE(test_v6_any_addr.scope_id() == 0); + REQUIRE(test_v6_any_addr.flow_info() == 0); + + REQUIRE(test_v6_any_addr == test_v6_any_addr); + REQUIRE(test_v6_any_addr != default_addr); + REQUIRE(test_v6_any_addr != test_v4_addr); + REQUIRE(test_v6_any_addr != test_v4_addr_with_port); + REQUIRE(test_v6_any_addr != test_v6_addr); + REQUIRE(test_v6_any_addr != test_v6_addr_with_port); + REQUIRE(test_v6_any_addr != test_v4_linklocal_addr); + REQUIRE(test_v6_any_addr != test_v4_linklocal_addr_with_port); + REQUIRE(test_v6_any_addr != test_v6_linklocal_addr); + REQUIRE(test_v6_any_addr != test_v6_linklocal_addr_with_port); + REQUIRE(test_v6_any_addr != test_v4_any_addr); + REQUIRE(test_v6_any_addr != test_v4_any_addr_with_port); + } + + SECTION("IPv6 any-addr in6_addr with port properties") + { + REQUIRE(test_v6_any_addr_with_port.family() == AF_INET6); + REQUIRE(test_v6_any_addr_with_port.address_type() == NlatUnspecified); + REQUIRE(!test_v6_any_addr_with_port.is_address_linklocal()); + REQUIRE(!test_v6_any_addr_with_port.is_address_loopback()); + + REQUIRE(equals(*test_v6_any_addr_with_port.in6_addr(), Test_any_in6_addr)); + REQUIRE(equals(*test_v6_any_addr_with_port.in6_addr(), *test_v6_any_addr.in6_addr())); + REQUIRE(test_v6_any_addr_with_port.port() == TestPort); + REQUIRE(test_v6_any_addr_with_port.scope_id() == 0); + REQUIRE(test_v6_any_addr_with_port.flow_info() == 0); + + REQUIRE(test_v6_any_addr_with_port == test_v6_any_addr_with_port); + REQUIRE(test_v6_any_addr_with_port != default_addr); + REQUIRE(test_v6_any_addr_with_port != test_v4_addr); + REQUIRE(test_v6_any_addr_with_port != test_v4_addr_with_port); + REQUIRE(test_v6_any_addr_with_port != test_v6_addr); + REQUIRE(test_v6_any_addr_with_port != test_v6_addr_with_port); + REQUIRE(test_v6_any_addr_with_port != test_v4_linklocal_addr); + REQUIRE(test_v6_any_addr_with_port != test_v4_linklocal_addr_with_port); + REQUIRE(test_v6_any_addr_with_port != test_v6_linklocal_addr); + REQUIRE(test_v6_any_addr_with_port != test_v6_linklocal_addr_with_port); + REQUIRE(test_v6_any_addr_with_port != test_v4_any_addr); + REQUIRE(test_v6_any_addr_with_port != test_v4_any_addr_with_port); + REQUIRE(test_v6_any_addr_with_port != test_v6_any_addr); + } +} + +TEST_CASE("NetworkingTests::Verifying_operators", "[networking]") +{ + using wil::network::equals; + +#ifdef WIL_ENABLE_EXCEPTIONS + using wil::network::socket_address; + SECTION("verify v4 address comparisons") + { + // equal will be considered less-than + REQUIRE(socket_address{L"1.1.1.1"} < socket_address{L"1.1.1.1"}); + REQUIRE(!(socket_address{L"1.1.1.1"} > socket_address{L"1.1.1.1"})); + REQUIRE(socket_address{L"1.1.1.1"} == socket_address{L"1.1.1.1"}); + REQUIRE(!(socket_address{L"1.1.1.1"} != socket_address{L"1.1.1.1"})); + + REQUIRE(socket_address{L"1.1.1.1"} < socket_address{L"1.1.1.2"}); + REQUIRE(!(socket_address{L"1.1.1.1"} > socket_address{L"1.1.1.2"})); + REQUIRE(socket_address{L"1.1.1.1"} != socket_address{L"1.1.1.2"}); + REQUIRE(!(socket_address{L"1.1.1.1"} == socket_address{L"1.1.1.2"})); + + REQUIRE(socket_address{L"1.1.1.1"} < socket_address{L"2.1.1.1"}); + REQUIRE(!(socket_address{L"1.1.1.1"} > socket_address{L"2.1.1.1"})); + REQUIRE(socket_address{L"1.1.1.1"} != socket_address{L"2.1.1.1"}); + REQUIRE(!(socket_address{L"1.1.1.1"} == socket_address{L"2.1.1.1"})); + + REQUIRE(socket_address{L"1.0.0.0"} > socket_address{L"0.0.0.1"}); + REQUIRE(!(socket_address{L"1.0.0.0"} < socket_address{L"0.0.0.1"})); + REQUIRE(socket_address{L"1.0.0.0"} != socket_address{L"0.0.0.1"}); + REQUIRE(!(socket_address{L"1.0.0.0"} == socket_address{L"0.0.0.1"})); + + REQUIRE(socket_address{L"1.1.1.1", 1} < socket_address{L"1.1.1.1", 2}); + REQUIRE(!(socket_address{L"1.1.1.1", 1} > socket_address{L"1.1.1.1", 2})); + REQUIRE(socket_address{L"1.1.1.1", 1} != socket_address{L"1.1.1.1", 2}); + REQUIRE(!(socket_address{L"1.1.1.1", 1} == socket_address{L"1.1.1.1", 2})); + + REQUIRE(socket_address{L"1.1.1.1", 1} > socket_address{L"0.0.0.0", 65535}); + REQUIRE(!(socket_address{L"1.1.1.1", 1} < socket_address{L"0.0.0.0", 65535})); + REQUIRE(socket_address{L"1.1.1.1", 1} != socket_address{L"0.0.0.0", 65535}); + REQUIRE(!(socket_address{L"1.1.1.1", 1} == socket_address{L"0.0.0.0", 65535})); + + REQUIRE(socket_address{L"254.254.254.254"} > socket_address{L"127.127.127.127"}); + REQUIRE(!(socket_address{L"254.254.254.254"} < socket_address{L"127.127.127.127"})); + REQUIRE(socket_address{L"254.254.254.254"} != socket_address{L"127.127.127.127"}); + REQUIRE(!(socket_address{L"254.254.254.254"} == socket_address{L"127.127.127.127"})); + } + + SECTION("verify v6 address comparisons") + { + // equal will be considered less-than + REQUIRE(socket_address{L"2001::1002"} < socket_address{L"2001::1002"}); + REQUIRE(!(socket_address{L"2001::1002"} > socket_address{L"2001::1002"})); + REQUIRE(socket_address{L"2001::1002"} == socket_address{L"2001::1002"}); + REQUIRE(!(socket_address{L"2001::1002"} != socket_address{L"2001::1002"})); + + REQUIRE(socket_address{L"2001::1002"} < socket_address{L"2001::1003"}); + REQUIRE(!(socket_address{L"2001::1002"} > socket_address{L"2001::1003"})); + REQUIRE(socket_address{L"2001::1002"} != socket_address{L"2001::1003"}); + REQUIRE(!(socket_address{L"2001::1002"} == socket_address{L"2001::1003"})); + + REQUIRE(socket_address{L"2001::1002"} > socket_address{L"1002::2001"}); + REQUIRE(!(socket_address{L"2001::1002"} < socket_address{L"1002::2001"})); + REQUIRE(socket_address{L"2001::1002"} != socket_address{L"1002::2001"}); + REQUIRE(!(socket_address{L"2001::1002"} == socket_address{L"1002::2001"})); + + REQUIRE(socket_address{L"2001::1002"} > socket_address{L"::1"}); + REQUIRE(!(socket_address{L"2001::1002"} < socket_address{L"::1"})); + REQUIRE(socket_address{L"2001::1002"} != socket_address{L"::1"}); + REQUIRE(!(socket_address{L"2001::1002"} == socket_address{L"::1"})); + + REQUIRE(socket_address{L"2001::1002", 1} < socket_address{L"2001::1002", 2}); + REQUIRE(!(socket_address{L"2001::1002", 1} > socket_address{L"2001::1002", 2})); + REQUIRE(socket_address{L"2001::1002", 1} != socket_address{L"2001::1002", 2}); + REQUIRE(!(socket_address{L"2001::1002", 1} == socket_address{L"2001::1002", 2})); + + socket_address lhs_scope_id_test{L"2001::1002", 1}; + lhs_scope_id_test.set_scope_id(10000); + socket_address rhs_scope_id_test{L"2001::1002", 1}; + rhs_scope_id_test.set_scope_id(100000); + REQUIRE(lhs_scope_id_test < rhs_scope_id_test); + REQUIRE(!(lhs_scope_id_test > rhs_scope_id_test)); + REQUIRE(lhs_scope_id_test != rhs_scope_id_test); + REQUIRE(!(lhs_scope_id_test == rhs_scope_id_test)); + + socket_address lhs_flow_info_test{L"2001::1002", 1}; + lhs_flow_info_test.set_flow_info(10000); + socket_address rhs_flow_info_test{L"2001::1002", 1}; + rhs_flow_info_test.set_flow_info(100000); + REQUIRE(lhs_flow_info_test < rhs_flow_info_test); + REQUIRE(!(lhs_flow_info_test > rhs_flow_info_test)); + REQUIRE(lhs_flow_info_test != rhs_flow_info_test); + REQUIRE(!(lhs_flow_info_test == rhs_flow_info_test)); + } +#endif +} + +TEST_CASE("NetworkingTests::Verifying_set_functions", "[networking]") +{ + InitTestAddresses(); + + wil::network::socket_address default_addr{}; + REQUIRE(default_addr.length() == sizeof(SOCKADDR_INET)); + + wil::network::socket_address test_v4_addr{&Test_in_addr}; + wil::network::socket_address test_v4_addr2{&Test_in_addr2}; + wil::network::socket_address test_v4_addr_with_port{&Test_in_addr, TestPort}; + + wil::network::socket_address test_v6_addr{&Test_in6_addr}; + wil::network::socket_address test_v6_addr2{&Test_in6_addr2}; + wil::network::socket_address test_v6_addr_with_port{&Test_in6_addr, TestPort}; + + wil::network::socket_address test_v4_linklocal_addr{&Test_linklocal_in_addr}; + wil::network::socket_address test_v4_linklocal_addr_with_port{&Test_linklocal_in_addr, TestPort}; + + wil::network::socket_address test_v6_linklocal_addr{&Test_linklocal_in6_addr}; + wil::network::socket_address test_v6_linklocal_addr_with_port{&Test_linklocal_in6_addr, TestPort}; + + wil::network::socket_address test_v4_any_addr{&Test_any_in_addr}; + wil::network::socket_address test_v4_any_addr_with_port{&Test_any_in_addr, TestPort}; + + wil::network::socket_address test_v6_any_addr{&Test_any_in6_addr}; + wil::network::socket_address test_v6_any_addr_with_port{&Test_any_in6_addr, TestPort}; + + wil::network::socket_address test_v4_loopback_addr{&Test_loopback_in_addr}; + wil::network::socket_address test_v4_loopback_addr_with_port{&Test_loopback_in_addr, TestPort}; + + wil::network::socket_address test_v6_loopback_addr{&Test_loopback_in6_addr}; + wil::network::socket_address test_v6_loopback_addr_with_port{&Test_loopback_in6_addr, TestPort}; + + // need WSAStartup called for some functions below + auto wsa_startup_tracking = wil::network::WSAStartup_nothrow(); + REQUIRE(static_cast(wsa_startup_tracking)); + + using wil::network::equals; + + SECTION("verify set_address_any") + { + const auto VerifyV4AnyAddress = [&](const wil::network::socket_address& v4_address, bool with_port) { + wil::network::socket_address_wstring any_address_test_string{}; + REQUIRE(SUCCEEDED(v4_address.write_address_nothrow(any_address_test_string))); + REQUIRE(0 == memcmp(Test_any_in_addr_string, any_address_test_string, sizeof(Test_any_in_addr_string))); +#ifdef WIL_ENABLE_EXCEPTIONS + REQUIRE(Test_any_in_addr_string == v4_address.write_address()); +#endif + + ZeroMemory(any_address_test_string, sizeof(any_address_test_string)); + + REQUIRE(SUCCEEDED(v4_address.write_complete_address_nothrow(any_address_test_string))); + if (with_port) + { + REQUIRE(0 == memcmp(Test_any_in_addr_string_with_port, any_address_test_string, sizeof(Test_any_in_addr_string_with_port))); +#ifdef WIL_ENABLE_EXCEPTIONS + REQUIRE(Test_any_in_addr_string_with_port == v4_address.write_complete_address()); +#endif + } + else + { + REQUIRE(0 == memcmp(Test_any_in_addr_string, any_address_test_string, sizeof(Test_any_in_addr_string))); +#ifdef WIL_ENABLE_EXCEPTIONS + REQUIRE(Test_any_in_addr_string == v4_address.write_complete_address()); +#endif + } + + // also char* versions + wil::network::socket_address_string any_address_test_char_string{}; + REQUIRE(SUCCEEDED(v4_address.write_address_nothrow(any_address_test_char_string))); + REQUIRE(0 == memcmp(Test_any_in_addr_char_string, any_address_test_char_string, sizeof(Test_any_in_addr_char_string))); + + ZeroMemory(any_address_test_char_string, sizeof(any_address_test_char_string)); + + REQUIRE(SUCCEEDED(v4_address.write_complete_address_nothrow(any_address_test_char_string))); + if (with_port) + { + REQUIRE(0 == memcmp(Test_any_in_addr_char_string_with_port, any_address_test_char_string, sizeof(Test_any_in_addr_char_string_with_port))); + } + else + { + REQUIRE(0 == memcmp(Test_any_in_addr_char_string, any_address_test_char_string, sizeof(Test_any_in_addr_char_string))); + } + }; + + const auto VerifyV6AnyAddress = [&](const wil::network::socket_address& v6_address, bool with_port) { + wil::network::socket_address_wstring any_address_test_string{}; + REQUIRE(SUCCEEDED(v6_address.write_address_nothrow(any_address_test_string))); + REQUIRE(0 == memcmp(Test_any_in6_addr_string, any_address_test_string, sizeof(Test_any_in6_addr_string))); +#ifdef WIL_ENABLE_EXCEPTIONS + REQUIRE(Test_any_in6_addr_string == v6_address.write_address()); +#endif + + ZeroMemory(any_address_test_string, sizeof(any_address_test_string)); + + REQUIRE(SUCCEEDED(v6_address.write_complete_address_nothrow(any_address_test_string))); + if (with_port) + { + REQUIRE(0 == memcmp(Test_any_in6_addr_string_with_port, any_address_test_string, sizeof(Test_any_in6_addr_string_with_port))); +#ifdef WIL_ENABLE_EXCEPTIONS + REQUIRE(Test_any_in6_addr_string_with_port == v6_address.write_complete_address()); +#endif + } + else + { + REQUIRE(0 == memcmp(Test_any_in6_addr_string, any_address_test_string, sizeof(Test_any_in6_addr_string))); +#ifdef WIL_ENABLE_EXCEPTIONS + REQUIRE(Test_any_in6_addr_string == v6_address.write_complete_address()); +#endif + } + + // also char* versions + wil::network::socket_address_string any_address_test_char_string{}; + REQUIRE(SUCCEEDED(v6_address.write_address_nothrow(any_address_test_char_string))); + REQUIRE(0 == memcmp(Test_any_in6_addr_char_string, any_address_test_char_string, sizeof(Test_any_in6_addr_char_string))); + + ZeroMemory(any_address_test_char_string, sizeof(any_address_test_char_string)); + + REQUIRE(SUCCEEDED(v6_address.write_complete_address_nothrow(any_address_test_char_string))); + if (with_port) + { + REQUIRE(0 == memcmp(Test_any_in6_addr_char_string_with_port, any_address_test_char_string, sizeof(Test_any_in6_addr_char_string_with_port))); + } + else + { + REQUIRE(0 == memcmp(Test_any_in6_addr_char_string, any_address_test_char_string, sizeof(Test_any_in6_addr_char_string))); + } + }; + + wil::network::socket_address v4_address; + v4_address.set_address_any(AF_INET); + REQUIRE(v4_address.family() == AF_INET); + REQUIRE(v4_address.port() == 0); + REQUIRE(v4_address.address_type() == NlatUnspecified); + REQUIRE(!v4_address.is_address_linklocal()); + REQUIRE(!v4_address.is_address_loopback()); + REQUIRE(v4_address == test_v4_any_addr); + VerifyV4AnyAddress(v4_address, false); + + v4_address.set_port(TestPort); + REQUIRE(v4_address.family() == AF_INET); + REQUIRE(v4_address.port() == TestPort); + REQUIRE(v4_address.address_type() == NlatUnspecified); + REQUIRE(!v4_address.is_address_linklocal()); + REQUIRE(!v4_address.is_address_loopback()); + REQUIRE(v4_address == test_v4_any_addr_with_port); + VerifyV4AnyAddress(v4_address, true); + + // verify changing families + v4_address.set_address_any(AF_INET6); + REQUIRE(v4_address.family() == AF_INET6); + REQUIRE(v4_address.port() == TestPort); + REQUIRE(v4_address.address_type() == NlatUnspecified); + REQUIRE(!v4_address.is_address_linklocal()); + REQUIRE(!v4_address.is_address_loopback()); + REQUIRE(v4_address == test_v6_any_addr_with_port); + VerifyV6AnyAddress(v4_address, true); + + wil::network::socket_address v6_address; + v6_address.set_address_any(AF_INET6); + REQUIRE(v6_address.family() == AF_INET6); + REQUIRE(v6_address.port() == 0); + REQUIRE(v6_address.address_type() == NlatUnspecified); + REQUIRE(!v6_address.is_address_linklocal()); + REQUIRE(!v6_address.is_address_loopback()); + REQUIRE(v6_address == test_v6_any_addr); + VerifyV6AnyAddress(v6_address, false); + + v6_address.set_port(TestPort); + REQUIRE(v6_address.family() == AF_INET6); + REQUIRE(v6_address.port() == TestPort); + REQUIRE(v6_address.address_type() == NlatUnspecified); + REQUIRE(!v6_address.is_address_linklocal()); + REQUIRE(!v6_address.is_address_loopback()); + REQUIRE(v6_address == test_v6_any_addr_with_port); + VerifyV6AnyAddress(v6_address, true); + + // verify changing families + v6_address.set_address_any(AF_INET); + REQUIRE(v6_address.family() == AF_INET); + REQUIRE(v6_address.port() == TestPort); + REQUIRE(v6_address.address_type() == NlatUnspecified); + REQUIRE(!v6_address.is_address_linklocal()); + REQUIRE(!v6_address.is_address_loopback()); + REQUIRE(v6_address == test_v4_any_addr_with_port); + VerifyV4AnyAddress(v6_address, true); + + wil::network::socket_address defaulted_v4_address{AF_INET}; + defaulted_v4_address.set_address_any(); + REQUIRE(defaulted_v4_address.family() == AF_INET); + REQUIRE(defaulted_v4_address.port() == 0); + REQUIRE(defaulted_v4_address.address_type() == NlatUnspecified); + REQUIRE(!defaulted_v4_address.is_address_linklocal()); + REQUIRE(!defaulted_v4_address.is_address_loopback()); + REQUIRE(defaulted_v4_address == test_v4_any_addr); + VerifyV4AnyAddress(defaulted_v4_address, false); + + defaulted_v4_address.set_port(TestPort); + REQUIRE(defaulted_v4_address.family() == AF_INET); + REQUIRE(defaulted_v4_address.port() == TestPort); + REQUIRE(defaulted_v4_address.address_type() == NlatUnspecified); + REQUIRE(!defaulted_v4_address.is_address_linklocal()); + REQUIRE(!defaulted_v4_address.is_address_loopback()); + REQUIRE(defaulted_v4_address == test_v4_any_addr_with_port); + VerifyV4AnyAddress(defaulted_v4_address, true); + + wil::network::socket_address defaulted_v6_address{AF_INET6}; + defaulted_v6_address.set_address_any(); + REQUIRE(defaulted_v6_address.family() == AF_INET6); + REQUIRE(defaulted_v6_address.port() == 0); + REQUIRE(defaulted_v6_address.address_type() == NlatUnspecified); + REQUIRE(!defaulted_v6_address.is_address_linklocal()); + REQUIRE(!defaulted_v6_address.is_address_loopback()); + REQUIRE(defaulted_v6_address == test_v6_any_addr); + VerifyV6AnyAddress(defaulted_v6_address, false); + + defaulted_v6_address.set_port(TestPort); + REQUIRE(defaulted_v6_address.family() == AF_INET6); + REQUIRE(defaulted_v6_address.port() == TestPort); + REQUIRE(defaulted_v6_address.address_type() == NlatUnspecified); + REQUIRE(!defaulted_v6_address.is_address_linklocal()); + REQUIRE(!defaulted_v6_address.is_address_loopback()); + REQUIRE(defaulted_v6_address == test_v6_any_addr_with_port); + VerifyV6AnyAddress(defaulted_v6_address, true); + } + + SECTION("verify set_address_loopback") + { + const auto VerifyV4LoopbackAddress = [&](const wil::network::socket_address& v4_address, bool with_port) { + wil::network::socket_address_wstring loopback_address_test_string{}; + REQUIRE(SUCCEEDED(v4_address.write_address_nothrow(loopback_address_test_string))); + REQUIRE(0 == memcmp(Test_loopback_in_addr_string, loopback_address_test_string, sizeof(Test_loopback_in_addr_string))); +#ifdef WIL_ENABLE_EXCEPTIONS + REQUIRE(Test_loopback_in_addr_string == v4_address.write_address()); +#endif + + ZeroMemory(loopback_address_test_string, sizeof(loopback_address_test_string)); + + REQUIRE(SUCCEEDED(v4_address.write_complete_address_nothrow(loopback_address_test_string))); + if (with_port) + { + REQUIRE(0 == memcmp(Test_loopback_in_addr_string_with_port, loopback_address_test_string, sizeof(Test_loopback_in_addr_string_with_port))); +#ifdef WIL_ENABLE_EXCEPTIONS + REQUIRE(Test_loopback_in_addr_string_with_port == v4_address.write_complete_address()); +#endif + } + else + { + REQUIRE(0 == memcmp(Test_loopback_in_addr_string, loopback_address_test_string, sizeof(Test_loopback_in_addr_string))); +#ifdef WIL_ENABLE_EXCEPTIONS + REQUIRE(Test_loopback_in_addr_string == v4_address.write_complete_address()); +#endif + } + }; + + const auto VerifyV6LoopbackAddress = [&](const wil::network::socket_address& v6_address, bool with_port) { + wil::network::socket_address_wstring loopback_address_test_string{}; + REQUIRE(SUCCEEDED(v6_address.write_address_nothrow(loopback_address_test_string))); + REQUIRE(0 == memcmp(Test_loopback_in6_addr_string, loopback_address_test_string, sizeof(Test_loopback_in6_addr_string))); +#ifdef WIL_ENABLE_EXCEPTIONS + REQUIRE(Test_loopback_in6_addr_string == v6_address.write_address()); +#endif + + ZeroMemory(loopback_address_test_string, sizeof(loopback_address_test_string)); + + REQUIRE(SUCCEEDED(v6_address.write_complete_address_nothrow(loopback_address_test_string))); + if (with_port) + { + REQUIRE(0 == memcmp(Test_loopback_in6_addr_string_with_port, loopback_address_test_string, sizeof(Test_loopback_in6_addr_string_with_port))); +#ifdef WIL_ENABLE_EXCEPTIONS + REQUIRE(Test_loopback_in6_addr_string_with_port == v6_address.write_complete_address()); +#endif + } + else + { + REQUIRE(0 == memcmp(Test_loopback_in6_addr_string, loopback_address_test_string, sizeof(Test_loopback_in6_addr_string))); +#ifdef WIL_ENABLE_EXCEPTIONS + REQUIRE(Test_loopback_in6_addr_string == v6_address.write_complete_address()); +#endif + } + }; + + wil::network::socket_address v4_address; + v4_address.set_address_loopback(AF_INET); + REQUIRE(v4_address.family() == AF_INET); + REQUIRE(v4_address.port() == 0); + REQUIRE(v4_address.address_type() == NlatUnicast); + REQUIRE(!v4_address.is_address_linklocal()); + REQUIRE(v4_address.is_address_loopback()); + REQUIRE(v4_address == test_v4_loopback_addr); + VerifyV4LoopbackAddress(v4_address, false); + + v4_address.set_port(TestPort); + REQUIRE(v4_address.family() == AF_INET); + REQUIRE(v4_address.port() == TestPort); + REQUIRE(v4_address.address_type() == NlatUnicast); + REQUIRE(!v4_address.is_address_linklocal()); + REQUIRE(v4_address.is_address_loopback()); + REQUIRE(v4_address == test_v4_loopback_addr_with_port); + VerifyV4LoopbackAddress(v4_address, true); + + // verify changing families + v4_address.set_address_loopback(AF_INET6); + REQUIRE(v4_address.family() == AF_INET6); + REQUIRE(v4_address.port() == TestPort); + REQUIRE(v4_address.address_type() == NlatUnicast); + REQUIRE(!v4_address.is_address_linklocal()); + REQUIRE(v4_address.is_address_loopback()); + REQUIRE(v4_address == test_v6_loopback_addr_with_port); + VerifyV6LoopbackAddress(v4_address, true); + + wil::network::socket_address v6_address; + v6_address.set_address_loopback(AF_INET6); + REQUIRE(v6_address.family() == AF_INET6); + REQUIRE(v6_address.port() == 0); + REQUIRE(v6_address.address_type() == NlatUnicast); + REQUIRE(!v6_address.is_address_linklocal()); + REQUIRE(v6_address.is_address_loopback()); + REQUIRE(v6_address == test_v6_loopback_addr); + VerifyV6LoopbackAddress(v6_address, false); + + v6_address.set_port(TestPort); + REQUIRE(v6_address.family() == AF_INET6); + REQUIRE(v6_address.port() == TestPort); + REQUIRE(v6_address.address_type() == NlatUnicast); + REQUIRE(!v6_address.is_address_linklocal()); + REQUIRE(v6_address.is_address_loopback()); + REQUIRE(v6_address == test_v6_loopback_addr_with_port); + VerifyV6LoopbackAddress(v6_address, true); + + // verify changing families + v6_address.set_address_loopback(AF_INET); + REQUIRE(v6_address.family() == AF_INET); + REQUIRE(v6_address.port() == TestPort); + REQUIRE(v6_address.address_type() == NlatUnicast); + REQUIRE(!v6_address.is_address_linklocal()); + REQUIRE(v6_address.is_address_loopback()); + REQUIRE(v6_address == test_v4_loopback_addr_with_port); + VerifyV4LoopbackAddress(v6_address, true); + + wil::network::socket_address defaulted_v4_address{AF_INET}; + defaulted_v4_address.set_address_loopback(); + REQUIRE(defaulted_v4_address.family() == AF_INET); + REQUIRE(defaulted_v4_address.port() == 0); + REQUIRE(defaulted_v4_address.address_type() == NlatUnicast); + REQUIRE(!defaulted_v4_address.is_address_linklocal()); + REQUIRE(defaulted_v4_address.is_address_loopback()); + REQUIRE(defaulted_v4_address == test_v4_loopback_addr); + VerifyV4LoopbackAddress(defaulted_v4_address, false); + + defaulted_v4_address.set_port(TestPort); + REQUIRE(defaulted_v4_address.family() == AF_INET); + REQUIRE(defaulted_v4_address.port() == TestPort); + REQUIRE(defaulted_v4_address.address_type() == NlatUnicast); + REQUIRE(!defaulted_v4_address.is_address_linklocal()); + REQUIRE(defaulted_v4_address.is_address_loopback()); + REQUIRE(defaulted_v4_address == test_v4_loopback_addr_with_port); + VerifyV4LoopbackAddress(defaulted_v4_address, true); + + wil::network::socket_address defaulted_v6_address{AF_INET6}; + defaulted_v6_address.set_address_loopback(); + REQUIRE(defaulted_v6_address.family() == AF_INET6); + REQUIRE(defaulted_v6_address.port() == 0); + REQUIRE(defaulted_v6_address.address_type() == NlatUnicast); + REQUIRE(!defaulted_v6_address.is_address_linklocal()); + REQUIRE(defaulted_v6_address.is_address_loopback()); + REQUIRE(defaulted_v6_address == test_v6_loopback_addr); + VerifyV6LoopbackAddress(defaulted_v6_address, false); + + defaulted_v6_address.set_port(TestPort); + REQUIRE(defaulted_v6_address.family() == AF_INET6); + REQUIRE(defaulted_v6_address.port() == TestPort); + REQUIRE(defaulted_v6_address.address_type() == NlatUnicast); + REQUIRE(!defaulted_v6_address.is_address_linklocal()); + REQUIRE(defaulted_v6_address.is_address_loopback()); + REQUIRE(defaulted_v6_address == test_v6_loopback_addr_with_port); + VerifyV6LoopbackAddress(defaulted_v6_address, true); + } + + SECTION("verify v4 set_sockaddr_nothrow") + { + wil::network::socket_address v4_address; + v4_address.set_address_loopback(AF_INET); + v4_address.set_port(TestPort); + REQUIRE(v4_address.address_type() == NlatUnicast); + REQUIRE(v4_address.is_address_loopback()); + REQUIRE(TestPort == v4_address.port()); + + REQUIRE(SUCCEEDED(v4_address.set_sockaddr_nothrow(Test_in_addr_string))); + REQUIRE(equals(*v4_address.in_addr(), Test_in_addr)); + REQUIRE(v4_address.port() == 0); + REQUIRE(v4_address.address_type() == NlatUnicast); + REQUIRE(!v4_address.is_address_loopback()); + + wil::network::socket_address_wstring v4_address_string{}; + REQUIRE(SUCCEEDED(v4_address.write_address_nothrow(v4_address_string))); + REQUIRE(0 == memcmp(Test_in_addr_string, v4_address_string, sizeof(Test_in_addr_string))); + + REQUIRE(SUCCEEDED(v4_address.set_sockaddr_nothrow(Test_in_addr_string2))); + REQUIRE(equals(*v4_address.in_addr(), Test_in_addr2)); + REQUIRE(v4_address.port() == 0); + REQUIRE(v4_address.address_type() == NlatUnicast); + REQUIRE(!v4_address.is_address_loopback()); + + REQUIRE(SUCCEEDED(v4_address.write_address_nothrow(v4_address_string))); + REQUIRE(0 == memcmp(Test_in_addr_string2, v4_address_string, sizeof(Test_in_addr_string2))); + + REQUIRE(SUCCEEDED(v4_address.set_sockaddr_nothrow(Test_in_addr_char_string))); + REQUIRE(equals(*v4_address.in_addr(), Test_in_addr)); + REQUIRE(v4_address.port() == 0); + REQUIRE(v4_address.address_type() == NlatUnicast); + REQUIRE(!v4_address.is_address_loopback()); + + wil::network::socket_address_string v4_address_char_string{}; + REQUIRE(SUCCEEDED(v4_address.write_address_nothrow(v4_address_char_string))); + REQUIRE(0 == memcmp(Test_in_addr_char_string, v4_address_char_string, sizeof(Test_in_addr_char_string))); + + REQUIRE(SUCCEEDED(v4_address.set_sockaddr_nothrow(Test_in_addr_char_string2))); + REQUIRE(equals(*v4_address.in_addr(), Test_in_addr2)); + REQUIRE(v4_address.port() == 0); + REQUIRE(v4_address.address_type() == NlatUnicast); + REQUIRE(!v4_address.is_address_loopback()); + + REQUIRE(SUCCEEDED(v4_address.write_address_nothrow(v4_address_char_string))); + REQUIRE(0 == memcmp(Test_in_addr_char_string2, v4_address_char_string, sizeof(Test_in_addr_char_string2))); + + // set_sockaddr via a SOCKET bound to an address + wil::unique_socket test_socket{::socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)}; + REQUIRE(test_socket.get() != INVALID_SOCKET); + wil::network::socket_address test_address; + test_address.set_address_loopback(AF_INET); + test_address.set_port(TestPort); + + int gle = 0; + auto bind_error = ::bind(test_socket.get(), test_address.sockaddr(), test_address.length()); + if (bind_error != 0) + { + gle = ::WSAGetLastError(); + } + REQUIRE(gle == 0); + REQUIRE(bind_error == 0); + + v4_address.reset(AF_UNSPEC); + REQUIRE(v4_address.address_type() == NlatUnspecified); + REQUIRE(!v4_address.is_address_loopback()); + REQUIRE(0 == v4_address.port()); + + REQUIRE(SUCCEEDED(v4_address.set_sockaddr_nothrow(test_socket.get()))); + REQUIRE(AF_INET == v4_address.family()); + REQUIRE(v4_address.address_type() == NlatUnicast); + REQUIRE(v4_address.is_address_loopback()); + REQUIRE(TestPort == v4_address.port()); + + REQUIRE(SUCCEEDED(v4_address.write_address_nothrow(v4_address_string))); + REQUIRE(0 == memcmp(Test_loopback_in_addr_string, v4_address_string, sizeof(Test_loopback_in_addr_string))); + } + + SECTION("verify v4 set_sockaddr throwing version") + { +#ifdef WIL_ENABLE_EXCEPTIONS + wil::network::socket_address v4_address; + v4_address.set_address_loopback(AF_INET); + v4_address.set_port(TestPort); + REQUIRE(v4_address.address_type() == NlatUnicast); + REQUIRE(v4_address.is_address_loopback()); + REQUIRE(TestPort == v4_address.port()); + + v4_address.set_sockaddr(Test_in_addr_string); + REQUIRE(equals(*v4_address.in_addr(), Test_in_addr)); + REQUIRE(v4_address.port() == 0); + REQUIRE(v4_address.address_type() == NlatUnicast); + REQUIRE(!v4_address.is_address_loopback()); + + std::wstring v4_address_string; + v4_address_string = v4_address.write_address(); + REQUIRE(v4_address_string == Test_in_addr_string); + + v4_address.set_sockaddr(Test_in_addr_string2); + REQUIRE(equals(*v4_address.in_addr(), Test_in_addr2)); + REQUIRE(v4_address.port() == 0); + REQUIRE(v4_address.address_type() == NlatUnicast); + REQUIRE(!v4_address.is_address_loopback()); + + v4_address_string = v4_address.write_address(); + REQUIRE(v4_address_string == Test_in_addr_string2); + + v4_address.set_sockaddr(Test_in_addr_char_string); + REQUIRE(equals(*v4_address.in_addr(), Test_in_addr)); + REQUIRE(v4_address.port() == 0); + REQUIRE(v4_address.address_type() == NlatUnicast); + REQUIRE(!v4_address.is_address_loopback()); + + v4_address_string = v4_address.write_address(); + REQUIRE(v4_address_string == Test_in_addr_string); + + v4_address.set_sockaddr(Test_in_addr_char_string2); + REQUIRE(equals(*v4_address.in_addr(), Test_in_addr2)); + REQUIRE(v4_address.port() == 0); + REQUIRE(v4_address.address_type() == NlatUnicast); + REQUIRE(!v4_address.is_address_loopback()); + + v4_address_string = v4_address.write_address(); + REQUIRE(v4_address_string == Test_in_addr_string2); + + // set_sockaddr via a SOCKET bound to an address + wil::unique_socket test_socket{::socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)}; + REQUIRE(test_socket.get() != INVALID_SOCKET); + wil::network::socket_address test_address; + test_address.set_address_loopback(AF_INET); + test_address.set_port(TestPort); + + int gle = 0; + auto bind_error = ::bind(test_socket.get(), test_address.sockaddr(), test_address.length()); + if (bind_error != 0) + { + gle = ::WSAGetLastError(); + } + REQUIRE(gle == 0); + REQUIRE(bind_error == 0); + + v4_address.reset(AF_UNSPEC); + REQUIRE(v4_address.address_type() == NlatUnspecified); + REQUIRE(!v4_address.is_address_loopback()); + REQUIRE(0 == v4_address.port()); + + v4_address.set_sockaddr(test_socket.get()); + REQUIRE(AF_INET == v4_address.family()); + REQUIRE(v4_address.address_type() == NlatUnicast); + REQUIRE(v4_address.is_address_loopback()); + REQUIRE(TestPort == v4_address.port()); + + v4_address_string = v4_address.write_address(); + REQUIRE(v4_address_string == Test_loopback_in_addr_string); +#endif + } + + SECTION("verify v6 set_sockaddr_nothrow") + { + wil::network::socket_address v6_address; + v6_address.set_address_loopback(AF_INET6); + v6_address.set_port(TestPort); + REQUIRE(v6_address.address_type() == NlatUnicast); + REQUIRE(v6_address.is_address_loopback()); + REQUIRE(TestPort == v6_address.port()); + + REQUIRE(SUCCEEDED(v6_address.set_sockaddr_nothrow(Test_in6_addr_string))); + REQUIRE(equals(*v6_address.in6_addr(), Test_in6_addr)); + REQUIRE(v6_address.port() == 0); + REQUIRE(v6_address.address_type() == NlatUnicast); + REQUIRE(!v6_address.is_address_loopback()); + + wil::network::socket_address_wstring v6_address_string{}; + REQUIRE(SUCCEEDED(v6_address.write_address_nothrow(v6_address_string))); + REQUIRE(0 == memcmp(Test_in6_addr_string, v6_address_string, sizeof(Test_in6_addr_string))); + + REQUIRE(SUCCEEDED(v6_address.set_sockaddr_nothrow(Test_in6_addr_string2))); + REQUIRE(equals(*v6_address.in6_addr(), Test_in6_addr2)); + REQUIRE(v6_address.port() == 0); + REQUIRE(v6_address.address_type() == NlatUnicast); + REQUIRE(!v6_address.is_address_loopback()); + + REQUIRE(SUCCEEDED(v6_address.write_address_nothrow(v6_address_string))); + REQUIRE(0 == memcmp(Test_in6_addr_string2, v6_address_string, sizeof(Test_in6_addr_string2))); + + REQUIRE(SUCCEEDED(v6_address.set_sockaddr_nothrow(Test_in6_addr_char_string))); + REQUIRE(equals(*v6_address.in6_addr(), Test_in6_addr)); + REQUIRE(v6_address.port() == 0); + REQUIRE(v6_address.address_type() == NlatUnicast); + REQUIRE(!v6_address.is_address_loopback()); + + wil::network::socket_address_string v6_address_char_string{}; + REQUIRE(SUCCEEDED(v6_address.write_address_nothrow(v6_address_char_string))); + REQUIRE(0 == memcmp(Test_in6_addr_char_string, v6_address_char_string, sizeof(Test_in6_addr_char_string))); + + REQUIRE(SUCCEEDED(v6_address.set_sockaddr_nothrow(Test_in6_addr_char_string2))); + REQUIRE(equals(*v6_address.in6_addr(), Test_in6_addr2)); + REQUIRE(v6_address.port() == 0); + REQUIRE(v6_address.address_type() == NlatUnicast); + REQUIRE(!v6_address.is_address_loopback()); + + REQUIRE(SUCCEEDED(v6_address.write_address_nothrow(v6_address_char_string))); + REQUIRE(0 == memcmp(Test_in6_addr_char_string2, v6_address_char_string, sizeof(Test_in6_addr_char_string2))); + + // set_sockaddr via a SOCKET bound to an address + wil::unique_socket test_socket{::socket(AF_INET6, SOCK_STREAM, IPPROTO_TCP)}; + REQUIRE(test_socket.get() != INVALID_SOCKET); + wil::network::socket_address test_address; + test_address.set_address_loopback(AF_INET6); + test_address.set_port(TestPort); + + int gle = 0; + auto bind_error = ::bind(test_socket.get(), test_address.sockaddr(), test_address.length()); + if (bind_error != 0) + { + gle = ::WSAGetLastError(); + } + REQUIRE(gle == 0); + REQUIRE(bind_error == 0); + + v6_address.reset(AF_UNSPEC); + REQUIRE(v6_address.address_type() == NlatUnspecified); + REQUIRE(!v6_address.is_address_loopback()); + REQUIRE(0 == v6_address.port()); + + REQUIRE(SUCCEEDED(v6_address.set_sockaddr_nothrow(test_socket.get()))); + REQUIRE(AF_INET6 == v6_address.family()); + REQUIRE(v6_address.address_type() == NlatUnicast); + REQUIRE(v6_address.is_address_loopback()); + REQUIRE(TestPort == v6_address.port()); + + REQUIRE(SUCCEEDED(v6_address.write_address_nothrow(v6_address_string))); + REQUIRE(0 == memcmp(Test_loopback_in6_addr_string, v6_address_string, sizeof(Test_loopback_in6_addr_string))); + } + + SECTION("verify v6 set_sockaddr throwing version") + { +#ifdef WIL_ENABLE_EXCEPTIONS + wil::network::socket_address v6_address; + v6_address.set_address_loopback(AF_INET6); + v6_address.set_port(TestPort); + REQUIRE(v6_address.address_type() == NlatUnicast); + REQUIRE(v6_address.is_address_loopback()); + REQUIRE(TestPort == v6_address.port()); + + v6_address.set_sockaddr(Test_in6_addr_string); + REQUIRE(equals(*v6_address.in6_addr(), Test_in6_addr)); + REQUIRE(v6_address.port() == 0); + REQUIRE(v6_address.address_type() == NlatUnicast); + REQUIRE(!v6_address.is_address_loopback()); + + std::wstring v6_address_string; + v6_address_string = v6_address.write_address(); + REQUIRE(v6_address_string == Test_in6_addr_string); + + v6_address.set_sockaddr(Test_in6_addr_string2); + REQUIRE(equals(*v6_address.in6_addr(), Test_in6_addr2)); + REQUIRE(v6_address.port() == 0); + REQUIRE(v6_address.address_type() == NlatUnicast); + REQUIRE(!v6_address.is_address_loopback()); + + v6_address_string = v6_address.write_address(); + REQUIRE(v6_address_string == Test_in6_addr_string2); + + v6_address.set_sockaddr(Test_in6_addr_char_string); + REQUIRE(equals(*v6_address.in6_addr(), Test_in6_addr)); + REQUIRE(v6_address.port() == 0); + REQUIRE(v6_address.address_type() == NlatUnicast); + REQUIRE(!v6_address.is_address_loopback()); + + v6_address_string = v6_address.write_address(); + REQUIRE(v6_address_string == Test_in6_addr_string); + + v6_address.set_sockaddr(Test_in6_addr_char_string2); + REQUIRE(equals(*v6_address.in6_addr(), Test_in6_addr2)); + REQUIRE(v6_address.port() == 0); + REQUIRE(v6_address.address_type() == NlatUnicast); + REQUIRE(!v6_address.is_address_loopback()); + + v6_address_string = v6_address.write_address(); + REQUIRE(v6_address_string == Test_in6_addr_string2); + + // set_sockaddr via a SOCKET bound to an address + wil::unique_socket test_socket{::socket(AF_INET6, SOCK_STREAM, IPPROTO_TCP)}; + REQUIRE(test_socket.get() != INVALID_SOCKET); + wil::network::socket_address test_address; + test_address.set_address_loopback(AF_INET6); + test_address.set_port(TestPort); + + int gle = 0; + auto bind_error = ::bind(test_socket.get(), test_address.sockaddr(), test_address.length()); + if (bind_error != 0) + { + gle = ::WSAGetLastError(); + } + REQUIRE(gle == 0); + REQUIRE(bind_error == 0); + + v6_address.reset(AF_UNSPEC); + REQUIRE(v6_address.address_type() == NlatUnspecified); + REQUIRE(!v6_address.is_address_loopback()); + REQUIRE(0 == v6_address.port()); + + v6_address.set_sockaddr(test_socket.get()); + REQUIRE(AF_INET6 == v6_address.family()); + REQUIRE(v6_address.address_type() == NlatUnicast); + REQUIRE(v6_address.is_address_loopback()); + REQUIRE(TestPort == v6_address.port()); + + v6_address_string = v6_address.write_address(); + REQUIRE(v6_address_string == Test_loopback_in6_addr_string); +#endif + } + + SECTION("verify additional set_* properties") + { + SOCKADDR_IN v4_test_sockaddr{}; + v4_test_sockaddr.sin_family = AF_INET; + v4_test_sockaddr.sin_port = htons(TestPort); // the raw value is in network byte order + memcpy(&v4_test_sockaddr.sin_addr, &Test_linklocal_in_addr, sizeof(in_addr)); + + wil::network::socket_address v4_addr{&v4_test_sockaddr}; + REQUIRE(v4_addr.family() == AF_INET); + REQUIRE(v4_addr.address_type() == NlatUnicast); + REQUIRE(v4_addr.is_address_linklocal()); + REQUIRE(!v4_addr.is_address_loopback()); + REQUIRE(v4_addr.port() == TestPort); +#ifdef WIL_ENABLE_EXCEPTIONS + REQUIRE(Test_linklocal_in_addr_string == v4_addr.write_address()); +#endif + + v4_addr.set_port(TestPort + 1); + // should be stored in network-byte-order + REQUIRE(v4_addr.port() == TestPort + 1); + REQUIRE(v4_addr.sockaddr_in()->sin_port == htons(TestPort + 1)); + + SOCKADDR_IN6 v6_test_sockaddr{}; + v6_test_sockaddr.sin6_family = AF_INET6; + // these raw values are in network byte order + v6_test_sockaddr.sin6_port = htons(TestPort); + v6_test_sockaddr.sin6_flowinfo = htonl(123456); + v6_test_sockaddr.sin6_scope_id = htonl(234567); + memcpy(&v6_test_sockaddr.sin6_addr, &Test_linklocal_in6_addr, sizeof(in6_addr)); + + wil::network::socket_address v6_addr{&v6_test_sockaddr}; + REQUIRE(v6_addr.family() == AF_INET6); + REQUIRE(v6_addr.address_type() == NlatUnicast); + REQUIRE(v6_addr.is_address_linklocal()); + REQUIRE(!v6_addr.is_address_loopback()); + REQUIRE(v6_addr.port() == TestPort); + REQUIRE(v6_addr.flow_info() == 123456); + REQUIRE(v6_addr.scope_id() == 234567); +#ifdef WIL_ENABLE_EXCEPTIONS + REQUIRE(Test_linklocal_in6_addr_string == v6_addr.write_address()); +#endif + + v6_addr.set_flow_info(345678); + // should be stored in network-byte-order + REQUIRE(v6_addr.flow_info() == 345678); + REQUIRE(v6_addr.sockaddr_in6()->sin6_flowinfo == htonl(345678)); + + v6_addr.set_scope_id(456789); + // should be stored in network-byte-order + REQUIRE(v6_addr.scope_id() == 456789); + REQUIRE(v6_addr.sockaddr_in6()->sin6_scope_id == htonl(456789)); + } + + SECTION("verify swap") + { + SOCKADDR_IN v4_test_sockaddr{}; + v4_test_sockaddr.sin_family = AF_INET; + v4_test_sockaddr.sin_port = htons(TestPort); // the raw value is in network byte order + memcpy(&v4_test_sockaddr.sin_addr, &Test_linklocal_in_addr, sizeof(in_addr)); + + wil::network::socket_address v4_addr{&v4_test_sockaddr}; + REQUIRE(v4_addr.family() == AF_INET); + REQUIRE(v4_addr.address_type() == NlatUnicast); + REQUIRE(v4_addr.is_address_linklocal()); + REQUIRE(!v4_addr.is_address_loopback()); + REQUIRE(v4_addr.port() == TestPort); +#ifdef WIL_ENABLE_EXCEPTIONS + REQUIRE(Test_linklocal_in_addr_string == v4_addr.write_address()); +#endif + + SOCKADDR_IN6 v6_test_sockaddr{}; + v6_test_sockaddr.sin6_family = AF_INET6; + // these raw values are in network byte order + v6_test_sockaddr.sin6_port = htons(TestPort); + v6_test_sockaddr.sin6_flowinfo = htonl(123456); + v6_test_sockaddr.sin6_scope_id = htonl(234567); + memcpy(&v6_test_sockaddr.sin6_addr, &Test_linklocal_in6_addr, sizeof(in6_addr)); + + wil::network::socket_address v6_addr{&v6_test_sockaddr}; + REQUIRE(v6_addr.family() == AF_INET6); + REQUIRE(v6_addr.address_type() == NlatUnicast); + REQUIRE(v6_addr.is_address_linklocal()); + REQUIRE(!v6_addr.is_address_loopback()); + REQUIRE(v6_addr.port() == TestPort); + REQUIRE(v6_addr.flow_info() == 123456); + REQUIRE(v6_addr.scope_id() == 234567); +#ifdef WIL_ENABLE_EXCEPTIONS + REQUIRE(Test_linklocal_in6_addr_string == v6_addr.write_address()); +#endif + + // swap v4 and v6 + wil::network::swap(v4_addr, v6_addr); + + // verify each has the others' properties + REQUIRE(v6_addr.family() == AF_INET); + REQUIRE(v6_addr.address_type() == NlatUnicast); + REQUIRE(v6_addr.is_address_linklocal()); + REQUIRE(!v6_addr.is_address_loopback()); + REQUIRE(v6_addr.port() == TestPort); +#ifdef WIL_ENABLE_EXCEPTIONS + REQUIRE(Test_linklocal_in_addr_string == v6_addr.write_address()); +#endif + + REQUIRE(v4_addr.family() == AF_INET6); + REQUIRE(v4_addr.address_type() == NlatUnicast); + REQUIRE(v4_addr.is_address_linklocal()); + REQUIRE(!v4_addr.is_address_loopback()); + REQUIRE(v4_addr.port() == TestPort); + REQUIRE(v4_addr.flow_info() == 123456); + REQUIRE(v4_addr.scope_id() == 234567); +#ifdef WIL_ENABLE_EXCEPTIONS + REQUIRE(Test_linklocal_in6_addr_string == v4_addr.write_address()); +#endif + } + + SECTION("verify map_dual_mode_4to6") + { + constexpr wchar_t dual_mode_string[] = L"::ffff:1.1.1.1"; + + SOCKADDR_IN v4_test_sockaddr{}; + v4_test_sockaddr.sin_family = AF_INET; + v4_test_sockaddr.sin_port = htons(TestPort); // the raw value is in network byte order + memcpy(&v4_test_sockaddr.sin_addr, &Test_in_addr, sizeof(in_addr)); + + wil::network::socket_address v4_addr{&v4_test_sockaddr}; + + wil::network::socket_address mapped_addr = wil::network::map_dual_mode_4to6(v4_addr); + REQUIRE(mapped_addr.family() == AF_INET6); + REQUIRE(IN6_IS_ADDR_V4MAPPED(mapped_addr.in6_addr())); +#ifdef WIL_ENABLE_EXCEPTIONS + REQUIRE(dual_mode_string == mapped_addr.write_address()); +#else + (void)dual_mode_string; +#endif + } +} + +TEST_CASE("NetworkingTests::Verifying_failure_paths", "[networking]") +{ + InitTestAddresses(); + auto wsa_startup_tracking = wil::network::WSAStartup_nothrow(); + REQUIRE(static_cast(wsa_startup_tracking)); + + SECTION("verify set_sockaddr socket failure path") + { + + // set_sockaddr via a SOCKET bound to an address - but this time do not call bind + wil::unique_socket test_socket{::socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)}; + REQUIRE(test_socket.get() != INVALID_SOCKET); + + wil::network::socket_address test_address; + REQUIRE(FAILED(test_address.set_sockaddr_nothrow(test_socket.get()))); + +#ifdef WIL_ENABLE_EXCEPTIONS + bool exception_thrown = false; + try + { + test_address.set_sockaddr(test_socket.get()); + } + catch (const wil::ResultException& e) + { + REQUIRE(e.GetErrorCode() == HRESULT_FROM_WIN32(WSAEINVAL)); + exception_thrown = true; + } + catch (...) + { + REQUIRE(false); + } + REQUIRE(exception_thrown); +#endif + } + + SECTION("verify set_sockaddr_nothrow bad-address-string failure path") + { + wil::network::socket_address test_address; + REQUIRE(FAILED(test_address.set_sockaddr_nothrow(L"abcdefg"))); + REQUIRE(FAILED(test_address.set_sockaddr_nothrow("abcdefg"))); + +#ifdef WIL_ENABLE_EXCEPTIONS + bool exception_thrown = false; + try + { + test_address.set_sockaddr(L"abcdefg"); + } + catch (const wil::ResultException& e) + { + REQUIRE(e.GetErrorCode() == HRESULT_FROM_WIN32(ERROR_INVALID_PARAMETER)); + exception_thrown = true; + } + catch (...) + { + REQUIRE(false); + } + REQUIRE(exception_thrown); + + exception_thrown = false; + try + { + test_address.set_sockaddr("abcdefg"); + } + catch (const wil::ResultException& e) + { + REQUIRE(e.GetErrorCode() == HRESULT_FROM_WIN32(ERROR_INVALID_PARAMETER)); + exception_thrown = true; + } + catch (...) + { + REQUIRE(false); + } + REQUIRE(exception_thrown); +#endif + } + + SECTION("verify write_address_nothrow AF_UNSPEC") + { + wil::network::socket_address af_unspec_address; + af_unspec_address.reset(AF_UNSPEC); + + wil::network::socket_address_string string_address; + REQUIRE(SUCCEEDED(af_unspec_address.write_address_nothrow(string_address))); + REQUIRE('\0' == string_address[0]); + REQUIRE(SUCCEEDED(af_unspec_address.write_complete_address_nothrow(string_address))); + REQUIRE('\0' == string_address[0]); + + wil::network::socket_address_wstring wstring_address; + REQUIRE(SUCCEEDED(af_unspec_address.write_address_nothrow(wstring_address))); + REQUIRE(L'\0' == wstring_address[0]); + REQUIRE(SUCCEEDED(af_unspec_address.write_complete_address_nothrow(wstring_address))); + REQUIRE(L'\0' == wstring_address[0]); + +#ifdef WIL_ENABLE_EXCEPTIONS + std::wstring test_string = af_unspec_address.write_address(); + REQUIRE(test_string.empty()); + + test_string = af_unspec_address.write_complete_address(); + REQUIRE(test_string.empty()); +#endif + } + + SECTION("verify write_address_nothrow failure path") + { + wil::network::socket_address test_address; + // set an unsupported family for verifying failure paths + test_address.reset(AF_APPLETALK); + + wil::network::socket_address_wstring wstring_address; + REQUIRE(FAILED(test_address.write_address_nothrow(wstring_address))); + REQUIRE(FAILED(test_address.write_complete_address_nothrow(wstring_address))); + +#ifdef WIL_ENABLE_EXCEPTIONS + bool exception_thrown = false; + try + { + std::wstring test_string = test_address.write_address(); + // should never get here + REQUIRE(test_string.empty()); + } + catch (const wil::ResultException& e) + { + REQUIRE(e.GetErrorCode() == HRESULT_FROM_WIN32(WSAEAFNOSUPPORT)); + exception_thrown = true; + } + catch (...) + { + REQUIRE(false); + } + REQUIRE(exception_thrown); + + exception_thrown = false; + try + { + std::wstring test_string = test_address.write_complete_address(); + // should never get here + REQUIRE(test_string.empty()); + } + catch (const wil::ResultException& e) + { + REQUIRE(e.GetErrorCode() == HRESULT_FROM_WIN32(WSAEINVAL)); + exception_thrown = true; + } + catch (...) + { + REQUIRE(false); + } + REQUIRE(exception_thrown); +#endif + + wil::network::socket_address_string string_address; + REQUIRE(FAILED(test_address.write_address_nothrow(string_address))); + REQUIRE(FAILED(test_address.write_complete_address_nothrow(string_address))); + } + + SECTION("verify write_address_nothrow maximum string size") + { + { + wil::network::socket_address test_mapped_address; + REQUIRE(SUCCEEDED(test_mapped_address.set_sockaddr_nothrow(L"0000:0000:0000:0000:0000:ffff:255.255.255.255"))); + test_mapped_address.set_port(std::numeric_limits::max()); + test_mapped_address.set_scope_id(std::numeric_limits::max()); + test_mapped_address.set_flow_info(std::numeric_limits::max()); + + wil::network::socket_address_wstring test_mapped_address_string; + REQUIRE(SUCCEEDED(test_mapped_address.write_address_nothrow(test_mapped_address_string))); + REQUIRE(SUCCEEDED(test_mapped_address.write_complete_address_nothrow(test_mapped_address_string))); +#ifdef WIL_ENABLE_EXCEPTIONS + std::wstring test_mapped_address_wstring = test_mapped_address.write_complete_address(); + constexpr auto* expected_test_mapped_address_string = L"[::ffff:255.255.255.255%4294967295]:65535"; + REQUIRE(expected_test_mapped_address_string == test_mapped_address_wstring); +#endif + } + + { + wil::network::socket_address test_max_v6_address; + REQUIRE(SUCCEEDED(test_max_v6_address.set_sockaddr_nothrow(L"ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff"))); + test_max_v6_address.set_port(std::numeric_limits::max()); + test_max_v6_address.set_scope_id(std::numeric_limits::max()); + test_max_v6_address.set_flow_info(std::numeric_limits::max()); + + wil::network::socket_address_wstring test_max_v6_address_string; + REQUIRE(SUCCEEDED(test_max_v6_address.write_address_nothrow(test_max_v6_address_string))); + REQUIRE(SUCCEEDED(test_max_v6_address.write_complete_address_nothrow(test_max_v6_address_string))); +#ifdef WIL_ENABLE_EXCEPTIONS + std::wstring test_max_v6_address_wstring = test_max_v6_address.write_complete_address(); + constexpr auto* expected_test_max_v6_address_string = L"[ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff%4294967295]:65535"; + REQUIRE(expected_test_max_v6_address_string == test_max_v6_address_wstring); +#endif + } + } +} + +TEST_CASE("NetworkingTests::Verifying_function_tables", "[networking]") +{ + using wil::network::equals; + + SECTION("verify winsock_extension_function_table") + { + // verify the first 3 function pointers are calling through correctly to confirm the function table is correct + wil::network::winsock_extension_function_table test_table = wil::network::winsock_extension_function_table::load(); + REQUIRE(static_cast(test_table)); + REQUIRE(test_table.f.AcceptEx); + REQUIRE(test_table.f.ConnectEx); + REQUIRE(test_table.f.DisconnectEx); + REQUIRE(test_table.f.GetAcceptExSockaddrs); + REQUIRE(test_table.f.TransmitFile); + REQUIRE(test_table.f.TransmitPackets); + REQUIRE(test_table.f.WSARecvMsg); + REQUIRE(test_table.f.WSASendMsg); + + // create a listening socket and post an AcceptEx on it + wil::unique_socket listeningSocket{::socket(AF_INET6, SOCK_STREAM, IPPROTO_TCP)}; + REQUIRE(listeningSocket.get() != INVALID_SOCKET); + wil::network::socket_address listenAddress{AF_INET6}; + listenAddress.set_address_loopback(); + listenAddress.set_port(TestPort); + + int gle = 0; + auto bind_error = ::bind(listeningSocket.get(), listenAddress.sockaddr(), listenAddress.length()); + if (bind_error != 0) + { + gle = ::WSAGetLastError(); + } + REQUIRE(gle == 0); + REQUIRE(bind_error == 0); + + gle = 0; + auto listen_error = ::listen(listeningSocket.get(), 1); + if (listen_error != 0) + { + gle = ::WSAGetLastError(); + } + REQUIRE(gle == 0); + REQUIRE(listen_error == 0); + + // the buffer to supply to AcceptEx to capture the address information + static constexpr size_t singleAddressOutputBufferSize = listenAddress.length() + 16; + char acceptex_output_buffer[singleAddressOutputBufferSize * 2]{}; + wil::unique_socket acceptSocket{::socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)}; + REQUIRE(acceptSocket.get() != INVALID_SOCKET); + + DWORD acceptex_bytes_received{}; + wil::unique_event_nothrow acceptex_overlapped_event{}; + REQUIRE(SUCCEEDED(acceptex_overlapped_event.create())); + REQUIRE(acceptex_overlapped_event.get() != nullptr); + OVERLAPPED acceptex_overlapped{}; + acceptex_overlapped.hEvent = acceptex_overlapped_event.get(); + + gle = 0; + auto acceptex_return = test_table.f.AcceptEx( + listeningSocket.get(), + acceptSocket.get(), + acceptex_output_buffer, + 0, + singleAddressOutputBufferSize, + singleAddressOutputBufferSize, + &acceptex_bytes_received, + &acceptex_overlapped); + if (!acceptex_return) + { + gle = ::WSAGetLastError(); + } + // should fail with ERROR_IO_PENDING + REQUIRE(!acceptex_return); + REQUIRE(gle == ERROR_IO_PENDING); + // ensure that if this test function fails (returns) before AcceptEx completes asynchronously + // that we wait for this async (overlapped) call to complete + const auto ensure_acceptex_overlapped_completes = wil::scope_exit([&] { + // close the sockets to cancel any pended IO + acceptSocket.reset(); + listeningSocket.reset(); + // now wait for our async call + acceptex_overlapped_event.wait(); + }); + + // now create a socket to connect to it + wil::unique_event_nothrow connectex_overlapped_event{}; + REQUIRE(SUCCEEDED(connectex_overlapped_event.create())); + REQUIRE(connectex_overlapped_event.get() != nullptr); + OVERLAPPED connectex_overlapped{}; + connectex_overlapped.hEvent = connectex_overlapped_event.get(); + + wil::unique_socket connectingSocket{::socket(AF_INET6, SOCK_STREAM, IPPROTO_TCP)}; + REQUIRE(connectingSocket.get() != INVALID_SOCKET); + // ConnectEx requires a bound socket + wil::network::socket_address connecting_from_address{AF_INET6}; + connecting_from_address.set_address_loopback(); + connecting_from_address.set_port(0); // just an ephemeral port, ConnectEx will find a port + + gle = 0; + bind_error = ::bind(connectingSocket.get(), connecting_from_address.sockaddr(), connecting_from_address.length()); + if (bind_error != 0) + { + gle = ::WSAGetLastError(); + } + REQUIRE(gle == 0); + REQUIRE(bind_error == 0); + + gle = 0; + auto connectex_return = test_table.f.ConnectEx( + connectingSocket.get(), listenAddress.sockaddr(), listenAddress.length(), nullptr, 0, nullptr, &connectex_overlapped); + if (!connectex_return) + { + gle = ::WSAGetLastError(); + } + // should fail with ERROR_IO_PENDING + REQUIRE(!connectex_return); + REQUIRE(gle == ERROR_IO_PENDING); + // ensure that if this test function fails (returns) before ConnectEx completes asynchronously + // that we wait for this async (overlapped) call to complete + const auto ensure_connectex_overlapped_completes = wil::scope_exit([&] { + // close the socket to cancel any pended IO + connectingSocket.reset(); + // now wait for our async call + connectex_overlapped_event.wait(); + }); + + // wait for both connect and accept to complete + DWORD transfer_unused{}; + DWORD flags_unused{}; + gle = 0; + auto connectex_overlapped_result = ::WSAGetOverlappedResult( + connectingSocket.get(), + &connectex_overlapped, + &transfer_unused, + TRUE, // should wait for connect to complete + &flags_unused); + if (!connectex_overlapped_result) + { + gle = ::WSAGetLastError(); + } + REQUIRE(gle == 0); + REQUIRE(connectex_overlapped_result == TRUE); + + gle = 0; + auto acceptex_overlapped_result = ::WSAGetOverlappedResult( + acceptSocket.get(), + &acceptex_overlapped, + &transfer_unused, + TRUE, // should wait for connect to complete + &flags_unused); + if (!acceptex_overlapped_result) + { + gle = ::WSAGetLastError(); + } + REQUIRE(gle == 0); + REQUIRE(acceptex_overlapped_result == TRUE); + + // issue a DisconnectEx from the client + wil::unique_event_nothrow disconnectex_overlapped_event{}; + REQUIRE(SUCCEEDED(disconnectex_overlapped_event.create())); + REQUIRE(disconnectex_overlapped_event.get() != nullptr); + OVERLAPPED disconnectex_overlapped{}; + disconnectex_overlapped.hEvent = disconnectex_overlapped_event.get(); + + auto disconnectex_return = test_table.f.DisconnectEx( + connectingSocket.get(), + &disconnectex_overlapped, + 0, // not passing the reuse-socket flag + 0); + if (!disconnectex_return) + { + gle = ::WSAGetLastError(); + } + // should fail with ERROR_IO_PENDING + REQUIRE(!disconnectex_return); + REQUIRE(gle == ERROR_IO_PENDING); + + gle = 0; + auto disconnectex_overlapped_result = ::WSAGetOverlappedResult( + connectingSocket.get(), + &disconnectex_overlapped, + &transfer_unused, + TRUE, // should wait for connect to complete + &flags_unused); + if (!disconnectex_overlapped_result) + { + gle = ::WSAGetLastError(); + } + REQUIRE(gle == 0); + REQUIRE(disconnectex_overlapped_result == TRUE); + } + + SECTION("verify rio_extension_function_table") + { + // verify 2 function pointers are calling through correctly to confirm the function table is correct + wil::network::rio_extension_function_table test_table = wil::network::rio_extension_function_table::load(); + REQUIRE(static_cast(test_table)); + REQUIRE(test_table.f.cbSize > 0); + REQUIRE(test_table.f.RIOReceive); + REQUIRE(test_table.f.RIOReceiveEx); + REQUIRE(test_table.f.RIOSend); + REQUIRE(test_table.f.RIOSendEx); + REQUIRE(test_table.f.RIOCloseCompletionQueue); + REQUIRE(test_table.f.RIOCreateCompletionQueue); + REQUIRE(test_table.f.RIOCreateRequestQueue); + REQUIRE(test_table.f.RIODequeueCompletion); + REQUIRE(test_table.f.RIODeregisterBuffer); + REQUIRE(test_table.f.RIONotify); + REQUIRE(test_table.f.RIORegisterBuffer); + REQUIRE(test_table.f.RIOResizeCompletionQueue); + REQUIRE(test_table.f.RIOResizeRequestQueue); + + wil::unique_event_nothrow rio_completion_notification_event{}; + REQUIRE(SUCCEEDED(rio_completion_notification_event.create())); + REQUIRE(rio_completion_notification_event.get() != nullptr); + + RIO_NOTIFICATION_COMPLETION rio_completion_notification{}; + rio_completion_notification.Type = RIO_EVENT_COMPLETION; + rio_completion_notification.Event.EventHandle = rio_completion_notification_event.get(); + rio_completion_notification.Event.NotifyReset = FALSE; + + int gle = 0; + const RIO_CQ rio_cq = test_table.f.RIOCreateCompletionQueue( + 10, // queue size + &rio_completion_notification); + if (rio_cq == RIO_INVALID_CQ) + { + gle = WSAGetLastError(); + } + REQUIRE(gle == 0); + REQUIRE(rio_cq != RIO_INVALID_CQ); + + test_table.f.RIOCloseCompletionQueue(rio_cq); + } +} + +TEST_CASE("NetworkingTests::Verifying_addr_info", "[networking]") +{ + using wil::network::addr_info_ansi_iterator; + using wil::network::addr_info_iterator; + using wil::network::addr_infoex_iterator; + using wil::network::equals; + + const auto cleanup = wil::network::WSAStartup_nothrow(); + InitTestAddresses(); + + SECTION("verify resolve_local_addresses") + { +#if (defined(WIL_ENABLE_EXCEPTIONS)) + const wil::unique_addrinfo test_addr = wil::network::resolve_local_addresses(); + REQUIRE(addr_info_iterator{test_addr.get()} != addr_info_iterator::end()); + + // verify operator-> + const addr_info_iterator test_addr_iterator{test_addr.get()}; + REQUIRE(!test_addr_iterator->is_address_loopback()); + + uint32_t count = 0; + for (const auto& address : wil::make_range(addr_info_iterator{test_addr.get()}, addr_info_iterator::end())) + { + const auto family = address.family(); + REQUIRE((family == AF_INET || family == AF_INET6)); + REQUIRE(!address.is_address_loopback()); + + wil::network::socket_address_wstring address_string; + REQUIRE(SUCCEEDED(address.write_address_nothrow(address_string))); + wprintf(L"... resolve_local_addresses : %ws\n", address_string); + ++count; + } + + REQUIRE(count > 0); +#endif + } + + SECTION("verify resolve_localhost_addresses") + { +#if (defined(WIL_ENABLE_EXCEPTIONS)) + const wil::unique_addrinfo test_addr = wil::network::resolve_localhost_addresses(); + REQUIRE(addr_info_iterator{test_addr.get()} != addr_info_iterator::end()); + + // verify operator-> + const addr_info_iterator test_addr_iterator{test_addr.get()}; + REQUIRE(test_addr_iterator->is_address_loopback()); + + uint32_t count = 0; + for (const auto& address : wil::make_range(addr_info_iterator{test_addr.get()}, addr_info_iterator::end())) + { + const auto family = address.family(); + REQUIRE((family == AF_INET || family == AF_INET6)); + REQUIRE(address.is_address_loopback()); + + switch (address.family()) + { + case AF_INET: + REQUIRE(equals(*address.in_addr(), Test_loopback_in_addr)); + break; + + case AF_INET6: + REQUIRE(equals(*address.in6_addr(), Test_loopback_in6_addr)); + break; + + default: + REQUIRE(false); + } + + wil::network::socket_address_wstring address_string; + REQUIRE(SUCCEEDED(address.write_address_nothrow(address_string))); + wprintf(L"... resolve_localhost_addresses : %ws\n", address_string); + ++count; + } + + REQUIRE(count > 0); +#endif + } + + SECTION("verify const addr_info_iterator") + { +#if (defined(WIL_ENABLE_EXCEPTIONS) && defined(_STRING_)) + const wil::unique_addrinfo test_addr = wil::network::resolve_localhost_addresses(); + REQUIRE(addr_info_iterator{test_addr.get()} != addr_info_iterator::end()); + + const addr_info_iterator test_addr_iterator{test_addr.get()}; + REQUIRE(test_addr_iterator->is_address_loopback()); + + const auto& test_address_reference = *test_addr_iterator; + REQUIRE(test_address_reference.is_address_loopback()); +#endif + } + + SECTION("verify addr_info_iterator increment") + { +#if (defined(WIL_ENABLE_EXCEPTIONS) && defined(_STRING_)) + const wil::unique_addrinfo initial_addr = wil::network::resolve_localhost_addresses(); + REQUIRE(addr_info_iterator{initial_addr.get()} != addr_info_iterator::end()); + + auto total_count = 0; + for (auto it = addr_info_iterator{initial_addr.get()}; it != addr_info_iterator::end(); ++it) + { + ++total_count; + } + + const wil::unique_addrinfo test_addr = wil::network::resolve_localhost_addresses(); + REQUIRE(addr_info_iterator{test_addr.get()} != addr_info_iterator::end()); + + addr_info_iterator test_iterator = addr_info_iterator{test_addr.get()}; + test_iterator += total_count; + REQUIRE(test_iterator == addr_info_iterator::end()); +#endif + } + + SECTION("verify addr_info_iterator move behavior") + { +#if (defined(WIL_ENABLE_EXCEPTIONS)) + wil::unique_addrinfo moved_from_addr = wil::network::resolve_local_addresses(); + REQUIRE(addr_info_iterator{moved_from_addr.get()} != addr_info_iterator::end()); + + const wil::unique_addrinfo moved_to_addr = std::move(moved_from_addr); + // moved_from_addr should be end() now + REQUIRE(addr_info_iterator{moved_from_addr.get()} == addr_info_iterator::end()); + REQUIRE(addr_info_iterator{moved_to_addr.get()} != addr_info_iterator::end()); + + for (const auto& address : wil::make_range(addr_info_iterator{moved_to_addr.get()}, addr_info_iterator::end())) + { + const auto family = address.family(); + REQUIRE((family == AF_INET || family == AF_INET6)); + REQUIRE(!address.is_address_loopback()); + + wil::network::socket_address_wstring address_string; + REQUIRE(SUCCEEDED(address.write_address_nothrow(address_string))); + wprintf(L"... moved resolve_local_addresses : %ws\n", address_string); + } +#endif + } + + SECTION("verify addr_info_iterator move assignment behavior") + { +#if (defined(WIL_ENABLE_EXCEPTIONS)) + wil::unique_addrinfo moved_from_addr = wil::network::resolve_local_addresses(); + REQUIRE(addr_info_iterator{moved_from_addr.get()} != addr_info_iterator::end()); + + wil::unique_addrinfo moved_to_addr{wil::network::resolve_local_addresses()}; + moved_to_addr = std::move(moved_from_addr); + + // moved_from_addr should be end() now + REQUIRE(addr_info_iterator{moved_from_addr.get()} == addr_info_iterator::end()); + REQUIRE(addr_info_iterator{moved_to_addr.get()} != addr_info_iterator::end()); + + // move to self + moved_to_addr = std::move(moved_to_addr); + REQUIRE(addr_info_iterator{moved_to_addr.get()} != addr_info_iterator::end()); + + for (const auto& address : wil::make_range(addr_info_iterator{moved_to_addr.get()}, addr_info_iterator::end())) + { + const auto family = address.family(); + REQUIRE((family == AF_INET || family == AF_INET6)); + REQUIRE(!address.is_address_loopback()); + + wil::network::socket_address_wstring address_string; + REQUIRE(SUCCEEDED(address.write_address_nothrow(address_string))); + wprintf(L"... move assignment resolve_local_addresses : %ws\n", address_string); + } +#endif + } + + // retest with unique_addrinfo_ansi + SECTION("verify addr_info_ansi_iterator increment") + { + wil::unique_addrinfo_ansi initial_addr; + REQUIRE(0 == getaddrinfo("localhost", nullptr, nullptr, initial_addr.addressof())); + REQUIRE(addr_info_ansi_iterator{initial_addr.get()} != addr_info_ansi_iterator::end()); + + auto total_count = 0; + for (auto it = addr_info_ansi_iterator{initial_addr.get()}; it != addr_info_ansi_iterator::end(); ++it) + { + ++total_count; + } + + wil::unique_addrinfo_ansi test_addr; + REQUIRE(0 == getaddrinfo("localhost", nullptr, nullptr, test_addr.addressof())); + REQUIRE(addr_info_ansi_iterator{test_addr.get()} != addr_info_ansi_iterator::end()); + + addr_info_ansi_iterator test_iterator = addr_info_ansi_iterator{test_addr.get()}; + test_iterator += total_count; + REQUIRE(test_iterator == addr_info_ansi_iterator::end()); + } + + SECTION("verify addr_info_ansi_iterator move behavior") + { + wil::unique_addrinfo_ansi moved_from_addr; + REQUIRE(0 == getaddrinfo("", nullptr, nullptr, moved_from_addr.addressof())); + REQUIRE(addr_info_ansi_iterator{moved_from_addr.get()} != addr_info_ansi_iterator::end()); + + const wil::unique_addrinfo_ansi moved_to_addr = std::move(moved_from_addr); + // moved_from_addr should be end() now + REQUIRE(addr_info_ansi_iterator{moved_from_addr.get()} == addr_info_ansi_iterator::end()); + REQUIRE(addr_info_ansi_iterator{moved_to_addr.get()} != addr_info_ansi_iterator::end()); + + for (const auto& address : wil::make_range(addr_info_ansi_iterator{moved_to_addr.get()}, addr_info_ansi_iterator::end())) + { + const auto family = address.family(); + REQUIRE((family == AF_INET || family == AF_INET6)); + REQUIRE(!address.is_address_loopback()); + + wil::network::socket_address_wstring address_string; + REQUIRE(SUCCEEDED(address.write_address_nothrow(address_string))); + wprintf(L"... moved getaddrinfo(unique_addrinfo_ansi) : %ws\n", address_string); + } + } + + SECTION("verify addr_info_ansi_iterator move assignment behavior") + { + wil::unique_addrinfo_ansi moved_from_addr; + REQUIRE(0 == getaddrinfo("", nullptr, nullptr, moved_from_addr.addressof())); + REQUIRE(addr_info_ansi_iterator{moved_from_addr.get()} != addr_info_ansi_iterator::end()); + + wil::unique_addrinfo_ansi moved_to_addr; + REQUIRE(0 == getaddrinfo("", nullptr, nullptr, moved_to_addr.addressof())); + moved_to_addr = std::move(moved_from_addr); + + // moved_from_addr should be end() now + REQUIRE(addr_info_ansi_iterator{moved_from_addr.get()} == addr_info_ansi_iterator::end()); + REQUIRE(addr_info_ansi_iterator{moved_to_addr.get()} != addr_info_ansi_iterator::end()); + + // move to self + moved_to_addr = std::move(moved_to_addr); + REQUIRE(addr_info_ansi_iterator{moved_to_addr.get()} != addr_info_ansi_iterator::end()); + + for (const auto& address : wil::make_range(addr_info_ansi_iterator{moved_to_addr.get()}, addr_info_ansi_iterator::end())) + { + const auto family = address.family(); + REQUIRE((family == AF_INET || family == AF_INET6)); + REQUIRE(!address.is_address_loopback()); + + wil::network::socket_address_wstring address_string; + REQUIRE(SUCCEEDED(address.write_address_nothrow(address_string))); + wprintf(L"... move assignment getaddrinfo(unique_addrinfo_ansi) : %ws\n", address_string); + } + } + + // retest with unique_addrinfoex + SECTION("verify addr_info_ansi_iterator increment") + { + wil::unique_addrinfoex initial_addr; + REQUIRE(0 == GetAddrInfoExW(L"localhost", nullptr, NS_ALL, nullptr, nullptr, initial_addr.addressof(), nullptr, nullptr, nullptr, nullptr)); + REQUIRE(addr_infoex_iterator{initial_addr.get()} != addr_infoex_iterator::end()); + + auto total_count = 0; + for (auto it = addr_infoex_iterator{initial_addr.get()}; it != addr_infoex_iterator::end(); ++it) + { + ++total_count; + } + + wil::unique_addrinfoex test_addr; + REQUIRE(0 == GetAddrInfoExW(L"localhost", nullptr, NS_ALL, nullptr, nullptr, test_addr.addressof(), nullptr, nullptr, nullptr, nullptr)); + REQUIRE(addr_infoex_iterator{test_addr.get()} != addr_infoex_iterator::end()); + + addr_infoex_iterator test_iterator = addr_infoex_iterator{test_addr.get()}; + test_iterator += total_count; + REQUIRE(test_iterator == addr_infoex_iterator::end()); + } + + SECTION("verify addr_infoex_iterator move behavior") + { + wil::unique_addrinfoex moved_from_addr; + REQUIRE(0 == GetAddrInfoExW(L"", nullptr, NS_ALL, nullptr, nullptr, moved_from_addr.addressof(), nullptr, nullptr, nullptr, nullptr)); + REQUIRE(addr_infoex_iterator{moved_from_addr.get()} != addr_infoex_iterator::end()); + + const wil::unique_addrinfoex moved_to_addr = std::move(moved_from_addr); + // moved_from_addr should be end() now + REQUIRE(addr_infoex_iterator{moved_from_addr.get()} == addr_infoex_iterator::end()); + REQUIRE(addr_infoex_iterator{moved_to_addr.get()} != addr_infoex_iterator::end()); + + for (const auto& address : wil::make_range(addr_infoex_iterator{moved_to_addr.get()}, addr_infoex_iterator::end())) + { + const auto family = address.family(); + REQUIRE((family == AF_INET || family == AF_INET6)); + REQUIRE(!address.is_address_loopback()); + + wil::network::socket_address_wstring address_string; + REQUIRE(SUCCEEDED(address.write_address_nothrow(address_string))); + wprintf(L"... moved GetAddrInfoExW(unique_addrinfoex) : %ws\n", address_string); + } + } + + SECTION("verify addr_infoex_iterator move assignment behavior") + { + wil::unique_addrinfoex moved_from_addr; + REQUIRE(0 == GetAddrInfoExW(L"", nullptr, NS_ALL, nullptr, nullptr, moved_from_addr.addressof(), nullptr, nullptr, nullptr, nullptr)); + REQUIRE(addr_infoex_iterator{moved_from_addr.get()} != addr_infoex_iterator::end()); + + wil::unique_addrinfoex moved_to_addr; + REQUIRE(0 == GetAddrInfoExW(L"", nullptr, NS_ALL, nullptr, nullptr, moved_to_addr.addressof(), nullptr, nullptr, nullptr, nullptr)); + moved_to_addr = std::move(moved_from_addr); + + // moved_from_addr should be end() now + REQUIRE(addr_infoex_iterator{moved_from_addr.get()} == addr_infoex_iterator::end()); + REQUIRE(addr_infoex_iterator{moved_to_addr.get()} != addr_infoex_iterator::end()); + + // move to self + moved_to_addr = std::move(moved_to_addr); + REQUIRE(addr_infoex_iterator{moved_to_addr.get()} != addr_infoex_iterator::end()); + + for (const auto& address : wil::make_range(addr_infoex_iterator{moved_to_addr.get()}, addr_infoex_iterator::end())) + { + const auto family = address.family(); + REQUIRE((family == AF_INET || family == AF_INET6)); + REQUIRE(!address.is_address_loopback()); + + wil::network::socket_address_wstring address_string; + REQUIRE(SUCCEEDED(address.write_address_nothrow(address_string))); + wprintf(L"... move assignment GetAddrInfoExW(unique_addrinfoex) : %ws\n", address_string); + } + } +} \ No newline at end of file diff --git a/tests/cpplatest/CMakeLists.txt b/tests/cpplatest/CMakeLists.txt index 060881691..e16f661b5 100644 --- a/tests/cpplatest/CMakeLists.txt +++ b/tests/cpplatest/CMakeLists.txt @@ -26,11 +26,12 @@ endif() target_sources(witest.cpplatest PRIVATE ${COMMON_SOURCES} ${COROUTINE_SOURCES} - ${CMAKE_CURRENT_SOURCE_DIR}/../RegistryTests.cpp ${CMAKE_CURRENT_SOURCE_DIR}/../CoroutineTests.cpp ${CMAKE_CURRENT_SOURCE_DIR}/../CppWinRTTests.cpp ${CMAKE_CURRENT_SOURCE_DIR}/../CppWinRT20Tests.cpp ${CMAKE_CURRENT_SOURCE_DIR}/../CppWinRTAuthoringTests.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/../NetworkingTests.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/../RegistryTests.cpp ${CMAKE_CURRENT_SOURCE_DIR}/../StlTests.cpp ${CMAKE_CURRENT_SOURCE_DIR}/../TokenHelpersTests.cpp ${CMAKE_CURRENT_SOURCE_DIR}/../UniqueWinRTEventTokenTests.cpp diff --git a/tests/noexcept/CMakeLists.txt b/tests/noexcept/CMakeLists.txt index b68b44677..6ff689cce 100644 --- a/tests/noexcept/CMakeLists.txt +++ b/tests/noexcept/CMakeLists.txt @@ -22,6 +22,7 @@ endif() target_sources(witest.noexcept PRIVATE ${COMMON_SOURCES} + ${CMAKE_CURRENT_SOURCE_DIR}/../NetworkingTests.cpp ${CMAKE_CURRENT_SOURCE_DIR}/../RegistryTests.cpp ${CMAKE_CURRENT_SOURCE_DIR}/../TokenHelpersTests.cpp ${CMAKE_CURRENT_SOURCE_DIR}/../UniqueWinRTEventTokenTests.cpp diff --git a/tests/normal/CMakeLists.txt b/tests/normal/CMakeLists.txt index 7b651c2f4..6164e3cfc 100644 --- a/tests/normal/CMakeLists.txt +++ b/tests/normal/CMakeLists.txt @@ -5,6 +5,7 @@ target_precompile_headers(witest PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/../pch.h) target_sources(witest PRIVATE ${COMMON_SOURCES} + ${CMAKE_CURRENT_SOURCE_DIR}/../NetworkingTests.cpp ${CMAKE_CURRENT_SOURCE_DIR}/../RegistryTests.cpp ${CMAKE_CURRENT_SOURCE_DIR}/../StlTests.cpp ${CMAKE_CURRENT_SOURCE_DIR}/../TokenHelpersTests.cpp diff --git a/tests/win7/CMakeLists.txt b/tests/win7/CMakeLists.txt index 15445f9f7..736cec8b5 100644 --- a/tests/win7/CMakeLists.txt +++ b/tests/win7/CMakeLists.txt @@ -9,6 +9,7 @@ target_compile_definitions(witest.win7 PRIVATE target_sources(witest.win7 PRIVATE ${COMMON_SOURCES} + ${CMAKE_CURRENT_SOURCE_DIR}/../NetworkingTests.cpp ${CMAKE_CURRENT_SOURCE_DIR}/../RegistryTests.cpp ${CMAKE_CURRENT_SOURCE_DIR}/../StlTests.cpp ${CMAKE_CURRENT_SOURCE_DIR}/../TokenHelpersTests.cpp