1911 lines
46 KiB
1911 lines
46 KiB
/* |
|
net_chan.c - network channel |
|
Copyright (C) 2008 Uncle Mike |
|
|
|
This program is free software: you can redistribute it and/or modify |
|
it under the terms of the GNU General Public License as published by |
|
the Free Software Foundation, either version 3 of the License, or |
|
(at your option) any later version. |
|
|
|
This program is distributed in the hope that it will be useful, |
|
but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
GNU General Public License for more details. |
|
*/ |
|
|
|
#include "common.h" |
|
#include "netchan.h" |
|
#include "xash3d_mathlib.h" |
|
#include "net_encode.h" |
|
#include "protocol.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.0f / 3.0f ) // how fast to converge flow estimates |
|
#define FLOW_INTERVAL 0.1 // don't compute more often than this |
|
#define MAX_RELIABLE_PAYLOAD 1400 // biggest packet that has frag and or reliable data |
|
|
|
// 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 |
|
MSG_Write* (&netchan->message, <data>). |
|
|
|
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_showdrop; |
|
convar_t *net_qport; |
|
|
|
int net_drop; |
|
netadr_t net_from; |
|
sizebuf_t net_message; |
|
static poolhandle_t net_mempool; |
|
byte net_message_buffer[NET_MAX_MESSAGE]; |
|
|
|
const char *ns_strings[NS_COUNT] = |
|
{ |
|
"Client", |
|
"Server", |
|
}; |
|
|
|
|
|
/* |
|
================================= |
|
|
|
NETWORK PACKET SPLIT |
|
|
|
================================= |
|
*/ |
|
|
|
/* |
|
====================== |
|
NetSplit_GetLong |
|
|
|
Collect fragmrnts with signature 0xFFFFFFFE to single packet |
|
return true when got full packet |
|
====================== |
|
*/ |
|
qboolean NetSplit_GetLong( netsplit_t *ns, netadr_t *from, byte *data, size_t *length ) |
|
{ |
|
netsplit_packet_t *packet = (netsplit_packet_t*)data; |
|
netsplit_chain_packet_t * p; |
|
|
|
//ASSERT( *length > NETSPLIT_HEADER_SIZE ); |
|
if( *length <= NETSPLIT_HEADER_SIZE ) return false; |
|
|
|
LittleLongSW(packet->id); |
|
LittleLongSW(packet->length); |
|
LittleLongSW(packet->part); |
|
|
|
p = &ns->packets[packet->id & NETSPLIT_BACKUP_MASK]; |
|
// Con_Reportf( S_NOTE "NetSplit_GetLong: packet from %s, id %d, index %d length %d\n", NET_AdrToString( *from ), (int)packet->id, (int)packet->index, (int)*length ); |
|
|
|
// no packets with this id received |
|
if( packet->id != p->id ) |
|
{ |
|
// warn if previous packet not received |
|
if( p->received < p->count ) |
|
{ |
|
UI_ShowConnectionWarning(); |
|
Con_Reportf( S_WARN "NetSplit_GetLong: lost packet %d\n", p->id ); |
|
} |
|
|
|
p->id = packet->id; |
|
p->count = packet->count; |
|
p->received = 0; |
|
memset( p->recieved_v, 0, 32 ); |
|
} |
|
|
|
// use bool vector to detect dup packets |
|
if( p->recieved_v[packet->index >> 5 ] & ( 1 << ( packet->index & 31 ) ) ) |
|
{ |
|
Con_Reportf( S_WARN "NetSplit_GetLong: dup packet from %s\n", NET_AdrToString( *from ) ); |
|
return false; |
|
} |
|
|
|
p->received++; |
|
|
|
// mark as received |
|
p->recieved_v[packet->index >> 5] |= 1 << ( packet->index & 31 ); |
|
|
|
// prevent overflow |
|
if( packet->part * packet->index > NET_MAX_PAYLOAD ) |
|
{ |
|
Con_Reportf( S_WARN "NetSplit_GetLong: packet out fo bounds from %s (part %d index %d)\n", NET_AdrToString( *from ), packet->part, packet->index ); |
|
return false; |
|
} |
|
|
|
if( packet->length > NET_MAX_PAYLOAD ) |
|
{ |
|
Con_Reportf( S_WARN "NetSplit_GetLong: packet out fo bounds from %s (length %d)\n", NET_AdrToString( *from ), packet->length ); |
|
return false; |
|
} |
|
|
|
memcpy( p->data + packet->part * packet->index, packet->data, *length - 18 ); |
|
|
|
// rewrite results of NET_GetPacket |
|
if( p->received == packet->count ) |
|
{ |
|
//ASSERT( packet->length % packet->part == (*length - NETSPLIT_HEADER_SIZE) % packet->part ); |
|
size_t len = packet->length; |
|
|
|
ns->total_received += len; |
|
|
|
ns->total_received_uncompressed += len; |
|
*length = len; |
|
|
|
// Con_Reportf( S_NOTE "NetSplit_GetLong: packet from %s, id %d received %d length %d\n", NET_AdrToString( *from ), (int)packet->id, (int)p->received, (int)packet->length ); |
|
memcpy( data, p->data, len ); |
|
return true; |
|
} |
|
else |
|
*length = NETSPLIT_HEADER_SIZE + packet->part; |
|
|
|
|
|
return false; |
|
} |
|
|
|
/* |
|
====================== |
|
NetSplit_SendLong |
|
|
|
Send parts that are less or equal maxpacket |
|
====================== |
|
*/ |
|
void NetSplit_SendLong( netsrc_t sock, size_t length, void *data, netadr_t to, unsigned int maxpacket, unsigned int id) |
|
{ |
|
netsplit_packet_t packet = {0}; |
|
unsigned int part = maxpacket - NETSPLIT_HEADER_SIZE; |
|
|
|
packet.signature = LittleLong(0xFFFFFFFE); |
|
packet.id = LittleLong(id); |
|
packet.length = LittleLong(length); |
|
packet.part = LittleLong(part); |
|
packet.count = ( length - 1 ) / part + 1; |
|
|
|
//Con_Reportf( S_NOTE "NetSplit_SendLong: packet to %s, count %d, length %d\n", NET_AdrToString( to ), (int)packet.count, (int)packet.length ); |
|
|
|
while( packet.index < packet.count ) |
|
{ |
|
unsigned int size = part; |
|
|
|
if( size > length ) |
|
size = length; |
|
|
|
length -= size; |
|
|
|
memcpy( packet.data, (const byte*)data + packet.index * part, size ); |
|
//Con_Reportf( S_NOTE "NetSplit_SendLong: packet to %s, id %d, index %d\n", NET_AdrToString( to ), (int)packet.id, (int)packet.index ); |
|
|
|
NET_SendPacket( sock, size + NETSPLIT_HEADER_SIZE, &packet, to ); |
|
packet.index++; |
|
} |
|
|
|
} |
|
|
|
/* |
|
=============== |
|
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_showdrop = Cvar_Get( "net_showdrop", "0", 0, "show packets that are dropped" ); |
|
net_qport = Cvar_Get( "net_qport", va( "%i", port ), FCVAR_READ_ONLY, "current quake netport" ); |
|
|
|
net_mempool = Mem_AllocPool( "Network Pool" ); |
|
|
|
MSG_InitMasks(); // initialize bit-masks |
|
} |
|
|
|
void Netchan_Shutdown( void ) |
|
{ |
|
Mem_FreePool( &net_mempool ); |
|
} |
|
|
|
void Netchan_ReportFlow( netchan_t *chan ) |
|
{ |
|
char incoming[64]; |
|
char outgoing[64]; |
|
|
|
if( CL_IsPlaybackDemo( )) |
|
return; |
|
|
|
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 )); |
|
|
|
Con_DPrintf( "Signon network traffic: %s from server, %s to server\n", incoming, outgoing ); |
|
} |
|
|
|
/* |
|
============== |
|
Netchan_IsLocal |
|
|
|
detect a loopback message |
|
============== |
|
*/ |
|
qboolean Netchan_IsLocal( netchan_t *chan ) |
|
{ |
|
if( !NET_IsActive() || NET_IsLocalAddress( chan->remote_address )) |
|
return true; |
|
return false; |
|
} |
|
|
|
/* |
|
============== |
|
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, void *client, int (*pfnBlockSize)(void *, fragsize_t mode )) |
|
{ |
|
Netchan_Clear( chan ); |
|
|
|
memset( chan, 0, sizeof( *chan )); |
|
|
|
chan->sock = sock; |
|
chan->remote_address = adr; |
|
chan->last_received = host.realtime; |
|
chan->connect_time = host.realtime; |
|
chan->incoming_sequence = 0; |
|
chan->outgoing_sequence = 1; |
|
chan->rate = DEFAULT_RATE; |
|
chan->qport = qport; |
|
chan->client = client; |
|
chan->pfnBlockSize = pfnBlockSize; |
|
|
|
MSG_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, qboolean choke ) |
|
{ |
|
// never choke loopback packets. |
|
if( !choke || ( !net_chokeloopback->value && NET_IsLocalAddress( chan->remote_address ) )) |
|
{ |
|
chan->cleartime = host.realtime; |
|
return true; |
|
} |
|
|
|
return chan->cleartime < host.realtime ? true : false; |
|
} |
|
|
|
/* |
|
============================== |
|
Netchan_UnlinkFragment |
|
|
|
============================== |
|
*/ |
|
void Netchan_UnlinkFragment( fragbuf_t *buf, fragbuf_t **list ) |
|
{ |
|
fragbuf_t *search; |
|
|
|
if( !list ) 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; |
|
} |
|
} |
|
|
|
/* |
|
============================== |
|
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->frag_message_buf ); |
|
Mem_Free( buf ); |
|
buf = n; |
|
} |
|
|
|
*ppbuf = NULL; |
|
} |
|
|
|
/* |
|
============================== |
|
Netchan_ClearFragments |
|
|
|
============================== |
|
*/ |
|
void Netchan_ClearFragments( netchan_t *chan ) |
|
{ |
|
fragbufwaiting_t *wait, *next; |
|
int i; |
|
|
|
for( i = 0; i < MAX_STREAMS; i++ ) |
|
{ |
|
wait = chan->waitlist[i]; |
|
|
|
while( wait ) |
|
{ |
|
next = wait->next; |
|
Netchan_ClearFragbufs( &wait->fragbufs ); |
|
Mem_Free( 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; |
|
} |
|
|
|
if( chan->tempbuffer ) |
|
{ |
|
Mem_Free( chan->tempbuffer ); |
|
chan->tempbuffer = NULL; |
|
} |
|
chan->tempbuffersize = 0; |
|
|
|
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 ) |
|
{ |
|
byte send_buf[MAX_PRINT_MSG]; |
|
sizebuf_t send; |
|
|
|
// write the packet header |
|
MSG_Init( &send, "SequencePacket", send_buf, sizeof( send_buf )); |
|
|
|
MSG_WriteLong( &send, NET_HEADER_OUTOFBANDPACKET ); // -1 sequence means out of band |
|
MSG_WriteBytes( &send, data, length ); |
|
|
|
if( !CL_IsPlaybackDemo( )) |
|
{ |
|
// send the datagram |
|
NET_SendPacket( net_socket, MSG_GetNumBytesWritten( &send ), MSG_GetData( &send ), adr ); |
|
} |
|
} |
|
|
|
/* |
|
=============== |
|
Netchan_OutOfBandPrint |
|
|
|
Sends a text message in an out-of-band datagram |
|
================ |
|
*/ |
|
void Netchan_OutOfBandPrint( int net_socket, netadr_t adr, const char *format, ... ) |
|
{ |
|
char string[MAX_PRINT_MSG]; |
|
va_list argptr; |
|
|
|
va_start( argptr, format ); |
|
Q_vsnprintf( string, sizeof( string ) - 1, format, argptr ); |
|
va_end( argptr ); |
|
|
|
Netchan_OutOfBand( net_socket, adr, Q_strlen( string ), (byte *)string ); |
|
} |
|
|
|
/* |
|
============================== |
|
Netchan_AllocFragbuf |
|
|
|
============================== |
|
*/ |
|
fragbuf_t *Netchan_AllocFragbuf( int fragment_size ) |
|
{ |
|
fragbuf_t *buf; |
|
|
|
buf = (fragbuf_t *)Mem_Calloc( net_mempool, sizeof( fragbuf_t )); |
|
buf->frag_message_buf = (byte *)Mem_Calloc( net_mempool, fragment_size ); |
|
MSG_Init( &buf->frag_message, "Frag Message", buf->frag_message_buf, fragment_size ); |
|
|
|
return buf; |
|
} |
|
|
|
/* |
|
============================== |
|
Netchan_AddFragbufToTail |
|
|
|
============================== |
|
*/ |
|
void Netchan_AddFragbufToTail( fragbufwaiting_t *wait, fragbuf_t *buf ) |
|
{ |
|
fragbuf_t *p; |
|
|
|
buf->next = NULL; |
|
wait->fragbufcount++; |
|
p = wait->fragbufs; |
|
|
|
if( p ) |
|
{ |
|
while( p->next ) |
|
p = p->next; |
|
p->next = buf; |
|
} |
|
else wait->fragbufs = buf; |
|
} |
|
|
|
/* |
|
============================== |
|
Netchan_UpdateFlow |
|
|
|
============================== |
|
*/ |
|
void Netchan_UpdateFlow( netchan_t *chan ) |
|
{ |
|
float faccumulatedtime = 0.0; |
|
int i, bytes = 0; |
|
int flow, start; |
|
|
|
if( !chan ) return; |
|
|
|
for( flow = 0; flow < 2; flow++ ) |
|
{ |
|
flow_t *pflow = &chan->flow[flow]; |
|
|
|
if(( host.realtime - pflow->nextcompute ) < FLOW_INTERVAL ) |
|
continue; |
|
|
|
pflow->nextcompute = host.realtime + FLOW_INTERVAL; |
|
start = pflow->current - 1; |
|
|
|
// compute data flow rate |
|
for( i = 0; i < MASK_LATENT; i++ ) |
|
{ |
|
flowstats_t *pprev = &pflow->stats[(start - i) & MASK_LATENT]; |
|
flowstats_t *pstat = &pflow->stats[(start - i - 1) & MASK_LATENT]; |
|
|
|
faccumulatedtime += ( pprev->time - pstat->time ); |
|
bytes += pstat->size; |
|
} |
|
|
|
pflow->kbytespersec = (faccumulatedtime == 0.0f) ? 0.0f : bytes / faccumulatedtime / 1024.0f; |
|
pflow->avgkbytespersec = pflow->avgkbytespersec * FLOW_AVG + pflow->kbytespersec * (1.0f - FLOW_AVG); |
|
} |
|
} |
|
|
|
/* |
|
============================== |
|
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; |
|
|
|
wait = chan->waitlist[i]; |
|
|
|
// nothing to queue? |
|
if( !wait ) continue; |
|
|
|
chan->waitlist[i] = wait->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_ |
|
|
|
============================== |
|
*/ |
|
static void Netchan_CreateFragments_( netchan_t *chan, sizebuf_t *msg ) |
|
{ |
|
fragbuf_t *buf; |
|
int chunksize; |
|
int remaining; |
|
int bytes, pos; |
|
int bufferid = 1; |
|
fragbufwaiting_t *wait, *p; |
|
|
|
if( MSG_GetNumBytesWritten( msg ) == 0 ) |
|
return; |
|
|
|
if( chan->pfnBlockSize != NULL ) |
|
chunksize = chan->pfnBlockSize( chan->client, FRAGSIZE_FRAG ); |
|
else chunksize = FRAGMENT_MAX_SIZE; // fallback |
|
|
|
wait = (fragbufwaiting_t *)Mem_Calloc( net_mempool, sizeof( fragbufwaiting_t )); |
|
|
|
if( !LZSS_IsCompressed( MSG_GetData( msg ))) |
|
{ |
|
uint uCompressedSize = 0; |
|
uint uSourceSize = MSG_GetNumBytesWritten( msg ); |
|
byte *pbOut = LZSS_Compress( msg->pData, uSourceSize, &uCompressedSize ); |
|
|
|
if( pbOut && uCompressedSize > 0 && uCompressedSize < uSourceSize ) |
|
{ |
|
Con_Reportf( "Compressing split packet (%d -> %d bytes)\n", uSourceSize, uCompressedSize ); |
|
memcpy( msg->pData, pbOut, uCompressedSize ); |
|
MSG_SeekToBit( msg, uCompressedSize << 3, SEEK_SET ); |
|
} |
|
if( pbOut ) free( pbOut ); |
|
} |
|
|
|
remaining = MSG_GetNumBytesWritten( msg ); |
|
pos = 0; // current position in bytes |
|
|
|
while( remaining > 0 ) |
|
{ |
|
bytes = Q_min( remaining, chunksize ); |
|
remaining -= bytes; |
|
|
|
buf = Netchan_AllocFragbuf( bytes ); |
|
buf->bufferid = bufferid++; |
|
|
|
// Copy in data |
|
MSG_Clear( &buf->frag_message ); |
|
MSG_WriteBits( &buf->frag_message, &msg->pData[pos], bytes << 3 ); |
|
|
|
Netchan_AddFragbufToTail( wait, buf ); |
|
pos += bytes; |
|
} |
|
|
|
// 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( netchan_t *chan, sizebuf_t *msg ) |
|
{ |
|
// always queue any pending reliable data ahead of the fragmentation buffer |
|
if( MSG_GetNumBytesWritten( &chan->message ) > 0 ) |
|
{ |
|
Netchan_CreateFragments_( chan, &chan->message ); |
|
MSG_Clear( &chan->message ); |
|
} |
|
|
|
Netchan_CreateFragments_( 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( NET_MAX_FRAGMENT ); |
|
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 += MSG_GetNumBytesWritten( &p->frag_message ); |
|
c++; |
|
|
|
id = FRAG_GETID( p->bufferid ); |
|
if( id != c ) |
|
{ |
|
if( chan->sock == NS_CLIENT ) |
|
{ |
|
Con_DPrintf( S_ERROR "Lost/dropped fragment would cause stall, retrying connection\n" ); |
|
Cbuf_AddText( "reconnect\n" ); |
|
} |
|
} |
|
p = p->next; |
|
} |
|
|
|
// received final message |
|
if( c == intotalbuffers ) |
|
chan->incomingready[stream] = true; |
|
} |
|
|
|
/* |
|
============================== |
|
Netchan_CreateFileFragmentsFromBuffer |
|
|
|
============================== |
|
*/ |
|
void Netchan_CreateFileFragmentsFromBuffer( netchan_t *chan, const 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; |
|
|
|
if( chan->pfnBlockSize != NULL ) |
|
chunksize = chan->pfnBlockSize( chan->client, FRAGSIZE_FRAG ); |
|
else chunksize = FRAGMENT_MAX_SIZE; // fallback |
|
|
|
if( !LZSS_IsCompressed( pbuf )) |
|
{ |
|
uint uCompressedSize = 0; |
|
byte *pbOut = LZSS_Compress( pbuf, size, &uCompressedSize ); |
|
|
|
if( pbOut && uCompressedSize > 0 && uCompressedSize < size ) |
|
{ |
|
Con_DPrintf( "Compressing filebuffer (%s -> %s)\n", Q_memprint( size ), Q_memprint( uCompressedSize )); |
|
memcpy( pbuf, pbOut, uCompressedSize ); |
|
size = uCompressedSize; |
|
} |
|
if( pbOut ) free( pbOut ); |
|
} |
|
|
|
wait = (fragbufwaiting_t *)Mem_Calloc( net_mempool, sizeof( fragbufwaiting_t )); |
|
remaining = size; |
|
pos = 0; |
|
|
|
while( remaining > 0 ) |
|
{ |
|
send = Q_min( remaining, chunksize ); |
|
|
|
buf = Netchan_AllocFragbuf( send ); |
|
buf->bufferid = bufferid++; |
|
|
|
// copy in data |
|
MSG_Clear( &buf->frag_message ); |
|
|
|
if( firstfragment ) |
|
{ |
|
// write filename |
|
MSG_WriteString( &buf->frag_message, filename ); |
|
|
|
// send a bit less on first package |
|
send -= MSG_GetNumBytesWritten( &buf->frag_message ); |
|
|
|
firstfragment = false; |
|
} |
|
|
|
buf->isbuffer = true; |
|
buf->isfile = true; |
|
buf->size = send; |
|
buf->foffset = pos; |
|
|
|
MSG_WriteBits( &buf->frag_message, pbuf + pos, send << 3 ); |
|
|
|
remaining -= send; |
|
pos += 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( netchan_t *chan, const char *filename ) |
|
{ |
|
int chunksize; |
|
int send, pos; |
|
int remaining; |
|
int bufferid = 1; |
|
fs_offset_t filesize = 0; |
|
char compressedfilename[MAX_OSPATH]; |
|
int compressedFileTime; |
|
int fileTime; |
|
qboolean firstfragment = true; |
|
qboolean bCompressed = false; |
|
fragbufwaiting_t *wait, *p; |
|
fragbuf_t *buf; |
|
|
|
if(( filesize = FS_FileSize( filename, false )) <= 0 ) |
|
{ |
|
Con_Printf( S_WARN "Unable to open %s for transfer\n", filename ); |
|
return 0; |
|
} |
|
|
|
if( chan->pfnBlockSize != NULL ) |
|
chunksize = chan->pfnBlockSize( chan->client, FRAGSIZE_FRAG ); |
|
else chunksize = FRAGMENT_MAX_SIZE; // fallback |
|
|
|
Q_strncpy( compressedfilename, filename, sizeof( compressedfilename )); |
|
COM_ReplaceExtension( compressedfilename, ".ztmp" ); |
|
compressedFileTime = FS_FileTime( compressedfilename, false ); |
|
fileTime = FS_FileTime( filename, false ); |
|
|
|
if( compressedFileTime >= fileTime ) |
|
{ |
|
// if compressed file already created and newer than source |
|
fs_offset_t compressedSize = FS_FileSize( compressedfilename, false ); |
|
if( compressedSize != -1 ) |
|
{ |
|
bCompressed = true; |
|
filesize = compressedSize; |
|
} |
|
} |
|
else |
|
{ |
|
uint uCompressedSize; |
|
byte *uncompressed; |
|
byte *compressed; |
|
|
|
uncompressed = FS_LoadFile( filename, &filesize, false ); |
|
compressed = LZSS_Compress( uncompressed, filesize, &uCompressedSize ); |
|
|
|
if( compressed ) |
|
{ |
|
Con_DPrintf( "compressed file %s (%s -> %s)\n", filename, Q_memprint( filesize ), Q_memprint( uCompressedSize )); |
|
FS_WriteFile( compressedfilename, compressed, uCompressedSize ); |
|
filesize = uCompressedSize; |
|
bCompressed = true; |
|
free( compressed ); |
|
} |
|
Mem_Free( uncompressed ); |
|
} |
|
|
|
wait = (fragbufwaiting_t *)Mem_Calloc( net_mempool, sizeof( fragbufwaiting_t )); |
|
remaining = filesize; |
|
pos = 0; |
|
|
|
while( remaining > 0 ) |
|
{ |
|
send = Q_min( remaining, chunksize ); |
|
|
|
buf = Netchan_AllocFragbuf( send ); |
|
buf->bufferid = bufferid++; |
|
|
|
// copy in data |
|
MSG_Clear( &buf->frag_message ); |
|
|
|
if( firstfragment ) |
|
{ |
|
// Write filename |
|
MSG_WriteString( &buf->frag_message, filename ); |
|
|
|
// Send a bit less on first package |
|
send -= MSG_GetNumBytesWritten( &buf->frag_message ); |
|
|
|
firstfragment = false; |
|
} |
|
|
|
buf->isfile = true; |
|
buf->size = send; |
|
buf->foffset = pos; |
|
buf->iscompressed = bCompressed; |
|
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; |
|
|
|
MSG_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, size_t *length ) |
|
{ |
|
size_t size = 0; |
|
fragbuf_t *p, *n; |
|
|
|
if( !chan->incomingready[FRAG_NORMAL_STREAM] ) |
|
return false; |
|
|
|
if( !chan->incomingbufs[FRAG_NORMAL_STREAM] ) |
|
{ |
|
chan->incomingready[FRAG_NORMAL_STREAM] = false; |
|
return false; |
|
} |
|
|
|
p = chan->incomingbufs[FRAG_NORMAL_STREAM]; |
|
|
|
MSG_Init( msg, "NetMessage", net_message_buffer, sizeof( net_message_buffer )); |
|
|
|
while( p ) |
|
{ |
|
n = p->next; |
|
|
|
// copy it in |
|
MSG_WriteBytes( msg, MSG_GetData( &p->frag_message ), MSG_GetNumBytesWritten( &p->frag_message )); |
|
size += MSG_GetNumBytesWritten( &p->frag_message ); |
|
|
|
Mem_Free( p ); |
|
p = n; |
|
} |
|
|
|
if( LZSS_IsCompressed( MSG_GetData( msg ))) |
|
{ |
|
uint uDecompressedLen = LZSS_GetActualSize( MSG_GetData( msg )); |
|
byte buf[NET_MAX_MESSAGE]; |
|
|
|
if( uDecompressedLen <= sizeof( buf )) |
|
{ |
|
size = LZSS_Decompress( MSG_GetData( msg ), buf ); |
|
memcpy( msg->pData, buf, size ); |
|
} |
|
else |
|
{ |
|
// g-cont. this should not happens |
|
Con_Printf( S_ERROR "buffer to small to decompress message\n" ); |
|
return false; |
|
} |
|
} |
|
|
|
chan->incomingbufs[FRAG_NORMAL_STREAM] = NULL; |
|
|
|
// reset flag |
|
chan->incomingready[FRAG_NORMAL_STREAM] = false; |
|
|
|
// tell about message size |
|
if( length ) *length = size; |
|
|
|
return true; |
|
} |
|
|
|
/* |
|
============================== |
|
Netchan_CopyFileFragments |
|
|
|
============================== |
|
*/ |
|
qboolean Netchan_CopyFileFragments( netchan_t *chan, sizebuf_t *msg ) |
|
{ |
|
char filename[MAX_OSPATH]; |
|
int nsize, pos; |
|
byte *buffer; |
|
fragbuf_t *p, *n; |
|
|
|
if( !chan->incomingready[FRAG_FILE_STREAM] ) |
|
return false; |
|
|
|
if( !chan->incomingbufs[FRAG_FILE_STREAM] ) |
|
{ |
|
chan->incomingready[FRAG_FILE_STREAM] = false; |
|
return false; |
|
} |
|
|
|
p = chan->incomingbufs[FRAG_FILE_STREAM]; |
|
|
|
MSG_Init( msg, "NetMessage", net_message_buffer, sizeof( net_message_buffer )); |
|
|
|
// copy in first chunk so we can get filename out |
|
MSG_WriteBytes( msg, MSG_GetData( &p->frag_message ), MSG_GetNumBytesWritten( &p->frag_message )); |
|
MSG_Clear( msg ); |
|
|
|
Q_strncpy( filename, MSG_ReadString( msg ), sizeof( filename )); |
|
|
|
if( !COM_CheckString( filename )) |
|
{ |
|
Con_Printf( S_ERROR "file fragment received with no filename\nFlushing input queue\n" ); |
|
Netchan_FlushIncoming( chan, FRAG_FILE_STREAM ); |
|
return false; |
|
} |
|
else if( filename[0] != '!' && !COM_IsSafeFileToDownload( filename )) |
|
{ |
|
Con_Printf( S_ERROR "file fragment received with bad path, ignoring\n" ); |
|
Netchan_FlushIncoming( chan, FRAG_FILE_STREAM ); |
|
return false; |
|
} |
|
|
|
if( filename[0] != '!' ) |
|
{ |
|
string temp_filename; |
|
Q_snprintf( temp_filename, sizeof( temp_filename ), "downloaded/%s", filename ); |
|
Q_strncpy( filename, temp_filename, sizeof( filename )); |
|
} |
|
|
|
Q_strncpy( chan->incomingfilename, filename, sizeof( chan->incomingfilename )); |
|
|
|
if( filename[0] != '!' && FS_FileExists( filename, false )) |
|
{ |
|
Con_Printf( S_ERROR "can't download %s, already exists\n", filename ); |
|
Netchan_FlushIncoming( chan, FRAG_FILE_STREAM ); |
|
return true; |
|
} |
|
|
|
// create file from buffers |
|
nsize = 0; |
|
while ( p ) |
|
{ |
|
nsize += MSG_GetNumBytesWritten( &p->frag_message ); // Size will include a bit of slop, oh well |
|
if( p == chan->incomingbufs[FRAG_FILE_STREAM] ) |
|
nsize -= MSG_GetNumBytesRead( msg ); |
|
p = p->next; |
|
} |
|
|
|
buffer = Mem_Calloc( net_mempool, nsize + 1 ); |
|
p = chan->incomingbufs[FRAG_FILE_STREAM]; |
|
pos = 0; |
|
|
|
while( p ) |
|
{ |
|
int cursize; |
|
|
|
n = p->next; |
|
|
|
cursize = MSG_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 -= MSG_GetNumBytesRead( msg ); |
|
memcpy( &buffer[pos], &p->frag_message.pData[MSG_GetNumBytesRead( msg )], cursize ); |
|
} |
|
else |
|
{ |
|
memcpy( &buffer[pos], p->frag_message.pData, cursize ); |
|
} |
|
|
|
pos += cursize; |
|
Mem_Free( p ); |
|
p = n; |
|
} |
|
|
|
if( LZSS_IsCompressed( buffer )) |
|
{ |
|
uint uncompressedSize = LZSS_GetActualSize( buffer ) + 1; |
|
byte *uncompressedBuffer = Mem_Calloc( net_mempool, uncompressedSize ); |
|
|
|
nsize = LZSS_Decompress( buffer, uncompressedBuffer ); |
|
Mem_Free( buffer ); |
|
buffer = uncompressedBuffer; |
|
} |
|
|
|
// customization files goes int tempbuffer |
|
if( filename[0] == '!' ) |
|
{ |
|
if( chan->tempbuffer ) |
|
Mem_Free( chan->tempbuffer ); |
|
chan->tempbuffer = buffer; |
|
chan->tempbuffersize = nsize; |
|
} |
|
else |
|
{ |
|
// g-cont. it's will be stored downloaded files directly into game folder |
|
FS_WriteFile( filename, buffer, nsize ); |
|
Mem_Free( buffer ); |
|
} |
|
|
|
// clear remnants |
|
MSG_Clear( msg ); |
|
|
|
chan->incomingbufs[FRAG_FILE_STREAM] = NULL; |
|
chan->incomingready[FRAG_FILE_STREAM] = false; |
|
|
|
return true; |
|
} |
|
|
|
qboolean Netchan_Validate( netchan_t *chan, sizebuf_t *sb, qboolean *frag_message, uint *fragid, int *frag_offset, int *frag_length ) |
|
{ |
|
int i, buffer, offset; |
|
int count, length; |
|
|
|
for( i = 0; i < MAX_STREAMS; i++ ) |
|
{ |
|
if( !frag_message[i] ) |
|
continue; |
|
|
|
buffer = FRAG_GETID( fragid[i] ); |
|
count = FRAG_GETCOUNT( fragid[i] ); |
|
offset = BitByte( frag_offset[i] ); |
|
length = BitByte( frag_length[i] ); |
|
|
|
if( buffer < 0 || buffer > NET_MAX_BUFFER_ID ) |
|
return false; |
|
|
|
if( count < 0 || count > NET_MAX_BUFFERS_COUNT ) |
|
return false; |
|
|
|
if( length < 0 || length > ( FRAGMENT_MAX_SIZE << 3 )) |
|
return false; |
|
|
|
if( offset < 0 || offset > ( FRAGMENT_MAX_SIZE << 3 )) |
|
return false; |
|
} |
|
|
|
return true; |
|
} |
|
|
|
/* |
|
============================== |
|
Netchan_UpdateProgress |
|
|
|
============================== |
|
*/ |
|
void Netchan_UpdateProgress( netchan_t *chan ) |
|
{ |
|
#if !XASH_DEDICATED |
|
fragbuf_t *p; |
|
int i, c = 0; |
|
int total = 0; |
|
float bestpercent = 0.0; |
|
|
|
if( host.downloadcount == 0 ) |
|
{ |
|
scr_download->value = -1.0f; |
|
host.downloadfile[0] = '\0'; |
|
} |
|
|
|
// 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 = 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 *)MSG_GetData( &p->frag_message ); |
|
out = sz; |
|
|
|
while( *in ) |
|
{ |
|
*out++ = *in++; |
|
len++; |
|
if( len > 128 ) |
|
break; |
|
} |
|
*out = '\0'; |
|
|
|
if( COM_CheckStringEmpty( sz ) && sz[0] != '!' ) |
|
Q_strncpy( host.downloadfile, sz, sizeof( host.downloadfile )); |
|
} |
|
} |
|
else if( chan->fragbufs[i] ) // Sending data |
|
{ |
|
if( chan->fragbufcount[i] ) |
|
{ |
|
float percent = 100.0f * (float)chan->fragbufs[i]->bufferid / (float)chan->fragbufcount[i]; |
|
|
|
if( percent > bestpercent ) |
|
bestpercent = percent; |
|
} |
|
} |
|
|
|
} |
|
|
|
scr_download->value = bestpercent; |
|
#endif // XASH_DEDICATED |
|
} |
|
|
|
/* |
|
=============== |
|
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 ) |
|
{ |
|
byte send_buf[NET_MAX_MESSAGE]; |
|
qboolean send_reliable_fragment; |
|
uint w1, w2, statId; |
|
qboolean send_reliable; |
|
sizebuf_t send; |
|
int i, j; |
|
float fRate; |
|
|
|
// check for message overflow |
|
if( MSG_CheckOverflow( &chan->message )) |
|
{ |
|
Con_Printf( S_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; |
|
|
|
// 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 = 0; |
|
|
|
// 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 = MSG_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] )) |
|
{ |
|
int maxsize = MAX_RELIABLE_PAYLOAD; |
|
send_from_regular = false; |
|
|
|
|
|
if( chan->pfnBlockSize ) |
|
maxsize = chan->pfnBlockSize( chan->client, FRAGSIZE_SPLIT ); |
|
if( maxsize == 0 ) |
|
maxsize = MAX_RELIABLE_PAYLOAD; |
|
|
|
// if the reliable buffer has gotten too big, queue it at the end of everything and clear out buffer |
|
if( MSG_GetNumBytesWritten( &chan->message ) + (((uint)length) >> 3) > maxsize ) |
|
{ |
|
Netchan_CreateFragments_( chan, &chan->message ); |
|
MSG_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 ) |
|
{ |
|
memcpy( chan->reliable_buf, chan->message_buf, MSG_GetNumBytesWritten( &chan->message )); |
|
chan->reliable_length = MSG_GetNumBitsWritten( &chan->message ); |
|
MSG_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 newpayloadsize; |
|
int fragment_size; |
|
|
|
// is there someting in the fragbuf? |
|
pbuf = chan->fragbufs[i]; |
|
fragment_size = 0; |
|
|
|
if( pbuf ) |
|
{ |
|
fragment_size = MSG_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 < NET_MAX_FRAGMENT ) |
|
{ |
|
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[NET_MAX_FRAGMENT]; |
|
file_t *file; |
|
|
|
if( pbuf->iscompressed ) |
|
{ |
|
char compressedfilename[MAX_OSPATH]; |
|
|
|
Q_strncpy( compressedfilename, pbuf->filename, sizeof( compressedfilename )); |
|
COM_ReplaceExtension( compressedfilename, ".ztmp" ); |
|
file = FS_Open( compressedfilename, "rb", false ); |
|
} |
|
else file = FS_Open( pbuf->filename, "rb", false ); |
|
|
|
FS_Seek( file, pbuf->foffset, SEEK_SET ); |
|
FS_Read( file, filebuffer, pbuf->size ); |
|
|
|
MSG_WriteBits( &pbuf->frag_message, filebuffer, pbuf->size << 3 ); |
|
FS_Close( file ); |
|
} |
|
|
|
// copy frag stuff on top of current buffer |
|
MSG_StartWriting( &temp, chan->reliable_buf, sizeof( chan->reliable_buf ), chan->reliable_length, -1 ); |
|
MSG_WriteBits( &temp, MSG_GetData( &pbuf->frag_message ), MSG_GetNumBitsWritten( &pbuf->frag_message )); |
|
chan->reliable_length += MSG_GetNumBitsWritten( &pbuf->frag_message ); |
|
chan->frag_length[i] = MSG_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]; |
|
} |
|
} |
|
} |
|
|
|
memset( send_buf, 0, sizeof( send_buf )); |
|
MSG_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 ) |
|
SetBits( w1, BIT( 30 )); |
|
|
|
chan->outgoing_sequence++; |
|
|
|
MSG_WriteLong( &send, w1 ); |
|
MSG_WriteLong( &send, w2 ); |
|
|
|
// send the qport if we are a client |
|
if( chan->sock == NS_CLIENT ) |
|
{ |
|
MSG_WriteWord( &send, Cvar_VariableInteger( "net_qport" )); |
|
} |
|
|
|
if( send_reliable && send_reliable_fragment ) |
|
{ |
|
for( i = 0; i < MAX_STREAMS; i++ ) |
|
{ |
|
if( chan->reliable_fragment[i] ) |
|
{ |
|
MSG_WriteByte( &send, 1 ); |
|
MSG_WriteLong( &send, chan->reliable_fragid[i] ); |
|
MSG_WriteLong( &send, chan->frag_startpos[i] ); |
|
MSG_WriteLong( &send, chan->frag_length[i] ); |
|
} |
|
else |
|
{ |
|
MSG_WriteByte( &send, 0 ); |
|
} |
|
} |
|
} |
|
|
|
// copy the reliable message to the packet first |
|
if( send_reliable ) |
|
{ |
|
MSG_WriteBits( &send, chan->reliable_buf, chan->reliable_length ); |
|
chan->last_reliable_sequence = chan->outgoing_sequence - 1; |
|
} |
|
|
|
if( length ) |
|
{ |
|
int maxsize = NET_MAX_MESSAGE; |
|
if( chan->pfnBlockSize ) |
|
maxsize = chan->pfnBlockSize( chan->client, FRAGSIZE_UNRELIABLE ); |
|
|
|
if( (( MSG_GetNumBytesWritten( &send ) + length ) >> 3) <= maxsize ) |
|
MSG_WriteBits( &send, data, length ); |
|
else Con_Printf( S_WARN "Netchan_Transmit: unreliable message overflow: %d\n", MSG_GetNumBytesWritten( &send ) ); |
|
} |
|
|
|
// deal with packets that are too small for some networks |
|
if( MSG_GetNumBytesWritten( &send ) < 16 && !NET_IsLocalAddress( chan->remote_address )) // packet too small for some networks |
|
{ |
|
// go ahead and pad a full 16 extra bytes -- this only happens during authentication / signon |
|
for( i = MSG_GetNumBytesWritten( &send ); i < 16; i++ ) |
|
{ |
|
if( chan->sock == NS_CLIENT ) |
|
MSG_BeginClientCmd( &send, clc_nop ); |
|
else if( chan->sock == NS_SERVER ) |
|
MSG_BeginServerCmd( &send, svc_nop ); |
|
else break; |
|
} |
|
} |
|
|
|
statId = chan->flow[FLOW_OUTGOING].current & MASK_LATENT; |
|
chan->flow[FLOW_OUTGOING].stats[statId].size = MSG_GetNumBytesWritten( &send ) + UDP_HEADER_SIZE; |
|
chan->flow[FLOW_OUTGOING].stats[statId].time = host.realtime; |
|
chan->flow[FLOW_OUTGOING].totalbytes += chan->flow[FLOW_OUTGOING].stats[statId].size; |
|
chan->flow[FLOW_OUTGOING].current++; |
|
|
|
Netchan_UpdateFlow( chan ); |
|
|
|
chan->total_sended += MSG_GetNumBytesWritten( &send ); |
|
|
|
// send the datagram |
|
if( !CL_IsPlaybackDemo( )) |
|
{ |
|
int splitsize = 0; |
|
if( chan->pfnBlockSize ) |
|
splitsize = chan->pfnBlockSize( chan->client, FRAGSIZE_SPLIT ); |
|
NET_SendPacketEx( chan->sock, MSG_GetNumBytesWritten( &send ), MSG_GetData( &send ), chan->remote_address, splitsize ); |
|
} |
|
|
|
if( SV_Active() && sv_lan.value && sv_lan_rate.value > 1000.0f ) |
|
fRate = 1.0f / sv_lan_rate.value; |
|
else fRate = 1.0f / chan->rate; |
|
|
|
if( chan->cleartime < host.realtime ) |
|
chan->cleartime = host.realtime; |
|
|
|
chan->cleartime += ( MSG_GetNumBytesWritten( &send ) + UDP_HEADER_SIZE ) * fRate; |
|
|
|
if( net_showpackets->value && net_showpackets->value != 2.0f ) |
|
{ |
|
Con_Printf( " %s --> sz=%i seq=%i ack=%i rel=%i tm=%f\n" |
|
, ns_strings[chan->sock] |
|
, MSG_GetNumBytesWritten( &send ) |
|
, ( chan->outgoing_sequence - 1 ) & 63 |
|
, chan->incoming_sequence & 63 |
|
, send_reliable ? 1 : 0 |
|
, (float)host.realtime ); |
|
} |
|
} |
|
|
|
/* |
|
=============== |
|
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; |
|
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; |
|
int i, qport, statId; |
|
|
|
if( !CL_IsPlaybackDemo() && !NET_CompareAdr( net_from, chan->remote_address )) |
|
return false; |
|
|
|
// get sequence numbers |
|
MSG_Clear( msg ); |
|
sequence = MSG_ReadLong( msg ); |
|
sequence_ack = MSG_ReadLong( msg ); |
|
|
|
// read the qport if we are a server |
|
if( chan->sock == NS_SERVER ) |
|
qport = MSG_ReadShort( msg ); |
|
|
|
reliable_message = sequence >> 31; |
|
reliable_ack = sequence_ack >> 31; |
|
|
|
message_contains_fragments = FBitSet( sequence, BIT( 30 )) ? true : false; |
|
|
|
if( message_contains_fragments ) |
|
{ |
|
for( i = 0; i < MAX_STREAMS; i++ ) |
|
{ |
|
if( MSG_ReadByte( msg )) |
|
{ |
|
frag_message[i] = true; |
|
fragid[i] = MSG_ReadLong( msg ); |
|
frag_offset[i] = MSG_ReadLong( msg ); |
|
frag_length[i] = MSG_ReadLong( msg ); |
|
} |
|
} |
|
|
|
if( !Netchan_Validate( chan, msg, frag_message, fragid, frag_offset, frag_length )) |
|
return false; |
|
} |
|
|
|
sequence &= ~BIT( 31 ); |
|
sequence &= ~BIT( 30 ); |
|
sequence_ack &= ~BIT( 30 ); |
|
sequence_ack &= ~BIT( 31 ); |
|
|
|
if( net_showpackets->value && net_showpackets->value != 3.0f ) |
|
{ |
|
Con_Printf( " %s <-- sz=%i seq=%i ack=%i rel=%i tm=%f\n" |
|
, ns_strings[chan->sock] |
|
, MSG_GetMaxBytes( msg ) |
|
, sequence & 63 |
|
, sequence_ack & 63 |
|
, reliable_message |
|
, host.realtime ); |
|
} |
|
|
|
// discard stale or duplicated packets |
|
if( sequence <= (uint)chan->incoming_sequence ) |
|
{ |
|
if( net_showdrop->value ) |
|
{ |
|
const char *adr = NET_AdrToString( chan->remote_address ); |
|
|
|
if( sequence == (uint)chan->incoming_sequence ) |
|
Con_Printf( "%s:duplicate packet %i at %i\n", adr, sequence, chan->incoming_sequence ); |
|
else Con_Printf( "%s:out of order packet %i at %i\n", adr, 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 && net_showdrop->value ) |
|
Con_Printf( "%s:dropped %i packets at %i\n", NET_AdrToString( chan->remote_address ), net_drop, 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( sequence_ack >= (uint)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 |
|
statId = chan->flow[FLOW_INCOMING].current & MASK_LATENT; |
|
chan->flow[FLOW_INCOMING].stats[statId].size = MSG_GetMaxBytes( msg ) + UDP_HEADER_SIZE; |
|
chan->flow[FLOW_INCOMING].stats[statId].time = host.realtime; |
|
chan->flow[FLOW_INCOMING].totalbytes += chan->flow[FLOW_INCOMING].stats[statId].size; |
|
chan->flow[FLOW_INCOMING].current++; |
|
|
|
Netchan_UpdateFlow( chan ); |
|
|
|
chan->total_received += MSG_GetMaxBytes( msg ); |
|
|
|
if( message_contains_fragments ) |
|
{ |
|
for( i = 0; i < MAX_STREAMS; i++ ) |
|
{ |
|
int j, inbufferid; |
|
int intotalbuffers; |
|
int oldpos, curbit; |
|
int numbitstoremove; |
|
fragbuf_t *pbuf; |
|
|
|
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 ) |
|
{ |
|
byte buffer[NET_MAX_FRAGMENT]; |
|
int bits, size; |
|
sizebuf_t temp; |
|
|
|
size = MSG_GetNumBitsRead( msg ) + frag_offset[i]; |
|
bits = frag_length[i]; |
|
|
|
// copy in data |
|
MSG_Clear( &pbuf->frag_message ); |
|
|
|
MSG_StartReading( &temp, msg->pData, MSG_GetMaxBytes( msg ), size, -1 ); |
|
MSG_ReadBits( &temp, buffer, bits ); |
|
MSG_WriteBits( &pbuf->frag_message, buffer, bits ); |
|
} |
|
|
|
// 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 = MSG_GetNumBitsRead( msg ); |
|
curbit = MSG_GetNumBitsRead( msg ) + frag_offset[i]; |
|
numbitstoremove = frag_length[i]; |
|
|
|
MSG_ExciseBits( msg, curbit, numbitstoremove ); |
|
MSG_SeekToBit( msg, oldpos, SEEK_SET ); |
|
|
|
for( j = i + 1; j < MAX_STREAMS; j++ ) |
|
frag_offset[j] -= frag_length[i]; |
|
} |
|
|
|
// is there anything left to process? |
|
if( MSG_GetNumBitsLeft( msg ) <= 0 ) |
|
{ |
|
return false; |
|
} |
|
} |
|
|
|
return true; |
|
}
|
|
|