#include #include #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; /* send of the packets */ conn->construct.ack = conn->ack; send_packet(conn->socket, &conn->peeraddr, &conn->construct); /* update send times */ 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(); resend->last_send_time = resend->first_send_time; 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_chunk(NETCONNECTION *conn, NETCHUNKDATA *resend) { conn_queue_chunk(conn, resend->flags|NET_CHUNKFLAG_RESEND, resend->data_size, resend->data); resend->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_resend_chunk(conn, resend); item = ringbuf_next(conn->buffer, item); max--; resend_count++; if(!max) break; } if(config.debug) 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_PENDING; 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) { }*/ } } } else { if(conn->state == NET_CONNSTATE_PENDING) { conn->state = NET_CONNSTATE_ONLINE; if(config.debug) dbg_msg("connection", "connecting online"); } } 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; /* 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"); } /* fix resends */ if(ringbuf_first(conn->buffer)) { NETCHUNKDATA *resend = (NETCHUNKDATA *)ringbuf_first(conn->buffer); /* check if we have some really old stuff laying around and abort if not acked */ 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)"); } else { /* resend packet if we havn't got it acked in 1 second */ if(now-resend->last_send_time > time_freq()) conn_resend_chunk(conn, resend); } } /* send keep alives if nothing has happend for 250ms */ if(conn->state == NET_CONNSTATE_ONLINE) { if(time_get()-conn->last_send_time > time_freq()/2) /* flush connection after 500ms 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); } else if(conn->state == NET_CONNSTATE_PENDING) { 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); } return 0; }