diff --git a/build/scripts/modules/network.lua b/build/scripts/modules/network.lua new file mode 100644 index 000000000..8cf8b068a --- /dev/null +++ b/build/scripts/modules/network.lua @@ -0,0 +1,19 @@ +MODULE.Name = "Network" + +MODULE.Libraries = { + "NazaraCore" +} + +MODULE.OsFiles.Windows = { + "../src/Nazara/Network/Win32/**.hpp", + "../src/Nazara/Network/Win32/**.cpp" +} + +MODULE.OsFiles.Posix = { + "../src/Nazara/Network/Posix/**.hpp", + "../src/Nazara/Network/Posix/**.cpp" +} + +MODULE.OsLibraries.Windows = { + "ws2_32" +} diff --git a/include/Nazara/Network.hpp b/include/Nazara/Network.hpp new file mode 100644 index 000000000..fe2948399 --- /dev/null +++ b/include/Nazara/Network.hpp @@ -0,0 +1,44 @@ +// This file was automatically generated on 09 Nov 2015 at 13:52:47 + +/* + Nazara Engine - Network module + + Copyright (C) 2015 J茅r么me "Lynix" Leclercq (Lynix680@gmail.com) + + Permission is hereby granted, free of charge, to any person obtaining a copy of + this software and associated documentation files (the "Software"), to deal in + the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do + so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + + 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. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. +*/ + +#pragma once + +#ifndef NAZARA_GLOBAL_NETWORK_HPP +#define NAZARA_GLOBAL_NETWORK_HPP + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#endif // NAZARA_GLOBAL_NETWORK_HPP diff --git a/include/Nazara/Network/AbstractSocket.hpp b/include/Nazara/Network/AbstractSocket.hpp new file mode 100644 index 000000000..8c9fa5df5 --- /dev/null +++ b/include/Nazara/Network/AbstractSocket.hpp @@ -0,0 +1,63 @@ +// Copyright (C) 2015 J閞鬽e Leclercq +// This file is part of the "Nazara Engine - Network module" +// For conditions of distribution and use, see copyright notice in Config.hpp + +#pragma once + +#ifndef NAZARA_ABSTRACTSOCKET_HPP +#define NAZARA_ABSTRACTSOCKET_HPP + +#include +#include +#include +#include +#include + +namespace Nz +{ + class NAZARA_NETWORK_API AbstractSocket + { + public: + AbstractSocket(const AbstractSocket&) = delete; + AbstractSocket(AbstractSocket&& abstractSocket); + virtual ~AbstractSocket(); + + void Close(); + + void EnableBlocking(bool blocking); + + inline SocketError GetLastError() const; + inline SocketHandle GetNativeHandle() const; + inline SocketState GetState() const; + inline SocketType GetType() const; + + inline bool IsBlockingEnabled() const; + + unsigned int QueryAvailableBytes() const; + + // Slots + NazaraSignal(OnStateChange, const AbstractSocket* /*socket*/, SocketState /*oldState*/, SocketState /*newState*/); + + protected: + AbstractSocket(SocketType type); + + inline void ChangeState(SocketState newState); + + virtual void OnClose(); + virtual void OnOpened(); + + bool Open(NetProtocol protocol); + void Open(SocketHandle existingHandle); + + NetProtocol m_protocol; + SocketError m_lastError; + SocketHandle m_handle; + SocketState m_state; + SocketType m_type; + bool m_isBlockingEnabled; + }; +} + +#include + +#endif // NAZARA_ABSTRACTSOCKET_HPP \ No newline at end of file diff --git a/include/Nazara/Network/AbstractSocket.inl b/include/Nazara/Network/AbstractSocket.inl new file mode 100644 index 000000000..cf75726b0 --- /dev/null +++ b/include/Nazara/Network/AbstractSocket.inl @@ -0,0 +1,45 @@ +// Copyright (C) 2015 J閞鬽e Leclercq +// This file is part of the "Nazara Engine - Network module" +// For conditions of distribution and use, see copyright notice in Config.hpp + +#include + +namespace Nz +{ + inline SocketError AbstractSocket::GetLastError() const + { + return m_lastError; + } + + inline SocketHandle AbstractSocket::GetNativeHandle() const + { + return m_handle; + } + + inline SocketState AbstractSocket::GetState() const + { + return m_state; + } + + inline SocketType AbstractSocket::GetType() const + { + return m_type; + } + + inline bool AbstractSocket::IsBlockingEnabled() const + { + return m_isBlockingEnabled; + } + + inline void AbstractSocket::ChangeState(SocketState newState) + { + if (m_state != newState) + { + SocketState oldState = m_state; + m_state = newState; + OnStateChange(this, oldState, m_state); + } + } +} + +#include diff --git a/include/Nazara/Network/Algorithm.hpp b/include/Nazara/Network/Algorithm.hpp new file mode 100644 index 000000000..554f67d37 --- /dev/null +++ b/include/Nazara/Network/Algorithm.hpp @@ -0,0 +1,21 @@ +// Copyright (C) 2015 J茅r么me Leclercq +// This file is part of the "Nazara Engine - Network module" +// For conditions of distribution and use, see copyright notice in Config.hpp + +#pragma once + +#ifndef NAZARA_ALGORITHM_NETWORK_HPP +#define NAZARA_ALGORITHM_NETWORK_HPP + +#include +#include +#include + +namespace Nz +{ + bool ParseIPAddress(const char* addressPtr, UInt8 result[16], UInt16* port = nullptr, bool* isIPv6 = nullptr, const char** endOfRead = nullptr); +} + +#include + +#endif // NAZARA_ALGORITHM_NETWORK_HPP diff --git a/include/Nazara/Network/Algorithm.inl b/include/Nazara/Network/Algorithm.inl new file mode 100644 index 000000000..ecf609987 --- /dev/null +++ b/include/Nazara/Network/Algorithm.inl @@ -0,0 +1,7 @@ +// Copyright (C) 2015 J茅r么me Leclercq +// This file is part of the "Nazara Engine - Core module" +// For conditions of distribution and use, see copyright notice in Config.hpp + +#include + +#include diff --git a/include/Nazara/Network/Config.hpp b/include/Nazara/Network/Config.hpp new file mode 100644 index 000000000..727108061 --- /dev/null +++ b/include/Nazara/Network/Config.hpp @@ -0,0 +1,53 @@ +/* + Nazara Engine - Network module + + Copyright (C) 2015 J茅r么me "Lynix" Leclercq (Lynix680@gmail.com) + + Permission is hereby granted, free of charge, to any person obtaining a copy of + this software and associated documentation files (the "Software"), to deal in + the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do + so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + + 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. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. +*/ + +#pragma once + +#ifndef NAZARA_CONFIG_NETWORK_HPP +#define NAZARA_CONFIG_NETWORK_HPP + +/// Chaque modification d'un param猫tre du module n茅cessite une recompilation de celui-ci + +// Utilise le MemoryManager pour g茅rer les allocations dynamiques (d茅tecte les leaks au prix d'allocations/lib茅rations dynamiques plus lentes) +#define NAZARA_NETWORK_MANAGE_MEMORY 0 + +// Active les tests de s茅curit茅 bas茅s sur le code (Conseill茅 pour le d茅veloppement) +#define NAZARA_NETWORK_SAFE 1 + +/// Chaque modification d'un param猫tre ci-dessous implique une modification (souvent mineure) du code + +/// V茅rification des valeurs et types de certaines constantes +#include + +#if defined(NAZARA_STATIC) + #define NAZARA_NETWORK_API +#else + #ifdef NAZARA_NETWORK_BUILD + #define NAZARA_NETWORK_API NAZARA_EXPORT + #else + #define NAZARA_NETWORK_API NAZARA_IMPORT + #endif +#endif + +#endif // NAZARA_CONFIG_NETWORK_HPP diff --git a/include/Nazara/Network/ConfigCheck.hpp b/include/Nazara/Network/ConfigCheck.hpp new file mode 100644 index 000000000..73513ebc9 --- /dev/null +++ b/include/Nazara/Network/ConfigCheck.hpp @@ -0,0 +1,20 @@ +// Copyright (C) 2015 J茅r么me Leclercq +// This file is part of the "Nazara Engine - Network module" +// For conditions of distribution and use, see copyright notice in Config.hpp + +#pragma once + +#ifndef NAZARA_CONFIG_CHECK_NETWORK_HPP +#define NAZARA_CONFIG_CHECK_NETWORK_HPP + +/// Ce fichier sert 脿 v茅rifier la valeur des constantes du fichier Config.hpp + +#include + +// On force la valeur de MANAGE_MEMORY en mode debug +#if defined(NAZARA_DEBUG) && !NAZARA_NETWORK_MANAGE_MEMORY + #undef NAZARA_NETWORK_MANAGE_MEMORY + #define NAZARA_NETWORK_MANAGE_MEMORY 0 +#endif + +#endif // NAZARA_CONFIG_CHECK_NETWORK_HPP diff --git a/include/Nazara/Network/Debug.hpp b/include/Nazara/Network/Debug.hpp new file mode 100644 index 000000000..458bcf548 --- /dev/null +++ b/include/Nazara/Network/Debug.hpp @@ -0,0 +1,8 @@ +// Copyright (C) 2015 J茅r么me Leclercq +// This file is part of the "Nazara Engine - Network module" +// For conditions of distribution and use, see copyright notice in Config.hpp + +#include +#if NAZARA_MODULENAME_MANAGE_MEMORY + #include +#endif diff --git a/include/Nazara/Network/DebugOff.hpp b/include/Nazara/Network/DebugOff.hpp new file mode 100644 index 000000000..9ae8c8e69 --- /dev/null +++ b/include/Nazara/Network/DebugOff.hpp @@ -0,0 +1,9 @@ +// Copyright (C) 2015 J茅r么me Leclercq +// This file is part of the "Nazara Engine - Network module" +// For conditions of distribution and use, see copyright notice in Config.hpp + +// On suppose que Debug.hpp a d茅j脿 茅t茅 inclus, tout comme Config.hpp +#if NAZARA_MODULENAME_MANAGE_MEMORY + #undef delete + #undef new +#endif diff --git a/include/Nazara/Network/Enums.hpp b/include/Nazara/Network/Enums.hpp new file mode 100644 index 000000000..b2184ab64 --- /dev/null +++ b/include/Nazara/Network/Enums.hpp @@ -0,0 +1,61 @@ +// Copyright (C) 2015 J茅r么me Leclercq +// This file is part of the "Nazara Engine - Network module" +// For conditions of distribution and use, see copyright notice in Config.hpp + +#pragma once + +#ifndef NAZARA_ENUMS_NETWORK_HPP +#define NAZARA_ENUMS_NETWORK_HPP + +namespace Nz +{ + enum NetProtocol + { + NetProtocol_Any, + NetProtocol_IPv4, + NetProtocol_IPv6, + + NetProtocol_Max = NetProtocol_IPv6 + }; + + enum SocketError + { + SocketError_NoError, + + SocketError_AddressNotAvailable, //< The address is already in use (when binding/listening) + SocketError_ConnectionClosed, //< The connection has been closed + SocketError_ConnectionRefused, //< The connection attempt was refused + SocketError_DatagramSize, //< The datagram size is over the system limit + SocketError_Internal, //< The error is coming from the engine + SocketError_NetworkError, //< The network system has failed (maybe network is down) + SocketError_NotInitialized, //< Nazara network has not been initialized + SocketError_NotSupported, //< The operation is not supported (e.g. creating a bluetooth socket on a system without any bluetooth adaptater) + SocketError_ResourceError, //< The operating system lacks the resources to proceed (e.g. memory/socket descriptor) + SocketError_UnreachableHost, //< The host is not reachable + SocketError_TimedOut, //< The operation timed out + SocketError_Unknown, //< The last operation failed with an unlisted error code + + SocketError_Max = SocketError_Unknown + }; + + enum SocketState + { + SocketState_Bound, //< The socket is currently bound + SocketState_Connecting, //< The socket is currently connecting + SocketState_Connected, //< The socket is currently connected + SocketState_NotConnected, //< The socket is not connected (or has been disconnected) + + SocketState_Max = SocketState_NotConnected + }; + + enum SocketType + { + SocketType_Raw, + SocketType_TCP, + SocketType_UDP, + + SocketType_Max = SocketType_UDP + }; +} + +#endif // NAZARA_ENUMS_NETWORK_HPP diff --git a/include/Nazara/Network/IpAddress.hpp b/include/Nazara/Network/IpAddress.hpp new file mode 100644 index 000000000..b426f1c16 --- /dev/null +++ b/include/Nazara/Network/IpAddress.hpp @@ -0,0 +1,102 @@ +// Copyright (C) 2015 J茅r么me Leclercq +// This file is part of the "Nazara Engine - Network module" +// For conditions of distribution and use, see copyright notice in Config.hpp + +#pragma once + +#ifndef NAZARA_IPADDRESS_HPP +#define NAZARA_IPADDRESS_HPP + +#include +#include +#include +#include +#include +#include + +namespace Nz +{ + struct HostnameInfo; + + class NAZARA_NETWORK_API IpAddress + { + public: + using IPv4 = std::array; //< four 8bits blocks + using IPv6 = std::array; //< eight 16bits blocks + + inline IpAddress(); + inline IpAddress(const IPv4& ip, UInt16 port = 0); + inline IpAddress(const IPv6& ip, UInt16 port = 0); + inline IpAddress(const UInt8& a, const UInt8& b, const UInt8& c, const UInt8& d, UInt16 port = 0); + inline IpAddress(const UInt16& a, const UInt16& b, const UInt16& c, const UInt16& d, const UInt16& e, const UInt16& f, const UInt16& g, const UInt16& h, UInt16 port = 0); + inline IpAddress(const char* address); + inline IpAddress(const String& address); + IpAddress(const IpAddress&) = default; + IpAddress(IpAddress&&) = default; + ~IpAddress() = default; + + bool BuildFromAddress(const char* address); + + inline UInt16 GetPort() const; + inline NetProtocol GetProtocol() const; + + bool IsLoopback() const; + inline bool IsValid() const; + + inline void SetPort(UInt16 port); + + inline IPv4 ToIPv4() const; + inline IPv6 ToIPv6() const; + String ToString() const; + inline UInt32 ToUInt32() const; + + inline operator bool() const; + + IpAddress& operator=(const IpAddress&) = default; + IpAddress& operator=(IpAddress&&) = default; + + static String ResolveAddress(const IpAddress& address, String* service = nullptr); + static std::vector ResolveHostname(NetProtocol procol, const String& hostname, const String& protocol = "http"); + + inline friend std::ostream& operator<<(std::ostream& out, const IpAddress& address); + + inline friend bool operator==(const IpAddress& first, const IpAddress& second); + inline friend bool operator!=(const IpAddress& first, const IpAddress& second); + inline friend bool operator<(const IpAddress& first, const IpAddress& second); + inline friend bool operator<=(const IpAddress& first, const IpAddress& second); + inline friend bool operator>(const IpAddress& first, const IpAddress& second); + inline friend bool operator>=(const IpAddress& first, const IpAddress& second); + + static IpAddress AnyIpV4; + static IpAddress AnyIpV6; + static IpAddress BroadcastIpV4; + static IpAddress Invalid; + static IpAddress LoopbackIpV4; + static IpAddress LoopbackIpV6; + + private: + union + { + IPv4 m_ipv4; + IPv6 m_ipv6; + }; + + NetProtocol m_protocol; + UInt16 m_port; + bool m_isValid; + }; + + struct HostnameInfo + { + IpAddress address; + String canonicalName; + int flags; + int family; + int socketType; + }; + +} + +#include + +#endif // NAZARA_IPADDRESS_HPP diff --git a/include/Nazara/Network/IpAddress.inl b/include/Nazara/Network/IpAddress.inl new file mode 100644 index 000000000..f12361081 --- /dev/null +++ b/include/Nazara/Network/IpAddress.inl @@ -0,0 +1,205 @@ +// Copyright (C) 2015 J茅r么me Leclercq +// This file is part of the "Nazara Engine - Network module" +// For conditions of distribution and use, see copyright notice in Config.hpp + +#include +#include + +namespace Nz +{ + inline IpAddress::IpAddress() : + m_isValid(false) + { + } + + inline IpAddress::IpAddress(const IPv4& ip, UInt16 port) : + m_isValid(true), + m_ipv4(ip), + m_protocol(NetProtocol_IPv4), + m_port(port) + { + } + + inline IpAddress::IpAddress(const IPv6& ip, UInt16 port) : + m_isValid(true), + m_ipv6(ip), + m_protocol(NetProtocol_IPv6), + m_port(port) + { + } + + inline IpAddress::IpAddress(const UInt8& a, const UInt8& b, const UInt8& c, const UInt8& d, UInt16 port) : + IpAddress(IPv4{a, b, c, d}, port) + { + } + + inline IpAddress::IpAddress(const UInt16& a, const UInt16& b, const UInt16& c, const UInt16& d, const UInt16& e, const UInt16& f, const UInt16& g, const UInt16& h, UInt16 port) : + IpAddress(IPv6{a, b, c, d, e, f, g, h}, port) + { + } + + inline IpAddress::IpAddress(const char* address) + { + BuildFromAddress(address); + } + + inline IpAddress::IpAddress(const String& address) + { + BuildFromAddress(address.GetConstBuffer()); + } + + inline UInt16 IpAddress::GetPort() const + { + return m_port; + } + + inline NetProtocol IpAddress::GetProtocol() const + { + return m_protocol; + } + + inline bool IpAddress::IsValid() const + { + return m_isValid; + } + + inline void IpAddress::SetPort(UInt16 port) + { + m_port = port; + } + + inline IpAddress::IPv4 IpAddress::ToIPv4() const + { + NazaraAssert(m_isValid && m_protocol == NetProtocol_IPv4, "Address is not a valid IPv4"); + + return m_ipv4; + } + + inline IpAddress::IPv6 IpAddress::ToIPv6() const + { + NazaraAssert(m_isValid && m_protocol == NetProtocol_IPv6, "IP is not a valid IPv6"); + + return m_ipv6; + } + + inline UInt32 IpAddress::ToUInt32() const + { + NazaraAssert(m_isValid && m_protocol == NetProtocol_IPv4, "Address is not a valid IPv4"); + + return UInt32(m_ipv4[0]) << 24 | + UInt32(m_ipv4[1]) << 16 | + UInt32(m_ipv4[2]) << 8 | + UInt32(m_ipv4[3]) << 0; + } + + inline IpAddress::operator bool() const + { + return IsValid(); + } + + inline std::ostream& operator<<(std::ostream& out, const IpAddress& address) + { + out << "IpAddress(" << address.ToString() << ')'; + return out; + } + + inline bool operator==(const IpAddress& first, const IpAddress& second) + { + // We need to check the validity of each address before comparing them + if (!first.m_isValid || !second.m_isValid) + return first.m_isValid == second.m_isValid; + + // Then the protocol + if (first.m_protocol != second.m_protocol) + return false; + + // Each protocol has its variables to compare + switch (first.m_protocol) + { + case NetProtocol_IPv4: + { + if (first.m_ipv4 != second.m_ipv4) + return false; + + break; + } + + case NetProtocol_IPv6: + { + if (first.m_ipv6 != second.m_ipv6) + return false; + + break; + } + } + + // Check the port, in case there is one + if (first.m_port != second.m_port) + return false; + + return true; + } + + inline bool operator!=(const IpAddress& first, const IpAddress& second) + { + return !operator==(first, second); + } + + inline bool operator<(const IpAddress& first, const IpAddress& second) + { + // If the second address is invalid, there's no way we're lower than it + if (!second.m_isValid) + return false; + + // By this point, the second address is valid, thus check our validity + if (!first.m_isValid) + return true; // Invalid address are lower than valid one + + // Compare protocols + if (first.m_protocol != second.m_protocol) + return first.m_protocol < second.m_protocol; + + // Compare IP (thanks to std::array comparison operator) + switch (first.m_protocol) + { + case NetProtocol_IPv4: + { + if (first.m_ipv4 != second.m_ipv4) + return first.m_ipv4 < second.m_ipv4; + + break; + } + + case NetProtocol_IPv6: + { + if (first.m_ipv6 != second.m_ipv6) + return first.m_ipv6 < second.m_ipv6; + + break; + } + } + + // Compare port + if (first.m_port != second.m_port) + return first.m_port < second.m_port; + + return false; //< Same address + } + + inline bool operator<=(const IpAddress& first, const IpAddress& second) + { + return !operator<(second, first); + } + + inline bool operator>(const IpAddress& first, const IpAddress& second) + { + return second < first; + } + + inline bool operator>=(const IpAddress& first, const IpAddress& second) + { + return !operator<(first, second); + } +} + +#include diff --git a/include/Nazara/Network/Network.hpp b/include/Nazara/Network/Network.hpp new file mode 100644 index 000000000..93b0e921f --- /dev/null +++ b/include/Nazara/Network/Network.hpp @@ -0,0 +1,33 @@ +// Copyright (C) 2015 J茅r么me Leclercq +// This file is part of the "Nazara Engine - Network module" +// For conditions of distribution and use, see copyright notice in Config.hpp + +#pragma once + +#ifndef NAZARA_MODULENAME_HPP +#define NAZARA_MODULENAME_HPP + +#include +#include +#include + +namespace Nz +{ + class NAZARA_NETWORK_API Network + { + public: + Network() = delete; + ~Network() = delete; + + static bool Initialize(); + + static bool IsInitialized(); + + static void Uninitialize(); + + private: + static unsigned int s_moduleReferenceCounter; + }; +} + +#endif // NAZARA_MODULENAME_HPP diff --git a/include/Nazara/Network/SocketHandle.hpp b/include/Nazara/Network/SocketHandle.hpp new file mode 100644 index 000000000..2a09b80c3 --- /dev/null +++ b/include/Nazara/Network/SocketHandle.hpp @@ -0,0 +1,27 @@ +// Copyright (C) 2015 J茅r么me Leclercq +// This file is part of the "Nazara Engine - Network module" +// For conditions of distribution and use, see copyright notice in Config.hpp + +#pragma once + +#ifndef NAZARA_SOCKETHANDLE_HPP +#define NAZARA_SOCKETHANDLE_HPP + +#include + +#if defined(NAZARA_PLATFORM_WINDOWS) + #include +#endif + +namespace Nz +{ + #if defined(NAZARA_PLATFORM_WINDOWS) + using SocketHandle = UINT_PTR; + #elif defined(NAZARA_PLATFORM_POSIX) + using SocketHandle = int; + #else + #error Lack of implementation: SocketHandle + #endif +} + +#endif // NAZARA_SOCKETHANDLE_HPP diff --git a/include/Nazara/Network/TcpBase.hpp b/include/Nazara/Network/TcpBase.hpp new file mode 100644 index 000000000..0d7035026 --- /dev/null +++ b/include/Nazara/Network/TcpBase.hpp @@ -0,0 +1,42 @@ +// Copyright (C) 2015 J茅r么me Leclercq +// This file is part of the "Nazara Engine - Network module" +// For conditions of distribution and use, see copyright notice in Config.hpp + +#pragma once + +#ifndef NAZARA_TCPBASE_HPP +#define NAZARA_TCPBASE_HPP + +#include +#include + +namespace Nz +{ + class NAZARA_NETWORK_API TcpBase : public AbstractSocket + { + public: + ~TcpBase() = default; + + inline bool IsLowDelayEnabled() const; + inline bool IsKeepAliveEnabled() const; + + // Slots + NazaraSignal(OnStateChange, const TcpBase* /*socket*/, SocketState /*newState*/); + + protected: + TcpBase(); + TcpBase(TcpBase&& tcpBase); + + virtual void OnOpened() override; + + SocketState m_state; + UInt64 m_keepAliveInterval; + UInt64 m_keepAliveTime; + bool m_isLowDelayEnabled; + bool m_isKeepAliveEnabled; + }; +} + +#include + +#endif // NAZARA_TCPBASE_HPP \ No newline at end of file diff --git a/include/Nazara/Network/TcpBase.inl b/include/Nazara/Network/TcpBase.inl new file mode 100644 index 000000000..2a1d77d56 --- /dev/null +++ b/include/Nazara/Network/TcpBase.inl @@ -0,0 +1,36 @@ +// Copyright (C) 2015 J茅r么me Leclercq +// This file is part of the "Nazara Engine - Network module" +// For conditions of distribution and use, see copyright notice in Config.hpp + +#include +#include + +namespace Nz +{ + inline TcpBase::TcpBase() : + AbstractSocket(SocketType_TCP) + { + } + + inline TcpBase::TcpBase(TcpBase&& tcpBase) : + AbstractSocket(std::move(tcpBase)), + m_state(tcpBase.m_state), + m_keepAliveInterval(tcpBase.m_keepAliveInterval), + m_keepAliveTime(tcpBase.m_keepAliveTime), + m_isLowDelayEnabled(tcpBase.m_isLowDelayEnabled), + m_isKeepAliveEnabled(tcpBase.m_isKeepAliveEnabled) + { + } + + inline bool TcpBase::IsLowDelayEnabled() const + { + return m_isLowDelayEnabled; + } + + inline bool TcpBase::IsKeepAliveEnabled() const + { + return m_isKeepAliveEnabled; + } +} + +#include diff --git a/include/Nazara/Network/TcpClient.hpp b/include/Nazara/Network/TcpClient.hpp new file mode 100644 index 000000000..ce8c6a282 --- /dev/null +++ b/include/Nazara/Network/TcpClient.hpp @@ -0,0 +1,54 @@ +// Copyright (C) 2015 J茅r么me Leclercq +// This file is part of the "Nazara Engine - Network module" +// For conditions of distribution and use, see copyright notice in Config.hpp + +#pragma once + +#ifndef NAZARA_TCPCLIENT_HPP +#define NAZARA_TCPCLIENT_HPP + +#include +#include +#include +#include + +namespace Nz +{ + class NAZARA_NETWORK_API TcpClient : public TcpBase + { + friend class TcpServer; + + public: + TcpClient() = default; + inline TcpClient(TcpClient&& tcpClient); + ~TcpClient() = default; + + SocketState Connect(const IpAddress& remoteAddress, UInt64 msTimeout = 3000); + inline void Disconnect(); + + void EnableLowDelay(bool lowDelay); + void EnableKeepAlive(bool keepAlive, UInt64 msTime = 10000, UInt64 msInterval = 1000); + + inline UInt64 GetKeepAliveInterval() const; + inline UInt64 GetKeepAliveTime() const; + inline IpAddress GetRemoteAddress() const; + + SocketState QueryState(); + + bool Receive(void* buffer, std::size_t size, std::size_t* received); + + bool Send(const void* buffer, std::size_t size, std::size_t* sent); + + private: + void OnClose() override; + void OnOpened() override; + + void Reset(SocketHandle handle, const IpAddress& peerAddress); + + IpAddress m_peerAddress; + }; +} + +#include + +#endif // NAZARA_TCPCLIENT_HPP \ No newline at end of file diff --git a/include/Nazara/Network/TcpClient.inl b/include/Nazara/Network/TcpClient.inl new file mode 100644 index 000000000..6b981000b --- /dev/null +++ b/include/Nazara/Network/TcpClient.inl @@ -0,0 +1,37 @@ +// Copyright (C) 2015 J茅r么me Leclercq +// This file is part of the "Nazara Engine - Network module" +// For conditions of distribution and use, see copyright notice in Config.hpp + +#include +#include + +namespace Nz +{ + inline TcpClient::TcpClient(TcpClient&& tcpClient) : + TcpBase(std::move(tcpClient)), + m_peerAddress(std::move(tcpClient.m_peerAddress)) + { + } + + inline void TcpClient::Disconnect() + { + Close(); + } + + inline UInt64 TcpClient::GetKeepAliveInterval() const + { + return m_keepAliveInterval; + } + + inline UInt64 TcpClient::GetKeepAliveTime() const + { + return m_keepAliveTime; + } + + inline IpAddress TcpClient::GetRemoteAddress() const + { + return m_peerAddress; + } +} + +#include diff --git a/include/Nazara/Network/TcpServer.hpp b/include/Nazara/Network/TcpServer.hpp new file mode 100644 index 000000000..92b8a22a7 --- /dev/null +++ b/include/Nazara/Network/TcpServer.hpp @@ -0,0 +1,43 @@ +// Copyright (C) 2015 J茅r么me Leclercq +// This file is part of the "Nazara Engine - Network module" +// For conditions of distribution and use, see copyright notice in Config.hpp + +#pragma once + +#ifndef NAZARA_TCPSERVER_HPP +#define NAZARA_TCPSERVER_HPP + +#include +#include +#include + +namespace Nz +{ + class TcpClient; + + class NAZARA_NETWORK_API TcpServer : public TcpBase + { + public: + TcpServer() = default; + inline TcpServer(TcpServer&& tcpServer); + ~TcpServer() = default; + + bool AcceptClient(TcpClient* newClient); + + inline IpAddress GetBoundAddress() const; + inline UInt16 GetBoundPort() const; + + inline SocketState Listen(NetProtocol protocol, UInt16 port, unsigned int queueSize = 10); + SocketState Listen(const IpAddress& address, unsigned int queueSize = 10); + + private: + void OnClose() override; + void OnOpened() override; + + IpAddress m_boundAddress; + }; +} + +#include + +#endif // NAZARA_TCPSERVER_HPP \ No newline at end of file diff --git a/include/Nazara/Network/TcpServer.inl b/include/Nazara/Network/TcpServer.inl new file mode 100644 index 000000000..dfcdab110 --- /dev/null +++ b/include/Nazara/Network/TcpServer.inl @@ -0,0 +1,47 @@ +// Copyright (C) 2015 J茅r么me Leclercq +// This file is part of the "Nazara Engine - Network module" +// For conditions of distribution and use, see copyright notice in Config.hpp + +#include +#include + +namespace Nz +{ + inline TcpServer::TcpServer(TcpServer&& tcpServer) : + TcpBase(std::move(tcpServer)), + m_boundAddress(std::move(tcpServer.m_boundAddress)) + { + } + + inline IpAddress TcpServer::GetBoundAddress() const + { + return m_boundAddress; + } + + inline UInt16 TcpServer::GetBoundPort() const + { + return m_boundAddress.GetPort(); + } + + inline SocketState TcpServer::Listen(NetProtocol protocol, UInt16 port, unsigned int queueSize) + { + NazaraAssert(protocol != NetProtocol_Any, "Any protocol not supported for Listen"); //< TODO + + IpAddress any; + switch (protocol) + { + case NetProtocol_IPv4: + any = IpAddress::AnyIpV4; + break; + + case NetProtocol_IPv6: + any = IpAddress::AnyIpV6; + break; + } + + any.SetPort(port); + return Listen(any, queueSize); + } +} + +#include diff --git a/include/Nazara/Network/UdpSocket.hpp b/include/Nazara/Network/UdpSocket.hpp new file mode 100644 index 000000000..4730f4793 --- /dev/null +++ b/include/Nazara/Network/UdpSocket.hpp @@ -0,0 +1,50 @@ +// Copyright (C) 2015 J茅r么me Leclercq +// This file is part of the "Nazara Engine - Network module" +// For conditions of distribution and use, see copyright notice in Config.hpp + +#pragma once + +#ifndef NAZARA_UDPSOCKET_HPP +#define NAZARA_UDPSOCKET_HPP + +#include +#include +#include + +namespace Nz +{ + class NAZARA_NETWORK_API UdpSocket : public AbstractSocket + { + public: + inline UdpSocket(); + inline UdpSocket(NetProtocol protocol); + inline UdpSocket(UdpSocket&& udpSocket); + ~UdpSocket() = default; + + inline SocketState Bind(UInt16 port); + SocketState Bind(const IpAddress& address); + + inline bool Create(NetProtocol protocol); + + inline IpAddress GetBoundAddress() const; + inline UInt16 GetBoundPort() const; + inline SocketState GetState() const; + + unsigned int QueryMaxDatagramSize(); + + bool Receive(void* buffer, std::size_t size, IpAddress* from, std::size_t* received); + + bool Send(const IpAddress& to, const void* buffer, std::size_t size, std::size_t* sent); + + private: + void OnClose() override; + void OnOpened() override; + + IpAddress m_boundAddress; + SocketState m_state; + }; +} + +#include + +#endif // NAZARA_UDPSOCKET_HPP \ No newline at end of file diff --git a/include/Nazara/Network/UdpSocket.inl b/include/Nazara/Network/UdpSocket.inl new file mode 100644 index 000000000..a77aeca3b --- /dev/null +++ b/include/Nazara/Network/UdpSocket.inl @@ -0,0 +1,66 @@ +// Copyright (C) 2015 J茅r么me Leclercq +// This file is part of the "Nazara Engine - Network module" +// For conditions of distribution and use, see copyright notice in Config.hpp + +#include + +namespace Nz +{ + inline UdpSocket::UdpSocket() : + AbstractSocket(SocketType_UDP) + { + } + + inline UdpSocket::UdpSocket(NetProtocol protocol) : + UdpSocket() + { + Create(protocol); + } + + inline UdpSocket::UdpSocket(UdpSocket&& udpSocket) : + AbstractSocket(std::move(udpSocket)), + m_boundAddress(std::move(udpSocket.m_boundAddress)), + m_state(udpSocket.m_state) + { + } + + inline SocketState UdpSocket::Bind(UInt16 port) + { + IpAddress any; + switch (m_protocol) + { + case NetProtocol_IPv4: + any = IpAddress::AnyIpV4; + break; + + case NetProtocol_IPv6: + any = IpAddress::AnyIpV6; + break; + } + + any.SetPort(port); + return Bind(any); + } + + bool UdpSocket::Create(NetProtocol protocol) + { + return Open(protocol); + } + + inline IpAddress UdpSocket::GetBoundAddress() const + { + return m_boundAddress; + } + + inline UInt16 UdpSocket::GetBoundPort() const + { + return m_boundAddress.GetPort(); + } + + inline SocketState UdpSocket::GetState() const + { + return m_state; + } +} + +#include diff --git a/src/Nazara/Network/AbstractSocket.cpp b/src/Nazara/Network/AbstractSocket.cpp new file mode 100644 index 000000000..bcbf8c638 --- /dev/null +++ b/src/Nazara/Network/AbstractSocket.cpp @@ -0,0 +1,108 @@ +// Copyright (C) 2015 J茅r么me Leclercq +// This file is part of the "Nazara Engine - Utility module" +// For conditions of distribution and use, see copyright notice in Config.hpp + +#include +#include +#include +#include + +#if defined(NAZARA_PLATFORM_WINDOWS) +#include +#else +#error Missing implementation: Socket +#endif + +namespace Nz +{ + AbstractSocket::AbstractSocket(SocketType type) : + m_handle(SocketImpl::InvalidHandle), + m_state(SocketState_NotConnected), + m_type(type), + m_isBlockingEnabled(true) + { + } + + AbstractSocket::AbstractSocket(AbstractSocket&& abstractSocket) : + m_protocol(abstractSocket.m_protocol), + m_lastError(abstractSocket.m_lastError), + m_handle(abstractSocket.m_handle), + m_state(abstractSocket.m_state), + m_type(abstractSocket.m_type), + m_isBlockingEnabled(abstractSocket.m_isBlockingEnabled) + { + abstractSocket.m_handle = SocketImpl::InvalidHandle; + } + + AbstractSocket::~AbstractSocket() + { + Close(); + } + + void AbstractSocket::Close() + { + if (m_handle != SocketImpl::InvalidHandle) + { + OnClose(); + + SocketImpl::Close(m_handle); + m_handle = SocketImpl::InvalidHandle; + } + } + + void AbstractSocket::EnableBlocking(bool blocking) + { + if (m_isBlockingEnabled != blocking) + { + if (m_handle != SocketImpl::InvalidHandle) + SocketImpl::SetBlocking(m_handle, blocking, &m_lastError); + + m_isBlockingEnabled = blocking; + } + } + + unsigned int AbstractSocket::QueryAvailableBytes() const + { + if (m_handle == SocketImpl::InvalidHandle) + return 0; + + return SocketImpl::QueryAvailableBytes(m_handle); + } + + void AbstractSocket::OnClose() + { + ChangeState(SocketState_NotConnected); + } + + void AbstractSocket::OnOpened() + { + SocketError error; + if (!SocketImpl::SetBlocking(m_handle, m_isBlockingEnabled, &error)) + NazaraWarning("Failed to set socket blocking mode (0x" + String::Number(ERROR, 16) + ')'); + } + + bool AbstractSocket::Open(NetProtocol protocol) + { + if (m_handle == SocketImpl::InvalidHandle || m_protocol != protocol) + { + SocketHandle handle = SocketImpl::Create(protocol, m_type, &m_lastError); + if (handle == SocketImpl::InvalidHandle) + return false; + + m_protocol = protocol; + Open(handle); + } + + return true; + } + + void AbstractSocket::Open(SocketHandle handle) + { + NazaraAssert(handle != SocketImpl::InvalidHandle, "Invalid handle"); + + Close(); + + m_handle = handle; + OnOpened(); + } +} \ No newline at end of file diff --git a/src/Nazara/Network/Algorithm.cpp b/src/Nazara/Network/Algorithm.cpp new file mode 100644 index 000000000..628e9cf45 --- /dev/null +++ b/src/Nazara/Network/Algorithm.cpp @@ -0,0 +1,257 @@ +// Copyright (C) 2015 J茅r么me Leclercq +// This file is part of the "Nazara Engine - Utility module" +// For conditions of distribution and use, see copyright notice in Config.hpp + +#include +#include +#include + +namespace Nz +{ + namespace Detail + { + bool ParseDecimal(const char* str, unsigned int* number, const char** endOfRead) + { + const char* ptr = str; + unsigned int val = 0; + while (*ptr >= '0' && *ptr <= '9') + { + val *= 10; + val += *ptr - '0'; + + ++ptr; + } + + if (str == ptr) + return false; + + if (number) + *number = val; + + if (endOfRead) + *endOfRead = ptr; + + return true; + } + + bool ParseHexadecimal(const char* str, unsigned int* number, const char** endOfRead) + { + const char* ptr = str; + unsigned int val = 0; + while ((*ptr >= '0' && *ptr <= '9') || ((*ptr & 0x5F) >= 'A' && (*ptr & 0x5F) <= 'F')) + { + val *= 16; + val += (*ptr > '9') ? ((*ptr & 0x5F) - 'A' + 10) : *ptr - '0'; + + ++ptr; + } + + if (str == ptr) + return false; + + if (number) + *number = val; + + if (endOfRead) + *endOfRead = ptr; + + return true; + } + } + + // From http://rosettacode.org/wiki/Parse_an_IP_Address + // Parse a textual IPv4 or IPv6 address, optionally with port, into a binary + // array (for the address, in host order), and an optionally provided port. + // Also, indicate which of those forms (4 or 6) was parsed. + bool ParseIPAddress(const char* addressPtr, UInt8 result[16], UInt16* port, bool* isIPv6, const char** endOfRead) + { + NazaraAssert(addressPtr, "Invalid address string"); + NazaraAssert(result, "Invalid result pointer"); + + //find first colon, dot, and open bracket + const char* colonPtr = std::strchr(addressPtr, ':'); + const char* dotPtr = std::strchr(addressPtr, '.'); + const char* openBracketPtr = std::strchr(addressPtr, '['); + + // we'll consider this to (probably) be IPv6 if we find an open + // bracket, or an absence of dots, or if there is a colon, and it + // precedes any dots that may or may not be there + bool detectedIPv6 = openBracketPtr || !dotPtr || (colonPtr && (!dotPtr || colonPtr < dotPtr)); + + // OK, now do a little further sanity check our initial guess... + if (detectedIPv6) + { + // if open bracket, then must have close bracket that follows somewhere + const char* closeBracketPtr = std::strchr(addressPtr, ']'); + if (openBracketPtr && (!closeBracketPtr || closeBracketPtr < openBracketPtr)) + return false; + } + else // probably ipv4 + { + // dots must exist, and precede any colons + if (!dotPtr || (colonPtr && colonPtr < dotPtr)) + return false; + } + + // OK, there should be no correctly formed strings which are miscategorized, + // and now any format errors will be found out as we continue parsing + // according to plan. + if (!detectedIPv6) //try to parse as IPv4 + { + // 4 dotted quad decimal; optional port if there is a colon + // since there are just 4, and because the last one can be terminated + // differently, I'm just going to unroll any potential loop. + UInt8* resultPtr = result; + + for (unsigned int i = 0; i < 4; ++i) + { + unsigned int value; + if (!Detail::ParseDecimal(addressPtr, &value, &addressPtr) || value > 255) //must be in range and followed by dot and nonempty + return false; + + if (i != 3) + { + if (*addressPtr != '.') + return false; + + addressPtr++; + } + + *resultPtr++ = static_cast(value); + } + } + else // try to parse as IPv6 + { + UInt8* resultPtr; + UInt8* zeroLoc; + + // up to 8 16-bit hex quantities, separated by colons, with at most one + // empty quantity, acting as a stretchy run of zeros. optional port + // if there are brackets followed by colon and decimal port number. + // A further form allows an ipv4 dotted quad instead of the last two + // 16-bit quantities, but only if in the ipv4 space ::ffff:x:x . + + if (openBracketPtr) // start past the open bracket, if it exists + addressPtr = openBracketPtr + 1; + + resultPtr = result; + zeroLoc = nullptr; // if we find a 'zero compression' location + + bool mappedIPv4 = false; + unsigned int i; + for (i = 0; i < 8; ++i) // we've got up to 8 of these, so we will use a loop + { + const char* savedPtr = addressPtr; + unsigned int value; // get value; these are hex + if (!Detail::ParseHexadecimal(addressPtr, &value, &addressPtr)) // if empty, we are zero compressing; note the loc + { + if (zeroLoc) //there can be only one! + { + // unless it's a terminal empty field, then this is OK, it just means we're done with the host part + if (resultPtr == zeroLoc) + { + --i; + break; + } + + return false; // otherwise, it's a format error + } + + if (*addressPtr != ':') // empty field can only be via : + return false; + + if (i == 0 && *++addressPtr != ':') // leading zero compression requires an extra peek, and adjustment + return false; + + zeroLoc = resultPtr; + ++addressPtr; + } + else + { + if ('.' == *addressPtr) // special case of ipv4 convenience notation + { + addressPtr = savedPtr; + + // who knows how to parse ipv4? we do! + UInt8 result[16]; + bool isIPv6; + if (!ParseIPAddress(addressPtr, result, nullptr, &isIPv6, &addressPtr) || isIPv6) // must parse and must be ipv4 + return false; + + // transfer addrlocal into the present location + for (unsigned int j = 0; j < 4; ++j) + *(resultPtr++) = result[j]; + + ++i; // pretend like we took another short, since the ipv4 effectively is two shorts + mappedIPv4 = true; // remember how we got here for further validation later + break; // totally done with address + } + + if (value > 65535) // must be 16 bit quantity + return false; + + *(resultPtr++) = value >> 8; + *(resultPtr++) = value & 0xFF; + + if (*addressPtr == ':') // typical case inside; carry on + ++addressPtr; + else // some other terminating character; done with this parsing parts + break; + } + } + + // handle any zero compression we found + if (zeroLoc) + { + std::ptrdiff_t nHead = (int) (zeroLoc - result); // how much before zero compression + std::ptrdiff_t nTail = i * 2 - nHead; // how much after zero compression + std::ptrdiff_t nZeros = 16 - nTail - nHead; // how much zeros + std::memmove(&result[16 - nTail], zeroLoc, nTail); // scootch stuff down + std::memset(zeroLoc, 0, nZeros); // clear the compressed zeros + } + + // validation of ipv4 subspace ::ffff:x.x + if (mappedIPv4) + { + static const UInt8 abyPfx[] = {0,0, 0,0, 0,0, 0,0, 0,0, 0xFF,0xFF}; + if (std::memcmp(result, abyPfx, sizeof(abyPfx)) != 0) + return false; + } + + // close bracket + if (openBracketPtr) + { + if (*addressPtr != ']') + return false; + + ++addressPtr; + } + } + + // if asked to read the port + if (port) + { + if (*addressPtr == ':') // have port part + { + ++addressPtr; // past the colon + + unsigned int portValue; + if (!Detail::ParseDecimal(addressPtr, &portValue, nullptr) || portValue > 65535) + return false; + + if (port) + *port = static_cast(portValue); + } + else // finished just with IP address + *port = 0; // indicate we have no port part + } + + if (isIPv6) + *isIPv6 = detectedIPv6; + + if (endOfRead) + *endOfRead = addressPtr; + + return true; + } +} diff --git a/src/Nazara/Network/Debug/NewOverload.cpp b/src/Nazara/Network/Debug/NewOverload.cpp new file mode 100644 index 000000000..80be1880c --- /dev/null +++ b/src/Nazara/Network/Debug/NewOverload.cpp @@ -0,0 +1,31 @@ +// Copyright (C) 2015 J茅r么me Leclercq +// This file is part of the "Nazara Engine - Network module" +// For conditions of distribution and use, see copyright notice in Config.hpp + +#include +#if NAZARA_NETWORK_MANAGE_MEMORY + +#include +#include // Is this necessary ? + +void* operator new(std::size_t size) +{ + return Nz::MemoryManager::Allocate(size, false); +} + +void* operator new[](std::size_t size) +{ + return Nz::MemoryManager::Allocate(size, true); +} + +void operator delete(void* pointer) noexcept +{ + Nz::MemoryManager::Free(pointer, false); +} + +void operator delete[](void* pointer) noexcept +{ + Nz::MemoryManager::Free(pointer, true); +} + +#endif // NAZARA_NETWORK_MANAGE_MEMORY diff --git a/src/Nazara/Network/IpAddress.cpp b/src/Nazara/Network/IpAddress.cpp new file mode 100644 index 000000000..41654eb4e --- /dev/null +++ b/src/Nazara/Network/IpAddress.cpp @@ -0,0 +1,164 @@ +// Copyright (C) 2015 J茅r么me Leclercq +// This file is part of the "Nazara Engine - Network module" +// For conditions of distribution and use, see copyright notice in Config.hpp + +#include +#include +#include +#include +#include +#include + +#if defined(NAZARA_PLATFORM_WINDOWS) +#include +#else +#error Missing implementation: Network +#endif + +#include + +namespace Nz +{ + bool IpAddress::BuildFromAddress(const char* address) + { + m_isValid = false; + + bool isIPv6; + UInt8 result[16]; + if (!ParseIPAddress(address, result, &m_port, &isIPv6, nullptr)) + return false; + + m_isValid = true; + if (isIPv6) + { + m_protocol = NetProtocol_IPv6; + + for (unsigned int i = 0; i < 8; ++i) + m_ipv6[i] = UInt32(result[i*2]) << 8 | result[i*2 + 1]; + } + else + { + m_protocol = NetProtocol_IPv4; + + for (unsigned int i = 0; i < 4; ++i) + m_ipv4[i] = result[i]; + } + + return true; + } + + bool IpAddress::IsLoopback() const + { + if (!m_isValid) + return false; + + NazaraAssert(m_protocol <= NetProtocol_Max, "Protocol has value out of enum"); + switch (m_protocol) + { + case NetProtocol_IPv4: + return m_ipv4[0] == 127; + + case NetProtocol_IPv6: + return m_ipv6 == LoopbackIpV6.m_ipv6; // Only compare the ip value + } + + NazaraInternalError("Invalid protocol for IpAddress (0x" + String::Number(m_protocol) + ')'); + return false; + } + + String IpAddress::ToString() const + { + StringStream stream; + + if (m_isValid) + { + NazaraAssert(m_protocol <= NetProtocol_Max, "Protocol has value out of enum"); + switch (m_protocol) + { + case NetProtocol_IPv4: + for (unsigned int i = 0; i < 4; ++i) + { + stream << int(m_ipv4[i]); + if (i != 3) + stream << '.'; + } + break; + + case NetProtocol_IPv6: + // Canonical representation of an IPv6 + // https://tools.ietf.org/html/rfc5952 + + // Find the longest zero sequence + int f0 = -1; + int l0 = -1; + + for (unsigned int i = 0; i < 8; ++i) + { + if (m_ipv6[i] == 0) + { + unsigned int j; + for (j = i + 1; j < 8; ++j) + { + if (m_ipv6[j] != 0) + break; + } + + if (j - i > std::max(l0 - f0, 1)) + { + f0 = i; + l0 = j; + } + } + } + + // We need brackets around our IPv6 address if we have a port + if (m_port != 0) + stream << '['; + + for (unsigned int i = 0; i < 8; ++i) + { + if (i == f0) + { + stream << "::"; + i = l0; + if (i >= 8) + break; + } + else if (i != 0) + stream << ':'; + + stream << String::Number(m_ipv6[i], 16).ToLower(); + } + + if (m_port != 0) + stream << ']'; + break; + } + + if (m_port != 0) + stream << ':' << m_port; + } + + return stream; + } + + String IpAddress::ResolveAddress(const IpAddress& address, String* service) + { + String hostname; + IpAddressImpl::ResolveAddress(address, &hostname, service); + + return hostname; + } + + std::vector IpAddress::ResolveHostname(NetProtocol protocol, const String& hostname, const String& service) + { + return IpAddressImpl::ResolveHostname(protocol, hostname, service); + } + + IpAddress IpAddress::AnyIpV4(0, 0, 0, 0); + IpAddress IpAddress::AnyIpV6(0, 0, 0, 0, 0, 0, 0, 0, 0); + IpAddress IpAddress::BroadcastIpV4(255, 255, 255, 255); + IpAddress IpAddress::Invalid; + IpAddress IpAddress::LoopbackIpV4(127, 0, 0, 1); + IpAddress IpAddress::LoopbackIpV6(0, 0, 0, 0, 0, 0, 0, 1); +} diff --git a/src/Nazara/Network/Network.cpp b/src/Nazara/Network/Network.cpp new file mode 100644 index 000000000..ffe4842ca --- /dev/null +++ b/src/Nazara/Network/Network.cpp @@ -0,0 +1,84 @@ +// Copyright (C) 2015 J茅r么me Leclercq +// This file is part of the "Nazara Engine - Network module" +// For conditions of distribution and use, see copyright notice in Config.hpp + +#include +#include +#include +#include +#include +#include +#include + +#if defined(NAZARA_PLATFORM_WINDOWS) +#include +#else +#error Missing implementation: Network +#endif + +#include + +namespace Nz +{ + bool Network::Initialize() + { + if (s_moduleReferenceCounter > 0) + { + s_moduleReferenceCounter++; + return true; // Already initialized + } + + // Initialize module dependencies + if (!Core::Initialize()) + { + NazaraError("Failed to initialize core module"); + return false; + } + + s_moduleReferenceCounter++; + + CallOnExit onExit(Network::Uninitialize); + + // Initialize module here + if (!SocketImpl::Initialize()) + { + NazaraError("Failed to initialize socket implementation"); + return false; + } + + onExit.Reset(); + + NazaraNotice("Initialized: Network module"); + return true; + } + + bool Network::IsInitialized() + { + return s_moduleReferenceCounter != 0; + } + + void Network::Uninitialize() + { + if (s_moduleReferenceCounter != 1) + { + // Either the module is not initialized, either it was initialized multiple times + if (s_moduleReferenceCounter > 1) + s_moduleReferenceCounter--; + + return; + } + + s_moduleReferenceCounter = 0; + + // Uninitialize module here + SocketImpl::Uninitialize(); + + NazaraNotice("Uninitialized: Network module"); + + // Free module dependencies + Core::Uninitialize(); + } + + unsigned int Network::s_moduleReferenceCounter = 0; +} + diff --git a/src/Nazara/Network/SystemSocket.hpp b/src/Nazara/Network/SystemSocket.hpp new file mode 100644 index 000000000..95b7dff37 --- /dev/null +++ b/src/Nazara/Network/SystemSocket.hpp @@ -0,0 +1,10 @@ +// Copyright (C) 2015 J茅r么me Leclercq +// This file is part of the "Nazara Engine - Network module" +// For conditions of distribution and use, see copyright notice in Config.hpp + +#ifdef NAZARA_PLATFORM_WINDOWS +#include +#include +#elif defined(NAZARA_PLATFORM_POSIX) +#include +#endif \ No newline at end of file diff --git a/src/Nazara/Network/TcpBase.cpp b/src/Nazara/Network/TcpBase.cpp new file mode 100644 index 000000000..3f9f173eb --- /dev/null +++ b/src/Nazara/Network/TcpBase.cpp @@ -0,0 +1,30 @@ +// Copyright (C) 2015 J茅r么me Leclercq +// This file is part of the "Nazara Engine - Utility module" +// For conditions of distribution and use, see copyright notice in Config.hpp + +#include +#include +#include +#include +#include + +#if defined(NAZARA_PLATFORM_WINDOWS) +#include +#else +#error Missing implementation: Socket +#endif + +namespace Nz +{ + void TcpBase::OnOpened() + { + AbstractSocket::OnOpened(); + + m_isLowDelayEnabled = false; //< Nagle's algorithm, is this enabled everywhere? + m_isKeepAliveEnabled = false; //< default documentation value, OS can change this (TODO: Query OS default value) + m_keepAliveInterval = 1000; //< default documentation value, OS can change this (TODO: Query OS default value) + m_keepAliveTime = 7200000; //< default documentation value, OS can change this (TODO: Query OS default value) + + ChangeState(SocketState_NotConnected); + } +} \ No newline at end of file diff --git a/src/Nazara/Network/TcpClient.cpp b/src/Nazara/Network/TcpClient.cpp new file mode 100644 index 000000000..0c41db30e --- /dev/null +++ b/src/Nazara/Network/TcpClient.cpp @@ -0,0 +1,207 @@ +// Copyright (C) 2015 J茅r么me Leclercq +// This file is part of the "Nazara Engine - Utility module" +// For conditions of distribution and use, see copyright notice in Config.hpp + +#include +#include +#include +#include +#include + +#if defined(NAZARA_PLATFORM_WINDOWS) +#include +#else +#error Missing implementation: Socket +#endif + +namespace Nz +{ + SocketState TcpClient::Connect(const IpAddress& remoteAddress, UInt64 msTimeout) + { + NazaraAssert(remoteAddress.IsValid(), "Invalid remote address"); + NazaraAssert(remoteAddress.GetPort() != 0, "Remote address has no port"); + + Open(remoteAddress.GetProtocol()); + + CallOnExit restoreBlocking; + if (m_isBlockingEnabled) + { + SocketImpl::SetBlocking(m_handle, false); + restoreBlocking.Reset([this] () + { + SocketImpl::SetBlocking(m_handle, true); + }); + } + + SocketState state; + if (msTimeout > 0) + state = SocketImpl::Connect(m_handle, remoteAddress, msTimeout, &m_lastError); + else + state = SocketImpl::Connect(m_handle, remoteAddress, &m_lastError); + + if (state != SocketState_NotConnected) + m_peerAddress = remoteAddress; + + ChangeState(state); + return state; + } + + void TcpClient::EnableLowDelay(bool lowDelay) + { + if (m_isLowDelayEnabled != lowDelay) + { + SocketImpl::SetBlocking(m_handle, lowDelay, &m_lastError); + m_isLowDelayEnabled = lowDelay; + } + } + + void TcpClient::EnableKeepAlive(bool keepAlive, UInt64 msTime, UInt64 msInterval) + { + if (m_isKeepAliveEnabled != keepAlive || m_keepAliveTime != msTime || m_keepAliveInterval != msInterval) + { + SocketImpl::SetKeepAlive(m_handle, keepAlive, msTime, msInterval, &m_lastError); + m_isKeepAliveEnabled = keepAlive; + m_keepAliveInterval = msInterval; + m_keepAliveTime = msTime; + } + } + + SocketState TcpClient::QueryState() + { + // Check our state depending on our last state + switch (m_state) + { + case SocketState_Connecting: + { + // If we were connecting, check how it's going + SocketError getError; + SocketError error = SocketImpl::GetLastError(m_handle, &getError); + + if (getError != SocketError_NoError) + break; //< Do not update state if we cannot get socket state + + if (error == SocketError_NoError) + { + // No error yet, we're still connecting or connected, check that by connecting again + return Connect(m_peerAddress, 0); + } + else + { + // Our connection attempt failed + m_lastError = error; + ChangeState(SocketState_NotConnected); + } + + break; + } + + default: + { + // Check our peer address, if it works we're connected + SocketError error; + m_peerAddress = SocketImpl::QueryPeerAddress(m_handle, &error); + if (m_peerAddress == IpAddress::Invalid) + { + // Other errors mean a problem while getting the peer address + if (error == SocketError_ConnectionClosed) + ChangeState(SocketState_NotConnected); + } + else + ChangeState(SocketState_Connected); // If we are not connecting and have a peer address, we are connected + + break; + } + } + + return m_state; + } + + bool TcpClient::Receive(void* buffer, std::size_t size, std::size_t* received) + { + NazaraAssert(buffer && size > 0, "Invalid buffer"); + + int read; + if (!SocketImpl::Receive(m_handle, buffer, static_cast(size), &read, &m_lastError)) + { + switch (m_lastError) + { + case SocketError_ConnectionClosed: + case SocketError_ConnectionRefused: + ChangeState(SocketState_NotConnected); + break; + + default: + break; + } + + return false; + } + + if (received) + *received = read; + + ChangeState(SocketState_Connected); + return true; + } + + bool TcpClient::Send(const void* buffer, std::size_t size, std::size_t* sent) + { + NazaraAssert(buffer && size > 0, "Invalid buffer"); + + CallOnExit updateSent; + std::size_t totalByteSent = 0; + if (sent) + { + updateSent.Reset([sent, &totalByteSent] () + { + *sent = totalByteSent; + }); + } + + while (totalByteSent < size) + { + int sendSize = static_cast(std::min(size - totalByteSent, std::numeric_limits::max())); //< Handle very large send + int sentSize; + if (!SocketImpl::Send(m_handle, reinterpret_cast(buffer) + totalByteSent, sendSize, &sentSize, &m_lastError)) + { + switch (m_lastError) + { + case SocketError_ConnectionClosed: + case SocketError_ConnectionRefused: + ChangeState(SocketState_NotConnected); + break; + + default: + break; + } + + return false; + } + + totalByteSent += sentSize; + } + + ChangeState(SocketState_Connected); + return true; + } + + void TcpClient::OnClose() + { + TcpBase::OnClose(); + + m_peerAddress = IpAddress::Invalid; + } + + void TcpClient::OnOpened() + { + TcpBase::OnOpened(); + + m_peerAddress = IpAddress::Invalid; + } + + void TcpClient::Reset(SocketHandle handle, const IpAddress& peerAddress) + { + Open(handle); + m_peerAddress = peerAddress; + } +} \ No newline at end of file diff --git a/src/Nazara/Network/TcpServer.cpp b/src/Nazara/Network/TcpServer.cpp new file mode 100644 index 000000000..5cc999f1c --- /dev/null +++ b/src/Nazara/Network/TcpServer.cpp @@ -0,0 +1,63 @@ +// Copyright (C) 2015 J茅r么me Leclercq +// This file is part of the "Nazara Engine - Utility module" +// For conditions of distribution and use, see copyright notice in Config.hpp + +#include +#include +#include +#include +#include +#include + +#if defined(NAZARA_PLATFORM_WINDOWS) +#include +#else +#error Missing implementation: Socket +#endif + +namespace Nz +{ + bool TcpServer::AcceptClient(TcpClient* newClient) + { + NazaraAssert(m_handle != SocketImpl::InvalidHandle, "Server isn't listening"); + NazaraAssert(newClient, "Invalid client socket"); + + IpAddress clientAddress; + SocketHandle handle = SocketImpl::Accept(m_handle, &clientAddress, &m_lastError); + if (handle != SocketImpl::InvalidHandle) + { + newClient->Reset(handle, clientAddress); + return true; + } + else + return false; + } + + SocketState TcpServer::Listen(const IpAddress& address, unsigned int queueSize) + { + NazaraAssert(address.IsValid(), "Invalid address"); + + Open(address.GetProtocol()); + + SocketState state = SocketImpl::Listen(m_handle, address, queueSize, &m_lastError); + if (state == SocketState_Bound) + m_boundAddress = SocketImpl::QuerySocketAddress(m_handle); + + ChangeState(state); + return state; + } + + void TcpServer::OnClose() + { + TcpBase::OnClose(); + + m_boundAddress = IpAddress::Invalid; + } + + void TcpServer::OnOpened() + { + TcpBase::OnOpened(); + + m_boundAddress = IpAddress::Invalid; + } +} \ No newline at end of file diff --git a/src/Nazara/Network/UdpSocket.cpp b/src/Nazara/Network/UdpSocket.cpp new file mode 100644 index 000000000..b73afaf20 --- /dev/null +++ b/src/Nazara/Network/UdpSocket.cpp @@ -0,0 +1,79 @@ +// Copyright (C) 2015 J茅r么me Leclercq +// This file is part of the "Nazara Engine - Utility module" +// For conditions of distribution and use, see copyright notice in Config.hpp + +#include +#include + +#if defined(NAZARA_PLATFORM_WINDOWS) +#include +#else +#error Missing implementation: Socket +#endif + +namespace Nz +{ + SocketState UdpSocket::Bind(const IpAddress& address) + { + NazaraAssert(m_handle != SocketImpl::InvalidHandle, "Socket hasn't been created"); + NazaraAssert(address.IsValid(), "Invalid address"); + + SocketState state = SocketImpl::Bind(m_handle, address, &m_lastError); + if (state == SocketState_Bound) + m_boundAddress = SocketImpl::QuerySocketAddress(m_handle); + + ChangeState(state); + return state; + } + + unsigned int UdpSocket::QueryMaxDatagramSize() + { + NazaraAssert(m_handle != SocketImpl::InvalidHandle, "Socket hasn't been created"); + + return SocketImpl::QueryMaxDatagramSize(m_handle, &m_lastError); + } + + bool UdpSocket::Receive(void* buffer, std::size_t size, IpAddress* from, std::size_t* received) + { + NazaraAssert(buffer && size > 0, "Invalid buffer"); + + int read; + if (!SocketImpl::ReceiveFrom(m_handle, buffer, static_cast(size), from, &read, &m_lastError)) + return false; + + if (received) + *received = read; + + return true; + } + + bool UdpSocket::Send(const IpAddress& to, const void* buffer, std::size_t size, std::size_t* sent) + { + NazaraAssert(to.IsValid(), "Invalid ip address"); + NazaraAssert(to.GetProtocol() == m_protocol, "IP Address has a different protocol than the socket"); + NazaraAssert(buffer && size > 0, "Invalid buffer"); + + int byteSent; + if (!SocketImpl::SendTo(m_handle, buffer, static_cast(size), to, &byteSent, &m_lastError)) + return false; + + if (sent) + *sent = byteSent; + + return true; + } + + void UdpSocket::OnClose() + { + m_boundAddress = IpAddress::Invalid; + + ChangeState(SocketState_NotConnected); + } + + void UdpSocket::OnOpened() + { + m_boundAddress = IpAddress::Invalid; + + ChangeState(SocketState_NotConnected); + } +} \ No newline at end of file diff --git a/src/Nazara/Network/Win32/IpAddressImpl.cpp b/src/Nazara/Network/Win32/IpAddressImpl.cpp new file mode 100644 index 000000000..0ded6dbdc --- /dev/null +++ b/src/Nazara/Network/Win32/IpAddressImpl.cpp @@ -0,0 +1,240 @@ +// Copyright (C) 2015 J茅r么me Leclercq +// This file is part of the "Nazara Engine - Network module" +// For conditions of distribution and use, see copyright notice in Config.hpp + +#include +#include +#include +#include +#include + +namespace Nz +{ + namespace Detail + { + #if NAZARA_CORE_WINDOWS_VISTA + using addrinfoImpl = addrinfoW; + + int GetAddressInfo(const String& hostname, const String& service, const addrinfoImpl* hints, addrinfoImpl** results) + { + return GetAddrInfoW(hostname.GetWideString().c_str(), service.GetWideString().c_str(), &hints, &servinfo); + } + + int GetHostnameInfo(sockaddr* socketAddress, socklen_t socketLen, String* hostname, String* service) + { + std::array hostnameBuffer; + std::array serviceBuffer; + + int result = GetNameInfoW(socketAddress, socketLen, hostnameBuffer.data(), hostnameBuffer.size(), serviceBuffer.data(), serviceBuffer.size(), NI_NUMERICSERV); + if (result == 0) + { + if (hostname) + hostname->Set(hostnameBuffer.data()); + + if (service) + service->Set(serviceBuffer.data()); + } + + return result; + } + + void FreeAddressInfo(addrinfoImpl* results) + { + FreeAddrInfoW(results); + } + #else + using addrinfoImpl = addrinfo; + + int GetAddressInfo(const String& hostname, const String& service, const addrinfoImpl* hints, addrinfoImpl** results) + { + return getaddrinfo(hostname.GetConstBuffer(), service.GetConstBuffer(), hints, results); + } + + int GetHostnameInfo(sockaddr* socketAddress, socklen_t socketLen, String* hostname, String* service) + { + std::array hostnameBuffer; + std::array serviceBuffer; + + int result = getnameinfo(socketAddress, socketLen, hostnameBuffer.data(), hostnameBuffer.size(), serviceBuffer.data(), serviceBuffer.size(), NI_NUMERICSERV); + if (result == 0) + { + if (hostname) + hostname->Set(hostnameBuffer.data()); + + if (service) + service->Set(serviceBuffer.data()); + } + + return result; + } + + void FreeAddressInfo(addrinfoImpl* results) + { + freeaddrinfo(results); + } + #endif + } + + IpAddress IpAddressImpl::FromAddrinfo(const addrinfo* info) + { + switch (info->ai_family) + { + case AF_INET: + { + sockaddr_in* ipv4 = reinterpret_cast(info->ai_addr); + + auto& rawIpV4 = ipv4->sin_addr.S_un.S_un_b; + return IpAddress(rawIpV4.s_b1, rawIpV4.s_b2, rawIpV4.s_b3, rawIpV4.s_b4, ntohs(ipv4->sin_port)); + } + + case AF_INET6: + { + sockaddr_in6* ipv6 = reinterpret_cast(info->ai_addr); + + auto& rawIpV6 = ipv6->sin6_addr.u.Word; + return IpAddress(rawIpV6[0], rawIpV6[1], rawIpV6[2], rawIpV6[3], rawIpV6[4], rawIpV6[5], rawIpV6[6], rawIpV6[7], ntohs(ipv6->sin6_port)); + } + } + + return IpAddress(); + } + + IpAddress IpAddressImpl::FromAddrinfo(const addrinfoW* info) + { + switch (info->ai_family) + { + case AF_INET: + { + sockaddr_in* ipv4 = reinterpret_cast(info->ai_addr); + + return FromSockAddr(ipv4); + } + + case AF_INET6: + { + sockaddr_in6* ipv6 = reinterpret_cast(info->ai_addr); + + return FromSockAddr(ipv6); + } + } + + return IpAddress(); + } + + IpAddress IpAddressImpl::FromSockAddr(const sockaddr* address) + { + switch (address->sa_family) + { + case AF_INET: + return FromSockAddr(reinterpret_cast(address)); + + case AF_INET6: + return FromSockAddr(reinterpret_cast(address)); + } + + return IpAddress(); + } + + IpAddress IpAddressImpl::FromSockAddr(const sockaddr_in* addressv4) + { + auto& rawIpV4 = addressv4->sin_addr.S_un.S_un_b; + return IpAddress(rawIpV4.s_b1, rawIpV4.s_b2, rawIpV4.s_b3, rawIpV4.s_b4, ntohs(addressv4->sin_port)); + } + + IpAddress IpAddressImpl::FromSockAddr(const sockaddr_in6* addressv6) + { + auto& rawIpV6 = addressv6->sin6_addr.u.Word; + return IpAddress(rawIpV6[0], rawIpV6[1], rawIpV6[2], rawIpV6[3], rawIpV6[4], rawIpV6[5], rawIpV6[6], rawIpV6[7], ntohs(addressv6->sin6_port)); + } + + bool IpAddressImpl::ResolveAddress(const IpAddress& ipAddress, String* hostname, String* service) + { + SockAddrBuffer socketAddress; + socklen_t socketAddressLen = ToSockAddr(ipAddress, socketAddress.data()); + + return Detail::GetHostnameInfo(reinterpret_cast(socketAddress.data()), socketAddressLen, hostname, service) == 0; + } + + std::vector IpAddressImpl::ResolveHostname(NetProtocol procol, const String& hostname, const String& service) + { + std::vector results; + + Detail::addrinfoImpl hints; + std::memset(&hints, 0, sizeof(Detail::addrinfoImpl)); + hints.ai_family = SocketImpl::TranslateNetProtocolToAF(procol); + hints.ai_socktype = SOCK_STREAM; + + Detail::addrinfoImpl* servinfo; + int rv; + if ((rv = Detail::GetAddressInfo(hostname, service, &hints, &servinfo)) != 0) + { + fprintf(stderr, "getaddrinfo: %s\n", gai_strerror(rv)); + return results; + } + + CallOnExit onExit([servinfo]() + { + Detail::FreeAddressInfo(servinfo); + }); + + // loop through all the results and connect to the first we can + for (Detail::addrinfoImpl* p = servinfo; p != nullptr; p = p->ai_next) + { + HostnameInfo result; + result.address = FromAddrinfo(p); + result.canonicalName = String::Unicode(p->ai_canonname); + result.family = p->ai_family; + result.flags = p->ai_flags; + result.socketType = p->ai_socktype; + + results.push_back(result); + } + + return results; + } + + socklen_t IpAddressImpl::ToSockAddr(const IpAddress& ipAddress, void* buffer) + { + if (ipAddress.IsValid()) + { + switch (ipAddress.GetProtocol()) + { + case NetProtocol_IPv4: + { + sockaddr_in* socketAddress = reinterpret_cast(buffer); + + std::memset(socketAddress, 0, sizeof(sockaddr_in)); + socketAddress->sin_family = AF_INET; + socketAddress->sin_port = htons(ipAddress.GetPort()); + socketAddress->sin_addr.S_un.S_addr = htonl(ipAddress.ToUInt32()); + + return sizeof(sockaddr_in); + } + + case NetProtocol_IPv6: + { + sockaddr_in6* socketAddress = reinterpret_cast(buffer); + + std::memset(socketAddress, 0, sizeof(sockaddr_in6)); + socketAddress->sin6_family = AF_INET6; + socketAddress->sin6_port = htons(ipAddress.GetPort()); + + IpAddress::IPv6 address = ipAddress.ToIPv6(); + for (unsigned int i = 0; i < 8; ++i) + socketAddress->sin6_addr.u.Word[i] = htons(address[i]); + + IN6_ADDR any = in6addr_any; + + return sizeof(sockaddr_in6); + } + + default: + NazaraInternalError("Unhandled ip protocol (0x" + String::Number(ipAddress.GetProtocol()) + ')'); + break; + } + } + + NazaraError("Invalid ip address"); + return 0; + } +} diff --git a/src/Nazara/Network/Win32/IpAddressImpl.hpp b/src/Nazara/Network/Win32/IpAddressImpl.hpp new file mode 100644 index 000000000..57e6bbcc5 --- /dev/null +++ b/src/Nazara/Network/Win32/IpAddressImpl.hpp @@ -0,0 +1,30 @@ +// Copyright (C) 2015 J茅r么me Leclercq +// This file is part of the "Nazara Engine - Network module" +// For conditions of distribution and use, see copyright notice in Config.hpp + +#include +#include +#include + +namespace Nz +{ + class IpAddressImpl + { + public: + using SockAddrBuffer = std::array; + + IpAddressImpl() = delete; + ~IpAddressImpl() = delete; + + static IpAddress FromAddrinfo(const addrinfo* info); + static IpAddress FromAddrinfo(const addrinfoW* info); + static IpAddress FromSockAddr(const sockaddr* address); + static IpAddress FromSockAddr(const sockaddr_in* addressv4); + static IpAddress FromSockAddr(const sockaddr_in6* addressv6); + + static bool ResolveAddress(const IpAddress& ipAddress, String* hostname, String* service); + static std::vector ResolveHostname(NetProtocol procol, const String& hostname, const String& service); + + static socklen_t ToSockAddr(const IpAddress& ipAddress, void* buffer); + }; +} diff --git a/src/Nazara/Network/Win32/SocketImpl.cpp b/src/Nazara/Network/Win32/SocketImpl.cpp new file mode 100644 index 000000000..3ff57da52 --- /dev/null +++ b/src/Nazara/Network/Win32/SocketImpl.cpp @@ -0,0 +1,639 @@ +// Copyright (C) 2015 J茅r么me Leclercq +// This file is part of the "Nazara Engine - Network module" +// For conditions of distribution and use, see copyright notice in Config.hpp + +#include +#include +#include +#include +#include +#include + +namespace Nz +{ + SocketHandle SocketImpl::Accept(SocketHandle handle, IpAddress* address, SocketError* error) + { + NazaraAssert(handle != InvalidHandle, "Invalid handle"); + + IpAddressImpl::SockAddrBuffer nameBuffer; + int bufferLength = static_cast(nameBuffer.size()); + + SocketHandle newClient = accept(handle, reinterpret_cast(&nameBuffer), &bufferLength); + if (newClient != InvalidHandle) + { + if (address) + *address = IpAddressImpl::FromSockAddr(reinterpret_cast(&nameBuffer)); + + if (error) + *error = SocketError_NoError; + } + else + { + if (error) + *error = TranslateWSAErrorToSocketError(WSAGetLastError()); + } + + return newClient; + } + + SocketState SocketImpl::Bind(SocketHandle handle, const IpAddress& address, SocketError* error) + { + NazaraAssert(handle != InvalidHandle, "Invalid handle"); + NazaraAssert(address.IsValid(), "Invalid address"); + + IpAddressImpl::SockAddrBuffer nameBuffer; + int bufferLength = IpAddressImpl::ToSockAddr(address, nameBuffer.data()); + + if (bind(handle, reinterpret_cast(&nameBuffer), bufferLength) == SOCKET_ERROR) + { + if (error) + *error = TranslateWSAErrorToSocketError(WSAGetLastError()); + + return SocketState_NotConnected; + } + + if (error) + *error = SocketError_NoError; + + return SocketState_Bound; + } + + SocketHandle SocketImpl::Create(NetProtocol protocol, SocketType type, SocketError* error) + { + NazaraAssert(protocol != NetProtocol_Any, "Any protocol is not supported for socket creation"); + NazaraAssert(type <= SocketType_Max, "Type has value out of enum"); + + SocketHandle handle = socket(TranslateNetProtocolToAF(protocol), TranslateSocketTypeToSock(type), 0); + if (handle == InvalidHandle && error != nullptr) + *error = TranslateWSAErrorToSocketError(WSAGetLastError()); + + return handle; + } + + void SocketImpl::Close(SocketHandle handle) + { + NazaraAssert(handle != InvalidHandle, "Invalid handle"); + + if (closesocket(handle) == SOCKET_ERROR) + NazaraWarning("Failed to close socket: " + Error::GetLastSystemError(WSAGetLastError())); + } + + void SocketImpl::ClearErrorCode(SocketHandle handle) + { + NazaraAssert(handle != InvalidHandle, "Invalid handle"); + + if (GetLastError(handle, nullptr) < 0) + NazaraWarning("Failed to clear socket error code: " + Error::GetLastSystemError(WSAGetLastError())); + } + + SocketState SocketImpl::Connect(SocketHandle handle, const IpAddress& address, SocketError* error) + { + NazaraAssert(handle != InvalidHandle, "Invalid handle"); + NazaraAssert(address.IsValid(), "Invalid address"); + + IpAddressImpl::SockAddrBuffer nameBuffer; + int bufferLength = IpAddressImpl::ToSockAddr(address, nameBuffer.data()); + + if (error) + *error = SocketError_NoError; + + // Clear socket error status + ClearErrorCode(handle); + + if (connect(handle, reinterpret_cast(nameBuffer.data()), bufferLength) == SOCKET_ERROR) + { + int errorCode = WSAGetLastError(); + switch (errorCode) //< Check for "normal errors" first + { + case WSAEALREADY: + case WSAEWOULDBLOCK: + return SocketState_Connecting; + + case WSAEISCONN: + return SocketState_Connected; + } + + if (error) + { + if (errorCode == WSAEADDRNOTAVAIL) + *error = SocketError_ConnectionRefused; //< ConnectionRefused seems more legit than AddressNotAvailable in connect case + else + *error = TranslateWSAErrorToSocketError(errorCode); + } + + return SocketState_NotConnected; + } + + return SocketState_Connected; + } + + SocketState SocketImpl::Connect(SocketHandle handle, const IpAddress& address, UInt64 msTimeout, SocketError* error) + { + SocketState state = Connect(handle, address, error); + if (state == SocketState_Connecting) + { + // http://developerweb.net/viewtopic.php?id=3196 + fd_set localSet; + FD_ZERO(&localSet); + FD_SET(handle, &localSet); + + timeval tv; + tv.tv_sec = static_cast(msTimeout / 1000ULL); + tv.tv_usec = static_cast((msTimeout % 1000ULL) * 1000ULL); + + int ret = select(0, nullptr, &localSet, &localSet, (msTimeout > 0) ? &tv : nullptr); + if (ret > 0) + { + int code = GetLastErrorCode(handle, error); + if (code < 0) //< GetLastSocketError() failed + return SocketState_NotConnected; + + if (code) + { + if (error) + *error = TranslateWSAErrorToSocketError(code); + + return SocketState_NotConnected; + } + } + else if (ret == 0) + { + if (error) + *error = SocketError_TimedOut; + + return SocketState_NotConnected; + } + else + { + if (error) + *error = TranslateWSAErrorToSocketError(WSAGetLastError()); + + return SocketState_NotConnected; + } + + if (error) + *error = SocketError_NoError; + + state = SocketState_Connected; + } + + return state; + } + + bool SocketImpl::Initialize() + { + int errorCode = WSAStartup(MAKEWORD(2, 2), &s_WSA); + if (errorCode != 0) + { + NazaraError("Failed to initialize Windows Socket 2.2: " + Error::GetLastSystemError(errorCode)); + return false; + } + + NazaraDebug("Initialized Windows Socket " + String::Number(LOBYTE(s_WSA.wVersion)) + '.' + String::Number(HIBYTE(s_WSA.wVersion)) + " (" + String(s_WSA.szDescription) + ')'); + return true; + } + + SocketError SocketImpl::GetLastError(SocketHandle handle, SocketError* error) + { + int code = GetLastErrorCode(handle, error); + if (code < 0) + return SocketError_Internal; + + return TranslateWSAErrorToSocketError(code); + } + + int SocketImpl::GetLastErrorCode() + { + return WSAGetLastError(); + } + + int SocketImpl::GetLastErrorCode(SocketHandle handle, SocketError* error) + { + int code; + int codeLength = sizeof(code); + + if (getsockopt(handle, SOL_SOCKET, SO_ERROR, reinterpret_cast(&code), &codeLength) == SOCKET_ERROR) + { + if (error) + *error = TranslateWSAErrorToSocketError(WSAGetLastError()); + + return -1; + } + + if (error) + *error = SocketError_NoError; + + return code; + } + + SocketState SocketImpl::Listen(SocketHandle handle, const IpAddress& address, unsigned queueSize, SocketError* error) + { + NazaraAssert(handle != InvalidHandle, "Invalid handle"); + NazaraAssert(address.IsValid(), "Invalid address"); + + IpAddressImpl::SockAddrBuffer nameBuffer; + int bufferLength = IpAddressImpl::ToSockAddr(address, nameBuffer.data()); + + if (bind(handle, reinterpret_cast(&nameBuffer), bufferLength) == SOCKET_ERROR) + { + if (error) + *error = TranslateWSAErrorToSocketError(WSAGetLastError()); + + return SocketState_NotConnected; + } + + if (listen(handle, queueSize) == SOCKET_ERROR) + { + if (error) + *error = TranslateWSAErrorToSocketError(WSAGetLastError()); + + return SocketState_NotConnected; + } + + if (error) + *error = SocketError_NoError; + + return SocketState_Bound; + } + + unsigned int SocketImpl::QueryAvailableBytes(SocketHandle handle, SocketError* error) + { + NazaraAssert(handle != InvalidHandle, "Invalid handle"); + + u_long availableBytes; + if (ioctlsocket(handle, FIONREAD, &availableBytes) == SOCKET_ERROR) + { + if (error) + *error = TranslateWSAErrorToSocketError(WSAGetLastError()); + + availableBytes = 0; + } + + if (error) + *error = SocketError_NoError; + + return availableBytes; + } + + unsigned int SocketImpl::QueryMaxDatagramSize(SocketHandle handle, SocketError* error) + { + unsigned int code; + int codeLength = sizeof(code); + + if (getsockopt(handle, SOL_SOCKET, SO_MAX_MSG_SIZE, reinterpret_cast(&code), &codeLength) == SOCKET_ERROR) + { + if (error) + *error = TranslateWSAErrorToSocketError(WSAGetLastError()); + + return -1; + } + + if (error) + *error = SocketError_NoError; + + return code; + } + + IpAddress SocketImpl::QueryPeerAddress(SocketHandle handle, SocketError* error) + { + NazaraAssert(handle != InvalidHandle, "Invalid handle"); + + IpAddressImpl::SockAddrBuffer nameBuffer; + int bufferLength = static_cast(nameBuffer.size()); + + if (getpeername(handle, reinterpret_cast(nameBuffer.data()), &bufferLength) == SOCKET_ERROR) + { + if (error) + *error = TranslateWSAErrorToSocketError(WSAGetLastError()); + + return IpAddress(); + } + + if (error) + *error = SocketError_NoError; + + return IpAddressImpl::FromSockAddr(reinterpret_cast(nameBuffer.data())); + } + + IpAddress SocketImpl::QuerySocketAddress(SocketHandle handle, SocketError* error) + { + NazaraAssert(handle != InvalidHandle, "Invalid handle"); + + IpAddressImpl::SockAddrBuffer nameBuffer; + int bufferLength = static_cast(nameBuffer.size()); + + if (getsockname(handle, reinterpret_cast(nameBuffer.data()), &bufferLength) == SOCKET_ERROR) + { + if (error) + { + int errorCode = WSAGetLastError(); + if (errorCode == WSAEINVAL) + *error = SocketError_NoError; + else + *error = TranslateWSAErrorToSocketError(errorCode); + } + + return IpAddress(); + } + + if (error) + *error = SocketError_NoError; + + return IpAddressImpl::FromSockAddr(reinterpret_cast(nameBuffer.data())); + } + + + bool SocketImpl::Receive(SocketHandle handle, void* buffer, int length, int* read, SocketError* error) + { + NazaraAssert(handle != InvalidHandle, "Invalid handle"); + NazaraAssert(buffer && length > 0, "Invalid buffer"); + + int byteRead = recv(handle, reinterpret_cast(buffer), length, 0); + if (byteRead == SOCKET_ERROR) + { + int errorCode = WSAGetLastError(); + switch (errorCode) + { + case WSAEWOULDBLOCK: + { + // If we have no data and are not blocking, return true with 0 byte read + byteRead = 0; + break; + } + + default: + { + if (error) + *error = TranslateWSAErrorToSocketError(errorCode); + + return false; //< Error + } + } + } + else if (byteRead == 0) + { + if (error) + *error = SocketError_ConnectionClosed; + + return false; //< Connection has been closed + } + + if (read) + *read = byteRead; + + if (error) + *error = SocketError_NoError; + + return true; + } + + bool SocketImpl::ReceiveFrom(SocketHandle handle, void* buffer, int length, IpAddress* from, int* read, SocketError* error) + { + NazaraAssert(handle != InvalidHandle, "Invalid handle"); + NazaraAssert(buffer && length > 0, "Invalid buffer"); + + IpAddressImpl::SockAddrBuffer nameBuffer; + int bufferLength = static_cast(nameBuffer.size()); + + IpAddress senderIp; + + int byteRead = recvfrom(handle, reinterpret_cast(buffer), length, 0, reinterpret_cast(&nameBuffer), &bufferLength); + if (byteRead == SOCKET_ERROR) + { + int errorCode = WSAGetLastError(); + switch (errorCode) + { + case WSAEWOULDBLOCK: + { + // If we have no data and are not blocking, return true with 0 byte read + byteRead = 0; + senderIp = IpAddress::Invalid; + break; + } + + default: + { + if (error) + *error = TranslateWSAErrorToSocketError(errorCode); + + return false; //< Error + } + } + } + else if (byteRead == 0) + { + if (error) + *error = SocketError_ConnectionClosed; + + return false; //< Connection closed + } + else // else we received something + senderIp = IpAddressImpl::FromSockAddr(reinterpret_cast(&nameBuffer)); + + if (from) + *from = senderIp; + + if (read) + *read = byteRead; + + if (error) + *error = SocketError_NoError; + + return true; + } + + bool SocketImpl::Send(SocketHandle handle, const void* buffer, int length, int* sent, SocketError* error) + { + NazaraAssert(handle != InvalidHandle, "Invalid handle"); + NazaraAssert(buffer && length > 0, "Invalid buffer"); + + int byteSent = send(handle, reinterpret_cast(buffer), length, 0); + if (byteSent == SOCKET_ERROR) + { + if (error) + *error = TranslateWSAErrorToSocketError(WSAGetLastError()); + + return false; //< Error + } + + if (sent) + *sent = byteSent; + + if (error) + *error = SocketError_NoError; + + return true; + } + + bool SocketImpl::SendTo(SocketHandle handle, const void* buffer, int length, const IpAddress& to, int* sent, SocketError* error) + { + NazaraAssert(handle != InvalidHandle, "Invalid handle"); + NazaraAssert(buffer && length > 0, "Invalid buffer"); + + IpAddressImpl::SockAddrBuffer nameBuffer; + int bufferLength = IpAddressImpl::ToSockAddr(to, nameBuffer.data()); + + int byteSent = sendto(handle, reinterpret_cast(buffer), length, 0, reinterpret_cast(nameBuffer.data()), bufferLength); + if (byteSent == SOCKET_ERROR) + { + if (error) + *error = TranslateWSAErrorToSocketError(WSAGetLastError()); + + return false; //< Error + } + + if (sent) + *sent = byteSent; + + if (error) + *error = SocketError_NoError; + + return true; + } + + bool SocketImpl::SetBlocking(SocketHandle handle, bool blocking, SocketError* error) + { + NazaraAssert(handle != InvalidHandle, "Invalid handle"); + + u_long block = (blocking) ? 0 : 1; + if (ioctlsocket(handle, FIONBIO, &block) == SOCKET_ERROR) + { + if (error) + *error = TranslateWSAErrorToSocketError(WSAGetLastError()); + + return false; //< Error + } + + if (error) + *error = SocketError_NoError; + + return true; + } + + bool SocketImpl::SetKeepAlive(SocketHandle handle, bool enabled, UInt64 msTime, UInt64 msInterval, SocketError* error) + { + NazaraAssert(handle != InvalidHandle, "Invalid handle"); + + tcp_keepalive keepAlive; + keepAlive.onoff = (enabled) ? 1 : 0; + keepAlive.keepaliveinterval = static_cast(msInterval); + keepAlive.keepalivetime = static_cast(msTime); + + DWORD dummy; //< byteReturned + if (!WSAIoctl(handle, SIO_KEEPALIVE_VALS, &keepAlive, sizeof(keepAlive), nullptr, 0, &dummy, nullptr, nullptr)) + { + if (error) + *error = TranslateWSAErrorToSocketError(WSAGetLastError()); + + return false; //< Error + } + + if (error) + *error = SocketError_NoError; + + return true; + } + + SocketError SocketImpl::TranslateWSAErrorToSocketError(int error) + { + switch (error) + { + case 0: + return SocketError_NoError; + + // Engine error + case WSAEACCES: + case WSAEBADF: + case WSAEINVAL: + case WSAEFAULT: + case WSAENOTSOCK: + case WSAEPROTOTYPE: + case WSA_INVALID_HANDLE: + return SocketError_Internal; + + case WSAEADDRNOTAVAIL: + case WSAEADDRINUSE: + return SocketError_AddressNotAvailable; + + case WSAEAFNOSUPPORT: + case WSAEPFNOSUPPORT: + case WSAEOPNOTSUPP: + case WSAEPROTONOSUPPORT: + case WSAESOCKTNOSUPPORT: + return SocketError_NotSupported; + + // Those are not errors and should have been handled before the call + case WSAEALREADY: + case WSAEISCONN: + case WSAEWOULDBLOCK: + return SocketError_Internal; + + case WSAECONNREFUSED: + return SocketError_ConnectionRefused; + + case WSAEMSGSIZE: + return SocketError_DatagramSize; + + case WSAEMFILE: + case WSAENOBUFS: + case WSA_NOT_ENOUGH_MEMORY: + return SocketError_ResourceError; + + case WSAENOTCONN: + case WSAESHUTDOWN: + return SocketError_ConnectionClosed; + + case WSAEHOSTUNREACH: + return SocketError_UnreachableHost; + + case WSAENETDOWN: + case WSAENETUNREACH: + return SocketError_NetworkError; + + case WSANOTINITIALISED: + return SocketError_NotInitialized; + + case WSAETIMEDOUT: + return SocketError_TimedOut; + } + + NazaraWarning("Unhandled WinSock error: " + Error::GetLastSystemError(error) + " (" + String::Number(error) + ')'); + return SocketError_Unknown; + } + + int SocketImpl::TranslateNetProtocolToAF(NetProtocol protocol) + { + NazaraAssert(protocol <= NetProtocol_Max, "Protocol has value out of enum"); + + static int addressFamily[] = { + AF_UNSPEC, //< NetProtocol_Any + AF_INET, //< NetProtocol_IPv4 + AF_INET6 //< NetProtocol_IPv6 + }; + static_assert(sizeof(addressFamily) / sizeof(int) == NetProtocol_Max + 1, "Address family array is incomplete"); + + return addressFamily[protocol]; + } + + int SocketImpl::TranslateSocketTypeToSock(SocketType type) + { + NazaraAssert(type <= SocketType_Max, "Socket type has value out of enum"); + + static int socketType[] = { + SOCK_RAW, //< SocketType_Raw + SOCK_STREAM, //< SocketType_TCP + SOCK_DGRAM //< SocketType_UDP + }; + static_assert(sizeof(socketType) / sizeof(int) == SocketType_Max + 1, "Socket type array is incomplete"); + + return socketType[type]; + } + + void SocketImpl::Uninitialize() + { + WSACleanup(); + } + + SocketHandle SocketImpl::InvalidHandle = INVALID_SOCKET; + WSADATA SocketImpl::s_WSA; +} diff --git a/src/Nazara/Network/Win32/SocketImpl.hpp b/src/Nazara/Network/Win32/SocketImpl.hpp new file mode 100644 index 000000000..9eb2c11ff --- /dev/null +++ b/src/Nazara/Network/Win32/SocketImpl.hpp @@ -0,0 +1,63 @@ +// Copyright (C) 2015 J茅r么me Leclercq +// This file is part of the "Nazara Engine - Network module" +// For conditions of distribution and use, see copyright notice in Config.hpp + +#include +#include +#include +#include + +namespace Nz +{ + class SocketImpl + { + public: + SocketImpl() = delete; + ~SocketImpl() = delete; + + static SocketHandle Accept(SocketHandle handle, IpAddress* address, SocketError* error); + + static SocketState Bind(SocketHandle handle, const IpAddress& address, SocketError* error); + + static SocketHandle Create(NetProtocol protocol, SocketType type, SocketError* error); + + static void ClearErrorCode(SocketHandle handle); + static void Close(SocketHandle handle); + + static SocketState Connect(SocketHandle handle, const IpAddress& address, SocketError* error); + static SocketState Connect(SocketHandle handle, const IpAddress& address, UInt64 msTimeout, SocketError* error); + + static bool Initialize(); + + static SocketError GetLastError(SocketHandle handle, SocketError* error = nullptr); + static int GetLastErrorCode(); + static int GetLastErrorCode(SocketHandle handle, SocketError* error = nullptr); + + static SocketState Listen(SocketHandle handle, const IpAddress& address, unsigned queueSize, SocketError* error); + + static unsigned int QueryAvailableBytes(SocketHandle handle, SocketError* error = nullptr); + static unsigned int QueryMaxDatagramSize(SocketHandle handle, SocketError* error = nullptr); + static IpAddress QueryPeerAddress(SocketHandle handle, SocketError* error = nullptr); + static IpAddress QuerySocketAddress(SocketHandle handle, SocketError* error = nullptr); + + static bool Receive(SocketHandle handle, void* buffer, int length, int* read, SocketError* error); + static bool ReceiveFrom(SocketHandle handle, void* buffer, int length, IpAddress* from, int* read, SocketError* error); + + static bool Send(SocketHandle handle, const void* buffer, int length, int* sent, SocketError* error); + static bool SendTo(SocketHandle handle, const void* buffer, int length, const IpAddress& to, int* sent, SocketError* error); + + static bool SetBlocking(SocketHandle handle, bool blocking, SocketError* error = nullptr); + static bool SetKeepAlive(SocketHandle handle, bool enabled, UInt64 msTime, UInt64 msInterval, SocketError* error = nullptr); + + static SocketError TranslateWSAErrorToSocketError(int error); + static int TranslateNetProtocolToAF(NetProtocol protocol); + static int TranslateSocketTypeToSock(SocketType type); + + static void Uninitialize(); + + static SocketHandle InvalidHandle; + + private: + static WSADATA s_WSA; + }; +}