about summary refs log tree commit diff
path: root/src/engine
diff options
context:
space:
mode:
Diffstat (limited to 'src/engine')
-rw-r--r--src/engine/shared/websockets.cpp275
-rw-r--r--src/engine/shared/websockets.h16
2 files changed, 291 insertions, 0 deletions
diff --git a/src/engine/shared/websockets.cpp b/src/engine/shared/websockets.cpp
new file mode 100644
index 00000000..795af2c2
--- /dev/null
+++ b/src/engine/shared/websockets.cpp
@@ -0,0 +1,275 @@
+#if defined(WEBSOCKETS)
+
+#include <string.h>
+#include <stdlib.h>
+
+#include "engine/external/libwebsockets/libwebsockets.h"
+#include "base/system.h"
+#include "protocol.h"
+#include "ringbuffer.h"
+#include <arpa/inet.h>
+
+extern "C" {
+
+#include "websockets.h"
+
+// not sure why would anyone need more than one but well...
+#define WS_CONTEXTS 4
+// ddnet client opens two connections for whatever reason
+#define WS_CLIENTS MAX_CLIENTS*2
+
+typedef TStaticRingBuffer<unsigned char, WS_CLIENTS * 4 * 1024, CRingBufferBase::FLAG_RECYCLE> TRecvBuffer;
+typedef TStaticRingBuffer<unsigned char, 4 * 1024, CRingBufferBase::FLAG_RECYCLE> TSendBuffer;
+
+typedef struct
+{
+	size_t size;
+	size_t read;
+	sockaddr_in addr;
+	unsigned char data[0];
+} websocket_chunk;
+
+struct per_session_data
+{
+	struct libwebsocket *wsi;
+	int port;
+	sockaddr_in addr;
+	TSendBuffer send_buffer;
+};
+
+struct context_data
+{
+	libwebsocket_context *context;
+	per_session_data *port_map[WS_CLIENTS];
+	TRecvBuffer recv_buffer;
+	int last_used_port;
+};
+
+static int receive_chunk(context_data *ctx_data, struct per_session_data *pss, void *in, size_t len)
+{
+	websocket_chunk *chunk = (websocket_chunk *)ctx_data->recv_buffer.Allocate(len + sizeof(websocket_chunk));
+	if(chunk == 0)
+		return 1;
+	chunk->size = len;
+	chunk->read = 0;
+	memcpy(&chunk->addr, &pss->addr, sizeof(sockaddr_in));
+	memcpy(&chunk->data[0], in, len);
+	return 0;
+}
+
+static int websocket_callback(struct libwebsocket_context *context, struct libwebsocket *wsi, enum libwebsocket_callback_reasons reason, void *user, void *in, size_t len)
+{
+	struct per_session_data *pss = (struct per_session_data *)user;
+	context_data *ctx_data = (context_data *)libwebsocket_context_user(context);
+
+	switch(reason)
+	{
+
+	case LWS_CALLBACK_ESTABLISHED:
+	{
+		int port = -1;
+		for(int i = 0; i < WS_CLIENTS; i++)
+		{
+			int j = (ctx_data->last_used_port + i + 1) % WS_CLIENTS;
+			if(ctx_data->port_map[j] == NULL)
+			{
+				port = j;
+				break;
+			}
+		}
+		if(port == -1)
+		{
+			dbg_msg("websockets", "no free ports, dropping");
+			pss->port = -1;
+			return -1;
+		}
+		ctx_data->last_used_port = port;
+		pss->wsi = wsi;
+		int fd = libwebsocket_get_socket_fd(wsi);
+		socklen_t addr_size = sizeof(pss->addr);
+		getpeername(fd, (struct sockaddr *)&pss->addr, &addr_size);
+		int orig_port = ntohs(pss->addr.sin_port);
+		pss->addr.sin_port = htons(port);
+		pss->send_buffer.Init();
+		pss->port = port;
+		ctx_data->port_map[port] = pss;
+		char addr_str[NETADDR_MAXSTRSIZE];
+		inet_ntop(AF_INET, &pss->addr.sin_addr, addr_str, sizeof(addr_str));
+		dbg_msg("websockets", "connection established with %s:%d , assigned fake port %d", addr_str, orig_port, port);
+	}
+	break;
+
+	case LWS_CALLBACK_CLOSED:
+	{
+		dbg_msg("websockets", "connection with fake port %d closed", pss->port);
+		if (pss->port > -1) {
+			unsigned char close_packet[] = { 0x10, 0x0e, 0x00, 0x04 };
+			receive_chunk(ctx_data, pss, &close_packet, sizeof(close_packet));
+			pss->wsi = 0;
+			ctx_data->port_map[pss->port] = NULL;
+		}
+	}
+	break;
+
+	case LWS_CALLBACK_SERVER_WRITEABLE:
+	{
+		websocket_chunk *chunk = (websocket_chunk *)pss->send_buffer.First();
+		if(chunk == NULL)
+			break;
+		int len = chunk->size - chunk->read;
+		int n = libwebsocket_write(wsi, &chunk->data[LWS_SEND_BUFFER_PRE_PADDING + chunk->read], chunk->size - chunk->read, LWS_WRITE_BINARY);
+		if(n < 0)
+			return 1;
+		if(n < len)
+		{
+			chunk->read += n;
+			libwebsocket_callback_on_writable(context, wsi);
+			break;
+		}
+		pss->send_buffer.PopFirst();
+		libwebsocket_callback_on_writable(context, wsi);
+	}
+	break;
+
+	case LWS_CALLBACK_RECEIVE:
+		if(pss->port == -1)
+			return -1;
+		if(!receive_chunk(ctx_data, pss, in, len))
+			return 1;
+		break;
+
+	default:
+		break;
+	}
+
+	return 0;
+}
+
+static struct libwebsocket_protocols protocols[] = { {
+		                                               "binary", /* name */
+		                                               websocket_callback, /* callback */
+		                                               sizeof(struct per_session_data) /* per_session_data_size */
+		                                             },
+	                                                 {
+		                                               NULL, NULL, 0 /* End of list */
+		                                             } };
+
+static context_data contexts[WS_CONTEXTS];
+
+int websocket_create(const char *addr, int port)
+{
+	struct lws_context_creation_info info;
+	memset(&info, 0, sizeof(info));
+	info.port = port;
+	info.iface = addr;
+	info.protocols = protocols;
+	info.gid = -1;
+	info.uid = -1;
+
+	// find free context
+	int first_free = -1;
+	for(int i = 0; i < WS_CONTEXTS; i++)
+	{
+		if(contexts[i].context == NULL)
+		{
+			first_free = i;
+			break;
+		}
+	}
+	if(first_free == -1)
+		return -1;
+
+	context_data *ctx_data = &contexts[first_free];
+	info.user = (void *)ctx_data;
+
+	ctx_data->context = libwebsocket_create_context(&info);
+	if(ctx_data->context == NULL)
+	{
+		return -1;
+	}
+	memset(ctx_data->port_map, 0, sizeof(ctx_data->port_map));
+	ctx_data->recv_buffer.Init();
+	ctx_data->last_used_port = 0;
+	return first_free;
+}
+
+int websocket_destroy(int socket)
+{
+	libwebsocket_context *context = contexts[socket].context;
+	if(context == NULL)
+		return -1;
+	libwebsocket_context_destroy(context);
+	contexts[socket].context = NULL;
+	return 0;
+}
+
+int websocket_recv(int socket, unsigned char *data, size_t maxsize, struct sockaddr_in *sockaddrbuf, size_t fromLen)
+{
+	libwebsocket_context *context = contexts[socket].context;
+	if(context == NULL)
+		return -1;
+	int n = libwebsocket_service(context, 0);
+	if(n < 0)
+		return n;
+	context_data *ctx_data = (context_data *)libwebsocket_context_user(context);
+	websocket_chunk *chunk = (websocket_chunk *)ctx_data->recv_buffer.First();
+	if(chunk == 0)
+		return 0;
+	if(maxsize >= chunk->size - chunk->read)
+	{
+		int len = chunk->size - chunk->read;
+		memcpy(data, &chunk->data[chunk->read], len);
+		memcpy(sockaddrbuf, &chunk->addr, fromLen);
+		ctx_data->recv_buffer.PopFirst();
+		return len;
+	}
+	else
+	{
+		memcpy(data, &chunk->data[chunk->read], maxsize);
+		memcpy(sockaddrbuf, &chunk->addr, fromLen);
+		chunk->read += maxsize;
+		return maxsize;
+	}
+}
+
+int websocket_send(int socket, const unsigned char *data, size_t size, int port)
+{
+	libwebsocket_context *context = contexts[socket].context;
+	if(context == NULL)
+		return -1;
+	context_data *ctx_data = (context_data *)libwebsocket_context_user(context);
+	struct per_session_data *pss = ctx_data->port_map[port];
+	if(pss == NULL)
+		return -1;
+	websocket_chunk *chunk = (websocket_chunk *)pss->send_buffer.Allocate(size + sizeof(websocket_chunk) + LWS_SEND_BUFFER_PRE_PADDING + LWS_SEND_BUFFER_POST_PADDING);
+	if(chunk == NULL)
+		return -1;
+	chunk->size = size;
+	chunk->read = 0;
+	memcpy(&chunk->addr, &pss->addr, sizeof(sockaddr_in));
+	memcpy(&chunk->data[LWS_SEND_BUFFER_PRE_PADDING], data, size);
+	libwebsocket_callback_on_writable(context, pss->wsi);
+	return size;
+}
+
+int websocket_fd_set(int socket, fd_set *set)
+{
+	libwebsocket_context *context = contexts[socket].context;
+	if(context == NULL)
+		return -1;
+	context_data *ctx_data = (context_data *)libwebsocket_context_user(context);
+	int max = 0;
+	for(int i = 0; i < WS_CLIENTS; i++)
+	{
+		per_session_data *pss = ctx_data->port_map[i];
+		if(pss == NULL)
+			continue;
+		int fd = libwebsocket_get_socket_fd(pss->wsi);
+		if(fd > max)
+			max = fd;
+		FD_SET(fd, set);
+	}
+	return max;
+}
+}
+#endif
diff --git a/src/engine/shared/websockets.h b/src/engine/shared/websockets.h
new file mode 100644
index 00000000..95f6fc12
--- /dev/null
+++ b/src/engine/shared/websockets.h
@@ -0,0 +1,16 @@
+#ifndef WEBSOCKETS_H
+#define WEBSOCKETS_H
+
+#if !defined(CONF_FAMILY_UNIX)
+	#error websockets only work on unix, sorry
+#endif
+
+#include <netinet/in.h>
+
+int websocket_create(const char *addr, int port);
+int websocket_destroy(int socket);
+int websocket_recv(int socket, unsigned char *data, size_t maxsize, struct sockaddr_in *sockaddrbuf, size_t fromLen);
+int websocket_send(int socket, const unsigned char *data, size_t size, int port);
+int websocket_fd_set(int socket, fd_set *set);
+
+#endif