From 1d04ac8f1340007fa962d39a32c12925e3f6cefb Mon Sep 17 00:00:00 2001 From: Gawaboumga Date: Wed, 30 Dec 2015 15:20:07 +0100 Subject: [PATCH] First implementation of Posix Network Former-commit-id: 2b73870d8eef4dc92038224164396390ac43df46 --- src/Nazara/Network/AbstractSocket.cpp | 4 +- src/Nazara/Network/IpAddress.cpp | 2 + src/Nazara/Network/Network.cpp | 2 + src/Nazara/Network/Posix/IpAddressImpl.cpp | 309 +++++++++ src/Nazara/Network/Posix/IpAddressImpl.hpp | 32 + src/Nazara/Network/Posix/SocketImpl.cpp | 746 +++++++++++++++++++++ src/Nazara/Network/Posix/SocketImpl.hpp | 68 ++ src/Nazara/Network/TcpClient.cpp | 2 + src/Nazara/Network/TcpServer.cpp | 4 +- src/Nazara/Network/UdpSocket.cpp | 2 + 10 files changed, 1169 insertions(+), 2 deletions(-) create mode 100644 src/Nazara/Network/Posix/IpAddressImpl.cpp create mode 100644 src/Nazara/Network/Posix/IpAddressImpl.hpp create mode 100644 src/Nazara/Network/Posix/SocketImpl.cpp create mode 100644 src/Nazara/Network/Posix/SocketImpl.hpp diff --git a/src/Nazara/Network/AbstractSocket.cpp b/src/Nazara/Network/AbstractSocket.cpp index d86f93501..1406741ff 100644 --- a/src/Nazara/Network/AbstractSocket.cpp +++ b/src/Nazara/Network/AbstractSocket.cpp @@ -9,6 +9,8 @@ #if defined(NAZARA_PLATFORM_WINDOWS) #include +#elif defined(NAZARA_PLATFORM_POSIX) +#include #else #error Missing implementation: Socket #endif @@ -105,4 +107,4 @@ namespace Nz m_handle = handle; OnOpened(); } -} \ No newline at end of file +} diff --git a/src/Nazara/Network/IpAddress.cpp b/src/Nazara/Network/IpAddress.cpp index b44412aca..d40e2a581 100644 --- a/src/Nazara/Network/IpAddress.cpp +++ b/src/Nazara/Network/IpAddress.cpp @@ -12,6 +12,8 @@ #if defined(NAZARA_PLATFORM_WINDOWS) #include +#elif defined(NAZARA_PLATFORM_POSIX) +#include #else #error Missing implementation: Network #endif diff --git a/src/Nazara/Network/Network.cpp b/src/Nazara/Network/Network.cpp index ffe4842ca..d2f0d0d51 100644 --- a/src/Nazara/Network/Network.cpp +++ b/src/Nazara/Network/Network.cpp @@ -12,6 +12,8 @@ #if defined(NAZARA_PLATFORM_WINDOWS) #include +#elif defined(NAZARA_PLATFORM_POSIX) +#include #else #error Missing implementation: Network #endif diff --git a/src/Nazara/Network/Posix/IpAddressImpl.cpp b/src/Nazara/Network/Posix/IpAddressImpl.cpp new file mode 100644 index 000000000..1e4f6a89e --- /dev/null +++ b/src/Nazara/Network/Posix/IpAddressImpl.cpp @@ -0,0 +1,309 @@ +// 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 +{ + namespace Detail + { + 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, int flags) + { + std::array hostnameBuffer; + std::array serviceBuffer; + + int result = getnameinfo(socketAddress, socketLen, hostnameBuffer.data(), hostnameBuffer.size(), serviceBuffer.data(), serviceBuffer.size(), flags); + if (result == 0) + { + if (hostname) + hostname->Set(hostnameBuffer.data()); + + if (service) + service->Set(serviceBuffer.data()); + } + + return result; + } + + void FreeAddressInfo(addrinfoImpl* results) + { + freeaddrinfo(results); + } + + IpAddress::IPv4 convertSockaddrToIPv4(const in_addr& addr) + { + union byteToInt + { + UInt8 b[sizeof(uint32_t)]; + uint32_t i; + }; + + byteToInt hostOrder; + hostOrder.i = ntohl(addr.s_addr); + + return { hostOrder.b[3], hostOrder.b[2], hostOrder.b[1], hostOrder.b[0] }; + } + + IpAddress::IPv6 convertSockaddr6ToIPv6(const in6_addr& addr) + { + union byteToInt + { + UInt8 b[sizeof(uint32_t)]; + uint32_t i; + }; + + IpAddress::IPv6 ipv6Addr; + + for (auto i = 0; i < 4; ++i) + { + byteToInt hostOrder; + hostOrder.i = 0; + std::copy(addr.s6_addr + 4 * i, addr.s6_addr + 4 * (i + 1), hostOrder.b); + ipv6Addr[2 * i] = (hostOrder.b[3] << 8) + hostOrder.b[2]; + ipv6Addr[2 * i + 1] = (hostOrder.b[1] << 8) + hostOrder.b[0]; + } + + return ipv6Addr; + } + } + + IpAddress IpAddressImpl::FromAddrinfo(const addrinfo* 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::Invalid; + } + + 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::Invalid; + } + + IpAddress IpAddressImpl::FromSockAddr(const sockaddr_in* addressv4) + { + IpAddress::IPv4 ip4Address = Detail::convertSockaddrToIPv4(addressv4->sin_addr); + return IpAddress(ip4Address, ntohs(addressv4->sin_port)); + } + + IpAddress IpAddressImpl::FromSockAddr(const sockaddr_in6* addressv6) + { + IpAddress::IPv6 ip6Address = Detail::convertSockaddr6ToIPv6(addressv6->sin6_addr); + return IpAddress(ip6Address, ntohs(addressv6->sin6_port)); + } + + bool IpAddressImpl::ResolveAddress(const IpAddress& ipAddress, String* hostname, String* service, ResolveError* error) + { + SockAddrBuffer socketAddress; + socklen_t socketAddressLen = ToSockAddr(ipAddress, socketAddress.data()); + + if (Detail::GetHostnameInfo(reinterpret_cast(socketAddress.data()), socketAddressLen, hostname, service, NI_NUMERICSERV) != 0) + { + if (error) + *error = TranslateEAIErrorToResolveError(errno); + + return false; + } + + if (error) + *error = ResolveError_NoError; + + return true; + } + + std::vector IpAddressImpl::ResolveHostname(NetProtocol procol, const String& hostname, const String& service, ResolveError* error) + { + std::vector results; + + Detail::addrinfoImpl hints; + std::memset(&hints, 0, sizeof(Detail::addrinfoImpl)); + hints.ai_family = SocketImpl::TranslateNetProtocolToAF(procol); + hints.ai_flags = AI_CANONNAME; + hints.ai_socktype = SOCK_STREAM; + + Detail::addrinfoImpl* servinfo; + if (Detail::GetAddressInfo(hostname, service, &hints, &servinfo) != 0) + { + if (error) + *error = TranslateEAIErrorToResolveError(errno); + + 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.protocol = TranslatePFToNetProtocol(p->ai_family); + result.socketType = TranslateSockToNetProtocol(p->ai_socktype); + + results.push_back(result); + } + + if (error) + *error = ResolveError_NoError; + + 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_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) + { + UInt16 networkOrder = htons(address[i]); + socketAddress->sin6_addr.s6_addr[2 * i] = networkOrder / 256; + socketAddress->sin6_addr.s6_addr[2 * i + 1] = networkOrder % 256; + } + + return sizeof(sockaddr_in6); + } + + default: + NazaraInternalError("Unhandled ip protocol (0x" + String::Number(ipAddress.GetProtocol()) + ')'); + break; + } + } + + NazaraError("Invalid ip address"); + return 0; + } + + NetProtocol IpAddressImpl::TranslatePFToNetProtocol(int family) + { + switch (family) + { + case PF_INET: + return NetProtocol_IPv4; + + case PF_INET6: + return NetProtocol_IPv6; + + default: + return NetProtocol_Unknown; + } + } + + SocketType IpAddressImpl::TranslateSockToNetProtocol(int socketType) + { + switch (socketType) + { + case SOCK_STREAM: + return SocketType_TCP; + + case SOCK_DGRAM: + return SocketType_UDP; + + case SOCK_RAW: + return SocketType_Raw; + + default: + return SocketType_Unknown; + } + } + + ResolveError IpAddressImpl::TranslateEAIErrorToResolveError(int error) + { + // http://man7.org/linux/man-pages/man3/gai_strerror.3.html + switch (error) + { + case 0: + return ResolveError_NoError; + + // Engine error + case EAI_BADFLAGS: + case EAI_SYSTEM: + return ResolveError_Internal; + + case EAI_FAMILY: + case EAI_SERVICE: + case EAI_SOCKTYPE: + return ResolveError_ProtocolNotSupported; + + case EAI_NONAME: + return ResolveError_NotFound; + + case EAI_FAIL: + return ResolveError_NonRecoverable; + + case EAI_NODATA: + return ResolveError_NotInitialized; + + case EAI_MEMORY: + return ResolveError_ResourceError; + + case EAI_AGAIN: + return ResolveError_TemporaryFailure; + } + + NazaraWarning("Unhandled EAI error: " + Error::GetLastSystemError(error) + " (" + String::Number(error) + ") as " + gai_strerror(error)); + return ResolveError_Unknown; + } +} diff --git a/src/Nazara/Network/Posix/IpAddressImpl.hpp b/src/Nazara/Network/Posix/IpAddressImpl.hpp new file mode 100644 index 000000000..6ca919e78 --- /dev/null +++ b/src/Nazara/Network/Posix/IpAddressImpl.hpp @@ -0,0 +1,32 @@ +// 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 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, ResolveError* error); + static std::vector ResolveHostname(NetProtocol procol, const String& hostname, const String& service, ResolveError* error); + + static socklen_t ToSockAddr(const IpAddress& ipAddress, void* buffer); + static NetProtocol TranslatePFToNetProtocol(int family); + static SocketType TranslateSockToNetProtocol(int socketType); + static ResolveError TranslateEAIErrorToResolveError(int error); + }; +} diff --git a/src/Nazara/Network/Posix/SocketImpl.cpp b/src/Nazara/Network/Posix/SocketImpl.cpp new file mode 100644 index 000000000..cf5a638cf --- /dev/null +++ b/src/Nazara/Network/Posix/SocketImpl.cpp @@ -0,0 +1,746 @@ +// 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 +#include +#include +#include + +namespace Nz +{ + constexpr int SOCKET_ERROR = -1; + + SocketHandle SocketImpl::Accept(SocketHandle handle, IpAddress* address, SocketError* error) + { + NazaraAssert(handle != InvalidHandle, "Invalid handle"); + + IpAddressImpl::SockAddrBuffer nameBuffer; + socklen_t bufferLength = sizeof(sockaddr_in); + + 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 = TranslateErrnoToResolveError(GetLastErrorCode()); + } + + 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 = TranslateErrnoToResolveError(GetLastErrorCode()); + + 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 = TranslateErrnoToResolveError(GetLastErrorCode()); + + return handle; + } + + void SocketImpl::Close(SocketHandle handle) + { + NazaraAssert(handle != InvalidHandle, "Invalid handle"); + + if (close(handle) == SOCKET_ERROR) + NazaraWarning("Failed to close socket: " + Error::GetLastSystemError(GetLastErrorCode())); + } + + void SocketImpl::ClearErrorCode(SocketHandle handle) + { + NazaraAssert(handle != InvalidHandle, "Invalid handle"); + + if (GetLastError(handle, nullptr) < 0) + NazaraWarning("Failed to clear socket error code: " + Error::GetLastSystemError(GetLastErrorCode())); + } + + 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 = GetLastErrorCode(); + switch (errorCode) //< Check for "normal errors" first + { + case EALREADY: + case EINPROGRESS: + return SocketState_Connecting; + + case EISCONN: + return SocketState_Connected; + } + + if (error) + { + if (errorCode == EADDRNOTAVAIL) + *error = SocketError_ConnectionRefused; //< ConnectionRefused seems more legit than AddressNotAvailable in connect case + else + *error = TranslateErrnoToResolveError(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 == SOCKET_ERROR) + { + int code = GetLastErrorCode(handle, error); + if (code < 0) //< GetLastErrorCode() failed + return SocketState_NotConnected; + + if (code) + { + if (error) + *error = TranslateErrnoToResolveError(code); + + return SocketState_NotConnected; + } + } + else if (ret == 0) + { + if (error) + *error = SocketError_TimedOut; + + return SocketState_NotConnected; + } + else + { + if (error) + *error = TranslateErrnoToResolveError(GetLastErrorCode()); + + return SocketState_NotConnected; + } + + if (error) + *error = SocketError_NoError; + + state = SocketState_Connected; + } + + return state; + } + + bool SocketImpl::Initialize() + { + return true; + } + + SocketError SocketImpl::GetLastError(SocketHandle handle, SocketError* error) + { + int code = GetLastErrorCode(handle, error); + if (code < 0) + return SocketError_Internal; + + return TranslateErrnoToResolveError(code); + } + + int SocketImpl::GetLastErrorCode() + { + return errno; + } + + int SocketImpl::GetLastErrorCode(SocketHandle handle, SocketError* error) + { + int code; + unsigned int codeLength = sizeof(code); + + if (getsockopt(handle, SOL_SOCKET, SO_ERROR, &code, &codeLength) == SOCKET_ERROR) + { + if (error) + *error = TranslateErrnoToResolveError(GetLastErrorCode()); + + 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 = TranslateErrnoToResolveError(GetLastErrorCode()); + + return SocketState_NotConnected; + } + + if (listen(handle, queueSize) == SOCKET_ERROR) + { + if (error) + *error = TranslateErrnoToResolveError(GetLastErrorCode()); + + 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 (ioctl(handle, FIONREAD, &availableBytes) == SOCKET_ERROR) + { + if (error) + *error = TranslateErrnoToResolveError(GetLastErrorCode()); + + return 0; + } + + if (error) + *error = SocketError_NoError; + + return availableBytes; + } + + bool SocketImpl::QueryBroadcasting(SocketHandle handle, SocketError* error) + { + bool code; + unsigned int codeLength = sizeof(code); + + if (getsockopt(handle, SOL_SOCKET, SO_BROADCAST, &code, &codeLength) == SOCKET_ERROR) + { + if (error) + *error = TranslateErrnoToResolveError(GetLastErrorCode()); + + return false; + } + + if (error) + *error = SocketError_NoError; + + return code; + } + + bool SocketImpl::QueryKeepAlive(SocketHandle handle, SocketError* error) + { + bool code; + unsigned int codeLength = sizeof(code); + + if (getsockopt(handle, SOL_SOCKET, SO_KEEPALIVE, &code, &codeLength) == SOCKET_ERROR) + { + if (error) + *error = TranslateErrnoToResolveError(GetLastErrorCode()); + + return false; + } + + if (error) + *error = SocketError_NoError; + + return code; + } + + unsigned int SocketImpl::QueryMaxDatagramSize(SocketHandle handle, SocketError* error) + { + unsigned int code; + unsigned int codeLength = sizeof(code); + + if (getsockopt(handle, IPPROTO_IP, IP_MTU, &code, &codeLength) == SOCKET_ERROR) + { + if (error) + *error = TranslateErrnoToResolveError(GetLastErrorCode()); + + return -1; + } + + if (error) + *error = SocketError_NoError; + + return code; + } + + bool SocketImpl::QueryNoDelay(SocketHandle handle, SocketError* error) + { + bool code; + unsigned int codeLength = sizeof(code); + + if (getsockopt(handle, IPPROTO_TCP, TCP_NODELAY, &code, &codeLength) == SOCKET_ERROR) + { + if (error) + *error = TranslateErrnoToResolveError(GetLastErrorCode()); + + return false; + } + + if (error) + *error = SocketError_NoError; + + return code; + } + + IpAddress SocketImpl::QueryPeerAddress(SocketHandle handle, SocketError* error) + { + NazaraAssert(handle != InvalidHandle, "Invalid handle"); + + IpAddressImpl::SockAddrBuffer nameBuffer; + socklen_t bufferLength = sizeof(sockaddr_in); + + if (getpeername(handle, reinterpret_cast(nameBuffer.data()), &bufferLength) == SOCKET_ERROR) + { + if (error) + *error = TranslateErrnoToResolveError(GetLastErrorCode()); + + 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; + socklen_t bufferLength = sizeof(sockaddr_in); + + if (getsockname(handle, reinterpret_cast(nameBuffer.data()), &bufferLength) == SOCKET_ERROR) + { + if (error) + { + int errorCode = GetLastErrorCode(); + if (errorCode == EINVAL) + *error = SocketError_NoError; + else + *error = TranslateErrnoToResolveError(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 = GetLastErrorCode(); + switch (errorCode) + { + case EWOULDBLOCK: + { + // If we have no data and are not blocking, return true with 0 byte read + byteRead = 0; + break; + } + + default: + { + if (error) + *error = TranslateErrnoToResolveError(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; + socklen_t bufferLength = sizeof(sockaddr_in); + + IpAddress senderIp; + + int byteRead = recvfrom(handle, buffer, length, 0, reinterpret_cast(&nameBuffer), &bufferLength); + if (byteRead == SOCKET_ERROR) + { + int errorCode = GetLastErrorCode(); + switch (errorCode) + { + case EWOULDBLOCK: + { + // 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 = TranslateErrnoToResolveError(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 = TranslateErrnoToResolveError(GetLastErrorCode()); + + 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 = TranslateErrnoToResolveError(GetLastErrorCode()); + + 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 (ioctl(handle, FIONBIO, &block) == SOCKET_ERROR) + { + if (error) + *error = TranslateErrnoToResolveError(GetLastErrorCode()); + + return false; //< Error + } + + if (error) + *error = SocketError_NoError; + + return true; + } + + bool SocketImpl::SetBroadcasting(SocketHandle handle, bool broadcasting, SocketError* error) + { + NazaraAssert(handle != InvalidHandle, "Invalid handle"); + + bool option = broadcasting; + if (setsockopt(handle, SOL_SOCKET, SO_BROADCAST, reinterpret_cast(&option), sizeof(option)) == SOCKET_ERROR) + { + if (error) + *error = TranslateErrnoToResolveError(GetLastErrorCode()); + + 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"); + + int keepAlive = enabled ? 1 : 0; + int keepIdle = msTime / 1000; // Linux works with seconds. + int keepInterval = msInterval / 1000; // Linux works with seconds. + + if (setsockopt(handle, SOL_SOCKET, SO_KEEPALIVE, &keepAlive , sizeof(keepAlive)) == SOCKET_ERROR) + { + if (error) + *error = TranslateErrnoToResolveError(GetLastErrorCode()); + + return false; //< Error + } + + if (setsockopt(handle, IPPROTO_TCP, TCP_KEEPIDLE, &keepIdle, sizeof(keepIdle)) == SOCKET_ERROR) + { + if (error) + *error = TranslateErrnoToResolveError(GetLastErrorCode()); + + return false; //< Error + } + + if (setsockopt(handle, IPPROTO_TCP, TCP_KEEPINTVL, &keepInterval, sizeof(keepInterval)) == SOCKET_ERROR) + { + if (error) + *error = TranslateErrnoToResolveError(GetLastErrorCode()); + + return false; //< Error + } + + if (error) + *error = SocketError_NoError; + + return true; + } + + bool SocketImpl::SetNoDelay(SocketHandle handle, bool nodelay, SocketError* error) + { + NazaraAssert(handle != InvalidHandle, "Invalid handle"); + + int option = nodelay ? 1 : 0; + if (setsockopt(handle, IPPROTO_TCP, TCP_NODELAY, &option, sizeof(option)) == SOCKET_ERROR) + { + if (error) + *error = TranslateErrnoToResolveError(GetLastErrorCode()); + + return false; //< Error + } + + if (error) + *error = SocketError_NoError; + + return true; + } + + SocketError SocketImpl::TranslateErrnoToResolveError(int error) + { + switch (error) + { + case 0: + return SocketError_NoError; + + // Engine error + case EACCES: + case EBADF: + case EINVAL: + case EFAULT: + case ENOTSOCK: + case EPROTOTYPE: + return SocketError_Internal; + + case EADDRNOTAVAIL: + case EADDRINUSE: + return SocketError_AddressNotAvailable; + + case EAFNOSUPPORT: + case EPFNOSUPPORT: + case EOPNOTSUPP: + case EPROTONOSUPPORT: + case ESOCKTNOSUPPORT: + return SocketError_NotSupported; + + // Those are not errors and should have been handled before the call + case EALREADY: + case EISCONN: + case EWOULDBLOCK: + return SocketError_Internal; + + case ECONNREFUSED: + return SocketError_ConnectionRefused; + + case EMSGSIZE: + return SocketError_DatagramSize; + + case EMFILE: + case ENOBUFS: + case ENOMEM: + return SocketError_ResourceError; + + case ENOTCONN: + case ESHUTDOWN: + return SocketError_ConnectionClosed; + + case EHOSTUNREACH: + return SocketError_UnreachableHost; + + case ENETDOWN: + case ENETUNREACH: + return SocketError_NetworkError; + + case ENODATA: + return SocketError_NotInitialized; + + case ETIMEDOUT: + return SocketError_TimedOut; + } + + NazaraWarning("Unhandled POSIX 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 + -1 //< NetProtocol_Unknown + }; + 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 + -1 //< SocketType_Unknown + }; + static_assert(sizeof(socketType) / sizeof(int) == SocketType_Max + 1, "Socket type array is incomplete"); + + return socketType[type]; + } + + void SocketImpl::Uninitialize() + { + } + + SocketHandle SocketImpl::InvalidHandle = -1; + SocketImpl::socketID SocketImpl::s_socket; +} diff --git a/src/Nazara/Network/Posix/SocketImpl.hpp b/src/Nazara/Network/Posix/SocketImpl.hpp new file mode 100644 index 000000000..c23b4d3e4 --- /dev/null +++ b/src/Nazara/Network/Posix/SocketImpl.hpp @@ -0,0 +1,68 @@ +// 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 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 bool QueryBroadcasting(SocketHandle handle, SocketError* error = nullptr); + static bool QueryKeepAlive(SocketHandle handle, SocketError* error = nullptr); + static unsigned int QueryMaxDatagramSize(SocketHandle handle, SocketError* error = nullptr); + static bool QueryNoDelay(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 SetBroadcasting(SocketHandle handle, bool broadcasting, SocketError* error = nullptr); + static bool SetKeepAlive(SocketHandle handle, bool enabled, UInt64 msTime, UInt64 msInterval, SocketError* error = nullptr); + static bool SetNoDelay(SocketHandle handle, bool nodelay, SocketError* error = nullptr); + + static SocketError TranslateErrnoToResolveError(int error); + static int TranslateNetProtocolToAF(NetProtocol protocol); + static int TranslateSocketTypeToSock(SocketType type); + + static void Uninitialize(); + + static SocketHandle InvalidHandle; + + private: + using socketID = int; + static socketID s_socket; + }; +} diff --git a/src/Nazara/Network/TcpClient.cpp b/src/Nazara/Network/TcpClient.cpp index b91dfaffd..6bb26aef8 100644 --- a/src/Nazara/Network/TcpClient.cpp +++ b/src/Nazara/Network/TcpClient.cpp @@ -10,6 +10,8 @@ #if defined(NAZARA_PLATFORM_WINDOWS) #include +#elif defined(NAZARA_PLATFORM_POSIX) +#include #else #error Missing implementation: Socket #endif diff --git a/src/Nazara/Network/TcpServer.cpp b/src/Nazara/Network/TcpServer.cpp index 096603b8f..b5995b6f5 100644 --- a/src/Nazara/Network/TcpServer.cpp +++ b/src/Nazara/Network/TcpServer.cpp @@ -11,6 +11,8 @@ #if defined(NAZARA_PLATFORM_WINDOWS) #include +#elif defined(NAZARA_PLATFORM_POSIX) +#include #else #error Missing implementation: Socket #endif @@ -60,4 +62,4 @@ namespace Nz m_boundAddress = IpAddress::Invalid; } -} \ No newline at end of file +} diff --git a/src/Nazara/Network/UdpSocket.cpp b/src/Nazara/Network/UdpSocket.cpp index fb813b3d6..0ecd423bb 100644 --- a/src/Nazara/Network/UdpSocket.cpp +++ b/src/Nazara/Network/UdpSocket.cpp @@ -7,6 +7,8 @@ #if defined(NAZARA_PLATFORM_WINDOWS) #include +#elif defined(NAZARA_PLATFORM_POSIX) +#include #else #error Missing implementation: Socket #endif