From 3f4ef782b40a5fefdc03bd5e5fdd8c33985c2848 Mon Sep 17 00:00:00 2001 From: dmaivel Date: Mon, 27 May 2024 17:01:29 -0400 Subject: [PATCH] Overhaul for stable networking architecture Old UDP based infrastructure removed. The new protocol is split between: TCP: Favored for FIFO buffer transfer UDP: Favored for framebuffer transfer The new network architecture is stable enough to remove the recommendations in the README against using it exclusively. Extras: - Fixed formatting in kernel module for Linux - Made note in README about GLX symlink --- README.md | 8 +- inc/network/net.h | 46 +++- inc/network/packet.h | 87 +++--- kernel/linux/sharedgl.c | 4 +- src/client/glimpl.c | 230 +++++----------- src/network/net.c | 345 +++++++++++++++++++++--- src/server/main.c | 5 + src/server/processor.c | 579 ++++++++++------------------------------ 8 files changed, 615 insertions(+), 689 deletions(-) diff --git a/README.md b/README.md index c281dd9..39191f5 100644 --- a/README.md +++ b/README.md @@ -178,10 +178,8 @@ There are two ways to install the library on windows: ``` # Networking -> [!WARNING]\ -> The network protocol is currently in active early development and is prone to bugs. -Starting from `0.5.0`, SharedGL offers a networking feature that may be used in place of shared memory. No additional drivers are required for the network feature, meaning if you wish to have a driverless experience in your virtual machine, networking is the given alternative. If the networking feature is used exclusively **(NOT RECOMMENDED)**, the kernel drivers do not need be compiled/installed. However, installation of the ICD for either Linux or Windows is still required. +Starting from `0.5.0`, SharedGL offers a networking feature that may be used in place of shared memory. No additional drivers are required for the network feature, meaning if you wish to have a driverless experience in your virtual machine, networking is the given alternative. If the networking feature is used exclusively, the kernel drivers do not need be compiled/installed. However, installation of the ICD for either Linux or Windows is still required. - Start the server using `-n` (and provide a port if the default is not available through `-p PORT`) - Ensure the client libraries are installed - Ensure that the environment variable `SGL_NET_OVER_SHARED=ADDRESS:PORT` exists in the guest (`ADDRESS` being the host's IP address) @@ -189,7 +187,7 @@ Starting from `0.5.0`, SharedGL offers a networking feature that may be used in # Virtual machines > [!NOTE]\ -> If the networking feature is used exclusively **(NOT RECOMMENDED)**, this step can be skipped. +> If the networking feature is used exclusively, this step can be skipped. Before starting the virtual machine, you must pass a shared memory device and start the server before starting the virtual machine. This can be done within libvirt's XML editor or the command line. Before starting the virtual machine, start the server using `-v`, which will start the server and print the necessary configurations: @@ -249,8 +247,8 @@ This list describes the amount of functions left from each standard to implement - No Vsync - Resizing is possible, no proper implementation - Some GLFW applications cant request OpenGL profiles -- Networking is experimental, applications will halt after some time - New GLX FB configs may cause applications using `freeglut` or `glad` to no longer run + - Some applications may require an explicit `libGLX`, so run `ln -s libGL.so.1 libGLX.so.0` in `build` to make a symlink. # Troubleshooting You may encounter weird crashes/faults/errors such as `IOT instruction` or `No provider of glXXX found.`. Although the code base is buggy, these are some tips to try to further attempts to get an application to work: diff --git a/inc/network/net.h b/inc/network/net.h index c8ffa57..c6a4ce0 100644 --- a/inc/network/net.h +++ b/inc/network/net.h @@ -7,15 +7,21 @@ #define NET_DONTWAIT 0x40 /* - * opaque; contents do not need to be seen or accessed - * - * this is for includes sake, we only need to include network - * stuff in the net.c file, instead of scattered around the - * project + * opaque; so we don't include network headers everywhere */ struct net_context; -int net_generate_signature(); +#define NET_SOCKET_SERVER -1 +#define NET_SOCKET_NONE -2 +#define NET_SOCKET_FIRST_FD 2 +typedef int net_socket; + +enum net_poll_reason { + NET_POLL_FAILED = 0, + NET_POLL_INCOMING_CONNECTION = (1 << 0), + NET_POLL_INCOMING_UDP = (1 << 1), + NET_POLL_INCOMING_TCP = (1 << 2) +}; #ifndef _WIN32 char *net_get_ip(); @@ -24,7 +30,31 @@ char *net_get_ip(); char *net_init_server(struct net_context **ctx, int port); char *net_init_client(struct net_context **ctx, char *hostname, int port); -long net_recvfrom(struct net_context *ctx, void *__restrict __buf, size_t __n, int __flags); -long net_sendto(struct net_context *ctx, const void *__buf, size_t __n, int __flags); +/* + * SERVER-specific + * hide from windows, need WSAPoll support first + */ +#ifndef _WIN32 +enum net_poll_reason net_poll(struct net_context *ctx); +net_socket net_accept(struct net_context *ctx); +int net_fd_count(struct net_context *ctx); +bool net_did_event_happen_here(struct net_context *ctx, int fd); +#endif + +// SERVER +void net_close(struct net_context *ctx, int fd); + +// CLIENT +// void net_goodbye(struct net_context *ctx); + +// UDP +long net_recv_udp(struct net_context *ctx, void *__restrict __buf, size_t __n, int __flags); +long net_send_udp(struct net_context *ctx, const void *__buf, size_t __n, int __flags); +long net_recv_udp_timeout(struct net_context *ctx, void *__restrict __buf, size_t __n, int __flags, size_t timeout_ms); + +// TCP +bool net_recv_tcp(struct net_context *ctx, int fd, void *__restrict __buf, size_t __n); +bool net_send_tcp(struct net_context *ctx, int fd, const void *__buf, size_t __n); +bool net_recv_tcp_timeout(struct net_context *ctx, int fd, void *__restrict __buf, size_t __n, size_t timeout_ms); #endif \ No newline at end of file diff --git a/inc/network/packet.h b/inc/network/packet.h index efb1e9a..a7e5a4c 100644 --- a/inc/network/packet.h +++ b/inc/network/packet.h @@ -2,43 +2,15 @@ #define _SGL_PACKET_H_ #include +#include +#include /* * 1024 * 63 */ -#define SGL_PACKET_MAX_BLOCK_SIZE 64512 - -#define SGL_PACKET_MAX_SIZE SGL_PACKET_MAX_BLOCK_SIZE + sizeof(struct sgl_packet_header) - -enum { - /* - * notify server of our existence - * server sends this identical packet back (with the same signature) containing the client id - */ - SGL_PACKET_TYPE_CONNECT, - - /* - * upload fifo buffer to server - * server tells client fifo stuff is done - * - * NOTE: RETVAL IS RETURNED TO THE CLIENT - */ - SGL_PACKET_TYPE_FIFO_UPLOAD, - - /* - * request framebuffer from server - * - slow_but_safe: send small safe packets, min risk - * - fast_but_loss: send big packets, risk loss - */ - SGL_PACKET_TYPE_FRAMEBUFFER_SLOW_BUT_SAFE, - SGL_PACKET_TYPE_FRAMEBUFFER_FAST_BUT_LOSS, - SGL_PACKET_TYPE_FRAMEBUFFER_DONE, - - /* - * request missing data - */ - SGL_PACKET_TYPE_REQUEST_RECOVERY, -}; +#define SGL_FIFO_UPLOAD_COMMAND_BLOCK_COUNT 512 +#define SGL_FIFO_UPLOAD_COMMAND_BLOCK_SIZE (SGL_FIFO_UPLOAD_COMMAND_BLOCK_COUNT * sizeof(uint32_t)) +#define SGL_SWAPBUFFERS_RESULT_SIZE 60000 #ifndef _WIN32 #define PACKED __attribute__((packed)) @@ -49,14 +21,47 @@ enum { #ifdef _WIN32 __pragma( pack(push, 1) ) #endif -struct PACKED sgl_packet_header { - short client_id; - bool is_for_server; - char type; - unsigned int size; - short index; - unsigned short expected_blocks; - int signature; +struct PACKED sgl_packet_connect { + uint32_t client_id; + uint64_t framebuffer_size; + uint64_t fifo_size; + uint32_t gl_major; + uint32_t gl_minor; +}; + +struct PACKED sgl_packet_swapbuffers_request { + uint32_t client_id; + uint32_t width; + uint32_t height; + uint32_t vflip; + uint32_t format; +}; + +struct PACKED sgl_packet_swapbuffers_result { + uint32_t client_id; + uint32_t index; + uint32_t size; + uint8_t result[SGL_SWAPBUFFERS_RESULT_SIZE]; +}; + +struct PACKED sgl_packet_fifo_upload { + uint32_t client_id; + uint32_t expected_chunks; + uint32_t index; + uint32_t count; + uint32_t commands[SGL_FIFO_UPLOAD_COMMAND_BLOCK_COUNT]; +}; + +struct PACKED sgl_packet_retval { + union { + uint32_t retval_split[2]; + uint64_t retval; + }; + uint32_t retval_v[256 / sizeof(uint32_t)]; +}; + +struct PACKED sgl_packet_sync { + uint32_t sync; }; #ifdef _WIN32 __pragma( pack(pop)) diff --git a/kernel/linux/sharedgl.c b/kernel/linux/sharedgl.c index 332b187..396ac07 100644 --- a/kernel/linux/sharedgl.c +++ b/kernel/linux/sharedgl.c @@ -214,8 +214,8 @@ static struct pci_device_id pci_ids[] = { static struct pci_driver pchar_driver = { .name = "sharedgl", .id_table = pci_ids, - .probe = pci_probe, - .remove = pci_remove, + .probe = pci_probe, + .remove = pci_remove, }; static char *pci_char_devnode(const struct device *dev, umode_t *mode) diff --git a/src/client/glimpl.c b/src/client/glimpl.c index 1e623e2..fb39037 100644 --- a/src/client/glimpl.c +++ b/src/client/glimpl.c @@ -130,7 +130,6 @@ static const char glimpl_extensions_list[NUM_EXTENSIONS][64] = { static struct net_context *net_ctx = NULL; static int *fake_register_space = NULL; static int *fake_framebuffer = NULL; -static unsigned char packet_space[SGL_PACKET_MAX_SIZE]; static int glimpl_major = SGL_DEFAULT_MAJOR; static int glimpl_minor = SGL_DEFAULT_MINOR; @@ -172,18 +171,11 @@ static void *pb_ptr_hook(size_t offset) } } -static void filter_net_recvfrom(struct net_context *net, int type, int signature, struct sgl_packet_header *storage) -{ - while (1) { - net_recvfrom(net, packet_space, sizeof(packet_space), 0); - - struct sgl_packet_header *temporary_header = (struct sgl_packet_header*)packet_space; - if (temporary_header->type == type && temporary_header->signature == signature) { - *storage = *temporary_header; - return; - } - } -} +/* + * temporary solution to commit stalling out at `recv` when + * committing the goodbye message + */ +bool expecting_retval = true; void glimpl_commit() { @@ -191,7 +183,7 @@ void glimpl_commit() * processor stops at 0 */ pb_push(0); - +// printf("COMMIT START\n"); if (net_ctx == NULL) { /* * lock @@ -223,93 +215,53 @@ void glimpl_commit() void *ptr = pb_iptr(0); size_t size = pb_size(); - int blocks = size / SGL_PACKET_MAX_BLOCK_SIZE + (size % SGL_PACKET_MAX_BLOCK_SIZE != 0); + int blocks = size / SGL_FIFO_UPLOAD_COMMAND_BLOCK_SIZE + (size % SGL_FIFO_UPLOAD_COMMAND_BLOCK_SIZE != 0); size_t min_size = 0; /* * packet */ - int signature = net_generate_signature(); - struct sgl_packet_header header = { - client_id, - true, - SGL_PACKET_TYPE_FIFO_UPLOAD, - size, - -1, - blocks, - signature - }; - - /* - * initial notifying packet - */ - net_sendto(net_ctx, &header, sizeof(header), 0); - - /* - * send out in chunks - */ - size_t left_over = size; - size_t offset = 0; +retry:; + size_t count = size / 4; for (int i = 0; i < blocks; i++) { - size_t packet_size = left_over > SGL_PACKET_MAX_BLOCK_SIZE ? SGL_PACKET_MAX_BLOCK_SIZE : left_over; - left_over -= SGL_PACKET_MAX_BLOCK_SIZE; - - if (packet_size < SGL_PACKET_MAX_BLOCK_SIZE) - min_size = packet_size; - - header.index = i; - header.size = packet_size; + size_t packet_count = count > SGL_FIFO_UPLOAD_COMMAND_BLOCK_COUNT ? SGL_FIFO_UPLOAD_COMMAND_BLOCK_COUNT : count; - memcpy(packet_space, &header, sizeof(header)); - memcpy((char*)packet_space + sizeof(header), (char*)ptr + offset, packet_size); + struct sgl_packet_fifo_upload packet = { + /* client_id = */ client_id, + /* expected_chunks = */ blocks, + /* index = */ i, + /* count = */ packet_count, + /* commands = */ { 0 } + }; - net_sendto(net_ctx, packet_space, sizeof(struct sgl_packet_header) + packet_size, 0); + memcpy(packet.commands, (char*)ptr + (i * SGL_FIFO_UPLOAD_COMMAND_BLOCK_SIZE), packet_count * sizeof(uint32_t)); + net_send_tcp(net_ctx, NET_SOCKET_SERVER, &packet, sizeof(packet)); - offset += packet_size; + count -= SGL_FIFO_UPLOAD_COMMAND_BLOCK_COUNT; } /* - * wait for either success (in turn recieve RETVAL) or recovery messages + * get retval registers */ - bool done = false; - while (!done) { - net_recvfrom(net_ctx, packet_space, SGL_PACKET_MAX_SIZE, 0); - struct sgl_packet_header *temporary_header = (struct sgl_packet_header*)packet_space; - - /* - * check if packet is for us - */ - if (temporary_header->client_id != client_id || temporary_header->signature != signature) - continue; + struct sgl_packet_retval packet; + if (expecting_retval) + if (!net_recv_tcp_timeout(net_ctx, NET_SOCKET_SERVER, &packet, sizeof(packet), 500)) + goto retry; - /* - * either finished or recover - */ - size_t size = 0; - switch (temporary_header->type) { - case SGL_PACKET_TYPE_FIFO_UPLOAD: - done = true; - break; - case SGL_PACKET_TYPE_REQUEST_RECOVERY: - temporary_header->type = SGL_PACKET_TYPE_FIFO_UPLOAD; - size = temporary_header->index + 1 == blocks ? min_size : SGL_PACKET_MAX_BLOCK_SIZE; - memcpy((char*)packet_space + sizeof(struct sgl_packet_header), (char*)ptr + (temporary_header->index * SGL_PACKET_MAX_BLOCK_SIZE), size); - net_sendto(net_ctx, packet_space, sizeof(struct sgl_packet_header) + size, 0); - break; - } - } - /* * write response into fake register space */ - memcpy(fake_register_space, packet_space + sizeof(struct sgl_packet_header), 256 + 8); + memcpy(fake_register_space, &packet, 256 + 8); pb_reset(); } + // printf("COMMIT END\n"); } void glimpl_goodbye() { + expecting_retval = false; + /* * probably not a good idea to commit * all commands before sending our @@ -324,6 +276,9 @@ void glimpl_goodbye() pb_push(0); glimpl_commit(); + // if (net_ctx != NULL) + // net_goodbye(net_ctx); + #ifdef _WIN32 pb_unset(); #endif @@ -349,62 +304,38 @@ void glimpl_swap_buffers(int width, int height, int vflip, int format) } else { glimpl_commit(); - // return; - int signature = net_generate_signature(); - - struct sgl_packet_header header = { - client_id, - true, - SGL_PACKET_TYPE_FRAMEBUFFER_FAST_BUT_LOSS, - sizeof(int) * 4, - 0, - 0, - signature + struct sgl_packet_swapbuffers_request packet = { + /* client_id = */ client_id, + /* width = */ width, + /* height = */ height, + /* vflip = */ vflip, + /* format = */ format }; - memcpy(packet_space, &header, sizeof(header)); - memcpy(packet_space + sizeof(header) + (sizeof(int) * 0), &width, sizeof(int)); - memcpy(packet_space + sizeof(header) + (sizeof(int) * 1), &height, sizeof(int)); - memcpy(packet_space + sizeof(header) + (sizeof(int) * 2), &vflip, sizeof(int)); - memcpy(packet_space + sizeof(header) + (sizeof(int) * 3), &format, sizeof(int)); - - net_sendto(net_ctx, packet_space, sizeof(header) + (sizeof(int) * 4), 0); + net_send_udp(net_ctx, &packet, sizeof(packet), 0); - bool done = false; - size_t offset = 0; - int last_index = -1; - size_t block_size = 0; - //printf("start\n"); - - /* - * to-do: redesign this part because the misses check is causing significant reduced performance - * this part also causes the client to lock up, so redesign needed quick - */ - int misses = 0; - while (!done && misses < 10000) { - net_recvfrom(net_ctx, packet_space, sizeof(packet_space), NET_DONTWAIT); - struct sgl_packet_header *temporary_header = (struct sgl_packet_header*)packet_space; - - /* - * check if packet is for us - */ - if (temporary_header->client_id != client_id || temporary_header->signature != signature) + int expected = (width * height * 4) / SGL_SWAPBUFFERS_RESULT_SIZE + ((width * height * 4) % SGL_SWAPBUFFERS_RESULT_SIZE != 0); + + // wait for sync + // TO-DO: MAYBE TIMEOUT BECAUSE UDP IS NOT GUARANTEED + // WARNING: sync here appears to disturb the FIFO upload + struct sgl_packet_sync sync; + net_recv_tcp(net_ctx, NET_SOCKET_SERVER, &sync, sizeof(sync)); + + for (int i = 0; i < expected * 4; i++) { + struct sgl_packet_swapbuffers_result result; + // while (net_recv_udp(net_ctx, &result, sizeof(result), 0) == -1); + int recieved = net_recv_udp(net_ctx, &result, sizeof(result), 0); + + // global, so throwout any results that aren't ours (possible vuln for other applications? idk) + if (result.client_id != client_id || recieved < 0) { + // i--; continue; - - switch (temporary_header->type) { - case SGL_PACKET_TYPE_FRAMEBUFFER_DONE: - done = true; - break; - case SGL_PACKET_TYPE_FRAMEBUFFER_FAST_BUT_LOSS: - /* assume increments of SGL_PACKET_MAX_BLOCK_SIZE if sent in chunks */ - memcpy((char*)fake_framebuffer + (temporary_header->index * SGL_PACKET_MAX_BLOCK_SIZE), packet_space + sizeof(struct sgl_packet_header), temporary_header->size); - break; } - misses++; + memcpy((char*)fake_framebuffer + (result.index * SGL_SWAPBUFFERS_RESULT_SIZE), result.result, result.size); } - //printf("end\n"); } } @@ -448,39 +379,20 @@ void glimpl_init() char *res = net_init_client(&net_ctx, network, atoi(&network[strlen(network) + 1])); if (res != NULL) { - fprintf(stderr, "glimpl_init: could not initialize client (%s)", res); + fprintf(stderr, "glimpl_init: could not initialize client (%s)\n", res); exit(1); } - int signature = net_generate_signature(); + struct sgl_packet_connect packet = { 0 }; + net_recv_tcp(net_ctx, NET_SOCKET_SERVER, &packet, sizeof(packet)); - struct sgl_packet_header header = { - 0, - true, - SGL_PACKET_TYPE_CONNECT, - 0, - 0, - 0, - signature - }; - - size_t framebuffer_size, fifo_size; - - /* - * warning (to-do, fix-me, todo, fixme): sizeof(...) may change when switching to and from 32-bit - * architectures - */ - net_sendto(net_ctx, &header, sizeof(header), 0); - filter_net_recvfrom(net_ctx, SGL_PACKET_TYPE_CONNECT, header.signature, &header); - framebuffer_size = *(size_t*)&packet_space[sizeof(struct sgl_packet_header)]; - fifo_size = *(size_t*)&packet_space[sizeof(struct sgl_packet_header) + sizeof(framebuffer_size)]; - glimpl_major = *(int*)&packet_space[sizeof(struct sgl_packet_header) + sizeof(framebuffer_size) + sizeof(fifo_size)]; - glimpl_minor = *(int*)&packet_space[sizeof(struct sgl_packet_header) + sizeof(framebuffer_size) + sizeof(fifo_size) + sizeof(glimpl_major)]; + glimpl_major = packet.gl_major; + glimpl_minor = packet.gl_minor; - client_id = header.client_id; + client_id = packet.client_id; fake_register_space = malloc(SGL_OFFSET_COMMAND_START); - fake_framebuffer = malloc(framebuffer_size); + fake_framebuffer = malloc(packet.framebuffer_size); struct pb_net_hooks hooks = { pb_read_hook, @@ -492,7 +404,7 @@ void glimpl_init() NULL }; - pb_set_net(hooks, fifo_size); + pb_set_net(hooks, packet.fifo_size); } char *gl_version_override = getenv("GL_VERSION_OVERRIDE"); @@ -504,23 +416,23 @@ void glimpl_init() lockg = pb_ptr(SGL_OFFSET_REGISTER_LOCK); /* - * claim client id and increment the register for the - * next claimee to claim - */ + * claim client id and increment the register for the + * next claimee to claim + */ spin_lock(lockg); client_id = pb_read(SGL_OFFSET_REGISTER_CLAIM_ID); pb_write(SGL_OFFSET_REGISTER_READY_HINT, client_id); pb_write(SGL_OFFSET_REGISTER_CLAIM_ID, client_id + 1); /* - * notify the server we would like to connect - */ + * notify the server we would like to connect + */ pb_write(SGL_OFFSET_REGISTER_CONNECT, client_id); spin_unlock(lockg); /* - * commit - */ + * commit + */ pb_push(SGL_CMD_CREATE_CONTEXT); glimpl_commit(); } diff --git a/src/network/net.c b/src/network/net.c index 79e1f09..a30d4f6 100644 --- a/src/network/net.c +++ b/src/network/net.c @@ -5,14 +5,20 @@ #include #include #include +#include #include +#include + #ifndef _WIN32 #include #include #include +#include #include #include +#include +#include #else #include #include @@ -22,37 +28,40 @@ static WSADATA wsaData; #endif /* - * context definition; users plz dont use + * context definition */ struct net_context { bool is_server; - int socket; + int udp_socket; + int tcp_socket; struct sockaddr_in client; struct sockaddr_in server; + +#ifndef _WIN32 + struct pollfd fds[SOMAXCONN + 2]; + bool in_use[SOMAXCONN + 2]; + int n_fds; +#endif }; #define ERR_FAILED_TO_CREATE_SOCKET 0 #define ERR_FAILED_TO_BIND 1 #define ERR_WSA_STARTUP_FAILED 2 +#define ERR_FAILED_TO_CONNECT 3 +#define ERR_FAILED_TO_LISTEN 4 + +#define NET_PROTOCOL_TO_SOCKET(p) (p == NET_UDP ? SOCK_DGRAM : SOCK_STREAM) char *error_messages[] = { "failed to create socket", "failed to bind to port", - "wsa startup failed" + "wsa startup failed", + "failed to connect", + "failed to listen" }; -int net_generate_signature() -{ - static int seed = -1; - if (seed == -1) - seed = time(NULL); - - seed = (214013*seed+2531011); - return seed; -} - #ifndef _WIN32 char *net_get_ip() { @@ -70,6 +79,46 @@ char *net_get_ip() } #endif +static void set_nonblocking(int socket) +{ +#ifdef _WIN32 + u_long mode = 1; + ioctlsocket(socket, FIONBIO, &mode); +#else + int flags = fcntl(socket, F_GETFL, 0); + fcntl(socket, F_SETFL, flags | O_NONBLOCK); +#endif +} + +static bool set_no_delay(int socket) +{ + int flag = 1; + // set TCP_NODELAY to disable Nagle's algorithm + int ret = setsockopt(socket, IPPROTO_TCP, TCP_NODELAY, (char *)&flag, sizeof(int)); + return ret == 0; +} + +static bool set_reuse_addr(int socket) +{ + int flag = 1; + int ret = setsockopt(socket, SOL_SOCKET, SO_REUSEADDR, (char *)&flag, sizeof(int)); + return ret == 0; +} + +static char *net_create_sockets(struct net_context *nctx, int port) +{ + nctx->udp_socket = socket(AF_INET, SOCK_DGRAM, 0); + nctx->tcp_socket = socket(AF_INET, SOCK_STREAM, 0); + if (nctx->udp_socket < 0 || nctx->tcp_socket < 0) + return error_messages[ERR_FAILED_TO_CREATE_SOCKET]; + + memset(&nctx->server, 0, sizeof(nctx->server)); + nctx->server.sin_family = AF_INET; + nctx->server.sin_port = htons(port); + + return NULL; +} + char *net_init_server(struct net_context **ctx, int port) { *ctx = malloc(sizeof(struct net_context)); @@ -86,24 +135,40 @@ char *net_init_server(struct net_context **ctx, int port) return error_messages[ERR_WSA_STARTUP_FAILED]; #endif - nctx->socket = socket(AF_INET, SOCK_DGRAM, 0); - if (nctx->socket < 0) - return error_messages[ERR_FAILED_TO_CREATE_SOCKET]; + char *status = net_create_sockets(nctx, port); + if (status != NULL) + return NULL; - nctx->server.sin_family = AF_INET; - nctx->server.sin_port = port; nctx->server.sin_addr.s_addr = INADDR_ANY; -#ifndef _WIN32 - struct timeval timeout; - timeout.tv_sec = 0; - timeout.tv_usec = 100; - setsockopt(nctx->socket, SOL_SOCKET, SO_RCVTIMEO, &timeout, sizeof(timeout)); -#endif + set_nonblocking(nctx->udp_socket); + set_nonblocking(nctx->tcp_socket); + set_no_delay(nctx->tcp_socket); + set_no_delay(nctx->udp_socket); + set_reuse_addr(nctx->tcp_socket); + set_reuse_addr(nctx->udp_socket); + + if (bind(nctx->udp_socket, (struct sockaddr *)&nctx->server, sizeof(nctx->server)) < 0) + return error_messages[ERR_FAILED_TO_BIND]; - if (bind(nctx->socket, (struct sockaddr *)&nctx->server, sizeof(nctx->server)) < 0) + if (bind(nctx->tcp_socket, (struct sockaddr *)&nctx->server, sizeof(nctx->server)) < 0) return error_messages[ERR_FAILED_TO_BIND]; + if (listen(nctx->tcp_socket, SOMAXCONN) < 0) + return error_messages[ERR_FAILED_TO_LISTEN]; + + /* + * will cause errors on windows build + */ +#ifndef _WIN32 + memset(nctx->fds, 0, sizeof(nctx->fds)); + nctx->fds[0].fd = nctx->tcp_socket; + nctx->fds[0].events = POLLIN; + nctx->fds[1].fd = nctx->udp_socket; + nctx->fds[1].events = POLLIN; + nctx->n_fds = 2; +#endif + /* * return null because no error message is to be returned */ @@ -123,25 +188,108 @@ char *net_init_client(struct net_context **ctx, char *hostname, int port) return error_messages[ERR_WSA_STARTUP_FAILED]; #endif - nctx->socket = socket(AF_INET, SOCK_DGRAM, 0); - if (nctx->socket < 0) - return error_messages[ERR_FAILED_TO_CREATE_SOCKET]; + char *status = net_create_sockets(nctx, port); + if (status != NULL) + return NULL; - nctx->server.sin_family = AF_INET; - nctx->server.sin_port = port; nctx->server.sin_addr.s_addr = inet_addr(hostname); + set_nonblocking(nctx->udp_socket); + set_nonblocking(nctx->tcp_socket); + set_no_delay(nctx->tcp_socket); + set_no_delay(nctx->udp_socket); + + if (connect(nctx->tcp_socket, (struct sockaddr *) &nctx->server, sizeof(nctx->server)) < 0) + if (errno != EINPROGRESS) + return error_messages[ERR_FAILED_TO_CONNECT]; + /* * return null because no error message is to be returned */ return NULL; } -long net_recvfrom(struct net_context *ctx, void *__restrict __buf, size_t __n, int __flags) +#ifndef _WIN32 +/* + * fds[0] = tcp + * fds[1] = udp + * fds[2...] = clients + */ +enum net_poll_reason net_poll(struct net_context *ctx) +{ + enum net_poll_reason reason = NET_POLL_FAILED; + if (poll(ctx->fds, ctx->n_fds, -1) < 0) + return reason; + + if (ctx->fds[0].revents & POLLIN) + reason |= NET_POLL_INCOMING_CONNECTION; + if (ctx->fds[1].revents & POLLIN) + reason |= NET_POLL_INCOMING_UDP; + for (int i = 2; i < ctx->n_fds; i++) + if (ctx->fds[i].revents & POLLIN) { + reason |= NET_POLL_INCOMING_TCP; + break; + } + + return reason; +} + +net_socket net_accept(struct net_context *ctx) +{ + int socket = accept(ctx->tcp_socket, (struct sockaddr *)&ctx->client, &(socklen_t){ sizeof(ctx->client) }); + if (socket < 0) + return NET_SOCKET_NONE; + + set_nonblocking(socket); + set_no_delay(socket); + + ctx->fds[ctx->n_fds].fd = socket; + ctx->fds[ctx->n_fds].events = POLLIN; + ctx->in_use[ctx->n_fds] = true; + + return ctx->n_fds++; +} + +int net_fd_count(struct net_context *ctx) +{ + return ctx->n_fds; +} + +bool net_did_event_happen_here(struct net_context *ctx, int fd) +{ + return ctx->in_use[fd] && ctx->fds[fd].revents & POLLIN; +} +#endif + +void net_close(struct net_context *ctx, int fd) +{ + ctx->in_use[fd] = false; + ctx->fds[fd].revents = 0; + ctx->fds[fd].fd = -1; +#ifdef _WIN32 + closesocket(ctx->fds[fd].fd); +#else + close(ctx->fds[fd].fd); +#endif +} + +// void net_goodbye(struct net_context *ctx) +// { +// #ifdef _WIN32 +// closesocket(ctx->tcp_socket); +// closesocket(ctx->udp_socket); +// #else +// close(ctx->tcp_socket); +// close(ctx->udp_socket); +// #endif +// } + +long net_recv_udp(struct net_context *ctx, void *__restrict __buf, size_t __n, int __flags) { - struct sgl_packet_header *hdr = __buf; - - int res = recvfrom(ctx->socket, __buf, __n, __flags, (struct sockaddr *) &ctx->client, &(socklen_t){ sizeof(ctx->client) }); + // struct sgl_packet_header *hdr = __buf; + + int res = recvfrom(ctx->udp_socket, __buf, __n, __flags, (struct sockaddr *) &ctx->client, &(socklen_t){ sizeof(ctx->client) }); + // if (res != -1) // printf("recv { .client_id = %d | is_for_server = %hhu | type = %hu | size = %-6hu | index = %-6hu | expected_blocks = %-6hu | signature = %08X }, %ld)\n", // hdr->client_id, hdr->is_for_server, hdr->type, hdr->size, hdr->index, hdr->expected_blocks, hdr->signature, __n); @@ -150,15 +298,136 @@ long net_recvfrom(struct net_context *ctx, void *__restrict __buf, size_t __n, i return res; } -long net_sendto(struct net_context *ctx, const void *__buf, size_t __n, int __flags) +long net_send_udp(struct net_context *ctx, const void *__buf, size_t __n, int __flags) { - const struct sgl_packet_header *hdr = __buf; + // const struct sgl_packet_header *hdr = __buf; // printf("send { .client_id = %d | is_for_server = %hhu | type = %hu | size = %-6hu | index = %-6hu | expected_blocks = %-6hu | signature = %08X }, %ld)\n", // hdr->client_id, hdr->is_for_server, hdr->type, hdr->size, hdr->index, hdr->expected_blocks, hdr->signature, __n); // fflush(stdout); if (ctx->is_server) - return sendto(ctx->socket, __buf, __n, __flags, (struct sockaddr *) &ctx->client, sizeof(ctx->client)); + return sendto(ctx->udp_socket, __buf, __n, __flags, (struct sockaddr *) &ctx->client, sizeof(ctx->client)); else - return sendto(ctx->socket, __buf, __n, __flags, (struct sockaddr *) &ctx->server, sizeof(ctx->server)); + return sendto(ctx->udp_socket, __buf, __n, __flags, (struct sockaddr *) &ctx->server, sizeof(ctx->server)); +} + +static int64_t time_ms(void) +{ + struct timespec ts; + clock_gettime(CLOCK_MONOTONIC, &ts); + return (int64_t)ts.tv_sec * 1000 + (int64_t)ts.tv_nsec / 1000000; +} + +long net_recv_udp_timeout(struct net_context *ctx, void *__restrict __buf, size_t __n, int __flags, size_t timeout_ms) +{ + int64_t initial = time_ms(); + long res = -1; + while (res < 0) { + if (time_ms() - initial > timeout_ms) { + // fprintf(stderr, "net_recv_udp_timeout: timed out after %lds\n", timeout_ms); + return -1; + } + + res = recvfrom(ctx->udp_socket, __buf, __n, __flags, (struct sockaddr *) &ctx->client, &(socklen_t){ sizeof(ctx->client) }); + } + + return res; +} + +// struct { +// size_t size; +// char name[32]; +// } table[5] = { +// { sizeof(struct sgl_packet_connect), "sgl_packet_connect" }, +// { sizeof(struct sgl_packet_swapbuffers_request), "sgl_packet_swapbuffers_request" }, +// { sizeof(struct sgl_packet_swapbuffers_result), "sgl_packet_swapbuffers_result" }, +// { sizeof(struct sgl_packet_fifo_upload), "sgl_packet_fifo_upload" }, +// { sizeof(struct sgl_packet_retval), "sgl_packet_retval" } +// }; + +bool net_recv_tcp(struct net_context *ctx, int fd, void *__restrict __buf, size_t __n) +{ + int socket = fd != NET_SOCKET_SERVER ? ctx->fds[fd].fd : ctx->tcp_socket; + + // for (int i = 0; i < 5; i++) + // if (__n == table[i].size) + // printf("net_recv_tcp: %s\n", table[i].name); + // fflush(stdout); + + size_t bytes_recv = 0; + while (bytes_recv < __n) { + ssize_t n = recv(socket, (char *)__buf + bytes_recv, __n - bytes_recv, MSG_NOSIGNAL); + if (n == -1 && errno != EAGAIN && errno != EWOULDBLOCK) + return false; + else if (n == -1) + continue; + + // if (n <= 0) + // return false; + + bytes_recv += n; + } + + return true; +} + +bool net_send_tcp(struct net_context *ctx, int fd, const void *__buf, size_t __n) +{ + int socket = fd != NET_SOCKET_SERVER ? ctx->fds[fd].fd : ctx->tcp_socket; + + // for (int i = 0; i < 5; i++) + // if (__n == table[i].size) + // printf("net_send_tcp: %s\n", table[i].name); + // fflush(stdout); + + size_t bytes_sent = 0; + while (bytes_sent < __n) { + ssize_t n = send(socket, (const char *)__buf + bytes_sent, __n - bytes_sent, MSG_NOSIGNAL); + if (n == -1 && errno != EAGAIN && errno != EWOULDBLOCK) { + // printf("net_send_tcp: socket error, [socket = %d | errno = %d]\n", socket, errno); + return false; + } + else if (n == -1) + continue; + + // if (n < 0) + // return false; + + bytes_sent += n; + } + + return true; +} + +bool net_recv_tcp_timeout(struct net_context *ctx, int fd, void *__restrict __buf, size_t __n, size_t timeout_ms) +{ + int socket = fd != NET_SOCKET_SERVER ? ctx->fds[fd].fd : ctx->tcp_socket; + size_t initial = time_ms(); + + // for (int i = 0; i < 5; i++) + // if (__n == table[i].size) + // printf("net_recv_tcp_timeout: %s\n", table[i].name); + // fflush(stdout); + + size_t bytes_recv = 0; + while (bytes_recv < __n) { + ssize_t n = recv(socket, (char *)__buf + bytes_recv, __n - bytes_recv, 0); + if (n == -1 && errno != EAGAIN && errno != EWOULDBLOCK) + return false; + else { + if (time_ms() - initial > timeout_ms) { + fprintf(stderr, "net_recv_tcp_timeout: timed out after %lds\n", timeout_ms); + return false; + } + if (n == -1) + continue; + } + + // if (n <= 0) + // return false; + + bytes_recv += n; + } + + return true; } \ No newline at end of file diff --git a/src/server/main.c b/src/server/main.c index e356c48..fc8b05d 100644 --- a/src/server/main.c +++ b/src/server/main.c @@ -70,6 +70,10 @@ static void term_handler(int sig) COLOR_INFO, sgl_cmd2str(icmd), COLOR_RESET, COLOR_NUMB, icmd, COLOR_RESET); break; + case SIGPIPE: + printf("%sfatal%s: socket unexpectedly closed", + COLOR_ERRO, COLOR_RESET); + break; } puts(""); @@ -145,6 +149,7 @@ int main(int argc, char **argv) signal(SIGINT, term_handler); signal(SIGSEGV, term_handler); + signal(SIGPIPE, SIG_IGN); printf("%sinfo%s: press %sCTRL+C%s to terminate server\n\n", COLOR_INFO, COLOR_RESET, COLOR_NUMB, COLOR_RESET); printf("%sinfo%s: reporting gl version %s%d%s.%s%d%s\n\n", COLOR_INFO, COLOR_RESET, COLOR_NUMB, major, COLOR_RESET, COLOR_NUMB, minor, COLOR_RESET); diff --git a/src/server/processor.c b/src/server/processor.c index 798ca82..69ee751 100644 --- a/src/server/processor.c +++ b/src/server/processor.c @@ -1,3 +1,4 @@ +#include "network/packet.h" #define SHAREDGL_HOST #include @@ -6,17 +7,10 @@ #include #include -/* - * has nothing to do with the client; we just need the spinlock - * for the network based operations - */ -#include - #include #include #include -#include /* * this is for detecting packet losses @@ -33,42 +27,12 @@ struct sgl_connection { struct sgl_connection *next; int id; + int fd; struct sgl_host_context *ctx; }; static struct sgl_connection *connections = NULL; -struct sgl_data_block { - struct sgl_data_block *next; - - int index; - size_t size; - void *data; -}; - -struct sgl_incoming_server_msg { - struct sgl_incoming_server_msg *next; - - struct sgl_packet_header header; - - struct sgl_data_block *data_blocks; - short recieved_blocks; - size_t recieved_size; - - /* - * restream data if lost - */ - int alive_for; - - void *processed_data; - bool is_ready_to_be_processed; -}; - -static unsigned char global_packet_space[SGL_PACKET_MAX_SIZE]; - -struct sgl_incoming_server_msg *server_msg_queue; -int msg_queue_lock = 0; - static bool match_connection(void *elem, void *data) { struct sgl_connection *con = elem; @@ -85,47 +49,12 @@ static bool match_equal(void *elem, void *data) return elem == data; } -static void connection_add(int id) +static void connection_add(int id, int fd) { struct sgl_connection *con = dynarr_alloc((void**)&connections, 0, sizeof(struct sgl_connection)); con->id = id; con->ctx = sgl_context_create(); -} - -/* - * bool reset: - * - true: internally reset to the start of the connections list - * only used when a client disconnects to ensure we dont - * attempt to access a null pointer - * - false: don't do anything fancy internally, just return either - * the next client id, the only client, or loop back and - * return the first client id - * - * this function returns one of two possible values/ranges: - * = 0: no current connections exist - * > 0: a valid connection id - */ -static int connection_get(bool reset) -{ - static struct sgl_connection *cur = NULL; - if (!connections) - return 0; - - if (reset) { - cur = NULL; - return 0; - } - - if (!cur) { - cur = connections; - return cur->id; - } - - cur = cur->next; - if (!cur) - cur = connections; - - return cur->id; + con->fd = fd; } static void connection_current(int id) @@ -137,10 +66,27 @@ static void connection_current(int id) } } -static void connection_rem(int id) +static net_socket get_fd_from_id(int id) +{ + for (struct sgl_connection *con = connections; con; con = con->next) + if (con->id == id) + return con->fd; + return NET_SOCKET_NONE; +} + +static int get_id_from_fd(int fd) { + for (struct sgl_connection *con = connections; con; con = con->next) + if (con->fd == fd) + return con->id; + return 0; +} + +static void connection_rem(int id, struct net_context *net_ctx) +{ + if (net_ctx != NULL) + net_close(net_ctx, get_fd_from_id(id)); dynarr_free_element((void**)&connections, 0, match_connection, (void*)((long)id)); - connection_get(true); } static bool wait_for_commit(void *p) @@ -148,222 +94,26 @@ static bool wait_for_commit(void *p) return *(int*)(p + SGL_OFFSET_REGISTER_COMMIT) == 1; } +int scramble_arr[1000]; + /* - * to-do: move the proceeding functions into `net_processor.c` or something - * - * can't do this right now because `sgl_cmd_processor_start` needs - * to access the dynamic array to see what streamed commands it can - * finally process + * used for generating an out-of-order sequence of frames to be uploaded + * for the UDP protocol. Otherwise, mostly the upper half of the window + * recieves all the updates while the lower half of the window takes some + * time to update */ -void *sgl_cmd_net_recv_processor_thread(void *net_context) +static void scramble(int *arr, int n) { - struct net_context *net = net_context; - struct sgl_packet_header current_header = { 0 }; - - void *packet_space = malloc(SGL_PACKET_MAX_SIZE); - - while (1) { - /* - * extract header from packet - */ - long res = net_recvfrom(net, packet_space, SGL_PACKET_MAX_SIZE, 0); - memcpy(¤t_header, packet_space, sizeof(struct sgl_packet_header)); - - /* - * lock the queue as we will be reading and writing to it - */ - spin_lock(&msg_queue_lock); - - /* - * if no data, try recovering - */ - if (res == -1) - goto recovery_operations; - - /* - * update alive for tick count - */ - for (struct sgl_incoming_server_msg *msg = server_msg_queue; msg; msg = msg->next) - msg->alive_for++; - - /* - * get matching block if this packet is continuing another block - * - * requirements for a match: - * - signature is the same - * - client id is the same - */ - struct sgl_incoming_server_msg *matching_msg = NULL; - for (struct sgl_incoming_server_msg *msg = server_msg_queue; msg; msg = msg->next) { - if (msg->header.signature == current_header.signature && msg->header.client_id == current_header.client_id) { - matching_msg = msg; - break; - } - } - - /* - * if this isn't a continuing/continous block, we dont have to do much - */ - if (matching_msg == NULL && current_header.expected_blocks == 0) { - struct sgl_incoming_server_msg *msg = dynarr_alloc((void**)&server_msg_queue, 0, sizeof(struct sgl_incoming_server_msg)); - - /* - * copy the header, indicate that the data does not need to be processed - */ - msg->header = current_header; - msg->is_ready_to_be_processed = true; - msg->recieved_size = msg->header.size; - msg->data_blocks = NULL; - - /* - * if there is data to be processed, plz copy - */ - if (msg->header.size) { - msg->processed_data = malloc(msg->header.size); - memcpy(msg->processed_data, packet_space + sizeof(struct sgl_packet_header), msg->header.size); - } - - /* - * to-do: get rid of this goto - */ - goto recovery_operations; - } - - /* - * otherwise, we must either start the root block - * or continue processing into the root block - */ - - /* - * an index of -1 indicates that the packet was just a header - * - * note: there is no sanity check, but `matching_msg` should be NULL - * for this first if statement - */ - if (current_header.index == -1) { - struct sgl_incoming_server_msg *msg = dynarr_alloc((void**)&server_msg_queue, 0, sizeof(struct sgl_incoming_server_msg)); - - /* - * copy the header, indicate that the data does not need to be processed - * at this time as we await incoming data blocks - */ - msg->header = current_header; - msg->is_ready_to_be_processed = false; - msg->recieved_size = 0; - msg->data_blocks = NULL; - msg->recieved_blocks = 0; - } - /* - * an index >= 0 indicates an actual block of data - */ - else { - matching_msg->recieved_blocks++; - matching_msg->recieved_size += current_header.size; - - struct sgl_data_block *data_block = dynarr_alloc((void**)&matching_msg->data_blocks, 0, sizeof(struct sgl_data_block)); - data_block->index = current_header.index; - data_block->size = current_header.size; - - data_block->data = malloc(data_block->size); - memcpy(data_block->data, packet_space + sizeof(struct sgl_packet_header), data_block->size); - - /* - * check if this stream of data is ready - */ - if (matching_msg->recieved_blocks == matching_msg->header.expected_blocks) { - matching_msg->is_ready_to_be_processed = true; - - /* - * "flatten" the data into one continous buffer - */ - matching_msg->processed_data = malloc(matching_msg->header.size); - size_t offset = 0; - for (int i = 0; i < matching_msg->header.expected_blocks; i++) { - /* - * search for index "i" - * write data block into buffer - */ - struct sgl_data_block *data_block = NULL; - for (struct sgl_data_block *block = matching_msg->data_blocks; block; block = block->next) { - if (block->index == i) { - memcpy( - matching_msg->processed_data + offset, - block->data, - block->size - ); - - free(block->data); - - offset += block->size; - break; - } - } - } - - /* - * free all the linked data - */ - dynarr_free((void**)&matching_msg->data_blocks, 0); - } - } + for (int i = 0; i < n; i++) + arr[i] = i; -recovery_operations: - /* - * broadcast recovery messages if needed - */ - for (struct sgl_incoming_server_msg *msg = server_msg_queue; msg; msg = msg->next) { - if (msg->alive_for > msg->recieved_blocks + RECOVER_AFTER_N_MISSES && !msg->is_ready_to_be_processed) { - /* - * determine which blocks we already recieved to figure out which ones - * we need to request for - */ - bool *do_we_have_this_packet = calloc(msg->header.expected_blocks, sizeof(bool)); - for (struct sgl_data_block *block = msg->data_blocks; block; block = block->next) - do_we_have_this_packet[block->index] = true; - - /* - * request header - */ - struct sgl_packet_header send_header = { - .client_id = msg->header.client_id, - .is_for_server = false, - .type = SGL_PACKET_TYPE_REQUEST_RECOVERY, - .size = msg->header.size, - .index = -1, /* determine later */ - .expected_blocks = msg->header.expected_blocks, - .signature = msg->header.signature - }; + for (int i = n - 1; i > 0; i--) { + int j = rand() % (i + 1); - /* - * loop through and send requests for missing packets - */ - int total_missing = 0; - for (int i = 0; i < msg->header.expected_blocks; i++) { - if (do_we_have_this_packet[i]) - continue; - - send_header.index = i; - net_sendto(net, &send_header, sizeof(struct sgl_packet_header), 0); - total_missing++; - } - - /* - * soft reset alive_for so we don't end up spamming the clients - */ - msg->alive_for -= total_missing; - } - } - - /* - * remember to unlock - */ - spin_unlock(&msg_queue_lock); + int temp = arr[i]; + arr[i] = arr[j]; + arr[j] = temp; } - - /* - * we're not reaching this point - */ - return NULL; } void sgl_cmd_processor_start(struct sgl_cmd_processor_args args) @@ -385,7 +135,6 @@ void sgl_cmd_processor_start(struct sgl_cmd_processor_args args) int cmd; struct net_context *net_ctx = NULL; - struct sgl_packet_header fifo_request_header = { 0 }; if ((signed)fifo_size < 0) { printf("%sfatal%s: framebuffer too big! try increasing memory\n", COLOR_ERRO, COLOR_RESET); @@ -417,7 +166,6 @@ void sgl_cmd_processor_start(struct sgl_cmd_processor_args args) if (args.internal_cmd_ptr) *args.internal_cmd_ptr = &cmd; - pthread_t net_thread; if (args.network_over_shared) { char *res = net_init_server(&net_ctx, args.port); if (res != NULL) { @@ -425,14 +173,14 @@ void sgl_cmd_processor_start(struct sgl_cmd_processor_args args) return; } - pthread_create(&net_thread, NULL, sgl_cmd_net_recv_processor_thread, net_ctx); - printf("%sinfo%s: running server on %s%s%s:%s%d%s\n\n", COLOR_INFO, COLOR_RESET, COLOR_NUMB, net_get_ip(), COLOR_RESET, COLOR_NUMB, args.port, COLOR_RESET); } + bool network_expecting_retval = true; + while (1) { int client_id = 0; int timeout = 0; @@ -458,7 +206,7 @@ void sgl_cmd_processor_start(struct sgl_cmd_processor_args args) /* * add connection to the dynamic array */ - connection_add(creg); + connection_add(creg, 0); printf("%sinfo%s: client %s%d%s connected\n", COLOR_INFO, COLOR_RESET, COLOR_NUMB, creg, COLOR_RESET); @@ -485,157 +233,120 @@ void sgl_cmd_processor_start(struct sgl_cmd_processor_args args) else { bool ready_to_render = false; while (!ready_to_render) { - /* - * we need mutex to access the queue because we're trying to read - * the queue as the thread is also reading/writing to it - */ - spin_lock(&msg_queue_lock); + enum net_poll_reason reason = net_poll(net_ctx); // to-do: check failure - /* - * inspect contents of message queue, find completed messages - */ - for (struct sgl_incoming_server_msg *msg = server_msg_queue; msg;) { - /* - * skip: - * - incomplete messages - * - messages not for us - */ - if (!msg->is_ready_to_be_processed || !msg->header.is_for_server) { - msg = msg->next; - continue; - } - - /* - * parse message type - */ - switch (msg->header.type) { - /* - * when a client connects, we want to send the same connect packet back, - * instead containing the actual client_id - */ - case SGL_PACKET_TYPE_CONNECT: - connection_add(*(int*)(p + SGL_OFFSET_REGISTER_CLAIM_ID)); + if (reason & NET_POLL_INCOMING_CONNECTION) { + net_socket socket = net_accept(net_ctx); + int id = *(int*)(p + SGL_OFFSET_REGISTER_CLAIM_ID); - msg->header.client_id = *(int*)(p + SGL_OFFSET_REGISTER_CLAIM_ID); - msg->header.is_for_server = false; + connection_add(id, socket); - memcpy(global_packet_space, &msg->header, sizeof(struct sgl_packet_header)); - memcpy(global_packet_space + sizeof(struct sgl_packet_header), &framebuffer_size, sizeof(framebuffer_size)); - memcpy(global_packet_space + sizeof(struct sgl_packet_header) + sizeof(framebuffer_size), &fifo_size, sizeof(fifo_size)); - memcpy(global_packet_space + sizeof(struct sgl_packet_header) + sizeof(framebuffer_size) + sizeof(fifo_size), &args.gl_major, sizeof(args.gl_major)); - memcpy(global_packet_space + sizeof(struct sgl_packet_header) + sizeof(framebuffer_size) + sizeof(fifo_size) + sizeof(args.gl_major), &args.gl_minor, sizeof(args.gl_minor)); + struct sgl_packet_connect packet = { + /* client_id = */ id, + /* framebuffer_size = */ framebuffer_size, + /* fifo_size = */ fifo_size, + /* gl_major = */ args.gl_major, + /* gl_minor = */ args.gl_minor + }; - printf("%sinfo%s: client %s%d%s connected\n", COLOR_INFO, COLOR_RESET, COLOR_NUMB, msg->header.client_id, COLOR_RESET); + net_send_tcp(net_ctx, socket, &packet, sizeof(packet)); - net_sendto(net_ctx, global_packet_space, sizeof(struct sgl_packet_header) + sizeof(framebuffer_size) + sizeof(fifo_size) + sizeof(args.gl_major) + sizeof(args.gl_minor), 0); - *((int*)(p + SGL_OFFSET_REGISTER_CLAIM_ID)) += 1; - break; + *((int*)(p + SGL_OFFSET_REGISTER_CLAIM_ID)) += 1; + + printf("%sinfo%s: client %s%d%s connected\n", COLOR_INFO, COLOR_RESET, COLOR_NUMB, id, COLOR_RESET); + } + + /* + * incoming udp packets to the server could only mean that + * a client has requested a framebuffer update + */ + if (reason & NET_POLL_INCOMING_UDP) { + struct sgl_packet_swapbuffers_request packet; + net_recv_udp(net_ctx, &packet, sizeof(packet), 0); + + connection_current(packet.client_id); + sgl_read_pixels(packet.width, packet.height, p + SGL_OFFSET_COMMAND_START + fifo_size, packet.vflip, packet.format, 0); // to-do: show memory for overlay /* - * fifo has been uploaded - * we don't actually send a response here. we send a response upon completion, as explained - * towards the bottom of the file - * - * in case thats too much scrolling: - * we need to send retval at the end of execution to ensure that the client that requested - * this exact fifo buffer will get the exact retval returned from said execution, since packets - * may be out of order + * send sync packet, otherwise most frames are lost */ - case SGL_PACKET_TYPE_FIFO_UPLOAD: - memcpy(&fifo_request_header, &msg->header, sizeof(struct sgl_packet_header)); - memcpy(p + SGL_OFFSET_COMMAND_START, msg->processed_data, msg->recieved_size); - ready_to_render = true; - client_id = msg->header.client_id; - break; + struct sgl_packet_sync sync = { 0 }; + net_send_tcp(net_ctx, get_fd_from_id(packet.client_id), &sync, sizeof(sync)); + + size_t left_over = packet.width * packet.height * 4; + int expected = left_over / SGL_SWAPBUFFERS_RESULT_SIZE + (left_over % SGL_SWAPBUFFERS_RESULT_SIZE != 0); /* - * stream the framebuffer over using big packets - * we may lose these packets in transit, but we dont care - * - * unlike when the client has to send the server something, we don't expect - * the client to recieve everything, so we don't send an "expeect all this" header - * - * warning: SGL_PACKET_TYPE_FRAMEBUFFER_DONE may be sent out-of-order, so expect additional packet loss + * generate an out-of-sequence order of frames. this way, we update as much of the window + * as possible, tackling packet loss from UDP */ - case SGL_PACKET_TYPE_FRAMEBUFFER_FAST_BUT_LOSS: { - int *buf = msg->processed_data; - int w = *buf++, - h = *buf++, - vflip = *buf++, - format = *buf++; + scramble(scramble_arr, expected); + int last_index = expected - 1; + size_t left_over_size = left_over % SGL_SWAPBUFFERS_RESULT_SIZE; - connection_current(msg->header.client_id); - sgl_read_pixels(w, h, p + SGL_OFFSET_COMMAND_START + fifo_size, vflip, format, 0); + if (left_over_size == 0) + left_over_size = SGL_SWAPBUFFERS_RESULT_SIZE; - /* - * to-do: swap out 4 for detecting size of format - */ - const size_t size = w * h * 4; - size_t left_over = size; - size_t offset = 0; + for (int i = 0; i < expected; i++) { + int index = scramble_arr[i]; + size_t size = index != last_index ? SGL_SWAPBUFFERS_RESULT_SIZE : left_over_size; - /* - * stream framebuffer in max sized chunks, adjusting at the end when left over becomes smaller than the max - */ - for (int i = 0; i < size / SGL_PACKET_MAX_BLOCK_SIZE + (size % SGL_PACKET_MAX_BLOCK_SIZE != 0); i++) { - size_t packet_size = left_over > SGL_PACKET_MAX_BLOCK_SIZE ? SGL_PACKET_MAX_BLOCK_SIZE : left_over; - left_over -= SGL_PACKET_MAX_BLOCK_SIZE; + struct sgl_packet_swapbuffers_result result = { + /* client_id = */ packet.client_id, + /* index = */ index, + /* size = */ size, + /* result = */ { 0 } + }; - struct sgl_packet_header header = { - .client_id = msg->header.client_id, - .is_for_server = false, - .type = SGL_PACKET_TYPE_FRAMEBUFFER_FAST_BUT_LOSS, - .size = packet_size, - .index = i, - .expected_blocks = -1, /* lossy api doesn't need to report expected blocks, right ?? */ - .signature = msg->header.signature - }; + memcpy(result.result, p + SGL_OFFSET_COMMAND_START + fifo_size + (index * SGL_SWAPBUFFERS_RESULT_SIZE), size); - memcpy(global_packet_space, &header, sizeof(struct sgl_packet_header)); - memcpy(global_packet_space + sizeof(struct sgl_packet_header), p + SGL_OFFSET_COMMAND_START + fifo_size + offset, packet_size); + net_send_udp(net_ctx, &result, sizeof(result), 0); + } + } - net_sendto(net_ctx, global_packet_space, sizeof(struct sgl_packet_header) + packet_size, 0); + /* + * incoming tcp packets to the server could only mean that + * a client is uploading its fifo buffer + */ + if (reason & NET_POLL_INCOMING_TCP) { + for (int i = NET_SOCKET_FIRST_FD; i < net_fd_count(net_ctx); i++) { + if (!net_did_event_happen_here(net_ctx, i)) + continue; + + struct sgl_packet_fifo_upload initial_upload_packet, *the_rest_of_the_packets = NULL; + if (!net_recv_tcp_timeout(net_ctx, i, &initial_upload_packet, sizeof(initial_upload_packet), 500)) { + int id = get_id_from_fd(i); + printf("%sinfo%s: client %s%d%s timed out, disconnected\n", COLOR_INFO, COLOR_RESET, COLOR_NUMB, id, COLOR_RESET); + connection_rem(id, net_ctx); + memset(p + SGL_OFFSET_COMMAND_START, 0, fifo_size); + break; + } - offset += packet_size; + if (initial_upload_packet.expected_chunks > 1) { + the_rest_of_the_packets = malloc(sizeof(struct sgl_packet_fifo_upload) * (initial_upload_packet.expected_chunks - 1)); + for (int j = 0; j < initial_upload_packet.expected_chunks - 1; j++) + net_recv_tcp(net_ctx, i, &the_rest_of_the_packets[j], sizeof(initial_upload_packet)); } - struct sgl_packet_header header = { - .client_id = msg->header.client_id, - .is_for_server = false, - .type = SGL_PACKET_TYPE_FRAMEBUFFER_DONE, - .size = 0, - .index = 0, - .expected_blocks = -1, - .signature = msg->header.signature - }; + size_t offset = 0; + for (int i = 0; i < initial_upload_packet.expected_chunks; i++) { + struct sgl_packet_fifo_upload *packet = i == 0 ? &initial_upload_packet : &the_rest_of_the_packets[i - 1]; + size_t size = sizeof(uint32_t) * packet->count; - /* - * to-do: fix this, this part causes lock ups - */ - usleep(1000); - net_sendto(net_ctx, &header, sizeof(struct sgl_packet_header), 0); - net_sendto(net_ctx, &header, sizeof(struct sgl_packet_header), 0); - //printf("done framebuffer\n"); - break; - } + memcpy(p + SGL_OFFSET_COMMAND_START + offset, packet->commands, size); + offset += size; + } - default: - printf("(out_of_branch_err) unhandled packet type (%d)\n", msg->header.type); + if (the_rest_of_the_packets != NULL) + free(the_rest_of_the_packets); + + ready_to_render = true; + client_id = initial_upload_packet.client_id; + + // break here so we can handle other clients after break; } - - /* - * remove processed message - */ - struct sgl_incoming_server_msg *next_msg = msg->next; - dynarr_free_element((void**)&server_msg_queue, 0, match_equal, msg); - msg = next_msg; } - - /* - * mandatory unlock - */ - spin_unlock(&msg_queue_lock); } } @@ -645,9 +356,10 @@ void sgl_cmd_processor_start(struct sgl_cmd_processor_args args) connection_current(client_id); int *pb = p + SGL_OFFSET_COMMAND_START; + // int track = 0; while (*pb != SGL_CMD_INVALID) { cmd = *pb; - // printf("[+] command: %s (%d)\n", *pb < SGL_CMD_MAX ? SGL_CMD_STRING_TABLE[*pb] : "????", *pb); fflush(stdout); + // printf("[%-5d] command: %s (%d)\n", track++, sgl_cmd2str(*pb), *pb); fflush(stdout); // if (*pb >= SGL_CMD_MAX) // exit(1); switch (*pb++) { @@ -659,8 +371,9 @@ void sgl_cmd_processor_start(struct sgl_cmd_processor_args args) case SGL_CMD_GOODBYE_WORLD: { int id = *pb++; printf("%sinfo%s: client %s%d%s disconnected\n", COLOR_INFO, COLOR_RESET, COLOR_NUMB, id, COLOR_RESET); - connection_rem(id); + connection_rem(id, net_ctx); memset(p + SGL_OFFSET_COMMAND_START, 0, fifo_size); + network_expecting_retval = false; // exit(1); break; } @@ -4999,25 +4712,19 @@ void sgl_cmd_processor_start(struct sgl_cmd_processor_args args) /* * for networking only: we need to send retval back to client upon completion - * BECAUSE another fifo may be uploaded and executed before the og client can get its retval, - * overwriting whatever value was there before :[ */ if (net_ctx != NULL) { - char *scratch_buffer = malloc(sizeof(struct sgl_packet_header) + 8 + 256); - - fifo_request_header.size = 8 + 256; - fifo_request_header.is_for_server = false; - - /* - * copy header, retval, & retval_v into packet - */ - memcpy(scratch_buffer, &fifo_request_header, sizeof(struct sgl_packet_header)); - memcpy(scratch_buffer + sizeof(struct sgl_packet_header), (uint64_t*)(p + SGL_OFFSET_REGISTER_RETVAL), 8); - memcpy(scratch_buffer + sizeof(struct sgl_packet_header) + 8, (uint64_t*)(p + SGL_OFFSET_REGISTER_RETVAL_V), 256); - - net_sendto(net_ctx, scratch_buffer, sizeof(struct sgl_packet_header) + 8 + 256, 0); + if (network_expecting_retval) { + struct sgl_packet_retval packet; - free(scratch_buffer); + memcpy(&packet.retval, (uint64_t*)(p + SGL_OFFSET_REGISTER_RETVAL), 8); + memcpy(&packet.retval_v, (uint64_t*)(p + SGL_OFFSET_REGISTER_RETVAL_V), 256); + + net_send_tcp(net_ctx, get_fd_from_id(client_id), &packet, sizeof(packet)); + } + else { + network_expecting_retval = true; + } } } } \ No newline at end of file