mirror of
https://github.com/w23/xash3d-fwgs
synced 2024-12-16 14:10:11 +01:00
1911 lines
46 KiB
C
1911 lines
46 KiB
C
/*
|
|
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];
|
|
}
|
|
}
|
|
}
|
|
|
|
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;
|
|
}
|