//======================================================================= // Copyright XashXT Group 2008 © // net_chan.c - network channel //======================================================================= #include "common.h" #include "netchan.h" #include "mathlib.h" #include "net_encode.h" #define MAKE_FRAGID( id, count ) ((( id & 0xffff ) << 16 ) | ( count & 0xffff )) #define FRAG_GETID( fragid ) (( fragid >> 16 ) & 0xffff ) #define FRAG_GETCOUNT( fragid ) ( fragid & 0xffff ) #define UDP_HEADER_SIZE 28 #define FLOW_AVG ( 2.0 / 3.0 ) // how fast to converge flow estimates #define FLOW_INTERVAL 0.1 // don't compute more often than this #define MAX_RELIABLE_PAYLOAD 1200 // biggest packet that has frag and or reliable data #define MAX_RESEND_PAYLOAD 1400 // biggest packet on a resend // forward declarations void Netchan_FlushIncoming( netchan_t *chan, int stream ); void Netchan_AddBufferToList( fragbuf_t **pplist, fragbuf_t *pbuf ); /* packet header ( size in bits ) ------------- 31 sequence 1 does this message contain a reliable payload 31 acknowledge sequence 1 acknowledge receipt of even/odd message 16 qport The remote connection never knows if it missed a reliable message, the local side detects that it has been dropped by seeing a sequence acknowledge higher thatn the last reliable sequence, but without the correct evon/odd bit for the reliable set. If the sender notices that a reliable message has been dropped, it will be retransmitted. It will not be retransmitted again until a message after the retransmit has been acknowledged and the reliable still failed to get there. if the sequence number is -1, the packet should be handled without a netcon The reliable message can be added to at any time by doing BF_Write* (&netchan->message, ). If the message buffer is overflowed, either by a single message, or by multiple frames worth piling up while the last reliable transmit goes unacknowledged, the netchan signals a fatal error. Reliable messages are allways placed first in a packet, then the unreliable message is included if there is sufficient room. To the receiver, there is no distinction between the reliable and unreliable parts of the message, they are just processed out as a single larger message. Illogical packet sequence numbers cause the packet to be dropped, but do not kill the connection. This, combined with the tight window of valid reliable acknowledgement numbers provides protection against malicious address spoofing. The qport field is a workaround for bad address translating routers that sometimes remap the client's source port on a packet during gameplay. If the base part of the net address matches and the qport matches, then the channel matches even if the IP port differs. The IP port should be updated to the new value before sending out any replies. If there is no information that needs to be transfered on a given frame, such as during the connection stage while waiting for the client to load, then a packet only needs to be delivered if there is something in the unacknowledged reliable */ convar_t *net_showpackets; convar_t *net_chokeloopback; convar_t *net_drawslider; convar_t *net_blocksize; convar_t *net_showdrop; convar_t *net_speeds; convar_t *net_qport; int net_drop; netadr_t net_from; sizebuf_t net_message; byte *net_mempool; byte net_message_buffer[NET_MAX_PAYLOAD]; /* =============== Netchan_Init =============== */ void Netchan_Init( void ) { int port; // pick a port value that should be nice and random port = Com_RandomLong( 1, 65535 ); net_showpackets = Cvar_Get ("net_showpackets", "0", 0, "show network packets" ); net_chokeloopback = Cvar_Get( "net_chokeloop", "0", 0, "apply bandwidth choke to loopback packets" ); net_drawslider = Cvar_Get( "net_drawslider", "0", CVAR_ARCHIVE, "draw completion slider during signon" ); net_blocksize = Cvar_Get( "net_blocksize", "1024", 0, "network file fragmentation block size" ); net_showdrop = Cvar_Get( "net_showdrop", "0", 0, "show packets that are dropped" ); net_speeds = Cvar_Get( "net_speeds", "0", CVAR_ARCHIVE, "show network packets" ); net_qport = Cvar_Get( "net_qport", va( "%i", port ), CVAR_INIT, "current quake netport" ); net_mempool = Mem_AllocPool( "Network Pool" ); Huff_Init (); // initialize huffman compression BF_InitMasks (); // initialize bit-masks } void Netchan_Shutdown( void ) { Mem_FreePool( &net_mempool ); } void Netchan_ReportFlow( netchan_t *chan ) { char incoming[CS_SIZE]; char outgoing[CS_SIZE]; ASSERT( chan != NULL ); Q_strcpy( incoming, Q_pretifymem((float)chan->flow[FLOW_INCOMING].totalbytes, 3 )); Q_strcpy( outgoing, Q_pretifymem((float)chan->flow[FLOW_OUTGOING].totalbytes, 3 )); MsgDev( D_INFO, "Signon network traffic: %s from server, %s to server\n", incoming, outgoing ); } /* ============== Netchan_Setup called to open a channel to a remote system ============== */ void Netchan_Setup( netsrc_t sock, netchan_t *chan, netadr_t adr, int qport ) { Netchan_Clear( chan ); Q_memset( chan, 0, sizeof( *chan )); chan->sock = sock; chan->remote_address = adr; chan->last_received = host.realtime; chan->incoming_sequence = 0; chan->outgoing_sequence = 1; chan->rate = DEFAULT_RATE; chan->compress = false; // work but low efficiency chan->qport = qport; BF_Init( &chan->message, "NetData", chan->message_buf, sizeof( chan->message_buf )); } /* ============================== Netchan_IncomingReady ============================== */ qboolean Netchan_IncomingReady( netchan_t *chan ) { int i; for( i = 0; i < MAX_STREAMS; i++ ) { if( chan->incomingready[i] ) { return true; } } return false; } /* =============== Netchan_CanPacket Returns true if the bandwidth choke isn't active ================ */ qboolean Netchan_CanPacket( netchan_t *chan ) { // never choke loopback packets. if( !net_chokeloopback->integer && NET_IsLocalAddress( chan->remote_address )) { chan->cleartime = host.realtime; return true; } if( chan->cleartime < host.realtime ) { return true; } return false; } /* ============================== Netchan_UnlinkFragment ============================== */ void Netchan_UnlinkFragment( fragbuf_t *buf, fragbuf_t **list ) { fragbuf_t *search; if( !list ) { MsgDev( D_WARN, "Netchan_UnlinkFragment: Asked to unlink fragment from empty list, ignored\n" ); return; } // at head of list if( buf == *list ) { // remove first element *list = buf->next; // destroy remnant Mem_Free( buf ); return; } search = *list; while( search->next ) { if( search->next == buf ) { search->next = buf->next; // destroy remnant Mem_Free( buf ); return; } search = search->next; } MsgDev( D_ERROR, "Netchan_UnlinkFragment: Couldn't find fragment\n" ); } /* ============================== Netchan_ClearFragbufs ============================== */ void Netchan_ClearFragbufs( fragbuf_t **ppbuf ) { fragbuf_t *buf, *n; if( !ppbuf ) return; // Throw away any that are sitting around buf = *ppbuf; while( buf ) { n = buf->next; Mem_Free( buf ); buf = n; } *ppbuf = NULL; } /* ============================== Netchan_ClearFragments ============================== */ void Netchan_ClearFragments( netchan_t *chan ) { fragbufwaiting_t *wait; int i; for( i = 0; i < MAX_STREAMS; i++ ) { wait = chan->waitlist[i]; while( wait ) { Netchan_ClearFragbufs( &wait->fragbufs ); wait = wait->next; } chan->waitlist[i] = NULL; Netchan_ClearFragbufs( &chan->fragbufs[i] ); Netchan_FlushIncoming( chan, i ); } } /* ============================== Netchan_Clear ============================== */ void Netchan_Clear( netchan_t *chan ) { int i; Netchan_ClearFragments( chan ); chan->cleartime = 0.0; chan->reliable_length = 0; for( i = 0; i < MAX_STREAMS; i++ ) { chan->reliable_fragid[i] = 0; chan->reliable_fragment[i] = 0; chan->fragbufcount[i] = 0; chan->frag_startpos[i] = 0; chan->frag_length[i] = 0; chan->incomingready[i] = false; } Q_memset( chan->flow, 0, sizeof( chan->flow )); } /* =============== Netchan_OutOfBand Sends an out-of-band datagram ================ */ void Netchan_OutOfBand( int net_socket, netadr_t adr, int length, byte *data ) { sizebuf_t send; byte send_buf[NET_MAX_PAYLOAD]; // write the packet header BF_Init( &send, "SequencePacket", send_buf, sizeof( send_buf )); BF_WriteLong( &send, -1 ); // -1 sequence means out of band BF_WriteBytes( &send, data, length ); // send the datagram NET_SendPacket( net_socket, BF_GetNumBytesWritten( &send ), BF_GetData( &send ), adr ); } /* =============== Netchan_OutOfBandPrint Sends a text message in an out-of-band datagram ================ */ void Netchan_OutOfBandPrint( int net_socket, netadr_t adr, char *format, ... ) { va_list argptr; char string[MAX_SYSPATH]; va_start( argptr, format ); Q_vsprintf( string, format, argptr ); va_end( argptr ); Netchan_OutOfBand( net_socket, adr, Q_strlen( string ), string ); } /* ============================== Netchan_AllocFragbuf ============================== */ fragbuf_t *Netchan_AllocFragbuf( void ) { fragbuf_t *buf; buf = (fragbuf_t *)Mem_Alloc( net_mempool, sizeof( fragbuf_t )); BF_Init( &buf->frag_message, "Frag Message", buf->frag_message_buf, sizeof( buf->frag_message_buf )); return buf; } /* ============================== Netchan_AddFragbufToTail ============================== */ void Netchan_AddFragbufToTail( fragbufwaiting_t *wait, fragbuf_t *buf ) { fragbuf_t *p; buf->next = NULL; wait->fragbufcount++; if( !wait->fragbufs ) { wait->fragbufs = buf; return; } p = wait->fragbufs; while( p->next ) { p = p->next; } p->next = buf; } /* ============================== Netchan_UpdateFlow ============================== */ void Netchan_UpdateFlow( netchan_t *chan ) { int flow, start; int i, bytes = 0; float faccumulatedtime = 0.0; flowstats_t *pstat, *pprev; flow_t *pflow; if( !chan ) return; for( flow = 0; flow < 2; flow++ ) { pflow = &chan->flow[flow]; if(( host.realtime - pflow->nextcompute ) < FLOW_INTERVAL ) continue; pflow->nextcompute = host.realtime + FLOW_INTERVAL; start = pflow->current - 1; pprev = &pflow->stats[start & ( MAX_LATENT - 1 )]; // compute data flow rate for( i = 1; i < MAX_LATENT / 2; i++ ) { // Most recent message then backward from there pstat = &pflow->stats[( start - i ) & ( MAX_LATENT - 1 )]; bytes += ( pstat->size ); faccumulatedtime += ( pprev->time - pstat->time ); pprev = pstat; } if( !faccumulatedtime ) { pflow->kbytespersec = 0.0; } else { pflow->kbytespersec = (float)bytes / faccumulatedtime; pflow->kbytespersec /= 1024.0; } pflow->avgkbytespersec = ( FLOW_AVG ) * pflow->avgkbytespersec + ( 1.0 - FLOW_AVG ) * pflow->kbytespersec; } } /* ============================== Netchan_FragSend Fragmentation buffer is full and user is prepared to send ============================== */ void Netchan_FragSend( netchan_t *chan ) { fragbufwaiting_t *wait; int i; if( !chan ) return; for( i = 0; i < MAX_STREAMS; i++ ) { // already something queued up, just leave in waitlist if( chan->fragbufs[i] ) { continue; } // nothing to queue? if( !chan->waitlist[i] ) { continue; } wait = chan->waitlist[i] ; chan->waitlist[i] = chan->waitlist[i]->next; wait->next = NULL; // copy in to fragbuf chan->fragbufs[i] = wait->fragbufs; chan->fragbufcount[i] = wait->fragbufcount; // throw away wait list Mem_Free( wait ); } } /* ============================== Netchan_AddBufferToList ============================== */ void Netchan_AddBufferToList( fragbuf_t **pplist, fragbuf_t *pbuf ) { // Find best slot fragbuf_t *pprev, *n; int id1, id2; pbuf->next = NULL; if( !pplist ) return; if( !*pplist ) { pbuf->next = *pplist; *pplist = pbuf; return; } pprev = *pplist; while( pprev->next ) { n = pprev->next; // next item in list id1 = FRAG_GETID( n->bufferid ); id2 = FRAG_GETID( pbuf->bufferid ); if( id1 > id2 ) { // insert here pbuf->next = n->next; pprev->next = pbuf; return; } pprev = pprev->next; } // insert at end pprev->next = pbuf; } /* ============================== Netchan_CreateFragments_ ============================== */ void Netchan_CreateFragments_( qboolean server, netchan_t *chan, sizebuf_t *msg ) { fragbuf_t *buf; int chunksize; int send, pos; int remaining; int bufferid = 1; fragbufwaiting_t *wait, *p; if( BF_GetNumBytesWritten( msg ) == 0 ) return; chunksize = bound( 16, net_blocksize->integer, 1400 ); wait = (fragbufwaiting_t *)Mem_Alloc( net_mempool, sizeof( fragbufwaiting_t )); remaining = BF_GetNumBytesWritten( msg ); pos = 0; while( remaining > 0 ) { send = min( remaining, chunksize ); remaining -= send; buf = Netchan_AllocFragbuf(); buf->bufferid = bufferid++; // Copy in data BF_Clear( &buf->frag_message ); BF_WriteBits( &buf->frag_message, msg->pData + pos, send << 3 ); pos += send; Netchan_AddFragbufToTail( wait, buf ); } // now add waiting list item to end of buffer queue if( !chan->waitlist[FRAG_NORMAL_STREAM] ) { chan->waitlist[FRAG_NORMAL_STREAM] = wait; } else { p = chan->waitlist[FRAG_NORMAL_STREAM]; while( p->next ) { p = p->next; } p->next = wait; } } /* ============================== Netchan_CreateFragments ============================== */ void Netchan_CreateFragments( qboolean server, netchan_t *chan, sizebuf_t *msg ) { // always queue any pending reliable data ahead of the fragmentation buffer if( BF_GetNumBytesWritten( &chan->message ) > 0 ) { Netchan_CreateFragments_( server, chan, &chan->message ); BF_Clear( &chan->message ); } Netchan_CreateFragments_( server, chan, msg ); } /* ============================== Netchan_FindBufferById ============================== */ fragbuf_t *Netchan_FindBufferById( fragbuf_t **pplist, int id, qboolean allocate ) { fragbuf_t *list = *pplist; fragbuf_t *pnewbuf; while( list ) { if( list->bufferid == id ) return list; list = list->next; } if( !allocate ) return NULL; // create new entry pnewbuf = Netchan_AllocFragbuf(); pnewbuf->bufferid = id; Netchan_AddBufferToList( pplist, pnewbuf ); return pnewbuf; } /* ============================== Netchan_CheckForCompletion ============================== */ void Netchan_CheckForCompletion( netchan_t *chan, int stream, int intotalbuffers ) { int c, id; int size; fragbuf_t *p; size = 0; c = 0; p = chan->incomingbufs[stream]; if( !p ) return; while( p ) { size += BF_GetNumBytesWritten( &p->frag_message ); c++; id = FRAG_GETID( p->bufferid ); if( id != c ) { if( chan->sock == NS_CLIENT ) { MsgDev( D_ERROR, "Lost/dropped fragment would cause stall, retrying connection\n" ); Cbuf_AddText( "retry\n" ); } } p = p->next; } // received final message if( c == intotalbuffers ) { chan->incomingready[stream] = true; MsgDev( D_NOTE, "\nincoming is complete %i bytes waiting\n", size ); } } /* ============================== Netchan_CreateFileFragmentsFromBuffer ============================== */ void Netchan_CreateFileFragmentsFromBuffer( qboolean server, netchan_t *chan, char *filename, byte *pbuf, int size ) { int chunksize; int send, pos; int remaining; int bufferid = 1; qboolean firstfragment = true; fragbufwaiting_t *wait, *p; fragbuf_t *buf; if( !size ) return; chunksize = bound( 16, net_blocksize->integer, 512 ); wait = ( fragbufwaiting_t * )Mem_Alloc( net_mempool, sizeof( fragbufwaiting_t )); remaining = size; pos = 0; while( remaining > 0 ) { send = min( remaining, chunksize ); buf = Netchan_AllocFragbuf(); buf->bufferid = bufferid++; // copy in data BF_Clear( &buf->frag_message ); if( firstfragment ) { firstfragment = false; // write filename BF_WriteString( &buf->frag_message, filename ); // send a bit less on first package send -= BF_GetNumBytesWritten( &buf->frag_message ); } buf->isbuffer = true; buf->isfile = true; buf->size = send; buf->foffset = pos; BF_WriteBits( &buf->frag_message, pbuf + pos, send << 3 ); pos += send; remaining -= send; Netchan_AddFragbufToTail( wait, buf ); } // now add waiting list item to end of buffer queue if( !chan->waitlist[FRAG_FILE_STREAM] ) { chan->waitlist[FRAG_FILE_STREAM] = wait; } else { p = chan->waitlist[FRAG_FILE_STREAM]; while( p->next ) { p = p->next; } p->next = wait; } } /* ============================== Netchan_CreateFileFragments ============================== */ int Netchan_CreateFileFragments( qboolean server, netchan_t *chan, const char *filename ) { int chunksize; int send, pos; int remaining; int bufferid = 1; int filesize = 0; qboolean firstfragment = true; fragbufwaiting_t *wait, *p; fragbuf_t *buf; chunksize = bound( 16, net_blocksize->integer, 512 ); filesize = FS_FileSize( filename, false ); if( filesize <= 0 ) { MsgDev( D_WARN, "Unable to open %s for transfer\n", filename ); return 0; } wait = (fragbufwaiting_t *)Mem_Alloc( net_mempool, sizeof( fragbufwaiting_t )); remaining = filesize; pos = 0; while( remaining > 0 ) { send = min( remaining, chunksize ); buf = Netchan_AllocFragbuf(); buf->bufferid = bufferid++; // copy in data BF_Clear( &buf->frag_message ); if( firstfragment ) { firstfragment = false; // Write filename BF_WriteString( &buf->frag_message, filename ); // Send a bit less on first package send -= BF_GetNumBytesWritten( &buf->frag_message ); } buf->isfile = true; buf->size = send; buf->foffset = pos; Q_strncpy( buf->filename, filename, sizeof( buf->filename )); pos += send; remaining -= send; Netchan_AddFragbufToTail( wait, buf ); } // now add waiting list item to end of buffer queue if( !chan->waitlist[FRAG_FILE_STREAM] ) { chan->waitlist[FRAG_FILE_STREAM] = wait; } else { p = chan->waitlist[FRAG_FILE_STREAM]; while( p->next ) { p = p->next; } p->next = wait; } return 1; } /* ============================== Netchan_FlushIncoming ============================== */ void Netchan_FlushIncoming( netchan_t *chan, int stream ) { fragbuf_t *p, *n; BF_Clear( &net_message ); p = chan->incomingbufs[ stream ]; while( p ) { n = p->next; Mem_Free( p ); p = n; } chan->incomingbufs[stream] = NULL; chan->incomingready[stream] = false; } /* ============================== Netchan_CopyNormalFragments ============================== */ qboolean Netchan_CopyNormalFragments( netchan_t *chan, sizebuf_t *msg ) { fragbuf_t *p, *n; if( !chan->incomingready[FRAG_NORMAL_STREAM] ) return false; if( !chan->incomingbufs[FRAG_NORMAL_STREAM] ) { MsgDev( D_ERROR, "Netchan_CopyNormalFragments: Called with no fragments readied\n" ); chan->incomingready[FRAG_NORMAL_STREAM] = false; return false; } p = chan->incomingbufs[FRAG_NORMAL_STREAM]; BF_Init( msg, "NetMessage", net_message_buffer, sizeof( net_message_buffer )); while( p ) { n = p->next; // copy it in BF_WriteBits( msg, BF_GetData( &p->frag_message ), BF_GetNumBitsWritten( &p->frag_message )); Mem_Free( p ); p = n; } chan->incomingbufs[FRAG_NORMAL_STREAM] = NULL; // reset flag chan->incomingready[FRAG_NORMAL_STREAM] = false; return true; } /* ============================== Netchan_CopyFileFragments ============================== */ qboolean Netchan_CopyFileFragments( netchan_t *chan, sizebuf_t *msg ) { fragbuf_t *p, *n; char filename[CS_SIZE]; int nsize; byte *buffer; int pos; if( !chan->incomingready[FRAG_FILE_STREAM] ) return false; if( !chan->incomingbufs[FRAG_FILE_STREAM] ) { MsgDev( D_WARN, "Netchan_CopyFileFragments: Called with no fragments readied\n" ); chan->incomingready[FRAG_FILE_STREAM] = false; return false; } p = chan->incomingbufs[FRAG_FILE_STREAM]; BF_Init( msg, "NetMessage", net_message_buffer, sizeof( net_message_buffer )); // copy in first chunk so we can get filename out BF_WriteBits( msg, BF_GetData( &p->frag_message ), BF_GetNumBitsWritten( &p->frag_message )); BF_SeekToBit( msg, 0 ); // rewind buffer Q_strncpy( filename, BF_ReadString( msg ), sizeof( filename )); if( Q_strlen( filename ) <= 0 ) { MsgDev( D_ERROR, "File fragment received with no filename\nFlushing input queue\n" ); // clear out bufs Netchan_FlushIncoming( chan, FRAG_FILE_STREAM ); return false; } else if( Q_strstr( filename, ".." )) { MsgDev( D_ERROR, "File fragment received with relative path, ignoring\n" ); // clear out bufs Netchan_FlushIncoming( chan, FRAG_FILE_STREAM ); return false; } Q_strncpy( chan->incomingfilename, filename, sizeof( chan->incomingfilename )); if( FS_FileExists( filename, false )) { MsgDev( D_ERROR, "Can't download %s, already exists\n", filename ); // clear out bufs Netchan_FlushIncoming( chan, FRAG_FILE_STREAM ); return true; } // create file from buffers nsize = 0; while ( p ) { nsize += BF_GetNumBytesWritten( &p->frag_message ); // Size will include a bit of slop, oh well if( p == chan->incomingbufs[FRAG_FILE_STREAM] ) { nsize -= BF_GetNumBytesRead( msg ); } p = p->next; } buffer = Mem_Alloc( net_mempool, nsize + 1 ); p = chan->incomingbufs[ FRAG_FILE_STREAM ]; pos = 0; while( p ) { int cursize; n = p->next; cursize = BF_GetNumBytesWritten( &p->frag_message ); // first message has the file name, don't write that into the data stream, // just write the rest of the actual data if( p == chan->incomingbufs[FRAG_FILE_STREAM] ) { // copy it in cursize -= BF_GetNumBytesRead( msg ); Q_memcpy( &buffer[pos], &p->frag_message.pData[BF_GetNumBytesRead( msg )], cursize ); } else { Q_memcpy( &buffer[pos], p->frag_message.pData, cursize ); } pos += cursize; Mem_Free( p ); p = n; } FS_WriteFile( filename, buffer, pos ); Mem_Free( buffer ); // clear remnants BF_Clear( msg ); chan->incomingbufs[FRAG_FILE_STREAM] = NULL; // reset flag chan->incomingready[FRAG_FILE_STREAM] = false; return true; } /* ============================== Netchan_UpdateProgress ============================== */ void Netchan_UpdateProgress( netchan_t *chan ) { fragbuf_t *p; int i, c = 0; int total = 0; float bestpercent = 0.0; if( scr_download->integer != -1 ) { Cvar_SetFloat( "scr_download", -1 ); } if ( net_drawslider->integer != 1 ) { // do show slider for file downloads. if( !chan->incomingbufs[FRAG_FILE_STREAM] ) { return; } } for( i = MAX_STREAMS - 1; i >= 0; i-- ) { // receiving data if( chan->incomingbufs[i] ) { p = chan->incomingbufs[i]; total = FRAG_GETCOUNT( p->bufferid ); while( p ) { c++; p = p->next; } if( total ) { float percent; percent = 100.0f * ( float )c / ( float )total; if( percent > bestpercent ) { bestpercent = percent; } } p = chan->incomingbufs[i]; if( i == FRAG_FILE_STREAM ) { char sz[MAX_SYSPATH]; char *in, *out; int len = 0; in = (char *)BF_GetData( &p->frag_message ); out = sz; while( *in ) { *out++ = *in++; len++; if( len > 128 ) break; } *out = '\0'; } } else if( chan->fragbufs[i] ) // Sending data { if( chan->fragbufcount[i] ) { float percent; percent = 100.0f * (float)chan->fragbufs[i]->bufferid / (float)chan->fragbufcount[i]; if( percent > bestpercent ) { bestpercent = percent; } } } } Cvar_SetFloat( "scr_download", bestpercent ); } /* =============== Netchan_TransmitBits tries to send an unreliable message to a connection, and handles the transmition / retransmition of the reliable messages. A 0 length will still generate a packet and deal with the reliable messages. ================ */ void Netchan_TransmitBits( netchan_t *chan, int length, byte *data ) { sizebuf_t send; byte send_buf[NET_MAX_MESSAGE]; qboolean send_reliable_fragment; qboolean send_resending = false; qboolean send_reliable; size_t size1, size2; uint w1, w2, hdr_size; int i, j; float fRate; // check for message overflow // check for message overflow if( BF_CheckOverflow( &chan->message )) { MsgDev( D_ERROR, "%s:outgoing message overflow\n", NET_AdrToString( chan->remote_address )); return; } // if the remote side dropped the last reliable message, resend it send_reliable = false; if( chan->incoming_acknowledged > chan->last_reliable_sequence && chan->incoming_reliable_acknowledged != chan->reliable_sequence ) { send_reliable = true; send_resending = true; } // A packet can have "reliable payload + frag payload + unreliable payload // frag payload can be a file chunk, if so, it needs to be parsed on the receiving end and reliable payload + unreliable payload need // to be passed on to the message queue. The processing routine needs to be able to handle the case where a message comes in and a file // transfer completes // if the reliable transmit buffer is empty, copy the current message out if( !chan->reliable_length ) { qboolean send_frag = false; fragbuf_t *pbuf; // will be true if we are active and should let chan->message get some bandwidth int send_from_frag[MAX_STREAMS] = { 0, 0 }; int send_from_regular = false; // if we have data in the waiting list(s) and we have cleared the current queue(s), then // push the waitlist(s) into the current queue(s) Netchan_FragSend( chan ); // sending regular payload send_from_regular = BF_GetNumBytesWritten( &chan->message ) ? 1 : 0; // check to see if we are sending a frag payload for( i = 0; i < MAX_STREAMS; i++ ) { if( chan->fragbufs[i] ) { send_from_frag[i] = 1; } } // stall reliable payloads if sending from frag buffer if( send_from_regular && ( send_from_frag[FRAG_NORMAL_STREAM] )) { send_from_regular = false; // if the reliable buffer has gotten too big, queue it at the end of everything and clear out buffer if( BF_GetNumBytesWritten( &chan->message ) > MAX_RELIABLE_PAYLOAD ) { Netchan_CreateFragments(( chan->sock == NS_SERVER ), chan, &chan->message ); BF_Clear( &chan->message ); } } // startpos will be zero if there is no regular payload for( i = 0; i < MAX_STREAMS; i++ ) { chan->frag_startpos[i] = 0; // assume no fragment is being sent chan->reliable_fragment[i] = 0; chan->reliable_fragid[i] = 0; chan->frag_length[i] = 0; if( send_from_frag[i] ) { send_frag = true; } } if( send_from_regular || send_frag ) { chan->reliable_sequence ^= 1; send_reliable = true; } if( send_from_regular ) { Q_memcpy( chan->reliable_buf, chan->message_buf, BF_GetNumBytesWritten( &chan->message )); chan->reliable_length = BF_GetNumBitsWritten( &chan->message ); BF_Clear( &chan->message ); // if we send fragments, this is where they'll start for( i = 0; i < MAX_STREAMS; i++ ) { chan->frag_startpos[i] = chan->reliable_length; } } for( i = 0; i < MAX_STREAMS; i++ ) { int fragment_size; int newpayloadsize; // is there someting in the fragbuf? pbuf = chan->fragbufs[i]; fragment_size = 0; if( pbuf ) { fragment_size = BF_GetNumBytesWritten( &pbuf->frag_message ); // files set size a bit differently. if( pbuf->isfile && !pbuf->isbuffer ) { fragment_size = pbuf->size; } } newpayloadsize = (( chan->reliable_length + ( fragment_size << 3 )) + 7 ) >> 3; // make sure we have enought space left if( send_from_frag[i] && pbuf && ( newpayloadsize < MAX_RELIABLE_PAYLOAD )) { sizebuf_t temp; // which buffer are we sending ? chan->reliable_fragid[i] = MAKE_FRAGID( pbuf->bufferid, chan->fragbufcount[i] ); // if it's not in-memory, then we'll need to copy it in frame the file handle. if( pbuf->isfile && !pbuf->isbuffer ) { byte filebuffer[2048]; file_t *hfile; hfile = FS_Open( pbuf->filename, "rb", false ); FS_Seek( hfile, pbuf->foffset, SEEK_SET ); FS_Read( hfile, filebuffer, pbuf->size ); BF_WriteBits( &pbuf->frag_message, filebuffer, pbuf->size << 3 ); FS_Close( hfile ); } // copy frag stuff on top of current buffer BF_StartWriting( &temp, chan->reliable_buf, sizeof( chan->reliable_buf ), chan->reliable_length, -1 ); BF_WriteBits( &temp, BF_GetData( &pbuf->frag_message ), BF_GetNumBitsWritten( &pbuf->frag_message )); chan->reliable_length += BF_GetNumBitsWritten( &pbuf->frag_message ); chan->frag_length[i] = BF_GetNumBitsWritten( &pbuf->frag_message ); // unlink pbuf Netchan_UnlinkFragment( pbuf, &chan->fragbufs[i] ); chan->reliable_fragment[i] = 1; // offset the rest of the starting positions for( j = i + 1; j < MAX_STREAMS; j++ ) { chan->frag_startpos[j] += chan->frag_length[i]; } } } } BF_Init( &send, "NetSend", send_buf, sizeof( send_buf )); // prepare the packet header w1 = chan->outgoing_sequence | (send_reliable << 31); w2 = chan->incoming_sequence | (chan->incoming_reliable_sequence << 31); send_reliable_fragment = false; for( i = 0; i < MAX_STREAMS; i++ ) { if( chan->reliable_fragment[i] ) { send_reliable_fragment = true; break; } } if( send_reliable && send_reliable_fragment ) { w1 |= ( 1 << 30 ); } chan->outgoing_sequence++; chan->last_sent = host.realtime; BF_WriteLong( &send, w1 ); BF_WriteLong( &send, w2 ); // send the qport if we are a client if( chan->sock == NS_CLIENT ) { BF_WriteWord( &send, Cvar_VariableValue( "net_qport" )); } if( send_reliable && send_reliable_fragment ) { for( i = 0; i < MAX_STREAMS; i++ ) { if( chan->reliable_fragment[i] ) { BF_WriteByte( &send, 1 ); BF_WriteLong( &send, chan->reliable_fragid[i] ); BF_WriteLong( &send, chan->frag_startpos[i] ); BF_WriteLong( &send, chan->frag_length[i] ); } else { BF_WriteByte( &send, 0 ); } } } hdr_size = BF_GetNumBytesWritten( &send ); // copy the reliable message to the packet first if( send_reliable ) { BF_WriteBits( &send, chan->reliable_buf, chan->reliable_length ); chan->last_reliable_sequence = chan->outgoing_sequence - 1; } // is there room for the unreliable payload? if( BF_GetNumBitsLeft( &send ) >= length ) { BF_WriteBits( &send, data, length ); } else { MsgDev( D_WARN, "Netchan_Transmit: unreliable message overflow\n" ); } // deal with packets that are too small for some networks if( BF_GetNumBytesWritten( &send ) < 16 ) // packet too small for some networks { int i; // go ahead and pad a full 16 extra bytes -- this only happens during authentication / signon for( i = BF_GetNumBytesWritten( &send ); i < 16; i++ ) { // NOTE: that the server can parse svc_nop, too. BF_WriteByte( &send, 1 ); } } chan->flow[FLOW_OUTGOING].stats[chan->flow[FLOW_OUTGOING].current & ( MAX_LATENT-1 )].size = BF_GetNumBytesWritten( &send ) + UDP_HEADER_SIZE; chan->flow[FLOW_OUTGOING].stats[chan->flow[FLOW_OUTGOING].current & ( MAX_LATENT-1 )].time = host.realtime; chan->flow[FLOW_OUTGOING].totalbytes += ( BF_GetNumBytesWritten( &send ) + UDP_HEADER_SIZE ); chan->flow[FLOW_OUTGOING].current++; Netchan_UpdateFlow( chan ); size1 = BF_GetNumBytesWritten( &send ); if( chan->compress ) Huff_CompressPacket( &send, hdr_size ); size2 = BF_GetNumBytesWritten( &send ); chan->total_sended += size2; chan->total_sended_uncompressed += size1; // send the datagram NET_SendPacket( chan->sock, BF_GetNumBytesWritten( &send ), BF_GetData( &send ), chan->remote_address ); fRate = 1.0f / chan->rate; if( chan->cleartime < host.realtime ) { chan->cleartime = host.realtime; } chan->cleartime += ( BF_GetNumBytesWritten( &send ) + UDP_HEADER_SIZE ) * fRate; if( net_showpackets->integer == 1 ) { char c; int mask = 63; c = ( chan->sock == NS_CLIENT ) ? 'c' : 's'; Msg( " %c --> sz=%i seq=%i ack=%i rel=%i tm=%f\n" , c , BF_GetNumBytesWritten( &send ) , ( chan->outgoing_sequence - 1 ) & mask , chan->incoming_sequence & mask , send_reliable ? 1 : 0 , (float)Sys_DoubleTime( )); } } /* =============== Netchan_Transmit tries to send an unreliable message to a connection, and handles the transmition / retransmition of the reliable messages. A 0 length will still generate a packet and deal with the reliable messages. ================ */ void Netchan_Transmit( netchan_t *chan, int lengthInBytes, byte *data ) { Netchan_TransmitBits( chan, lengthInBytes << 3, data ); } /* ================= Netchan_Process called when the current net_message is from remote_address modifies net_message so that it points to the packet payload ================= */ qboolean Netchan_Process( netchan_t *chan, sizebuf_t *msg ) { uint sequence, sequence_ack, hdr_size; uint reliable_ack, reliable_message; uint fragid[MAX_STREAMS] = { 0, 0 }; qboolean frag_message[MAX_STREAMS] = { false, false }; int frag_offset[MAX_STREAMS] = { 0, 0 }; int frag_length[MAX_STREAMS] = { 0, 0 }; qboolean message_contains_fragments; size_t size1, size2; int i, qport; if( !NET_CompareAdr( net_from, chan->remote_address )) { return false; } // get sequence numbers BF_Clear( msg ); sequence = BF_ReadLong( msg ); sequence_ack = BF_ReadLong( msg ); // read the qport if we are a server if( chan->sock == NS_SERVER ) { qport = BF_ReadShort( msg ); } reliable_message = sequence >> 31; reliable_ack = sequence_ack >> 31; message_contains_fragments = sequence & ( 1 << 30 ) ? true : false; if( message_contains_fragments ) { for( i = 0; i < MAX_STREAMS; i++ ) { if( BF_ReadByte( msg )) { frag_message[i] = true; fragid[i] = BF_ReadLong( msg ); frag_offset[i] = (int)BF_ReadLong( msg ); frag_length[i] = (int)BF_ReadLong( msg ); } } } sequence &= ~(1<<31); sequence &= ~(1<<30); sequence_ack &= ~(1<<31); if( net_showpackets->integer == 2 ) { char c; c = ( chan->sock == NS_CLIENT ) ? 'c' : 's'; Msg( " %c <-- sz=%i seq=%i ack=%i rel=%i tm=%f\n" , c , BF_GetMaxBytes( msg ) , sequence & 63 , sequence_ack & 63 , reliable_message , (float)Sys_DoubleTime( )); } // discard stale or duplicated packets if( sequence <= (uint)chan->incoming_sequence ) { if( net_showdrop->integer ) { if( sequence == (uint)chan->incoming_sequence ) { Msg( "%s:duplicate packet %i at %i\n" , NET_AdrToString( chan->remote_address ) , sequence , chan->incoming_sequence ); } else { Msg( "%s:out of order packet %i at %i\n" , NET_AdrToString( chan->remote_address ) , sequence , chan->incoming_sequence ); } } return false; } // dropped packets don't keep the message from being used net_drop = sequence - ( chan->incoming_sequence + 1 ); if( net_drop > 0 ) { chan->drop_count += 1; if( net_showdrop->integer ) { Msg( "%s:Dropped %i packets at %i\n" , NET_AdrToString( chan->remote_address ) , sequence - (chan->incoming_sequence + 1) , sequence ); } } // if the current outgoing reliable message has been acknowledged // clear the buffer to make way for the next if( reliable_ack == (uint)chan->reliable_sequence ) { // make sure we actually could have ack'd this message if( chan->incoming_acknowledged + 1 >= chan->last_reliable_sequence ) { chan->reliable_length = 0; // it has been received } } // if this message contains a reliable message, bump incoming_reliable_sequence chan->incoming_sequence = sequence; chan->incoming_acknowledged = sequence_ack; chan->incoming_reliable_acknowledged = reliable_ack; if( reliable_message ) { chan->incoming_reliable_sequence ^= 1; } chan->last_received = host.realtime; // Update data flow stats chan->flow[FLOW_INCOMING].stats[chan->flow[FLOW_INCOMING].current & ( MAX_LATENT-1 )].size = BF_GetMaxBytes( msg ) + UDP_HEADER_SIZE; chan->flow[FLOW_INCOMING].stats[chan->flow[FLOW_INCOMING].current & ( MAX_LATENT-1 )].time = host.realtime; chan->flow[FLOW_INCOMING].totalbytes += ( BF_GetMaxBytes( msg ) + UDP_HEADER_SIZE ); chan->flow[FLOW_INCOMING].current++; Netchan_UpdateFlow( chan ); hdr_size = BF_GetNumBytesRead( msg ); size1 = BF_GetMaxBytes( msg ); if( chan->compress ) Huff_DecompressPacket( msg, hdr_size ); size2 = BF_GetMaxBytes( msg ); chan->total_received += size1; chan->total_received_uncompressed += size2; chan->good_count += 1; if( message_contains_fragments ) { for( i = 0; i < MAX_STREAMS; i++ ) { fragbuf_t *pbuf; int j, inbufferid; int intotalbuffers; int oldpos, curbit; int numbitstoremove; if( !frag_message[i] ) continue; inbufferid = FRAG_GETID( fragid[i] ); intotalbuffers = FRAG_GETCOUNT( fragid[i] ); if( fragid[i] != 0 ) { pbuf = Netchan_FindBufferById( &chan->incomingbufs[i], fragid[i], true ); if( pbuf ) { int bits; sizebuf_t temp; byte buffer[2048]; bits = frag_length[i]; // copy in data BF_Clear( &pbuf->frag_message ); BF_StartReading( &temp, msg->pData, BF_GetMaxBytes( msg ), BF_GetNumBitsRead( msg ) + frag_offset[i], -1 ); BF_ReadBits( msg, buffer, bits ); BF_WriteBits( &pbuf->frag_message, buffer, bits ); } else { MsgDev( D_ERROR, "Netchan_Process: Couldn't allocate or find buffer %i\n", inbufferid ); } // count # of incoming bufs we've queued? are we done? Netchan_CheckForCompletion( chan, i, intotalbuffers ); } // rearrange incoming data to not have the frag stuff in the middle of it oldpos = BF_GetNumBitsRead( msg ); curbit = BF_GetNumBitsRead( msg ) + frag_offset[i]; numbitstoremove = frag_length[i]; BF_ExciseBits( msg, curbit, numbitstoremove ); BF_SeekToBit( msg, oldpos ); for( j = i + 1; j < MAX_STREAMS; j++ ) { frag_offset[j] -= frag_length[i]; } } // is there anything left to process? if( BF_GetNumBitsLeft( msg ) <= 0 ) { return false; } } return true; }