diff options
| -rw-r--r-- | .gitignore | 1 | ||||
| -rw-r--r-- | examples/network/network_ping_pong.c | 225 | ||||
| -rw-r--r-- | examples/network/network_resolve_host.c | 80 | ||||
| -rw-r--r-- | examples/network/network_tcp_client.c | 151 | ||||
| -rw-r--r-- | examples/network/network_tcp_server.c | 165 | ||||
| -rw-r--r-- | examples/network/network_test.c | 148 | ||||
| -rw-r--r-- | examples/network/network_udp_client.c | 128 | ||||
| -rw-r--r-- | examples/network/network_udp_server.c | 134 | ||||
| -rw-r--r-- | src/raylib.h | 176 | ||||
| -rw-r--r-- | src/rnet.c | 2023 | ||||
| -rw-r--r-- | src/rnet.h | 228 |
11 files changed, 3459 insertions, 0 deletions
@@ -133,3 +133,4 @@ templates/android_project/output GPATH GRTAGS GTAGS +.vs diff --git a/examples/network/network_ping_pong.c b/examples/network/network_ping_pong.c new file mode 100644 index 00000000..719f6739 --- /dev/null +++ b/examples/network/network_ping_pong.c @@ -0,0 +1,225 @@ +/*******************************************************************************************
+ *
+ * raylib [network] example - Client/Server ping-pong
+ *
+ * Welcome to raylib!
+ *
+ * To test examples, just press F6 and execute raylib_compile_execute script
+ * Note that compiled executable is placed in the same folder as .c file
+ *
+ * You can find all basic examples on C:\raylib\raylib\examples folder or
+ * raylib official webpage: www.raylib.com
+ *
+ * Enjoy using raylib. :)
+ *
+ * This example has been created using raylib 2.0 (www.raylib.com)
+ * raylib is licensed under an unmodified zlib/libpng license (View raylib.h
+ *for details)
+ *
+ * Copyright (c) 2013-2016 Ramon Santamaria (@raysan5)
+ *
+ ********************************************************************************************/
+
+#include "raylib.h"
+#include "rnet.h"
+
+#include <assert.h>
+#include <stdio.h>
+#include <string.h>
+
+float elapsed = 0.0f;
+float delay = 1.0f;
+bool ping = false;
+bool pong = false;
+bool connected = false;
+bool client_connected = false;
+const char * pingmsg = "Ping!";
+const char * pongmsg = "Pong!";
+int msglen = 0;
+SocketConfig server_cfg = {.host = "127.0.0.1", .port = "4950", .type = SOCKET_TCP, .server = true, .nonblocking = true};
+SocketConfig client_cfg = {.host = "127.0.0.1", .port = "4950", .type = SOCKET_TCP, .nonblocking = true};
+SocketConfig connection_cfg = {.nonblocking = true};
+SocketResult *server_res = NULL;
+SocketResult *client_res = NULL;
+SocketSet * socket_set = NULL;
+Socket * connection = NULL;
+char recvBuffer[512];
+
+// Attempt to connect to the network (Either TCP, or UDP)
+void NetworkConnect()
+{
+ // If the server is configured as UDP, ignore connection requests
+ if (server_cfg.type == SOCKET_UDP && client_cfg.type == SOCKET_UDP) {
+ ping = true;
+ connected = true;
+ } else {
+ // If the client is connected, run the server code to check for a connection
+ if (client_connected) {
+ int active = CheckSockets(socket_set, 0);
+ if (active != 0) {
+ TraceLog(LOG_DEBUG,
+ "There are currently %d socket(s) with data to be processed.", active);
+ }
+ if (active > 0) {
+ if ((connection = SocketAccept(server_res->socket, &connection_cfg)) != NULL) {
+ AddSocket(socket_set, connection);
+ ping = true;
+ connected = true;
+ }
+ }
+ } else {
+ // Check if we're connected every _delay_ seconds
+ elapsed += GetFrameTime();
+ if (elapsed > delay) {
+ if (IsSocketConnected(client_res->socket)) {
+ client_connected = true;
+ }
+ elapsed = 0.0f;
+ }
+ }
+ }
+}
+
+// Once connected to the network, check the sockets for pending information
+// and when information is ready, send either a Ping or a Pong.
+void NetworkUpdate()
+{
+ // CheckSockets
+ //
+ // If any of the sockets in the socket_set are pending (received data, or requests)
+ // then mark the socket as being ready. You can check this with IsSocketReady(client_res->socket)
+ int active = CheckSockets(socket_set, 0);
+ if (active != 0) {
+ TraceLog(LOG_DEBUG,
+ "There are currently %d socket(s) with data to be processed.", active);
+ }
+
+ // IsSocketReady
+ //
+ // If the socket is ready, attempt to receive data from the socket
+ int bytesRecv = 0;
+ if (server_cfg.type == SOCKET_UDP && client_cfg.type == SOCKET_UDP) {
+ if (IsSocketReady(client_res->socket)) {
+ bytesRecv = SocketReceive(client_res->socket, recvBuffer, msglen);
+ }
+ if (IsSocketReady(server_res->socket)) {
+ bytesRecv = SocketReceive(server_res->socket, recvBuffer, msglen);
+ }
+ } else {
+ if (IsSocketReady(connection)) {
+ bytesRecv = SocketReceive(connection, recvBuffer, msglen);
+ }
+ }
+
+ // If we received data, was that data a "Ping!" or a "Pong!"
+ if (bytesRecv > 0) {
+ if (strcmp(recvBuffer, pingmsg) == 0) { pong = true; }
+ if (strcmp(recvBuffer, pongmsg) == 0) { ping = true; }
+ }
+
+ // After each delay has expired, send a response "Ping!" for a "Pong!" and vice versa
+ elapsed += GetFrameTime();
+ if (elapsed > delay) {
+ if (ping) {
+ ping = false;
+ if (server_cfg.type == SOCKET_UDP && client_cfg.type == SOCKET_UDP) {
+ SocketSend(client_res->socket, pingmsg, msglen);
+ } else {
+ SocketSend(client_res->socket, pingmsg, msglen);
+ }
+ } else if (pong) {
+ pong = false;
+ if (server_cfg.type == SOCKET_UDP && client_cfg.type == SOCKET_UDP) {
+ SocketSend(client_res->socket, pongmsg, msglen);
+ } else {
+ SocketSend(client_res->socket, pongmsg, msglen);
+ }
+ }
+ elapsed = 0.0f;
+ }
+}
+
+int main()
+{
+ // Setup
+ int screenWidth = 800;
+ int screenHeight = 450;
+ InitWindow(
+ screenWidth, screenHeight, "raylib [network] example - ping pong");
+ SetTargetFPS(60);
+ SetTraceLogLevel(LOG_DEBUG);
+
+ // Networking
+ InitNetwork();
+
+ // Create the server
+ //
+ // Performs
+ // getaddrinfo
+ // socket
+ // setsockopt
+ // bind
+ // listen
+ server_res = AllocSocketResult();
+ if (!SocketCreate(&server_cfg, server_res)) {
+ TraceLog(LOG_WARNING, "Failed to open server: status %d, errno %d",
+ server_res->status, server_res->socket->status);
+ } else {
+ if (!SocketBind(&server_cfg, server_res)) {
+ TraceLog(LOG_WARNING, "Failed to bind server: status %d, errno %d",
+ server_res->status, server_res->socket->status);
+ } else {
+ if (!(server_cfg.type == SOCKET_UDP)) {
+ if (!SocketListen(&server_cfg, server_res)) {
+ TraceLog(LOG_WARNING,
+ "Failed to start listen server: status %d, errno %d",
+ server_res->status, server_res->socket->status);
+ }
+ }
+ }
+ }
+
+ // Create the client
+ //
+ // Performs
+ // getaddrinfo
+ // socket
+ // setsockopt
+ // connect (TCP only)
+ client_res = AllocSocketResult();
+ if (!SocketCreate(&client_cfg, client_res)) {
+ TraceLog(LOG_WARNING, "Failed to open client: status %d, errno %d",
+ client_res->status, client_res->socket->status);
+ } else {
+ if (!(client_cfg.type == SOCKET_UDP)) {
+ if (!SocketConnect(&client_cfg, client_res)) {
+ TraceLog(LOG_WARNING,
+ "Failed to connect to server: status %d, errno %d",
+ client_res->status, client_res->socket->status);
+ }
+ }
+ }
+
+ // Create & Add sockets to the socket set
+ socket_set = AllocSocketSet(3);
+ msglen = strlen(pingmsg) + 1;
+ memset(recvBuffer, '\0', sizeof(recvBuffer));
+ AddSocket(socket_set, server_res->socket);
+ AddSocket(socket_set, client_res->socket);
+
+ // Main game loop
+ while (!WindowShouldClose()) {
+ BeginDrawing();
+ ClearBackground(RAYWHITE);
+ if (connected) {
+ NetworkUpdate();
+ } else {
+ NetworkConnect();
+ }
+ EndDrawing();
+ }
+
+ // Cleanup
+ CloseWindow();
+ return 0;
+}
\ No newline at end of file diff --git a/examples/network/network_resolve_host.c b/examples/network/network_resolve_host.c new file mode 100644 index 00000000..195e03b5 --- /dev/null +++ b/examples/network/network_resolve_host.c @@ -0,0 +1,80 @@ +/*******************************************************************************************
+ *
+ * raylib [network] example - Resolve Host
+ *
+ * Welcome to raylib!
+ *
+ * To test examples, just press F6 and execute raylib_compile_execute script
+ * Note that compiled executable is placed in the same folder as .c file
+ *
+ * You can find all basic examples on C:\raylib\raylib\examples folder or
+ * raylib official webpage: www.raylib.com
+ *
+ * Enjoy using raylib. :)
+ *
+ * This example has been created using raylib 2.0 (www.raylib.com)
+ * raylib is licensed under an unmodified zlib/libpng license (View raylib.h
+ *for details)
+ *
+ * Copyright (c) 2013-2016 Ramon Santamaria (@raysan5)
+ *
+ ********************************************************************************************/
+
+#include "raylib.h"
+#include "rnet.h"
+
+char buffer[ADDRESS_IPV6_ADDRSTRLEN];
+uint16_t port = 0;
+
+int main()
+{
+ // Setup
+ int screenWidth = 800;
+ int screenHeight = 450;
+ InitWindow(
+ screenWidth, screenHeight, "raylib [network] example - ping pong");
+ SetTargetFPS(60);
+
+ SetTraceLogLevel(LOG_DEBUG);
+
+ // Networking
+ InitNetwork();
+
+ AddressInformation* addr = AllocAddressList(1);
+ int count = ResolveHost(
+ NULL,
+ "5210",
+ ADDRESS_TYPE_IPV4,
+ 0 // Uncomment any of these flags
+ // ADDRESS_INFO_NUMERICHOST // or try them in conjunction to
+ // ADDRESS_INFO_NUMERICSERV // specify custom behaviour from
+ // ADDRESS_INFO_DNS_ONLY // the function getaddrinfo()
+ // ADDRESS_INFO_ALL //
+ // ADDRESS_INFO_FQDN // e.g. ADDRESS_INFO_CANONNAME | ADDRESS_INFO_NUMERICSERV
+ ,
+ addr
+ );
+
+ if (count > 0)
+ {
+ GetAddressHostAndPort(addr[0], buffer, &port);
+ TraceLog(LOG_INFO, "Resolved to ip %s::%d\n", buffer, port);
+ }
+
+ // Main game loop
+ while (!WindowShouldClose())
+ {
+ // Draw
+ BeginDrawing();
+
+ // Clear
+ ClearBackground(RAYWHITE);
+
+ // End draw
+ EndDrawing();
+ }
+
+ // Cleanup
+ CloseWindow();
+ return 0;
+}
\ No newline at end of file diff --git a/examples/network/network_tcp_client.c b/examples/network/network_tcp_client.c new file mode 100644 index 00000000..6eed205a --- /dev/null +++ b/examples/network/network_tcp_client.c @@ -0,0 +1,151 @@ +/*******************************************************************************************
+ *
+ * raylib [network] example - TCP Client
+ *
+ * Welcome to raylib!
+ *
+ * To test examples, just press F6 and execute raylib_compile_execute script
+ * Note that compiled executable is placed in the same folder as .c file
+ *
+ * You can find all basic examples on C:\raylib\raylib\examples folder or
+ * raylib official webpage: www.raylib.com
+ *
+ * Enjoy using raylib. :)
+ *
+ * This example has been created using raylib 2.0 (www.raylib.com)
+ * raylib is licensed under an unmodified zlib/libpng license (View raylib.h
+ *for details)
+ *
+ * Copyright (c) 2013-2016 Ramon Santamaria (@raysan5)
+ *
+ ********************************************************************************************/
+
+#include "raylib.h"
+#include "rnet.h"
+
+#include <assert.h>
+#include <stdio.h>
+#include <string.h>
+
+float elapsed = 0.0f;
+float delay = 1.0f;
+bool ping = false;
+bool pong = false;
+bool connected = false;
+const char * pingmsg = "Ping!";
+const char * pongmsg = "Pong!";
+int msglen = 0;
+SocketConfig client_cfg = {.host = "127.0.0.1", .port = "4950", .type = SOCKET_TCP, .nonblocking = true};
+SocketResult *client_res = NULL;
+SocketSet * socket_set = NULL;
+char recvBuffer[512];
+
+// Attempt to connect to the network (Either TCP, or UDP)
+void NetworkConnect()
+{
+ // Check if we're connected every _delay_ seconds
+ elapsed += GetFrameTime();
+ if (elapsed > delay) {
+ if (IsSocketConnected(client_res->socket)) { connected = true; }
+ elapsed = 0.0f;
+ }
+}
+
+// Once connected to the network, check the sockets for pending information
+// and when information is ready, send either a Ping or a Pong.
+void NetworkUpdate()
+{
+ // CheckSockets
+ //
+ // If any of the sockets in the socket_set are pending (received data, or requests)
+ // then mark the socket as being ready. You can check this with IsSocketReady(client_res->socket)
+ int active = CheckSockets(socket_set, 0);
+ if (active != 0) {
+ TraceLog(LOG_DEBUG,
+ "There are currently %d socket(s) with data to be processed.", active);
+ }
+
+ // IsSocketReady
+ //
+ // If the socket is ready, attempt to receive data from the socket
+ int bytesRecv = 0;
+ if (IsSocketReady(client_res->socket)) {
+ bytesRecv = SocketReceive(client_res->socket, recvBuffer, msglen);
+ }
+
+ // If we received data, was that data a "Ping!" or a "Pong!"
+ if (bytesRecv > 0) {
+ if (strcmp(recvBuffer, pingmsg) == 0) { pong = true; }
+ if (strcmp(recvBuffer, pongmsg) == 0) { ping = true; }
+ }
+
+ // After each delay has expired, send a response "Ping!" for a "Pong!" and vice versa
+ elapsed += GetFrameTime();
+ if (elapsed > delay) {
+ if (ping) {
+ ping = false;
+ SocketSend(client_res->socket, pingmsg, msglen);
+ } else if (pong) {
+ pong = false;
+ SocketSend(client_res->socket, pongmsg, msglen);
+ }
+ elapsed = 0.0f;
+ }
+}
+
+int main()
+{
+ // Setup
+ int screenWidth = 800;
+ int screenHeight = 450;
+ InitWindow(
+ screenWidth, screenHeight, "raylib [network] example - tcp client");
+ SetTargetFPS(60);
+ SetTraceLogLevel(LOG_DEBUG);
+
+ // Networking
+ InitNetwork();
+
+ // Create the client
+ //
+ // Performs
+ // getaddrinfo
+ // socket
+ // setsockopt
+ // connect (TCP only)
+ client_res = AllocSocketResult();
+ if (!SocketCreate(&client_cfg, client_res)) {
+ TraceLog(LOG_WARNING, "Failed to open client: status %d, errno %d",
+ client_res->status, client_res->socket->status);
+ } else {
+ if (!(client_cfg.type == SOCKET_UDP)) {
+ if (!SocketConnect(&client_cfg, client_res)) {
+ TraceLog(LOG_WARNING,
+ "Failed to connect to server: status %d, errno %d",
+ client_res->status, client_res->socket->status);
+ }
+ }
+ }
+
+ // Create & Add sockets to the socket set
+ socket_set = AllocSocketSet(1);
+ msglen = strlen(pingmsg) + 1;
+ memset(recvBuffer, '\0', sizeof(recvBuffer));
+ AddSocket(socket_set, client_res->socket);
+
+ // Main game loop
+ while (!WindowShouldClose()) {
+ BeginDrawing();
+ ClearBackground(RAYWHITE);
+ if (connected) {
+ NetworkUpdate();
+ } else {
+ NetworkConnect();
+ }
+ EndDrawing();
+ }
+
+ // Cleanup
+ CloseWindow();
+ return 0;
+}
\ No newline at end of file diff --git a/examples/network/network_tcp_server.c b/examples/network/network_tcp_server.c new file mode 100644 index 00000000..89e9c181 --- /dev/null +++ b/examples/network/network_tcp_server.c @@ -0,0 +1,165 @@ +/*******************************************************************************************
+ *
+ * raylib [network] example - TCP Server
+ *
+ * Welcome to raylib!
+ *
+ * To test examples, just press F6 and execute raylib_compile_execute script
+ * Note that compiled executable is placed in the same folder as .c file
+ *
+ * You can find all basic examples on C:\raylib\raylib\examples folder or
+ * raylib official webpage: www.raylib.com
+ *
+ * Enjoy using raylib. :)
+ *
+ * This example has been created using raylib 2.0 (www.raylib.com)
+ * raylib is licensed under an unmodified zlib/libpng license (View raylib.h
+ *for details)
+ *
+ * Copyright (c) 2013-2016 Ramon Santamaria (@raysan5)
+ *
+ ********************************************************************************************/
+
+#include "raylib.h"
+#include "rnet.h"
+
+#include <assert.h>
+#include <stdio.h>
+#include <string.h>
+
+float elapsed = 0.0f;
+float delay = 1.0f;
+bool ping = false;
+bool pong = false;
+bool connected = false;
+const char * pingmsg = "Ping!";
+const char * pongmsg = "Pong!";
+int msglen = 0;
+SocketConfig server_cfg = {.host = "127.0.0.1", .port = "4950", .type = SOCKET_TCP, .server = true, .nonblocking = true};
+SocketConfig connection_cfg = {.nonblocking = true};
+SocketResult *server_res = NULL;
+SocketSet * socket_set = NULL;
+Socket * connection = NULL;
+char recvBuffer[512];
+
+// Attempt to connect to the network (Either TCP, or UDP)
+void NetworkConnect()
+{
+ int active = CheckSockets(socket_set, 0);
+ if (active != 0) {
+ TraceLog(LOG_DEBUG,
+ "There are currently %d socket(s) with data to be processed.", active);
+ }
+ if (active > 0) {
+ if ((connection = SocketAccept(server_res->socket, &connection_cfg)) != NULL) {
+ AddSocket(socket_set, connection);
+ ping = true;
+ connected = true;
+ }
+ }
+}
+
+// Once connected to the network, check the sockets for pending information
+// and when information is ready, send either a Ping or a Pong.
+void NetworkUpdate()
+{
+ // CheckSockets
+ //
+ // If any of the sockets in the socket_set are pending (received data, or requests)
+ // then mark the socket as being ready. You can check this with IsSocketReady(client_res->socket)
+ int active = CheckSockets(socket_set, 0);
+ if (active != 0) {
+ TraceLog(LOG_DEBUG,
+ "There are currently %d socket(s) with data to be processed.", active);
+ }
+
+ // IsSocketReady
+ //
+ // If the socket is ready, attempt to receive data from the socket
+ int bytesRecv = 0;
+ if (IsSocketReady(connection)) {
+ bytesRecv = SocketReceive(connection, recvBuffer, msglen);
+ }
+
+ // If we received data, was that data a "Ping!" or a "Pong!"
+ if (bytesRecv > 0) {
+ if (strcmp(recvBuffer, pingmsg) == 0) { pong = true; }
+ if (strcmp(recvBuffer, pongmsg) == 0) { ping = true; }
+ }
+
+ // After each delay has expired, send a response "Ping!" for a "Pong!" and vice versa
+ elapsed += GetFrameTime();
+ if (elapsed > delay) {
+ if (ping) {
+ ping = false;
+ SocketSend(connection, pingmsg, msglen);
+ } else if (pong) {
+ pong = false;
+ SocketSend(connection, pongmsg, msglen);
+ }
+ elapsed = 0.0f;
+ }
+}
+
+int main()
+{
+ // Setup
+ int screenWidth = 800;
+ int screenHeight = 450;
+ InitWindow(
+ screenWidth, screenHeight, "raylib [network] example - tcp server");
+ SetTargetFPS(60);
+ SetTraceLogLevel(LOG_DEBUG);
+
+ // Networking
+ InitNetwork();
+
+ // Create the server
+ //
+ // Performs
+ // getaddrinfo
+ // socket
+ // setsockopt
+ // bind
+ // listen
+ server_res = AllocSocketResult();
+ if (!SocketCreate(&server_cfg, server_res)) {
+ TraceLog(LOG_WARNING, "Failed to open server: status %d, errno %d",
+ server_res->status, server_res->socket->status);
+ } else {
+ if (!SocketBind(&server_cfg, server_res)) {
+ TraceLog(LOG_WARNING, "Failed to bind server: status %d, errno %d",
+ server_res->status, server_res->socket->status);
+ } else {
+ if (!(server_cfg.type == SOCKET_UDP)) {
+ if (!SocketListen(&server_cfg, server_res)) {
+ TraceLog(LOG_WARNING,
+ "Failed to start listen server: status %d, errno %d",
+ server_res->status, server_res->socket->status);
+ }
+ }
+ }
+ }
+
+ // Create & Add sockets to the socket set
+ socket_set = AllocSocketSet(2);
+ msglen = strlen(pingmsg) + 1;
+ memset(recvBuffer, '\0', sizeof(recvBuffer));
+ AddSocket(socket_set, server_res->socket);
+
+ // Main game loop
+ while (!WindowShouldClose()) {
+ BeginDrawing();
+ ClearBackground(RAYWHITE);
+ if (connected) {
+ NetworkUpdate();
+ } else {
+ NetworkConnect();
+ }
+ EndDrawing();
+ }
+
+ // Cleanup
+ CloseWindow();
+ return 0;
+}
\ No newline at end of file diff --git a/examples/network/network_test.c b/examples/network/network_test.c new file mode 100644 index 00000000..f18a8b13 --- /dev/null +++ b/examples/network/network_test.c @@ -0,0 +1,148 @@ +/*******************************************************************************************
+ *
+ * raylib [network] example - Network Test
+ *
+ * Welcome to raylib!
+ *
+ * To test examples, just press F6 and execute raylib_compile_execute script
+ * Note that compiled executable is placed in the same folder as .c file
+ *
+ * You can find all basic examples on C:\raylib\raylib\examples folder or
+ * raylib official webpage: www.raylib.com
+ *
+ * Enjoy using raylib. :)
+ *
+ * This example has been created using raylib 2.0 (www.raylib.com)
+ * raylib is licensed under an unmodified zlib/libpng license (View raylib.h
+ *for details)
+ *
+ * Copyright (c) 2013-2016 Ramon Santamaria (@raysan5)
+ *
+ ********************************************************************************************/
+
+#include "raylib.h"
+#include "rnet.h"
+
+#include <assert.h>
+
+void test_network_initialise()
+{
+ assert(InitNetwork() == true);
+}
+
+void test_socket_result()
+{
+ SocketResult *result = AllocSocketResult();
+ assert(result != NULL);
+ FreeSocketResult(&result);
+ assert(result == NULL);
+}
+
+void test_socket()
+{
+ Socket *socket = AllocSocket();
+ assert(socket != NULL);
+ FreeSocket(&socket);
+ assert(socket == NULL);
+}
+
+void test_resolve_ip()
+{
+ const char *host = "8.8.8.8";
+ const char *port = "8080";
+ char ip[ADDRESS_IPV6_ADDRSTRLEN];
+ char service[ADDRESS_MAXSERV];
+
+ memset(ip, '\0', ADDRESS_IPV6_ADDRSTRLEN);
+ ResolveIP(host, port, NAME_INFO_NUMERICHOST, ip, service);
+ TraceLog(LOG_INFO, "Resolved %s to %s", host, ip);
+ assert(strcmp(ip, "8.8.8.8") == 0);
+
+ memset(ip, '\0', ADDRESS_IPV6_ADDRSTRLEN);
+ ResolveIP(host, port, NAME_INFO_DEFAULT, ip, service);
+ TraceLog(LOG_INFO, "Resolved %s to %s", host, ip);
+ assert(strcmp(ip, "google-public-dns-a.google.com") == 0);
+
+ memset(ip, '\0', ADDRESS_IPV6_ADDRSTRLEN);
+ ResolveIP(host, port, NAME_INFO_NOFQDN, ip, service);
+ TraceLog(LOG_INFO, "Resolved %s to %s", host, ip);
+ assert(strcmp(ip, "google-public-dns-a") == 0);
+
+ memset(ip, '\0', ADDRESS_IPV6_ADDRSTRLEN);
+ ResolveIP(host, port, NAME_INFO_NUMERICHOST, ip, service);
+ TraceLog(LOG_INFO, "Resolved %s to %s", host, ip);
+ assert(strcmp(ip, "8.8.8.8") == 0);
+
+ memset(ip, '\0', ADDRESS_IPV6_ADDRSTRLEN);
+ ResolveIP(host, port, NAME_INFO_NAMEREQD, ip, service);
+ TraceLog(LOG_INFO, "Resolved %s to %s", host, ip);
+ assert(strcmp(ip, "google-public-dns-a.google.com") == 0);
+
+ memset(ip, '\0', ADDRESS_IPV6_ADDRSTRLEN);
+ ResolveIP(host, port, NAME_INFO_NUMERICSERV, ip, service);
+ TraceLog(LOG_INFO, "Resolved %s to %s", host, ip);
+ assert(strcmp(ip, "google-public-dns-a.google.com") == 0);
+
+ memset(ip, '\0', ADDRESS_IPV6_ADDRSTRLEN);
+ ResolveIP(host, port, NAME_INFO_DGRAM, ip, service);
+ TraceLog(LOG_INFO, "Resolved %s to %s", host, ip);
+ assert(strcmp(ip, "google-public-dns-a.google.com") == 0);
+}
+
+void test_resolve_host()
+{
+ const char * address = "localhost";
+ const char * port = "80";
+ AddressInformation *addr = AllocAddressList(3);
+ int count = ResolveHost(address, port, ADDRESS_TYPE_ANY, 0, addr);
+
+ assert(GetAddressFamily(addr[0]) == ADDRESS_TYPE_IPV6);
+ assert(GetAddressFamily(addr[1]) == ADDRESS_TYPE_IPV4);
+ assert(GetAddressSocketType(addr[0]) == 0);
+ assert(GetAddressProtocol(addr[0]) == 0);
+ // for (size_t i = 0; i < count; i++) { PrintAddressInfo(addr[i]); }
+}
+
+void test_address()
+{
+}
+
+void test_address_list()
+{
+}
+
+void test_socket_create()
+{
+ SocketConfig server_cfg = {.host = "127.0.0.1", .port = "8080", .server = true, .nonblocking = true};
+ Socket * socket = AllocSocket();
+ SocketResult *server_res = AllocSocketResult();
+ SocketSet * socket_set = AllocSocketSet(1);
+ assert(SocketCreate(&server_cfg, server_res));
+ assert(AddSocket(socket_set, server_res->socket));
+ assert(SocketListen(&server_cfg, server_res));
+}
+
+int main()
+{
+ int screenWidth = 800;
+ int screenHeight = 450;
+ InitWindow(
+ screenWidth, screenHeight, "raylib [network] example - network test");
+ SetTargetFPS(60);
+
+ // Run the tests
+ test_network_initialise();
+ test_resolve_host();
+ // test_socket_create();
+
+ // Main game loop
+ while (!WindowShouldClose()) {
+ BeginDrawing();
+ ClearBackground(RAYWHITE);
+ DrawText("Congrats! You created your first window!", 190, 200, 20, LIGHTGRAY);
+ EndDrawing();
+ }
+ CloseWindow();
+
+ return 0;
+}
\ No newline at end of file diff --git a/examples/network/network_udp_client.c b/examples/network/network_udp_client.c new file mode 100644 index 00000000..c1c89c8d --- /dev/null +++ b/examples/network/network_udp_client.c @@ -0,0 +1,128 @@ +/*******************************************************************************************
+ *
+ * raylib [network] example - UDP Client
+ *
+ * Welcome to raylib!
+ *
+ * To test examples, just press F6 and execute raylib_compile_execute script
+ * Note that compiled executable is placed in the same folder as .c file
+ *
+ * You can find all basic examples on C:\raylib\raylib\examples folder or
+ * raylib official webpage: www.raylib.com
+ *
+ * Enjoy using raylib. :)
+ *
+ * This example has been created using raylib 2.0 (www.raylib.com)
+ * raylib is licensed under an unmodified zlib/libpng license (View raylib.h
+ *for details)
+ *
+ * Copyright (c) 2013-2016 Ramon Santamaria (@raysan5)
+ *
+ ********************************************************************************************/
+
+#include "raylib.h"
+#include "rnet.h"
+
+#include <assert.h>
+#include <stdio.h>
+#include <string.h>
+
+float elapsed = 0.0f;
+float delay = 1.0f;
+bool ping = false;
+bool pong = false;
+const char * pingmsg = "Ping!";
+const char * pongmsg = "Pong!";
+int msglen = 0;
+SocketConfig client_cfg = {.host = "127.0.0.1", .port = "4950", .type = SOCKET_UDP, .nonblocking = true};
+SocketResult *client_res = NULL;
+SocketSet * socket_set = NULL;
+char recvBuffer[512];
+
+// Once connected to the network, check the sockets for pending information
+// and when information is ready, send either a Ping or a Pong.
+void NetworkUpdate()
+{
+ // CheckSockets
+ //
+ // If any of the sockets in the socket_set are pending (received data, or requests)
+ // then mark the socket as being ready. You can check this with IsSocketReady(client_res->socket)
+ int active = CheckSockets(socket_set, 0);
+ if (active != 0) {
+ TraceLog(LOG_DEBUG,
+ "There are currently %d socket(s) with data to be processed.", active);
+ }
+
+ // IsSocketReady
+ //
+ // If the socket is ready, attempt to receive data from the socket
+ int bytesRecv = 0;
+ if (IsSocketReady(client_res->socket)) {
+ bytesRecv = SocketReceive(client_res->socket, recvBuffer, msglen);
+ }
+
+ // If we received data, was that data a "Ping!" or a "Pong!"
+ if (bytesRecv > 0) {
+ if (strcmp(recvBuffer, pingmsg) == 0) { pong = true; }
+ if (strcmp(recvBuffer, pongmsg) == 0) { ping = true; }
+ }
+
+ // After each delay has expired, send a response "Ping!" for a "Pong!" and vice versa
+ elapsed += GetFrameTime();
+ if (elapsed > delay) {
+ if (ping) {
+ ping = false;
+ SocketSend(client_res->socket, pingmsg, msglen);
+ } else if (pong) {
+ pong = false;
+ SocketSend(client_res->socket, pongmsg, msglen);
+ }
+ elapsed = 0.0f;
+ }
+}
+
+int main()
+{
+ // Setup
+ int screenWidth = 800;
+ int screenHeight = 450;
+ InitWindow(
+ screenWidth, screenHeight, "raylib [network] example - udp client");
+ SetTargetFPS(60);
+ SetTraceLogLevel(LOG_DEBUG);
+
+ // Networking
+ InitNetwork();
+
+ // Create the client
+ //
+ // Performs
+ // getaddrinfo
+ // socket
+ // setsockopt
+ // connect (TCP only)
+ client_res = AllocSocketResult();
+ if (!SocketCreate(&client_cfg, client_res)) {
+ TraceLog(LOG_WARNING, "Failed to open client: status %d, errno %d",
+ client_res->status, client_res->socket->status);
+ }
+
+ // Create & Add sockets to the socket set
+ socket_set = AllocSocketSet(1);
+ msglen = strlen(pingmsg) + 1;
+ ping = true;
+ memset(recvBuffer, '\0', sizeof(recvBuffer));
+ AddSocket(socket_set, client_res->socket);
+
+ // Main game loop
+ while (!WindowShouldClose()) {
+ BeginDrawing();
+ ClearBackground(RAYWHITE);
+ NetworkUpdate();
+ EndDrawing();
+ }
+
+ // Cleanup
+ CloseWindow();
+ return 0;
+}
\ No newline at end of file diff --git a/examples/network/network_udp_server.c b/examples/network/network_udp_server.c new file mode 100644 index 00000000..982cdf63 --- /dev/null +++ b/examples/network/network_udp_server.c @@ -0,0 +1,134 @@ +/*******************************************************************************************
+ *
+ * raylib [network] example - UDP Server
+ *
+ * Welcome to raylib!
+ *
+ * To test examples, just press F6 and execute raylib_compile_execute script
+ * Note that compiled executable is placed in the same folder as .c file
+ *
+ * You can find all basic examples on C:\raylib\raylib\examples folder or
+ * raylib official webpage: www.raylib.com
+ *
+ * Enjoy using raylib. :)
+ *
+ * This example has been created using raylib 2.0 (www.raylib.com)
+ * raylib is licensed under an unmodified zlib/libpng license (View raylib.h
+ *for details)
+ *
+ * Copyright (c) 2013-2016 Ramon Santamaria (@raysan5)
+ *
+ ********************************************************************************************/
+
+#include "raylib.h"
+#include "rnet.h"
+
+#include <assert.h>
+#include <stdio.h>
+#include <string.h>
+
+float elapsed = 0.0f;
+float delay = 1.0f;
+bool ping = false;
+bool pong = false;
+const char * pingmsg = "Ping!";
+const char * pongmsg = "Pong!";
+int msglen = 0;
+SocketConfig server_cfg = {.host = "127.0.0.1", .port = "4950", .server = true, .type = SOCKET_UDP, .nonblocking = true};
+SocketResult *server_res = NULL;
+SocketSet * socket_set = NULL;
+char recvBuffer[512];
+
+// Once connected to the network, check the sockets for pending information
+// and when information is ready, send either a Ping or a Pong.
+void NetworkUpdate()
+{
+ // CheckSockets
+ //
+ // If any of the sockets in the socket_set are pending (received data, or requests)
+ // then mark the socket as being ready. You can check this with IsSocketReady(client_res->socket)
+ int active = CheckSockets(socket_set, 0);
+ if (active != 0) {
+ TraceLog(LOG_DEBUG,
+ "There are currently %d socket(s) with data to be processed.", active);
+ }
+
+ // IsSocketReady
+ //
+ // If the socket is ready, attempt to receive data from the socket
+ // int bytesRecv = 0;
+ // if (IsSocketReady(server_res->socket)) {
+ // bytesRecv = SocketReceive(server_res->socket, recvBuffer, msglen);
+ // }
+ int bytesRecv = SocketReceive(server_res->socket, recvBuffer, msglen);
+
+ // If we received data, was that data a "Ping!" or a "Pong!"
+ if (bytesRecv > 0) {
+ if (strcmp(recvBuffer, pingmsg) == 0) { pong = true; }
+ if (strcmp(recvBuffer, pongmsg) == 0) { ping = true; }
+ }
+
+ // After each delay has expired, send a response "Ping!" for a "Pong!" and vice versa
+ elapsed += GetFrameTime();
+ if (elapsed > delay) {
+ if (ping) {
+ ping = false;
+ SocketSend(server_res->socket, pingmsg, msglen);
+ } else if (pong) {
+ pong = false;
+ SocketSend(server_res->socket, pongmsg, msglen);
+ }
+ elapsed = 0.0f;
+ }
+}
+
+int main()
+{
+ // Setup
+ int screenWidth = 800;
+ int screenHeight = 450;
+ InitWindow(
+ screenWidth, screenHeight, "raylib [network] example - udp server");
+ SetTargetFPS(60);
+ SetTraceLogLevel(LOG_DEBUG);
+
+ // Networking
+ InitNetwork();
+
+ // Create the server
+ //
+ // Performs
+ // getaddrinfo
+ // socket
+ // setsockopt
+ // bind
+ // listen
+ server_res = AllocSocketResult();
+ if (!SocketCreate(&server_cfg, server_res)) {
+ TraceLog(LOG_WARNING, "Failed to open server: status %d, errno %d",
+ server_res->status, server_res->socket->status);
+ } else {
+ if (!SocketBind(&server_cfg, server_res)) {
+ TraceLog(LOG_WARNING, "Failed to bind server: status %d, errno %d",
+ server_res->status, server_res->socket->status);
+ }
+ }
+
+ // Create & Add sockets to the socket set
+ socket_set = AllocSocketSet(1);
+ msglen = strlen(pingmsg) + 1;
+ memset(recvBuffer, '\0', sizeof(recvBuffer));
+ AddSocket(socket_set, server_res->socket);
+
+ // Main game loop
+ while (!WindowShouldClose()) {
+ BeginDrawing();
+ ClearBackground(RAYWHITE);
+ NetworkUpdate();
+ EndDrawing();
+ }
+
+ // Cleanup
+ CloseWindow();
+ return 0;
+}
\ No newline at end of file diff --git a/src/raylib.h b/src/raylib.h index 6a5f0ef8..710f69b6 100644 --- a/src/raylib.h +++ b/src/raylib.h @@ -75,6 +75,7 @@ #define RAYLIB_H #include <stdarg.h> // Required for: va_list - Only used by TraceLogCallback +#include <inttypes.h> // Required for rnet
#if defined(_WIN32) && defined(BUILD_LIBTYPE_SHARED) #define RLAPI __declspec(dllexport) // We are building raylib as a Win32 shared library (.dll) @@ -99,6 +100,11 @@ // Shader and material limits #define MAX_SHADER_LOCATIONS 32 // Maximum number of predefined locations stored in shader struct #define MAX_MATERIAL_MAPS 12 // Maximum number of texture maps stored in shader struct +
+// Network connection related defines +#define SOCKET_MAX_SOCK_OPTS (4) // Maximum socket options +#define SOCKET_MAX_UDPCHANNELS (32) // Maximum UDP channels +#define SOCKET_MAX_UDPADDRESSES (4) // Maximum bound UDP addresses // NOTE: MSC C++ compiler does not support compound literals (C99 feature) // Plain structures in C++ (without constructors) can be initialized from { } initializers. @@ -442,6 +448,100 @@ typedef struct VrDeviceInfo { float lensDistortionValues[4]; // HMD lens distortion constant parameters float chromaAbCorrection[4]; // HMD chromatic aberration correction parameters } VrDeviceInfo; +
+// Network typedefs
+typedef uint32_t SocketChannel;
+typedef struct _AddressInformation * AddressInformation;
+typedef struct _SocketAddress * SocketAddress;
+typedef struct _SocketAddressIPv4 * SocketAddressIPv4;
+typedef struct _SocketAddressIPv6 * SocketAddressIPv6;
+typedef struct _SocketAddressStorage *SocketAddressStorage;
+
+// IPAddress definition (in network byte order)
+typedef struct IPAddress
+{
+ unsigned long host; /* 32-bit IPv4 host address */
+ unsigned short port; /* 16-bit protocol port */
+} IPAddress;
+
+// An option ID, value, sizeof(value) tuple for setsockopt(2).
+typedef struct SocketOpt
+{
+ int id;
+ void *value;
+ int valueLen;
+} SocketOpt;
+
+typedef enum
+{
+ SOCKET_TCP = 0, // SOCK_STREAM
+ SOCKET_UDP = 1 // SOCK_DGRAM
+} SocketType;
+
+typedef struct UDPChannel
+{
+ int numbound; // The total number of addresses this channel is bound to
+ IPAddress address[SOCKET_MAX_UDPADDRESSES]; // The list of remote addresses this channel is bound to
+} UDPChannel;
+
+typedef struct Socket
+{
+ int ready; // Is the socket ready? i.e. has information
+ int status; // The last status code to have occured using this socket
+ bool isServer; // Is this socket a server socket (i.e. TCP/UDP Listen Server)
+ SocketChannel channel; // The socket handle id
+ SocketType type; // Is this socket a TCP or UDP socket?
+ bool isIPv6; // Is this socket address an ipv6 address?
+ SocketAddressIPv4 addripv4; // The host/target IPv4 for this socket (in network byte order)
+ SocketAddressIPv6 addripv6; // The host/target IPv6 for this socket (in network byte order)
+
+ struct UDPChannel binding[SOCKET_MAX_UDPCHANNELS]; // The amount of channels (if UDP) this socket is bound to
+} Socket;
+
+typedef struct SocketSet
+{
+ int numsockets;
+ int maxsockets;
+ struct Socket **sockets;
+} SocketSet;
+
+typedef struct SocketDataPacket
+{
+ int channel; // The src/dst channel of the packet
+ unsigned char *data; // The packet data
+ int len; // The length of the packet data
+ int maxlen; // The size of the data buffer
+ int status; // packet status after sending
+ IPAddress address; // The source/dest address of an incoming/outgoing packet
+} SocketDataPacket;
+
+// Configuration for a socket.
+typedef struct SocketConfig
+{
+ char * host; // The host address in xxx.xxx.xxx.xxx form
+ char * port; // The target port/service in the form "http" or "25565"
+ bool server; // Listen for incoming clients?
+ SocketType type; // The type of socket, TCP/UDP
+ bool nonblocking; // non-blocking operation?
+ int backlog_size; // set a custom backlog size
+ SocketOpt sockopts[SOCKET_MAX_SOCK_OPTS];
+} SocketConfig;
+
+// Result from calling open with a given config.
+typedef struct SocketResult
+{
+ int status;
+ Socket *socket;
+} SocketResult;
+
+//
+typedef struct Packet
+{
+ uint32_t size; // The total size of bytes in data
+ uint32_t offs; // The offset to data access
+ uint32_t maxs; // The max size of data
+ uint8_t *data; // Data stored in network byte order
+} Packet; //---------------------------------------------------------------------------------- // Enumerators Definition @@ -1407,6 +1507,82 @@ RLAPI bool IsAudioStreamPlaying(AudioStream stream); // Check i RLAPI void StopAudioStream(AudioStream stream); // Stop audio stream RLAPI void SetAudioStreamVolume(AudioStream stream, float volume); // Set volume for audio stream (1.0 is max level) RLAPI void SetAudioStreamPitch(AudioStream stream, float pitch); // Set pitch for audio stream (1.0 is base level) +
+//------------------------------------------------------------------------------------ +// Network (Module: network) +//------------------------------------------------------------------------------------ + +// Initialisation and cleanup +RLAPI bool InitNetwork(void); +RLAPI void CloseNetwork(void); + +// Address API +RLAPI void ResolveIP(const char *ip, const char *service, int flags, char *outhost, char *outserv); +RLAPI int ResolveHost(const char *address, const char *service, int addressType, int flags, AddressInformation *outAddr); +RLAPI int GetAddressFamily(AddressInformation address); +RLAPI int GetAddressSocketType(AddressInformation address); +RLAPI int GetAddressProtocol(AddressInformation address); +RLAPI char* GetAddressCanonName(AddressInformation address); +RLAPI char* GetAddressHostAndPort(AddressInformation address, char *outhost, int *outport); +RLAPI void PrintAddressInfo(AddressInformation address); + +// Address Memory API +RLAPI AddressInformation AllocAddress(); +RLAPI void FreeAddress(AddressInformation *addressInfo); +RLAPI AddressInformation *AllocAddressList(int size); + +// Socket API +RLAPI bool SocketCreate(SocketConfig *config, SocketResult *result); +RLAPI bool SocketBind(SocketConfig *config, SocketResult *result); +RLAPI bool SocketListen(SocketConfig *config, SocketResult *result); +RLAPI bool SocketConnect(SocketConfig *config, SocketResult *result); +RLAPI Socket *SocketAccept(Socket *server, SocketConfig *config); + +// UDP Socket API +RLAPI int SocketSetChannel(Socket *socket, int channel, const IPAddress *address); +RLAPI void SocketUnsetChannel(Socket *socket, int channel); + +// UDP DataPacket API +RLAPI SocketDataPacket *AllocPacket(int size); +RLAPI int ResizePacket(SocketDataPacket *packet, int newsize); +RLAPI void FreePacket(SocketDataPacket *packet); +RLAPI SocketDataPacket **AllocPacketList(int count, int size); +RLAPI void FreePacketList(SocketDataPacket **packets); + +// General Socket API +RLAPI int SocketSend(Socket *sock, const void *datap, int len); +RLAPI int SocketReceive(Socket *sock, void *data, int maxlen); +RLAPI void SocketClose(Socket *sock); +RLAPI SocketAddressStorage SocketGetPeerAddress(Socket *sock); +RLAPI char* GetSocketAddressHost(SocketAddressStorage storage); +RLAPI short GetSocketAddressPort(SocketAddressStorage storage); + +// Socket Memory API +RLAPI Socket *AllocSocket(); +RLAPI void FreeSocket(Socket **sock); +RLAPI SocketResult *AllocSocketResult(); +RLAPI void FreeSocketResult(SocketResult **result); +RLAPI SocketSet *AllocSocketSet(int max); +RLAPI void FreeSocketSet(SocketSet *sockset); + +// Socket I/O API +RLAPI bool IsSocketReady(Socket *sock); +RLAPI bool IsSocketConnected(Socket *sock); +RLAPI int AddSocket(SocketSet *set, Socket *sock); +RLAPI int RemoveSocket(SocketSet *set, Socket *sock); +RLAPI int CheckSockets(SocketSet *set, unsigned int timeout); + +// Packet API +void PacketSend(Packet *packet); +void PacketReceive(Packet *packet); +void PacketWrite8(Packet *packet, uint16_t value); +void PacketWrite16(Packet *packet, uint16_t value); +void PacketWrite32(Packet *packet, uint32_t value); +void PacketWrite64(Packet *packet, uint64_t value); +uint16_t PacketRead8(Packet *packet); +uint16_t PacketRead16(Packet *packet); +uint32_t PacketRead32(Packet *packet); +uint64_t PacketRead64(Packet *packet); #if defined(__cplusplus) } diff --git a/src/rnet.c b/src/rnet.c new file mode 100644 index 00000000..0a47b44f --- /dev/null +++ b/src/rnet.c @@ -0,0 +1,2023 @@ +/*********************************************************************************************
+*
+* rnet - A simple and easy-to-use network module for raylib
+*
+* FEATURES:
+* - Provides a simple and (hopefully) easy to use wrapper around the Berkeley socket API
+*
+* DEPENDENCIES:
+* raylib.h - TraceLog
+* rnet.h - platform-specific network includes
+*
+* CONTRIBUTORS:
+* Jak Barnes (github: @syphonx) (Feb. 2019):
+* - Initial version
+*
+*
+* LICENSE: zlib/libpng
+*
+* Copyright (c) 2014-2019 Ramon Santamaria (@raysan5)
+*
+* This software is provided "as-is", without any express or implied warranty. In no event
+* will the authors be held liable for any damages arising from the use of this software.
+*
+* Permission is granted to anyone to use this software for any purpose, including commercial
+* applications, and to alter it and redistribute it freely, subject to the following restrictions:
+*
+* 1. The origin of this software must not be misrepresented; you must not claim that you
+* wrote the original software. If you use this software in a product, an acknowledgment
+* in the product documentation would be appreciated but is not required.
+*
+* 2. Altered source versions must be plainly marked as such, and must not be misrepresented
+* as being the original software.
+*
+* 3. This notice may not be removed or altered from any source distribution.
+*
+**********************************************************************************************/
+
+//----------------------------------------------------------------------------------
+// Check if config flags have been externally provided on compilation line
+//----------------------------------------------------------------------------------
+
+#include "rnet.h"
+
+#include "raylib.h"
+
+#include <assert.h> // Required for: assert()
+#include <stdio.h> // Required for: FILE, fopen(), fclose(), fread()
+#include <stdlib.h> // Required for: malloc(), free()
+#include <string.h> // Required for: strcmp(), strncmp()
+
+//----------------------------------------------------------------------------------
+// Module defines
+//----------------------------------------------------------------------------------
+
+#define NET_DEBUG_ENABLED (1)
+
+//----------------------------------------------------------------------------------
+// Types and Structures Definition
+//----------------------------------------------------------------------------------
+
+typedef struct _SocketAddress
+{
+ struct sockaddr address;
+} _SocketAddress;
+
+typedef struct _SocketAddressIPv4
+{
+ struct sockaddr_in address;
+} _SocketAddressIPv4;
+
+typedef struct _SocketAddressIPv6
+{
+ struct sockaddr_in6 address;
+} _SocketAddressIPv6;
+
+typedef struct _SocketAddressStorage
+{
+ struct sockaddr_storage address;
+} _SocketAddressStorage;
+
+typedef struct _AddressInformation
+{
+ struct addrinfo addr;
+} _AddressInformation;
+
+
+
+//----------------------------------------------------------------------------------
+// Global module forward declarations
+//----------------------------------------------------------------------------------
+
+static void PrintSocket(struct sockaddr_storage *addr, const int family, const int socktype, const int protocol);
+static const char *SocketAddressToString(struct sockaddr_storage *sockaddr);
+static bool IsIPv4Address(const char *ip);
+static bool IsIPv6Address(const char *ip);
+static void * GetSocketPortPtr(struct sockaddr_storage *sa);
+static void * GetSocketAddressPtr(struct sockaddr_storage *sa);
+static bool IsSocketValid(Socket *sock);
+static void SocketSetLastError(int err);
+static int SocketGetLastError();
+static char * SocketGetLastErrorString();
+static char * SocketErrorCodeToString(int err);
+static bool SocketSetDefaults(SocketConfig *config);
+static bool InitSocket(Socket *sock, struct addrinfo *addr);
+static bool CreateSocket(SocketConfig *config, SocketResult *outresult);
+static bool SocketSetBlocking(Socket *sock);
+static bool SocketSetNonBlocking(Socket *sock);
+static bool SocketSetOptions(SocketConfig *config, Socket *sock);
+static void SocketSetHints(SocketConfig *config, struct addrinfo *hints);
+
+//----------------------------------------------------------------------------------
+// Global module implementation
+//----------------------------------------------------------------------------------
+
+// Print socket information
+static void PrintSocket(struct sockaddr_storage *addr, const int family, const int socktype, const int protocol)
+{
+ switch (family)
+ {
+ case AF_UNSPEC: { TraceLog(LOG_DEBUG, "\tFamily: Unspecified");
+ }
+ break;
+ case AF_INET:
+ {
+ TraceLog(LOG_DEBUG, "\tFamily: AF_INET (IPv4)");
+ TraceLog(LOG_INFO, "\t- IPv4 address %s", SocketAddressToString(addr));
+ }
+ break;
+ case AF_INET6:
+ {
+ TraceLog(LOG_DEBUG, "\tFamily: AF_INET6 (IPv6)");
+ TraceLog(LOG_INFO, "\t- IPv6 address %s", SocketAddressToString(addr));
+ }
+ break;
+ case AF_NETBIOS:
+ {
+ TraceLog(LOG_DEBUG, "\tFamily: AF_NETBIOS (NetBIOS)");
+ }
+ break;
+ default: { TraceLog(LOG_DEBUG, "\tFamily: Other %ld", family);
+ }
+ break;
+ }
+ TraceLog(LOG_DEBUG, "\tSocket type:");
+ switch (socktype)
+ {
+ case 0: TraceLog(LOG_DEBUG, "\t- Unspecified"); break;
+ case SOCK_STREAM:
+ TraceLog(LOG_DEBUG, "\t- SOCK_STREAM (stream)");
+ break;
+ case SOCK_DGRAM:
+ TraceLog(LOG_DEBUG, "\t- SOCK_DGRAM (datagram)");
+ break;
+ case SOCK_RAW: TraceLog(LOG_DEBUG, "\t- SOCK_RAW (raw)"); break;
+ case SOCK_RDM:
+ TraceLog(LOG_DEBUG, "\t- SOCK_RDM (reliable message datagram)");
+ break;
+ case SOCK_SEQPACKET:
+ TraceLog(LOG_DEBUG, "\t- SOCK_SEQPACKET (pseudo-stream packet)");
+ break;
+ default: TraceLog(LOG_DEBUG, "\t- Other %ld", socktype); break;
+ }
+ TraceLog(LOG_DEBUG, "\tProtocol:");
+ switch (protocol)
+ {
+ case 0: TraceLog(LOG_DEBUG, "\t- Unspecified"); break;
+ case IPPROTO_TCP: TraceLog(LOG_DEBUG, "\t- IPPROTO_TCP (TCP)"); break;
+ case IPPROTO_UDP: TraceLog(LOG_DEBUG, "\t- IPPROTO_UDP (UDP)"); break;
+ default: TraceLog(LOG_DEBUG, "\t- Other %ld", protocol); break;
+ }
+}
+
+// Convert network ordered socket address to human readable string (127.0.0.1)
+static const char *SocketAddressToString(struct sockaddr_storage *sockaddr)
+{
+ static const char* ipv6[INET6_ADDRSTRLEN];
+ assert(sockaddr != NULL);
+ assert(sockaddr->ss_family == AF_INET || sockaddr->ss_family == AF_INET6);
+ switch (sockaddr->ss_family)
+ {
+ case AF_INET:
+ {
+ struct sockaddr_in *s = ((struct sockaddr_in *) sockaddr);
+ return inet_ntop(AF_INET, &s->sin_addr, ipv6, INET_ADDRSTRLEN);
+ }
+ break;
+ case AF_INET6:
+ {
+ struct sockaddr_in6 *s = ((struct sockaddr_in6 *) sockaddr);
+ return inet_ntop(AF_INET6, &s->sin6_addr, ipv6, INET6_ADDRSTRLEN);
+ }
+ break;
+ }
+ return NULL;
+}
+
+// Check if the null terminated string ip is a valid IPv4 address
+static bool IsIPv4Address(const char *ip)
+{
+ struct sockaddr_in sa;
+ int result = inet_pton(AF_INET, ip, &(sa.sin_addr));
+ return result != 0;
+}
+
+// Check if the null terminated string ip is a valid IPv6 address
+static bool IsIPv6Address(const char *ip)
+{
+ struct sockaddr_in6 sa;
+ int result = inet_pton(AF_INET6, ip, &(sa.sin6_addr));
+ return result != 0;
+}
+
+// Return a pointer to the port from the correct address family (IPv4, or IPv6)
+static void *GetSocketPortPtr(struct sockaddr_storage *sa)
+{
+ if (sa->ss_family == AF_INET)
+ {
+ return &(((struct sockaddr_in *) sa)->sin_port);
+ }
+
+ return &(((struct sockaddr_in6 *) sa)->sin6_port);
+}
+
+// Return a pointer to the address from the correct address family (IPv4, or IPv6)
+static void *GetSocketAddressPtr(struct sockaddr_storage *sa)
+{
+ if (sa->ss_family == AF_INET)
+ {
+ return &(((struct sockaddr_in *) sa)->sin_addr);
+ }
+
+ return &(((struct sockaddr_in6 *) sa)->sin6_addr);
+}
+
+// Is the socket in a valid state?
+static bool IsSocketValid(Socket *sock)
+{
+ if (sock != NULL)
+ {
+ return (sock->channel != INVALID_SOCKET);
+ }
+ return false;
+}
+
+// Sets the error code that can be retrieved through the WSAGetLastError function.
+static void SocketSetLastError(int err)
+{
+#if PLATFORM == PLATFORM_WINDOWS
+ WSASetLastError(err);
+#else
+ errno = err;
+#endif
+}
+
+// Returns the error status for the last Sockets operation that failed
+static int SocketGetLastError()
+{
+#if PLATFORM == PLATFORM_WINDOWS
+ return WSAGetLastError();
+#else
+ return errno;
+#endif
+}
+
+// Returns a human-readable string representing the last error message
+static char *SocketGetLastErrorString()
+{
+ return SocketErrorCodeToString(SocketGetLastError());
+}
+
+// Returns a human-readable string representing the error message (err)
+static char *SocketErrorCodeToString(int err)
+{
+#if PLATFORM == PLATFORM_WINDOWS
+ static char gaiStrErrorBuffer[GAI_STRERROR_BUFFER_SIZE];
+ sprintf(gaiStrErrorBuffer, "%ws", gai_strerror(err));
+ return gaiStrErrorBuffer;
+#else
+ return gai_strerror(err);
+#endif
+}
+
+// Set the defaults in the supplied SocketConfig if they're not already set
+static bool SocketSetDefaults(SocketConfig *config)
+{
+ if (config->backlog_size == 0)
+ {
+ config->backlog_size = SOCKET_MAX_QUEUE_SIZE;
+ }
+
+ return true;
+}
+
+// Create the socket channel
+static bool InitSocket(Socket *sock, struct addrinfo *addr)
+{
+ switch (sock->type)
+ {
+ case SOCKET_TCP:
+ if (addr->ai_family == AF_INET)
+ {
+ sock->channel = socket(AF_INET, SOCK_STREAM, 0);
+ }
+ else
+ {
+ sock->channel = socket(AF_INET6, SOCK_STREAM, 0);
+ }
+ break;
+ case SOCKET_UDP:
+ if (addr->ai_family == AF_INET)
+ {
+ sock->channel = socket(AF_INET, SOCK_DGRAM, 0);
+ }
+ else
+ {
+ sock->channel = socket(AF_INET6, SOCK_DGRAM, 0);
+ }
+ break;
+ default:
+ TraceLog(LOG_WARNING, "Invalid socket type specified.");
+ break;
+ }
+ return IsSocketValid(sock);
+}
+
+// CreateSocket() - Interally called by CreateSocket()
+//
+// This here is the bread and butter of the socket API, This function will
+// attempt to open a socket, bind and listen to it based on the config passed in
+//
+// SocketConfig* config - Configuration for which socket to open
+// SocketResult* result - The results of this function (if any, including errors)
+//
+// e.g.
+// SocketConfig server_config = { SocketConfig client_config = {
+// .host = "127.0.0.1", .host = "127.0.0.1",
+// .port = 8080, .port = 8080,
+// .server = true, };
+// .nonblocking = true,
+// };
+// SocketResult server_res; SocketResult client_res;
+static bool CreateSocket(SocketConfig *config, SocketResult *outresult)
+{
+ bool success = true;
+ int addrstatus;
+ struct addrinfo hints; // Address flags (IPV4, IPV6, UDP?)
+ struct addrinfo *res; // A pointer to the resulting address list
+ outresult->socket->channel = INVALID_SOCKET;
+ outresult->status = RESULT_FAILURE;
+
+ // Set the socket type
+ outresult->socket->type = config->type;
+
+ // Set the hints based on information in the config
+ //
+ // AI_CANONNAME Causes the ai_canonname of the result to the filled out with the host's canonical (real) name.
+ // AI_PASSIVE: Causes the result's IP address to be filled out with INADDR_ANY (IPv4)or in6addr_any (IPv6);
+ // Note: This causes a subsequent call to bind() to auto-fill the IP address
+ // of the struct sockaddr with the address of the current host.
+ //
+ SocketSetHints(config, &hints);
+
+ // Populate address information
+ addrstatus = getaddrinfo(config->host, // e.g. "www.example.com" or IP (Can be null if AI_PASSIVE flag is set
+ config->port, // e.g. "http" or port number
+ &hints, // e.g. SOCK_STREAM/SOCK_DGRAM
+ &res // The struct to populate
+ );
+
+ // Did we succeed?
+ if (addrstatus != 0)
+ {
+ outresult->socket->status = SocketGetLastError();
+ TraceLog(LOG_WARNING,
+ "Socket Error: %s",
+ SocketErrorCodeToString(outresult->socket->status));
+ SocketSetLastError(0);
+ TraceLog(LOG_WARNING,
+ "Failed to get resolve host %s:%s: %s",
+ config->host,
+ config->port,
+ SocketGetLastErrorString());
+ return (success = false);
+ }
+ else
+ {
+ char hoststr[NI_MAXHOST];
+ char portstr[NI_MAXSERV];
+ socklen_t client_len = sizeof(struct sockaddr_storage);
+ int rc = getnameinfo((struct sockaddr *) res->ai_addr, client_len, hoststr, sizeof(hoststr), portstr, sizeof(portstr), NI_NUMERICHOST | NI_NUMERICSERV);
+ TraceLog(LOG_INFO, "Successfully resolved host %s:%s", hoststr, portstr);
+ }
+
+ // Walk the address information linked-list
+ struct addrinfo *it;
+ for (it = res; it != NULL; it = it->ai_next)
+ {
+ // Initialise the socket
+ if (!InitSocket(outresult->socket, it))
+ {
+ outresult->socket->status = SocketGetLastError();
+ TraceLog(LOG_WARNING,
+ "Socket Error: %s",
+ SocketErrorCodeToString(outresult->socket->status));
+ SocketSetLastError(0);
+ continue;
+ }
+
+ // Set socket options
+ if (!SocketSetOptions(config, outresult->socket))
+ {
+ outresult->socket->status = SocketGetLastError();
+ TraceLog(LOG_WARNING,
+ "Socket Error: %s",
+ SocketErrorCodeToString(outresult->socket->status));
+ SocketSetLastError(0);
+ freeaddrinfo(res);
+ return (success = false);
+ }
+ }
+
+ if (!IsSocketValid(outresult->socket))
+ {
+ outresult->socket->status = SocketGetLastError();
+ TraceLog(
+ LOG_WARNING, "Socket Error: %s", SocketErrorCodeToString(outresult->status));
+ SocketSetLastError(0);
+ freeaddrinfo(res);
+ return (success = false);
+ }
+
+ if (success)
+ {
+ outresult->status = RESULT_SUCCESS;
+ outresult->socket->ready = 0;
+ outresult->socket->status = 0;
+ if (!(config->type == SOCKET_UDP))
+ {
+ outresult->socket->isServer = config->server;
+ }
+ switch (res->ai_addr->sa_family)
+ {
+ case AF_INET:
+ {
+ outresult->socket->addripv4 = (struct _SocketAddressIPv4 *) malloc(
+ sizeof(*outresult->socket->addripv4));
+ if (outresult->socket->addripv4 != NULL)
+ {
+ memset(outresult->socket->addripv4, 0,
+ sizeof(*outresult->socket->addripv4));
+ if (outresult->socket->addripv4 != NULL)
+ {
+ memcpy(&outresult->socket->addripv4->address,
+ (struct sockaddr_in *) res->ai_addr, sizeof(struct sockaddr_in));
+ outresult->socket->isIPv6 = false;
+ char hoststr[NI_MAXHOST];
+ char portstr[NI_MAXSERV];
+ socklen_t client_len = sizeof(struct sockaddr_storage);
+ getnameinfo(
+ (struct sockaddr *) &outresult->socket->addripv4->address, client_len, hoststr, sizeof(hoststr), portstr, sizeof(portstr), NI_NUMERICHOST | NI_NUMERICSERV);
+ TraceLog(LOG_INFO, "Socket address set to %s:%s", hoststr, portstr);
+ }
+ }
+ }
+ break;
+ case AF_INET6:
+ {
+ outresult->socket->addripv6 = (struct _SocketAddressIPv6 *) malloc(
+ sizeof(*outresult->socket->addripv6));
+ if (outresult->socket->addripv6 != NULL)
+ {
+ memset(outresult->socket->addripv6, 0,
+ sizeof(*outresult->socket->addripv6));
+ if (outresult->socket->addripv6 != NULL)
+ {
+ memcpy(&outresult->socket->addripv6->address,
+ (struct sockaddr_in6 *) res->ai_addr, sizeof(struct sockaddr_in6));
+ outresult->socket->isIPv6 = true;
+ char hoststr[NI_MAXHOST];
+ char portstr[NI_MAXSERV];
+ socklen_t client_len = sizeof(struct sockaddr_storage);
+ getnameinfo(
+ (struct sockaddr *) &outresult->socket->addripv6->address, client_len, hoststr, sizeof(hoststr), portstr, sizeof(portstr), NI_NUMERICHOST | NI_NUMERICSERV);
+ TraceLog(LOG_INFO, "Socket address set to %s:%s", hoststr, portstr);
+ }
+ }
+ }
+ break;
+ }
+ }
+ freeaddrinfo(res);
+ return success;
+}
+
+// Set the state of the Socket sock to blocking
+static bool SocketSetBlocking(Socket *sock)
+{
+ bool ret = true;
+#if PLATFORM == PLATFORM_WINDOWS
+ unsigned long mode = 0;
+ ret = ioctlsocket(sock->channel, FIONBIO, &mode);
+#else
+ const int flags = fcntl(sock->channel, F_GETFL, 0);
+ if (!(flags & O_NONBLOCK))
+ {
+ TraceLog(LOG_DEBUG, "Socket was already in blocking mode");
+ return ret;
+ }
+
+ ret = (0 == fcntl(sock->channel, F_SETFL, (flags ^ O_NONBLOCK)));
+#endif
+ return ret;
+}
+
+// Set the state of the Socket sock to non-blocking
+static bool SocketSetNonBlocking(Socket *sock)
+{
+ bool ret = true;
+#if PLATFORM == PLATFORM_WINDOWS
+ unsigned long mode = 1;
+ ret = ioctlsocket(sock->channel, FIONBIO, &mode);
+#else
+ const int flags = fcntl(sock->channel, F_GETFL, 0);
+ if ((flags & O_NONBLOCK))
+ {
+ TraceLog(LOG_DEBUG, "Socket was already in non-blocking mode");
+ return ret;
+ }
+ ret = (0 == fcntl(sock->channel, F_SETFL, (flags | O_NONBLOCK)));
+#endif
+ return ret;
+}
+
+// Set options specified in SocketConfig to Socket sock
+static bool SocketSetOptions(SocketConfig *config, Socket *sock)
+{
+ for (int i = 0; i < SOCKET_MAX_SOCK_OPTS; i++)
+ {
+ SocketOpt *opt = &config->sockopts[i];
+ if (opt->id == 0)
+ {
+ break;
+ }
+
+ if (setsockopt(sock->channel, SOL_SOCKET, opt->id, opt->value, opt->valueLen) < 0)
+ {
+ return false;
+ }
+ }
+
+ return true;
+}
+
+// Set "hints" in an addrinfo struct, to be passed to getaddrinfo.
+static void SocketSetHints(SocketConfig *config, struct addrinfo *hints)
+{
+ if (config == NULL || hints == NULL)
+ {
+ return;
+ }
+ memset(hints, 0, sizeof(*hints));
+
+ // Check if the ip supplied in the config is a valid ipv4 ip ipv6 address
+ if (IsIPv4Address(config->host))
+ {
+ hints->ai_family = AF_INET;
+ hints->ai_flags |= AI_NUMERICHOST;
+ }
+ else
+ {
+ if (IsIPv6Address(config->host))
+ {
+ hints->ai_family = AF_INET6;
+ hints->ai_flags |= AI_NUMERICHOST;
+ }
+ else
+ {
+ hints->ai_family = AF_UNSPEC;
+ }
+ }
+
+ if (config->type == SOCKET_UDP)
+ {
+ hints->ai_socktype = SOCK_DGRAM;
+ }
+ else
+ {
+ hints->ai_socktype = SOCK_STREAM;
+ }
+
+ // Set passive unless UDP client
+ if (!(config->type == SOCKET_UDP) || config->server)
+ {
+ hints->ai_flags = AI_PASSIVE;
+ }
+}
+
+//----------------------------------------------------------------------------------
+// Module implementation
+//----------------------------------------------------------------------------------
+
+// Initialise the network (requires for windows platforms only)
+bool InitNetwork()
+{
+#if PLATFORM == PLATFORM_WINDOWS
+ WORD wVersionRequested;
+ WSADATA wsaData;
+ int err;
+
+ wVersionRequested = MAKEWORD(2, 2);
+ err = WSAStartup(wVersionRequested, &wsaData);
+ if (err != 0)
+ {
+ TraceLog(LOG_WARNING, "WinSock failed to initialise.");
+ return false;
+ }
+ else
+ {
+ TraceLog(LOG_INFO, "WinSock initialised.");
+ }
+
+ if (LOBYTE(wsaData.wVersion) != 2 || HIBYTE(wsaData.wVersion) != 2)
+ {
+ TraceLog(LOG_WARNING, "WinSock failed to initialise.");
+ WSACleanup();
+ return false;
+ }
+
+ return true;
+#else
+ return true;
+#endif
+}
+
+// Cleanup, and close the network
+void CloseNetwork()
+{
+#if PLATFORM == PLATFORM_WINDOWS
+ WSACleanup();
+#endif
+}
+
+// Protocol-independent name resolution from an address to an ANSI host name
+// and from a port number to the ANSI service name.
+//
+// The flags parameter can be used to customize processing of the getnameinfo function
+//
+// The following flags are available:
+//
+// NAME_INFO_DEFAULT 0x00 // No flags set
+// NAME_INFO_NOFQDN 0x01 // Only return nodename portion for local hosts
+// NAME_INFO_NUMERICHOST 0x02 // Return numeric form of the host's address
+// NAME_INFO_NAMEREQD 0x04 // Error if the host's name not in DNS
+// NAME_INFO_NUMERICSERV 0x08 // Return numeric form of the service (port #)
+// NAME_INFO_DGRAM 0x10 // Service is a datagram service
+void ResolveIP(const char *ip, const char *port, int flags, char *host, char *serv)
+{
+ // Variables
+ int status; // Status value to return (0) is success
+ struct addrinfo hints; // Address flags (IPV4, IPV6, UDP?)
+ struct addrinfo *res; // A pointer to the resulting address list
+
+ // Set the hints
+ memset(&hints, 0, sizeof hints);
+ hints.ai_family = AF_UNSPEC; // Either IPv4 or IPv6 (AF_INET, AF_INET6)
+ hints.ai_protocol = 0; // Automatically select correct protocol (IPPROTO_TCP), (IPPROTO_UDP)
+
+ // Populate address information
+ status = getaddrinfo(ip, // e.g. "www.example.com" or IP
+ port, // e.g. "http" or port number
+ &hints, // e.g. SOCK_STREAM/SOCK_DGRAM
+ &res // The struct to populate
+ );
+
+ // Did we succeed?
+ if (status != 0)
+ {
+ TraceLog(LOG_WARNING, "Failed to get resolve host %s:%s: %s", ip, port, gai_strerror(errno));
+ }
+ else
+ {
+ TraceLog(LOG_DEBUG, "Resolving... %s::%s", ip, port);
+ }
+
+ // Attempt to resolve network byte order ip to hostname
+ switch (res->ai_family)
+ {
+ case AF_INET:
+ status = getnameinfo(&*((struct sockaddr *) res->ai_addr),
+ sizeof(*((struct sockaddr_in *) res->ai_addr)),
+ host,
+ NI_MAXHOST,
+ serv,
+ NI_MAXSERV,
+ flags);
+ break;
+ case AF_INET6:
+ status = getnameinfo(&*((struct sockaddr_in6 *) res->ai_addr),
+ sizeof(*((struct sockaddr_in6 *) res->ai_addr)),
+ host,
+ NI_MAXHOST,
+ serv,
+ NI_MAXSERV,
+ flags);
+ break;
+ default: break;
+ }
+
+ if (status != 0)
+ {
+ TraceLog(LOG_WARNING, "Failed to resolve ip %s: %s", ip, SocketGetLastErrorString());
+ }
+ else
+ {
+ TraceLog(LOG_DEBUG, "Successfully resolved %s::%s to %s", ip, port, host);
+ }
+
+ // Free the pointer to the data returned by addrinfo
+ freeaddrinfo(res);
+}
+
+// Protocol-independent translation from an ANSI host name to an address
+//
+// e.g.
+// const char* address = "127.0.0.1" (local address)
+// const char* port = "80"
+//
+// Parameters:
+// const char* address - A pointer to a NULL-terminated ANSI string that contains a host (node) name or a numeric host address string.
+// const char* service - A pointer to a NULL-terminated ANSI string that contains either a service name or port number represented as a string.
+//
+// Returns:
+// The total amount of addresses found, -1 on error
+//
+int ResolveHost(const char *address, const char *service, int addressType, int flags, AddressInformation *outAddr)
+{
+ // Variables
+ int status; // Status value to return (0) is success
+ struct addrinfo hints; // Address flags (IPV4, IPV6, UDP?)
+ struct addrinfo *res; // will point to the results
+ struct addrinfo *iterator;
+ assert(((address != NULL || address != 0) || (service != NULL || service != 0)));
+ assert(((addressType == AF_INET) || (addressType == AF_INET6) || (addressType == AF_UNSPEC)));
+
+ // Set the hints
+ memset(&hints, 0, sizeof hints);
+ hints.ai_family = addressType; // Either IPv4 or IPv6 (ADDRESS_TYPE_IPV4, ADDRESS_TYPE_IPV6)
+ hints.ai_protocol = 0; // Automatically select correct protocol (IPPROTO_TCP), (IPPROTO_UDP)
+ hints.ai_flags = flags;
+ assert(hints.ai_addrlen == NULL || hints.ai_addrlen == 0);
+ assert(hints.ai_canonname == NULL || hints.ai_canonname == 0);
+ assert(hints.ai_addr == NULL || hints.ai_addr == 0);
+ assert(hints.ai_next == NULL || hints.ai_next == 0);
+
+ // When the address is NULL, populate the IP for me
+ if (address == NULL)
+ {
+ if ((hints.ai_flags & AI_PASSIVE) == 0)
+ {
+ hints.ai_flags |= AI_PASSIVE;
+ }
+ }
+
+ TraceLog(LOG_INFO, "Resolving host...");
+
+ // Populate address information
+ status = getaddrinfo(address, // e.g. "www.example.com" or IP
+ service, // e.g. "http" or port number
+ &hints, // e.g. SOCK_STREAM/SOCK_DGRAM
+ &res // The struct to populate
+ );
+
+ // Did we succeed?
+ if (status != 0)
+ {
+ int error = SocketGetLastError();
+ SocketSetLastError(0);
+ TraceLog(LOG_WARNING, "Failed to get resolve host: %s", SocketErrorCodeToString(error));
+ return -1;
+ }
+ else
+ {
+ TraceLog(LOG_INFO, "Successfully resolved host %s:%s", address, service);
+ }
+
+ // Calculate the size of the address information list
+ int size = 0;
+ for (iterator = res; iterator != NULL; iterator = iterator->ai_next)
+ {
+ size++;
+ }
+
+ // Validate the size is > 0, otherwise return
+ if (size <= 0)
+ {
+ TraceLog(LOG_WARNING, "Error, no addresses found.");
+ return -1;
+ }
+
+ // If not address list was allocated, allocate it dynamically with the known address size
+ if (outAddr == NULL)
+ {
+ outAddr = (AddressInformation *) malloc(size * sizeof(AddressInformation));
+ }
+
+ // Dynamically allocate an array of address information structs
+ if (outAddr != NULL)
+ {
+ int i;
+ for (i = 0; i < size; ++i)
+ {
+ outAddr[i] = AllocAddress();
+ if (outAddr[i] == NULL)
+ {
+ break;
+ }
+ }
+ outAddr[i] = NULL;
+ if (i != size)
+ {
+ outAddr = NULL;
+ }
+ }
+ else
+ {
+ TraceLog(LOG_WARNING,
+ "Error, failed to dynamically allocate memory for the address list");
+ return -1;
+ }
+
+ // Copy all the address information from res into outAddrList
+ int i = 0;
+ for (iterator = res; iterator != NULL; iterator = iterator->ai_next)
+ {
+ if (i < size)
+ {
+ outAddr[i]->addr.ai_flags = iterator->ai_flags;
+ outAddr[i]->addr.ai_family = iterator->ai_family;
+ outAddr[i]->addr.ai_socktype = iterator->ai_socktype;
+ outAddr[i]->addr.ai_protocol = iterator->ai_protocol;
+ outAddr[i]->addr.ai_addrlen = iterator->ai_addrlen;
+ *outAddr[i]->addr.ai_addr = *iterator->ai_addr;
+#if NET_DEBUG_ENABLED
+ TraceLog(LOG_DEBUG, "GetAddressInformation");
+ TraceLog(LOG_DEBUG, "\tFlags: 0x%x", iterator->ai_flags);
+ PrintSocket(outAddr[i]->addr.ai_addr,
+ outAddr[i]->addr.ai_family,
+ outAddr[i]->addr.ai_socktype,
+ outAddr[i]->addr.ai_protocol);
+ TraceLog(LOG_DEBUG, "Length of this sockaddr: %d", outAddr[i]->addr.ai_addrlen);
+ TraceLog(LOG_DEBUG, "Canonical name: %s", iterator->ai_canonname);
+#endif
+ i++;
+ }
+ }
+
+ // Free the pointer to the data returned by addrinfo
+ freeaddrinfo(res);
+
+ // Return the total count of addresses found
+ return size;
+}
+
+// This here is the bread and butter of the socket API, This function will
+// attempt to open a socket, bind and listen to it based on the config passed in
+//
+// SocketConfig* config - Configuration for which socket to open
+// SocketResult* result - The results of this function (if any, including errors)
+//
+// e.g.
+// SocketConfig server_config = { SocketConfig client_config = {
+// .host = "127.0.0.1", .host = "127.0.0.1",
+// .port = 8080, .port = 8080,
+// .server = true, };
+// .nonblocking = true,
+// };
+// SocketResult server_res; SocketResult client_res;
+bool SocketCreate(SocketConfig *config, SocketResult *result)
+{
+ // Socket creation result
+ bool success = true;
+
+ // Make sure we've not received a null config or result pointer
+ if (config == NULL || result == NULL)
+ {
+ return (success = false);
+ }
+
+ // Set the defaults based on the config
+ if (!SocketSetDefaults(config))
+ {
+ TraceLog(LOG_WARNING, "Configuration Error.");
+ success = false;
+ }
+ else
+ {
+ // Create the socket
+ if (CreateSocket(config, result))
+ {
+ if (config->nonblocking)
+ {
+ SocketSetNonBlocking(result->socket);
+ }
+ else
+ {
+ SocketSetBlocking(result->socket);
+ }
+ }
+ else
+ {
+ success = false;
+ }
+ }
+ return success;
+}
+
+// Bind a socket to a local address
+// Note: The bind function is required on an unconnected socket before subsequent calls to the listen function.
+bool SocketBind(SocketConfig *config, SocketResult *result)
+{
+ bool success = false;
+ result->status = RESULT_FAILURE;
+ struct sockaddr_storage *sock_addr = NULL;
+
+ // Don't bind to a socket that isn't configured as a server
+ if (!IsSocketValid(result->socket) || !config->server)
+ {
+ TraceLog(LOG_WARNING,
+ "Cannot bind to socket marked as \"Client\" in SocketConfig.");
+ success = false;
+ }
+ else
+ {
+ if (result->socket->isIPv6)
+ {
+ sock_addr = (struct sockaddr_storage *) &result->socket->addripv6->address;
+ }
+ else
+ {
+ sock_addr = (struct sockaddr_storage *) &result->socket->addripv4->address;
+ }
+ if (sock_addr != NULL)
+ {
+ if (bind(result->socket->channel, (struct sockaddr *) sock_addr, sizeof(*sock_addr)) != SOCKET_ERROR)
+ {
+ TraceLog(LOG_INFO, "Successfully bound socket.");
+ success = true;
+ }
+ else
+ {
+ result->socket->status = SocketGetLastError();
+ TraceLog(LOG_WARNING, "Socket Error: %s",
+ SocketErrorCodeToString(result->socket->status));
+ SocketSetLastError(0);
+ success = false;
+ }
+ }
+ }
+ // Was the bind a success?
+ if (success)
+ {
+ result->status = RESULT_SUCCESS;
+ result->socket->ready = 0;
+ result->socket->status = 0;
+ socklen_t sock_len = sizeof(*sock_addr);
+ if (getsockname(result->socket->channel, (struct sockaddr *) sock_addr, &sock_len) < 0)
+ {
+ TraceLog(LOG_WARNING, "Couldn't get socket address");
+ }
+ else
+ {
+ struct sockaddr_in *s = (struct sockaddr_in *) sock_addr;
+ // result->socket->address.host = s->sin_addr.s_addr;
+ // result->socket->address.port = s->sin_port;
+
+ //
+ result->socket->addripv4
+ = (struct _SocketAddressIPv4 *) malloc(sizeof(*result->socket->addripv4));
+ if (result->socket->addripv4 != NULL)
+ {
+ memset(result->socket->addripv4, 0, sizeof(*result->socket->addripv4));
+ }
+ memcpy(&result->socket->addripv4->address, (struct sockaddr_in *) &s->sin_addr, sizeof(struct sockaddr_in));
+ //
+ }
+ }
+ return success;
+}
+
+// Listens (and queues) incoming connections requests for a bound port.
+bool SocketListen(SocketConfig *config, SocketResult *result)
+{
+ bool success = false;
+ result->status = RESULT_FAILURE;
+
+ // Don't bind to a socket that isn't configured as a server
+ if (!IsSocketValid(result->socket) || !config->server)
+ {
+ TraceLog(LOG_WARNING,
+ "Cannot listen on socket marked as \"Client\" in SocketConfig.");
+ success = false;
+ }
+ else
+ {
+ // Don't listen on UDP sockets
+ if (!(config->type == SOCKET_UDP))
+ {
+ if (listen(result->socket->channel, config->backlog_size) != SOCKET_ERROR)
+ {
+ TraceLog(LOG_INFO, "Started listening on socket...");
+ success = true;
+ }
+ else
+ {
+ success = false;
+ result->socket->status = SocketGetLastError();
+ TraceLog(LOG_WARNING, "Socket Error: %s",
+ SocketErrorCodeToString(result->socket->status));
+ SocketSetLastError(0);
+ }
+ }
+ else
+ {
+ TraceLog(LOG_WARNING,
+ "Cannot listen on socket marked as \"UDP\" (datagram) in SocketConfig.");
+ success = false;
+ }
+ }
+
+ // Was the listen a success?
+ if (success)
+ {
+ result->status = RESULT_SUCCESS;
+ result->socket->ready = 0;
+ result->socket->status = 0;
+ }
+ return success;
+}
+
+// Connect the socket to the destination specified by "host" and "port" in SocketConfig
+bool SocketConnect(SocketConfig *config, SocketResult *result)
+{
+ bool success = true;
+ result->status = RESULT_FAILURE;
+
+ // Only bind to sockets marked as server
+ if (config->server)
+ {
+ TraceLog(LOG_WARNING,
+ "Cannot connect to socket marked as \"Server\" in SocketConfig.");
+ success = false;
+ }
+ else
+ {
+ if (IsIPv4Address(config->host))
+ {
+ struct sockaddr_in ip4addr;
+ ip4addr.sin_family = AF_INET;
+ unsigned long hport;
+ hport = strtoul(config->port, NULL, 0);
+ ip4addr.sin_port = htons(hport);
+ inet_pton(AF_INET, config->host, &ip4addr.sin_addr);
+ int connect_result = connect(result->socket->channel, (struct sockaddr *) &ip4addr, sizeof(ip4addr));
+ if (connect_result == SOCKET_ERROR)
+ {
+ result->socket->status = SocketGetLastError();
+ SocketSetLastError(0);
+ switch (result->socket->status)
+ {
+ case WSAEWOULDBLOCK:
+ {
+ success = true;
+ break;
+ }
+ default:
+ {
+ TraceLog(LOG_WARNING, "Socket Error: %s",
+ SocketErrorCodeToString(result->socket->status));
+ success = false;
+ break;
+ }
+ }
+ }
+ else
+ {
+ TraceLog(LOG_INFO, "Successfully connected to socket.");
+ success = true;
+ }
+ }
+ else
+ {
+ if (IsIPv6Address(config->host))
+ {
+ struct sockaddr_in6 ip6addr;
+ ip6addr.sin6_family = AF_INET6;
+ unsigned long hport;
+ hport = strtoul(config->port, NULL, 0);
+ ip6addr.sin6_port = htons(hport);
+ inet_pton(AF_INET6, config->host, &ip6addr.sin6_addr);
+ int connect_result = connect(result->socket->channel, (struct sockaddr *) &ip6addr, sizeof(ip6addr));
+ if (connect_result == SOCKET_ERROR)
+ {
+ result->socket->status = SocketGetLastError();
+ SocketSetLastError(0);
+ switch (result->socket->status)
+ {
+ case WSAEWOULDBLOCK:
+ {
+ success = true;
+ break;
+ }
+ default:
+ {
+ TraceLog(LOG_WARNING, "Socket Error: %s",
+ SocketErrorCodeToString(result->socket->status));
+ success = false;
+ break;
+ }
+ }
+ }
+ else
+ {
+ TraceLog(LOG_INFO, "Successfully connected to socket.");
+ success = true;
+ }
+ }
+ }
+ }
+
+ if (success)
+ {
+ result->status = RESULT_SUCCESS;
+ result->socket->ready = 0;
+ result->socket->status = 0;
+ }
+
+ return success;
+}
+
+// Closes an existing socket
+//
+// SocketChannel socket - The id of the socket to close
+void SocketClose(Socket *sock)
+{
+ if (sock != NULL)
+ {
+ if (sock->channel != INVALID_SOCKET)
+ {
+ closesocket(sock->channel);
+ }
+ }
+}
+
+// Returns the sockaddress for a specific socket in a generic storage struct
+SocketAddressStorage SocketGetPeerAddress(Socket *sock)
+{
+ if (sock->isServer)
+ {
+ return NULL;
+ }
+ if (sock->isIPv6)
+ {
+ return sock->addripv6;
+ }
+ else
+ {
+ return sock->addripv4;
+ }
+}
+
+// Return the address-type appropriate host portion of a socket address
+char *GetSocketAddressHost(SocketAddressStorage storage)
+{
+ assert(storage->address.ss_family == AF_INET || storage->address.ss_family == AF_INET6);
+ return SocketAddressToString((struct sockaddr_storage *) storage);
+}
+
+// Return the address-type appropriate port(service) portion of a socket address
+short GetSocketAddressPort(SocketAddressStorage storage)
+{
+ return ntohs(GetSocketPortPtr(storage));
+}
+
+// The accept function permits an incoming connection attempt on a socket.
+//
+// SocketChannel listener - The socket to listen for incoming connections on (i.e. server)
+// SocketResult* out - The result of this function (if any, including errors)
+//
+// e.g.
+//
+// SocketResult connection;
+// bool connected = false;
+// if (!connected)
+// {
+// if (SocketAccept(server_res.socket.channel, &connection))
+// {
+// connected = true;
+// }
+// }
+Socket *SocketAccept(Socket *server, SocketConfig *config)
+{
+ if (!server->isServer || server->type == SOCKET_UDP)
+ {
+ return NULL;
+ }
+ struct sockaddr_storage sock_addr;
+ socklen_t sock_alen;
+ Socket * sock;
+ sock = AllocSocket();
+ server->ready = 0;
+ sock_alen = sizeof(sock_addr);
+ sock->channel = accept(server->channel, (struct sockaddr *) &sock_addr, &sock_alen);
+ if (sock->channel == INVALID_SOCKET)
+ {
+ sock->status = SocketGetLastError();
+ TraceLog(LOG_WARNING, "Socket Error: %s", SocketErrorCodeToString(sock->status));
+ SocketSetLastError(0);
+ SocketClose(sock);
+ return NULL;
+ }
+ (config->nonblocking) ? SocketSetNonBlocking(sock) : SocketSetBlocking(sock);
+ sock->isServer = false;
+ sock->ready = 0;
+ sock->type = server->type;
+ switch (sock_addr.ss_family)
+ {
+ case AF_INET:
+ {
+ struct sockaddr_in *s = ((struct sockaddr_in *) &sock_addr);
+ sock->addripv4 = (struct _SocketAddressIPv4 *) malloc(sizeof(*sock->addripv4));
+ if (sock->addripv4 != NULL)
+ {
+ memset(sock->addripv4, 0, sizeof(*sock->addripv4));
+ memcpy(&sock->addripv4->address, (struct sockaddr_in *) &s->sin_addr, sizeof(struct sockaddr_in));
+ TraceLog(LOG_INFO, "Server: Got connection from %s::%hu", SocketAddressToString((struct sockaddr_storage *) s),
+ ntohs(sock->addripv4->address.sin_port));
+ }
+ }
+ break;
+ case AF_INET6:
+ {
+ struct sockaddr_in6 *s = ((struct sockaddr_in6 *) &sock_addr);
+ sock->addripv6 = (struct _SocketAddressIPv6 *) malloc(sizeof(*sock->addripv6));
+ if (sock->addripv6 != NULL)
+ {
+ memset(sock->addripv6, 0, sizeof(*sock->addripv6));
+ memcpy(&sock->addripv6->address, (struct sockaddr_in6 *) &s->sin6_addr, sizeof(struct sockaddr_in6));
+ TraceLog(LOG_INFO, "Server: Got connection from %s::%hu", SocketAddressToString((struct sockaddr_storage *) s),
+ ntohs(sock->addripv6->address.sin6_port));
+ }
+ }
+ break;
+ }
+ return sock;
+}
+
+// Verify that the channel is in the valid range
+static int ValidChannel(int channel)
+{
+ if ((channel < 0) || (channel >= SOCKET_MAX_UDPCHANNELS))
+ {
+ TraceLog(LOG_WARNING, "Invalid channel");
+ return 0;
+ }
+ return 1;
+}
+
+// Set the socket channel
+int SocketSetChannel(Socket *socket, int channel, const IPAddress *address)
+{
+ struct UDPChannel *binding;
+ if (socket == NULL)
+ {
+ TraceLog(LOG_WARNING, "Passed a NULL socket");
+ return (-1);
+ }
+ if (channel == -1)
+ {
+ for (channel = 0; channel < SOCKET_MAX_UDPCHANNELS; ++channel)
+ {
+ binding = &socket->binding[channel];
+ if (binding->numbound < SOCKET_MAX_UDPADDRESSES)
+ {
+ break;
+ }
+ }
+ }
+ else
+ {
+ if (!ValidChannel(channel))
+ {
+ return (-1);
+ }
+ binding = &socket->binding[channel];
+ }
+ if (binding->numbound == SOCKET_MAX_UDPADDRESSES)
+ {
+ TraceLog(LOG_WARNING, "No room for new addresses");
+ return (-1);
+ }
+ binding->address[binding->numbound++] = *address;
+ return (channel);
+}
+
+// Remove the socket channel
+void SocketUnsetChannel(Socket *socket, int channel)
+{
+ if ((channel >= 0) && (channel < SOCKET_MAX_UDPCHANNELS))
+ {
+ socket->binding[channel].numbound = 0;
+ }
+}
+
+/* Allocate/free a single UDP packet 'size' bytes long.
+ The new packet is returned, or NULL if the function ran out of memory.
+ */
+SocketDataPacket *AllocPacket(int size)
+{
+ SocketDataPacket *packet;
+ int error;
+
+ error = 1;
+ packet = (SocketDataPacket *) malloc(sizeof(*packet));
+ if (packet != NULL)
+ {
+ packet->maxlen = size;
+ packet->data = (uint8_t *) malloc(size);
+ if (packet->data != NULL)
+ {
+ error = 0;
+ }
+ }
+ if (error)
+ {
+ FreePacket(packet);
+ packet = NULL;
+ }
+ return (packet);
+}
+
+int ResizePacket(SocketDataPacket *packet, int newsize)
+{
+ uint8_t *newdata;
+
+ newdata = (uint8_t *) malloc(newsize);
+ if (newdata != NULL)
+ {
+ free(packet->data);
+ packet->data = newdata;
+ packet->maxlen = newsize;
+ }
+ return (packet->maxlen);
+}
+
+void FreePacket(SocketDataPacket *packet)
+{
+ if (packet)
+ {
+ free(packet->data);
+ free(packet);
+ }
+}
+
+/* Allocate/Free a UDP packet vector (array of packets) of 'howmany' packets,
+ each 'size' bytes long.
+ A pointer to the packet array is returned, or NULL if the function ran out
+ of memory.
+ */
+SocketDataPacket **AllocPacketList(int howmany, int size)
+{
+ SocketDataPacket **packetV;
+
+ packetV = (SocketDataPacket **) malloc((howmany + 1) * sizeof(*packetV));
+ if (packetV != NULL)
+ {
+ int i;
+ for (i = 0; i < howmany; ++i)
+ {
+ packetV[i] = AllocPacket(size);
+ if (packetV[i] == NULL)
+ {
+ break;
+ }
+ }
+ packetV[i] = NULL;
+
+ if (i != howmany)
+ {
+ FreePacketList(packetV);
+ packetV = NULL;
+ }
+ }
+ return (packetV);
+}
+
+void FreePacketList(SocketDataPacket **packetV)
+{
+ if (packetV)
+ {
+ int i;
+ for (i = 0; packetV[i]; ++i)
+ {
+ FreePacket(packetV[i]);
+ }
+ free(packetV);
+ }
+}
+
+// Send 'len' bytes of 'data' over the non-server socket 'sock'
+//
+// Example
+int SocketSend(Socket *sock, const void *datap, int length)
+{
+ int sent = 0;
+ int left = length;
+ int status = -1;
+ int numsent = 0;
+ const unsigned char *data = (const unsigned char *) datap;
+
+ // Server sockets are for accepting connections only
+ if (sock->isServer)
+ {
+ TraceLog(LOG_WARNING, "Cannot send information on a server socket");
+ return -1;
+ }
+
+ // Which socket are we trying to send data on
+ switch (sock->type)
+ {
+ case SOCKET_TCP:
+ {
+ SocketSetLastError(0);
+ do
+ {
+ length = send(sock->channel, (const char *) data, left, 0);
+ if (length > 0)
+ {
+ sent += length;
+ left -= length;
+ data += length;
+ }
+ } while ((left > 0) && // While we still have bytes left to send
+ ((length > 0) || // The amount of bytes we actually sent is > 0
+ (SocketGetLastError() == WSAEINTR)) // The socket was interupted
+ );
+
+ if (length == SOCKET_ERROR)
+ {
+ sock->status = SocketGetLastError();
+ TraceLog(LOG_DEBUG, "Socket Error: %s", SocketErrorCodeToString(sock->status));
+ SocketSetLastError(0);
+ }
+ else
+ {
+ TraceLog(LOG_DEBUG, "Successfully sent \"%s\" (%d bytes)", datap, sent);
+ }
+
+ return sent;
+ }
+ break;
+ case SOCKET_UDP:
+ {
+ SocketSetLastError(0);
+ if (sock->isIPv6)
+ {
+ status = sendto(sock->channel, (const char *) data, left, 0,
+ (struct sockaddr *) &sock->addripv6->address,
+ sizeof(sock->addripv6->address));
+ }
+ else
+ {
+ status = sendto(sock->channel, (const char *) data, left, 0,
+ (struct sockaddr *) &sock->addripv4->address,
+ sizeof(sock->addripv4->address));
+ }
+ if (sent >= 0)
+ {
+ sock->status = 0;
+ ++numsent;
+ TraceLog(LOG_DEBUG, "Successfully sent \"%s\" (%d bytes)", datap, status);
+ }
+ else
+ {
+ sock->status = SocketGetLastError();
+ TraceLog(LOG_DEBUG, "Socket Error: %s", SocketGetLastErrorString(sock->status));
+ SocketSetLastError(0);
+ return 0;
+ }
+ return numsent;
+ }
+ break;
+ default: break;
+ }
+ return -1;
+}
+
+// Receive up to 'maxlen' bytes of data over the non-server socket 'sock',
+// and store them in the buffer pointed to by 'data'.
+// This function returns the actual amount of data received. If the return
+// value is less than or equal to zero, then either the remote connection was
+// closed, or an unknown socket error occurred.
+int SocketReceive(Socket *sock, void *data, int maxlen)
+{
+ int len = 0;
+ int numrecv = 0;
+ int status = 0;
+ socklen_t sock_len;
+ struct sockaddr_storage sock_addr;
+ char ip[INET6_ADDRSTRLEN];
+
+ // Server sockets are for accepting connections only
+ if (sock->isServer && sock->type == SOCKET_TCP)
+ {
+ sock->status = SocketGetLastError();
+ TraceLog(LOG_DEBUG, "Socket Error: %s",
+ "Server sockets cannot be used to receive data");
+ SocketSetLastError(0);
+ return 0;
+ }
+
+ // Which socket are we trying to send data on
+ switch (sock->type)
+ {
+ case SOCKET_TCP:
+ {
+ SocketSetLastError(0);
+ do
+ {
+ len = recv(sock->channel, (char *) data, maxlen, 0);
+ } while (SocketGetLastError() == WSAEINTR);
+
+ if (len > 0)
+ {
+ // Who sent the packet?
+ if (sock->type == SOCKET_UDP)
+ {
+ TraceLog(
+ LOG_DEBUG, "Received data from: %s", inet_ntop(sock_addr.ss_family, GetSocketAddressPtr((struct sockaddr *) &sock_addr), ip, sizeof(ip)));
+ }
+ ((unsigned char *) data)[len] = '\0'; // Add null terminating character to the end of the stream
+ TraceLog(LOG_DEBUG, "Received \"%s\" (%d bytes)", data, len);
+ }
+ sock->ready = 0;
+ return len;
+ }
+ break;
+ case SOCKET_UDP:
+ {
+ SocketSetLastError(0);
+ sock_len = sizeof(sock_addr);
+ status = recvfrom(sock->channel, // The receving channel
+ data, // A pointer to the data buffer to fill
+ maxlen, // The max length of the data to fill
+ 0, // Flags
+ (struct sockaddr *) &sock_addr, // The address of the recevied data
+ &sock_len // The length of the received data address
+ );
+ if (status >= 0)
+ {
+ ++numrecv;
+ }
+ else
+ {
+ sock->status = SocketGetLastError();
+ switch (sock->status)
+ {
+ case WSAEWOULDBLOCK: { break;
+ }
+ default:
+ {
+ TraceLog(LOG_WARNING, "Socket Error: %s", SocketErrorCodeToString(sock->status));
+ break;
+ }
+ }
+ SocketSetLastError(0);
+ return 0;
+ }
+ sock->ready = 0;
+ return numrecv;
+ }
+ break;
+ }
+ return -1;
+}
+
+// Does the socket have it's 'ready' flag set?
+bool IsSocketReady(Socket *sock)
+{
+ return (sock != NULL) && (sock->ready);
+}
+
+// Check if the socket is considered connected
+bool IsSocketConnected(Socket *sock)
+{
+#if PLATFORM_WINDOWS
+ FD_SET writefds;
+ FD_ZERO(&writefds);
+ FD_SET(sock->channel, &writefds);
+ struct timeval timeout;
+ timeout.tv_sec = 1;
+ timeout.tv_usec = 1000000000UL;
+ int total = select(0, NULL, &writefds, NULL, &timeout);
+ if (total == -1)
+ { // Error
+ sock->status = SocketGetLastError();
+ TraceLog(LOG_WARNING, "Socket Error: %s", SocketErrorCodeToString(sock->status));
+ SocketSetLastError(0);
+ }
+ else if (total == 0)
+ { // Timeout
+ return false;
+ }
+ else
+ {
+ if (FD_ISSET(sock->channel, &writefds))
+ {
+ return true;
+ }
+ }
+ return false;
+#else
+ return true;
+#endif
+}
+
+// Allocate and return a SocketResult struct
+SocketResult *AllocSocketResult()
+{
+ struct SocketResult *res;
+ res = (struct SocketResult *) malloc(sizeof(*res));
+ if (res != NULL)
+ {
+ memset(res, 0, sizeof(*res));
+ if ((res->socket = AllocSocket()) == NULL)
+ {
+ free(res);
+ res = NULL;
+ }
+ }
+ return res;
+}
+
+// Free an allocated SocketResult
+void FreeSocketResult(SocketResult **result)
+{
+ if (*result != NULL)
+ {
+ if ((*result)->socket != NULL)
+ {
+ FreeSocket(&((*result)->socket));
+ }
+ free(*result);
+ *result = NULL;
+ }
+}
+
+// Allocate a Socket
+Socket *AllocSocket()
+{
+ // Allocate a socket if one already hasn't been
+ struct Socket *sock;
+ sock = (Socket *) malloc(sizeof(*sock));
+ if (sock != NULL)
+ {
+ memset(sock, 0, sizeof(*sock));
+ }
+ else
+ {
+ TraceLog(
+ LOG_WARNING, "Ran out of memory attempting to allocate a socket");
+ SocketClose(sock);
+ free(sock);
+ sock = NULL;
+ }
+ return sock;
+}
+
+// Free an allocated Socket
+void FreeSocket(Socket **sock)
+{
+ if (*sock != NULL)
+ {
+ free(*sock);
+ *sock = NULL;
+ }
+}
+
+// Allocate a SocketSet
+SocketSet *AllocSocketSet(int max)
+{
+ struct SocketSet *set;
+ int i;
+
+ set = (struct SocketSet *) malloc(sizeof(*set));
+ if (set != NULL)
+ {
+ set->numsockets = 0;
+ set->maxsockets = max;
+ set->sockets = (struct Socket **) malloc(max * sizeof(*set->sockets));
+ if (set->sockets != NULL)
+ {
+ for (i = 0; i < max; ++i)
+ {
+ set->sockets[i] = NULL;
+ }
+ }
+ else
+ {
+ free(set);
+ set = NULL;
+ }
+ }
+ return (set);
+}
+
+// Free an allocated SocketSet
+void FreeSocketSet(SocketSet *set)
+{
+ if (set)
+ {
+ free(set->sockets);
+ free(set);
+ }
+}
+
+// Add a Socket "sock" to the SocketSet "set"
+int AddSocket(SocketSet *set, Socket *sock)
+{
+ if (sock != NULL)
+ {
+ if (set->numsockets == set->maxsockets)
+ {
+ TraceLog(LOG_DEBUG, "Socket Error: %s", "SocketSet is full");
+ SocketSetLastError(0);
+ return (-1);
+ }
+ set->sockets[set->numsockets++] = (struct Socket *) sock;
+ }
+ else
+ {
+ TraceLog(LOG_DEBUG, "Socket Error: %s", "Socket was null");
+ SocketSetLastError(0);
+ return (-1);
+ }
+ return (set->numsockets);
+}
+
+// Remove a Socket "sock" to the SocketSet "set"
+int RemoveSocket(SocketSet *set, Socket *sock)
+{
+ int i;
+
+ if (sock != NULL)
+ {
+ for (i = 0; i < set->numsockets; ++i)
+ {
+ if (set->sockets[i] == (struct Socket *) sock)
+ {
+ break;
+ }
+ }
+ if (i == set->numsockets)
+ {
+ TraceLog(LOG_DEBUG, "Socket Error: %s", "Socket not found");
+ SocketSetLastError(0);
+ return (-1);
+ }
+ --set->numsockets;
+ for (; i < set->numsockets; ++i)
+ {
+ set->sockets[i] = set->sockets[i + 1];
+ }
+ }
+ return (set->numsockets);
+}
+
+// Check the sockets in the socket set for pending information
+int CheckSockets(SocketSet *set, unsigned int timeout)
+{
+ int i;
+ SOCKET maxfd;
+ int retval;
+ struct timeval tv;
+ fd_set mask;
+
+ /* Find the largest file descriptor */
+ maxfd = 0;
+ for (i = set->numsockets - 1; i >= 0; --i)
+ {
+ if (set->sockets[i]->channel > maxfd)
+ {
+ maxfd = set->sockets[i]->channel;
+ }
+ }
+
+ // Check the file descriptors for available data
+ do
+ {
+ SocketSetLastError(0);
+
+ // Set up the mask of file descriptors
+ FD_ZERO(&mask);
+ for (i = set->numsockets - 1; i >= 0; --i)
+ {
+ FD_SET(set->sockets[i]->channel, &mask);
+ } // Set up the timeout
+ tv.tv_sec = timeout / 1000;
+ tv.tv_usec = (timeout % 1000) * 1000;
+
+ /* Look! */
+ retval = select(maxfd + 1, &mask, NULL, NULL, &tv);
+ } while (SocketGetLastError() == WSAEINTR);
+
+ // Mark all file descriptors ready that have data available
+ if (retval > 0)
+ {
+ for (i = set->numsockets - 1; i >= 0; --i)
+ {
+ if (FD_ISSET(set->sockets[i]->channel, &mask))
+ {
+ set->sockets[i]->ready = 1;
+ }
+ }
+ }
+ return (retval);
+}
+
+// Allocate an AddressInformation
+AddressInformation AllocAddress()
+{
+ AddressInformation addressInfo = NULL;
+ addressInfo = (AddressInformation) calloc(1, sizeof(*addressInfo));
+ if (addressInfo != NULL)
+ {
+ addressInfo->addr.ai_addr = (struct sockaddr *) calloc(1, sizeof(struct sockaddr));
+ if (addressInfo->addr.ai_addr == NULL)
+ {
+ TraceLog(LOG_WARNING,
+ "Failed to allocate memory for \"struct sockaddr\"");
+ }
+ }
+ else
+ {
+ TraceLog(LOG_WARNING,
+ "Failed to allocate memory for \"struct AddressInformation\"");
+ }
+ return addressInfo;
+}
+
+// Free an AddressInformation struct
+void FreeAddress(AddressInformation *addressInfo)
+{
+ if (*addressInfo != NULL)
+ {
+ if ((*addressInfo)->addr.ai_addr != NULL)
+ {
+ free((*addressInfo)->addr.ai_addr);
+ (*addressInfo)->addr.ai_addr = NULL;
+ }
+ free(*addressInfo);
+ *addressInfo = NULL;
+ }
+}
+
+// Allocate a list of AddressInformation
+AddressInformation *AllocAddressList(int size)
+{
+ AddressInformation *addr;
+ addr = (AddressInformation *) malloc(size * sizeof(AddressInformation));
+ return addr;
+}
+
+// Opaque datatype accessor addrinfo->ai_family
+int GetAddressFamily(AddressInformation address)
+{
+ return address->addr.ai_family;
+}
+
+// Opaque datatype accessor addrinfo->ai_socktype
+int GetAddressSocketType(AddressInformation address)
+{
+ return address->addr.ai_socktype;
+}
+
+// Opaque datatype accessor addrinfo->ai_protocol
+int GetAddressProtocol(AddressInformation address)
+{
+ return address->addr.ai_protocol;
+}
+
+// Opaque datatype accessor addrinfo->ai_canonname
+char *GetAddressCanonName(AddressInformation address)
+{
+ return address->addr.ai_canonname;
+}
+
+// Opaque datatype accessor addrinfo->ai_addr
+char *GetAddressHostAndPort(AddressInformation address, char *outhost, int *outport)
+{
+ char * ip[INET6_ADDRSTRLEN];
+ char * result = NULL;
+ struct sockaddr_storage *storage = (struct sockaddr_storage *) address->addr.ai_addr;
+ switch (storage->ss_family)
+ {
+ case AF_INET:
+ {
+ struct sockaddr_in *s = ((struct sockaddr_in *) address->addr.ai_addr);
+ result = inet_ntop(AF_INET, &s->sin_addr, ip, INET_ADDRSTRLEN);
+ *outport = ntohs(s->sin_port);
+ }
+ break;
+ case AF_INET6:
+ {
+ struct sockaddr_in6 *s = ((struct sockaddr_in6 *) address->addr.ai_addr);
+ result = inet_ntop(AF_INET6, &s->sin6_addr, ip, INET6_ADDRSTRLEN);
+ *outport = ntohs(s->sin6_port);
+ }
+ break;
+ }
+ if (result == NULL)
+ {
+ TraceLog(LOG_WARNING, "Socket Error: %s", SocketErrorCodeToString(SocketGetLastError()));
+ SocketSetLastError(0);
+ }
+ else
+ {
+ strcpy(outhost, result);
+ }
+ return result;
+}
+
+//
+void PacketSend(Packet *packet)
+{
+ printf("Sending packet (%s) with size %d\n", packet->data, packet->size);
+}
+
+//
+void PacketReceive(Packet *packet)
+{
+ printf("Receiving packet (%s) with size %d\n", packet->data, packet->size);
+}
+
+//
+void PacketWrite16(Packet *packet, uint16_t value)
+{
+ printf("Original: 0x%04" PRIX16 " - %" PRIu16 "\n", value, value);
+ uint8_t *data = packet->data + packet->offs;
+ *data++ = (uint8_t)(value >> 8);
+ *data++ = (uint8_t)(value);
+ packet->size += sizeof(uint16_t);
+ packet->offs += sizeof(uint16_t);
+ printf("Network: 0x%04" PRIX16 " - %" PRIu16 "\n", (uint16_t) *data, (uint16_t) *data);
+}
+
+//
+void PacketWrite32(Packet *packet, uint32_t value)
+{
+ printf("Original: 0x%08" PRIX32 " - %" PRIu32 "\n", value, value);
+ uint8_t *data = packet->data + packet->offs;
+ *data++ = (uint8_t)(value >> 24);
+ *data++ = (uint8_t)(value >> 16);
+ *data++ = (uint8_t)(value >> 8);
+ *data++ = (uint8_t)(value);
+ packet->size += sizeof(uint32_t);
+ packet->offs += sizeof(uint32_t);
+ printf("Network: 0x%08" PRIX32 " - %" PRIu32 "\n",
+ (uint32_t)(((intptr_t) packet->data) - packet->offs),
+ (uint32_t)(((intptr_t) packet->data) - packet->offs));
+}
+
+//
+void PacketWrite64(Packet *packet, uint64_t value)
+{
+ printf("Original: 0x%016" PRIX64 " - %" PRIu64 "\n", value, value);
+ uint8_t *data = packet->data + packet->offs;
+ *data++ = (uint8_t)(value >> 56);
+ *data++ = (uint8_t)(value >> 48);
+ *data++ = (uint8_t)(value >> 40);
+ *data++ = (uint8_t)(value >> 32);
+ *data++ = (uint8_t)(value >> 24);
+ *data++ = (uint8_t)(value >> 16);
+ *data++ = (uint8_t)(value >> 8);
+ *data++ = (uint8_t)(value);
+ packet->size += sizeof(uint64_t);
+ packet->offs += sizeof(uint64_t);
+ printf("Network: 0x%016" PRIX64 " - %" PRIu64 "\n",
+ (uint64_t)(packet->data - packet->offs),
+ (uint64_t)(packet->data - packet->offs));
+}
+
+//
+uint16_t PacketRead16(Packet *packet)
+{
+ uint8_t *data = packet->data + packet->offs;
+ packet->size += sizeof(uint16_t);
+ packet->offs += sizeof(uint16_t);
+ uint16_t value = ((uint16_t) data[0] << 8) | data[1];
+ printf("Original: 0x%04" PRIX16 " - %" PRIu16 "\n", value, value);
+ return value;
+}
+
+//
+uint32_t PacketRead32(Packet *packet)
+{
+ uint8_t *data = packet->data + packet->offs;
+ packet->size += sizeof(uint32_t);
+ packet->offs += sizeof(uint32_t);
+ uint32_t value = ((uint32_t) data[0] << 24) | ((uint32_t) data[1] << 16) | ((uint32_t) data[2] << 8) | data[3];
+ printf("Original: 0x%08" PRIX32 " - %" PRIu32 "\n", value, value);
+ return value;
+}
+
+//
+uint64_t PacketRead64(Packet *packet)
+{
+ uint8_t *data = packet->data + packet->offs;
+ packet->size += sizeof(uint64_t);
+ packet->offs += sizeof(uint64_t);
+ uint64_t value = ((uint64_t) data[0] << 56) | ((uint64_t) data[1] << 48) | ((uint64_t) data[2] << 40) | ((uint64_t) data[3] << 32) | ((uint64_t) data[4] << 24) | ((uint64_t) data[5] << 16) | ((uint64_t) data[6] << 8) | data[7];
+ printf("Original: 0x%016" PRIX64 " - %" PRIu64 "\n", value, value);
+ return value;
+}
diff --git a/src/rnet.h b/src/rnet.h new file mode 100644 index 00000000..c2a14015 --- /dev/null +++ b/src/rnet.h @@ -0,0 +1,228 @@ +/**********************************************************************************************
+*
+* rnet - Provides cross-platform network defines, macros etc
+*
+* DEPENDENCIES:
+* <limits.h> - Used for cross-platform type specifiers
+*
+* INSPIRED BY:
+* SFML Sockets - https://www.sfml-dev.org/documentation/2.5.1/classsf_1_1Socket.php
+* SDL_net - https://www.libsdl.org/projects/SDL_net/
+* BSD Sockets - https://www.gnu.org/software/libc/manual/html_node/Sockets.html
+* BEEJ - https://beej.us/guide/bgnet/html/single/bgnet.html
+* Winsock2 - https://docs.microsoft.com/en-us/windows/desktop/api/winsock2
+*
+*
+* CONTRIBUTORS:
+* Jak Barnes (github: @syphonx) (Feb. 2019):
+* - Initial version
+*
+* LICENSE: zlib/libpng
+*
+* Copyright (c) 2014-2019 Ramon Santamaria (@raysan5)
+*
+* This software is provided "as-is", without any express or implied warranty. In no event
+* will the authors be held liable for any damages arising from the use of this software.
+*
+* Permission is granted to anyone to use this software for any purpose, including commercial
+* applications, and to alter it and redistribute it freely, subject to the following restrictions:
+*
+* 1. The origin of this software must not be misrepresented; you must not claim that you
+* wrote the original software. If you use this software in a product, an acknowledgment
+* in the product documentation would be appreciated but is not required.
+*
+* 2. Altered source versions must be plainly marked as such, and must not be misrepresented
+* as being the original software.
+*
+* 3. This notice may not be removed or altered from any source distribution.
+*
+**********************************************************************************************/
+
+//----------------------------------------------------------------------------------
+// Platform type sizes
+//----------------------------------------------------------------------------------
+
+#include <limits.h>
+
+//----------------------------------------------------------------------------------
+// Undefine any conflicting windows.h symbols
+//----------------------------------------------------------------------------------
+
+// If defined, the following flags inhibit definition of the indicated items.
+#define NOGDICAPMASKS // CC_*, LC_*, PC_*, CP_*, TC_*, RC_
+#define NOVIRTUALKEYCODES // VK_*
+#define NOWINMESSAGES // WM_*, EM_*, LB_*, CB_*
+#define NOWINSTYLES // WS_*, CS_*, ES_*, LBS_*, SBS_*, CBS_*
+#define NOSYSMETRICS // SM_*
+#define NOMENUS // MF_*
+#define NOICONS // IDI_*
+#define NOKEYSTATES // MK_*
+#define NOSYSCOMMANDS // SC_*
+#define NORASTEROPS // Binary and Tertiary raster ops
+#define NOSHOWWINDOW // SW_*
+#define OEMRESOURCE // OEM Resource values
+#define NOATOM // Atom Manager routines
+#define NOCLIPBOARD // Clipboard routines
+#define NOCOLOR // Screen colors
+#define NOCTLMGR // Control and Dialog routines
+#define NODRAWTEXT // DrawText() and DT_*
+#define NOGDI // All GDI defines and routines
+#define NOKERNEL // All KERNEL defines and routines
+#define NOUSER // All USER defines and routines
+#define NONLS // All NLS defines and routines
+#define NOMB // MB_* and MessageBox()
+#define NOMEMMGR // GMEM_*, LMEM_*, GHND, LHND, associated routines
+#define NOMETAFILE // typedef METAFILEPICT
+#define NOMINMAX // Macros min(a,b) and max(a,b)
+#define NOMSG // typedef MSG and associated routines
+#define NOOPENFILE // OpenFile(), OemToAnsi, AnsiToOem, and OF_*
+#define NOSCROLL // SB_* and scrolling routines
+#define NOSERVICE // All Service Controller routines, SERVICE_ equates, etc.
+#define NOSOUND // Sound driver routines
+#define NOTEXTMETRIC // typedef TEXTMETRIC and associated routines
+#define NOWH // SetWindowsHook and WH_*
+#define NOWINOFFSETS // GWL_*, GCL_*, associated routines
+#define NOCOMM // COMM driver routines
+#define NOKANJI // Kanji support stuff.
+#define NOHELP // Help engine interface.
+#define NOPROFILER // Profiler interface.
+#define NODEFERWINDOWPOS // DeferWindowPos routines
+#define NOMCX // Modem Configuration Extensions
+#define MMNOSOUND
+
+//----------------------------------------------------------------------------------
+// Platform defines
+//----------------------------------------------------------------------------------
+
+#define PLATFORM_WINDOWS 1
+#define PLATFORM_LINUX 2
+
+#if defined(__WIN32__) || defined(WIN32)
+# define PLATFORM PLATFORM_WINDOWS
+#elif defined(_LINUX)
+# define PLATFORM PLATFORM_LINUX
+#endif
+
+//----------------------------------------------------------------------------------
+// Platform type definitions
+// From: https://github.com/DFHack/clsocket/blob/master/src/Host.h
+//----------------------------------------------------------------------------------
+
+#ifdef WIN32
+typedef int socklen_t;
+#endif
+
+#ifndef RESULT_SUCCESS
+# define RESULT_SUCCESS 0
+#endif // RESULT_SUCCESS
+
+#ifndef RESULT_FAILURE
+# define RESULT_FAILURE 1
+#endif // RESULT_FAILURE
+
+#ifndef htonll
+# ifdef _BIG_ENDIAN
+# define htonll(x) (x)
+# define ntohll(x) (x)
+# else
+# define htonll(x) ((((uint64) htonl(x)) << 32) + htonl(x >> 32))
+# define ntohll(x) ((((uint64) ntohl(x)) << 32) + ntohl(x >> 32))
+# endif // _BIG_ENDIAN
+#endif // htonll
+
+//----------------------------------------------------------------------------------
+// Platform specific network includes
+// From: https://github.com/SDL-mirror/SDL_net/blob/master/SDLnetsys.h
+//----------------------------------------------------------------------------------
+
+// Include system network headers
+
+#ifdef _WIN32
+# pragma comment(lib, "ws2_32.lib")
+# define __USE_W32_SOCKETS
+# define WIN32_LEAN_AND_MEAN
+# include <winsock2.h>
+# include <Ws2tcpip.h>
+# include <io.h>
+# define IPTOS_LOWDELAY 0x10
+#else /* UNIX */
+# include <sys/types.h>
+# include <fcntl.h>
+# include <netinet/in.h>
+# include <sys/ioctl.h>
+# include <sys/time.h>
+# include <unistd.h>
+# include <net/if.h>
+# include <netdb.h>
+# include <netinet/tcp.h>
+# include <sys/socket.h>
+# include <arpa/inet.h>
+#endif /* WIN32 */
+
+#ifndef INVALID_SOCKET
+# define INVALID_SOCKET ~(0)
+#endif
+
+#ifndef __USE_W32_SOCKETS
+# define closesocket close
+# define SOCKET int
+# define INVALID_SOCKET -1
+# define SOCKET_ERROR -1
+#endif
+
+#ifdef __USE_W32_SOCKETS
+# ifndef EINTR
+# define EINTR WSAEINTR
+# endif
+#endif
+
+//----------------------------------------------------------------------------------
+// Module defines
+//----------------------------------------------------------------------------------
+
+// Network connection related defines
+#define SOCKET_MAX_SET_SIZE (32) // Maximum sockets in a set
+#define SOCKET_MAX_QUEUE_SIZE (16) // Maximum socket queue size
+
+// Network address related defines
+#define ADDRESS_IPV4_ADDRSTRLEN (22) // IPv4 string length
+#define ADDRESS_IPV6_ADDRSTRLEN (65) // IPv6 string length
+#define ADDRESS_TYPE_ANY (0) // AF_UNSPEC
+#define ADDRESS_TYPE_IPV4 (2) // AF_INET
+#define ADDRESS_TYPE_IPV6 (23) // AF_INET6
+#define ADDRESS_MAXHOST (1025) // Max size of a fully-qualified domain name
+#define ADDRESS_MAXSERV (32) // Max size of a service name
+
+// Network address related defines
+#define ADDRESS_ANY ((unsigned long) 0x00000000)
+#define ADDRESS_LOOPBACK (0x7f000001)
+#define ADDRESS_BROADCAST ((unsigned long) 0xffffffff)
+#define ADDRESS_NONE (0xffffffff)
+
+// Address resolution related defines
+#if defined(_WIN32)
+ #define ADDRESS_INFO_PASSIVE (0x00000001) // Socket address will be used in bind() call
+ #define ADDRESS_INFO_CANONNAME (0x00000002) // Return canonical name in first ai_canonname
+ #define ADDRESS_INFO_NUMERICHOST (0x00000004) // Nodename must be a numeric address string
+ #define ADDRESS_INFO_NUMERICSERV (0x00000008) // Servicename must be a numeric port number
+ #define ADDRESS_INFO_DNS_ONLY (0x00000010) // Restrict queries to unicast DNS only (no LLMNR, netbios, etc.)
+ #define ADDRESS_INFO_ALL (0x00000100) // Query both IP6 and IP4 with AI_V4MAPPED
+ #define ADDRESS_INFO_ADDRCONFIG (0x00000400) // Resolution only if global address configured
+ #define ADDRESS_INFO_V4MAPPED (0x00000800) // On v6 failure, query v4 and convert to V4MAPPED format
+ #define ADDRESS_INFO_NON_AUTHORITATIVE (0x00004000) // LUP_NON_AUTHORITATIVE
+ #define ADDRESS_INFO_SECURE (0x00008000) // LUP_SECURE
+ #define ADDRESS_INFO_RETURN_PREFERRED_NAMES (0x00010000) // LUP_RETURN_PREFERRED_NAMES
+ #define ADDRESS_INFO_FQDN (0x00020000) // Return the FQDN in ai_canonname
+ #define ADDRESS_INFO_FILESERVER (0x00040000) // Resolving fileserver name resolution
+ #define ADDRESS_INFO_DISABLE_IDN_ENCODING (0x00080000) // Disable Internationalized Domain Names handling
+ #define ADDRESS_INFO_EXTENDED (0x80000000) // Indicates this is extended ADDRINFOEX(2/..) struct
+ #define ADDRESS_INFO_RESOLUTION_HANDLE (0x40000000) // Request resolution handle
+#endif
+
+// Network resolution related defines
+#define NAME_INFO_DEFAULT (0x00) // No flags set
+#define NAME_INFO_NOFQDN (0x01) // Only return nodename portion for local hosts
+#define NAME_INFO_NUMERICHOST (0x02) // Return numeric form of the host's address
+#define NAME_INFO_NAMEREQD (0x04) // Error if the host's name not in DNS
+#define NAME_INFO_NUMERICSERV (0x08) // Return numeric form of the service (port #)
+#define NAME_INFO_DGRAM (0x10) // Service is a datagram service
\ No newline at end of file |
