about summary refs log tree commit diff
path: root/src/engine/e_network_conn.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/engine/e_network_conn.c')
-rw-r--r--src/engine/e_network_conn.c364
1 files changed, 364 insertions, 0 deletions
diff --git a/src/engine/e_network_conn.c b/src/engine/e_network_conn.c
new file mode 100644
index 00000000..05ced197
--- /dev/null
+++ b/src/engine/e_network_conn.c
@@ -0,0 +1,364 @@
+#include <base/system.h>
+#include <string.h>
+#include "e_config.h"
+#include "e_network_internal.h"
+
+static void conn_reset_stats(NETCONNECTION *conn)
+{
+	mem_zero(&conn->stats, sizeof(conn->stats));
+}
+
+static void conn_reset(NETCONNECTION *conn)
+{
+	conn->seq = 0;
+	conn->ack = 0;
+	conn->remote_closed = 0;
+	
+	conn->state = NET_CONNSTATE_OFFLINE;
+	conn->state = NET_CONNSTATE_OFFLINE;
+	conn->last_send_time = 0;
+	conn->last_recv_time = 0;
+	conn->last_update_time = 0;
+	conn->token = -1;
+	mem_zero(&conn->peeraddr, sizeof(conn->peeraddr));
+	
+	conn->buffer = ringbuf_init(conn->buffer_memory, sizeof(conn->buffer_memory), 0);
+	
+	mem_zero(&conn->construct, sizeof(conn->construct));
+}
+
+
+const char *conn_error(NETCONNECTION *conn)
+{
+	return conn->error_string;
+}
+
+static void conn_set_error(NETCONNECTION *conn, const char *str)
+{
+	str_copy(conn->error_string, str, sizeof(conn->error_string));
+}
+
+void conn_init(NETCONNECTION *conn, NETSOCKET socket)
+{
+	conn_reset(conn);
+	conn_reset_stats(conn);
+	conn->socket = socket;
+	mem_zero(conn->error_string, sizeof(conn->error_string));
+}
+
+
+static void conn_ack(NETCONNECTION *conn, int ack)
+{
+	while(1)
+	{
+		NETCHUNKDATA *resend = (NETCHUNKDATA *)ringbuf_first(conn->buffer);
+		if(!resend)
+			break;
+			
+		if(resend->sequence <= ack || (ack < NET_MAX_SEQUENCE/3 && resend->sequence > NET_MAX_SEQUENCE/2))
+			ringbuf_popfirst(conn->buffer);
+		else
+			break;
+	}
+}
+
+void conn_want_resend(NETCONNECTION *conn)
+{
+	conn->construct.flags |= NET_PACKETFLAG_RESEND;
+}
+
+int conn_flush(NETCONNECTION *conn)
+{
+	int num_chunks = conn->construct.num_chunks;
+	if(!num_chunks && !conn->construct.flags)
+		return 0;
+	
+	conn->construct.ack = conn->ack;
+	send_packet(conn->socket, &conn->peeraddr, &conn->construct);
+	conn->last_send_time = time_get();
+	
+	/* clear construct so we can start building a new package */
+	mem_zero(&conn->construct, sizeof(conn->construct));
+	return num_chunks;
+}
+
+void conn_queue_chunk(NETCONNECTION *conn, int flags, int data_size, const void *data)
+{
+	unsigned char *chunk_data;
+	/* check if we have space for it, if not, flush the connection */
+	if(conn->construct.data_size + data_size + NET_MAX_CHUNKHEADERSIZE > sizeof(conn->construct.chunk_data))
+		conn_flush(conn);
+
+	if(flags&NET_CHUNKFLAG_VITAL && !(flags&NET_CHUNKFLAG_RESEND))
+		conn->seq = (conn->seq+1)%NET_MAX_SEQUENCE;
+
+	/* pack all the data */
+	chunk_data = &conn->construct.chunk_data[conn->construct.data_size];
+	chunk_data = pack_chunk_header(chunk_data, flags, data_size, conn->seq);
+	mem_copy(chunk_data, data, data_size);
+	chunk_data += data_size;
+
+	/* */
+	conn->construct.num_chunks++;
+	conn->construct.data_size = (int)(chunk_data-conn->construct.chunk_data);
+	
+	/* set packet flags aswell */
+	
+	if(flags&NET_CHUNKFLAG_VITAL && !(flags&NET_CHUNKFLAG_RESEND))
+	{
+		/* save packet if we need to resend */
+		NETCHUNKDATA *resend = (NETCHUNKDATA *)ringbuf_allocate(conn->buffer, sizeof(NETCHUNKDATA)+data_size);
+		if(resend)
+		{
+			resend->sequence = conn->seq;
+			resend->flags = flags;
+			resend->data_size = data_size;
+			resend->data = (unsigned char *)(resend+1);
+			resend->first_send_time = time_get();
+			mem_copy(resend->data, data, data_size);
+		}
+		else
+		{
+			/* out of buffer */
+			conn_disconnect(conn, "too weak connection (out of buffer)");
+		}
+	}
+}
+
+static void conn_send_control(NETCONNECTION *conn, int controlmsg, const void *extra, int extra_size)
+{
+	NETPACKETCONSTRUCT construct;
+	construct.flags = NET_PACKETFLAG_CONTROL;
+	construct.ack = conn->ack;
+	construct.num_chunks = 0;
+	construct.data_size = 1+extra_size;
+	construct.chunk_data[0] = controlmsg;
+	mem_copy(&construct.chunk_data[1], extra, extra_size);
+
+	/* send the control message */
+	send_packet(conn->socket, &conn->peeraddr, &construct);
+	conn->last_send_time = time_get();
+}
+
+static void conn_resend(NETCONNECTION *conn)
+{
+	int resend_count = 0;
+	int max = 10;
+	void *item = ringbuf_first(conn->buffer);
+	while(item)
+	{
+		NETCHUNKDATA *resend = item;
+		conn_queue_chunk(conn, resend->flags|NET_CHUNKFLAG_RESEND, resend->data_size, resend->data);
+		item = ringbuf_next(conn->buffer, item);
+		max--;
+		resend_count++;
+		if(!max)
+			break;
+	}
+	
+	dbg_msg("conn", "resent %d packets", resend_count);
+}
+
+int conn_connect(NETCONNECTION *conn, NETADDR *addr)
+{
+	if(conn->state != NET_CONNSTATE_OFFLINE)
+		return -1;
+	
+	/* init connection */
+	conn_reset(conn);
+	conn->peeraddr = *addr;
+	mem_zero(conn->error_string, sizeof(conn->error_string));
+	conn->state = NET_CONNSTATE_CONNECT;
+	conn_send_control(conn, NET_CTRLMSG_CONNECT, 0, 0);
+	return 0;
+}
+
+void conn_disconnect(NETCONNECTION *conn, const char *reason)
+{
+	if(conn->state == NET_CONNSTATE_OFFLINE)
+		return;
+
+	if(conn->remote_closed == 0)
+	{
+		if(reason)
+			conn_send_control(conn, NET_CTRLMSG_CLOSE, reason, strlen(reason)+1);
+		else
+			conn_send_control(conn, NET_CTRLMSG_CLOSE, 0, 0);
+
+		conn->error_string[0] = 0;
+		if(reason)
+			str_copy(conn->error_string, reason, sizeof(conn->error_string));
+	}
+	
+	conn_reset(conn);
+}
+
+int conn_feed(NETCONNECTION *conn, NETPACKETCONSTRUCT *packet, NETADDR *addr)
+{
+	int64 now = time_get();
+	conn->last_recv_time = now;
+	
+	/* check if resend is requested */
+	if(packet->flags&NET_PACKETFLAG_RESEND)
+		conn_resend(conn);
+
+	/* */									
+	if(packet->flags&NET_PACKETFLAG_CONTROL)
+	{
+		int ctrlmsg = packet->chunk_data[0];
+		
+		if(ctrlmsg == NET_CTRLMSG_CLOSE)
+		{
+			conn->state = NET_CONNSTATE_ERROR;
+			conn->remote_closed = 1;
+			
+			if(packet->data_size)
+			{
+				/* make sure to sanitize the error string form the other party*/
+				char str[128];
+				if(packet->data_size < 128)
+					str_copy(str, (char *)packet->chunk_data, packet->data_size);
+				else
+					str_copy(str, (char *)packet->chunk_data, 128);
+				str_sanitize_strong(str);
+				
+				/* set the error string */
+				conn_set_error(conn, str);
+			}
+			else
+				conn_set_error(conn, "no reason given");
+				
+			if(config.debug)
+				dbg_msg("conn", "closed reason='%s'", conn_error(conn));
+			return 0;			
+		}
+		else
+		{
+			if(conn->state == NET_CONNSTATE_OFFLINE)
+			{
+				if(ctrlmsg == NET_CTRLMSG_CONNECT)
+				{
+					/* send response and init connection */
+					conn_reset(conn);
+					conn->state = NET_CONNSTATE_ONLINE;
+					conn->peeraddr = *addr;
+					conn->last_send_time = now;
+					conn->last_recv_time = now;
+					conn->last_update_time = now;
+					conn_send_control(conn, NET_CTRLMSG_CONNECTACCEPT, 0, 0);
+					if(config.debug)
+						dbg_msg("connection", "got connection, sending connect+accept");			
+				}
+			}
+			else if(conn->state == NET_CONNSTATE_CONNECT)
+			{
+				/* connection made */
+				if(ctrlmsg == NET_CTRLMSG_CONNECTACCEPT)
+				{
+					conn_send_control(conn, NET_CTRLMSG_ACCEPT, 0, 0);
+					conn->state = NET_CONNSTATE_ONLINE;
+					if(config.debug)
+						dbg_msg("connection", "got connect+accept, sending accept. connection online");
+				}
+			}
+			else if(conn->state == NET_CONNSTATE_ONLINE)
+			{
+				/* connection made */
+				/*
+				if(ctrlmsg == NET_CTRLMSG_CONNECTACCEPT)
+				{
+					
+				}*/
+			}
+		}
+	}
+	
+	if(conn->state == NET_CONNSTATE_ONLINE)
+	{
+		conn_ack(conn, packet->ack);
+	}
+	
+	return 1;
+}
+
+int conn_update(NETCONNECTION *conn)
+{
+	int64 now = time_get();
+
+	if(conn->state == NET_CONNSTATE_OFFLINE || conn->state == NET_CONNSTATE_ERROR)
+		return 0;
+
+	/* watch out for major hitches */
+	{
+		/* TODO: fix this */
+		/*
+		int64 delta = now-conn->last_update_time;
+		if(conn->last_update_time && delta > time_freq()/2)
+		{
+			RINGBUFFER_ITEM *item = conn->buffer.first;
+	
+			dbg_msg("conn", "hitch %d", (int)((delta*1000)/time_freq()));
+			conn->last_recv_time += delta;
+	
+			while(item)
+			{
+				NETPACKETDATA *resend = (NETPACKETDATA *)rb_item_data(item);
+				resend->first_send_time += delta;
+				item = item->next;
+			}
+		}
+
+		conn->last_update_time = now;
+		*/
+	}
+		
+	
+	/* check for timeout */
+	if(conn->state != NET_CONNSTATE_OFFLINE &&
+		conn->state != NET_CONNSTATE_CONNECT &&
+		(now-conn->last_recv_time) > time_freq()*10)
+	{
+		conn->state = NET_CONNSTATE_ERROR;
+		conn_set_error(conn, "timeout");
+	}
+
+	if(ringbuf_first(conn->buffer))
+	{
+		/* TODO: fix this */
+		NETCHUNKDATA *resend = (NETCHUNKDATA *)ringbuf_first(conn->buffer);
+		if(now-resend->first_send_time > time_freq()*10)
+		{
+			conn->state = NET_CONNSTATE_ERROR;
+			conn_set_error(conn, "too weak connection (not acked for 10 seconds)");
+		}
+	}
+	
+	/* send keep alives if nothing has happend for 1000ms */
+	if(conn->state == NET_CONNSTATE_ONLINE)
+	{
+		if(time_get()-conn->last_send_time > time_freq()/2) /* flush connection after 250ms if needed */
+		{
+			int num_flushed_chunks = conn_flush(conn);
+			if(num_flushed_chunks && config.debug)
+				dbg_msg("connection", "flushed connection due to timeout. %d chunks.", num_flushed_chunks);
+		}
+			
+		if(time_get()-conn->last_send_time > time_freq())
+			conn_send_control(conn, NET_CTRLMSG_KEEPALIVE, 0, 0);
+	}
+	else if(conn->state == NET_CONNSTATE_CONNECT)
+	{
+		if(time_get()-conn->last_send_time > time_freq()/2) /* send a new connect every 500ms */
+			conn_send_control(conn, NET_CTRLMSG_CONNECT, 0, 0);
+			/*conn_send(conn, NETWORK_PACKETFLAG_CONNECT, 0, 0);*/
+	}
+	else if(conn->state == NET_CONNSTATE_CONNECTACCEPTED)
+	{
+
+		if(time_get()-conn->last_send_time > time_freq()/2) /* send a new connect/accept every 500ms */
+			conn_send_control(conn, NET_CTRLMSG_CONNECTACCEPT, 0, 0);
+			/*conn_send(conn, NETWORK_PACKETFLAG_CONNECT|NETWORK_PACKETFLAG_ACCEPT, 0, 0);*/
+	}
+	
+	return 0;
+}