diff options
Diffstat (limited to 'src/engine/e_network_conn.c')
| -rw-r--r-- | src/engine/e_network_conn.c | 364 |
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; +} |