Move files from lib/ into src/ and refactor the random client
This commit is contained in:
@@ -1,501 +0,0 @@
|
||||
#if defined(MAIN_SIMULATION) || defined(MAIN_TEST)
|
||||
#define QUAKEY_ENABLE_MOCKS
|
||||
#endif
|
||||
|
||||
#include <quakey.h>
|
||||
#include <assert.h>
|
||||
|
||||
#include "tcp.h"
|
||||
#include "message.h"
|
||||
|
||||
static int set_socket_blocking(SOCKET sock, bool value)
|
||||
{
|
||||
#ifdef _WIN32
|
||||
u_long mode = !value;
|
||||
if (ioctlsocket(sock, FIONBIO, &mode) == SOCKET_ERROR)
|
||||
return -1;
|
||||
#else
|
||||
int flags = fcntl(sock, F_GETFL, 0);
|
||||
if (flags < 0)
|
||||
return -1;
|
||||
if (value) flags &= ~O_NONBLOCK;
|
||||
else flags |= O_NONBLOCK;
|
||||
if (fcntl(sock, F_SETFL, flags) < 0)
|
||||
return -1;
|
||||
#endif
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static SOCKET create_listen_socket(Address addr)
|
||||
{
|
||||
SOCKET fd = socket(AF_INET, SOCK_STREAM, 0);
|
||||
if (fd == INVALID_SOCKET)
|
||||
return INVALID_SOCKET;
|
||||
|
||||
if (set_socket_blocking(fd, false) < 0) {
|
||||
CLOSE_SOCKET(fd);
|
||||
return INVALID_SOCKET;
|
||||
}
|
||||
|
||||
// TODO: mark address as reusable in debug builds
|
||||
|
||||
if (!addr.is_ipv4) {
|
||||
assert(0); // TODO
|
||||
}
|
||||
|
||||
struct sockaddr_in bind_buf;
|
||||
bind_buf.sin_family = AF_INET;
|
||||
bind_buf.sin_port = htons(addr.port);
|
||||
memcpy(&bind_buf.sin_addr, &addr.ipv4, sizeof(IPv4));
|
||||
if (bind(fd, (struct sockaddr*) &bind_buf, sizeof(bind_buf))) {
|
||||
CLOSE_SOCKET(fd);
|
||||
return INVALID_SOCKET;
|
||||
}
|
||||
|
||||
int backlog = 32;
|
||||
if (listen(fd, backlog) < 0) {
|
||||
CLOSE_SOCKET(fd);
|
||||
return INVALID_SOCKET;
|
||||
}
|
||||
|
||||
return fd;
|
||||
}
|
||||
|
||||
static int create_socket_pair(SOCKET *a, SOCKET *b)
|
||||
{
|
||||
#ifdef _WIN32
|
||||
SOCKET sock = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
|
||||
if (sock == INVALID_SOCKET)
|
||||
return -1;
|
||||
|
||||
// Bind to loopback address with port 0 (dynamic port assignment)
|
||||
struct sockaddr_in addr;
|
||||
int addr_len = sizeof(addr);
|
||||
memset(&addr, 0, sizeof(addr));
|
||||
addr.sin_family = AF_INET;
|
||||
addr.sin_addr.s_addr = htonl(INADDR_LOOPBACK); // 127.0.0.1
|
||||
addr.sin_port = 0; // Let system choose port
|
||||
|
||||
if (bind(sock, (struct sockaddr*)&addr, sizeof(addr)) == SOCKET_ERROR) {
|
||||
closesocket(sock);
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (getsockname(sock, (struct sockaddr*)&addr, &addr_len) == SOCKET_ERROR) {
|
||||
closesocket(sock);
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (connect(sock, (struct sockaddr*)&addr, sizeof(addr)) == SOCKET_ERROR) {
|
||||
closesocket(sock);
|
||||
return -1;
|
||||
}
|
||||
|
||||
*a = sock;
|
||||
*b = sock;
|
||||
|
||||
// Optional: Set socket to non-blocking mode
|
||||
// This prevents send() from blocking if the receive buffer is full
|
||||
u_long mode = 1;
|
||||
ioctlsocket(sock, FIONBIO, &mode); // TODO: does this fail?
|
||||
return 0;
|
||||
#else
|
||||
int fds[2];
|
||||
if (pipe(fds) < 0)
|
||||
return -1;
|
||||
*a = fds[0];
|
||||
*b = fds[1];
|
||||
return 0;
|
||||
#endif
|
||||
}
|
||||
|
||||
static void close_socket_pair(SOCKET a, SOCKET b)
|
||||
{
|
||||
#ifdef _WIN32
|
||||
closesocket(a);
|
||||
(void) b;
|
||||
#else
|
||||
close(a);
|
||||
close(b);
|
||||
#endif
|
||||
}
|
||||
|
||||
static void conn_init(Connection *conn, SOCKET fd, bool connecting)
|
||||
{
|
||||
conn->fd = fd;
|
||||
conn->tag = -1;
|
||||
conn->connecting = connecting;
|
||||
conn->closing = false;
|
||||
conn->msglen = 0;
|
||||
byte_queue_init(&conn->input, 1<<30);
|
||||
byte_queue_init(&conn->output, 1<<30);
|
||||
}
|
||||
|
||||
static void conn_free(Connection *conn)
|
||||
{
|
||||
CLOSE_SOCKET(conn->fd);
|
||||
byte_queue_free(&conn->input);
|
||||
byte_queue_free(&conn->output);
|
||||
}
|
||||
|
||||
static int conn_events(Connection *conn)
|
||||
{
|
||||
int events = 0;
|
||||
|
||||
if (conn->connecting)
|
||||
events |= POLLOUT;
|
||||
else {
|
||||
|
||||
assert(!byte_queue_full(&conn->input));
|
||||
if (!conn->closing)
|
||||
events |= POLLIN;
|
||||
|
||||
if (!byte_queue_empty(&conn->output))
|
||||
events |= POLLOUT;
|
||||
}
|
||||
return events;
|
||||
}
|
||||
|
||||
int tcp_context_init(TCP *tcp)
|
||||
{
|
||||
tcp->listen_fd = INVALID_SOCKET;
|
||||
tcp->num_conns = 0;
|
||||
|
||||
if (create_socket_pair(&tcp->wait_fd, &tcp->signal_fd) < 0)
|
||||
return -1;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void tcp_context_free(TCP *tcp)
|
||||
{
|
||||
// Free all connection byte queues without closing sockets
|
||||
// (sockets are managed by the simulation and will be cleaned up separately)
|
||||
for (int i = 0; i < tcp->num_conns; i++) {
|
||||
byte_queue_free(&tcp->conns[i].input);
|
||||
byte_queue_free(&tcp->conns[i].output);
|
||||
}
|
||||
tcp->num_conns = 0;
|
||||
|
||||
if (tcp->listen_fd != INVALID_SOCKET)
|
||||
CLOSE_SOCKET(tcp->listen_fd);
|
||||
|
||||
close_socket_pair(tcp->wait_fd, tcp->signal_fd);
|
||||
}
|
||||
|
||||
int tcp_wakeup(TCP *tcp)
|
||||
{
|
||||
send(tcp->signal_fd, "0", 1, 0); // TODO: Handle error
|
||||
return 0;
|
||||
}
|
||||
|
||||
int tcp_index_from_tag(TCP *tcp, int tag)
|
||||
{
|
||||
for (int i = 0; i < tcp->num_conns; i++)
|
||||
if (tcp->conns[i].tag == tag)
|
||||
return i;
|
||||
return -1;
|
||||
}
|
||||
|
||||
int tcp_listen(TCP *tcp, Address addr)
|
||||
{
|
||||
SOCKET listen_fd = create_listen_socket(addr);
|
||||
if (listen_fd == INVALID_SOCKET)
|
||||
return -1;
|
||||
|
||||
tcp->listen_fd = listen_fd;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int tcp_next_message(TCP *tcp, int conn_idx, ByteView *msg, uint16_t *type)
|
||||
{
|
||||
*msg = byte_queue_read_buf(&tcp->conns[conn_idx].input);
|
||||
|
||||
uint32_t len;
|
||||
int ret = message_peek(*msg, type, &len);
|
||||
|
||||
// Invalid message?
|
||||
if (ret < 0) {
|
||||
byte_queue_read_ack(&tcp->conns[conn_idx].input, 0);
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Still buffering header?
|
||||
if (ret == 0) {
|
||||
byte_queue_read_ack(&tcp->conns[conn_idx].input, 0);
|
||||
if (byte_queue_full(&tcp->conns[conn_idx].input))
|
||||
return -1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Message received
|
||||
assert(ret > 0);
|
||||
msg->len = len;
|
||||
tcp->conns[conn_idx].msglen = len;
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
void tcp_consume_message(TCP *tcp, int conn_idx)
|
||||
{
|
||||
byte_queue_read_ack(&tcp->conns[conn_idx].input, tcp->conns[conn_idx].msglen);
|
||||
tcp->conns[conn_idx].msglen = 0;
|
||||
}
|
||||
|
||||
int tcp_register_events(TCP *tcp, void **contexts, struct pollfd *polled)
|
||||
{
|
||||
int num_polled = 0;
|
||||
|
||||
polled[num_polled].fd = tcp->wait_fd;
|
||||
polled[num_polled].events = POLLIN;
|
||||
polled[num_polled].revents = 0;
|
||||
contexts[num_polled] = NULL;
|
||||
num_polled++;
|
||||
|
||||
if (tcp->listen_fd != INVALID_SOCKET && tcp->num_conns < TCP_CONNECTION_LIMIT) {
|
||||
polled[num_polled].fd = tcp->listen_fd;
|
||||
polled[num_polled].events = POLLIN;
|
||||
polled[num_polled].revents = 0;
|
||||
contexts[num_polled] = NULL;
|
||||
num_polled++;
|
||||
}
|
||||
|
||||
for (int i = 0; i < tcp->num_conns; i++) {
|
||||
int events = conn_events(&tcp->conns[i]);
|
||||
if (events) {
|
||||
polled[num_polled].fd = tcp->conns[i].fd;
|
||||
polled[num_polled].events = events;
|
||||
polled[num_polled].revents = 0;
|
||||
contexts[num_polled] = &tcp->conns[i];
|
||||
num_polled++;
|
||||
}
|
||||
}
|
||||
|
||||
return num_polled;
|
||||
}
|
||||
|
||||
// The "events" array must be an array of capacity TCP_EVENT_CAPACITY,
|
||||
// while "contexts" and "polled" must have capacity TCP_POLL_CAPACITY.
|
||||
int tcp_translate_events(TCP *tcp, Event *events, void **contexts, struct pollfd *polled, int num_polled)
|
||||
{
|
||||
bool removed[TCP_POLL_CAPACITY];
|
||||
for (int i = 0; i < TCP_POLL_CAPACITY; i++)
|
||||
removed[i] = false;
|
||||
|
||||
int num_events = 0;
|
||||
for (int i = 1; i < num_polled; i++) {
|
||||
|
||||
if (polled[i].fd == tcp->wait_fd) {
|
||||
|
||||
if (polled[i].revents & POLLIN) {
|
||||
char buf[100];
|
||||
recv(tcp->wait_fd, buf, sizeof(buf), 0); // TODO: Make sure all bytes are consumed
|
||||
events[num_events++] = (Event) { EVENT_WAKEUP, -1, -1 };
|
||||
}
|
||||
|
||||
} else if (polled[i].fd == tcp->listen_fd) {
|
||||
|
||||
assert(contexts[i] == NULL);
|
||||
|
||||
if (polled[i].revents & POLLIN) {
|
||||
SOCKET new_fd = accept(tcp->listen_fd, NULL, NULL);
|
||||
if (new_fd != INVALID_SOCKET) {
|
||||
|
||||
if (set_socket_blocking(new_fd, false) < 0)
|
||||
CLOSE_SOCKET(new_fd);
|
||||
else {
|
||||
conn_init(&tcp->conns[tcp->num_conns++], new_fd, false);
|
||||
events[num_events++] = (Event) { EVENT_CONNECT, tcp->num_conns-1, tcp->conns[tcp->num_conns-1].tag };
|
||||
}
|
||||
}
|
||||
}
|
||||
removed[i] = false;
|
||||
|
||||
} else {
|
||||
|
||||
Connection *conn = contexts[i];
|
||||
bool defer_close = false;
|
||||
bool defer_ready = false;
|
||||
|
||||
if (conn->connecting) {
|
||||
|
||||
// Check for error conditions on the socket
|
||||
if (polled[i].revents & (POLLERR | POLLHUP | POLLNVAL)) {
|
||||
defer_close = true;
|
||||
} else if (polled[i].revents & POLLOUT) {
|
||||
|
||||
int err = 0;
|
||||
socklen_t len = sizeof(err);
|
||||
if (getsockopt(conn->fd, SOL_SOCKET, SO_ERROR, (void*) &err, &len) < 0 || err != 0)
|
||||
defer_close = true;
|
||||
else {
|
||||
conn->connecting = false;
|
||||
events[num_events++] = (Event) { EVENT_CONNECT, conn - tcp->conns, conn->tag };
|
||||
}
|
||||
}
|
||||
|
||||
} else {
|
||||
|
||||
if (polled[i].revents & POLLIN) {
|
||||
byte_queue_write_setmincap(&conn->input, 1<<9);
|
||||
ByteView buf = byte_queue_write_buf(&conn->input);
|
||||
int num = recv(conn->fd, (char*) buf.ptr, buf.len, 0);
|
||||
if (num == 0)
|
||||
defer_close = true;
|
||||
else if (num < 0) {
|
||||
if (errno != EINTR && errno != EWOULDBLOCK && errno != EAGAIN) // TODO: does Windows return these error codes or not?
|
||||
defer_close = true;
|
||||
num = 0;
|
||||
}
|
||||
byte_queue_write_ack(&conn->input, num);
|
||||
ByteView msg = byte_queue_read_buf(&conn->input);
|
||||
int ret = message_peek(msg, NULL, NULL);
|
||||
byte_queue_read_ack(&conn->input, 0);
|
||||
if (ret < 0) {
|
||||
// Invalid message
|
||||
defer_close = true;
|
||||
} else if (ret == 0) {
|
||||
// Still buffering
|
||||
if (byte_queue_full(&conn->input))
|
||||
defer_close = true;
|
||||
} else {
|
||||
// Message received
|
||||
assert(ret > 0);
|
||||
defer_ready = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (polled[i].revents & POLLOUT) {
|
||||
ByteView buf = byte_queue_read_buf(&conn->output);
|
||||
int num = send(conn->fd, (char*) buf.ptr, buf.len, 0);
|
||||
if (num < 0) {
|
||||
if (errno != EINTR && errno != EWOULDBLOCK && errno != EAGAIN)
|
||||
defer_close = true;
|
||||
num = 0;
|
||||
}
|
||||
byte_queue_read_ack(&conn->output, num);
|
||||
if (conn->closing && byte_queue_empty(&conn->output))
|
||||
defer_close = true;
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: byte_queue_error here?
|
||||
|
||||
removed[i] = defer_close;
|
||||
if (0) {}
|
||||
else if (defer_close) events[num_events++] = (Event) { EVENT_DISCONNECT, conn - tcp->conns, conn->tag };
|
||||
else if (defer_ready) events[num_events++] = (Event) { EVENT_MESSAGE, conn - tcp->conns, conn->tag };
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = 1; i < num_polled; i++) {
|
||||
if (removed[i]) {
|
||||
Connection *conn = contexts[i];
|
||||
assert(conn);
|
||||
int removed_idx = conn - tcp->conns;
|
||||
conn_free(conn);
|
||||
int last_idx = --tcp->num_conns;
|
||||
if (removed_idx != last_idx) {
|
||||
*conn = tcp->conns[last_idx];
|
||||
// Update event conn_idx values to reflect the swap
|
||||
for (int j = 0; j < num_events; j++) {
|
||||
if (events[j].conn_idx == last_idx)
|
||||
events[j].conn_idx = removed_idx;
|
||||
}
|
||||
// Update contexts pointers for remaining iterations
|
||||
for (int j = i + 1; j < num_polled; j++) {
|
||||
if (contexts[j] == &tcp->conns[last_idx])
|
||||
contexts[j] = conn;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return num_events;
|
||||
}
|
||||
|
||||
ByteQueue *tcp_output_buffer(TCP *tcp, int conn_idx)
|
||||
{
|
||||
return &tcp->conns[conn_idx].output;
|
||||
}
|
||||
|
||||
int tcp_connect(TCP *tcp, Address addr, int tag, ByteQueue **output)
|
||||
{
|
||||
if (tcp->num_conns == TCP_CONNECTION_LIMIT)
|
||||
return -1;
|
||||
int conn_idx = tcp->num_conns;
|
||||
|
||||
SOCKET fd = socket(AF_INET, SOCK_STREAM, 0);
|
||||
if (fd == INVALID_SOCKET)
|
||||
return -1;
|
||||
|
||||
if (set_socket_blocking(fd, false) < 0) {
|
||||
CLOSE_SOCKET(fd);
|
||||
return -1;
|
||||
}
|
||||
|
||||
int ret;
|
||||
if (addr.is_ipv4) {
|
||||
struct sockaddr_in buf;
|
||||
buf.sin_family = AF_INET;
|
||||
buf.sin_port = htons(addr.port);
|
||||
memcpy(&buf.sin_addr, &addr.ipv4, sizeof(IPv4));
|
||||
ret = connect(fd, (struct sockaddr*) &buf, sizeof(buf));
|
||||
} else {
|
||||
struct sockaddr_in6 buf;
|
||||
buf.sin6_family = AF_INET6;
|
||||
buf.sin6_port = htons(addr.port);
|
||||
memcpy(&buf.sin6_addr, &addr.ipv6, sizeof(IPv6));
|
||||
ret = connect(fd, (struct sockaddr*) &buf, sizeof(buf));
|
||||
}
|
||||
|
||||
bool connecting;
|
||||
if (ret == 0) {
|
||||
connecting = false;
|
||||
} else {
|
||||
if (errno != EINPROGRESS) {
|
||||
CLOSE_SOCKET(fd);
|
||||
return -1;
|
||||
}
|
||||
connecting = true;
|
||||
}
|
||||
|
||||
// Check that this tag wasn't already used
|
||||
for (int i = 0; i < tcp->num_conns; i++)
|
||||
assert(tcp->conns[i].tag != tag);
|
||||
|
||||
conn_init(&tcp->conns[conn_idx], fd, connecting);
|
||||
tcp->conns[conn_idx].tag = tag;
|
||||
|
||||
if (output)
|
||||
*output = &tcp->conns[conn_idx].output;
|
||||
|
||||
tcp->num_conns++;
|
||||
return 0;
|
||||
}
|
||||
|
||||
void tcp_close(TCP *tcp, int conn_idx)
|
||||
{
|
||||
tcp->conns[conn_idx].closing = true;
|
||||
tcp->conns[conn_idx].tag = -1; // Clear tag so new sends create a fresh connection
|
||||
// TODO: if no event will be triggered, the connection will not be closed
|
||||
// if the output buffer is empty, the connection should be closed here.
|
||||
}
|
||||
|
||||
void tcp_set_tag(TCP *tcp, int conn_idx, int tag, bool unique)
|
||||
{
|
||||
assert(tag != -1);
|
||||
|
||||
if (unique) {
|
||||
for (int i = 0; i < tcp->num_conns; i++)
|
||||
assert(tcp->conns[i].tag != tag);
|
||||
}
|
||||
|
||||
tcp->conns[conn_idx].tag = tag;
|
||||
}
|
||||
|
||||
int tcp_get_tag(TCP *tcp, int conn_idx)
|
||||
{
|
||||
return tcp->conns[conn_idx].tag;
|
||||
}
|
||||
Reference in New Issue
Block a user