From 9e7733eb021567e480cb8648394c82f2e8d644bb Mon Sep 17 00:00:00 2001 From: Phillip Stephens Date: Tue, 27 Oct 2015 01:16:56 -0700 Subject: [PATCH 1/3] Initial networking --- CMakeLists.txt | 9 ++++ include/Athena/IPAddress.hpp | 44 ++++++++++++++++ include/Athena/OAuth.hpp | 17 +++++++ include/Athena/Socket.hpp | 16 ++++++ src/Athena/IPAddress.cpp | 97 ++++++++++++++++++++++++++++++++++++ src/Athena/OAuth.cpp | 1 + src/Athena/Socket.cpp | 1 + 7 files changed, 185 insertions(+) create mode 100644 include/Athena/IPAddress.hpp create mode 100644 include/Athena/OAuth.hpp create mode 100644 include/Athena/Socket.hpp create mode 100644 src/Athena/IPAddress.cpp create mode 100644 src/Athena/OAuth.cpp create mode 100644 src/Athena/Socket.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index edc02548..0f10297b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -153,6 +153,15 @@ add_library(AthenaZelda EXCLUDE_FROM_ALL include/Athena/SkywardSwordQuest.hpp ) +add_library(AthenaNet + src/Athena/Socket.cpp + src/Athena/OAuth.cpp + src/Athena/IPAddress.cpp + + include/Athena/Socket.hpp + include/Athena/OAuth.hpp + include/Athena/IPAddress.hpp) + # Icon set(ATHENA_ICO ${CMAKE_CURRENT_SOURCE_DIR}/Athena.ico) diff --git a/include/Athena/IPAddress.hpp b/include/Athena/IPAddress.hpp new file mode 100644 index 00000000..a56e031d --- /dev/null +++ b/include/Athena/IPAddress.hpp @@ -0,0 +1,44 @@ +#ifndef IPADDRESS_HPP +#define IPADDRESS_HPP + +#include "Athena/Global.hpp" +#include + +#include +#include + +namespace Athena +{ +namespace net +{ +class IPAddress +{ +public: + + static const IPAddress None; + static const IPAddress Any; + static const IPAddress Localhost; + static const IPAddress Broadcast; + + IPAddress() : m_address(~0u), m_valid(false) {} + + IPAddress(const std::string& address); + + IPAddress(atUint8 a, atUint8 b, atUint8 c, atUint8 d); + + IPAddress(atUint32 address); + + const std::string toString() const; + const atUint32 toInt() const; + + static IPAddress localAddress(); +private: + + atUint32 m_address; + bool m_valid; + void resolve(const std::string& address); +}; +} +} + +#endif diff --git a/include/Athena/OAuth.hpp b/include/Athena/OAuth.hpp new file mode 100644 index 00000000..22fee295 --- /dev/null +++ b/include/Athena/OAuth.hpp @@ -0,0 +1,17 @@ +#ifndef OAUTH_HPP +#define OAUTH_HPP + +#include + +namespace Athena +{ +namespace net +{ +class OAuth +{ + +}; +} +} + +#endif diff --git a/include/Athena/Socket.hpp b/include/Athena/Socket.hpp new file mode 100644 index 00000000..1f7bf895 --- /dev/null +++ b/include/Athena/Socket.hpp @@ -0,0 +1,16 @@ +#ifndef SOCKET_HPP +#define SOCKET_HPP + +namespace Athena +{ +namespace net +{ +class Socket +{ +public: +private: +}; +} +} + +#endif diff --git a/src/Athena/IPAddress.cpp b/src/Athena/IPAddress.cpp new file mode 100644 index 00000000..a75f9324 --- /dev/null +++ b/src/Athena/IPAddress.cpp @@ -0,0 +1,97 @@ +#include "Athena/IPAddress.hpp" + +#ifdef __unix__ +#include +#include +#include +#include +#include +#include +#include +#elif defined(_WIN32) +#ifdef _WIN32_WINDOWS +#undef _WIN32_WINDOWS +#endif +#ifdef _WIN32_WINNT +#undef _WIN32_WINNT +#endif +#define _WIN32_WINDOWS 0x0501 +#define _WIN32_WINNT 0x0501 +#include +#include +#endif + +namespace Athena +{ +namespace net +{ +const IPAddress IPAddress::Any; +const IPAddress IPAddress::None = IPAddress( 0, 0, 0, 0); +const IPAddress IPAddress::Localhost = IPAddress(127, 0, 0, 1); +const IPAddress IPAddress::Broadcast = IPAddress(255, 255, 255, 255); + +IPAddress::IPAddress(const std::string& address) + : m_valid(false) +{ + resolve(address); +} + +IPAddress::IPAddress(atUint8 a, atUint8 b, atUint8 c, atUint8 d) + : m_address(htonl((a << 24)| (b << 16) | (c << 8) | d)), + m_valid(true) +{ +} + +IPAddress::IPAddress(atUint32 address) + : m_address(htonl(address)), + m_valid(true) +{ +} + +const std::string IPAddress::toString() const +{ + in_addr address; + address.s_addr = m_address; + return inet_ntoa(address); +} + +void IPAddress::resolve(const std::string& address) +{ + if (address == "0.0.0.0") + { + m_address = 0; + m_valid = true; + } + else if(address == "255.255.255.255") + { + m_address = ~0u; + m_valid = true; + } + else + { + atUint32 ip = inet_addr(address.c_str()); + if (ip == INADDR_NONE) + { + addrinfo hints = {0}; + hints.ai_family = AF_INET; + addrinfo* result = nullptr; + if (getaddrinfo(address.c_str(), NULL, &hints, &result)) + { + if (result) + { + ip = reinterpret_cast(result->ai_addr)->sin_addr.s_addr; + freeaddrinfo(result); + m_address = ip; + m_valid = true; + } + } + } + else + { + m_address = ip; + m_valid = true; + } + } +} +} +} diff --git a/src/Athena/OAuth.cpp b/src/Athena/OAuth.cpp new file mode 100644 index 00000000..67d3cd86 --- /dev/null +++ b/src/Athena/OAuth.cpp @@ -0,0 +1 @@ +#include "Athena/OAuth.hpp" diff --git a/src/Athena/Socket.cpp b/src/Athena/Socket.cpp new file mode 100644 index 00000000..1bd5f24f --- /dev/null +++ b/src/Athena/Socket.cpp @@ -0,0 +1 @@ +#include "Athena/Socket.hpp" From 7c4d0bb6fd62b48c3b7c5bee38b31a8fec0899e6 Mon Sep 17 00:00:00 2001 From: Phillip Stephens Date: Tue, 27 Oct 2015 08:47:07 -0700 Subject: [PATCH 2/3] Implement base Socket class Fix derp in IPAddress::resolve --- CMakeLists.txt | 2 + include/Athena/Socket.hpp | 28 ++++++++++++ include/sockwrap.h | 61 +++++++++++++++++++++++++ src/Athena/IPAddress.cpp | 56 +++++++++++++---------- src/Athena/Socket.cpp | 52 ++++++++++++++++++++++ src/sockwrap.c | 93 +++++++++++++++++++++++++++++++++++++++ 6 files changed, 269 insertions(+), 23 deletions(-) create mode 100644 include/sockwrap.h create mode 100644 src/sockwrap.c diff --git a/CMakeLists.txt b/CMakeLists.txt index 0f10297b..3fc819bb 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -154,10 +154,12 @@ add_library(AthenaZelda EXCLUDE_FROM_ALL ) add_library(AthenaNet + src/sockwrap.c src/Athena/Socket.cpp src/Athena/OAuth.cpp src/Athena/IPAddress.cpp + include/sockwrap.h include/Athena/Socket.hpp include/Athena/OAuth.hpp include/Athena/IPAddress.hpp) diff --git a/include/Athena/Socket.hpp b/include/Athena/Socket.hpp index 1f7bf895..7ffd78e3 100644 --- a/include/Athena/Socket.hpp +++ b/include/Athena/Socket.hpp @@ -1,15 +1,43 @@ #ifndef SOCKET_HPP #define SOCKET_HPP +#include "Athena/Global.hpp" +#include "sockwrap.h" + namespace Athena { namespace net { + class Socket { + // Disable copying public: + enum Type + { + TCP, + UDP + }; + + explicit Socket(Type type = TCP); + + virtual ~Socket() { close() ; } + + void setBlocking(bool blocking); + bool isBlocking() const { return m_isBlocking; } +protected: + sockhandle_t handle() { return m_handle; } + void create(); + void close(); private: + // Disable copying + Socket(const Socket&)=delete; + Socket& operator=(const Socket&)=delete; + Type m_type; + sockhandle_t m_handle; + bool m_isBlocking; }; + } } diff --git a/include/sockwrap.h b/include/sockwrap.h new file mode 100644 index 00000000..885eb8a4 --- /dev/null +++ b/include/sockwrap.h @@ -0,0 +1,61 @@ +#ifndef SOCKWRAP_H +#define SOCKWRAP_H + +#include +#include + +#ifdef _WIN32 +#ifdef _WIN32_WINDOWS +#undef _WIN32_WINDOWS +#endif +#ifdef _WIN32_WINNT +#undef _WIN32_WINNT +#endif +#define _WIN32_WINDOWS 0x0501 +#define _WIN32_WINNT 0x0501 +#include +#include +#include + +typedef UINT_PTR sockhandle_t; +typedef int addrlen_t; +#else +#include +#include +#include +#include +#include +#include +#include + +typedef int32_t sockhandle_t; +typedef socklen_t addrlen_t; +#endif + +#define ANY_PORT 0 + +#ifdef __cplusplus +extern "C" { +#endif + +// TODO: More granular errors +typedef enum +{ + SS_Done, + SS_NotReady, + SS_Partial, + SS_Disconnected, + SS_Error +} sockstatus_t; + +struct sockaddr_in sock_create_address(uint32_t address, uint16_t port); +void sock_close_socket(sockhandle_t sock); +void sock_set_blocking(sockhandle_t sock, bool block); +sockstatus_t sock_error_status(); +sockhandle_t sock_invalid_socket(); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/src/Athena/IPAddress.cpp b/src/Athena/IPAddress.cpp index a75f9324..ff913cc8 100644 --- a/src/Athena/IPAddress.cpp +++ b/src/Athena/IPAddress.cpp @@ -1,25 +1,5 @@ #include "Athena/IPAddress.hpp" - -#ifdef __unix__ -#include -#include -#include -#include -#include -#include -#include -#elif defined(_WIN32) -#ifdef _WIN32_WINDOWS -#undef _WIN32_WINDOWS -#endif -#ifdef _WIN32_WINNT -#undef _WIN32_WINNT -#endif -#define _WIN32_WINDOWS 0x0501 -#define _WIN32_WINNT 0x0501 -#include -#include -#endif +#include "sockwrap.h" // To resolve local IP namespace Athena { @@ -55,6 +35,35 @@ const std::string IPAddress::toString() const return inet_ntoa(address); } +const atUint32 IPAddress::toInt() const +{ + return ntohl(m_address); +} + +IPAddress IPAddress::localAddress() +{ + sockhandle_t sock = socket(PF_INET, SOCK_DGRAM, 0); + if (sock == sock_invalid_socket()) + return IPAddress(); + + struct sockaddr_in address = sock_create_address(ntohl(INADDR_LOOPBACK), 9); + if (connect(sock, reinterpret_cast(&address), sizeof(struct sockaddr_in)) == -1) + { + sock_close_socket(sock); + return IPAddress(); + } + + addrlen_t size = sizeof(address); + if (getsockname(sock, reinterpret_cast(&address), &size) == -1) + { + sock_close_socket(sock); + return IPAddress(); + } + + sock_close_socket(sock); + return IPAddress(ntohl(address.sin_addr.s_addr)); +} + void IPAddress::resolve(const std::string& address) { if (address == "0.0.0.0") @@ -72,10 +81,11 @@ void IPAddress::resolve(const std::string& address) atUint32 ip = inet_addr(address.c_str()); if (ip == INADDR_NONE) { - addrinfo hints = {0}; + addrinfo hints; + memset(&hints, 0, sizeof(addrinfo)); hints.ai_family = AF_INET; addrinfo* result = nullptr; - if (getaddrinfo(address.c_str(), NULL, &hints, &result)) + if (getaddrinfo(address.c_str(), nullptr, &hints, &result) == 0) { if (result) { diff --git a/src/Athena/Socket.cpp b/src/Athena/Socket.cpp index 1bd5f24f..df60f9d0 100644 --- a/src/Athena/Socket.cpp +++ b/src/Athena/Socket.cpp @@ -1 +1,53 @@ #include "Athena/Socket.hpp" + +namespace Athena +{ +namespace net +{ + +Socket::Socket(Socket::Type type) + : m_type(type), + m_handle(sock_invalid_socket()), + m_isBlocking(true) +{} + +void Socket::create() +{ + if (m_handle == sock_invalid_socket()) + { + m_handle = socket(PF_INET, m_type == TCP ? SOCK_STREAM : SOCK_DGRAM, 0); + setBlocking(m_isBlocking); + + int yes = 1; + if (m_type == TCP) + { + // Disable Nagle algorithm + if (setsockopt(m_handle, IPPROTO_TCP, TCP_NODELAY, &yes, sizeof(int)) == -1) + atWarning("Failed to set socket option \"TCP_NODELAY\", TCP packets will be buffered"); + +#ifdef __APPLE__ + if (setsockopt(m_handle, SOL_SOCKET, SO_NOSIGPIPE, &yes, sizeof(int)) == -1) + atWarning("Failed to set socket option \"SO_NOSIGPIPE\""); +#endif + } + else + { + if (setsockopt(m_handle, SOL_SOCKET, SO_BROADCAST, &yes, sizeof(int)) == -1) + atWarning("Failed to enable broadcast on UDP socket"); + } + } +} + +void Socket::close() +{ + sock_close_socket(m_handle); + m_handle = sock_invalid_socket(); +} + +void Socket::setBlocking(bool blocking) +{ + sock_set_blocking(m_handle, blocking); +} + +} +} diff --git a/src/sockwrap.c b/src/sockwrap.c new file mode 100644 index 00000000..c6dc1698 --- /dev/null +++ b/src/sockwrap.c @@ -0,0 +1,93 @@ +#include "sockwrap.h" + +#ifndef _WIN32 +#include +#include +#endif + +struct sockaddr_in sock_create_address(uint32_t address, uint16_t port) +{ + struct sockaddr_in addr = {0}; + addr.sin_addr.s_addr = htonl(address); + addr.sin_family = AF_INET; + addr.sin_port = htons(port); + +#ifdef __APPLE__ + addr.sin_len = sizeof(addr); +#endif + + return addr; +} + +void sock_close_socket(sockhandle_t sock) +{ +#ifdef _WIN32 + closesocket(sock); +#else + close(sock); +#endif +} + +void sock_set_blocking(sockhandle_t sock, bool block) +{ +#ifdef _WIN32 + u_long blocking = block ? 0 : 1; + ioctlsocket(sock, FIONBIO, &blocking); +#else + int status = fcntl(sock, F_GETFL); + if (block) + status &= ~O_NONBLOCK; + else + status |= O_NONBLOCK; + + fcntl(sock, F_SETFL, status); +#endif +} + +sockstatus_t sock_error_status() +{ +#ifdef _WIN32 + switch(WSAGetLastError()) + { + case WSAEWOULDBLOCK: + case WSAEALREADY: + return SS_NotReady; + case WSAECONNABORTED: + case WSAECONNRESET: + case WSATIMEDOUT: + case WSAENETRESET: + case WSAENOTCONN: + return SS_Disconnected; + case WSAEISCONN: + return SS_Done; + default: + return SS_Error; + } +#else + if (errno == EAGAIN || errno == EINPROGRESS) + return SS_NotReady; + + switch(errno) + { + case EWOULDBLOCK: return SS_NotReady; + case ECONNABORTED: + case ECONNRESET: + case ETIMEDOUT: + case ENETRESET: + case ENOTCONN: + case EPIPE: + return SS_Disconnected; + default: + return SS_Error; + } +#endif +} + +sockhandle_t sock_invalid_socket() +{ +#if _WIN32 + return INVALID_SOCKET; +#else + return -1; +#endif +} From a411b1084cb67b49bdf1768b537fcfc0d6792fd9 Mon Sep 17 00:00:00 2001 From: Phillip Stephens Date: Tue, 27 Oct 2015 08:51:38 -0700 Subject: [PATCH 3/3] Add WSA initializer to handle winderp --- src/Athena/Socket.cpp | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/src/Athena/Socket.cpp b/src/Athena/Socket.cpp index df60f9d0..f1f784d6 100644 --- a/src/Athena/Socket.cpp +++ b/src/Athena/Socket.cpp @@ -49,5 +49,23 @@ void Socket::setBlocking(bool blocking) sock_set_blocking(m_handle, blocking); } +#if _WIN32 +struct WSADerpHandler +{ + WSADerpHandler() + { + WSADATA init; + WSAStartup(MAKEWORD(2, 2), &init); + } + + ~WSADerpHandler() + { + WSACleanup(); + } +}; + +static const WSADerpHandler __wsaderp__; +#endif + } }