diff --git a/CMakeLists.txt b/CMakeLists.txt index edc02548..3fc819bb 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -153,6 +153,17 @@ add_library(AthenaZelda EXCLUDE_FROM_ALL include/Athena/SkywardSwordQuest.hpp ) +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) + # 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..7ffd78e3 --- /dev/null +++ b/include/Athena/Socket.hpp @@ -0,0 +1,44 @@ +#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; +}; + +} +} + +#endif 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 new file mode 100644 index 00000000..ff913cc8 --- /dev/null +++ b/src/Athena/IPAddress.cpp @@ -0,0 +1,107 @@ +#include "Athena/IPAddress.hpp" +#include "sockwrap.h" // To resolve local IP + +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); +} + +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") + { + 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; + memset(&hints, 0, sizeof(addrinfo)); + hints.ai_family = AF_INET; + addrinfo* result = nullptr; + if (getaddrinfo(address.c_str(), nullptr, &hints, &result) == 0) + { + 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..f1f784d6 --- /dev/null +++ b/src/Athena/Socket.cpp @@ -0,0 +1,71 @@ +#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); +} + +#if _WIN32 +struct WSADerpHandler +{ + WSADerpHandler() + { + WSADATA init; + WSAStartup(MAKEWORD(2, 2), &init); + } + + ~WSADerpHandler() + { + WSACleanup(); + } +}; + +static const WSADerpHandler __wsaderp__; +#endif + +} +} 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 +}