264 lines
7.3 KiB
C++
Executable File
264 lines
7.3 KiB
C++
Executable File
//-----------------------------------------------------------------------------
|
|
// Torque Game Engine
|
|
// Copyright (C) GarageGames.com, Inc.
|
|
//-----------------------------------------------------------------------------
|
|
|
|
#include "core/dnet.h"
|
|
#include "console/console.h"
|
|
#include "core/bitStream.h"
|
|
#include "console/consoleTypes.h"
|
|
|
|
bool gLogToConsole = false;
|
|
|
|
S32 gNetBitsReceived = 0;
|
|
|
|
enum NetPacketType
|
|
{
|
|
DataPacket,
|
|
PingPacket,
|
|
AckPacket,
|
|
InvalidPacketType,
|
|
};
|
|
|
|
static const char *packetTypeNames[] =
|
|
{
|
|
"DataPacket",
|
|
"PingPacket",
|
|
"AckPacket",
|
|
};
|
|
|
|
//-----------------------------------------------------------------
|
|
//-----------------------------------------------------------------
|
|
//-----------------------------------------------------------------
|
|
ConsoleFunction(DNetSetLogging, void, 2, 2, "(bool enabled)")
|
|
{
|
|
argc;
|
|
gLogToConsole = dAtob(argv[1]);
|
|
}
|
|
|
|
ConnectionProtocol::ConnectionProtocol()
|
|
{
|
|
mLastSeqRecvd = 0;
|
|
mHighestAckedSeq = 0;
|
|
mLastSendSeq = 0; // start sending at 1
|
|
mAckMask = 0;
|
|
mLastRecvAckAck = 0;
|
|
}
|
|
void ConnectionProtocol::buildSendPacketHeader(BitStream *stream, S32 packetType)
|
|
{
|
|
S32 ackByteCount = ((mLastSeqRecvd - mLastRecvAckAck + 7) >> 3);
|
|
AssertFatal(ackByteCount <= 4, "Too few ack bytes!");
|
|
|
|
S32 headerSize = 3 + ackByteCount;
|
|
S32 datalen = 0;
|
|
|
|
if(packetType == DataPacket)
|
|
mLastSendSeq++;
|
|
|
|
stream->writeFlag(true);
|
|
stream->writeInt(mConnectSequence & 1, 1);
|
|
stream->writeInt(mLastSendSeq, 9);
|
|
stream->writeInt(mLastSeqRecvd, 9);
|
|
stream->writeInt(packetType, 2);
|
|
stream->writeInt(ackByteCount, 3);
|
|
stream->writeInt(mAckMask, ackByteCount * 8);
|
|
|
|
// if we're resending this header, we can't advance the
|
|
// sequence recieved (in case this packet drops and the prev one
|
|
// goes through)
|
|
|
|
if(gLogToConsole)
|
|
Con::printf("build hdr %d %d", mLastSendSeq, packetType);
|
|
|
|
if(packetType == DataPacket)
|
|
mLastSeqRecvdAtSend[mLastSendSeq & 0x1F] = mLastSeqRecvd;
|
|
}
|
|
|
|
void ConnectionProtocol::sendPingPacket()
|
|
{
|
|
U8 buffer[16];
|
|
BitStream bs(buffer, 16);
|
|
buildSendPacketHeader(&bs, PingPacket);
|
|
if(gLogToConsole)
|
|
Con::printf("send ping %d", mLastSendSeq);
|
|
|
|
sendPacket(&bs);
|
|
}
|
|
|
|
void ConnectionProtocol::sendAckPacket()
|
|
{
|
|
U8 buffer[16];
|
|
BitStream bs(buffer, 16);
|
|
buildSendPacketHeader(&bs, AckPacket);
|
|
if(gLogToConsole)
|
|
Con::printf("send ack %d", mLastSendSeq);
|
|
|
|
sendPacket(&bs);
|
|
}
|
|
|
|
// packets are read directly into the data portion of
|
|
// connection notify packets... makes the events easier to post into
|
|
// the system.
|
|
|
|
void ConnectionProtocol::processRawPacket(BitStream *pstream)
|
|
{
|
|
// read in the packet header:
|
|
|
|
// Fixed packet header: 3 bytes
|
|
//
|
|
// 1 bit game packet flag
|
|
// 1 bit connect sequence
|
|
// 9 bits packet seq number
|
|
// 9 bits ackstart seq number
|
|
// 2 bits packet type
|
|
// 2 bits ack byte count
|
|
//
|
|
// type is:
|
|
// 00 data packet
|
|
// 01 ping packet
|
|
// 02 ack packet
|
|
|
|
// next 1-4 bytes are ack flags
|
|
//
|
|
// header len is 4-9 bytes
|
|
// average case 4 byte header
|
|
|
|
gNetBitsReceived = pstream->getStreamSize();
|
|
|
|
pstream->readFlag(); // get rid of the game info packet bit
|
|
U32 pkConnectSeqBit = pstream->readInt(1);
|
|
U32 pkSequenceNumber = pstream->readInt(9);
|
|
U32 pkHighestAck = pstream->readInt(9);
|
|
U32 pkPacketType = pstream->readInt(2);
|
|
S32 pkAckByteCount = pstream->readInt(3);
|
|
|
|
// check connection sequence bit
|
|
if(pkConnectSeqBit != (mConnectSequence & 1))
|
|
return;
|
|
|
|
if(pkAckByteCount > 4 || pkPacketType >= InvalidPacketType)
|
|
return;
|
|
|
|
S32 pkAckMask = pstream->readInt(8 * pkAckByteCount);
|
|
|
|
// verify packet ordering and acking and stuff
|
|
// check if the 9-bit sequence is within the packet window
|
|
// (within 31 packets of the last received sequence number).
|
|
|
|
pkSequenceNumber |= (mLastSeqRecvd & 0xFFFFFE00);
|
|
// account for wrap around
|
|
if(pkSequenceNumber < mLastSeqRecvd)
|
|
pkSequenceNumber += 0x200;
|
|
|
|
if(pkSequenceNumber > mLastSeqRecvd + 31)
|
|
{
|
|
// the sequence number is outside the window... must be out of order
|
|
// discard.
|
|
return;
|
|
}
|
|
|
|
pkHighestAck |= (mHighestAckedSeq & 0xFFFFFE00);
|
|
// account for wrap around
|
|
|
|
if(pkHighestAck < mHighestAckedSeq)
|
|
pkHighestAck += 0x200;
|
|
|
|
if(pkHighestAck > mLastSendSeq)
|
|
{
|
|
// the ack number is outside the window... must be an out of order
|
|
// packet, discard.
|
|
return;
|
|
}
|
|
|
|
if(gLogToConsole)
|
|
{
|
|
for(U32 i = mLastSeqRecvd+1; i < pkSequenceNumber; i++)
|
|
Con::printf("Not recv %d", i);
|
|
Con::printf("Recv %d %s", pkSequenceNumber, packetTypeNames[pkPacketType]);
|
|
}
|
|
|
|
// shift up the ack mask by the packet difference
|
|
// this essentially nacks all the packets dropped
|
|
|
|
mAckMask <<= pkSequenceNumber - mLastSeqRecvd;
|
|
|
|
// if this packet is a data packet (i.e. not a ping packet or an ack packet), ack it
|
|
if(pkPacketType == DataPacket)
|
|
mAckMask |= 1;
|
|
|
|
// do all the notifies...
|
|
for(U32 i = mHighestAckedSeq+1; i <= pkHighestAck; i++)
|
|
{
|
|
bool packetTransmitSuccess = pkAckMask & (1 << (pkHighestAck - i));
|
|
handleNotify(packetTransmitSuccess);
|
|
if(gLogToConsole)
|
|
Con::printf("Ack %d %d", i, packetTransmitSuccess);
|
|
|
|
if(packetTransmitSuccess)
|
|
{
|
|
mLastRecvAckAck = mLastSeqRecvdAtSend[i & 0x1F];
|
|
if(!mConnectionEstablished)
|
|
{
|
|
mConnectionEstablished = true;
|
|
handleConnectionEstablished();
|
|
}
|
|
}
|
|
}
|
|
|
|
// the other side knows more about its window than we do.
|
|
if(pkSequenceNumber - mLastRecvAckAck > 32)
|
|
mLastRecvAckAck = pkSequenceNumber - 32;
|
|
|
|
mHighestAckedSeq = pkHighestAck;
|
|
|
|
// first things first...
|
|
// ackback any pings or accept connects
|
|
|
|
if(pkPacketType == PingPacket)
|
|
{
|
|
// send an ack to the other side
|
|
// the ack will have the same packet sequence as our last sent packet
|
|
// if the last packet we sent was the connection accepted packet
|
|
// we must resend that packet
|
|
sendAckPacket();
|
|
}
|
|
keepAlive(); // notification that the connection is ok
|
|
|
|
if(mLastSeqRecvd != pkSequenceNumber && pkPacketType == DataPacket)
|
|
handlePacket(pstream);
|
|
|
|
mLastSeqRecvd = pkSequenceNumber;
|
|
}
|
|
|
|
bool ConnectionProtocol::windowFull()
|
|
{
|
|
return mLastSendSeq - mHighestAckedSeq >= 30;
|
|
}
|
|
|
|
void ConnectionProtocol::writeDemoStartBlock(ResizeBitStream *stream)
|
|
{
|
|
for(U32 i = 0; i < 32; i++)
|
|
stream->write(mLastSeqRecvdAtSend[i]);
|
|
stream->write(mLastSeqRecvd);
|
|
stream->write(mHighestAckedSeq);
|
|
stream->write(mLastSendSeq);
|
|
stream->write(mAckMask);
|
|
stream->write(mConnectSequence);
|
|
stream->write(mLastRecvAckAck);
|
|
stream->write(mConnectionEstablished);
|
|
}
|
|
|
|
bool ConnectionProtocol::readDemoStartBlock(BitStream *stream)
|
|
{
|
|
for(U32 i = 0; i < 32; i++)
|
|
stream->read(&mLastSeqRecvdAtSend[i]);
|
|
stream->read(&mLastSeqRecvd);
|
|
stream->read(&mHighestAckedSeq);
|
|
stream->read(&mLastSendSeq);
|
|
stream->read(&mAckMask);
|
|
stream->read(&mConnectSequence);
|
|
stream->read(&mLastRecvAckAck);
|
|
stream->read(&mConnectionEstablished);
|
|
return true;
|
|
}
|