Initial commit

This commit is contained in:
2025-12-17 16:47:48 +00:00
commit 13813f3363
4964 changed files with 1079753 additions and 0 deletions

230
third-party/RakNet/CMakeLists.txt vendored Normal file
View File

@@ -0,0 +1,230 @@
add_library(RakNet STATIC
src/RakNet/AutopatcherPatchContext.hpp
src/RakNet/AutopatcherRepositoryInterface.hpp
src/RakNet/BitStream.cpp
src/RakNet/BitStream.hpp
src/RakNet/CCRakNetSlidingWindow.cpp
src/RakNet/CCRakNetSlidingWindow.hpp
src/RakNet/CCRakNetUDT.cpp
src/RakNet/CCRakNetUDT.hpp
src/RakNet/CheckSum.cpp
src/RakNet/CheckSum.hpp
src/RakNet/CommandParserInterface.cpp
src/RakNet/CommandParserInterface.hpp
src/RakNet/ConnectionGraph2.cpp
src/RakNet/ConnectionGraph2.hpp
src/RakNet/ConsoleServer.hpp
src/RakNet/DataCompressor.cpp
src/RakNet/DataCompressor.hpp
src/RakNet/DirectoryDeltaTransfer.cpp
src/RakNet/DirectoryDeltaTransfer.hpp
src/RakNet/DS_BinarySearchTree.hpp
src/RakNet/DS_BPlusTree.hpp
src/RakNet/DS_BytePool.cpp
src/RakNet/DS_BytePool.hpp
src/RakNet/DS_ByteQueue.cpp
src/RakNet/DS_ByteQueue.hpp
src/RakNet/DS_Heap.hpp
src/RakNet/DS_HuffmanEncodingTree.cpp
src/RakNet/DS_HuffmanEncodingTree.hpp
src/RakNet/DS_HuffmanEncodingTreeFactory.hpp
src/RakNet/DS_HuffmanEncodingTreeNode.hpp
src/RakNet/DS_LinkedList.hpp
src/RakNet/DS_List.hpp
src/RakNet/DS_Map.hpp
src/RakNet/DS_MemoryPool.hpp
src/RakNet/DS_Multilist.hpp
src/RakNet/DS_OrderedChannelHeap.hpp
src/RakNet/DS_OrderedList.hpp
src/RakNet/DS_Queue.hpp
src/RakNet/DS_QueueLinkedList.hpp
src/RakNet/DS_RangeList.hpp
src/RakNet/DS_Table.cpp
src/RakNet/DS_Table.hpp
src/RakNet/DS_ThreadsafeAllocatingQueue.hpp
src/RakNet/DS_Tree.hpp
src/RakNet/DS_WeightedGraph.hpp
src/RakNet/EmailSender.cpp
src/RakNet/EmailSender.hpp
src/RakNet/EncodeClassName.cpp
src/RakNet/EpochTimeToString.cpp
src/RakNet/EpochTimeToString.hpp
src/RakNet/Export.hpp
src/RakNet/FileList.cpp
src/RakNet/FileList.hpp
src/RakNet/FileListNodeContext.hpp
src/RakNet/FileListTransfer.cpp
src/RakNet/FileListTransfer.hpp
src/RakNet/FileListTransferCBInterface.hpp
src/RakNet/FileOperations.cpp
src/RakNet/FileOperations.hpp
src/RakNet/FormatString.cpp
src/RakNet/FormatString.hpp
src/RakNet/FullyConnectedMesh2.cpp
src/RakNet/FullyConnectedMesh2.hpp
src/RakNet/Getche.cpp
src/RakNet/Getche.hpp
src/RakNet/GetTime.cpp
src/RakNet/GetTime.hpp
src/RakNet/gettimeofday.cpp
src/RakNet/gettimeofday.hpp
src/RakNet/GridSectorizer.cpp
src/RakNet/GridSectorizer.hpp
src/RakNet/HTTPConnection.cpp
src/RakNet/HTTPConnection.hpp
src/RakNet/IncrementalReadInterface.cpp
src/RakNet/IncrementalReadInterface.hpp
src/RakNet/InternalPacket.hpp
src/RakNet/Itoa.cpp
src/RakNet/Itoa.hpp
src/RakNet/Kbhit.hpp
src/RakNet/LinuxStrings.cpp
src/RakNet/LinuxStrings.hpp
src/RakNet/LocklessTypes.cpp
src/RakNet/LocklessTypes.hpp
src/RakNet/LogCommandParser.cpp
src/RakNet/LogCommandParser.hpp
src/RakNet/MessageFilter.cpp
src/RakNet/MessageFilter.hpp
src/RakNet/MessageIdentifiers.hpp
src/RakNet/MTUSize.hpp
src/RakNet/NativeFeatureIncludes.hpp
src/RakNet/NativeFeatureIncludesOverrides.hpp
src/RakNet/NativeTypes.hpp
src/RakNet/NatPunchthroughClient.cpp
src/RakNet/NatPunchthroughClient.hpp
src/RakNet/NatPunchthroughServer.cpp
src/RakNet/NatPunchthroughServer.hpp
src/RakNet/NatTypeDetectionClient.cpp
src/RakNet/NatTypeDetectionClient.hpp
src/RakNet/NatTypeDetectionCommon.cpp
src/RakNet/NatTypeDetectionCommon.hpp
src/RakNet/NatTypeDetectionServer.cpp
src/RakNet/NatTypeDetectionServer.hpp
src/RakNet/NetworkIDManager.cpp
src/RakNet/NetworkIDManager.hpp
src/RakNet/NetworkIDObject.cpp
src/RakNet/NetworkIDObject.hpp
src/RakNet/PacketConsoleLogger.cpp
src/RakNet/PacketConsoleLogger.hpp
src/RakNet/PacketFileLogger.cpp
src/RakNet/PacketFileLogger.hpp
src/RakNet/PacketizedTCP.cpp
src/RakNet/PacketizedTCP.hpp
src/RakNet/PacketLogger.cpp
src/RakNet/PacketLogger.hpp
src/RakNet/PacketOutputWindowLogger.cpp
src/RakNet/PacketOutputWindowLogger.hpp
src/RakNet/PacketPool.hpp
src/RakNet/PacketPriority.hpp
src/RakNet/PluginInterface2.cpp
src/RakNet/PluginInterface2.hpp
src/RakNet/RakAlloca.hpp
src/RakNet/RakAssert.hpp
src/RakNet/RakMemoryOverride.cpp
src/RakNet/RakMemoryOverride.hpp
src/RakNet/RakNetCommandParser.cpp
src/RakNet/RakNetCommandParser.hpp
src/RakNet/RakNetDefines.hpp
src/RakNet/RakNetDefinesOverrides.hpp
src/RakNet/RakNetSmartPtr.hpp
src/RakNet/RakNetSocket.cpp
src/RakNet/RakNetSocket.hpp
src/RakNet/RakNetStatistics.cpp
src/RakNet/RakNetStatistics.hpp
src/RakNet/RakNetTime.hpp
src/RakNet/RakNetTransport2.cpp
src/RakNet/RakNetTransport2.hpp
src/RakNet/RakNetTypes.cpp
src/RakNet/RakNetTypes.hpp
src/RakNet/RakNetVersion.hpp
src/RakNet/RakPeer.cpp
src/RakNet/RakPeer.hpp
src/RakNet/RakPeerInterface.hpp
src/RakNet/RakSleep.cpp
src/RakNet/RakSleep.hpp
src/RakNet/RakString.cpp
src/RakNet/RakString.hpp
src/RakNet/RakThread.cpp
src/RakNet/RakThread.hpp
src/RakNet/Rand.cpp
src/RakNet/Rand.hpp
src/RakNet/rdlmalloc-options.hpp
src/RakNet/rdlmalloc.cpp
src/RakNet/rdlmalloc.hpp
src/RakNet/ReadyEvent.cpp
src/RakNet/ReadyEvent.hpp
src/RakNet/RefCountedObj.hpp
src/RakNet/ReliabilityLayer.cpp
src/RakNet/ReliabilityLayer.hpp
src/RakNet/ReplicaEnums.hpp
src/RakNet/ReplicaManager3.cpp
src/RakNet/ReplicaManager3.hpp
src/RakNet/Router2.cpp
src/RakNet/Router2.hpp
src/RakNet/RPC4Plugin.cpp
src/RakNet/RPC4Plugin.hpp
src/RakNet/SendToThread.cpp
src/RakNet/SendToThread.hpp
src/RakNet/SHA1.cpp
src/RakNet/SHA1.hpp
src/RakNet/SignaledEvent.cpp
src/RakNet/SignaledEvent.hpp
src/RakNet/SimpleMutex.cpp
src/RakNet/SimpleMutex.hpp
src/RakNet/SimpleTCPServer.hpp
src/RakNet/SingleProducerConsumer.hpp
src/RakNet/SocketIncludes.hpp
src/RakNet/SocketLayer.cpp
src/RakNet/SocketLayer.hpp
src/RakNet/SQLite3Plugin/sqlite3.hpp
src/RakNet/SQLite3Plugin/SQLite3ClientPlugin.cpp
src/RakNet/SQLite3Plugin/SQLite3ClientPlugin.hpp
src/RakNet/SQLite3Plugin/sqlite3ext.hpp
src/RakNet/SQLite3Plugin/SQLite3PluginCommon.cpp
src/RakNet/SQLite3Plugin/SQLite3PluginCommon.hpp
src/RakNet/SQLite3Plugin/SQLiteClientLoggerPlugin.cpp
src/RakNet/SQLite3Plugin/SQLiteClientLoggerPlugin.hpp
src/RakNet/SQLite3Plugin/SQLiteLoggerCommon.cpp
src/RakNet/SQLite3Plugin/SQLiteLoggerCommon.hpp
src/RakNet/StringCompressor.cpp
src/RakNet/StringCompressor.hpp
src/RakNet/StringTable.cpp
src/RakNet/StringTable.hpp
src/RakNet/SuperFastHash.cpp
src/RakNet/SuperFastHash.hpp
src/RakNet/TableSerializer.cpp
src/RakNet/TableSerializer.hpp
src/RakNet/TCPInterface.cpp
src/RakNet/TCPInterface.hpp
src/RakNet/TeamBalancer.cpp
src/RakNet/TeamBalancer.hpp
src/RakNet/TelnetTransport.cpp
src/RakNet/TelnetTransport.hpp
src/RakNet/ThreadPool.hpp
src/RakNet/ThreadsafePacketLogger.cpp
src/RakNet/ThreadsafePacketLogger.hpp
src/RakNet/TransportInterface.hpp
src/RakNet/UDPForwarder.cpp
src/RakNet/UDPForwarder.hpp
src/RakNet/UDPProxyClient.cpp
src/RakNet/UDPProxyClient.hpp
src/RakNet/UDPProxyCommon.hpp
src/RakNet/UDPProxyCoordinator.cpp
src/RakNet/UDPProxyCoordinator.hpp
src/RakNet/UDPProxyServer.cpp
src/RakNet/UDPProxyServer.hpp
src/RakNet/VariableDeltaSerializer.cpp
src/RakNet/VariableDeltaSerializer.hpp
src/RakNet/VariableListDeltaTracker.cpp
src/RakNet/VariableListDeltaTracker.hpp
src/RakNet/VariadicSQLParser.cpp
src/RakNet/VariadicSQLParser.hpp
src/RakNet/WindowsIncludes.hpp
src/RakNet/WSAStartupSingleton.cpp
src/RakNet/WSAStartupSingleton.hpp
src/RakNet/_FindFirst.cpp
src/RakNet/_FindFirst.hpp
)
target_include_directories(RakNet PUBLIC src PRIVATE ${ENGINE_DIR}/app/src ${ENGINE_DIR}/core/src)

View File

@@ -0,0 +1,23 @@
///
/// This file is part of RakNet Copyright 2003 Jenkins Software LLC
/// Usage of RakNet is subject to the appropriate license agreement.
///
#ifndef __AUTOPATCHER_PATCH_CONTEXT_H
#define __AUTOPATCHER_PATCH_CONTEXT_H
enum PatchContext
{
PC_HASH_1_WITH_PATCH,
PC_HASH_2_WITH_PATCH,
PC_WRITE_FILE,
PC_ERROR_FILE_WRITE_FAILURE,
PC_ERROR_PATCH_TARGET_MISSING,
PC_ERROR_PATCH_APPLICATION_FAILURE,
PC_ERROR_PATCH_RESULT_CHECKSUM_FAILURE,
PC_NOTICE_WILL_COPY_ON_RESTART,
PC_NOTICE_FILE_DOWNLOADED,
PC_NOTICE_FILE_DOWNLOADED_PATCH,
};
#endif

View File

@@ -0,0 +1,72 @@
///
/// \file AutopatcherRepositoryInterface.h
/// \brief An interface used by AutopatcherServer to get the data necessary to run an autopatcher.
///
/// This file is part of RakNet Copyright 2003 Jenkins Software LLC
/// Usage of RakNet is subject to the appropriate license agreement.
///
#ifndef __AUTOPATCHER_REPOSITORY_INTERFACE_H
#define __AUTOPATCHER_REPOSITORY_INTERFACE_H
#include "IncrementalReadInterface.hpp"
#include "SimpleMutex.hpp"
namespace RakNet
{
/// Forward declarations
class FileList;
class BitStream;
/// An interface used by AutopatcherServer to get the data necessary to run an autopatcher. This is up to you to implement for custom repository solutions.
class AutopatcherRepositoryInterface : public IncrementalReadInterface
{
public:
/// Get list of files added and deleted since a certain date. This is used by AutopatcherServer and not usually explicitly called.
/// \param[in] applicationName A null terminated string identifying the application
/// \param[out] addedFiles A list of the current versions of filenames with hashes as their data that were created after \a sinceData
/// \param[out] deletedFiles A list of the current versions of filenames that were deleted after \a sinceData
/// \param[in] An input date, in whatever format your repository uses
/// \param[out] currentDate The current server date, in whatever format your repository uses
/// \return True on success, false on failure.
virtual bool GetChangelistSinceDate(const char *applicationName, FileList *addedFiles, FileList *deletedFiles, double sinceDate)=0;
/// Get patches (or files) for every file in input, assuming that input has a hash for each of those files.
/// \param[in] applicationName A null terminated string identifying the application
/// \param[in] input A list of files with SHA1_LENGTH byte hashes to get from the database.
/// \param[out] patchList You should return list of files with either the filedata or the patch. This is a subset of \a input. The context data for each file will be either PC_WRITE_FILE (to just write the file) or PC_HASH_WITH_PATCH (to patch). If PC_HASH_WITH_PATCH, then the file contains a SHA1_LENGTH byte patch followed by the hash. The datalength is patchlength + SHA1_LENGTH
/// \param[out] currentDate The current server date, in whatever format your repository uses
/// \return True on success, false on failure.
virtual bool GetPatches(const char *applicationName, FileList *input, FileList *patchList)=0;
/// For the most recent update, return files that were patched, added, or deleted. For files that were patched, return both the patch in \a patchedFiles and the current version in \a updatedFiles
/// The cache will be used if the client last patched between \a priorRowPatchTime and \a mostRecentRowPatchTime
/// No files changed will be returned to the client if the client last patched after mostRecentRowPatchTime
/// \param[in,out] applicationName Name of the application to get patches for. If empty, uses the most recently updated application, and the string will be updated to reflect this name.
/// \param[out] patchedFiles Given the most recent update, if a file was patched, add it to this list. The context data for each file will be PC_HASH_WITH_PATCH. The first 4 bytes of data should be a hash of the file being patched. The second 4 bytes should be the hash of the file after the patch. The remaining bytes should be the patch itself.
/// \param[out] updatedFiles The current value of the file. List should have the same length and order as \a patchedFiles
/// \param[out] updatedFileHashes The hash of the current value of the file. List should have the same length and order as \a patchedFiles
/// \param[out] deletedFiles Files that were deleted in the last patch.
/// \param[out] priorRowPatchTime When the patch before the most recent patch took place. 0 means never.
/// \param[out] mostRecentRowPatchTime When the most recent patch took place. 0 means never.
/// \return true on success, false on failure
virtual bool GetMostRecentChangelistWithPatches(
RakNet::RakString &applicationName,
FileList *patchedFiles,
FileList *updatedFiles,
FileList *updatedFileHashes,
FileList *deletedFiles,
double *priorRowPatchTime,
double *mostRecentRowPatchTime)=0;
/// \return Whatever this function returns is sent from the AutopatcherServer to the AutopatcherClient when one of the above functions returns false.
virtual const char *GetLastError(void) const=0;
/// \return Passed to FileListTransfer::Send() as the _chunkSize parameter.
virtual const int GetIncrementalReadChunkSize(void) const=0;
};
} // namespace RakNet
#endif

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,332 @@
#include "CCRakNetSlidingWindow.hpp"
#if USE_SLIDING_WINDOW_CONGESTION_CONTROL==1
static const double UNSET_TIME_US=-1;
#if CC_TIME_TYPE_BYTES==4
static const CCTimeType SYN=10;
#else
static const CCTimeType SYN=10000;
#endif
#include "MTUSize.hpp"
#include <stdio.h>
#include <math.h>
#include <stdlib.h>
#include "RakAssert.hpp"
#include "RakAlloca.hpp"
using namespace RakNet;
// ****************************************************** PUBLIC METHODS ******************************************************
CCRakNetSlidingWindow::CCRakNetSlidingWindow()
{
}
// ----------------------------------------------------------------------------------------------------------------------------
CCRakNetSlidingWindow::~CCRakNetSlidingWindow()
{
}
// ----------------------------------------------------------------------------------------------------------------------------
void CCRakNetSlidingWindow::Init(CCTimeType curTime, uint32_t maxDatagramPayload)
{
(void) curTime;
RTT=UNSET_TIME_US;
MAXIMUM_MTU_INCLUDING_UDP_HEADER=maxDatagramPayload;
cwnd=maxDatagramPayload;
ssThresh=0.0;
oldestUnsentAck=0;
nextDatagramSequenceNumber=0;
nextCongestionControlBlock=0;
backoffThisBlock=speedUpThisBlock=false;
expectedNextSequenceNumber=0;
_isContinuousSend=false;
}
// ----------------------------------------------------------------------------------------------------------------------------
void CCRakNetSlidingWindow::Update(CCTimeType curTime, bool hasDataToSendOrResend)
{
(void) curTime;
(void) hasDataToSendOrResend;
}
// ----------------------------------------------------------------------------------------------------------------------------
int CCRakNetSlidingWindow::GetRetransmissionBandwidth(CCTimeType curTime, CCTimeType timeSinceLastTick, uint32_t unacknowledgedBytes, bool isContinuousSend)
{
(void) curTime;
(void) isContinuousSend;
(void) timeSinceLastTick;
return unacknowledgedBytes;
}
// ----------------------------------------------------------------------------------------------------------------------------
int CCRakNetSlidingWindow::GetTransmissionBandwidth(CCTimeType curTime, CCTimeType timeSinceLastTick, uint32_t unacknowledgedBytes, bool isContinuousSend)
{
(void) curTime;
(void) timeSinceLastTick;
_isContinuousSend=isContinuousSend;
if (unacknowledgedBytes<=cwnd)
return (int) (cwnd-unacknowledgedBytes);
else
return 0;
}
// ----------------------------------------------------------------------------------------------------------------------------
bool CCRakNetSlidingWindow::ShouldSendACKs(CCTimeType curTime, CCTimeType estimatedTimeToNextTick)
{
CCTimeType rto = GetSenderRTOForACK();
(void) estimatedTimeToNextTick;
// iphone crashes on comparison between double and int64 http://www.jenkinssoftware.com/forum/index.php?topic=2717.0
if (rto==static_cast<double>(UNSET_TIME_US))
{
// Unknown how long until the remote system will retransmit, so better send right away
return true;
}
return curTime >= oldestUnsentAck + SYN;
}
// ----------------------------------------------------------------------------------------------------------------------------
DatagramSequenceNumberType CCRakNetSlidingWindow::GetNextDatagramSequenceNumber(void)
{
return nextDatagramSequenceNumber;
}
// ----------------------------------------------------------------------------------------------------------------------------
DatagramSequenceNumberType CCRakNetSlidingWindow::GetAndIncrementNextDatagramSequenceNumber(void)
{
DatagramSequenceNumberType dsnt=nextDatagramSequenceNumber;
nextDatagramSequenceNumber++;
return dsnt;
}
// ----------------------------------------------------------------------------------------------------------------------------
void CCRakNetSlidingWindow::OnSendBytes(CCTimeType curTime, uint32_t numBytes)
{
(void) curTime;
(void) numBytes;
}
// ----------------------------------------------------------------------------------------------------------------------------
void CCRakNetSlidingWindow::OnGotPacketPair(DatagramSequenceNumberType datagramSequenceNumber, uint32_t sizeInBytes, CCTimeType curTime)
{
(void) curTime;
(void) sizeInBytes;
(void) datagramSequenceNumber;
}
// ----------------------------------------------------------------------------------------------------------------------------
bool CCRakNetSlidingWindow::OnGotPacket(DatagramSequenceNumberType datagramSequenceNumber, bool isContinuousSend, CCTimeType curTime, uint32_t sizeInBytes, uint32_t *skippedMessageCount)
{
(void) curTime;
(void) sizeInBytes;
(void) isContinuousSend;
if (oldestUnsentAck==0)
oldestUnsentAck=curTime;
if (datagramSequenceNumber==expectedNextSequenceNumber)
{
*skippedMessageCount=0;
expectedNextSequenceNumber=datagramSequenceNumber+(DatagramSequenceNumberType)1;
}
else if (GreaterThan(datagramSequenceNumber, expectedNextSequenceNumber))
{
*skippedMessageCount=datagramSequenceNumber-expectedNextSequenceNumber;
// Sanity check, just use timeout resend if this was really valid
if (*skippedMessageCount>1000)
{
// During testing, the nat punchthrough server got 51200 on the first packet. I have no idea where this comes from, but has happened twice
if (*skippedMessageCount>(uint32_t)50000)
return false;
*skippedMessageCount=1000;
}
expectedNextSequenceNumber=datagramSequenceNumber+(DatagramSequenceNumberType)1;
}
else
{
*skippedMessageCount=0;
}
return true;
}
// ----------------------------------------------------------------------------------------------------------------------------
void CCRakNetSlidingWindow::OnResend(CCTimeType curTime)
{
(void) curTime;
if (_isContinuousSend && backoffThisBlock==false && cwnd>MAXIMUM_MTU_INCLUDING_UDP_HEADER*2)
{
ssThresh=cwnd/2;
if (ssThresh<MAXIMUM_MTU_INCLUDING_UDP_HEADER)
ssThresh=MAXIMUM_MTU_INCLUDING_UDP_HEADER;
cwnd=MAXIMUM_MTU_INCLUDING_UDP_HEADER;
// Only backoff once per period
backoffThisBlock=true;
}
}
// ----------------------------------------------------------------------------------------------------------------------------
void CCRakNetSlidingWindow::OnNAK(CCTimeType curTime, DatagramSequenceNumberType nakSequenceNumber)
{
(void) nakSequenceNumber;
OnResend(curTime);
}
// ----------------------------------------------------------------------------------------------------------------------------
void CCRakNetSlidingWindow::OnAck(CCTimeType curTime, CCTimeType rtt, bool hasBAndAS, BytesPerMicrosecond B, BytesPerMicrosecond AS, double totalUserDataBytesAcked, bool isContinuousSend, DatagramSequenceNumberType sequenceNumber )
{
(void) B;
(void) totalUserDataBytesAcked;
(void) AS;
(void) hasBAndAS;
(void) curTime;
(void) rtt;
RTT=(double) rtt;
_isContinuousSend=isContinuousSend;
if (isContinuousSend==false)
return;
bool isNewCongestionControlPeriod;
isNewCongestionControlPeriod = GreaterThan(sequenceNumber, nextCongestionControlBlock);
if (isNewCongestionControlPeriod)
{
nextCongestionControlBlock=nextDatagramSequenceNumber;
backoffThisBlock=false;
speedUpThisBlock=false;
}
if (IsInSlowStart())
{
// if (isNewCongestionControlPeriod)
{
// Keep the number in range to avoid overflow
if (cwnd<10000000)
{
cwnd*=2;
if (cwnd>ssThresh && ssThresh!=0)
{
cwnd=ssThresh;
cwnd+=MAXIMUM_MTU_INCLUDING_UDP_HEADER*MAXIMUM_MTU_INCLUDING_UDP_HEADER/cwnd;
}
}
}
}
else
{
if (isNewCongestionControlPeriod)
cwnd+=MAXIMUM_MTU_INCLUDING_UDP_HEADER*MAXIMUM_MTU_INCLUDING_UDP_HEADER/cwnd;
}
}
// ----------------------------------------------------------------------------------------------------------------------------
void CCRakNetSlidingWindow::OnDuplicateAck( CCTimeType curTime, DatagramSequenceNumberType sequenceNumber )
{
(void) sequenceNumber;
OnResend(curTime);
}
// ----------------------------------------------------------------------------------------------------------------------------
void CCRakNetSlidingWindow::OnSendAckGetBAndAS(CCTimeType curTime, bool *hasBAndAS, BytesPerMicrosecond *B, BytesPerMicrosecond *AS)
{
(void) curTime;
(void) B;
(void) AS;
*hasBAndAS=false;
}
// ----------------------------------------------------------------------------------------------------------------------------
void CCRakNetSlidingWindow::OnSendAck(CCTimeType curTime, uint32_t numBytes)
{
(void) curTime;
(void) numBytes;
oldestUnsentAck=0;
}
// ----------------------------------------------------------------------------------------------------------------------------
void CCRakNetSlidingWindow::OnSendNACK(CCTimeType curTime, uint32_t numBytes)
{
(void) curTime;
(void) numBytes;
}
// ----------------------------------------------------------------------------------------------------------------------------
CCTimeType CCRakNetSlidingWindow::GetRTOForRetransmission(void) const
{
#if CC_TIME_TYPE_BYTES==4
const CCTimeType maxThreshold=2000;
const CCTimeType minThreshold=100;
#else
const CCTimeType maxThreshold=2000000;
const CCTimeType minThreshold=100000;
#endif
if (RTT==UNSET_TIME_US)
{
return maxThreshold;
}
if (RTT * 3 > maxThreshold)
return maxThreshold;
if (RTT * 3 < minThreshold)
return minThreshold;
return (CCTimeType) RTT * 3;
}
// ----------------------------------------------------------------------------------------------------------------------------
void CCRakNetSlidingWindow::SetMTU(uint32_t bytes)
{
MAXIMUM_MTU_INCLUDING_UDP_HEADER=bytes;
}
// ----------------------------------------------------------------------------------------------------------------------------
uint32_t CCRakNetSlidingWindow::GetMTU(void) const
{
return MAXIMUM_MTU_INCLUDING_UDP_HEADER;
}
// ----------------------------------------------------------------------------------------------------------------------------
BytesPerMicrosecond CCRakNetSlidingWindow::GetLocalReceiveRate(CCTimeType currentTime) const
{
(void) currentTime;
return 0; // TODO
}
// ----------------------------------------------------------------------------------------------------------------------------
double CCRakNetSlidingWindow::GetRTT(void) const
{
if (RTT==UNSET_TIME_US)
return 0.0;
return RTT;
}
// ----------------------------------------------------------------------------------------------------------------------------
bool CCRakNetSlidingWindow::GreaterThan(DatagramSequenceNumberType a, DatagramSequenceNumberType b)
{
// a > b?
const DatagramSequenceNumberType halfSpan =(DatagramSequenceNumberType) (((DatagramSequenceNumberType)(const uint32_t)-1)/(DatagramSequenceNumberType)2);
return b!=a && b-a>halfSpan;
}
// ----------------------------------------------------------------------------------------------------------------------------
bool CCRakNetSlidingWindow::LessThan(DatagramSequenceNumberType a, DatagramSequenceNumberType b)
{
// a < b?
const DatagramSequenceNumberType halfSpan = ((DatagramSequenceNumberType)(const uint32_t)-1)/(DatagramSequenceNumberType)2;
return b!=a && b-a<halfSpan;
}
// ----------------------------------------------------------------------------------------------------------------------------
uint64_t CCRakNetSlidingWindow::GetBytesPerSecondLimitByCongestionControl(void) const
{
return 0; // TODO
}
// ----------------------------------------------------------------------------------------------------------------------------
CCTimeType CCRakNetSlidingWindow::GetSenderRTOForACK(void) const
{
if (RTT==UNSET_TIME_US)
return (CCTimeType) UNSET_TIME_US;
return (CCTimeType)(RTT + SYN);
}
// ----------------------------------------------------------------------------------------------------------------------------
bool CCRakNetSlidingWindow::IsInSlowStart(void) const
{
return cwnd <= ssThresh || ssThresh==0;
}
// ----------------------------------------------------------------------------------------------------------------------------
#endif

View File

@@ -0,0 +1,209 @@
/*
http://www.ssfnet.org/Exchange/tcp/tcpTutorialNotes.html
cwnd=max bytes allowed on wire at once
Start:
cwnd=mtu
ssthresh=unlimited
Slow start:
On ack cwnd*=2
congestion avoidance:
On ack during new period
cwnd+=mtu*mtu/cwnd
on loss or duplicate ack during period:
sshtresh=cwnd/2
cwnd=MTU
This reenters slow start
If cwnd < ssthresh, then use slow start
else use congestion avoidance
*/
#include "RakNetDefines.hpp"
#if USE_SLIDING_WINDOW_CONGESTION_CONTROL==1
#ifndef __CONGESTION_CONTROL_SLIDING_WINDOW_H
#define __CONGESTION_CONTROL_SLIDING_WINDOW_H
#include "NativeTypes.hpp"
#include "RakNetTime.hpp"
#include "RakNetTypes.hpp"
#include "DS_Queue.hpp"
/// Sizeof an UDP header in byte
#define UDP_HEADER_SIZE 28
#define CC_DEBUG_PRINTF_1(x)
#define CC_DEBUG_PRINTF_2(x,y)
#define CC_DEBUG_PRINTF_3(x,y,z)
#define CC_DEBUG_PRINTF_4(x,y,z,a)
#define CC_DEBUG_PRINTF_5(x,y,z,a,b)
//#define CC_DEBUG_PRINTF_1(x) printf(x)
//#define CC_DEBUG_PRINTF_2(x,y) printf(x,y)
//#define CC_DEBUG_PRINTF_3(x,y,z) printf(x,y,z)
//#define CC_DEBUG_PRINTF_4(x,y,z,a) printf(x,y,z,a)
//#define CC_DEBUG_PRINTF_5(x,y,z,a,b) printf(x,y,z,a,b)
/// Set to 4 if you are using the iPod Touch TG. See http://www.jenkinssoftware.com/forum/index.php?topic=2717.0
#define CC_TIME_TYPE_BYTES 8
typedef RakNet::TimeUS CCTimeType;
typedef RakNet::uint24_t DatagramSequenceNumberType;
typedef double BytesPerMicrosecond;
typedef double BytesPerSecond;
typedef double MicrosecondsPerByte;
namespace RakNet
{
class CCRakNetSlidingWindow
{
public:
CCRakNetSlidingWindow();
~CCRakNetSlidingWindow();
/// Reset all variables to their initial states, for a new connection
void Init(CCTimeType curTime, uint32_t maxDatagramPayload);
/// Update over time
void Update(CCTimeType curTime, bool hasDataToSendOrResend);
int GetRetransmissionBandwidth(CCTimeType curTime, CCTimeType timeSinceLastTick, uint32_t unacknowledgedBytes, bool isContinuousSend);
int GetTransmissionBandwidth(CCTimeType curTime, CCTimeType timeSinceLastTick, uint32_t unacknowledgedBytes, bool isContinuousSend);
/// Acks do not have to be sent immediately. Instead, they can be buffered up such that groups of acks are sent at a time
/// This reduces overall bandwidth usage
/// How long they can be buffered depends on the retransmit time of the sender
/// Should call once per update tick, and send if needed
bool ShouldSendACKs(CCTimeType curTime, CCTimeType estimatedTimeToNextTick);
/// Every data packet sent must contain a sequence number
/// Call this function to get it. The sequence number is passed into OnGotPacketPair()
DatagramSequenceNumberType GetAndIncrementNextDatagramSequenceNumber(void);
DatagramSequenceNumberType GetNextDatagramSequenceNumber(void);
/// Call this when you send packets
/// Every 15th and 16th packets should be sent as a packet pair if possible
/// When packets marked as a packet pair arrive, pass to OnGotPacketPair()
/// When any packets arrive, (additionally) pass to OnGotPacket
/// Packets should contain our system time, so we can pass rtt to OnNonDuplicateAck()
void OnSendBytes(CCTimeType curTime, uint32_t numBytes);
/// Call this when you get a packet pair
void OnGotPacketPair(DatagramSequenceNumberType datagramSequenceNumber, uint32_t sizeInBytes, CCTimeType curTime);
/// Call this when you get a packet (including packet pairs)
/// If the DatagramSequenceNumberType is out of order, skippedMessageCount will be non-zero
/// In that case, send a NAK for every sequence number up to that count
bool OnGotPacket(DatagramSequenceNumberType datagramSequenceNumber, bool isContinuousSend, CCTimeType curTime, uint32_t sizeInBytes, uint32_t *skippedMessageCount);
/// Call when you get a NAK, with the sequence number of the lost message
/// Affects the congestion control
void OnResend(CCTimeType curTime);
void OnNAK(CCTimeType curTime, DatagramSequenceNumberType nakSequenceNumber);
/// Call this when an ACK arrives.
/// hasBAndAS are possibly written with the ack, see OnSendAck()
/// B and AS are used in the calculations in UpdateWindowSizeAndAckOnAckPerSyn
/// B and AS are updated at most once per SYN
void OnAck(CCTimeType curTime, CCTimeType rtt, bool hasBAndAS, BytesPerMicrosecond B, BytesPerMicrosecond AS, double totalUserDataBytesAcked, bool isContinuousSend, DatagramSequenceNumberType sequenceNumber );
void OnDuplicateAck( CCTimeType curTime, DatagramSequenceNumberType sequenceNumber );
/// Call when you send an ack, to see if the ack should have the B and AS parameters transmitted
/// Call before calling OnSendAck()
void OnSendAckGetBAndAS(CCTimeType curTime, bool *hasBAndAS, BytesPerMicrosecond *B, BytesPerMicrosecond *AS);
/// Call when we send an ack, to write B and AS if needed
/// B and AS are only written once per SYN, to prevent slow calculations
/// Also updates SND, the period between sends, since data is written out
/// Be sure to call OnSendAckGetBAndAS() before calling OnSendAck(), since whether you write it or not affects \a numBytes
void OnSendAck(CCTimeType curTime, uint32_t numBytes);
/// Call when we send a NACK
/// Also updates SND, the period between sends, since data is written out
void OnSendNACK(CCTimeType curTime, uint32_t numBytes);
/// Retransmission time out for the sender
/// If the time difference between when a message was last transmitted, and the current time is greater than RTO then packet is eligible for retransmission, pending congestion control
/// RTO = (RTT + 4 * RTTVar) + SYN
/// If we have been continuously sending for the last RTO, and no ACK or NAK at all, SND*=2;
/// This is per message, which is different from UDT, but RakNet supports packetloss with continuing data where UDT is only RELIABLE_ORDERED
/// Minimum value is 100 milliseconds
CCTimeType GetRTOForRetransmission(void) const;
/// Set the maximum amount of data that can be sent in one datagram
/// Default to MAXIMUM_MTU_SIZE-UDP_HEADER_SIZE
void SetMTU(uint32_t bytes);
/// Return what was set by SetMTU()
uint32_t GetMTU(void) const;
/// Query for statistics
BytesPerMicrosecond GetLocalSendRate(void) const {return 0;}
BytesPerMicrosecond GetLocalReceiveRate(CCTimeType currentTime) const;
BytesPerMicrosecond GetRemoveReceiveRate(void) const {return 0;}
//BytesPerMicrosecond GetEstimatedBandwidth(void) const {return B;}
BytesPerMicrosecond GetEstimatedBandwidth(void) const {return GetLinkCapacityBytesPerSecond()*1000000.0;}
double GetLinkCapacityBytesPerSecond(void) const {return 0;}
/// Query for statistics
double GetRTT(void) const;
bool GetIsInSlowStart(void) const {return IsInSlowStart();}
uint32_t GetCWNDLimit(void) const {return (uint32_t) 0;}
/// Is a > b, accounting for variable overflow?
static bool GreaterThan(DatagramSequenceNumberType a, DatagramSequenceNumberType b);
/// Is a < b, accounting for variable overflow?
static bool LessThan(DatagramSequenceNumberType a, DatagramSequenceNumberType b);
// void SetTimeBetweenSendsLimit(unsigned int bitsPerSecond);
uint64_t GetBytesPerSecondLimitByCongestionControl(void) const;
protected:
// Maximum amount of bytes that the user can send, e.g. the size of one full datagram
uint32_t MAXIMUM_MTU_INCLUDING_UDP_HEADER;
double RTT;
double cwnd; // max bytes on wire
double ssThresh; // Threshhold between slow start and congestion avoidance
/// When we get an ack, if oldestUnsentAck==0, set it to the current time
/// When we send out acks, set oldestUnsentAck to 0
CCTimeType oldestUnsentAck;
CCTimeType GetSenderRTOForACK(void) const;
/// Every outgoing datagram is assigned a sequence number, which increments by 1 every assignment
DatagramSequenceNumberType nextDatagramSequenceNumber;
DatagramSequenceNumberType nextCongestionControlBlock;
bool backoffThisBlock, speedUpThisBlock;
/// Track which datagram sequence numbers have arrived.
/// If a sequence number is skipped, send a NAK for all skipped messages
DatagramSequenceNumberType expectedNextSequenceNumber;
bool _isContinuousSend;
bool IsInSlowStart(void) const;
};
}
#endif
#endif

View File

@@ -0,0 +1,798 @@
#include "CCRakNetUDT.hpp"
#if USE_SLIDING_WINDOW_CONGESTION_CONTROL!=1
#include "Rand.hpp"
#include "MTUSize.hpp"
#include <stdio.h>
#include <math.h>
#include <stdlib.h>
//#include <memory.h>
#include "RakAssert.hpp"
#include "RakAlloca.hpp"
using namespace RakNet;
static const double UNSET_TIME_US=-1;
static const double CWND_MIN_THRESHOLD=2.0;
static const double UNDEFINED_TRANSFER_RATE=0.0;
/// Interval at which to update aspects of the system
/// 1. send acks
/// 2. update time interval between outgoing packets
/// 3, Yodate retransmit timeout
#if CC_TIME_TYPE_BYTES==4
static const CCTimeType SYN=10;
#else
static const CCTimeType SYN=10000;
#endif
#if CC_TIME_TYPE_BYTES==4
#define MAX_RTT 1000
#define RTT_TOLERANCE 30
#else
#define MAX_RTT 1000000
#define RTT_TOLERANCE 30000
#endif
double RTTVarMultiple=4.0;
// ****************************************************** PUBLIC METHODS ******************************************************
CCRakNetUDT::CCRakNetUDT()
{
}
// ----------------------------------------------------------------------------------------------------------------------------
CCRakNetUDT::~CCRakNetUDT()
{
}
// ----------------------------------------------------------------------------------------------------------------------------
void CCRakNetUDT::Init(CCTimeType curTime, uint32_t maxDatagramPayload)
{
(void) curTime;
nextSYNUpdate=0;
packetPairRecieptHistoryWriteIndex=0;
packetArrivalHistoryWriteIndex=0;
packetArrivalHistoryWriteCount=0;
RTT=UNSET_TIME_US;
// RTTVar=UNSET_TIME_US;
isInSlowStart=true;
NAKCount=1000;
AvgNAKNum=1;
DecInterval=1;
DecCount=0;
nextDatagramSequenceNumber=0;
lastPacketPairPacketArrivalTime=0;
lastPacketPairSequenceNumber=(DatagramSequenceNumberType)(const uint32_t)-1;
lastPacketArrivalTime=0;
CWND=CWND_MIN_THRESHOLD;
lastUpdateWindowSizeAndAck=0;
lastTransmitOfBAndAS=0;
ExpCount=1.0;
totalUserDataBytesSent=0;
oldestUnsentAck=0;
MAXIMUM_MTU_INCLUDING_UDP_HEADER=maxDatagramPayload;
CWND_MAX_THRESHOLD=RESEND_BUFFER_ARRAY_LENGTH;
#if CC_TIME_TYPE_BYTES==4
const BytesPerMicrosecond DEFAULT_TRANSFER_RATE=(BytesPerMicrosecond) 3.6;
#else
const BytesPerMicrosecond DEFAULT_TRANSFER_RATE=(BytesPerMicrosecond) .0036;
#endif
#if CC_TIME_TYPE_BYTES==4
lastRttOnIncreaseSendRate=1000;
#else
lastRttOnIncreaseSendRate=1000000;
#endif
nextCongestionControlBlock=0;
lastRtt=0;
// B=DEFAULT_TRANSFER_RATE;
AS=UNDEFINED_TRANSFER_RATE;
const MicrosecondsPerByte DEFAULT_BYTE_INTERVAL=(MicrosecondsPerByte) (1.0/DEFAULT_TRANSFER_RATE);
SND=DEFAULT_BYTE_INTERVAL;
expectedNextSequenceNumber=0;
sendBAndASCount=0;
packetArrivalHistoryContinuousGapsIndex=0;
//packetPairRecipetHistoryGapsIndex=0;
hasWrittenToPacketPairReceiptHistory=false;
InitPacketArrivalHistory();
estimatedLinkCapacityBytesPerSecond=0;
bytesCanSendThisTick=0;
hadPacketlossThisBlock=false;
pingsLastInterval.Clear(__FILE__,__LINE__);
}
// ----------------------------------------------------------------------------------------------------------------------------
void CCRakNetUDT::SetMTU(uint32_t bytes)
{
MAXIMUM_MTU_INCLUDING_UDP_HEADER=bytes;
}
// ----------------------------------------------------------------------------------------------------------------------------
uint32_t CCRakNetUDT::GetMTU(void) const
{
return MAXIMUM_MTU_INCLUDING_UDP_HEADER;
}
// ----------------------------------------------------------------------------------------------------------------------------
void CCRakNetUDT::Update(CCTimeType curTime, bool hasDataToSendOrResend)
{
(void) hasDataToSendOrResend;
(void) curTime;
return;
// I suspect this is causing major lag
/*
if (hasDataToSendOrResend==false)
halveSNDOnNoDataTime=0;
else if (halveSNDOnNoDataTime==0)
{
UpdateHalveSNDOnNoDataTime(curTime);
ExpCount=1.0;
}
// If you send, and get no data at all from that time to RTO, then halve send rate7
if (HasHalveSNDOnNoDataTimeElapsed(curTime))
{
/// 2000 bytes per second
/// 0.0005 seconds per byte
/// 0.5 milliseconds per byte
/// 500 microseconds per byte
// printf("No incoming data, halving send rate\n");
SND*=2.0;
CapMinSnd(_FILE_AND_LINE_);
ExpCount+=1.0;
if (ExpCount>8.0)
ExpCount=8.0;
UpdateHalveSNDOnNoDataTime(curTime);
}
*/
}
// ----------------------------------------------------------------------------------------------------------------------------
int CCRakNetUDT::GetRetransmissionBandwidth(CCTimeType curTime, CCTimeType timeSinceLastTick, uint32_t unacknowledgedBytes, bool isContinuousSend)
{
(void) curTime;
if (isInSlowStart)
{
uint32_t CWNDLimit = (uint32_t) (CWND*MAXIMUM_MTU_INCLUDING_UDP_HEADER);
return CWNDLimit;
}
return GetTransmissionBandwidth(curTime,timeSinceLastTick,unacknowledgedBytes,isContinuousSend);
}
// ----------------------------------------------------------------------------------------------------------------------------
int CCRakNetUDT::GetTransmissionBandwidth(CCTimeType curTime, CCTimeType timeSinceLastTick, uint32_t unacknowledgedBytes, bool isContinuousSend)
{
(void) curTime;
if (isInSlowStart)
{
uint32_t CWNDLimit = (uint32_t) (CWND*MAXIMUM_MTU_INCLUDING_UDP_HEADER-unacknowledgedBytes);
return CWNDLimit;
}
if (bytesCanSendThisTick>0)
bytesCanSendThisTick=0;
#if CC_TIME_TYPE_BYTES==4
if (isContinuousSend==false && timeSinceLastTick>100)
timeSinceLastTick=100;
#else
if (isContinuousSend==false && timeSinceLastTick>100000)
timeSinceLastTick=100000;
#endif
bytesCanSendThisTick=(int)((double)timeSinceLastTick*((double)1.0/SND)+(double)bytesCanSendThisTick);
if (bytesCanSendThisTick>0)
return bytesCanSendThisTick;
return 0;
}
uint64_t CCRakNetUDT::GetBytesPerSecondLimitByCongestionControl(void) const
{
if (isInSlowStart)
return 0;
#if CC_TIME_TYPE_BYTES==4
return (uint64_t) ((double)1.0/(SND*1000.0));
#else
return (uint64_t) ((double)1.0/(SND*1000000.0));
#endif
}
// ----------------------------------------------------------------------------------------------------------------------------
bool CCRakNetUDT::ShouldSendACKs(CCTimeType curTime, CCTimeType estimatedTimeToNextTick)
{
CCTimeType rto = GetSenderRTOForACK();
// iphone crashes on comparison between double and int64 http://www.jenkinssoftware.com/forum/index.php?topic=2717.0
if (rto==static_cast<double>(UNSET_TIME_US))
{
// Unknown how long until the remote system will retransmit, so better send right away
return true;
}
// CCTimeType remoteRetransmitTime=oldestUnsentAck+rto-RTT*.5;
// CCTimeType ackArrivalTimeIfWeDelay=RTT*.5+estimatedTimeToNextTick+curTime;
// return ackArrivalTimeIfWeDelay<remoteRetransmitTime;
// Simplified equation
// GU: At least one ACK should be sent per SYN, otherwise your protocol will increase slower.
return curTime >= oldestUnsentAck + SYN ||
estimatedTimeToNextTick+curTime < oldestUnsentAck+rto-RTT;
}
// ----------------------------------------------------------------------------------------------------------------------------
DatagramSequenceNumberType CCRakNetUDT::GetNextDatagramSequenceNumber(void)
{
return nextDatagramSequenceNumber;
}
// ----------------------------------------------------------------------------------------------------------------------------
DatagramSequenceNumberType CCRakNetUDT::GetAndIncrementNextDatagramSequenceNumber(void)
{
DatagramSequenceNumberType dsnt=nextDatagramSequenceNumber;
nextDatagramSequenceNumber++;
return dsnt;
}
// ----------------------------------------------------------------------------------------------------------------------------
void CCRakNetUDT::OnSendBytes(CCTimeType curTime, uint32_t numBytes)
{
(void) curTime;
totalUserDataBytesSent+=numBytes;
if (isInSlowStart==false)
bytesCanSendThisTick-=numBytes;
}
// ****************************************************** PROTECTED METHODS ******************************************************
void CCRakNetUDT::SetNextSYNUpdate(CCTimeType currentTime)
{
nextSYNUpdate+=SYN;
if (nextSYNUpdate < currentTime)
nextSYNUpdate=currentTime+SYN;
}
// ----------------------------------------------------------------------------------------------------------------------------
BytesPerMicrosecond CCRakNetUDT::ReceiverCalculateDataArrivalRate(CCTimeType curTime) const
{
(void) curTime;
// Not an instantaneous measurement
/*
if (continuousBytesReceivedStartTime!=0 && curTime>continuousBytesReceivedStartTime)
{
#if CC_TIME_TYPE_BYTES==4
const CCTimeType threshold=100;
#else
const CCTimeType threshold=100000;
#endif
if (curTime-continuousBytesReceivedStartTime>threshold)
return (BytesPerMicrosecond) continuousBytesReceived/(BytesPerMicrosecond) (curTime-continuousBytesReceivedStartTime);
}
return UNDEFINED_TRANSFER_RATE;
*/
if (packetArrivalHistoryWriteCount<CC_RAKNET_UDT_PACKET_HISTORY_LENGTH)
return UNDEFINED_TRANSFER_RATE;
BytesPerMicrosecond median = ReceiverCalculateDataArrivalRateMedian();
int i;
const BytesPerMicrosecond oneEighthMedian=median*(1.0/8.0);
const BytesPerMicrosecond eightTimesMedian=median*8.0f;
BytesPerMicrosecond medianListLength=0.0;
BytesPerMicrosecond sum=0.0;
// Find average of acceptedMedianValues
for (i=0; i < CC_RAKNET_UDT_PACKET_HISTORY_LENGTH; i++)
{
if (packetArrivalHistory[i]>=oneEighthMedian &&
packetArrivalHistory[i]<eightTimesMedian)
{
medianListLength=medianListLength+1.0;
sum+=packetArrivalHistory[i];
}
}
if (medianListLength==0.0)
return UNDEFINED_TRANSFER_RATE;
return sum/medianListLength;
}
// ----------------------------------------------------------------------------------------------------------------------------
BytesPerMicrosecond CCRakNetUDT::ReceiverCalculateDataArrivalRateMedian(void) const
{
return CalculateListMedianRecursive(packetArrivalHistory, CC_RAKNET_UDT_PACKET_HISTORY_LENGTH, 0, 0);
}
// ----------------------------------------------------------------------------------------------------------------------------
BytesPerMicrosecond CCRakNetUDT::CalculateListMedianRecursive(const BytesPerMicrosecond inputList[CC_RAKNET_UDT_PACKET_HISTORY_LENGTH], int inputListLength, int lessThanSum, int greaterThanSum)
{
BytesPerMicrosecond lessThanMedian[CC_RAKNET_UDT_PACKET_HISTORY_LENGTH], greaterThanMedian[CC_RAKNET_UDT_PACKET_HISTORY_LENGTH];
int lessThanMedianListLength=0, greaterThanMedianListLength=0;
BytesPerMicrosecond median=inputList[0];
int i;
for (i=1; i < inputListLength; i++)
{
// If same value, spread among lists evenly
if (inputList[i]<median || ((i&1)==0 && inputList[i]==median))
lessThanMedian[lessThanMedianListLength++]=inputList[i];
else
greaterThanMedian[greaterThanMedianListLength++]=inputList[i];
}
RakAssert(CC_RAKNET_UDT_PACKET_HISTORY_LENGTH%2==0);
if (lessThanMedianListLength+lessThanSum==greaterThanMedianListLength+greaterThanSum+1 ||
lessThanMedianListLength+lessThanSum==greaterThanMedianListLength+greaterThanSum-1)
return median;
if (lessThanMedianListLength+lessThanSum < greaterThanMedianListLength+greaterThanSum)
{
lessThanMedian[lessThanMedianListLength++]=median;
return CalculateListMedianRecursive(greaterThanMedian, greaterThanMedianListLength, lessThanMedianListLength+lessThanSum, greaterThanSum);
}
else
{
greaterThanMedian[greaterThanMedianListLength++]=median;
return CalculateListMedianRecursive(lessThanMedian, lessThanMedianListLength, lessThanSum, greaterThanMedianListLength+greaterThanSum);
}
}
// ----------------------------------------------------------------------------------------------------------------------------
bool CCRakNetUDT::GreaterThan(DatagramSequenceNumberType a, DatagramSequenceNumberType b)
{
// a > b?
const DatagramSequenceNumberType halfSpan =(DatagramSequenceNumberType) (((DatagramSequenceNumberType)(const uint32_t)-1)/(DatagramSequenceNumberType)2);
return b!=a && b-a>halfSpan;
}
// ----------------------------------------------------------------------------------------------------------------------------
bool CCRakNetUDT::LessThan(DatagramSequenceNumberType a, DatagramSequenceNumberType b)
{
// a < b?
const DatagramSequenceNumberType halfSpan = ((DatagramSequenceNumberType)(const uint32_t)-1)/(DatagramSequenceNumberType)2;
return b!=a && b-a<halfSpan;
}
// ----------------------------------------------------------------------------------------------------------------------------
CCTimeType CCRakNetUDT::GetSenderRTOForACK(void) const
{
if (RTT==UNSET_TIME_US)
return (CCTimeType) UNSET_TIME_US;
double RTTVar = maxRTT-minRTT;
return (CCTimeType)(RTT + RTTVarMultiple * RTTVar + SYN);
}
// ----------------------------------------------------------------------------------------------------------------------------
CCTimeType CCRakNetUDT::GetRTOForRetransmission(void) const
{
#if CC_TIME_TYPE_BYTES==4
const CCTimeType maxThreshold=10000;
const CCTimeType minThreshold=100;
#else
const CCTimeType maxThreshold=1000000;
const CCTimeType minThreshold=100000;
#endif
if (RTT==UNSET_TIME_US)
{
return (CCTimeType) maxThreshold;
}
CCTimeType ret = lastRttOnIncreaseSendRate*2;
if (ret<minThreshold)
return minThreshold;
if (ret>maxThreshold)
return maxThreshold;
return ret;
}
// ----------------------------------------------------------------------------------------------------------------------------
void CCRakNetUDT::OnResend(CCTimeType curTime)
{
(void) curTime;
if (isInSlowStart)
{
if (AS!=UNDEFINED_TRANSFER_RATE)
EndSlowStart();
return;
}
if (hadPacketlossThisBlock==false)
{
// Logging
// printf("Sending SLOWER due to Resend, Rate=%f MBPS. Rtt=%i\n", GetLocalSendRate(), lastRtt );
IncreaseTimeBetweenSends();
hadPacketlossThisBlock=true;
}
}
// ----------------------------------------------------------------------------------------------------------------------------
void CCRakNetUDT::OnNAK(CCTimeType curTime, DatagramSequenceNumberType nakSequenceNumber)
{
(void) nakSequenceNumber;
(void) curTime;
if (isInSlowStart)
{
if (AS!=UNDEFINED_TRANSFER_RATE)
EndSlowStart();
return;
}
if (hadPacketlossThisBlock==false)
{
// Logging
//printf("Sending SLOWER due to NAK, Rate=%f MBPS. Rtt=%i\n", GetLocalSendRate(), lastRtt );
if (pingsLastInterval.Size()>10)
{
for (int i=0; i < 10; i++)
printf("%i, ", pingsLastInterval[pingsLastInterval.Size()-1-i]/1000);
}
printf("\n");
IncreaseTimeBetweenSends();
hadPacketlossThisBlock=true;
}
}
// ----------------------------------------------------------------------------------------------------------------------------
void CCRakNetUDT::EndSlowStart(void)
{
RakAssert(isInSlowStart==true);
RakAssert(AS!=UNDEFINED_TRANSFER_RATE);
// This overestimates
estimatedLinkCapacityBytesPerSecond=AS * 1000000.0;
isInSlowStart=false;
SND=1.0/AS;
CapMinSnd(_FILE_AND_LINE_);
// printf("ENDING SLOW START\n");
#if CC_TIME_TYPE_BYTES==4
// printf("Initial SND=%f Kilobytes per second\n", 1.0/SND);
#else
// printf("Initial SND=%f Megabytes per second\n", 1.0/SND);
#endif
if (SND > .1)
PrintLowBandwidthWarning();
}
// ----------------------------------------------------------------------------------------------------------------------------
void CCRakNetUDT::OnGotPacketPair(DatagramSequenceNumberType datagramSequenceNumber, uint32_t sizeInBytes, CCTimeType curTime)
{
(void) datagramSequenceNumber;
(void) sizeInBytes;
(void) curTime;
}
// ----------------------------------------------------------------------------------------------------------------------------
bool CCRakNetUDT::OnGotPacket(DatagramSequenceNumberType datagramSequenceNumber, bool isContinuousSend, CCTimeType curTime, uint32_t sizeInBytes, uint32_t *skippedMessageCount)
{
CC_DEBUG_PRINTF_2("R%i ",datagramSequenceNumber.val);
if (datagramSequenceNumber==expectedNextSequenceNumber)
{
*skippedMessageCount=0;
expectedNextSequenceNumber=datagramSequenceNumber+(DatagramSequenceNumberType)1;
}
else if (GreaterThan(datagramSequenceNumber, expectedNextSequenceNumber))
{
*skippedMessageCount=datagramSequenceNumber-expectedNextSequenceNumber;
// Sanity check, just use timeout resend if this was really valid
if (*skippedMessageCount>1000)
{
// During testing, the nat punchthrough server got 51200 on the first packet. I have no idea where this comes from, but has happened twice
if (*skippedMessageCount>(uint32_t)50000)
return false;
*skippedMessageCount=1000;
}
expectedNextSequenceNumber=datagramSequenceNumber+(DatagramSequenceNumberType)1;
}
else
{
*skippedMessageCount=0;
}
if (curTime>lastPacketArrivalTime)
{
CCTimeType interval = curTime-lastPacketArrivalTime;
// printf("Packet arrival gap is %I64u\n", (interval));
if (isContinuousSend)
{
continuousBytesReceived+=sizeInBytes;
if (continuousBytesReceivedStartTime==0)
continuousBytesReceivedStartTime=lastPacketArrivalTime;
mostRecentPacketArrivalHistory=(BytesPerMicrosecond)sizeInBytes/(BytesPerMicrosecond)interval;
// if (mostRecentPacketArrivalHistory < (BytesPerMicrosecond)0.0035)
// {
// printf("%s:%i LIKELY BUG: Calculated packetArrivalHistory is below 28.8 Kbps modem\nReport to rakkar@jenkinssoftware.com with file and line number\n", _FILE_AND_LINE_);
// }
packetArrivalHistoryContinuousGaps[packetArrivalHistoryContinuousGapsIndex++]=(int) interval;
packetArrivalHistoryContinuousGapsIndex&=(CC_RAKNET_UDT_PACKET_HISTORY_LENGTH-1);
packetArrivalHistoryWriteCount++;
packetArrivalHistory[packetArrivalHistoryWriteIndex++]=mostRecentPacketArrivalHistory;
// Wrap to 0 at the end of the range
// Assumes power of 2 for CC_RAKNET_UDT_PACKET_HISTORY_LENGTH
packetArrivalHistoryWriteIndex&=(CC_RAKNET_UDT_PACKET_HISTORY_LENGTH-1);
}
else
{
continuousBytesReceivedStartTime=0;
continuousBytesReceived=0;
}
lastPacketArrivalTime=curTime;
}
return true;
}
// ----------------------------------------------------------------------------------------------------------------------------
void CCRakNetUDT::OnAck(CCTimeType curTime, CCTimeType rtt, bool hasBAndAS, BytesPerMicrosecond _B, BytesPerMicrosecond _AS, double totalUserDataBytesAcked, bool isContinuousSend, DatagramSequenceNumberType sequenceNumber )
{
#if CC_TIME_TYPE_BYTES==4
RakAssert(rtt < 10000);
#else
RakAssert(rtt < 10000000);
#endif
(void) _B;
if (hasBAndAS)
{
/// RakAssert(_B!=UNDEFINED_TRANSFER_RATE && _AS!=UNDEFINED_TRANSFER_RATE);
// B=B * .875 + _B * .125;
// AS is packet arrival rate
RakAssert(_AS!=UNDEFINED_TRANSFER_RATE);
AS=_AS;
CC_DEBUG_PRINTF_4("ArrivalRate=%f linkCap=%f incomingLinkCap=%f\n", _AS,B,_B);
}
if (oldestUnsentAck==0)
oldestUnsentAck=curTime;
if (isInSlowStart==true)
{
nextCongestionControlBlock=nextDatagramSequenceNumber;
lastRttOnIncreaseSendRate=rtt;
UpdateWindowSizeAndAckOnAckPreSlowStart(totalUserDataBytesAcked);
}
else
{
UpdateWindowSizeAndAckOnAckPerSyn(curTime, rtt, isContinuousSend, sequenceNumber);
}
lastUpdateWindowSizeAndAck=curTime;
}
// ----------------------------------------------------------------------------------------------------------------------------
void CCRakNetUDT::OnSendAckGetBAndAS(CCTimeType curTime, bool *hasBAndAS, BytesPerMicrosecond *_B, BytesPerMicrosecond *_AS)
{
if (curTime>lastTransmitOfBAndAS+SYN)
{
*_B=0;
*_AS=ReceiverCalculateDataArrivalRate(curTime);
if (*_AS==UNDEFINED_TRANSFER_RATE)
{
*hasBAndAS=false;
}
else
{
*hasBAndAS=true;
}
}
else
{
*hasBAndAS=false;
}
}
// ----------------------------------------------------------------------------------------------------------------------------
void CCRakNetUDT::OnSendAck(CCTimeType curTime, uint32_t numBytes)
{
(void) numBytes;
(void) curTime;
// This is not accounted for on the remote system, and thus causes bandwidth to be underutilized
//UpdateNextAllowedSend(curTime, numBytes+UDP_HEADER_SIZE);
oldestUnsentAck=0;
}
// ----------------------------------------------------------------------------------------------------------------------------
void CCRakNetUDT::OnSendNACK(CCTimeType curTime, uint32_t numBytes)
{
(void) numBytes;
(void) curTime;
// This is not accounted for on the remote system, and thus causes bandwidth to be underutilized
// UpdateNextAllowedSend(curTime, numBytes+UDP_HEADER_SIZE);
}
// ----------------------------------------------------------------------------------------------------------------------------
void CCRakNetUDT::UpdateWindowSizeAndAckOnAckPreSlowStart(double totalUserDataBytesAcked)
{
// During slow start, max window size is the number of full packets that have been sent out
// CWND=(double) ((double)totalUserDataBytesSent/(double)MAXIMUM_MTU_INCLUDING_UDP_HEADER);
CC_DEBUG_PRINTF_3("CWND increasing from %f to %f\n", CWND, (double) ((double)totalUserDataBytesAcked/(double)MAXIMUM_MTU_INCLUDING_UDP_HEADER));
CWND=(double) ((double)totalUserDataBytesAcked/(double)MAXIMUM_MTU_INCLUDING_UDP_HEADER);
if (CWND>=CWND_MAX_THRESHOLD)
{
CWND=CWND_MAX_THRESHOLD;
if (AS!=UNDEFINED_TRANSFER_RATE)
EndSlowStart();
}
if (CWND<CWND_MIN_THRESHOLD)
CWND=CWND_MIN_THRESHOLD;
}
// ----------------------------------------------------------------------------------------------------------------------------
void CCRakNetUDT::UpdateWindowSizeAndAckOnAckPerSyn(CCTimeType curTime, CCTimeType rtt, bool isContinuousSend, DatagramSequenceNumberType sequenceNumber)
{
(void) curTime;
(void) sequenceNumber;
if (isContinuousSend==false)
{
nextCongestionControlBlock=nextDatagramSequenceNumber;
pingsLastInterval.Clear(__FILE__,__LINE__);
return;
}
pingsLastInterval.Push(rtt,__FILE__,__LINE__);
static const int intervalSize=33; // Should be odd
if (pingsLastInterval.Size()>intervalSize)
pingsLastInterval.Pop();
if (GreaterThan(sequenceNumber, nextCongestionControlBlock) &&
sequenceNumber-nextCongestionControlBlock>=intervalSize &&
pingsLastInterval.Size()==intervalSize)
{
double slopeSum=0.0;
double average=(double) pingsLastInterval[0];
int sampleSize=pingsLastInterval.Size();
for (int i=1; i < sampleSize; i++)
{
slopeSum+=(double)pingsLastInterval[i]-(double)pingsLastInterval[i-1];
average+=pingsLastInterval[i];
}
average/=sampleSize;
if (hadPacketlossThisBlock==true)
{
}
else if (slopeSum < -.10*average)
{
// Logging
//printf("Ping dropping. slope=%f%%. Rate=%f MBPS. Rtt=%i\n", 100.0*slopeSum/average, GetLocalSendRate(), rtt );
}
else if (slopeSum > .10*average)
{
// Logging
//printf("Ping rising. slope=%f%%. Rate=%f MBPS. Rtt=%i\n", 100.0*slopeSum/average, GetLocalSendRate(), rtt );
IncreaseTimeBetweenSends();
}
else
{
// Logging
//printf("Ping stable. slope=%f%%. Rate=%f MBPS. Rtt=%i\n", 100.0*slopeSum/average, GetLocalSendRate(), rtt );
// No packetloss over time threshhold, and rtt decreased, so send faster
lastRttOnIncreaseSendRate=rtt;
DecreaseTimeBetweenSends();
}
pingsLastInterval.Clear(__FILE__,__LINE__);
hadPacketlossThisBlock=false;
nextCongestionControlBlock=nextDatagramSequenceNumber;
}
lastRtt=rtt;
}
// ----------------------------------------------------------------------------------------------------------------------------
double CCRakNetUDT::BytesPerMicrosecondToPacketsPerMillisecond(BytesPerMicrosecond in)
{
#if CC_TIME_TYPE_BYTES==4
const BytesPerMicrosecond factor = 1.0 / (BytesPerMicrosecond) MAXIMUM_MTU_INCLUDING_UDP_HEADER;
#else
const BytesPerMicrosecond factor = 1000.0 / (BytesPerMicrosecond) MAXIMUM_MTU_INCLUDING_UDP_HEADER;
#endif
return in * factor;
}
// ----------------------------------------------------------------------------------------------------------------------------
void CCRakNetUDT::InitPacketArrivalHistory(void)
{
unsigned int i;
for (i=0; i < CC_RAKNET_UDT_PACKET_HISTORY_LENGTH; i++)
{
packetArrivalHistory[i]=UNDEFINED_TRANSFER_RATE;
packetArrivalHistoryContinuousGaps[i]=0;
}
packetArrivalHistoryWriteCount=0;
continuousBytesReceived=0;
continuousBytesReceivedStartTime=0;
}
// ----------------------------------------------------------------------------------------------------------------------------
void CCRakNetUDT::PrintLowBandwidthWarning(void)
{
/*
printf("\n-------LOW BANDWIDTH -----\n");
if (isInSlowStart==false)
printf("SND=%f Megabytes per second\n", 1.0/SND);
printf("Window size=%f\n", CWND);
printf("Pipe from packet pair = %f megabytes per second\n", B);
printf("RTT=%f milliseconds\n", RTT/1000.0);
printf("RTT Variance=%f milliseconds\n", RTTVar/1000.0);
printf("Retransmission=%i milliseconds\n", GetRTOForRetransmission()/1000);
printf("Packet arrival rate on the remote system=%f megabytes per second\n", AS);
printf("Packet arrival rate on our system=%f megabytes per second\n", ReceiverCalculateDataArrivalRate());
printf("isInSlowStart=%i\n", isInSlowStart);
printf("---------------\n");
*/
}
BytesPerMicrosecond CCRakNetUDT::GetLocalReceiveRate(CCTimeType currentTime) const
{
return ReceiverCalculateDataArrivalRate(currentTime);
}
double CCRakNetUDT::GetRTT(void) const
{
if (RTT==UNSET_TIME_US)
return 0.0;
return RTT;
}
void CCRakNetUDT::CapMinSnd(const char *file, int line)
{
(void) file;
(void) line;
if (SND > 500)
{
SND=500;
CC_DEBUG_PRINTF_3("%s:%i LIKELY BUG: SND has gotten above 500 microseconds between messages (28.8 modem)\nReport to rakkar@jenkinssoftware.com with file and line number\n", file, line);
}
}
void CCRakNetUDT::IncreaseTimeBetweenSends(void)
{
// In order to converge, bigger numbers have to increase slower and decrease faster
// SND==500 then increment is .02
// SND==0 then increment is near 0
// (SND+1.0) brings it to the range of 1 to 501
// Square the number, which is the range of 1 to 251001
// Divide by 251001, which is the range of 1/251001 to 1
double increment;
increment = .02 * ((SND+1.0) * (SND+1.0)) / (501.0*501.0) ;
// SND=500 then increment=.02
// SND=0 then increment=near 0
SND*=(1.02 - increment);
// SND=0 then fast increase, slow decrease
// SND=500 then slow increase, fast decrease
CapMinSnd(__FILE__,__LINE__);
}
void CCRakNetUDT::DecreaseTimeBetweenSends(void)
{
double increment;
increment = .01 * ((SND+1.0) * (SND+1.0)) / (501.0*501.0) ;
// SND=500 then increment=.01
// SND=0 then increment=near 0
SND*=(.99 - increment);
}
/*
void CCRakNetUDT::SetTimeBetweenSendsLimit(unsigned int bitsPerSecond)
{
// bitsPerSecond / 1000000 = bitsPerMicrosecond
// bitsPerMicrosecond / 8 = BytesPerMicrosecond
// 1 / BytesPerMicrosecond = MicrosecondsPerByte
// 1 / ( (bitsPerSecond / 1000000) / 8 ) =
// 1 / (bitsPerSecond / 8000000) =
// 8000000 / bitsPerSecond
#if CC_TIME_TYPE_BYTES==4
MicrosecondsPerByte limit = (MicrosecondsPerByte) 8000 / (MicrosecondsPerByte)bitsPerSecond;
#else
MicrosecondsPerByte limit = (MicrosecondsPerByte) 8000000 / (MicrosecondsPerByte)bitsPerSecond;
#endif
if (limit > SND)
SND=limit;
}
*/
#endif

View File

@@ -0,0 +1,394 @@
#include "RakNetDefines.hpp"
#if USE_SLIDING_WINDOW_CONGESTION_CONTROL!=1
#ifndef __CONGESTION_CONTROL_UDT_H
#define __CONGESTION_CONTROL_UDT_H
#include "NativeTypes.hpp"
#include "RakNetTime.hpp"
#include "RakNetTypes.hpp"
#include "DS_Queue.hpp"
/// Set to 4 if you are using the iPod Touch TG. See http://www.jenkinssoftware.com/forum/index.php?topic=2717.0
#define CC_TIME_TYPE_BYTES 8
namespace RakNet
{
typedef uint64_t CCTimeType;
typedef uint24_t DatagramSequenceNumberType;
typedef double BytesPerMicrosecond;
typedef double BytesPerSecond;
typedef double MicrosecondsPerByte;
/// CC_RAKNET_UDT_PACKET_HISTORY_LENGTH should be a power of 2 for the writeIndex variables to wrap properly
#define CC_RAKNET_UDT_PACKET_HISTORY_LENGTH 64
#define RTT_HISTORY_LENGTH 64
/// Sizeof an UDP header in byte
#define UDP_HEADER_SIZE 28
#define CC_DEBUG_PRINTF_1(x)
#define CC_DEBUG_PRINTF_2(x,y)
#define CC_DEBUG_PRINTF_3(x,y,z)
#define CC_DEBUG_PRINTF_4(x,y,z,a)
#define CC_DEBUG_PRINTF_5(x,y,z,a,b)
//#define CC_DEBUG_PRINTF_1(x) printf(x)
//#define CC_DEBUG_PRINTF_2(x,y) printf(x,y)
//#define CC_DEBUG_PRINTF_3(x,y,z) printf(x,y,z)
//#define CC_DEBUG_PRINTF_4(x,y,z,a) printf(x,y,z,a)
//#define CC_DEBUG_PRINTF_5(x,y,z,a,b) printf(x,y,z,a,b)
/// \brief Encapsulates UDT congestion control, as used by RakNet
/// Requirements:
/// <OL>
/// <LI>Each datagram is no more than MAXIMUM_MTU_SIZE, after accounting for the UDP header
/// <LI>Each datagram containing a user message has a sequence number which is set after calling OnSendBytes(). Set it by calling GetAndIncrementNextDatagramSequenceNumber()
/// <LI>System is designed to be used from a single thread.
/// <LI>Each packet should have a timeout time based on GetSenderRTOForACK(). If this time elapses, add the packet to the head of the send list for retransmission.
/// </OL>
///
/// Recommended:
/// <OL>
/// <LI>Call sendto in its own thread. This takes a significant amount of time in high speed networks.
/// </OL>
///
/// Algorithm:
/// <OL>
/// <LI>On a new connection, call Init()
/// <LI>On a periodic interval (SYN time is the best) call Update(). Also call ShouldSendACKs(), and send buffered ACKS if it returns true.
/// <LI>Call OnSendAck() when sending acks.
/// <LI>When you want to send or resend data, call GetNumberOfBytesToSend(). It will return you enough bytes to keep you busy for \a estimatedTimeToNextTick. You can send more than this to fill out a datagram, or to send packet pairs
/// <LI>Call OnSendBytes() when sending datagrams.
/// <LI>When data arrives, record the sequence number and buffer an ACK for it, to be sent from Update() if ShouldSendACKs() returns true
/// <LI>Every 16 packets that you send, send two of them back to back (a packet pair) as long as both packets are the same size. If you don't have two packets the same size, it is fine to defer this until you do.
/// <LI>When you get a packet, call OnGotPacket(). If the packet is also either of a packet pair, call OnGotPacketPair()
/// <LI>If you get a packet, and the sequence number is not 1 + the last sequence number, send a NAK. On the remote system, call OnNAK() and resend that message.
/// <LI>If you get an ACK, remove that message from retransmission. Call OnNonDuplicateAck().
/// <LI>If a message is not ACKed for GetRTOForRetransmission(), resend it.
/// </OL>
class CCRakNetUDT
{
public:
CCRakNetUDT();
~CCRakNetUDT();
/// Reset all variables to their initial states, for a new connection
void Init(CCTimeType curTime, uint32_t maxDatagramPayload);
/// Update over time
void Update(CCTimeType curTime, bool hasDataToSendOrResend);
int GetRetransmissionBandwidth(CCTimeType curTime, CCTimeType timeSinceLastTick, uint32_t unacknowledgedBytes, bool isContinuousSend);
int GetTransmissionBandwidth(CCTimeType curTime, CCTimeType timeSinceLastTick, uint32_t unacknowledgedBytes, bool isContinuousSend);
/// Acks do not have to be sent immediately. Instead, they can be buffered up such that groups of acks are sent at a time
/// This reduces overall bandwidth usage
/// How long they can be buffered depends on the retransmit time of the sender
/// Should call once per update tick, and send if needed
bool ShouldSendACKs(CCTimeType curTime, CCTimeType estimatedTimeToNextTick);
/// Every data packet sent must contain a sequence number
/// Call this function to get it. The sequence number is passed into OnGotPacketPair()
DatagramSequenceNumberType GetAndIncrementNextDatagramSequenceNumber(void);
DatagramSequenceNumberType GetNextDatagramSequenceNumber(void);
/// Call this when you send packets
/// Every 15th and 16th packets should be sent as a packet pair if possible
/// When packets marked as a packet pair arrive, pass to OnGotPacketPair()
/// When any packets arrive, (additionally) pass to OnGotPacket
/// Packets should contain our system time, so we can pass rtt to OnNonDuplicateAck()
void OnSendBytes(CCTimeType curTime, uint32_t numBytes);
/// Call this when you get a packet pair
void OnGotPacketPair(DatagramSequenceNumberType datagramSequenceNumber, uint32_t sizeInBytes, CCTimeType curTime);
/// Call this when you get a packet (including packet pairs)
/// If the DatagramSequenceNumberType is out of order, skippedMessageCount will be non-zero
/// In that case, send a NAK for every sequence number up to that count
bool OnGotPacket(DatagramSequenceNumberType datagramSequenceNumber, bool isContinuousSend, CCTimeType curTime, uint32_t sizeInBytes, uint32_t *skippedMessageCount);
/// Call when you get a NAK, with the sequence number of the lost message
/// Affects the congestion control
void OnResend(CCTimeType curTime);
void OnNAK(CCTimeType curTime, DatagramSequenceNumberType nakSequenceNumber);
/// Call this when an ACK arrives.
/// hasBAndAS are possibly written with the ack, see OnSendAck()
/// B and AS are used in the calculations in UpdateWindowSizeAndAckOnAckPerSyn
/// B and AS are updated at most once per SYN
void OnAck(CCTimeType curTime, CCTimeType rtt, bool hasBAndAS, BytesPerMicrosecond _B, BytesPerMicrosecond _AS, double totalUserDataBytesAcked, bool isContinuousSend, DatagramSequenceNumberType sequenceNumber );
void OnDuplicateAck( CCTimeType curTime, DatagramSequenceNumberType sequenceNumber ) {}
/// Call when you send an ack, to see if the ack should have the B and AS parameters transmitted
/// Call before calling OnSendAck()
void OnSendAckGetBAndAS(CCTimeType curTime, bool *hasBAndAS, BytesPerMicrosecond *_B, BytesPerMicrosecond *_AS);
/// Call when we send an ack, to write B and AS if needed
/// B and AS are only written once per SYN, to prevent slow calculations
/// Also updates SND, the period between sends, since data is written out
/// Be sure to call OnSendAckGetBAndAS() before calling OnSendAck(), since whether you write it or not affects \a numBytes
void OnSendAck(CCTimeType curTime, uint32_t numBytes);
/// Call when we send a NACK
/// Also updates SND, the period between sends, since data is written out
void OnSendNACK(CCTimeType curTime, uint32_t numBytes);
/// Retransmission time out for the sender
/// If the time difference between when a message was last transmitted, and the current time is greater than RTO then packet is eligible for retransmission, pending congestion control
/// RTO = (RTT + 4 * RTTVar) + SYN
/// If we have been continuously sending for the last RTO, and no ACK or NAK at all, SND*=2;
/// This is per message, which is different from UDT, but RakNet supports packetloss with continuing data where UDT is only RELIABLE_ORDERED
/// Minimum value is 100 milliseconds
CCTimeType GetRTOForRetransmission(void) const;
/// Set the maximum amount of data that can be sent in one datagram
/// Default to MAXIMUM_MTU_SIZE-UDP_HEADER_SIZE
void SetMTU(uint32_t bytes);
/// Return what was set by SetMTU()
uint32_t GetMTU(void) const;
/// Query for statistics
BytesPerMicrosecond GetLocalSendRate(void) const {return 1.0 / SND;}
BytesPerMicrosecond GetLocalReceiveRate(CCTimeType currentTime) const;
BytesPerMicrosecond GetRemoveReceiveRate(void) const {return AS;}
//BytesPerMicrosecond GetEstimatedBandwidth(void) const {return B;}
BytesPerMicrosecond GetEstimatedBandwidth(void) const {return GetLinkCapacityBytesPerSecond()*1000000.0;}
double GetLinkCapacityBytesPerSecond(void) const {return estimatedLinkCapacityBytesPerSecond;};
/// Query for statistics
double GetRTT(void) const;
bool GetIsInSlowStart(void) const {return isInSlowStart;}
uint32_t GetCWNDLimit(void) const {return (uint32_t) (CWND*MAXIMUM_MTU_INCLUDING_UDP_HEADER);}
/// Is a > b, accounting for variable overflow?
static bool GreaterThan(DatagramSequenceNumberType a, DatagramSequenceNumberType b);
/// Is a < b, accounting for variable overflow?
static bool LessThan(DatagramSequenceNumberType a, DatagramSequenceNumberType b);
// void SetTimeBetweenSendsLimit(unsigned int bitsPerSecond);
uint64_t GetBytesPerSecondLimitByCongestionControl(void) const;
protected:
// --------------------------- PROTECTED VARIABLES ---------------------------
/// time interval between bytes, in microseconds.
/// Only used when slowStart==false
/// Increased over time as we continually get messages
/// Decreased on NAK and timeout
/// Starts at 0 (invalid)
MicrosecondsPerByte SND;
/// Supportive window mechanism, controlling the maximum number of in-flight packets
/// Used both during and after slow-start, but primarily during slow-start
/// Starts at 2, which is also the low threshhold
/// Max is the socket receive buffer / MTU
/// CWND = AS * (RTT + SYN) + 16
double CWND;
/// When we do an update process on the SYN interval, nextSYNUpdate is set to the next time we should update
/// Normally this is nextSYNUpdate+=SYN, in order to update on a consistent schedule
/// However, if this would result in an immediate update yet again, it is set to SYN microseconds past the current time (in case the thread did not update for a long time)
CCTimeType nextSYNUpdate;
/// Index into packetPairRecieptHistory where we will next write
/// The history is always full (starting with default values) so no read index is needed
int packetPairRecieptHistoryWriteIndex;
/// Sent to the sender by the receiver from packetPairRecieptHistory whenever a back to back packet arrives on the receiver
/// Updated by B = B * .875 + incomingB * .125
//BytesPerMicrosecond B;
/// Running round trip time (ping*2)
/// Only sender needs to know this
/// Initialized to UNSET
/// Set to rtt on first calculation
/// Updated gradually by RTT = RTT * 0.875 + rtt * 0.125
double RTT;
/// Round trip time variance
/// Only sender needs to know this
/// Initialized to UNSET
/// Set to rtt on first calculation
// double RTTVar;
/// Update: Use min/max, RTTVar follows current variance too closely resulting in packetloss
double minRTT, maxRTT;
/// Used to calculate packet arrival rate (in UDT) but data arrival rate (in RakNet, where not all datagrams are the same size)
/// Filter is used to cull lowest half of values for bytesPerMicrosecond, to discount spikes and inactivity
/// Referred to in the documentation as AS, data arrival rate
/// AS is sent to the sender and calculated every 10th ack
/// Each node represents (curTime-lastPacketArrivalTime)/bytes
/// Used with ReceiverCalculateDataArrivalRate();
BytesPerMicrosecond packetArrivalHistory[CC_RAKNET_UDT_PACKET_HISTORY_LENGTH];
BytesPerMicrosecond packetArrivalHistoryContinuousGaps[CC_RAKNET_UDT_PACKET_HISTORY_LENGTH];
unsigned char packetArrivalHistoryContinuousGapsIndex;
uint64_t continuousBytesReceived;
CCTimeType continuousBytesReceivedStartTime;
unsigned int packetArrivalHistoryWriteCount;
/// Index into packetArrivalHistory where we will next write
/// The history is always full (starting with default values) so no read index is needed
int packetArrivalHistoryWriteIndex;
/// Tracks the time the last packet that arrived, so BytesPerMicrosecond can be calculated for packetArrivalHistory when a new packet arrives
CCTimeType lastPacketArrivalTime;
/// Data arrival rate from the sender to the receiver, as told to us by the receiver
/// Used to calculate initial sending rate when slow start stops
BytesPerMicrosecond AS;
/// When the receiver last calculated and send B and AS, from packetArrivalHistory and packetPairRecieptHistory
/// Used to prevent it from being calculated and send too frequently, as they are slow operations
CCTimeType lastTransmitOfBAndAS;
/// New connections start in slow start
/// During slow start, SND is not used, only CWND
/// Slow start ends when we get a NAK, or the maximum size of CWND is reached
/// SND is initialized to the inverse of the receiver's packet arrival rate when slow start ends
bool isInSlowStart;
/// How many NAKs arrived this congestion period
/// Initialized to 1 when the congestion period starts
uint32_t NAKCount;
/// How many NAKs do you get on average during a congestion period?
/// Starts at 1
/// Used to generate a random number, DecRandom, between 1 and AvgNAKNum
uint32_t AvgNAKNum;
/// How many times we have decremented SND this congestion period. Used to limit the number of decrements to 5
uint32_t DecCount;
/// Every DecInterval NAKs per congestion period, we decrease the send rate
uint32_t DecInterval;
/// Every outgoing datagram is assigned a sequence number, which increments by 1 every assignment
DatagramSequenceNumberType nextDatagramSequenceNumber;
/// If a packet is marked as a packet pair, lastPacketPairPacketArrivalTime is set to the time it arrives
/// This is used so when the 2nd packet of the pair arrives, we can calculate the time interval between the two
CCTimeType lastPacketPairPacketArrivalTime;
/// If a packet is marked as a packet pair, lastPacketPairSequenceNumber is checked to see if the last packet we got
/// was the packet immediately before the one that arrived
/// If so, we can use lastPacketPairPacketArrivalTime to get the time between the two packets, and thus estimate the link capacity
/// Initialized to -1, so the first packet of a packet pair won't be treated as the second
DatagramSequenceNumberType lastPacketPairSequenceNumber;
/// Used to cap UpdateWindowSizeAndAckOnAckPerSyn() to once speed increase per SYN
/// This is to prevent speeding up faster than congestion control can compensate for
CCTimeType lastUpdateWindowSizeAndAck;
/// Every time SND is halved due to timeout, the RTO is increased
/// This is to prevent massive retransmissions to an unresponsive system
/// Reset on any data arriving
double ExpCount;
/// Total number of user data bytes sent
/// Used to adjust the window size, on ACK, during slow start
uint64_t totalUserDataBytesSent;
/// When we get an ack, if oldestUnsentAck==0, set it to the current time
/// When we send out acks, set oldestUnsentAck to 0
CCTimeType oldestUnsentAck;
// Maximum amount of bytes that the user can send, e.g. the size of one full datagram
uint32_t MAXIMUM_MTU_INCLUDING_UDP_HEADER;
// Max window size
double CWND_MAX_THRESHOLD;
/// Track which datagram sequence numbers have arrived.
/// If a sequence number is skipped, send a NAK for all skipped messages
DatagramSequenceNumberType expectedNextSequenceNumber;
// How many times have we sent B and AS? Used to force it to send at least CC_RAKNET_UDT_PACKET_HISTORY_LENGTH times
// Otherwise, the default values in the array generate inaccuracy
uint32_t sendBAndASCount;
/// Most recent values read into the corresponding lists
/// Used during the beginning of a connection, when the median filter is still inaccurate
BytesPerMicrosecond mostRecentPacketArrivalHistory;
bool hasWrittenToPacketPairReceiptHistory;
// uint32_t rttHistory[RTT_HISTORY_LENGTH];
// uint32_t rttHistoryIndex;
// uint32_t rttHistoryWriteCount;
// uint32_t rttSum, rttLow;
// CCTimeType lastSndUpdateTime;
double estimatedLinkCapacityBytesPerSecond;
// --------------------------- PROTECTED METHODS ---------------------------
/// Update nextSYNUpdate by SYN, or the same amount past the current time if no updates have occurred for a long time
void SetNextSYNUpdate(CCTimeType currentTime);
/// Returns the rate of data arrival, based on packets arriving on the sender.
BytesPerMicrosecond ReceiverCalculateDataArrivalRate(CCTimeType curTime) const;
/// Returns the median of the data arrival rate
BytesPerMicrosecond ReceiverCalculateDataArrivalRateMedian(void) const;
/// Calculates the median an array of BytesPerMicrosecond
static BytesPerMicrosecond CalculateListMedianRecursive(const BytesPerMicrosecond inputList[CC_RAKNET_UDT_PACKET_HISTORY_LENGTH], int inputListLength, int lessThanSum, int greaterThanSum);
// static uint32_t CalculateListMedianRecursive(const uint32_t inputList[RTT_HISTORY_LENGTH], int inputListLength, int lessThanSum, int greaterThanSum);
/// Same as GetRTOForRetransmission, but does not factor in ExpCount
/// This is because the receiver does not know ExpCount for the sender, and even if it did, acks shouldn't be delayed for this reason
CCTimeType GetSenderRTOForACK(void) const;
/// Stop slow start, and enter normal transfer rate
void EndSlowStart(void);
/// Does the named conversion
inline double BytesPerMicrosecondToPacketsPerMillisecond(BytesPerMicrosecond in);
/// Update the round trip time, from ACK or ACK2
//void UpdateRTT(CCTimeType rtt);
/// Update the corresponding variables pre-slow start
void UpdateWindowSizeAndAckOnAckPreSlowStart(double totalUserDataBytesAcked);
/// Update the corresponding variables post-slow start
void UpdateWindowSizeAndAckOnAckPerSyn(CCTimeType curTime, CCTimeType rtt, bool isContinuousSend, DatagramSequenceNumberType sequenceNumber);
/// Sets halveSNDOnNoDataTime to the future, and also resets ExpCount, which is used to multiple the RTO on no data arriving at all
void ResetOnDataArrivalHalveSNDOnNoDataTime(CCTimeType curTime);
// Init array
void InitPacketArrivalHistory(void);
// Printf
void PrintLowBandwidthWarning(void);
// Bug: SND can sometimes get super high - have seen 11693
void CapMinSnd(const char *file, int line);
void DecreaseTimeBetweenSends(void);
void IncreaseTimeBetweenSends(void);
int bytesCanSendThisTick;
CCTimeType lastRttOnIncreaseSendRate;
CCTimeType lastRtt;
DatagramSequenceNumberType nextCongestionControlBlock;
bool hadPacketlossThisBlock;
DataStructures::Queue<CCTimeType> pingsLastInterval;
};
}
#endif
#endif

View File

@@ -0,0 +1,97 @@
/**
* @file
* @brief CheckSum implementation from http://www.flounder.com/checksum.htm
*
*/
#include "CheckSum.hpp"
/****************************************************************************
* CheckSum::add
* Inputs:
* unsigned int d: word to add
* Result: void
*
* Effect:
* Adds the bytes of the unsigned int to the CheckSum
****************************************************************************/
void CheckSum::Add ( unsigned int value )
{
union
{
unsigned int value;
unsigned char bytes[ 4 ];
}
data;
data.value = value;
for ( unsigned int i = 0; i < sizeof( data.bytes ); i++ )
Add ( data.bytes[ i ] )
;
} // CheckSum::add(unsigned int)
/****************************************************************************
* CheckSum::add
* Inputs:
* unsigned short value:
* Result: void
*
* Effect:
* Adds the bytes of the unsigned short value to the CheckSum
****************************************************************************/
void CheckSum::Add ( unsigned short value )
{
union
{
unsigned short value;
unsigned char bytes[ 2 ];
}
data;
data.value = value;
for ( unsigned int i = 0; i < sizeof( data.bytes ); i++ )
Add ( data.bytes[ i ] )
;
} // CheckSum::add(unsigned short)
/****************************************************************************
* CheckSum::add
* Inputs:
* unsigned char value:
* Result: void
*
* Effect:
* Adds the byte to the CheckSum
****************************************************************************/
void CheckSum::Add ( unsigned char value )
{
unsigned char cipher = (unsigned char)( value ^ ( r >> 8 ) );
r = ( cipher + r ) * c1 + c2;
sum += cipher;
} // CheckSum::add(unsigned char)
/****************************************************************************
* CheckSum::add
* Inputs:
* LPunsigned char b: pointer to byte array
* unsigned int length: count
* Result: void
*
* Effect:
* Adds the bytes to the CheckSum
****************************************************************************/
void CheckSum::Add ( unsigned char *b, unsigned int length )
{
for ( unsigned int i = 0; i < length; i++ )
Add ( b[ i ] )
;
} // CheckSum::add(LPunsigned char, unsigned int)

View File

@@ -0,0 +1,53 @@
///
/// \file CheckSum.cpp
/// \brief [Internal] CheckSum implementation from http://www.flounder.com/checksum.htm
///
#ifndef __CHECKSUM_H
#define __CHECKSUM_H
#include "RakMemoryOverride.hpp"
/// Generates and validates checksums
class CheckSum
{
public:
/// Default constructor
CheckSum()
{
Clear();
}
void Clear()
{
sum = 0;
r = 55665;
c1 = 52845;
c2 = 22719;
}
void Add ( unsigned int w );
void Add ( unsigned short w );
void Add ( unsigned char* b, unsigned int length );
void Add ( unsigned char b );
unsigned int Get ()
{
return sum;
}
protected:
unsigned short r;
unsigned short c1;
unsigned short c2;
unsigned int sum;
};
#endif

View File

@@ -0,0 +1,242 @@
#include "NativeFeatureIncludes.hpp"
#if _RAKNET_SUPPORT_CloudClient==1
#include "CloudClient.hpp"
#include "GetTime.hpp"
#include "MessageIdentifiers.hpp"
#include "BitStream.hpp"
#include "RakPeerInterface.hpp"
using namespace RakNet;
STATIC_FACTORY_DEFINITIONS(CloudClient,CloudClient);
CloudClient::CloudClient()
{
callback=0;
allocator=&unsetDefaultAllocator;
}
CloudClient::~CloudClient()
{
}
void CloudClient::SetDefaultCallbacks(CloudAllocator *_allocator, CloudClientCallback *_callback)
{
callback=_callback;
allocator=_allocator;
}
void CloudClient::Post(CloudKey *cloudKey, const unsigned char *data, uint32_t dataLengthBytes, RakNetGUID systemIdentifier)
{
RakAssert(cloudKey);
RakNet::BitStream bsOut;
bsOut.Write((MessageID)ID_CLOUD_POST_REQUEST);
cloudKey->Serialize(true,&bsOut);
if (data==0)
dataLengthBytes=0;
bsOut.Write(dataLengthBytes);
if (dataLengthBytes>0)
bsOut.WriteAlignedBytes((const unsigned char*) data, dataLengthBytes);
SendUnified(&bsOut, HIGH_PRIORITY, RELIABLE_ORDERED, 0, systemIdentifier, false);
}
void CloudClient::Release(DataStructures::List<CloudKey> &keys, RakNetGUID systemIdentifier)
{
RakNet::BitStream bsOut;
bsOut.Write((MessageID)ID_CLOUD_RELEASE_REQUEST);
RakAssert(keys.Size() < (uint16_t)-1 );
bsOut.WriteCasted<uint16_t>(keys.Size());
for (uint16_t i=0; i < keys.Size(); i++)
{
keys[i].Serialize(true,&bsOut);
}
SendUnified(&bsOut, HIGH_PRIORITY, RELIABLE_ORDERED, 0, systemIdentifier, false);
}
bool CloudClient::Get(CloudQuery *keyQuery, RakNetGUID systemIdentifier)
{
RakNet::BitStream bsOut;
bsOut.Write((MessageID)ID_CLOUD_GET_REQUEST);
keyQuery->Serialize(true, &bsOut);
bsOut.WriteCasted<uint16_t>(0); // Specific systems
SendUnified(&bsOut, HIGH_PRIORITY, RELIABLE_ORDERED, 0, systemIdentifier, false);
return true;
}
bool CloudClient::Get(CloudQuery *keyQuery, DataStructures::List<RakNetGUID> &specificSystems, RakNetGUID systemIdentifier)
{
RakNet::BitStream bsOut;
bsOut.Write((MessageID)ID_CLOUD_GET_REQUEST);
keyQuery->Serialize(true, &bsOut);
bsOut.WriteCasted<uint16_t>(specificSystems.Size());
RakAssert(specificSystems.Size() < (uint16_t)-1 );
for (uint16_t i=0; i < specificSystems.Size(); i++)
{
bsOut.Write(specificSystems[i]);
}
SendUnified(&bsOut, HIGH_PRIORITY, RELIABLE_ORDERED, 0, systemIdentifier, false);
return true;
}
bool CloudClient::Get(CloudQuery *keyQuery, DataStructures::List<CloudQueryRow*> &specificSystems, RakNetGUID systemIdentifier)
{
RakNet::BitStream bsOut;
bsOut.Write((MessageID)ID_CLOUD_GET_REQUEST);
keyQuery->Serialize(true, &bsOut);
bsOut.WriteCasted<uint16_t>(specificSystems.Size());
RakAssert(specificSystems.Size() < (uint16_t)-1 );
for (uint16_t i=0; i < specificSystems.Size(); i++)
{
if (specificSystems[i]->clientGUID!=UNASSIGNED_RAKNET_GUID)
{
bsOut.Write(true);
bsOut.Write(specificSystems[i]->clientGUID);
}
else
{
bsOut.Write(false);
bsOut.Write(specificSystems[i]->clientSystemAddress);
}
}
SendUnified(&bsOut, HIGH_PRIORITY, RELIABLE_ORDERED, 0, systemIdentifier, false);
return true;
}
void CloudClient::Unsubscribe(DataStructures::List<CloudKey> &keys, RakNetGUID systemIdentifier)
{
RakNet::BitStream bsOut;
bsOut.Write((MessageID)ID_CLOUD_UNSUBSCRIBE_REQUEST);
RakAssert(keys.Size() < (uint16_t)-1 );
bsOut.WriteCasted<uint16_t>(keys.Size());
for (uint16_t i=0; i < keys.Size(); i++)
{
keys[i].Serialize(true,&bsOut);
}
bsOut.WriteCasted<uint16_t>(0);
SendUnified(&bsOut, HIGH_PRIORITY, RELIABLE_ORDERED, 0, systemIdentifier, false);
}
void CloudClient::Unsubscribe(DataStructures::List<CloudKey> &keys, DataStructures::List<RakNetGUID> &specificSystems, RakNetGUID systemIdentifier)
{
RakNet::BitStream bsOut;
bsOut.Write((MessageID)ID_CLOUD_UNSUBSCRIBE_REQUEST);
RakAssert(keys.Size() < (uint16_t)-1 );
bsOut.WriteCasted<uint16_t>(keys.Size());
for (uint16_t i=0; i < keys.Size(); i++)
{
keys[i].Serialize(true,&bsOut);
}
bsOut.WriteCasted<uint16_t>(specificSystems.Size());
RakAssert(specificSystems.Size() < (uint16_t)-1 );
for (uint16_t i=0; i < specificSystems.Size(); i++)
{
bsOut.Write(specificSystems[i]);
}
SendUnified(&bsOut, HIGH_PRIORITY, RELIABLE_ORDERED, 0, systemIdentifier, false);
}
void CloudClient::Unsubscribe(DataStructures::List<CloudKey> &keys, DataStructures::List<CloudQueryRow*> &specificSystems, RakNetGUID systemIdentifier)
{
RakNet::BitStream bsOut;
bsOut.Write((MessageID)ID_CLOUD_UNSUBSCRIBE_REQUEST);
RakAssert(keys.Size() < (uint16_t)-1 );
bsOut.WriteCasted<uint16_t>(keys.Size());
for (uint16_t i=0; i < keys.Size(); i++)
{
keys[i].Serialize(true,&bsOut);
}
bsOut.WriteCasted<uint16_t>(specificSystems.Size());
RakAssert(specificSystems.Size() < (uint16_t)-1 );
for (uint16_t i=0; i < specificSystems.Size(); i++)
{
if (specificSystems[i]->clientGUID!=UNASSIGNED_RAKNET_GUID)
{
bsOut.Write(true);
bsOut.Write(specificSystems[i]->clientGUID);
}
else
{
bsOut.Write(false);
bsOut.Write(specificSystems[i]->clientSystemAddress);
}
}
SendUnified(&bsOut, HIGH_PRIORITY, RELIABLE_ORDERED, 0, systemIdentifier, false);
}
PluginReceiveResult CloudClient::OnReceive(Packet *packet)
{
(void) packet;
return RR_CONTINUE_PROCESSING;
}
void CloudClient::OnGetReponse(Packet *packet, CloudClientCallback *_callback, CloudAllocator *_allocator)
{
if (_callback==0)
_callback=callback;
if (_allocator==0)
_allocator=allocator;
CloudQueryResult cloudQueryResult;
RakNet::BitStream bsIn(packet->data, packet->length, false);
bsIn.IgnoreBytes(sizeof(MessageID));
cloudQueryResult.Serialize(false,&bsIn,_allocator);
bool deallocateRowsAfterReturn=true;
_callback->OnGet(&cloudQueryResult, &deallocateRowsAfterReturn);
if (deallocateRowsAfterReturn)
{
unsigned int i;
for (i=0; i < cloudQueryResult.rowsReturned.Size(); i++)
{
_allocator->DeallocateRowData(cloudQueryResult.rowsReturned[i]->data);
_allocator->DeallocateCloudQueryRow(cloudQueryResult.rowsReturned[i]);
}
}
}
void CloudClient::OnGetReponse(CloudQueryResult *cloudQueryResult, Packet *packet, CloudAllocator *_allocator)
{
if (_allocator==0)
_allocator=allocator;
RakNet::BitStream bsIn(packet->data, packet->length, false);
bsIn.IgnoreBytes(sizeof(MessageID));
cloudQueryResult->Serialize(false,&bsIn,_allocator);
}
void CloudClient::OnSubscriptionNotification(Packet *packet, CloudClientCallback *_callback, CloudAllocator *_allocator)
{
if (_callback==0)
_callback=callback;
if (_allocator==0)
_allocator=allocator;
bool wasUpdated=false;
CloudQueryRow row;
RakNet::BitStream bsIn(packet->data, packet->length, false);
bsIn.IgnoreBytes(sizeof(MessageID));
bsIn.Read(wasUpdated);
row.Serialize(false,&bsIn,_allocator);
bool deallocateRowAfterReturn=true;
_callback->OnSubscriptionNotification(&row, wasUpdated, &deallocateRowAfterReturn);
if (deallocateRowAfterReturn)
{
_allocator->DeallocateRowData(row.data);
}
}
void CloudClient::OnSubscriptionNotification(bool *wasUpdated, CloudQueryRow *row, Packet *packet, CloudAllocator *_allocator)
{
if (_allocator==0)
_allocator=allocator;
RakNet::BitStream bsIn(packet->data, packet->length, false);
bsIn.IgnoreBytes(sizeof(MessageID));
bool b=false;
bsIn.Read(b);
*wasUpdated=b;
row->Serialize(false,&bsIn,_allocator);
}
void CloudClient::DeallocateWithDefaultAllocator(CloudQueryResult *cloudQueryResult)
{
unsigned int i;
for (i=0; i < cloudQueryResult->rowsReturned.Size(); i++)
{
allocator->DeallocateRowData(cloudQueryResult->rowsReturned[i]->data);
allocator->DeallocateCloudQueryRow(cloudQueryResult->rowsReturned[i]);
}
}
void CloudClient::DeallocateWithDefaultAllocator(CloudQueryRow *row)
{
allocator->DeallocateRowData(row->data);
}
#endif

View File

@@ -0,0 +1,163 @@
/// \file CloudClient.h
/// \brief Queries CloudMemoryServer to download data that other clients have uploaded
///
/// This file is part of RakNet Copyright 2003 Jenkins Software LLC
///
/// Usage of RakNet is subject to the appropriate license agreement.
#include "NativeFeatureIncludes.hpp"
#if _RAKNET_SUPPORT_CloudClient==1
#ifndef __CLOUD_CLIENT_H
#define __CLOUD_CLIENT_H
#include "PluginInterface2.hpp"
#include "CloudCommon.hpp"
#include "RakMemoryOverride.hpp"
#include "DS_Hash.hpp"
namespace RakNet
{
/// Forward declarations
class RakPeerInterface;
class CloudClientCallback;
/// \defgroup CLOUD_GROUP CloudComputing
/// \brief Contains the CloudClient and CloudServer plugins
/// \details The CloudServer plugins operates on requests from the CloudClient plugin. The servers are in a fully connected mesh topology, which the clients are connected to any server. Clients can interact with each other by posting and subscribing to memory updates, without being directly connected or even knowing about each other.
/// \ingroup PLUGINS_GROUP
/// \brief Performs Post() and Get() operations on CloudMemoryServer
/// \details A CloudClient is a computer connected to one or more servers in a cloud configuration. Operations by one CloudClient can be received and subscribed to by other instances of CloudClient, without those clients being connected, even on different servers.
/// \ingroup CLOUD_GROUP
class RAK_DLL_EXPORT CloudClient : public PluginInterface2
{
public:
// GetInstance() and DestroyInstance(instance*)
STATIC_FACTORY_DECLARATIONS(CloudClient)
CloudClient();
virtual ~CloudClient();
/// \brief Set the default callbacks for OnGetReponse(), OnSubscriptionNotification(), and OnSubscriptionDataDeleted()
/// \details Pointers to CloudAllocator and CloudClientCallback can be stored by the system if desired. If a callback is not provided to OnGetReponse(), OnSubscriptionNotification(), OnSubscriptionDataDeleted(), the callback passed here will be used instead.
/// \param[in] _allocator An instance of CloudAllocator
/// \param[in] _callback An instance of CloudClientCallback
virtual void SetDefaultCallbacks(CloudAllocator *_allocator, CloudClientCallback *_callback);
/// \brief Uploads data to the cloud
/// \details Data uploaded to the cloud will be stored by the server sent to, identified by \a systemIdentifier.
/// As long as you are connected to this server, the data will persist. Queries for that data by the Get() operation will
/// return the RakNetGUID and SystemAddress of the uploader, as well as the data itself.
/// Furthermore, if any clients are subscribed to the particular CloudKey passed, those clients will get update notices that the data has changed
/// Passing data with the same \a cloudKey more than once will overwrite the prior value.
/// This call will silently fail if CloudServer::SetMaxUploadBytesPerClient() is exceeded
/// \param[in] cloudKey Identifies the data being uploaded
/// \param[in] data A pointer to data to upload. This pointer does not need to persist past the call
/// \param[in] dataLengthBytes The length in bytes of \a data
/// \param[in] systemIdentifier A remote system running CloudServer that we are already connected to.
virtual void Post(CloudKey *cloudKey, const unsigned char *data, uint32_t dataLengthBytes, RakNetGUID systemIdentifier);
/// \brief Releases one or more data previously uploaded with Post()
/// \details If a remote system has subscribed to one or more of the \a keys uploaded, they will get ID_CLOUD_SUBSCRIPTION_NOTIFICATION notifications containing the last value uploaded before deletions
/// \param[in] cloudKey Identifies the data to release. It is possible to remove uploads from multiple Post() calls at once.
/// \param[in] systemIdentifier A remote system running CloudServer that we are already connected to.
virtual void Release(DataStructures::List<CloudKey> &keys, RakNetGUID systemIdentifier);
/// \brief Gets data from the cloud
/// \details For a given query containing one or more keys, return data that matches those keys.
/// The values will be returned in the ID_CLOUD_GET_RESPONSE packet, which should be passed to OnGetReponse() and will invoke CloudClientCallback::OnGet()
/// CloudQuery::startingRowIndex is used to skip the first n values that would normally be returned..
/// CloudQuery::maxRowsToReturn is used to limit the number of rows returned. The number of rows returned may also be limited by CloudServer::SetMaxBytesPerDownload();
/// CloudQuery::subscribeToResults if set to true, will cause ID_CLOUD_SUBSCRIPTION_NOTIFICATION to be returned to us when any of the keys in the query are updated or are deleted.
/// ID_CLOUD_GET_RESPONSE will be returned even if subscribing to the result list. Only later updates will return ID_CLOUD_SUBSCRIPTION_NOTIFICATION.
/// Calling Get() with CloudQuery::subscribeToResults false, when you are already subscribed, does not remove the subscription. Use Unsubscribe() for this.
/// Resubscribing using the same CloudKey but a different or no \a specificSystems overwrites the subscribed systems for those keys.
/// \param[in] cloudQuery One or more keys, and optional parameters to perform with the Get
/// \param[in] systemIdentifier A remote system running CloudServer that we are already connected to.
/// \param[in] specificSystems It is possible to get or subscribe to updates only for specific uploading CloudClient instances. Pass the desired instances here. The overload that does not have the specificSystems parameter is treated as subscribing to all updates from all clients.
virtual bool Get(CloudQuery *cloudQuery, RakNetGUID systemIdentifier);
virtual bool Get(CloudQuery *cloudQuery, DataStructures::List<RakNetGUID> &specificSystems, RakNetGUID systemIdentifier);
virtual bool Get(CloudQuery *cloudQuery, DataStructures::List<CloudQueryRow*> &specificSystems, RakNetGUID systemIdentifier);
/// \brief Unsubscribe from updates previously subscribed to using Get() with the CloudQuery::subscribeToResults set to true
/// The \a keys and \a specificSystems parameters are logically treated as AND when checking subscriptions on the server
/// The overload that does not take specificSystems unsubscribes to all passed keys, regardless of system
/// You cannot unsubscribe specific systems when previously subscribed to updates from any system. To do this, first Unsubscribe() from all systems, and call Get() with the \a specificSystems parameter explicilty listing the systems you want to subscribe to.
virtual void Unsubscribe(DataStructures::List<CloudKey> &keys, RakNetGUID systemIdentifier);
virtual void Unsubscribe(DataStructures::List<CloudKey> &keys, DataStructures::List<RakNetGUID> &specificSystems, RakNetGUID systemIdentifier);
virtual void Unsubscribe(DataStructures::List<CloudKey> &keys, DataStructures::List<CloudQueryRow*> &specificSystems, RakNetGUID systemIdentifier);
/// \brief Call this when you get ID_CLOUD_GET_RESPONSE
/// If \a callback or \a allocator are 0, the default callbacks passed to SetDefaultCallbacks() are used
/// \param[in] packet Packet structure returned from RakPeerInterface
/// \param[in] _callback Callback to be called from the function containing output parameters. If 0, default is used.
/// \param[in] _allocator Allocator to be used to allocate data. If 0, default is used.
virtual void OnGetReponse(Packet *packet, CloudClientCallback *_callback=0, CloudAllocator *_allocator=0);
/// \brief Call this when you get ID_CLOUD_GET_RESPONSE
/// Different form of OnGetReponse that returns to a structure that you pass, instead of using a callback
/// You are responsible for deallocation with this form
/// If \a allocator is 0, the default callback passed to SetDefaultCallbacks() are used
/// \param[out] cloudQueryResult A pointer to a structure that will be filled out with data
/// \param[in] packet Packet structure returned from RakPeerInterface
/// \param[in] _allocator Allocator to be used to allocate data. If 0, default is used.
virtual void OnGetReponse(CloudQueryResult *cloudQueryResult, Packet *packet, CloudAllocator *_allocator=0);
/// \brief Call this when you get ID_CLOUD_SUBSCRIPTION_NOTIFICATION
/// If \a callback or \a allocator are 0, the default callbacks passed to SetDefaultCallbacks() are used
/// \param[in] packet Packet structure returned from RakPeerInterface
/// \param[in] _callback Callback to be called from the function containing output parameters. If 0, default is used.
/// \param[in] _allocator Allocator to be used to allocate data. If 0, default is used.
virtual void OnSubscriptionNotification(Packet *packet, CloudClientCallback *_callback=0, CloudAllocator *_allocator=0);
/// \brief Call this when you get ID_CLOUD_SUBSCRIPTION_NOTIFICATION
/// Different form of OnSubscriptionNotification that returns to a structure that you pass, instead of using a callback
/// You are responsible for deallocation with this form
/// If \a allocator is 0, the default callback passed to SetDefaultCallbacks() are used
/// \param[out] wasUpdated If true, the row was updated. If false, it was deleted. \a result will contain the last value just before deletion
/// \param[out] row A pointer to a structure that will be filled out with data
/// \param[in] packet Packet structure returned from RakPeerInterface
/// \param[in] _allocator Allocator to be used to allocate data. If 0, default is used.
virtual void OnSubscriptionNotification(bool *wasUpdated, CloudQueryRow *row, Packet *packet, CloudAllocator *_allocator=0);
/// If you never specified an allocator, and used the non-callback form of OnGetReponse(), deallocate cloudQueryResult with this function
virtual void DeallocateWithDefaultAllocator(CloudQueryResult *cloudQueryResult);
/// If you never specified an allocator, and used the non-callback form of OnSubscriptionNotification(), deallocate row with this function
virtual void DeallocateWithDefaultAllocator(CloudQueryRow *row);
protected:
PluginReceiveResult OnReceive(Packet *packet);
CloudClientCallback *callback;
CloudAllocator *allocator;
CloudAllocator unsetDefaultAllocator;
};
/// \ingroup CLOUD_GROUP
/// Parses ID_CLOUD_GET_RESPONSE and ID_CLOUD_SUBSCRIPTION_NOTIFICATION in a convenient callback form
class RAK_DLL_EXPORT CloudClientCallback
{
public:
CloudClientCallback() {}
virtual ~CloudClientCallback() {}
/// \brief Called in response to ID_CLOUD_GET_RESPONSE
/// \param[out] result Contains the original query passed to Get(), and a list of rows returned.
/// \param[out] deallocateRowsAfterReturn CloudQueryResult::rowsReturned will be deallocated after the function returns by default. Set to false to not deallocate these pointers. The pointers are allocated through CloudAllocator.
virtual void OnGet(RakNet::CloudQueryResult *result, bool *deallocateRowsAfterReturn) {(void) result; (void) deallocateRowsAfterReturn;}
/// \brief Called in response to ID_CLOUD_SUBSCRIPTION_NOTIFICATION
/// \param[out] result Contains the row updated
/// \param[out] wasUpdated If true, the row was updated. If false, it was deleted. \a result will contain the last value just before deletion
/// \param[out] deallocateRowAfterReturn \a result will be deallocated after the function returns by default. Set to false to not deallocate these pointers. The pointers are allocated through CloudAllocator.
virtual void OnSubscriptionNotification(RakNet::CloudQueryRow *result, bool wasUpdated, bool *deallocateRowAfterReturn) {(void) result; (void) wasUpdated; (void) deallocateRowAfterReturn;}
};
} // namespace RakNet
#endif
#endif // _RAKNET_SUPPORT_*

View File

@@ -0,0 +1,159 @@
#include "NativeFeatureIncludes.hpp"
#if _RAKNET_SUPPORT_CloudClient==1 || _RAKNET_SUPPORT_CloudServer==1
#include "CloudCommon.hpp"
#include "BitStream.hpp"
using namespace RakNet;
int RakNet::CloudKeyComp(const CloudKey &key, const CloudKey &data)
{
if (key.primaryKey < data.primaryKey)
return -1;
if (key.primaryKey > data.primaryKey)
return 1;
if (key.secondaryKey < data.secondaryKey)
return -1;
if (key.secondaryKey > data.secondaryKey)
return 1;
return 0;
}
CloudQueryRow* CloudAllocator::AllocateCloudQueryRow(void)
{
return RakNet::OP_NEW<CloudQueryRow>(_FILE_AND_LINE_);
}
void CloudAllocator::DeallocateCloudQueryRow(CloudQueryRow *row)
{
RakNet::OP_DELETE(row,_FILE_AND_LINE_);
}
unsigned char *CloudAllocator::AllocateRowData(uint32_t bytesNeededForData)
{
return (unsigned char*) rakMalloc_Ex(bytesNeededForData,_FILE_AND_LINE_);
}
void CloudAllocator::DeallocateRowData(void *data)
{
rakFree_Ex(data, _FILE_AND_LINE_);
}
void CloudKey::Serialize(bool writeToBitstream, BitStream *bitStream)
{
bitStream->Serialize(writeToBitstream, primaryKey);
bitStream->Serialize(writeToBitstream, secondaryKey);
}
void CloudQuery::Serialize(bool writeToBitstream, BitStream *bitStream)
{
bool startingRowIndexIsZero=0;
bool maxRowsToReturnIsZero=0;
startingRowIndexIsZero=startingRowIndex==0;
maxRowsToReturnIsZero=maxRowsToReturn==0;
bitStream->Serialize(writeToBitstream,startingRowIndexIsZero);
bitStream->Serialize(writeToBitstream,maxRowsToReturnIsZero);
bitStream->Serialize(writeToBitstream,subscribeToResults);
if (startingRowIndexIsZero==false)
bitStream->Serialize(writeToBitstream,startingRowIndex);
if (maxRowsToReturnIsZero==false)
bitStream->Serialize(writeToBitstream,maxRowsToReturn);
RakAssert(keys.Size()<(uint16_t)-1);
uint16_t numKeys = (uint16_t) keys.Size();
bitStream->Serialize(writeToBitstream,numKeys);
if (writeToBitstream)
{
for (uint16_t i=0; i < numKeys; i++)
{
keys[i].Serialize(true,bitStream);
}
}
else
{
CloudKey cmdk;
for (uint16_t i=0; i < numKeys; i++)
{
cmdk.Serialize(false,bitStream);
keys.Push(cmdk, _FILE_AND_LINE_);
}
}
}
void CloudQueryRow::Serialize(bool writeToBitstream, BitStream *bitStream, CloudAllocator *allocator)
{
key.Serialize(writeToBitstream,bitStream);
bitStream->Serialize(writeToBitstream,serverSystemAddress);
bitStream->Serialize(writeToBitstream,clientSystemAddress);
bitStream->Serialize(writeToBitstream,serverGUID);
bitStream->Serialize(writeToBitstream,clientGUID);
bitStream->Serialize(writeToBitstream,length);
if (writeToBitstream)
{
bitStream->WriteAlignedBytes((const unsigned char*) data,length);
}
else
{
if (length>0)
{
data = allocator->AllocateRowData(length);
if (data)
{
bitStream->ReadAlignedBytes((unsigned char *) data,length);
}
else
{
notifyOutOfMemory(_FILE_AND_LINE_);
}
}
else
data=0;
}
}
void CloudQueryResult::SerializeHeader(bool writeToBitstream, BitStream *bitStream)
{
cloudQuery.Serialize(writeToBitstream,bitStream);
bitStream->Serialize(writeToBitstream,subscribeToResults);
}
void CloudQueryResult::SerializeNumRows(bool writeToBitstream, uint32_t &numRows, BitStream *bitStream)
{
bitStream->Serialize(writeToBitstream,numRows);
}
void CloudQueryResult::SerializeCloudQueryRows(bool writeToBitstream, uint32_t &numRows, BitStream *bitStream, CloudAllocator *allocator)
{
if (writeToBitstream)
{
for (uint16_t i=0; i < numRows; i++)
{
rowsReturned[i]->Serialize(true,bitStream, allocator);
}
}
else
{
CloudQueryRow* cmdr;
for (uint16_t i=0; i < numRows; i++)
{
cmdr = allocator->AllocateCloudQueryRow();
if (cmdr)
{
cmdr->Serialize(false,bitStream,allocator);
if (cmdr->data==0 && cmdr->length>0)
{
allocator->DeallocateCloudQueryRow(cmdr);
notifyOutOfMemory(_FILE_AND_LINE_);
numRows=i;
return;
}
rowsReturned.Push(cmdr, _FILE_AND_LINE_);
}
else
{
notifyOutOfMemory(_FILE_AND_LINE_);
numRows=i;
return;
}
}
}
}
void CloudQueryResult::Serialize(bool writeToBitstream, BitStream *bitStream, CloudAllocator *allocator)
{
SerializeHeader(writeToBitstream, bitStream);
uint32_t numRows = (uint32_t) rowsReturned.Size();
SerializeNumRows(writeToBitstream, numRows, bitStream);
SerializeCloudQueryRows(writeToBitstream, numRows, bitStream, allocator);
}
#endif // #if _RAKNET_SUPPORT_CloudMemoryClient==1 || _RAKNET_SUPPORT_CloudMemoryServer==1

View File

@@ -0,0 +1,141 @@
#include "NativeFeatureIncludes.hpp"
#if _RAKNET_SUPPORT_CloudClient==1 || _RAKNET_SUPPORT_CloudServer==1
#ifndef __CLOUD_COMMON_H
#define __CLOUD_COMMON_H
#include "RakNetTypes.hpp"
#include "RakString.hpp"
namespace RakNet
{
class BitStream;
struct CloudQueryRow;
/// Allocates CloudQueryRow and the row data. Override to use derived classes or different allocators
/// \ingroup CLOUD_GROUP
class RAK_DLL_EXPORT CloudAllocator
{
public:
CloudAllocator() {}
virtual ~CloudAllocator() {}
/// \brief Allocate a row
virtual CloudQueryRow* AllocateCloudQueryRow(void);
/// \brief Free a row
virtual void DeallocateCloudQueryRow(CloudQueryRow *row);
/// \brief Allocate CloudQueryRow::data
virtual unsigned char *AllocateRowData(uint32_t bytesNeededForData);
/// \brief Free CloudQueryRow::data
virtual void DeallocateRowData(void *data);
};
/// Serves as a key to identify data uploaded to or queried from the server.
/// \ingroup CLOUD_GROUP
struct RAK_DLL_EXPORT CloudKey
{
CloudKey() {}
CloudKey(RakNet::RakString _primaryKey, uint32_t _secondaryKey) : primaryKey(_primaryKey), secondaryKey(_secondaryKey) {}
~CloudKey() {}
/// Identifies the primary key. This is intended to be a major category, such as the name of the application
/// Must be non-empty
RakNet::RakString primaryKey;
/// Identifies the secondary key. This is intended to be a subcategory enumeration, such as PLAYER_LIST or RUNNING_SCORES
uint32_t secondaryKey;
/// \internal
void Serialize(bool writeToBitstream, BitStream *bitStream);
};
/// \internal
int CloudKeyComp(const CloudKey &key, const CloudKey &data);
/// Data members used to query the cloud
/// \ingroup CLOUD_GROUP
struct RAK_DLL_EXPORT CloudQuery
{
CloudQuery() {startingRowIndex=0; maxRowsToReturn=0; subscribeToResults=false;}
/// List of keys to query. Must be at least of length 1.
/// This query is run on uploads from all clients, and those that match the combination of primaryKey and secondaryKey are potentially returned
/// If you pass more than one key at a time, the results are concatenated so if you need to differentiate between queries then send two different queries
DataStructures::List<CloudKey> keys;
/// If limiting the number of rows to return, this is the starting offset into the list. Has no effect unless maxRowsToReturn is > 0
uint32_t startingRowIndex;
/// Maximum number of rows to return. Actual number may still be less than this. Pass 0 to mean no-limit.
uint32_t maxRowsToReturn;
/// If true, automatically get updates as the results returned to you change. Unsubscribe with CloudMemoryClient::Unsubscribe()
bool subscribeToResults;
/// \internal
void Serialize(bool writeToBitstream, BitStream *bitStream);
};
/// \ingroup CLOUD_GROUP
struct RAK_DLL_EXPORT CloudQueryRow
{
/// Key used to identify this data
CloudKey key;
/// Data uploaded
unsigned char *data;
/// Length of data uploaded
uint32_t length;
/// System address of server that is holding this data, and the client is connected to
SystemAddress serverSystemAddress;
/// System address of client that uploaded this data
SystemAddress clientSystemAddress;
/// RakNetGUID of server that is holding this data, and the client is connected to
RakNetGUID serverGUID;
/// RakNetGUID of client that uploaded this data
RakNetGUID clientGUID;
/// \internal
void Serialize(bool writeToBitstream, BitStream *bitStream, CloudAllocator *allocator);
};
/// \ingroup CLOUD_GROUP
struct RAK_DLL_EXPORT CloudQueryResult
{
/// Query originally passed to Download()
CloudQuery cloudQuery;
/// Results returned from query. If there were multiple keys in CloudQuery::keys then see resultKeyIndices
DataStructures::List<CloudQueryRow*> rowsReturned;
/// If there were multiple keys in CloudQuery::keys, then each key is processed in order and the result concatenated to rowsReturned
/// The starting index of each query is written to resultKeyIndices
/// For example, if CloudQuery::keys had 4 keys, returning 3 rows, 0, rows, 5 rows, and 12 rows then
/// resultKeyIndices would be 0, 3, 3, 8
DataStructures::List<uint32_t> resultKeyIndices;
/// Whatever was passed to CloudClient::Get() as CloudQuery::subscribeToResults
bool subscribeToResults;
/// \internal
void Serialize(bool writeToBitstream, BitStream *bitStream, CloudAllocator *allocator);
/// \internal
void SerializeHeader(bool writeToBitstream, BitStream *bitStream);
/// \internal
void SerializeNumRows(bool writeToBitstream, uint32_t &numRows, BitStream *bitStream);
/// \internal
void SerializeCloudQueryRows(bool writeToBitstream, uint32_t &numRows, BitStream *bitStream, CloudAllocator *allocator);
};
} // Namespace RakNet
#endif // __CLOUD_COMMON_H
#endif // #if _RAKNET_SUPPORT_CloudClient==1 || _RAKNET_SUPPORT_CloudServer==1

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,375 @@
/// \file CloudServer.h
/// \brief Stores client data, and allows cross-server communication to retrieve this data
/// \details TODO
///
/// This file is part of RakNet Copyright 2003 Jenkins Software LLC
///
/// Usage of RakNet is subject to the appropriate license agreement.
#include "NativeFeatureIncludes.hpp"
#if _RAKNET_SUPPORT_CloudServer==1
#ifndef __CLOUD_SERVER_H
#define __CLOUD_SERVER_H
#include "PluginInterface2.hpp"
#include "RakMemoryOverride.hpp"
#include "NativeTypes.hpp"
#include "RakString.hpp"
#include "DS_Hash.hpp"
#include "CloudCommon.hpp"
#include "DS_OrderedList.hpp"
/// If the data is smaller than this value, an allocation is avoid. However, this value exists for every row
#define CLOUD_SERVER_DATA_STACK_SIZE 32
namespace RakNet
{
/// Forward declarations
class RakPeerInterface;
/// \brief Zero or more instances of CloudServerQueryFilter can be attached to CloudServer to restrict client queries
/// All attached instances of CloudServerQueryFilter on each corresponding operation, from all directly connected clients
/// If any attached instance returns false for a given operation, that operation is silently rejected
/// \ingroup CLOUD_GROUP
class RAK_DLL_EXPORT CloudServerQueryFilter
{
public:
CloudServerQueryFilter() {}
virtual ~CloudServerQueryFilter() {}
/// Called when a local client wants to post data
/// \return true to allow, false to reject
virtual bool OnPostRequest(RakNetGUID clientGuid, SystemAddress clientAddress, CloudKey key, uint32_t dataLength, const char *data)=0;
/// Called when a local client wants to release data that it has previously uploaded
/// \return true to allow, false to reject
virtual bool OnReleaseRequest(RakNetGUID clientGuid, SystemAddress clientAddress, DataStructures::List<CloudKey> &cloudKeys)=0;
/// Called when a local client wants to query data
/// If you return false, the client will get no response at all
/// \return true to allow, false to reject
virtual bool OnGetRequest(RakNetGUID clientGuid, SystemAddress clientAddress, CloudQuery &query, DataStructures::List<RakNetGUID> &specificSystems)=0;
/// Called when a local client wants to stop getting updates for data
/// If you return false, the client will keep getting updates for that data
/// \return true to allow, false to reject
virtual bool OnUnsubscribeRequest(RakNetGUID clientGuid, SystemAddress clientAddress, DataStructures::List<CloudKey> &cloudKeys, DataStructures::List<RakNetGUID> &specificSystems)=0;
};
/// \brief Stores client data, and allows cross-server communication to retrieve this data
/// \ingroup CLOUD_GROUP
class RAK_DLL_EXPORT CloudServer : public PluginInterface2, CloudAllocator
{
public:
// GetInstance() and DestroyInstance(instance*)
STATIC_FACTORY_DECLARATIONS(CloudServer)
CloudServer();
virtual ~CloudServer();
/// \brief Max bytes a client can upload
/// Data in excess of this value is silently ignored
/// defaults to 0 (unlimited)
/// \param[in] bytes Max bytes a client can upload. 0 means unlimited.
void SetMaxUploadBytesPerClient(uint64_t bytes);
/// \brief Max bytes returned by a download. If the number of bytes would exceed this amount, the returned list is truncated
/// However, if this would result in no rows downloaded, then one row will be returned.
/// \param[in] bytes Max bytes a client can download from a single Get(). 0 means unlimited.
void SetMaxBytesPerDownload(uint64_t bytes);
/// \brief Add a server, which is assumed to be connected in a fully connected mesh to all other servers and also running the CloudServer plugin
/// The other system must also call AddServer before getting the subscription data, or it will be rejected.
/// Sending a message telling the other system to call AddServer(), followed by calling AddServer() locally, would be sufficient for this to work.
/// \note This sends subscription data to the other system, using RELIABLE_ORDERED on channel 0
/// \param[in] systemIdentifier Identifier of the remote system
void AddServer(RakNetGUID systemIdentifier);
/// \brief Removes a server added through AddServer()
/// \param[in] systemIdentifier Identifier of the remote system
void RemoveServer(RakNetGUID systemIdentifier);
/// Return list of servers added with AddServer()
/// \param[out] remoteServers List of servers added
void GetRemoteServers(DataStructures::List<RakNetGUID> &remoteServersOut);
/// \brief Frees all memory. Does not remove query filters
void Clear(void);
/// \brief Report the specified SystemAddress to client queries, rather than what RakPeer reads.
/// This is useful if you already know your public IP
/// This only applies to future updates, so call it before updating to apply to all queries
/// \param[in] forcedAddress The systmeAddress to return in queries. Use UNASSIGNED_SYSTEM_ADDRESS (default) to use what RakPeer returns
void ForceExternalSystemAddress(SystemAddress forcedAddress);
/// \brief Adds a callback called on each query. If all filters returns true for an operation, the operation is allowed.
/// If the filter was already added, the function silently fails
/// \param[in] filter An externally allocated instance of CloudServerQueryFilter. The instance must remain valid until it is removed with RemoveQueryFilter() or RemoveAllQueryFilters()
void AddQueryFilter(CloudServerQueryFilter* filter);
/// \brief Removes a callback added with AddQueryFilter()
/// The instance is not deleted, only unreferenced. It is up to the user to delete the instance, if necessary
/// \param[in] filter An externally allocated instance of CloudServerQueryFilter. The instance must remain valid until it is removed with RemoveQueryFilter() or RemoveAllQueryFilters()
void RemoveQueryFilter(CloudServerQueryFilter* filter);
/// \brief Removes all instances of CloudServerQueryFilter added with AddQueryFilter().
/// The instances are not deleted, only unreferenced. It is up to the user to delete the instances, if necessary
void RemoveAllQueryFilters(void);
protected:
virtual void Update(void);
virtual PluginReceiveResult OnReceive(Packet *packet);
virtual void OnClosedConnection(const SystemAddress &systemAddress, RakNetGUID rakNetGUID, PI2_LostConnectionReason lostConnectionReason );
virtual void OnRakPeerShutdown(void);
virtual void OnPostRequest(Packet *packet);
virtual void OnReleaseRequest(Packet *packet);
virtual void OnGetRequest(Packet *packet);
virtual void OnUnsubscribeRequest(Packet *packet);
virtual void OnServerToServerGetRequest(Packet *packet);
virtual void OnServerToServerGetResponse(Packet *packet);
uint64_t maxUploadBytesPerClient, maxBytesPerDowload;
// ----------------------------------------------------------------------------
// For a given data key, quickly look up one or all systems that have uploaded
// ----------------------------------------------------------------------------
struct CloudData
{
CloudData() {}
~CloudData() {if (allocatedData) rakFree_Ex(allocatedData, _FILE_AND_LINE_);}
bool IsUnused(void) const {return isUploaded==false && specificSubscribers.Size()==0;}
void Clear(void) {if (dataPtr==allocatedData) rakFree_Ex(allocatedData, _FILE_AND_LINE_); allocatedData=0; dataPtr=0; dataLengthBytes=0; isUploaded=false;}
unsigned char stackData[CLOUD_SERVER_DATA_STACK_SIZE];
unsigned char *allocatedData; // Uses allocatedData instead of stackData if length of data exceeds CLOUD_SERVER_DATA_STACK_SIZE
unsigned char *dataPtr; // Points to either stackData or allocatedData
uint32_t dataLengthBytes;
bool isUploaded;
/// System address of server that is holding this data, and the client is connected to
SystemAddress serverSystemAddress;
/// System address of client that uploaded this data
SystemAddress clientSystemAddress;
/// RakNetGUID of server that is holding this data, and the client is connected to
RakNetGUID serverGUID;
/// RakNetGUID of client that uploaded this data
RakNetGUID clientGUID;
/// When the key data changes from this particular system, notify these subscribers
/// This list mutually exclusive with CloudDataList::nonSpecificSubscribers
DataStructures::OrderedList<RakNetGUID, RakNetGUID> specificSubscribers;
};
void WriteCloudQueryRowFromResultList(unsigned int i, DataStructures::List<CloudData*> &cloudDataResultList, DataStructures::List<CloudKey> &cloudKeyResultList, BitStream *bsOut);
void WriteCloudQueryRowFromResultList(DataStructures::List<CloudData*> &cloudDataResultList, DataStructures::List<CloudKey> &cloudKeyResultList, BitStream *bsOut);
static int KeyDataPtrComp( const RakNetGUID &key, CloudData* const &data );
struct CloudDataList
{
bool IsUnused(void) const {return keyData.Size()==0 && nonSpecificSubscribers.Size()==0;}
bool IsNotUploaded(void) const {return uploaderCount==0;}
bool RemoveSubscriber(RakNetGUID g) {
bool objectExists;
unsigned int index;
index = nonSpecificSubscribers.GetIndexFromKey(g, &objectExists);
if (objectExists)
{
subscriberCount--;
nonSpecificSubscribers.RemoveAtIndex(index);
return true;
}
return false;
}
unsigned int uploaderCount, subscriberCount;
CloudKey key;
// Data uploaded from or subscribed to for various systems
DataStructures::OrderedList<RakNetGUID, CloudData*, CloudServer::KeyDataPtrComp> keyData;
/// When the key data changes from any system, notify these subscribers
/// This list mutually exclusive with CloudData::specificSubscribers
DataStructures::OrderedList<RakNetGUID, RakNetGUID> nonSpecificSubscribers;
};
static int KeyDataListComp( const CloudKey &key, CloudDataList * const &data );
DataStructures::OrderedList<CloudKey, CloudDataList*, CloudServer::KeyDataListComp> dataRepository;
struct KeySubscriberID
{
CloudKey key;
DataStructures::OrderedList<RakNetGUID, RakNetGUID> specificSystemsSubscribedTo;
};
static int KeySubscriberIDComp(const CloudKey &key, KeySubscriberID * const &data );
// Remote systems
struct RemoteCloudClient
{
bool IsUnused(void) const {return uploadedKeys.Size()==0 && subscribedKeys.Size()==0;}
DataStructures::OrderedList<CloudKey,CloudKey,CloudKeyComp> uploadedKeys;
DataStructures::OrderedList<CloudKey,KeySubscriberID*,CloudServer::KeySubscriberIDComp> subscribedKeys;
uint64_t uploadedBytes;
};
DataStructures::Hash<RakNetGUID, RemoteCloudClient*, 2048, RakNetGUID::ToUint32> remoteSystems;
// For a given user, release all subscribed and uploaded keys
void ReleaseSystem(RakNetGUID clientAddress );
// For a given user, release a set of keys
void ReleaseKeys(RakNetGUID clientAddress, DataStructures::List<CloudKey> &keys );
void NotifyClientSubscribersOfDataChange( CloudData *cloudData, CloudKey &key, DataStructures::OrderedList<RakNetGUID, RakNetGUID> &subscribers, bool wasUpdated );
void NotifyClientSubscribersOfDataChange( CloudQueryRow *row, DataStructures::OrderedList<RakNetGUID, RakNetGUID> &subscribers, bool wasUpdated );
void NotifyServerSubscribersOfDataChange( CloudData *cloudData, CloudKey &key, bool wasUpdated );
struct RemoteServer
{
RakNetGUID serverAddress;
// This server needs to know about these keys when they are updated or deleted
DataStructures::OrderedList<CloudKey,CloudKey,CloudKeyComp> subscribedKeys;
// This server has uploaded these keys, and needs to know about Get() requests
DataStructures::OrderedList<CloudKey,CloudKey,CloudKeyComp> uploadedKeys;
// Just for processing
bool workingFlag;
// If false, we don't know what keys they have yet, so send everything
bool gotSubscribedAndUploadedKeys;
};
static int RemoteServerComp(const RakNetGUID &key, RemoteServer* const &data );
DataStructures::OrderedList<RakNetGUID, RemoteServer*, CloudServer::RemoteServerComp> remoteServers;
struct BufferedGetResponseFromServer
{
void Clear(CloudAllocator *allocator);
RakNetGUID serverAddress;
CloudQueryResult queryResult;
bool gotResult;
};
struct CloudQueryWithAddresses
{
// Inputs
CloudQuery cloudQuery;
DataStructures::List<RakNetGUID> specificSystems;
void Serialize(bool writeToBitstream, BitStream *bitStream);
};
static int BufferedGetResponseFromServerComp(const RakNetGUID &key, BufferedGetResponseFromServer* const &data );
struct GetRequest
{
void Clear(CloudAllocator *allocator);
bool AllRemoteServersHaveResponded(void) const;
CloudQueryWithAddresses cloudQueryWithAddresses;
// When request started. If takes too long for a response from another system, can abort remaining systems
RakNet::Time requestStartTime;
// Assigned by server that gets the request to identify response. See nextGetRequestId
uint32_t requestId;
RakNetGUID requestingClient;
DataStructures::OrderedList<RakNetGUID, BufferedGetResponseFromServer*, CloudServer::BufferedGetResponseFromServerComp> remoteServerResponses;
};
static int GetRequestComp(const uint32_t &key, GetRequest* const &data );
DataStructures::OrderedList<uint32_t, GetRequest*, CloudServer::GetRequestComp> getRequests;
RakNet::Time nextGetRequestsCheck;
uint32_t nextGetRequestId;
void ProcessAndTransmitGetRequest(GetRequest *getRequest);
void ProcessCloudQueryWithAddresses(
CloudServer::CloudQueryWithAddresses &cloudQueryWithAddresses,
DataStructures::List<CloudData*> &cloudDataResultList,
DataStructures::List<CloudKey> &cloudKeyResultList
);
void SendUploadedAndSubscribedKeysToServer( RakNetGUID systemAddress );
void SendUploadedKeyToServers( CloudKey &cloudKey );
void SendSubscribedKeyToServers( CloudKey &cloudKey );
void RemoveUploadedKeyFromServers( CloudKey &cloudKey );
void RemoveSubscribedKeyFromServers( CloudKey &cloudKey );
void OnSendUploadedAndSubscribedKeysToServer( Packet *packet );
void OnSendUploadedKeyToServers( Packet *packet );
void OnSendSubscribedKeyToServers( Packet *packet );
void OnRemoveUploadedKeyFromServers( Packet *packet );
void OnRemoveSubscribedKeyFromServers( Packet *packet );
void OnServerDataChanged( Packet *packet );
void GetServersWithUploadedKeys(
DataStructures::List<CloudKey> &keys,
DataStructures::List<RemoteServer*> &remoteServersWithData
);
CloudServer::CloudDataList *GetOrAllocateCloudDataList(CloudKey key, bool *dataRepositoryExists, unsigned int &dataRepositoryIndex);
void UnsubscribeFromKey(RemoteCloudClient *remoteCloudClient, RakNetGUID remoteCloudClientGuid, unsigned int keySubscriberIndex, CloudKey &cloudKey, DataStructures::List<RakNetGUID> &specificSystems);
void RemoveSpecificSubscriber(RakNetGUID specificSubscriber, CloudDataList *cloudDataList, RakNetGUID remoteCloudClientGuid);
DataStructures::List<CloudServerQueryFilter*> queryFilters;
SystemAddress forceAddress;
};
} // namespace RakNet
#endif
// Key subscription
//
// A given system can subscribe to one or more keys.
// The subscription can be further be defined as only subscribing to keys uploaded by or changed by a given system.
// It is possible to subscribe to keys not yet uploaded, or uploaded to another system
//
// Operations:
//
// 1. SubscribeToKey() - Get() operation with subscription
// A. Add to key subscription list for the client, which contains a keyId / specificUploaderList pair
// B. Send to remote servers that for this key, they should send us updates
// C. (Done, get operation returns current values)
//
// 2. UpdateData() - Post() operation
// A. Find all subscribers to this data, for the uploading system.
// B. Send them the uploaded data
// C. Find all servers that subscribe to this data
// D. Send them the uploaded data
//
// 3. DeleteData() - Release() operation
// A. Find all subscribers to this data, for the deleting system.
// B. Inform them of the deletion
// C. Find all servers that subscribe to this data
// D. Inform them of the deletion
//
// 4. Unsubscribe()
// A. Find this subscriber, and remove their subscription
// B. If no one else is subscribing to this key for any system, notify remote servers we no longer need subscription updates
//
// Internal operations:
//
// 1. Find if any connected client has subscribed to a given key
// A. This is used add and remove our subscription for this key to remote servers
//
// 2. For a given key and updating address, find all connected clients that care
// A. First find connected clients that have subscribed to this key, regardless of address
// B. Then find connected clients that have subscribed to this key for this particular address
//
// 3. Find all remote servers that have subscribed to a given key
// A. This is so when the key is updated or deleted, we know who to send it to
//
// 4. For a given client (such as on disconnect), remove all records of their subscriptions
#endif // _RAKNET_SUPPORT_*

View File

@@ -0,0 +1,161 @@
#include "CommandParserInterface.hpp"
#include "TransportInterface.hpp"
#include <string.h>
#include "RakAssert.hpp"
#include <stdio.h>
#if defined(_WIN32)
// IP_DONTFRAGMENT is different between winsock 1 and winsock 2. Therefore, Winsock2.h must be linked againt Ws2_32.lib
// winsock.h must be linked against WSock32.lib. If these two are mixed up the flag won't work correctly
#include <winsock2.h>
#else
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#endif
#include "LinuxStrings.hpp"
using namespace RakNet;
#ifdef _MSC_VER
#pragma warning( push )
#endif
const unsigned char CommandParserInterface::VARIABLE_NUMBER_OF_PARAMETERS=255;
int RakNet::RegisteredCommandComp( const char* const & key, const RegisteredCommand &data )
{
return _stricmp(key,data.command);
}
CommandParserInterface::CommandParserInterface() {}
CommandParserInterface::~CommandParserInterface() {}
void CommandParserInterface::ParseConsoleString(char *str, const char delineator, unsigned char delineatorToggle, unsigned *numParameters, char **parameterList, unsigned parameterListLength)
{
unsigned strIndex, parameterListIndex;
unsigned strLen;
bool replaceDelineator=true;
strLen = (unsigned) strlen(str);
// Replace every instance of delineator, \n, \r with 0
for (strIndex=0; strIndex < strLen; strIndex++)
{
if (str[strIndex]==delineator && replaceDelineator)
str[strIndex]=0;
if (str[strIndex]=='\n' || str[strIndex]=='\r')
str[strIndex]=0;
if (str[strIndex]==delineatorToggle)
{
str[strIndex]=0;
replaceDelineator=!replaceDelineator;
}
}
// Fill up parameterList starting at each non-0
for (strIndex=0, parameterListIndex=0; strIndex < strLen; )
{
if (str[strIndex]!=0)
{
parameterList[parameterListIndex]=str+strIndex;
parameterListIndex++;
RakAssert(parameterListIndex < parameterListLength);
if (parameterListIndex >= parameterListLength)
break;
strIndex++;
while (str[strIndex]!=0 && strIndex < strLen)
strIndex++;
}
else
strIndex++;
}
parameterList[parameterListIndex]=0;
*numParameters=parameterListIndex;
}
void CommandParserInterface::SendCommandList(TransportInterface *transport, const SystemAddress &systemAddress)
{
unsigned i;
if (commandList.Size())
{
for (i=0; i < commandList.Size(); i++)
{
transport->Send(systemAddress, "%s", commandList[i].command);
if (i < commandList.Size()-1)
transport->Send(systemAddress, ", ");
}
transport->Send(systemAddress, "\r\n");
}
else
transport->Send(systemAddress, "No registered commands\r\n");
}
void CommandParserInterface::RegisterCommand(unsigned char parameterCount, const char *command, const char *commandHelp)
{
RegisteredCommand rc;
rc.command=command;
rc.commandHelp=commandHelp;
rc.parameterCount=parameterCount;
commandList.Insert( command, rc, true, _FILE_AND_LINE_);
}
bool CommandParserInterface::GetRegisteredCommand(const char *command, RegisteredCommand *rc)
{
bool objectExists;
unsigned index;
index=commandList.GetIndexFromKey(command, &objectExists);
if (objectExists)
*rc=commandList[index];
return objectExists;
}
void CommandParserInterface::OnTransportChange(TransportInterface *transport)
{
(void) transport;
}
void CommandParserInterface::OnNewIncomingConnection(const SystemAddress &systemAddress, TransportInterface *transport)
{
(void) systemAddress;
(void) transport;
}
void CommandParserInterface::OnConnectionLost(const SystemAddress &systemAddress, TransportInterface *transport)
{
(void) systemAddress;
(void) transport;
}
void CommandParserInterface::ReturnResult(bool res, const char *command,TransportInterface *transport, const SystemAddress &systemAddress)
{
if (res)
transport->Send(systemAddress, "%s returned true.\r\n", command);
else
transport->Send(systemAddress, "%s returned false.\r\n", command);
}
void CommandParserInterface::ReturnResult(int res, const char *command,TransportInterface *transport, const SystemAddress &systemAddress)
{
transport->Send(systemAddress, "%s returned %i.\r\n", command, res);
}
void CommandParserInterface::ReturnResult(const char *command, TransportInterface *transport, const SystemAddress &systemAddress)
{
transport->Send(systemAddress, "Successfully called %s.\r\n", command);
}
void CommandParserInterface::ReturnResult(char *res, const char *command, TransportInterface *transport, const SystemAddress &systemAddress)
{
transport->Send(systemAddress, "%s returned %s.\r\n", command, res);
}
void CommandParserInterface::ReturnResult(SystemAddress res, const char *command, TransportInterface *transport, const SystemAddress &systemAddress)
{
char addr[128];
systemAddress.ToString(false,addr);
char addr2[128];
res.ToString(false,addr2);
transport->Send(systemAddress, "%s returned %s %s:%i\r\n", command,addr,addr2,res.GetPort());
}
#ifdef _MSC_VER
#pragma warning( pop )
#endif

View File

@@ -0,0 +1,140 @@
/// \file CommandParserInterface.h
/// \brief Contains CommandParserInterface , from which you derive custom command parsers
///
/// This file is part of RakNet Copyright 2003 Jenkins Software LLC
///
/// Usage of RakNet is subject to the appropriate license agreement.
#ifndef __COMMAND_PARSER_INTERFACE
#define __COMMAND_PARSER_INTERFACE
#include "RakMemoryOverride.hpp"
#include "RakNetTypes.hpp"
#include "DS_OrderedList.hpp"
#include "Export.hpp"
namespace RakNet
{
/// Forward declarations
class TransportInterface;
/// \internal
/// Contains the information related to one command registered with RegisterCommand()
/// Implemented so I can have an automatic help system via SendCommandList()
struct RAK_DLL_EXPORT RegisteredCommand
{
const char *command;
const char *commandHelp;
unsigned char parameterCount;
};
/// List of commands registered with RegisterCommand()
int RAK_DLL_EXPORT RegisteredCommandComp( const char* const & key, const RegisteredCommand &data );
/// \brief The interface used by command parsers.
/// \details CommandParserInterface provides a set of functions and interfaces that plug into the ConsoleServer class.
/// Each CommandParserInterface works at the same time as other interfaces in the system.
class RAK_DLL_EXPORT CommandParserInterface
{
public:
CommandParserInterface();
virtual ~CommandParserInterface();
/// You are responsible for overriding this function and returning a static string, which will identifier your parser.
/// This should return a static string
/// \return The name that you return.
virtual const char *GetName(void) const=0;
/// \brief A callback for when \a systemAddress has connected to us.
/// \param[in] systemAddress The player that has connected.
/// \param[in] transport The transport interface that sent us this information. Can be used to send messages to this or other players.
virtual void OnNewIncomingConnection(const SystemAddress &systemAddress, TransportInterface *transport);
/// \brief A callback for when \a systemAddress has disconnected, either gracefully or forcefully
/// \param[in] systemAddress The player that has disconnected.
/// \param[in] transport The transport interface that sent us this information.
virtual void OnConnectionLost(const SystemAddress &systemAddress, TransportInterface *transport);
/// \brief A callback for when you are expected to send a brief description of your parser to \a systemAddress
/// \param[in] transport The transport interface we can use to write to
/// \param[in] systemAddress The player that requested help.
virtual void SendHelp(TransportInterface *transport, const SystemAddress &systemAddress)=0;
/// \brief Given \a command with parameters \a parameterList , do whatever processing you wish.
/// \param[in] command The command to process
/// \param[in] numParameters How many parameters were passed along with the command
/// \param[in] parameterList The list of parameters. parameterList[0] is the first parameter and so on.
/// \param[in] transport The transport interface we can use to write to
/// \param[in] systemAddress The player that sent this command.
/// \param[in] originalString The string that was actually sent over the network, in case you want to do your own parsing
virtual bool OnCommand(const char *command, unsigned numParameters, char **parameterList, TransportInterface *transport, const SystemAddress &systemAddress, const char *originalString)=0;
/// \brief This is called every time transport interface is registered.
/// \details If you want to save a copy of the TransportInterface pointer
/// This is the place to do it
/// \param[in] transport The new TransportInterface
virtual void OnTransportChange(TransportInterface *transport);
/// \internal
/// Scan commandList and return the associated array
/// \param[in] command The string to find
/// \param[out] rc Contains the result of this operation
/// \return True if we found the command, false otherwise
virtual bool GetRegisteredCommand(const char *command, RegisteredCommand *rc);
/// \internal
/// Goes through str, replacing the delineating character with 0's.
/// \param[in] str The string sent by the transport interface
/// \param[in] delineator The character to scan for to use as a delineator
/// \param[in] delineatorToggle When encountered the delineator replacement is toggled on and off
/// \param[out] numParameters How many pointers were written to \a parameterList
/// \param[out] parameterList An array of pointers to characters. Will hold pointers to locations inside \a str
/// \param[in] parameterListLength How big the \a parameterList array is
static void ParseConsoleString(char *str, const char delineator, unsigned char delineatorToggle, unsigned *numParameters, char **parameterList, unsigned parameterListLength);
/// \internal
/// Goes through the variable commandList and sends the command portion of each struct
/// \param[in] transport The transport interface we can use to write to
/// \param[in] systemAddress The player to write to
virtual void SendCommandList(TransportInterface *transport, const SystemAddress &systemAddress);
static const unsigned char VARIABLE_NUMBER_OF_PARAMETERS;
// Currently only takes static strings - doesn't make a copy of what you pass.
// parameterCount is the number of parameters that the sender has to include with the command.
// Pass 255 to parameterCount to indicate variable number of parameters
/// Registers a command.
/// \param[in] parameterCount How many parameters your command requires. If you want to accept a variable number of commands, pass CommandParserInterface::VARIABLE_NUMBER_OF_PARAMETERS
/// \param[in] command A pointer to a STATIC string that has your command. I keep a copy of the pointer here so don't deallocate the string.
/// \param[in] commandHelp A pointer to a STATIC string that has the help information for your command. I keep a copy of the pointer here so don't deallocate the string.
virtual void RegisterCommand(unsigned char parameterCount, const char *command, const char *commandHelp);
/// \brief Just writes a string to the remote system based on the result ( \a res ) of your operation
/// \details This is not necessary to call, but makes it easier to return results of function calls.
/// \param[in] res The result to write
/// \param[in] command The command that this result came from
/// \param[in] transport The transport interface that will be written to
/// \param[in] systemAddress The player this result will be sent to
virtual void ReturnResult(bool res, const char *command, TransportInterface *transport, const SystemAddress &systemAddress);
virtual void ReturnResult(char *res, const char *command, TransportInterface *transport, const SystemAddress &systemAddress);
virtual void ReturnResult(SystemAddress res, const char *command, TransportInterface *transport, const SystemAddress &systemAddress);
virtual void ReturnResult(int res, const char *command,TransportInterface *transport, const SystemAddress &systemAddress);
/// \brief Just writes a string to the remote system when you are calling a function that has no return value.
/// \details This is not necessary to call, but makes it easier to return results of function calls.
/// \param[in] res The result to write
/// \param[in] command The command that this result came from
/// \param[in] transport The transport interface that will be written to
/// \param[in] systemAddress The player this result will be sent to
virtual void ReturnResult(const char *command,TransportInterface *transport, const SystemAddress &systemAddress);
protected:
DataStructures::OrderedList<const char*, RegisteredCommand, RegisteredCommandComp> commandList;
};
} // namespace RakNet
#endif

View File

@@ -0,0 +1,299 @@
#include "NativeFeatureIncludes.hpp"
#if _RAKNET_SUPPORT_ConnectionGraph2==1
#include "ConnectionGraph2.hpp"
#include "RakPeerInterface.hpp"
#include "MessageIdentifiers.hpp"
#include "BitStream.hpp"
using namespace RakNet;
STATIC_FACTORY_DEFINITIONS(ConnectionGraph2,ConnectionGraph2);
int RakNet::ConnectionGraph2::RemoteSystemComp( const RakNetGUID &key, RemoteSystem * const &data )
{
if (key < data->guid)
return -1;
if (key > data->guid)
return 1;
return 0;
}
int RakNet::ConnectionGraph2::SystemAddressAndGuidComp( const SystemAddressAndGuid &key, const SystemAddressAndGuid &data )
{
if (key.guid<data.guid)
return -1;
if (key.guid>data.guid)
return 1;
return 0;
}
ConnectionGraph2::ConnectionGraph2()
{
autoProcessNewConnections=true;
}
ConnectionGraph2::~ConnectionGraph2()
{
}
bool ConnectionGraph2::GetConnectionListForRemoteSystem(RakNetGUID remoteSystemGuid, SystemAddress *saOut, RakNetGUID *guidOut, unsigned int *outLength)
{
if ((saOut==0 && guidOut==0) || outLength==0 || *outLength==0 || remoteSystemGuid==UNASSIGNED_RAKNET_GUID)
{
*outLength=0;
return false;
}
bool objectExists;
unsigned int idx = remoteSystems.GetIndexFromKey(remoteSystemGuid, &objectExists);
if (objectExists==false)
{
*outLength=0;
return false;
}
unsigned int idx2;
if (remoteSystems[idx]->remoteConnections.Size() < *outLength)
*outLength=remoteSystems[idx]->remoteConnections.Size();
for (idx2=0; idx2 < *outLength; idx2++)
{
if (guidOut)
guidOut[idx2]=remoteSystems[idx]->remoteConnections[idx2].guid;
if (saOut)
saOut[idx2]=remoteSystems[idx]->remoteConnections[idx2].systemAddress;
}
return true;
}
bool ConnectionGraph2::ConnectionExists(RakNetGUID g1, RakNetGUID g2)
{
if (g1==g2)
return false;
bool objectExists;
unsigned int idx = remoteSystems.GetIndexFromKey(g1, &objectExists);
if (objectExists==false)
{
return false;
}
SystemAddressAndGuid sag;
sag.guid=g2;
return remoteSystems[idx]->remoteConnections.HasData(sag);
}
uint16_t ConnectionGraph2::GetPingBetweenSystems(RakNetGUID g1, RakNetGUID g2) const
{
if (g1==g2)
return 0;
if (g1==rakPeerInterface->GetMyGUID())
return (uint16_t) rakPeerInterface->GetAveragePing(g2);
if (g2==rakPeerInterface->GetMyGUID())
return (uint16_t) rakPeerInterface->GetAveragePing(g1);
bool objectExists;
unsigned int idx = remoteSystems.GetIndexFromKey(g1, &objectExists);
if (objectExists==false)
{
return (uint16_t) -1;
}
SystemAddressAndGuid sag;
sag.guid=g2;
unsigned int idx2 = remoteSystems[idx]->remoteConnections.GetIndexFromKey(sag, &objectExists);
if (objectExists==false)
{
return (uint16_t) -1;
}
return remoteSystems[idx]->remoteConnections[idx2].sendersPingToThatSystem;
}
/// Returns the system with the lowest total ping among all its connections. This can be used as the 'best host' for a peer to peer session
RakNetGUID ConnectionGraph2::GetLowestAveragePingSystem(void) const
{
float lowestPing=-1.0;
unsigned int lowestPingIdx=(unsigned int) -1;
float thisAvePing=0.0f;
unsigned int idx, idx2;
int ap, count=0;
for (idx=0; idx<remoteSystems.Size(); idx++)
{
thisAvePing=0.0f;
ap = rakPeerInterface->GetAveragePing(remoteSystems[idx]->guid);
if (ap!=-1)
{
thisAvePing+=(float) ap;
count++;
}
}
if (count>0)
{
lowestPing=thisAvePing/count;
}
for (idx=0; idx<remoteSystems.Size(); idx++)
{
thisAvePing=0.0f;
count=0;
RemoteSystem *remoteSystem = remoteSystems[idx];
for (idx2=0; idx2 < remoteSystem->remoteConnections.Size(); idx2++)
{
ap=remoteSystem->remoteConnections[idx2].sendersPingToThatSystem;
if (ap!=-1)
{
thisAvePing+=(float) ap;
count++;
}
}
if (count>0 && (lowestPing==-1.0f || thisAvePing/count < lowestPing))
{
lowestPing=thisAvePing/count;
lowestPingIdx=idx;
}
}
if (lowestPingIdx==(unsigned int) -1)
return rakPeerInterface->GetMyGUID();
return remoteSystems[lowestPingIdx]->guid;
}
void ConnectionGraph2::OnClosedConnection(const SystemAddress &systemAddress, RakNetGUID rakNetGUID, PI2_LostConnectionReason lostConnectionReason )
{
// Send notice to all existing connections
RakNet::BitStream bs;
if (lostConnectionReason==LCR_CONNECTION_LOST)
bs.Write((MessageID)ID_REMOTE_CONNECTION_LOST);
else
bs.Write((MessageID)ID_REMOTE_DISCONNECTION_NOTIFICATION);
bs.Write(systemAddress);
bs.Write(rakNetGUID);
SendUnified(&bs,HIGH_PRIORITY,RELIABLE_ORDERED,0,systemAddress,true);
bool objectExists;
unsigned int idx = remoteSystems.GetIndexFromKey(rakNetGUID, &objectExists);
if (objectExists)
{
RakNet::OP_DELETE(remoteSystems[idx],_FILE_AND_LINE_);
remoteSystems.RemoveAtIndex(idx);
}
}
void ConnectionGraph2::SetAutoProcessNewConnections(bool b)
{
autoProcessNewConnections=b;
}
bool ConnectionGraph2::GetAutoProcessNewConnections(void) const
{
return autoProcessNewConnections;
}
void ConnectionGraph2::AddParticipant(const SystemAddress &systemAddress, RakNetGUID rakNetGUID)
{
// Relay the new connection to other systems.
RakNet::BitStream bs;
bs.Write((MessageID)ID_REMOTE_NEW_INCOMING_CONNECTION);
bs.Write((uint32_t)1);
bs.Write(systemAddress);
bs.Write(rakNetGUID);
bs.WriteCasted<uint16_t>(rakPeerInterface->GetAveragePing(rakNetGUID));
SendUnified(&bs,HIGH_PRIORITY,RELIABLE_ORDERED,0,systemAddress,true);
// Send everyone to the new guy
DataStructures::List<SystemAddress> addresses;
DataStructures::List<RakNetGUID> guids;
rakPeerInterface->GetSystemList(addresses, guids);
bs.Reset();
bs.Write((MessageID)ID_REMOTE_NEW_INCOMING_CONNECTION);
BitSize_t writeOffset = bs.GetWriteOffset();
bs.Write((uint32_t) addresses.Size());
unsigned int i;
uint32_t count=0;
for (i=0; i < addresses.Size(); i++)
{
if (addresses[i]==systemAddress)
continue;
bs.Write(addresses[i]);
bs.Write(guids[i]);
bs.WriteCasted<uint16_t>(rakPeerInterface->GetAveragePing(guids[i]));
count++;
}
if (count>0)
{
BitSize_t writeOffset2 = bs.GetWriteOffset();
bs.SetWriteOffset(writeOffset);
bs.Write(count);
bs.SetWriteOffset(writeOffset2);
SendUnified(&bs,HIGH_PRIORITY,RELIABLE_ORDERED,0,systemAddress,false);
}
bool objectExists;
unsigned int ii = remoteSystems.GetIndexFromKey(rakNetGUID, &objectExists);
if (objectExists==false)
{
RemoteSystem* remoteSystem = RakNet::OP_NEW<RemoteSystem>(_FILE_AND_LINE_);
remoteSystem->guid=rakNetGUID;
remoteSystems.InsertAtIndex(remoteSystem,ii,_FILE_AND_LINE_);
}
}
void ConnectionGraph2::GetParticipantList(DataStructures::OrderedList<RakNetGUID, RakNetGUID> &participantList)
{
participantList.Clear(true, _FILE_AND_LINE_);
unsigned int i;
for (i=0; i < remoteSystems.Size(); i++)
participantList.InsertAtEnd(remoteSystems[i]->guid, _FILE_AND_LINE_);
}
void ConnectionGraph2::OnNewConnection(const SystemAddress &systemAddress, RakNetGUID rakNetGUID, bool isIncoming)
{
(void) isIncoming;
if (autoProcessNewConnections)
AddParticipant(systemAddress, rakNetGUID);
}
PluginReceiveResult ConnectionGraph2::OnReceive(Packet *packet)
{
if (packet->data[0]==ID_REMOTE_CONNECTION_LOST || packet->data[0]==ID_REMOTE_DISCONNECTION_NOTIFICATION)
{
bool objectExists;
unsigned idx = remoteSystems.GetIndexFromKey(packet->guid, &objectExists);
if (objectExists)
{
RakNet::BitStream bs(packet->data,packet->length,false);
bs.IgnoreBytes(1);
SystemAddressAndGuid saag;
bs.Read(saag.systemAddress);
bs.Read(saag.guid);
unsigned long idx2 = remoteSystems[idx]->remoteConnections.GetIndexFromKey(saag, &objectExists);
if (objectExists)
remoteSystems[idx]->remoteConnections.RemoveAtIndex(idx2);
}
}
else if (packet->data[0]==ID_REMOTE_NEW_INCOMING_CONNECTION)
{
bool objectExists;
unsigned idx = remoteSystems.GetIndexFromKey(packet->guid, &objectExists);
if (objectExists)
{
uint32_t numAddresses;
RakNet::BitStream bs(packet->data,packet->length,false);
bs.IgnoreBytes(1);
bs.Read(numAddresses);
for (unsigned int idx2=0; idx2 < numAddresses; idx2++)
{
SystemAddressAndGuid saag;
bs.Read(saag.systemAddress);
bs.Read(saag.guid);
bs.Read(saag.sendersPingToThatSystem);
bool objectExists;
unsigned int ii = remoteSystems[idx]->remoteConnections.GetIndexFromKey(saag, &objectExists);
if (objectExists==false)
remoteSystems[idx]->remoteConnections.InsertAtIndex(saag,ii,_FILE_AND_LINE_);
}
}
}
return RR_CONTINUE_PROCESSING;
}
#endif // _RAKNET_SUPPORT_*

View File

@@ -0,0 +1,118 @@
/// \file ConnectionGraph2.h
/// \brief Connection graph plugin, version 2. Tells new systems about existing and new connections
///
/// This file is part of RakNet Copyright 2003 Jenkins Software LLC
///
/// Usage of RakNet is subject to the appropriate license agreement.
#include "NativeFeatureIncludes.hpp"
#if _RAKNET_SUPPORT_ConnectionGraph2==1
#ifndef __CONNECTION_GRAPH_2_H
#define __CONNECTION_GRAPH_2_H
#include "RakMemoryOverride.hpp"
#include "RakNetTypes.hpp"
#include "PluginInterface2.hpp"
#include "DS_List.hpp"
#include "DS_WeightedGraph.hpp"
#include "GetTime.hpp"
#include "Export.hpp"
namespace RakNet
{
/// Forward declarations
class RakPeerInterface;
/// \brief A one hop connection graph.
/// \details Sends ID_REMOTE_CONNECTION_LOST, ID_REMOTE_DISCONNECTION_NOTIFICATION, ID_REMOTE_NEW_INCOMING_CONNECTION<BR>
/// All identifiers are followed by SystemAddress, then RakNetGUID
/// Also stores the list for you, which you can access with GetConnectionListForRemoteSystem
/// \ingroup CONNECTION_GRAPH_GROUP
class RAK_DLL_EXPORT ConnectionGraph2 : public PluginInterface2
{
public:
// GetInstance() and DestroyInstance(instance*)
STATIC_FACTORY_DECLARATIONS(ConnectionGraph2)
ConnectionGraph2();
~ConnectionGraph2();
/// \brief Given a remote system identified by RakNetGUID, return the list of SystemAddresses and RakNetGUIDs they are connected to
/// \param[in] remoteSystemGuid Which system we are referring to. This only works for remote systems, not ourselves.
/// \param[out] saOut A preallocated array to hold the output list of SystemAddress. Can be 0 if you don't care.
/// \param[out] guidOut A preallocated array to hold the output list of RakNetGUID. Can be 0 if you don't care.
/// \param[in,out] outLength On input, the size of \a saOut and \a guidOut. On output, modified to reflect the number of elements actually written
/// \return True if \a remoteSystemGuid was found. Otherwise false, and \a saOut, \a guidOut remain unchanged. \a outLength will be set to 0.
bool GetConnectionListForRemoteSystem(RakNetGUID remoteSystemGuid, SystemAddress *saOut, RakNetGUID *guidOut, unsigned int *outLength);
/// Returns if g1 is connected to g2
bool ConnectionExists(RakNetGUID g1, RakNetGUID g2);
/// Returns the average ping between two systems in the connection graph. Returns -1 if no connection exists between those systems
uint16_t GetPingBetweenSystems(RakNetGUID g1, RakNetGUID g2) const;
/// Returns the system with the lowest average ping among all its connections.
/// If you need one system in the peer to peer group to relay data, have the FullyConnectedMesh2 host call this function after host migration, and use that system
RakNetGUID GetLowestAveragePingSystem(void) const;
/// \brief If called with false, then new connections are only added to the connection graph when you call ProcessNewConnection();
/// \details This is useful if you want to perform validation before connecting a system to a mesh, or if you want a submesh (for example a server cloud)
/// \param[in] b True to automatically call ProcessNewConnection() on any new connection, false to not do so. Defaults to true.
void SetAutoProcessNewConnections(bool b);
/// \brief Returns value passed to SetAutoProcessNewConnections()
/// \return Value passed to SetAutoProcessNewConnections(), or the default of true if it was never called
bool GetAutoProcessNewConnections(void) const;
/// \brief If you call SetAutoProcessNewConnections(false);, then you will need to manually call ProcessNewConnection() on new connections
/// \details On ID_NEW_INCOMING_CONNECTION or ID_CONNECTION_REQUEST_ACCEPTED, adds that system to the graph
/// Do not call ProcessNewConnection() manually otherwise
/// \param[in] The packet->SystemAddress member
/// \param[in] The packet->guid member
void AddParticipant(const SystemAddress &systemAddress, RakNetGUID rakNetGUID);
/// Get the participants added with AddParticipant()
/// \param[out] participantList Participants added with AddParticipant();
void GetParticipantList(DataStructures::OrderedList<RakNetGUID, RakNetGUID> &participantList);
/// \internal
struct SystemAddressAndGuid
{
SystemAddress systemAddress;
RakNetGUID guid;
uint16_t sendersPingToThatSystem;
};
/// \internal
static int SystemAddressAndGuidComp( const SystemAddressAndGuid &key, const SystemAddressAndGuid &data );
/// \internal
struct RemoteSystem
{
DataStructures::OrderedList<SystemAddressAndGuid,SystemAddressAndGuid,ConnectionGraph2::SystemAddressAndGuidComp> remoteConnections;
RakNetGUID guid;
};
/// \internal
static int RemoteSystemComp( const RakNetGUID &key, RemoteSystem * const &data );
protected:
/// \internal
virtual void OnClosedConnection(const SystemAddress &systemAddress, RakNetGUID rakNetGUID, PI2_LostConnectionReason lostConnectionReason );
/// \internal
virtual void OnNewConnection(const SystemAddress &systemAddress, RakNetGUID rakNetGUID, bool isIncoming);
/// \internal
virtual PluginReceiveResult OnReceive(Packet *packet);
// List of systems I am connected to, which in turn stores which systems they are connected to
DataStructures::OrderedList<RakNetGUID, RemoteSystem*, ConnectionGraph2::RemoteSystemComp> remoteSystems;
bool autoProcessNewConnections;
};
} // namespace RakNet
#endif // #ifndef __CONNECTION_GRAPH_2_H
#endif // _RAKNET_SUPPORT_*

View File

@@ -0,0 +1,311 @@
#include "NativeFeatureIncludes.hpp"
#if _RAKNET_SUPPORT_ConsoleServer==1
#include "ConsoleServer.hpp"
#include "TransportInterface.hpp"
#include "CommandParserInterface.hpp"
#include <string.h>
#include <stdlib.h>
#define COMMAND_DELINATOR ' '
#define COMMAND_DELINATOR_TOGGLE '"'
#include "LinuxStrings.hpp"
using namespace RakNet;
STATIC_FACTORY_DEFINITIONS(ConsoleServer,ConsoleServer);
ConsoleServer::ConsoleServer()
{
transport=0;
password[0]=0;
prompt=0;
}
ConsoleServer::~ConsoleServer()
{
if (prompt)
rakFree_Ex(prompt, _FILE_AND_LINE_);
}
void ConsoleServer::SetTransportProvider(TransportInterface *transportInterface, unsigned short port)
{
// Replace the current TransportInterface, stopping the old one, if present, and starting the new one.
if (transportInterface)
{
if (transport)
{
RemoveCommandParser(transport->GetCommandParser());
transport->Stop();
}
transport=transportInterface;
transport->Start(port, true);
unsigned i;
for (i=0; i < commandParserList.Size(); i++)
commandParserList[i]->OnTransportChange(transport);
// The transport itself might have a command parser - for example password for the RakNet transport
AddCommandParser(transport->GetCommandParser());
}
}
void ConsoleServer::AddCommandParser(CommandParserInterface *commandParserInterface)
{
if (commandParserInterface==0)
return;
// Non-duplicate insertion
unsigned i;
for (i=0; i < commandParserList.Size(); i++)
{
if (commandParserList[i]==commandParserInterface)
return;
if (_stricmp(commandParserList[i]->GetName(), commandParserInterface->GetName())==0)
{
// Naming conflict between two command parsers
RakAssert(0);
return;
}
}
commandParserList.Insert(commandParserInterface, _FILE_AND_LINE_);
if (transport)
commandParserInterface->OnTransportChange(transport);
}
void ConsoleServer::RemoveCommandParser(CommandParserInterface *commandParserInterface)
{
if (commandParserInterface==0)
return;
// Overwrite the element we are removing from the back of the list and delete the back of the list
unsigned i;
for (i=0; i < commandParserList.Size(); i++)
{
if (commandParserList[i]==commandParserInterface)
{
commandParserList[i]=commandParserList[commandParserList.Size()-1];
commandParserList.RemoveFromEnd();
return;
}
}
}
void ConsoleServer::Update(void)
{
unsigned i;
char *parameterList[20]; // Up to 20 parameters
unsigned numParameters;
RakNet::SystemAddress newOrLostConnectionId;
RakNet::Packet *p;
RakNet::RegisteredCommand rc;
p = transport->Receive();
newOrLostConnectionId=transport->HasNewIncomingConnection();
if (newOrLostConnectionId!=UNASSIGNED_SYSTEM_ADDRESS)
{
for (i=0; i < commandParserList.Size(); i++)
{
commandParserList[i]->OnNewIncomingConnection(newOrLostConnectionId, transport);
}
transport->Send(newOrLostConnectionId, "Connected to remote command console.\r\nType 'help' for help.\r\n");
ListParsers(newOrLostConnectionId);
ShowPrompt(newOrLostConnectionId);
}
newOrLostConnectionId=transport->HasLostConnection();
if (newOrLostConnectionId!=UNASSIGNED_SYSTEM_ADDRESS)
{
for (i=0; i < commandParserList.Size(); i++)
commandParserList[i]->OnConnectionLost(newOrLostConnectionId, transport);
}
while (p)
{
bool commandParsed=false;
char copy[REMOTE_MAX_TEXT_INPUT];
memcpy(copy, p->data, p->length);
copy[p->length]=0;
RakNet::CommandParserInterface::ParseConsoleString((char*)p->data, COMMAND_DELINATOR, COMMAND_DELINATOR_TOGGLE, &numParameters, parameterList, 20); // Up to 20 parameters
if (numParameters==0)
{
transport->DeallocatePacket(p);
p = transport->Receive();
continue;
}
if (_stricmp(*parameterList, "help")==0 && numParameters<=2)
{
// Find the parser specified and display help for it
if (numParameters==1)
{
transport->Send(p->systemAddress, "\r\nINSTRUCTIONS:\r\n");
transport->Send(p->systemAddress, "Enter commands on your keyboard, using spaces to delineate parameters.\r\n");
transport->Send(p->systemAddress, "You can use quotation marks to toggle space delineation.\r\n");
transport->Send(p->systemAddress, "You can connect multiple times from the same computer.\r\n");
transport->Send(p->systemAddress, "You can direct commands to a parser by prefixing the parser name or number.\r\n");
transport->Send(p->systemAddress, "COMMANDS:\r\n");
transport->Send(p->systemAddress, "help Show this display.\r\n");
transport->Send(p->systemAddress, "help <ParserName> Show help on a particular parser.\r\n");
transport->Send(p->systemAddress, "help <CommandName> Show help on a particular command.\r\n");
transport->Send(p->systemAddress, "quit Disconnects from the server.\r\n");
transport->Send(p->systemAddress, "[<ParserName>] <Command> [<Parameters>] Execute a command\r\n");
transport->Send(p->systemAddress, "[<ParserNumber>] <Command> [<Parameters>] Execute a command\r\n");
ListParsers(p->systemAddress);
//ShowPrompt(p->systemAddress);
}
else // numParameters == 2, including the help tag
{
for (i=0; i < commandParserList.Size(); i++)
{
if (_stricmp(parameterList[1], commandParserList[i]->GetName())==0)
{
commandParsed=true;
commandParserList[i]->SendHelp(transport, p->systemAddress);
transport->Send(p->systemAddress, "COMMAND LIST:\r\n");
commandParserList[i]->SendCommandList(transport, p->systemAddress);
transport->Send(p->systemAddress, "\r\n");
break;
}
}
if (commandParsed==false)
{
// Try again, for all commands for all parsers.
RakNet::RegisteredCommand rc;
for (i=0; i < commandParserList.Size(); i++)
{
if (commandParserList[i]->GetRegisteredCommand(parameterList[1], &rc))
{
if (rc.parameterCount==RakNet::CommandParserInterface::VARIABLE_NUMBER_OF_PARAMETERS)
transport->Send(p->systemAddress, "(Variable parms): %s %s\r\n", rc.command, rc.commandHelp);
else
transport->Send(p->systemAddress, "(%i parms): %s %s\r\n", rc.parameterCount, rc.command, rc.commandHelp);
commandParsed=true;
break;
}
}
}
if (commandParsed==false)
{
// Don't know what to do
transport->Send(p->systemAddress, "Unknown help topic: %s.\r\n", parameterList[1]);
}
//ShowPrompt(p->systemAddress);
}
}
else if (_stricmp(*parameterList, "quit")==0 && numParameters==1)
{
transport->Send(p->systemAddress, "Goodbye!\r\n");
transport->CloseConnection(p->systemAddress);
}
else
{
bool tryAllParsers=true;
bool failed=false;
if (numParameters >=2) // At minimum <CommandParserName> <Command>
{
unsigned commandParserIndex=(unsigned)-1;
// Prefixing with numbers directs to a particular parser
if (**parameterList>='0' && **parameterList<='9')
{
commandParserIndex=atoi(*parameterList); // Use specified parser unless it's an invalid number
commandParserIndex--; // Subtract 1 since we displayed numbers starting at index+1
if (commandParserIndex >= commandParserList.Size())
{
transport->Send(p->systemAddress, "Invalid index.\r\n");
failed=true;
}
}
else
{
// // Prefixing with the name of a command parser directs to that parser. See if the first word matches a parser
for (i=0; i < commandParserList.Size(); i++)
{
if (_stricmp(parameterList[0], commandParserList[i]->GetName())==0)
{
commandParserIndex=i; // Matches parser at index i
break;
}
}
}
if (failed==false)
{
// -1 means undirected, so otherwise this is directed to a target
if (commandParserIndex!=(unsigned)-1)
{
// Only this parser should use this command
tryAllParsers=false;
if (commandParserList[commandParserIndex]->GetRegisteredCommand(parameterList[1], &rc))
{
commandParsed=true;
if (rc.parameterCount==CommandParserInterface::VARIABLE_NUMBER_OF_PARAMETERS || rc.parameterCount==numParameters-2)
commandParserList[commandParserIndex]->OnCommand(rc.command, numParameters-2, parameterList+2, transport, p->systemAddress, copy);
else
transport->Send(p->systemAddress, "Invalid parameter count.\r\n(%i parms): %s %s\r\n", rc.parameterCount, rc.command, rc.commandHelp);
}
}
}
}
if (failed == false && tryAllParsers)
{
for (i=0; i < commandParserList.Size(); i++)
{
// Undirected command. Try all the parsers to see if they understand the command
// Pass the 1nd element as the command, and the remainder as the parameter list
if (commandParserList[i]->GetRegisteredCommand(parameterList[0], &rc))
{
commandParsed=true;
if (rc.parameterCount==CommandParserInterface::VARIABLE_NUMBER_OF_PARAMETERS || rc.parameterCount==numParameters-1)
commandParserList[i]->OnCommand(rc.command, numParameters-1, parameterList+1, transport, p->systemAddress, copy);
else
transport->Send(p->systemAddress, "Invalid parameter count.\r\n(%i parms): %s %s\r\n", rc.parameterCount, rc.command, rc.commandHelp);
}
}
}
if (commandParsed==false && commandParserList.Size() > 0)
{
transport->Send(p->systemAddress, "Unknown command: Type 'help' for help.\r\n");
}
}
ShowPrompt(p->systemAddress);
transport->DeallocatePacket(p);
p = transport->Receive();
}
}
void ConsoleServer::ListParsers(SystemAddress systemAddress)
{
transport->Send(systemAddress,"INSTALLED PARSERS:\r\n");
unsigned i;
for (i=0; i < commandParserList.Size(); i++)
{
transport->Send(systemAddress, "%i. %s\r\n", i+1, commandParserList[i]->GetName());
}
}
void ConsoleServer::ShowPrompt(SystemAddress systemAddress)
{
transport->Send(systemAddress, prompt);
}
void ConsoleServer::SetPrompt(const char *_prompt)
{
if (prompt)
rakFree_Ex(prompt,_FILE_AND_LINE_);
if (_prompt && _prompt[0])
{
size_t len = strlen(_prompt);
prompt = (char*) rakMalloc_Ex(len+1,_FILE_AND_LINE_);
strcpy(prompt,_prompt);
}
else
prompt=0;
}
#endif // _RAKNET_SUPPORT_*

View File

@@ -0,0 +1,77 @@
/// \file ConsoleServer.h
/// \brief Contains ConsoleServer , used to plugin to your game to accept remote console-based connections
///
/// This file is part of RakNet Copyright 2003 Jenkins Software LLC
///
/// Usage of RakNet is subject to the appropriate license agreement.
#include "NativeFeatureIncludes.hpp"
#if _RAKNET_SUPPORT_ConsoleServer==1
#ifndef __CONSOLE_SERVER_H
#define __CONSOLE_SERVER_H
#include "RakMemoryOverride.hpp"
#include "DS_List.hpp"
#include "RakNetTypes.hpp"
#include "Export.hpp"
namespace RakNet
{
/// Forward declarations
class TransportInterface;
class CommandParserInterface;
/// \brief The main entry point for the server portion of your remote console application support.
/// \details ConsoleServer takes one TransportInterface and one or more CommandParserInterface (s)
/// The TransportInterface will be used to send data between the server and the client. The connecting client must support the
/// protocol used by your derivation of TransportInterface . TelnetTransport and RakNetTransport are two such derivations .
/// When a command is sent by a remote console, it will be processed by your implementations of CommandParserInterface
class RAK_DLL_EXPORT ConsoleServer
{
public:
// GetInstance() and DestroyInstance(instance*)
STATIC_FACTORY_DECLARATIONS(ConsoleServer)
ConsoleServer();
~ConsoleServer();
/// \brief Call this with a derivation of TransportInterface so that the console server can send and receive commands
/// \param[in] transportInterface Your interface to use.
/// \param[in] port The port to host on. Telnet uses port 23 by default. RakNet can use whatever you want.
void SetTransportProvider(TransportInterface *transportInterface, unsigned short port);
/// \brief Add an implementation of CommandParserInterface to the list of command parsers.
/// \param[in] commandParserInterface The command parser referred to
void AddCommandParser(CommandParserInterface *commandParserInterface);
/// \brief Remove an implementation of CommandParserInterface previously added with AddCommandParser().
/// \param[in] commandParserInterface The command parser referred to
void RemoveCommandParser(CommandParserInterface *commandParserInterface);
/// \brief Call update to read packet sent from your TransportInterface.
/// You should do this fairly frequently.
void Update(void);
/// \brief Sets a prompt to show when waiting for user input.
/// \details Pass an empty string to clear the prompt
/// Defaults to no prompt
/// \param[in] _prompt Null-terminated string of the prompt to use. If you want a newline, be sure to use /r/n
void SetPrompt(const char *_prompt);
protected:
void ListParsers(SystemAddress systemAddress);
void ShowPrompt(SystemAddress systemAddress);
TransportInterface *transport;
DataStructures::List<CommandParserInterface *> commandParserList;
char* password[256];
char *prompt;
};
} // namespace RakNet
#endif
#endif // _RAKNET_SUPPORT_*

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,149 @@
#include "DS_BytePool.hpp"
#include "RakAssert.hpp"
#ifndef __APPLE__
// Use stdlib and not malloc for compatibility
#include <stdlib.h>
#endif
using namespace DataStructures;
BytePool::BytePool()
{
pool128.SetPageSize(8192*4);
pool512.SetPageSize(8192*4);
pool2048.SetPageSize(8192*4);
pool8192.SetPageSize(8192*4);
}
BytePool::~BytePool()
{
}
void BytePool::SetPageSize(int size)
{
pool128.SetPageSize(size);
pool512.SetPageSize(size);
pool2048.SetPageSize(size);
pool8192.SetPageSize(size);
}
unsigned char *BytePool::Allocate(int bytesWanted, const char *file, unsigned int line)
{
#ifdef _DISABLE_BYTE_POOL
return rakMalloc_Ex(bytesWanted, _FILE_AND_LINE_);
#endif
unsigned char *out;
if (bytesWanted <= 127)
{
#ifdef _THREADSAFE_BYTE_POOL
mutex128.Lock();
#endif
out = (unsigned char*) pool128.Allocate(file, line);
#ifdef _THREADSAFE_BYTE_POOL
mutex128.Unlock();
#endif
out[0]=0;
return ((unsigned char*) out)+1;
}
if (bytesWanted <= 511)
{
#ifdef _THREADSAFE_BYTE_POOL
mutex512.Lock();
#endif
out = (unsigned char*) pool512.Allocate(file, line);
#ifdef _THREADSAFE_BYTE_POOL
mutex512.Unlock();
#endif
out[0]=1;
return ((unsigned char*) out)+1;
}
if (bytesWanted <= 2047)
{
#ifdef _THREADSAFE_BYTE_POOL
mutex2048.Lock();
#endif
out = (unsigned char*) pool2048.Allocate(file, line);
#ifdef _THREADSAFE_BYTE_POOL
mutex2048.Unlock();
#endif
out[0]=2;
return ((unsigned char*) out)+1;
}
if (bytesWanted <= 8191)
{
#ifdef _THREADSAFE_BYTE_POOL
mutex8192.Lock();
#endif
out = (unsigned char*) pool8192.Allocate(file, line);
#ifdef _THREADSAFE_BYTE_POOL
mutex8192.Unlock();
#endif
out[0]=3;
return ((unsigned char*) out)+1;
}
out = (unsigned char*) rakMalloc_Ex(bytesWanted+1, _FILE_AND_LINE_);
out[0]=(unsigned char)255;
return out+1;
}
void BytePool::Release(unsigned char *data, const char *file, unsigned int line)
{
#ifdef _DISABLE_BYTE_POOL
_rakFree_Ex(data, _FILE_AND_LINE_ );
#endif
unsigned char *realData = data-1;
switch (realData[0])
{
case 0:
#ifdef _THREADSAFE_BYTE_POOL
mutex128.Lock();
#endif
pool128.Release((unsigned char(*)[128]) realData, file, line );
#ifdef _THREADSAFE_BYTE_POOL
mutex128.Unlock();
#endif
break;
case 1:
#ifdef _THREADSAFE_BYTE_POOL
mutex512.Lock();
#endif
pool512.Release((unsigned char(*)[512]) realData, file, line );
#ifdef _THREADSAFE_BYTE_POOL
mutex512.Unlock();
#endif
break;
case 2:
#ifdef _THREADSAFE_BYTE_POOL
mutex2048.Lock();
#endif
pool2048.Release((unsigned char(*)[2048]) realData, file, line );
#ifdef _THREADSAFE_BYTE_POOL
mutex2048.Unlock();
#endif
break;
case 3:
#ifdef _THREADSAFE_BYTE_POOL
mutex8192.Lock();
#endif
pool8192.Release((unsigned char(*)[8192]) realData, file, line );
#ifdef _THREADSAFE_BYTE_POOL
mutex8192.Unlock();
#endif
break;
case 255:
rakFree_Ex(realData, file, line );
break;
default:
RakAssert(0);
break;
}
}
void BytePool::Clear(const char *file, unsigned int line)
{
(void) file;
(void) line;
#ifdef _THREADSAFE_BYTE_POOL
pool128.Clear(file, line);
pool512.Clear(file, line);
pool2048.Clear(file, line);
pool8192.Clear(file, line);
#endif
}

View File

@@ -0,0 +1,46 @@
/// \file DS_BytePool.h
///
/// This file is part of RakNet Copyright 2003 Jenkins Software LLC
///
/// Usage of RakNet is subject to the appropriate license agreement.
#ifndef __BYTE_POOL_H
#define __BYTE_POOL_H
#include "RakMemoryOverride.hpp"
#include "DS_MemoryPool.hpp"
#include "Export.hpp"
#include "SimpleMutex.hpp"
#include "RakAssert.hpp"
// #define _DISABLE_BYTE_POOL
// #define _THREADSAFE_BYTE_POOL
namespace DataStructures
{
// Allocate some number of bytes from pools. Uses the heap if necessary.
class RAK_DLL_EXPORT BytePool
{
public:
BytePool();
~BytePool();
// Should be at least 8 times bigger than 8192
void SetPageSize(int size);
unsigned char* Allocate(int bytesWanted, const char *file, unsigned int line);
void Release(unsigned char *data, const char *file, unsigned int line);
void Clear(const char *file, unsigned int line);
protected:
MemoryPool<unsigned char[128]> pool128;
MemoryPool<unsigned char[512]> pool512;
MemoryPool<unsigned char[2048]> pool2048;
MemoryPool<unsigned char[8192]> pool8192;
#ifdef _THREADSAFE_BYTE_POOL
SimpleMutex mutex128;
SimpleMutex mutex512;
SimpleMutex mutex2048;
SimpleMutex mutex8192;
#endif
};
}
#endif

View File

@@ -0,0 +1,127 @@
#include "DS_ByteQueue.hpp"
#include <string.h> // Memmove
#include <stdlib.h> // realloc
#include <stdio.h>
using namespace DataStructures;
ByteQueue::ByteQueue()
{
readOffset=writeOffset=lengthAllocated=0;
data=0;
}
ByteQueue::~ByteQueue()
{
Clear(_FILE_AND_LINE_);
}
void ByteQueue::WriteBytes(const char *in, unsigned length, const char *file, unsigned int line)
{
unsigned bytesWritten;
bytesWritten=GetBytesWritten();
if (lengthAllocated==0 || length > lengthAllocated-bytesWritten-1)
{
unsigned oldLengthAllocated=lengthAllocated;
// Always need to waste 1 byte for the math to work, else writeoffset==readoffset
unsigned newAmountToAllocate=length+oldLengthAllocated+1;
if (newAmountToAllocate<256)
newAmountToAllocate=256;
lengthAllocated=lengthAllocated + newAmountToAllocate;
data=(char*)rakRealloc_Ex(data, lengthAllocated, file, line);
if (writeOffset < readOffset)
{
if (writeOffset <= newAmountToAllocate)
{
memcpy(data + oldLengthAllocated, data, writeOffset);
writeOffset=readOffset+bytesWritten;
}
else
{
memcpy(data + oldLengthAllocated, data, newAmountToAllocate);
memmove(data, data+newAmountToAllocate, writeOffset-newAmountToAllocate);
writeOffset-=newAmountToAllocate;
}
}
}
if (length <= lengthAllocated-writeOffset)
memcpy(data+writeOffset, in, length);
else
{
// Wrap
memcpy(data+writeOffset, in, lengthAllocated-writeOffset);
memcpy(data, in+(lengthAllocated-writeOffset), length-(lengthAllocated-writeOffset));
}
writeOffset=(writeOffset+length) % lengthAllocated;
}
bool ByteQueue::ReadBytes(char *out, unsigned maxLengthToRead, bool peek)
{
unsigned bytesWritten = GetBytesWritten();
unsigned bytesToRead = bytesWritten < maxLengthToRead ? bytesWritten : maxLengthToRead;
if (bytesToRead==0)
return false;
if (writeOffset>=readOffset)
{
memcpy(out, data+readOffset, bytesToRead);
}
else
{
unsigned availableUntilWrap = lengthAllocated-readOffset;
if (bytesToRead <= availableUntilWrap)
{
memcpy(out, data+readOffset, bytesToRead);
}
else
{
memcpy(out, data+readOffset, availableUntilWrap);
memcpy(out+availableUntilWrap, data, bytesToRead-availableUntilWrap);
}
}
if (peek==false)
IncrementReadOffset(bytesToRead);
return true;
}
char* ByteQueue::PeekContiguousBytes(unsigned int *outLength) const
{
if (writeOffset>=readOffset)
*outLength=writeOffset-readOffset;
else
*outLength=lengthAllocated-readOffset;
return data+readOffset;
}
void ByteQueue::Clear(const char *file, unsigned int line)
{
if (lengthAllocated)
rakFree_Ex(data, file, line );
readOffset=writeOffset=lengthAllocated=0;
data=0;
}
unsigned ByteQueue::GetBytesWritten(void) const
{
if (writeOffset>=readOffset)
return writeOffset-readOffset;
else
return writeOffset+(lengthAllocated-readOffset);
}
void ByteQueue::IncrementReadOffset(unsigned length)
{
readOffset=(readOffset+length) % lengthAllocated;
}
void ByteQueue::DecrementReadOffset(unsigned length)
{
if (length>readOffset)
readOffset=lengthAllocated-(length-readOffset);
else
readOffset-=length;
}
void ByteQueue::Print(void)
{
unsigned i;
for (i=readOffset; i!=writeOffset; i++)
RAKNET_DEBUG_PRINTF("%i ", data[i]);
RAKNET_DEBUG_PRINTF("\n");
}

View File

@@ -0,0 +1,40 @@
/// \file DS_ByteQueue.h
/// \internal
/// \brief Byte queue
///
/// This file is part of RakNet Copyright 2003 Jenkins Software LLC
///
/// Usage of RakNet is subject to the appropriate license agreement.
#ifndef __BYTE_QUEUE_H
#define __BYTE_QUEUE_H
#include "RakMemoryOverride.hpp"
#include "Export.hpp"
/// The namespace DataStructures was only added to avoid compiler errors for commonly named data structures
/// As these data structures are stand-alone, you can use them outside of RakNet for your own projects if you wish.
namespace DataStructures
{
class ByteQueue
{
public:
ByteQueue();
~ByteQueue();
void WriteBytes(const char *in, unsigned length, const char *file, unsigned int line);
bool ReadBytes(char *out, unsigned maxLengthToRead, bool peek);
unsigned GetBytesWritten(void) const;
char* PeekContiguousBytes(unsigned int *outLength) const;
void IncrementReadOffset(unsigned length);
void DecrementReadOffset(unsigned length);
void Clear(const char *file, unsigned int line);
void Print(void);
protected:
char *data;
unsigned readOffset, writeOffset, lengthAllocated;
};
}
#endif

View File

@@ -0,0 +1,344 @@
/// \internal
/// \brief Hashing container
///
/// This file is part of RakNet Copyright 2003 Jenkins Software LLC
///
/// Usage of RakNet is subject to the appropriate license agreement.
#ifndef __HASH_H
#define __HASH_H
#include "RakAssert.hpp"
#include <string.h> // memmove
#include "Export.hpp"
#include "RakMemoryOverride.hpp"
#include "RakString.hpp"
/// The namespace DataStructures was only added to avoid compiler errors for commonly named data structures
/// As these data structures are stand-alone, you can use them outside of RakNet for your own projects if you wish.
namespace DataStructures
{
struct HashIndex
{
unsigned int primaryIndex;
unsigned int secondaryIndex;
bool IsInvalid(void) const {return primaryIndex==(unsigned int) -1;}
void SetInvalid(void) {primaryIndex=(unsigned int) -1; secondaryIndex=(unsigned int) -1;}
};
/// \brief Using a string as a identifier for a node, store an allocated pointer to that node
template <class key_type, class data_type, unsigned int HASH_SIZE, unsigned long (*hashFunction)(const key_type &) >
class RAK_DLL_EXPORT Hash
{
public:
/// Default constructor
Hash();
// Destructor
~Hash();
void Push(key_type key, const data_type &input, const char *file, unsigned int line );
data_type* Peek(key_type key );
bool Pop(data_type& out, key_type key, const char *file, unsigned int line );
bool RemoveAtIndex(HashIndex index, const char *file, unsigned int line );
bool Remove(key_type key, const char *file, unsigned int line );
HashIndex GetIndexOf(key_type key);
bool HasData(key_type key);
data_type& ItemAtIndex(const HashIndex &index);
key_type KeyAtIndex(const HashIndex &index);
void GetAsList(DataStructures::List<data_type> &itemList,DataStructures::List<key_type > &keyList,const char *file, unsigned int line) const;
unsigned int Size(void) const;
/// \brief Clear the list
void Clear( const char *file, unsigned int line );
struct Node
{
Node(key_type strIn, const data_type &_data) {string=strIn; data=_data;}
key_type string;
data_type data;
// Next in the list for this key
Node *next;
};
protected:
void ClearIndex(unsigned int index,const char *file, unsigned int line);
Node **nodeList;
unsigned int size;
};
template <class key_type, class data_type, unsigned int HASH_SIZE, unsigned long (*hashFunction)(const key_type &) >
Hash<key_type, data_type, HASH_SIZE, hashFunction>::Hash()
{
nodeList=0;
size=0;
}
template <class key_type, class data_type, unsigned int HASH_SIZE, unsigned long (*hashFunction)(const key_type &) >
Hash<key_type, data_type, HASH_SIZE, hashFunction>::~Hash()
{
Clear(_FILE_AND_LINE_);
}
template <class key_type, class data_type, unsigned int HASH_SIZE, unsigned long (*hashFunction)(const key_type &) >
void Hash<key_type, data_type, HASH_SIZE, hashFunction>::Push(key_type key, const data_type &input, const char *file, unsigned int line )
{
unsigned long hashIndex = (*hashFunction)(key) % HASH_SIZE;
if (nodeList==0)
{
nodeList=RakNet::OP_NEW_ARRAY<Node *>(HASH_SIZE,file,line);
memset(nodeList,0,sizeof(Node *)*HASH_SIZE);
}
Node *newNode=RakNet::OP_NEW_2<Node>(file,line,key,input);
newNode->next=nodeList[hashIndex];
nodeList[hashIndex]=newNode;
size++;
}
template <class key_type, class data_type, unsigned int HASH_SIZE, unsigned long (*hashFunction)(const key_type &) >
data_type* Hash<key_type, data_type, HASH_SIZE, hashFunction>::Peek(key_type key )
{
unsigned long hashIndex = (*hashFunction)(key) % HASH_SIZE;
Node *node = nodeList[hashIndex];
while (node!=0)
{
if (node->string==key)
return &node->data;
node=node->next;
}
return 0;
}
template <class key_type, class data_type, unsigned int HASH_SIZE, unsigned long (*hashFunction)(const key_type &) >
bool Hash<key_type, data_type, HASH_SIZE, hashFunction>::Pop(data_type& out, key_type key, const char *file, unsigned int line )
{
unsigned long hashIndex = (*hashFunction)(key) % HASH_SIZE;
Node *node = nodeList[hashIndex];
if (node==0)
return false;
if (node->next==0)
{
// Only one item.
if (node->string==key)
{
// Delete last item
out=node->data;
ClearIndex(hashIndex,_FILE_AND_LINE_);
return true;
}
else
{
// Single item doesn't match
return false;
}
}
else if (node->string==key)
{
// First item does match, but more than one item
out=node->data;
nodeList[hashIndex]=node->next;
RakNet::OP_DELETE(node,file,line);
size--;
return true;
}
Node *last=node;
node=node->next;
while (node!=0)
{
// First item does not match, but subsequent item might
if (node->string==key)
{
out=node->data;
// Skip over subsequent item
last->next=node->next;
// Delete existing item
RakNet::OP_DELETE(node,file,line);
size--;
return true;
}
last=node;
node=node->next;
}
return false;
}
template <class key_type, class data_type, unsigned int HASH_SIZE, unsigned long (*hashFunction)(const key_type &) >
bool Hash<key_type, data_type, HASH_SIZE, hashFunction>::RemoveAtIndex(HashIndex index, const char *file, unsigned int line )
{
if (index.IsInvalid())
return false;
Node *node = nodeList[index.primaryIndex];
if (node==0)
return false;
if (node->next==0)
{
// Delete last item
ClearIndex(index.primaryIndex,file,line);
return true;
}
else if (index.secondaryIndex==0)
{
// First item does match, but more than one item
nodeList[index.primaryIndex]=node->next;
RakNet::OP_DELETE(node,file,line);
size--;
return true;
}
Node *last=node;
node=node->next;
--index.secondaryIndex;
while (index.secondaryIndex!=0)
{
last=node;
node=node->next;
--index.secondaryIndex;
}
// Skip over subsequent item
last->next=node->next;
// Delete existing item
RakNet::OP_DELETE(node,file,line);
size--;
return true;
}
template <class key_type, class data_type, unsigned int HASH_SIZE, unsigned long (*hashFunction)(const key_type &) >
bool Hash<key_type, data_type, HASH_SIZE, hashFunction>::Remove(key_type key, const char *file, unsigned int line )
{
return RemoveAtIndex(GetIndexOf(key),file,line);
}
template <class key_type, class data_type, unsigned int HASH_SIZE, unsigned long (*hashFunction)(const key_type &) >
HashIndex Hash<key_type, data_type, HASH_SIZE, hashFunction>::GetIndexOf(key_type key)
{
if (nodeList==0)
{
HashIndex temp;
temp.SetInvalid();
return temp;
}
HashIndex idx;
idx.primaryIndex=(*hashFunction)(key) % HASH_SIZE;
Node *node = nodeList[idx.primaryIndex];
if (node==0)
{
idx.SetInvalid();
return idx;
}
idx.secondaryIndex=0;
while (node!=0)
{
if (node->string==key)
{
return idx;
}
node=node->next;
idx.secondaryIndex++;
}
idx.SetInvalid();
return idx;
}
template <class key_type, class data_type, unsigned int HASH_SIZE, unsigned long (*hashFunction)(const key_type &) >
bool Hash<key_type, data_type, HASH_SIZE, hashFunction>::HasData(key_type key)
{
return GetIndexOf(key).IsInvalid()==false;
}
template <class key_type, class data_type, unsigned int HASH_SIZE, unsigned long (*hashFunction)(const key_type &) >
data_type& Hash<key_type, data_type, HASH_SIZE, hashFunction>::ItemAtIndex(const HashIndex &index)
{
Node *node = nodeList[index.primaryIndex];
RakAssert(node);
unsigned int i;
for (i=0; i < index.secondaryIndex; i++)
{
node=node->next;
RakAssert(node);
}
return node->data;
}
template <class key_type, class data_type, unsigned int HASH_SIZE, unsigned long (*hashFunction)(const key_type &) >
key_type Hash<key_type, data_type, HASH_SIZE, hashFunction>::KeyAtIndex(const HashIndex &index)
{
Node *node = nodeList[index.primaryIndex];
RakAssert(node);
unsigned int i;
for (i=0; i < index.secondaryIndex; i++)
{
node=node->next;
RakAssert(node);
}
return node->string;
}
template <class key_type, class data_type, unsigned int HASH_SIZE, unsigned long (*hashFunction)(const key_type &) >
void Hash<key_type, data_type, HASH_SIZE, hashFunction>::Clear(const char *file, unsigned int line)
{
if (nodeList)
{
unsigned int i;
for (i=0; i < HASH_SIZE; i++)
ClearIndex(i,file,line);
RakNet::OP_DELETE_ARRAY(nodeList,file,line);
nodeList=0;
size=0;
}
}
template <class key_type, class data_type, unsigned int HASH_SIZE, unsigned long (*hashFunction)(const key_type &) >
void Hash<key_type, data_type, HASH_SIZE, hashFunction>::ClearIndex(unsigned int index,const char *file, unsigned int line)
{
Node *node = nodeList[index];
Node *next;
while (node)
{
next=node->next;
RakNet::OP_DELETE(node,file,line);
node=next;
size--;
}
nodeList[index]=0;
}
template <class key_type, class data_type, unsigned int HASH_SIZE, unsigned long (*hashFunction)(const key_type &) >
void Hash<key_type, data_type, HASH_SIZE, hashFunction>::GetAsList(DataStructures::List<data_type> &itemList,DataStructures::List<key_type > &keyList,const char *file, unsigned int line) const
{
if (nodeList==0)
return;
itemList.Clear(false,_FILE_AND_LINE_);
keyList.Clear(false,_FILE_AND_LINE_);
Node *node;
unsigned int i;
for (i=0; i < HASH_SIZE; i++)
{
if (nodeList[i])
{
node=nodeList[i];
while (node)
{
itemList.Push(node->data,file,line);
keyList.Push(node->string,file,line);
node=node->next;
}
}
}
}
template <class key_type, class data_type, unsigned int HASH_SIZE, unsigned long (*hashFunction)(const key_type &) >
unsigned int Hash<key_type, data_type, HASH_SIZE, hashFunction>::Size(void) const
{
return size;
}
}
#endif

View File

@@ -0,0 +1,297 @@
/// \file DS_Heap.h
/// \internal
/// \brief Heap (Also serves as a priority queue)
///
/// This file is part of RakNet Copyright 2003 Jenkins Software LLC
///
/// Usage of RakNet is subject to the appropriate license agreement.
#ifndef __RAKNET_HEAP_H
#define __RAKNET_HEAP_H
#include "RakMemoryOverride.hpp"
#include "DS_List.hpp"
#include "Export.hpp"
#include "RakAssert.hpp"
#ifdef _MSC_VER
#pragma warning( push )
#endif
/// The namespace DataStructures was only added to avoid compiler errors for commonly named data structures
/// As these data structures are stand-alone, you can use them outside of RakNet for your own projects if you wish.
namespace DataStructures
{
template <class weight_type, class data_type, bool isMaxHeap>
class RAK_DLL_EXPORT Heap
{
public:
struct HeapNode
{
HeapNode() {}
HeapNode(const weight_type &w, const data_type &d) : weight(w), data(d) {}
weight_type weight; // I'm assuming key is a native numerical type - float or int
data_type data;
};
Heap();
~Heap();
void Push(const weight_type &weight, const data_type &data, const char *file, unsigned int line);
/// Call before calling PushSeries, for a new series of items
void StartSeries(void) {optimizeNextSeriesPush=false;}
/// If you are going to push a list of items, where the weights of the items on the list are in order and follow the heap order, PushSeries is faster than Push()
void PushSeries(const weight_type &weight, const data_type &data, const char *file, unsigned int line);
data_type Pop(const unsigned startingIndex);
data_type Peek(const unsigned startingIndex=0) const;
weight_type PeekWeight(const unsigned startingIndex=0) const;
void Clear(bool doNotDeallocateSmallBlocks, const char *file, unsigned int line);
data_type& operator[] ( const unsigned int position ) const;
unsigned Size(void) const;
protected:
unsigned LeftChild(const unsigned i) const;
unsigned RightChild(const unsigned i) const;
unsigned Parent(const unsigned i) const;
void Swap(const unsigned i, const unsigned j);
DataStructures::List<HeapNode> heap;
bool optimizeNextSeriesPush;
};
template <class weight_type, class data_type, bool isMaxHeap>
Heap<weight_type, data_type, isMaxHeap>::Heap()
{
optimizeNextSeriesPush=false;
}
template <class weight_type, class data_type, bool isMaxHeap>
Heap<weight_type, data_type, isMaxHeap>::~Heap()
{
//Clear(true, _FILE_AND_LINE_);
}
template <class weight_type, class data_type, bool isMaxHeap>
void Heap<weight_type, data_type, isMaxHeap>::PushSeries(const weight_type &weight, const data_type &data, const char *file, unsigned int line)
{
if (optimizeNextSeriesPush==false)
{
// If the weight of what we are inserting is greater than / less than in order of the heap of every sibling and sibling of parent, then can optimize next push
unsigned currentIndex = heap.Size();
unsigned parentIndex;
if (currentIndex>0)
{
for (parentIndex = Parent(currentIndex); parentIndex < currentIndex; parentIndex++)
{
#ifdef _MSC_VER
#pragma warning(disable:4127) // conditional expression is constant
#endif
if (isMaxHeap)
{
// Every child is less than its parent
if (weight>heap[parentIndex].weight)
{
// Can't optimize
Push(weight,data,file,line);
return;
}
}
else
{
// Every child is greater than than its parent
if (weight<heap[parentIndex].weight)
{
// Can't optimize
Push(weight,data,file,line);
return;
}
}
}
}
// Parent's subsequent siblings and this row's siblings all are less than / greater than inserted element. Can insert all further elements straight to the end
heap.Insert(HeapNode(weight, data), file, line);
optimizeNextSeriesPush=true;
}
else
{
heap.Insert(HeapNode(weight, data), file, line);
}
}
template <class weight_type, class data_type, bool isMaxHeap>
void Heap<weight_type, data_type, isMaxHeap>::Push(const weight_type &weight, const data_type &data, const char *file, unsigned int line)
{
unsigned currentIndex = heap.Size();
unsigned parentIndex;
heap.Insert(HeapNode(weight, data), file, line);
while (currentIndex!=0)
{
parentIndex = Parent(currentIndex);
#ifdef _MSC_VER
#pragma warning( disable : 4127 ) // warning C4127: conditional expression is constant
#endif
if (isMaxHeap)
{
if (heap[parentIndex].weight < weight)
{
Swap(currentIndex, parentIndex);
currentIndex=parentIndex;
}
else
break;
}
else
{
if (heap[parentIndex].weight > weight)
{
Swap(currentIndex, parentIndex);
currentIndex=parentIndex;
}
else
break;
}
}
}
template <class weight_type, class data_type, bool isMaxHeap>
data_type Heap<weight_type, data_type, isMaxHeap>::Pop(const unsigned startingIndex)
{
// While we have children, swap out with the larger of the two children.
// This line will assert on an empty heap
data_type returnValue=heap[startingIndex].data;
// Move the last element to the head, and re-heapify
heap[startingIndex]=heap[heap.Size()-1];
unsigned currentIndex,leftChild,rightChild;
weight_type currentWeight;
currentIndex=startingIndex;
currentWeight=heap[startingIndex].weight;
heap.RemoveFromEnd();
#ifdef _MSC_VER
#pragma warning( disable : 4127 ) // warning C4127: conditional expression is constant
#endif
while (1)
{
leftChild=LeftChild(currentIndex);
rightChild=RightChild(currentIndex);
if (leftChild >= heap.Size())
{
// Done
return returnValue;
}
if (rightChild >= heap.Size())
{
// Only left node.
if ((isMaxHeap==true && currentWeight < heap[leftChild].weight) ||
(isMaxHeap==false && currentWeight > heap[leftChild].weight))
Swap(leftChild, currentIndex);
return returnValue;
}
else
{
// Swap with the bigger/smaller of the two children and continue
if (isMaxHeap)
{
if (heap[leftChild].weight <= currentWeight && heap[rightChild].weight <= currentWeight)
return returnValue;
if (heap[leftChild].weight > heap[rightChild].weight)
{
Swap(leftChild, currentIndex);
currentIndex=leftChild;
}
else
{
Swap(rightChild, currentIndex);
currentIndex=rightChild;
}
}
else
{
if (heap[leftChild].weight >= currentWeight && heap[rightChild].weight >= currentWeight)
return returnValue;
if (heap[leftChild].weight < heap[rightChild].weight)
{
Swap(leftChild, currentIndex);
currentIndex=leftChild;
}
else
{
Swap(rightChild, currentIndex);
currentIndex=rightChild;
}
}
}
}
}
template <class weight_type, class data_type, bool isMaxHeap>
inline data_type Heap<weight_type, data_type, isMaxHeap>::Peek(const unsigned startingIndex) const
{
return heap[startingIndex].data;
}
template <class weight_type, class data_type, bool isMaxHeap>
inline weight_type Heap<weight_type, data_type, isMaxHeap>::PeekWeight(const unsigned startingIndex) const
{
return heap[startingIndex].weight;
}
template <class weight_type, class data_type, bool isMaxHeap>
void Heap<weight_type, data_type, isMaxHeap>::Clear(bool doNotDeallocateSmallBlocks, const char *file, unsigned int line)
{
heap.Clear(doNotDeallocateSmallBlocks, file, line);
}
template <class weight_type, class data_type, bool isMaxHeap>
inline data_type& Heap<weight_type, data_type, isMaxHeap>::operator[] ( const unsigned int position ) const
{
return heap[position].data;
}
template <class weight_type, class data_type, bool isMaxHeap>
unsigned Heap<weight_type, data_type, isMaxHeap>::Size(void) const
{
return heap.Size();
}
template <class weight_type, class data_type, bool isMaxHeap>
inline unsigned Heap<weight_type, data_type, isMaxHeap>::LeftChild(const unsigned i) const
{
return i*2+1;
}
template <class weight_type, class data_type, bool isMaxHeap>
inline unsigned Heap<weight_type, data_type, isMaxHeap>::RightChild(const unsigned i) const
{
return i*2+2;
}
template <class weight_type, class data_type, bool isMaxHeap>
inline unsigned Heap<weight_type, data_type, isMaxHeap>::Parent(const unsigned i) const
{
#ifdef _DEBUG
RakAssert(i!=0);
#endif
return (i-1)/2;
}
template <class weight_type, class data_type, bool isMaxHeap>
void Heap<weight_type, data_type, isMaxHeap>::Swap(const unsigned i, const unsigned j)
{
HeapNode temp;
temp=heap[i];
heap[i]=heap[j];
heap[j]=temp;
}
}
#ifdef _MSC_VER
#pragma warning( pop )
#endif
#endif

View File

@@ -0,0 +1,299 @@
/// \file
///
/// This file is part of RakNet Copyright 2003 Jenkins Software LLC
///
/// Usage of RakNet is subject to the appropriate license agreement.
#include "DS_HuffmanEncodingTree.hpp"
#include "DS_Queue.hpp"
#include "BitStream.hpp"
#include "RakAssert.hpp"
#ifdef _MSC_VER
#pragma warning( push )
#endif
using namespace RakNet;
HuffmanEncodingTree::HuffmanEncodingTree()
{
root = 0;
}
HuffmanEncodingTree::~HuffmanEncodingTree()
{
FreeMemory();
}
void HuffmanEncodingTree::FreeMemory( void )
{
if ( root == 0 )
return ;
// Use an in-order traversal to delete the tree
DataStructures::Queue<HuffmanEncodingTreeNode *> nodeQueue;
HuffmanEncodingTreeNode *node;
nodeQueue.Push( root, _FILE_AND_LINE_ );
while ( nodeQueue.Size() > 0 )
{
node = nodeQueue.Pop();
if ( node->left )
nodeQueue.Push( node->left, _FILE_AND_LINE_ );
if ( node->right )
nodeQueue.Push( node->right, _FILE_AND_LINE_ );
RakNet::OP_DELETE(node, _FILE_AND_LINE_);
}
// Delete the encoding table
for ( int i = 0; i < 256; i++ )
rakFree_Ex(encodingTable[ i ].encoding, _FILE_AND_LINE_ );
root = 0;
}
////#include <stdio.h>
// Given a frequency table of 256 elements, all with a frequency of 1 or more, generate the tree
void HuffmanEncodingTree::GenerateFromFrequencyTable( unsigned int frequencyTable[ 256 ] )
{
int counter;
HuffmanEncodingTreeNode * node;
HuffmanEncodingTreeNode *leafList[ 256 ]; // Keep a copy of the pointers to all the leaves so we can generate the encryption table bottom-up, which is easier
// 1. Make 256 trees each with a weight equal to the frequency of the corresponding character
DataStructures::LinkedList<HuffmanEncodingTreeNode *> huffmanEncodingTreeNodeList;
FreeMemory();
for ( counter = 0; counter < 256; counter++ )
{
node = RakNet::OP_NEW<HuffmanEncodingTreeNode>( _FILE_AND_LINE_ );
node->left = 0;
node->right = 0;
node->value = (unsigned char) counter;
node->weight = frequencyTable[ counter ];
if ( node->weight == 0 )
node->weight = 1; // 0 weights are illegal
leafList[ counter ] = node; // Used later to generate the encryption table
InsertNodeIntoSortedList( node, &huffmanEncodingTreeNodeList ); // Insert and maintain sort order.
}
// 2. While there is more than one tree, take the two smallest trees and merge them so that the two trees are the left and right
// children of a new node, where the new node has the weight the sum of the weight of the left and right child nodes.
#ifdef _MSC_VER
#pragma warning( disable : 4127 ) // warning C4127: conditional expression is constant
#endif
while ( 1 )
{
huffmanEncodingTreeNodeList.Beginning();
HuffmanEncodingTreeNode *lesser, *greater;
lesser = huffmanEncodingTreeNodeList.Pop();
greater = huffmanEncodingTreeNodeList.Pop();
node = RakNet::OP_NEW<HuffmanEncodingTreeNode>( _FILE_AND_LINE_ );
node->left = lesser;
node->right = greater;
node->weight = lesser->weight + greater->weight;
lesser->parent = node; // This is done to make generating the encryption table easier
greater->parent = node; // This is done to make generating the encryption table easier
if ( huffmanEncodingTreeNodeList.Size() == 0 )
{
// 3. Assign the one remaining node in the list to the root node.
root = node;
root->parent = 0;
break;
}
// Put the new node back into the list at the correct spot to maintain the sort. Linear search time
InsertNodeIntoSortedList( node, &huffmanEncodingTreeNodeList );
}
bool tempPath[ 256 ]; // Maximum path length is 256
unsigned short tempPathLength;
HuffmanEncodingTreeNode *currentNode;
RakNet::BitStream bitStream;
// Generate the encryption table. From before, we have an array of pointers to all the leaves which contain pointers to their parents.
// This can be done more efficiently but this isn't bad and it's way easier to program and debug
for ( counter = 0; counter < 256; counter++ )
{
// Already done at the end of the loop and before it!
tempPathLength = 0;
// Set the current node at the leaf
currentNode = leafList[ counter ];
do
{
if ( currentNode->parent->left == currentNode ) // We're storing the paths in reverse order.since we are going from the leaf to the root
tempPath[ tempPathLength++ ] = false;
else
tempPath[ tempPathLength++ ] = true;
currentNode = currentNode->parent;
}
while ( currentNode != root );
// Write to the bitstream in the reverse order that we stored the path, which gives us the correct order from the root to the leaf
while ( tempPathLength-- > 0 )
{
if ( tempPath[ tempPathLength ] ) // Write 1's and 0's because writing a bool will write the BitStream TYPE_CHECKING validation bits if that is defined along with the actual data bit, which is not what we want
bitStream.Write1();
else
bitStream.Write0();
}
// Read data from the bitstream, which is written to the encoding table in bits and bitlength. Note this function allocates the encodingTable[counter].encoding pointer
encodingTable[ counter ].bitLength = ( unsigned char ) bitStream.CopyData( &encodingTable[ counter ].encoding );
// Reset the bitstream for the next iteration
bitStream.Reset();
}
}
// Pass an array of bytes to array and a preallocated BitStream to receive the output
void HuffmanEncodingTree::EncodeArray( unsigned char *input, size_t sizeInBytes, RakNet::BitStream * output )
{
unsigned counter;
// For each input byte, Write out the corresponding series of 1's and 0's that give the encoded representation
for ( counter = 0; counter < sizeInBytes; counter++ )
{
output->WriteBits( encodingTable[ input[ counter ] ].encoding, encodingTable[ input[ counter ] ].bitLength, false ); // Data is left aligned
}
// Byte align the output so the unassigned remaining bits don't equate to some actual value
if ( output->GetNumberOfBitsUsed() % 8 != 0 )
{
// Find an input that is longer than the remaining bits. Write out part of it to pad the output to be byte aligned.
unsigned char remainingBits = (unsigned char) ( 8 - ( output->GetNumberOfBitsUsed() % 8 ) );
for ( counter = 0; counter < 256; counter++ )
if ( encodingTable[ counter ].bitLength > remainingBits )
{
output->WriteBits( encodingTable[ counter ].encoding, remainingBits, false ); // Data is left aligned
break;
}
#ifdef _DEBUG
RakAssert( counter != 256 ); // Given 256 elements, we should always be able to find an input that would be >= 7 bits
#endif
}
}
unsigned HuffmanEncodingTree::DecodeArray( RakNet::BitStream * input, BitSize_t sizeInBits, size_t maxCharsToWrite, unsigned char *output )
{
HuffmanEncodingTreeNode * currentNode;
unsigned outputWriteIndex;
outputWriteIndex = 0;
currentNode = root;
// For each bit, go left if it is a 0 and right if it is a 1. When we reach a leaf, that gives us the desired value and we restart from the root
for ( unsigned counter = 0; counter < sizeInBits; counter++ )
{
if ( input->ReadBit() == false ) // left!
currentNode = currentNode->left;
else
currentNode = currentNode->right;
if ( currentNode->left == 0 && currentNode->right == 0 ) // Leaf
{
if ( outputWriteIndex < maxCharsToWrite )
output[ outputWriteIndex ] = currentNode->value;
outputWriteIndex++;
currentNode = root;
}
}
return outputWriteIndex;
}
// Pass an array of encoded bytes to array and a preallocated BitStream to receive the output
void HuffmanEncodingTree::DecodeArray( unsigned char *input, BitSize_t sizeInBits, RakNet::BitStream * output )
{
HuffmanEncodingTreeNode * currentNode;
if ( sizeInBits <= 0 )
return ;
RakNet::BitStream bitStream( input, BITS_TO_BYTES(sizeInBits), false );
currentNode = root;
// For each bit, go left if it is a 0 and right if it is a 1. When we reach a leaf, that gives us the desired value and we restart from the root
for ( unsigned counter = 0; counter < sizeInBits; counter++ )
{
if ( bitStream.ReadBit() == false ) // left!
currentNode = currentNode->left;
else
currentNode = currentNode->right;
if ( currentNode->left == 0 && currentNode->right == 0 ) // Leaf
{
output->WriteBits( &( currentNode->value ), sizeof( char ) * 8, true ); // Use WriteBits instead of Write(char) because we want to avoid TYPE_CHECKING
currentNode = root;
}
}
}
// Insertion sort. Slow but easy to write in this case
void HuffmanEncodingTree::InsertNodeIntoSortedList( HuffmanEncodingTreeNode * node, DataStructures::LinkedList<HuffmanEncodingTreeNode *> *huffmanEncodingTreeNodeList ) const
{
if ( huffmanEncodingTreeNodeList->Size() == 0 )
{
huffmanEncodingTreeNodeList->Insert( node );
return ;
}
huffmanEncodingTreeNodeList->Beginning();
unsigned counter = 0;
#ifdef _MSC_VER
#pragma warning( disable : 4127 ) // warning C4127: conditional expression is constant
#endif
while ( 1 )
{
if ( huffmanEncodingTreeNodeList->Peek()->weight < node->weight )
++( *huffmanEncodingTreeNodeList );
else
{
huffmanEncodingTreeNodeList->Insert( node );
break;
}
// Didn't find a spot in the middle - add to the end
if ( ++counter == huffmanEncodingTreeNodeList->Size() )
{
huffmanEncodingTreeNodeList->End();
huffmanEncodingTreeNodeList->Add( node )
; // Add to the end
break;
}
}
}
#ifdef _MSC_VER
#pragma warning( pop )
#endif

View File

@@ -0,0 +1,67 @@
/// \file DS_HuffmanEncodingTree.h
/// \brief \b [Internal] Generates a huffman encoding tree, used for string and global compression.
///
/// This file is part of RakNet Copyright 2003 Jenkins Software LLC
///
/// Usage of RakNet is subject to the appropriate license agreement.
#ifndef __HUFFMAN_ENCODING_TREE
#define __HUFFMAN_ENCODING_TREE
#include "RakMemoryOverride.hpp"
#include "DS_HuffmanEncodingTreeNode.hpp"
#include "BitStream.hpp"
#include "Export.hpp"
#include "DS_LinkedList.hpp"
namespace RakNet
{
/// This generates special cases of the huffman encoding tree using 8 bit keys with the additional condition that unused combinations of 8 bits are treated as a frequency of 1
class RAK_DLL_EXPORT HuffmanEncodingTree
{
public:
HuffmanEncodingTree();
~HuffmanEncodingTree();
/// \brief Pass an array of bytes to array and a preallocated BitStream to receive the output.
/// \param [in] input Array of bytes to encode
/// \param [in] sizeInBytes size of \a input
/// \param [out] output The bitstream to write to
void EncodeArray( unsigned char *input, size_t sizeInBytes, RakNet::BitStream * output );
// \brief Decodes an array encoded by EncodeArray().
unsigned DecodeArray( RakNet::BitStream * input, BitSize_t sizeInBits, size_t maxCharsToWrite, unsigned char *output );
void DecodeArray( unsigned char *input, BitSize_t sizeInBits, RakNet::BitStream * output );
/// \brief Given a frequency table of 256 elements, all with a frequency of 1 or more, generate the tree.
void GenerateFromFrequencyTable( unsigned int frequencyTable[ 256 ] );
/// \brief Free the memory used by the tree.
void FreeMemory( void );
private:
/// The root node of the tree
HuffmanEncodingTreeNode *root;
/// Used to hold bit encoding for one character
struct CharacterEncoding
{
unsigned char* encoding;
unsigned short bitLength;
};
CharacterEncoding encodingTable[ 256 ];
void InsertNodeIntoSortedList( HuffmanEncodingTreeNode * node, DataStructures::LinkedList<HuffmanEncodingTreeNode *> *huffmanEncodingTreeNodeList ) const;
};
} // namespace RakNet
#endif

View File

@@ -0,0 +1,57 @@
/// \file DS_HuffmanEncodingTreeFactory.h
/// \internal
/// \brief Creates instances of the class HuffmanEncodingTree
///
/// This file is part of RakNet Copyright 2003 Jenkins Software LLC
///
/// Usage of RakNet is subject to the appropriate license agreement.
#ifndef __HUFFMAN_ENCODING_TREE_FACTORY
#define __HUFFMAN_ENCODING_TREE_FACTORY
#include "RakMemoryOverride.hpp"
namespace RakNet {
/// Forward declarations
class HuffmanEncodingTree;
/// \brief Creates instances of the class HuffmanEncodingTree
/// \details This class takes a frequency table and given that frequence table, will generate an instance of HuffmanEncodingTree
class HuffmanEncodingTreeFactory
{
public:
/// Default constructor
HuffmanEncodingTreeFactory();
/// \brief Reset the frequency table.
/// \details You don't need to call this unless you want to reuse the class for a new tree
void Reset( void );
/// \brief Pass an array of bytes to this to add those elements to the frequency table.
/// \param[in] array the data to insert into the frequency table
/// \param[in] size the size of the data to insert
void AddToFrequencyTable( unsigned char *array, int size );
/// \brief Copies the frequency table to the array passed. Retrieve the frequency table.
/// \param[in] _frequency The frequency table used currently
void GetFrequencyTable( unsigned int _frequency[ 256 ] );
/// \brief Returns the frequency table as a pointer.
/// \return the address of the frenquency table
unsigned int * GetFrequencyTable( void );
/// \brief Generate a HuffmanEncodingTree.
/// \details You can also use GetFrequencyTable and GenerateFromFrequencyTable in the tree itself
/// \return The generated instance of HuffmanEncodingTree
HuffmanEncodingTree * GenerateTree( void );
private:
/// Frequency table
unsigned int frequency[ 256 ];
};
} // namespace RakNet
#endif

View File

@@ -0,0 +1,21 @@
/// \file
/// \brief \b [Internal] A single node in the Huffman Encoding Tree.
///
/// This file is part of RakNet Copyright 2003 Jenkins Software LLC
///
/// Usage of RakNet is subject to the appropriate license agreement.
#ifndef __HUFFMAN_ENCODING_TREE_NODE
#define __HUFFMAN_ENCODING_TREE_NODE
struct HuffmanEncodingTreeNode
{
unsigned char value;
unsigned weight;
HuffmanEncodingTreeNode *left;
HuffmanEncodingTreeNode *right;
HuffmanEncodingTreeNode *parent;
};
#endif

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,518 @@
/// \file DS_List.h
/// \internal
/// \brief Array based list.
/// \details Usually the Queue class is used instead, since it has all the same functionality and is only worse at random access.
///
/// This file is part of RakNet Copyright 2003 Jenkins Software LLC
///
/// Usage of RakNet is subject to the appropriate license agreement.
#ifndef __LIST_H
#define __LIST_H
#include "RakAssert.hpp"
#include <string.h> // memmove
#include "Export.hpp"
#include "RakMemoryOverride.hpp"
/// Maximum unsigned long
static const unsigned int MAX_UNSIGNED_LONG = 4294967295U;
/// The namespace DataStructures was only added to avoid compiler errors for commonly named data structures
/// As these data structures are stand-alone, you can use them outside of RakNet for your own projects if you wish.
namespace DataStructures
{
/// \brief Array based implementation of a list.
/// \note ONLY USE THIS FOR SHALLOW COPIES. I don't bother with operator= to improve performance.
template <class list_type>
class RAK_DLL_EXPORT List
{
public:
/// Default constructor
List();
// Destructor
~List();
/// \brief Copy constructor.
/// \param[in] original_copy The list to duplicate
List( const List& original_copy );
/// \brief Assign one list to another.
List& operator= ( const List& original_copy );
/// \brief Access an element by its index in the array.
/// \param[in] position The index into the array.
/// \return The element at position \a position.
list_type& operator[] ( const unsigned int position ) const;
/// \brief Access an element by its index in the array.
/// \param[in] position The index into the array.
/// \return The element at position \a position.
list_type& Get ( const unsigned int position ) const;
/// \brief Push an element at the end of the stack.
/// \param[in] input The new element.
void Push(const list_type &input, const char *file, unsigned int line );
/// \brief Pop an element from the end of the stack.
/// \pre Size()>0
/// \return The element at the end.
list_type& Pop(void);
/// \brief Insert an element at position \a position in the list.
/// \param[in] input The new element.
/// \param[in] position The position of the new element.
void Insert( const list_type &input, const unsigned int position, const char *file, unsigned int line );
/// \brief Insert at the end of the list.
/// \param[in] input The new element.
void Insert( const list_type &input, const char *file, unsigned int line );
/// \brief Replace the value at \a position by \a input.
/// \details If the size of the list is less than @em position, it increase the capacity of
/// the list and fill slot with @em filler.
/// \param[in] input The element to replace at position @em position.
/// \param[in] filler The element use to fill new allocated capacity.
/// \param[in] position The position of input in the list.
void Replace( const list_type &input, const list_type filler, const unsigned int position, const char *file, unsigned int line );
/// \brief Replace the last element of the list by \a input.
/// \param[in] input The element used to replace the last element.
void Replace( const list_type &input );
/// \brief Delete the element at position \a position.
/// \param[in] position The index of the element to delete
void RemoveAtIndex( const unsigned int position );
/// \brief Delete the element at position \a position.
/// \note - swaps middle with end of list, only use if list order does not matter
/// \param[in] position The index of the element to delete
void RemoveAtIndexFast( const unsigned int position );
/// \brief Delete the element at the end of the list.
void RemoveFromEnd(const unsigned num=1);
/// \brief Returns the index of the specified item or MAX_UNSIGNED_LONG if not found.
/// \param[in] input The element to check for
/// \return The index or position of @em input in the list.
/// \retval MAX_UNSIGNED_LONG The object is not in the list
/// \retval [Integer] The index of the element in the list
unsigned int GetIndexOf( const list_type &input ) const;
/// \return The number of elements in the list
unsigned int Size( void ) const;
/// \brief Clear the list
void Clear( bool doNotDeallocateSmallBlocks, const char *file, unsigned int line );
/// \brief Preallocate the list, so it needs fewer reallocations at runtime.
void Preallocate( unsigned countNeeded, const char *file, unsigned int line );
/// \brief Frees overallocated members, to use the minimum memory necessary.
/// \attention
/// This is a slow operation
void Compress( const char *file, unsigned int line );
private:
/// An array of user values
list_type* listArray;
/// Number of elements in the list
unsigned int list_size;
/// Size of \a array
unsigned int allocation_size;
};
template <class list_type>
List<list_type>::List()
{
allocation_size = 0;
listArray = 0;
list_size = 0;
}
template <class list_type>
List<list_type>::~List()
{
if (allocation_size>0)
RakNet::OP_DELETE_ARRAY(listArray, _FILE_AND_LINE_);
}
template <class list_type>
List<list_type>::List( const List& original_copy )
{
// Allocate memory for copy
if ( original_copy.list_size == 0 )
{
list_size = 0;
allocation_size = 0;
}
else
{
listArray = RakNet::OP_NEW_ARRAY<list_type >( original_copy.list_size , _FILE_AND_LINE_ );
for ( unsigned int counter = 0; counter < original_copy.list_size; ++counter )
listArray[ counter ] = original_copy.listArray[ counter ];
// Don't call constructors, assignment operators, etc.
//memcpy(listArray, original_copy.listArray, original_copy.list_size*sizeof(list_type));
list_size = allocation_size = original_copy.list_size;
}
}
template <class list_type>
List<list_type>& List<list_type>::operator= ( const List& original_copy )
{
if ( ( &original_copy ) != this )
{
Clear( false, _FILE_AND_LINE_ );
// Allocate memory for copy
if ( original_copy.list_size == 0 )
{
list_size = 0;
allocation_size = 0;
}
else
{
listArray = RakNet::OP_NEW_ARRAY<list_type >( original_copy.list_size , _FILE_AND_LINE_ );
for ( unsigned int counter = 0; counter < original_copy.list_size; ++counter )
listArray[ counter ] = original_copy.listArray[ counter ];
// Don't call constructors, assignment operators, etc.
//memcpy(listArray, original_copy.listArray, original_copy.list_size*sizeof(list_type));
list_size = allocation_size = original_copy.list_size;
}
}
return *this;
}
template <class list_type>
inline list_type& List<list_type>::operator[] ( const unsigned int position ) const
{
#ifdef _DEBUG
if (position>=list_size)
{
RakAssert ( position < list_size );
}
#endif
return listArray[ position ];
}
// Just here for debugging
template <class list_type>
inline list_type& List<list_type>::Get ( const unsigned int position ) const
{
return listArray[ position ];
}
template <class list_type>
void List<list_type>::Push(const list_type &input, const char *file, unsigned int line)
{
Insert(input, file, line);
}
template <class list_type>
inline list_type& List<list_type>::Pop(void)
{
#ifdef _DEBUG
RakAssert(list_size>0);
#endif
--list_size;
return listArray[list_size];
}
template <class list_type>
void List<list_type>::Insert( const list_type &input, const unsigned int position, const char *file, unsigned int line )
{
#ifdef _DEBUG
if (position>list_size)
{
RakAssert( position <= list_size );
}
#endif
// Reallocate list if necessary
if ( list_size == allocation_size )
{
// allocate twice the currently allocated memory
list_type * new_array;
if ( allocation_size == 0 )
allocation_size = 16;
else
allocation_size *= 2;
new_array = RakNet::OP_NEW_ARRAY<list_type >( allocation_size , file, line );
// copy old array over
for ( unsigned int counter = 0; counter < list_size; ++counter )
new_array[ counter ] = listArray[ counter ];
// Don't call constructors, assignment operators, etc.
//memcpy(new_array, listArray, list_size*sizeof(list_type));
// set old array to point to the newly allocated and twice as large array
RakNet::OP_DELETE_ARRAY(listArray, file, line);
listArray = new_array;
}
// Move the elements in the list to make room
for ( unsigned int counter = list_size; counter != position; counter-- )
listArray[ counter ] = listArray[ counter - 1 ];
// Don't call constructors, assignment operators, etc.
//memmove(listArray+position+1, listArray+position, (list_size-position)*sizeof(list_type));
// Insert the new item at the correct spot
listArray[ position ] = input;
++list_size;
}
template <class list_type>
void List<list_type>::Insert( const list_type &input, const char *file, unsigned int line )
{
// Reallocate list if necessary
if ( list_size == allocation_size )
{
// allocate twice the currently allocated memory
list_type * new_array;
if ( allocation_size == 0 )
allocation_size = 16;
else
allocation_size *= 2;
new_array = RakNet::OP_NEW_ARRAY<list_type >( allocation_size , file, line );
if (listArray)
{
// copy old array over
for ( unsigned int counter = 0; counter < list_size; ++counter )
new_array[ counter ] = listArray[ counter ];
// Don't call constructors, assignment operators, etc.
//memcpy(new_array, listArray, list_size*sizeof(list_type));
// set old array to point to the newly allocated and twice as large array
RakNet::OP_DELETE_ARRAY(listArray, file, line);
}
listArray = new_array;
}
// Insert the new item at the correct spot
listArray[ list_size ] = input;
++list_size;
}
template <class list_type>
inline void List<list_type>::Replace( const list_type &input, const list_type filler, const unsigned int position, const char *file, unsigned int line )
{
if ( ( list_size > 0 ) && ( position < list_size ) )
{
// Direct replacement
listArray[ position ] = input;
}
else
{
if ( position >= allocation_size )
{
// Reallocate the list to size position and fill in blanks with filler
list_type * new_array;
allocation_size = position + 1;
new_array = RakNet::OP_NEW_ARRAY<list_type >( allocation_size , file, line );
// copy old array over
for ( unsigned int counter = 0; counter < list_size; ++counter )
new_array[ counter ] = listArray[ counter ];
// Don't call constructors, assignment operators, etc.
//memcpy(new_array, listArray, list_size*sizeof(list_type));
// set old array to point to the newly allocated array
RakNet::OP_DELETE_ARRAY(listArray, file, line);
listArray = new_array;
}
// Fill in holes with filler
while ( list_size < position )
listArray[ list_size++ ] = filler;
// Fill in the last element with the new item
listArray[ list_size++ ] = input;
#ifdef _DEBUG
RakAssert( list_size == position + 1 );
#endif
}
}
template <class list_type>
inline void List<list_type>::Replace( const list_type &input )
{
if ( list_size > 0 )
listArray[ list_size - 1 ] = input;
}
template <class list_type>
void List<list_type>::RemoveAtIndex( const unsigned int position )
{
#ifdef _DEBUG
if (position >= list_size)
{
RakAssert( position < list_size );
return;
}
#endif
if ( position < list_size )
{
// Compress the array
for ( unsigned int counter = position; counter < list_size - 1 ; ++counter )
listArray[ counter ] = listArray[ counter + 1 ];
// Don't call constructors, assignment operators, etc.
// memmove(listArray+position, listArray+position+1, (list_size-1-position) * sizeof(list_type));
RemoveFromEnd();
}
}
template <class list_type>
void List<list_type>::RemoveAtIndexFast( const unsigned int position )
{
#ifdef _DEBUG
if (position >= list_size)
{
RakAssert( position < list_size );
return;
}
#endif
--list_size;
listArray[position]=listArray[list_size];
}
template <class list_type>
inline void List<list_type>::RemoveFromEnd( const unsigned num )
{
// Delete the last elements on the list. No compression needed
#ifdef _DEBUG
RakAssert(list_size>=num);
#endif
list_size-=num;
}
template <class list_type>
unsigned int List<list_type>::GetIndexOf( const list_type &input ) const
{
for ( unsigned int i = 0; i < list_size; ++i )
if ( listArray[ i ] == input )
return i;
return MAX_UNSIGNED_LONG;
}
template <class list_type>
inline unsigned int List<list_type>::Size( void ) const
{
return list_size;
}
template <class list_type>
void List<list_type>::Clear( bool doNotDeallocateSmallBlocks, const char *file, unsigned int line )
{
if ( allocation_size == 0 )
return;
if (allocation_size>512 || doNotDeallocateSmallBlocks==false)
{
RakNet::OP_DELETE_ARRAY(listArray, file, line);
allocation_size = 0;
listArray = 0;
}
list_size = 0;
}
template <class list_type>
void List<list_type>::Compress( const char *file, unsigned int line )
{
list_type * new_array;
if ( allocation_size == 0 )
return ;
new_array = RakNet::OP_NEW_ARRAY<list_type >( allocation_size , file, line );
// copy old array over
for ( unsigned int counter = 0; counter < list_size; ++counter )
new_array[ counter ] = listArray[ counter ];
// Don't call constructors, assignment operators, etc.
//memcpy(new_array, listArray, list_size*sizeof(list_type));
// set old array to point to the newly allocated array
RakNet::OP_DELETE_ARRAY(listArray, file, line);
listArray = new_array;
}
template <class list_type>
void List<list_type>::Preallocate( unsigned countNeeded, const char *file, unsigned int line )
{
unsigned amountToAllocate = allocation_size;
if (allocation_size==0)
amountToAllocate=16;
while (amountToAllocate < countNeeded)
amountToAllocate<<=1;
if ( allocation_size < amountToAllocate)
{
// allocate twice the currently allocated memory
list_type * new_array;
allocation_size=amountToAllocate;
new_array = RakNet::OP_NEW_ARRAY< list_type >( allocation_size , file, line );
if (listArray)
{
// copy old array over
for ( unsigned int counter = 0; counter < list_size; ++counter )
new_array[ counter ] = listArray[ counter ];
// Don't call constructors, assignment operators, etc.
//memcpy(new_array, listArray, list_size*sizeof(list_type));
// set old array to point to the newly allocated and twice as large array
RakNet::OP_DELETE_ARRAY(listArray, file, line);
}
listArray = new_array;
}
}
} // End namespace
#endif

321
third-party/RakNet/src/RakNet/DS_Map.hpp vendored Normal file
View File

@@ -0,0 +1,321 @@
/// \file DS_Map.h
/// \internal
/// \brief Map
///
/// This file is part of RakNet Copyright 2003 Jenkins Software LLC
///
/// Usage of RakNet is subject to the appropriate license agreement.
#ifndef __RAKNET_MAP_H
#define __RAKNET_MAP_H
#include "DS_OrderedList.hpp"
#include "Export.hpp"
#include "RakMemoryOverride.hpp"
#include "RakAssert.hpp"
// If I want to change this to a red-black tree, this is a good site: http://www.cs.auckland.ac.nz/software/AlgAnim/red_black.html
// This makes insertions and deletions faster. But then traversals are slow, while they are currently fast.
/// The namespace DataStructures was only added to avoid compiler errors for commonly named data structures
/// As these data structures are stand-alone, you can use them outside of RakNet for your own projects if you wish.
namespace DataStructures
{
/// The default comparison has to be first so it can be called as a default parameter.
/// It then is followed by MapNode, followed by NodeComparisonFunc
template <class key_type>
int defaultMapKeyComparison(const key_type &a, const key_type &b)
{
if (a<b) return -1; if (a==b) return 0; return 1;
}
/// \note IMPORTANT! If you use defaultMapKeyComparison then call IMPLEMENT_DEFAULT_COMPARISON or you will get an unresolved external linker error.
template <class key_type, class data_type, int (*key_comparison_func)(const key_type&, const key_type&)=defaultMapKeyComparison<key_type> >
class RAK_DLL_EXPORT Map
{
public:
static void IMPLEMENT_DEFAULT_COMPARISON(void) {DataStructures::defaultMapKeyComparison<key_type>(key_type(),key_type());}
struct MapNode
{
MapNode() {}
MapNode(key_type _key, data_type _data) : mapNodeKey(_key), mapNodeData(_data) {}
MapNode& operator = ( const MapNode& input ) {mapNodeKey=input.mapNodeKey; mapNodeData=input.mapNodeData; return *this;}
MapNode( const MapNode & input) {mapNodeKey=input.mapNodeKey; mapNodeData=input.mapNodeData;}
key_type mapNodeKey;
data_type mapNodeData;
};
// Has to be a static because the comparison callback for DataStructures::OrderedList is a C function
static int NodeComparisonFunc(const key_type &a, const MapNode &b)
{
#ifdef _MSC_VER
#pragma warning( disable : 4127 ) // warning C4127: conditional expression is constant
#endif
return key_comparison_func(a, b.mapNodeKey);
}
Map();
~Map();
Map( const Map& original_copy );
Map& operator= ( const Map& original_copy );
data_type& Get(const key_type &key) const;
data_type Pop(const key_type &key);
// Add if needed
void Set(const key_type &key, const data_type &data);
// Must already exist
void SetExisting(const key_type &key, const data_type &data);
// Must add
void SetNew(const key_type &key, const data_type &data);
bool Has(const key_type &key) const;
bool Delete(const key_type &key);
data_type& operator[] ( const unsigned int position ) const;
key_type GetKeyAtIndex( const unsigned int position ) const;
unsigned GetIndexAtKey( const key_type &key );
void RemoveAtIndex(const unsigned index);
void Clear(void);
unsigned Size(void) const;
protected:
DataStructures::OrderedList< key_type,MapNode,&Map::NodeComparisonFunc > mapNodeList;
void SaveLastSearch(const key_type &key, unsigned index) const;
bool HasSavedSearchResult(const key_type &key) const;
unsigned lastSearchIndex;
key_type lastSearchKey;
bool lastSearchIndexValid;
};
template <class key_type, class data_type, int (*key_comparison_func)(const key_type&,const key_type&)>
Map<key_type, data_type, key_comparison_func>::Map()
{
lastSearchIndexValid=false;
}
template <class key_type, class data_type, int (*key_comparison_func)(const key_type&,const key_type&)>
Map<key_type, data_type, key_comparison_func>::~Map()
{
Clear();
}
template <class key_type, class data_type, int (*key_comparison_func)(const key_type&,const key_type&)>
Map<key_type, data_type, key_comparison_func>::Map( const Map& original_copy )
{
mapNodeList=original_copy.mapNodeList;
lastSearchIndex=original_copy.lastSearchIndex;
lastSearchKey=original_copy.lastSearchKey;
lastSearchIndexValid=original_copy.lastSearchIndexValid;
}
template <class key_type, class data_type, int (*key_comparison_func)(const key_type&,const key_type&)>
Map<key_type, data_type, key_comparison_func>& Map<key_type, data_type, key_comparison_func>::operator= ( const Map& original_copy )
{
mapNodeList=original_copy.mapNodeList;
lastSearchIndex=original_copy.lastSearchIndex;
lastSearchKey=original_copy.lastSearchKey;
lastSearchIndexValid=original_copy.lastSearchIndexValid;
return *this;
}
template <class key_type, class data_type, int (*key_comparison_func)(const key_type&,const key_type&)>
data_type& Map<key_type, data_type, key_comparison_func>::Get(const key_type &key) const
{
if (HasSavedSearchResult(key))
return mapNodeList[lastSearchIndex].mapNodeData;
bool objectExists;
unsigned index;
index=mapNodeList.GetIndexFromKey(key, &objectExists);
RakAssert(objectExists);
SaveLastSearch(key,index);
return mapNodeList[index].mapNodeData;
}
template <class key_type, class data_type, int (*key_comparison_func)(const key_type&,const key_type&)>
unsigned Map<key_type, data_type, key_comparison_func>::GetIndexAtKey( const key_type &key )
{
if (HasSavedSearchResult(key))
return lastSearchIndex;
bool objectExists;
unsigned index;
index=mapNodeList.GetIndexFromKey(key, &objectExists);
if (objectExists==false)
{
RakAssert(objectExists);
}
SaveLastSearch(key,index);
return index;
}
template <class key_type, class data_type, int (*key_comparison_func)(const key_type&,const key_type&)>
void Map<key_type, data_type, key_comparison_func>::RemoveAtIndex(const unsigned index)
{
mapNodeList.RemoveAtIndex(index);
lastSearchIndexValid=false;
}
template <class key_type, class data_type, int (*key_comparison_func)(const key_type&,const key_type&)>
data_type Map<key_type, data_type, key_comparison_func>::Pop(const key_type &key)
{
bool objectExists;
unsigned index;
if (HasSavedSearchResult(key))
index=lastSearchIndex;
else
{
index=mapNodeList.GetIndexFromKey(key, &objectExists);
RakAssert(objectExists);
}
data_type tmp = mapNodeList[index].mapNodeData;
mapNodeList.RemoveAtIndex(index);
lastSearchIndexValid=false;
return tmp;
}
template <class key_type, class data_type, int (*key_comparison_func)(const key_type&,const key_type&)>
void Map<key_type, data_type, key_comparison_func>::Set(const key_type &key, const data_type &data)
{
bool objectExists;
unsigned index;
if (HasSavedSearchResult(key))
{
mapNodeList[lastSearchIndex].mapNodeData=data;
return;
}
index=mapNodeList.GetIndexFromKey(key, &objectExists);
if (objectExists)
{
SaveLastSearch(key,index);
mapNodeList[index].mapNodeData=data;
}
else
{
SaveLastSearch(key,mapNodeList.Insert(key,MapNode(key,data), true, _FILE_AND_LINE_));
}
}
template <class key_type, class data_type, int (*key_comparison_func)(const key_type&,const key_type&)>
void Map<key_type, data_type, key_comparison_func>::SetExisting(const key_type &key, const data_type &data)
{
bool objectExists;
unsigned index;
if (HasSavedSearchResult(key))
{
index=lastSearchIndex;
}
else
{
index=mapNodeList.GetIndexFromKey(key, &objectExists);
RakAssert(objectExists);
SaveLastSearch(key,index);
}
mapNodeList[index].mapNodeData=data;
}
template <class key_type, class data_type, int (*key_comparison_func)(const key_type&,const key_type&)>
void Map<key_type, data_type, key_comparison_func>::SetNew(const key_type &key, const data_type &data)
{
#ifdef _DEBUG
bool objectExists;
mapNodeList.GetIndexFromKey(key, &objectExists);
RakAssert(objectExists==false);
#endif
SaveLastSearch(key,mapNodeList.Insert(key,MapNode(key,data), true, _FILE_AND_LINE_));
}
template <class key_type, class data_type, int (*key_comparison_func)(const key_type&,const key_type&)>
bool Map<key_type, data_type, key_comparison_func>::Has(const key_type &key) const
{
if (HasSavedSearchResult(key))
return true;
bool objectExists;
unsigned index;
index=mapNodeList.GetIndexFromKey(key, &objectExists);
if (objectExists)
SaveLastSearch(key,index);
return objectExists;
}
template <class key_type, class data_type, int (*key_comparison_func)(const key_type&,const key_type&)>
bool Map<key_type, data_type, key_comparison_func>::Delete(const key_type &key)
{
if (HasSavedSearchResult(key))
{
lastSearchIndexValid=false;
mapNodeList.RemoveAtIndex(lastSearchIndex);
return true;
}
bool objectExists;
unsigned index;
index=mapNodeList.GetIndexFromKey(key, &objectExists);
if (objectExists)
{
lastSearchIndexValid=false;
mapNodeList.RemoveAtIndex(index);
return true;
}
else
return false;
}
template <class key_type, class data_type, int (*key_comparison_func)(const key_type&,const key_type&)>
void Map<key_type, data_type, key_comparison_func>::Clear(void)
{
lastSearchIndexValid=false;
mapNodeList.Clear(false, _FILE_AND_LINE_);
}
template <class key_type, class data_type, int (*key_comparison_func)(const key_type&,const key_type&)>
data_type& Map<key_type, data_type, key_comparison_func>::operator[]( const unsigned int position ) const
{
return mapNodeList[position].mapNodeData;
}
template <class key_type, class data_type, int (*key_comparison_func)(const key_type&,const key_type&)>
key_type Map<key_type, data_type, key_comparison_func>::GetKeyAtIndex( const unsigned int position ) const
{
return mapNodeList[position].mapNodeKey;
}
template <class key_type, class data_type, int (*key_comparison_func)(const key_type&,const key_type&)>
unsigned Map<key_type, data_type, key_comparison_func>::Size(void) const
{
return mapNodeList.Size();
}
template <class key_type, class data_type, int (*key_comparison_func)(const key_type&,const key_type&)>
void Map<key_type, data_type, key_comparison_func>::SaveLastSearch(const key_type &key, const unsigned index) const
{
(void) key;
(void) index;
/*
lastSearchIndex=index;
lastSearchKey=key;
lastSearchIndexValid=true;
*/
}
template <class key_type, class data_type, int (*key_comparison_func)(const key_type&,const key_type&)>
bool Map<key_type, data_type, key_comparison_func>::HasSavedSearchResult(const key_type &key) const
{
(void) key;
// Not threadsafe!
return false;
// return lastSearchIndexValid && key_comparison_func(key,lastSearchKey)==0;
}
}
#endif

View File

@@ -0,0 +1,294 @@
/// \file DS_MemoryPool.h
///
/// This file is part of RakNet Copyright 2003 Jenkins Software LLC
///
/// Usage of RakNet is subject to the appropriate license agreement.
#ifndef __MEMORY_POOL_H
#define __MEMORY_POOL_H
#ifndef __APPLE__
// Use stdlib and not malloc for compatibility
#include <stdlib.h>
#endif
#include "RakAssert.hpp"
#include "Export.hpp"
#include "RakMemoryOverride.hpp"
// DS_MEMORY_POOL_MAX_FREE_PAGES must be > 1
#define DS_MEMORY_POOL_MAX_FREE_PAGES 4
//#define _DISABLE_MEMORY_POOL
namespace DataStructures
{
/// Very fast memory pool for allocating and deallocating structures that don't have constructors or destructors.
/// Contains a list of pages, each of which has an array of the user structures
template <class MemoryBlockType>
class RAK_DLL_EXPORT MemoryPool
{
public:
struct Page;
struct MemoryWithPage
{
MemoryBlockType userMemory;
Page *parentPage;
};
struct Page
{
MemoryWithPage** availableStack;
int availableStackSize;
MemoryWithPage* block;
Page *next, *prev;
};
MemoryPool();
~MemoryPool();
void SetPageSize(int size); // Defaults to 16384 bytes
MemoryBlockType *Allocate(const char *file, unsigned int line);
void Release(MemoryBlockType *m, const char *file, unsigned int line);
void Clear(const char *file, unsigned int line);
int GetAvailablePagesSize(void) const {return availablePagesSize;}
int GetUnavailablePagesSize(void) const {return unavailablePagesSize;}
int GetMemoryPoolPageSize(void) const {return memoryPoolPageSize;}
protected:
int BlocksPerPage(void) const;
void AllocateFirst(void);
bool InitPage(Page *page, Page *prev, const char *file, unsigned int line);
// availablePages contains pages which have room to give the user new blocks. We return these blocks from the head of the list
// unavailablePages are pages which are totally full, and from which we do not return new blocks.
// Pages move from the head of unavailablePages to the tail of availablePages, and from the head of availablePages to the tail of unavailablePages
Page *availablePages, *unavailablePages;
int availablePagesSize, unavailablePagesSize;
int memoryPoolPageSize;
};
template<class MemoryBlockType>
MemoryPool<MemoryBlockType>::MemoryPool()
{
#ifndef _DISABLE_MEMORY_POOL
//AllocateFirst();
availablePagesSize=0;
unavailablePagesSize=0;
memoryPoolPageSize=16384;
#endif
}
template<class MemoryBlockType>
MemoryPool<MemoryBlockType>::~MemoryPool()
{
#ifndef _DISABLE_MEMORY_POOL
Clear(_FILE_AND_LINE_);
#endif
}
template<class MemoryBlockType>
void MemoryPool<MemoryBlockType>::SetPageSize(int size)
{
memoryPoolPageSize=size;
}
template<class MemoryBlockType>
MemoryBlockType* MemoryPool<MemoryBlockType>::Allocate(const char *file, unsigned int line)
{
#ifdef _DISABLE_MEMORY_POOL
return (MemoryBlockType*) rakMalloc_Ex(sizeof(MemoryBlockType), file, line);
#else
if (availablePagesSize>0)
{
MemoryBlockType *retVal;
Page *curPage;
curPage=availablePages;
retVal = (MemoryBlockType*) curPage->availableStack[--(curPage->availableStackSize)];
if (curPage->availableStackSize==0)
{
--availablePagesSize;
availablePages=curPage->next;
RakAssert(availablePagesSize==0 || availablePages->availableStackSize>0);
curPage->next->prev=curPage->prev;
curPage->prev->next=curPage->next;
if (unavailablePagesSize++==0)
{
unavailablePages=curPage;
curPage->next=curPage;
curPage->prev=curPage;
}
else
{
curPage->next=unavailablePages;
curPage->prev=unavailablePages->prev;
unavailablePages->prev->next=curPage;
unavailablePages->prev=curPage;
}
}
RakAssert(availablePagesSize==0 || availablePages->availableStackSize>0);
return retVal;
}
availablePages = (Page *) rakMalloc_Ex(sizeof(Page), file, line);
if (availablePages==0)
return 0;
availablePagesSize=1;
if (InitPage(availablePages, availablePages, file, line)==false)
return 0;
// If this assert hits, we couldn't allocate even 1 block per page. Increase the page size
RakAssert(availablePages->availableStackSize>1);
return (MemoryBlockType *) availablePages->availableStack[--availablePages->availableStackSize];
#endif
}
template<class MemoryBlockType>
void MemoryPool<MemoryBlockType>::Release(MemoryBlockType *m, const char *file, unsigned int line)
{
#ifdef _DISABLE_MEMORY_POOL
rakFree_Ex(m, file, line);
return;
#else
// Find the page this block is in and return it.
Page *curPage;
MemoryWithPage *memoryWithPage = (MemoryWithPage*)m;
curPage=memoryWithPage->parentPage;
if (curPage->availableStackSize==0)
{
// The page is in the unavailable list so move it to the available list
curPage->availableStack[curPage->availableStackSize++]=memoryWithPage;
unavailablePagesSize--;
// As this page is no longer totally empty, move it to the end of available pages
curPage->next->prev=curPage->prev;
curPage->prev->next=curPage->next;
if (unavailablePagesSize>0 && curPage==unavailablePages)
unavailablePages=unavailablePages->next;
if (availablePagesSize++==0)
{
availablePages=curPage;
curPage->next=curPage;
curPage->prev=curPage;
}
else
{
curPage->next=availablePages;
curPage->prev=availablePages->prev;
availablePages->prev->next=curPage;
availablePages->prev=curPage;
}
}
else
{
curPage->availableStack[curPage->availableStackSize++]=memoryWithPage;
if (curPage->availableStackSize==BlocksPerPage() &&
availablePagesSize>=DS_MEMORY_POOL_MAX_FREE_PAGES)
{
// After a certain point, just deallocate empty pages rather than keep them around
if (curPage==availablePages)
{
availablePages=curPage->next;
RakAssert(availablePages->availableStackSize>0);
}
curPage->prev->next=curPage->next;
curPage->next->prev=curPage->prev;
availablePagesSize--;
rakFree_Ex(curPage->availableStack, file, line );
rakFree_Ex(curPage->block, file, line );
rakFree_Ex(curPage, file, line );
}
}
#endif
}
template<class MemoryBlockType>
void MemoryPool<MemoryBlockType>::Clear(const char *file, unsigned int line)
{
#ifdef _DISABLE_MEMORY_POOL
return;
#else
Page *cur, *freed;
if (availablePagesSize>0)
{
cur = availablePages;
#ifdef _MSC_VER
#pragma warning(disable:4127) // conditional expression is constant
#endif
while (true)
// do
{
rakFree_Ex(cur->availableStack, file, line );
rakFree_Ex(cur->block, file, line );
freed=cur;
cur=cur->next;
if (cur==availablePages)
{
rakFree_Ex(freed, file, line );
break;
}
rakFree_Ex(freed, file, line );
}// while(cur!=availablePages);
}
if (unavailablePagesSize>0)
{
cur = unavailablePages;
while (1)
//do
{
rakFree_Ex(cur->availableStack, file, line );
rakFree_Ex(cur->block, file, line );
freed=cur;
cur=cur->next;
if (cur==unavailablePages)
{
rakFree_Ex(freed, file, line );
break;
}
rakFree_Ex(freed, file, line );
} // while(cur!=unavailablePages);
}
availablePagesSize=0;
unavailablePagesSize=0;
#endif
}
template<class MemoryBlockType>
int MemoryPool<MemoryBlockType>::BlocksPerPage(void) const
{
return memoryPoolPageSize / sizeof(MemoryWithPage);
}
template<class MemoryBlockType>
bool MemoryPool<MemoryBlockType>::InitPage(Page *page, Page *prev, const char *file, unsigned int line)
{
int i=0;
const int bpp = BlocksPerPage();
page->block=(MemoryWithPage*) rakMalloc_Ex(memoryPoolPageSize, file, line);
if (page->block==0)
return false;
page->availableStack=(MemoryWithPage**)rakMalloc_Ex(sizeof(MemoryWithPage*)*bpp, file, line);
if (page->availableStack==0)
{
rakFree_Ex(page->block, file, line );
return false;
}
MemoryWithPage *curBlock = page->block;
MemoryWithPage **curStack = page->availableStack;
while (i < bpp)
{
curBlock->parentPage=page;
curStack[i]=curBlock++;
i++;
}
page->availableStackSize=bpp;
page->next=availablePages;
page->prev=prev;
return true;
}
}
#endif

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,244 @@
/// \file DS_OrderedChannelHeap.h
/// \internal
/// \brief Ordered Channel Heap . This is a heap where you add to it on multiple ordered channels, with each channel having a different weight.
///
/// This file is part of RakNet Copyright 2003 Jenkins Software LLC
///
/// Usage of RakNet is subject to the appropriate license agreement.
#ifndef __RAKNET_ORDERED_CHANNEL_HEAP_H
#define __RAKNET_ORDERED_CHANNEL_HEAP_H
#include "DS_Heap.hpp"
#include "DS_Map.hpp"
#include "DS_Queue.hpp"
#include "Export.hpp"
#include "RakAssert.hpp"
#include "Rand.hpp"
/// The namespace DataStructures was only added to avoid compiler errors for commonly named data structures
/// As these data structures are stand-alone, you can use them outside of RakNet for your own projects if you wish.
namespace DataStructures
{
template <class channel_key_type, class heap_data_type, int (*channel_key_comparison_func)(const channel_key_type&, const channel_key_type&)=defaultMapKeyComparison<channel_key_type> >
class RAK_DLL_EXPORT OrderedChannelHeap
{
public:
static void IMPLEMENT_DEFAULT_COMPARISON(void) {DataStructures::defaultMapKeyComparison<channel_key_type>(channel_key_type(),channel_key_type());}
OrderedChannelHeap();
~OrderedChannelHeap();
void Push(const channel_key_type &channelID, const heap_data_type &data);
void PushAtHead(const unsigned index, const channel_key_type &channelID, const heap_data_type &data);
heap_data_type Pop(const unsigned startingIndex=0);
heap_data_type Peek(const unsigned startingIndex) const;
void AddChannel(const channel_key_type &channelID, const double weight);
void RemoveChannel(channel_key_type channelID);
void Clear(void);
heap_data_type& operator[] ( const unsigned int position ) const;
unsigned ChannelSize(const channel_key_type &channelID);
unsigned Size(void) const;
struct QueueAndWeight
{
DataStructures::Queue<double> randResultQueue;
double weight;
bool signalDeletion;
};
struct HeapChannelAndData
{
HeapChannelAndData() {}
HeapChannelAndData(const channel_key_type &_channel, const heap_data_type &_data) : data(_data), channel(_channel) {}
heap_data_type data;
channel_key_type channel;
};
protected:
DataStructures::Map<channel_key_type, QueueAndWeight*, channel_key_comparison_func> map;
DataStructures::Heap<double, HeapChannelAndData, true> heap;
void GreatestRandResult(void);
};
template <class channel_key_type, class heap_data_type, int (*channel_key_comparison_func)(const channel_key_type&, const channel_key_type&)>
OrderedChannelHeap<channel_key_type, heap_data_type, channel_key_comparison_func>::OrderedChannelHeap()
{
}
template <class channel_key_type, class heap_data_type, int (*channel_key_comparison_func)(const channel_key_type&, const channel_key_type&)>
OrderedChannelHeap<channel_key_type, heap_data_type, channel_key_comparison_func>::~OrderedChannelHeap()
{
Clear();
}
template <class channel_key_type, class heap_data_type, int (*channel_key_comparison_func)(const channel_key_type&, const channel_key_type&)>
void OrderedChannelHeap<channel_key_type, heap_data_type, channel_key_comparison_func>::Push(const channel_key_type &channelID, const heap_data_type &data)
{
PushAtHead(MAX_UNSIGNED_LONG, channelID, data);
}
template <class channel_key_type, class heap_data_type, int (*channel_key_comparison_func)(const channel_key_type&, const channel_key_type&)>
void OrderedChannelHeap<channel_key_type, heap_data_type, channel_key_comparison_func>::GreatestRandResult(void)
{
double greatest;
unsigned i;
greatest=0.0;
for (i=0; i < map.Size(); i++)
{
if (map[i]->randResultQueue.Size() && map[i]->randResultQueue[0]>greatest)
greatest=map[i]->randResultQueue[0];
}
return greatest;
}
template <class channel_key_type, class heap_data_type, int (*channel_key_comparison_func)(const channel_key_type&, const channel_key_type&)>
void OrderedChannelHeap<channel_key_type, heap_data_type, channel_key_comparison_func>::PushAtHead(const unsigned index, const channel_key_type &channelID, const heap_data_type &data)
{
// If an assert hits here then this is an unknown channel. Call AddChannel first.
QueueAndWeight *queueAndWeight=map.Get(channelID);
double maxRange, minRange, rnd;
if (queueAndWeight->randResultQueue.Size()==0)
{
// Set maxRange to the greatest random number waiting to be returned, rather than 1.0 necessarily
// This is so weights are scaled similarly among channels. For example, if the head weight for a used channel was .25
// and then we added another channel, the new channel would need to choose between .25 and 0
// If we chose between 1.0 and 0, it would be 1/.25 (4x) more likely to be at the head of the heap than it should be
maxRange=GreatestRandResult();
if (maxRange==0.0)
maxRange=1.0;
minRange=0.0;
}
else if (index >= queueAndWeight->randResultQueue.Size())
{
maxRange=queueAndWeight->randResultQueue[queueAndWeight->randResultQueue.Size()-1]*.99999999;
minRange=0.0;
}
else
{
if (index==0)
{
maxRange=GreatestRandResult();
if (maxRange==queueAndWeight->randResultQueue[0])
maxRange=1.0;
}
else if (index >= queueAndWeight->randResultQueue.Size())
maxRange=queueAndWeight->randResultQueue[queueAndWeight->randResultQueue.Size()-1]*.99999999;
else
maxRange=queueAndWeight->randResultQueue[index-1]*.99999999;
minRange=maxRange=queueAndWeight->randResultQueue[index]*1.00000001;
}
#ifdef _DEBUG
RakAssert(maxRange!=0.0);
#endif
rnd=frandomMT() * (maxRange - minRange);
if (rnd==0.0)
rnd=maxRange/2.0;
if (index >= queueAndWeight->randResultQueue.Size())
queueAndWeight->randResultQueue.Push(rnd);
else
queueAndWeight->randResultQueue.PushAtHead(rnd, index);
heap.Push(rnd*queueAndWeight->weight, HeapChannelAndData(channelID, data));
}
template <class channel_key_type, class heap_data_type, int (*channel_key_comparison_func)(const channel_key_type&, const channel_key_type&)>
heap_data_type OrderedChannelHeap<channel_key_type, heap_data_type, channel_key_comparison_func>::Pop(const unsigned startingIndex)
{
RakAssert(startingIndex < heap.Size());
QueueAndWeight *queueAndWeight=map.Get(heap[startingIndex].channel);
if (startingIndex!=0)
{
// Ugly - have to count in the heap how many nodes have the same channel, so we know where to delete from in the queue
unsigned indiceCount=0;
unsigned i;
for (i=0; i < startingIndex; i++)
if (channel_key_comparison_func(heap[i].channel,heap[startingIndex].channel)==0)
indiceCount++;
queueAndWeight->randResultQueue.RemoveAtIndex(indiceCount);
}
else
{
// TODO - ordered channel heap uses progressively lower values as items are inserted. But this won't give relative ordering among channels. I have to renormalize after every pop.
queueAndWeight->randResultQueue.Pop();
}
// Try to remove the channel after every pop, because doing so is not valid while there are elements in the list.
if (queueAndWeight->signalDeletion)
RemoveChannel(heap[startingIndex].channel);
return heap.Pop(startingIndex).data;
}
template <class channel_key_type, class heap_data_type, int (*channel_key_comparison_func)(const channel_key_type&, const channel_key_type&)>
heap_data_type OrderedChannelHeap<channel_key_type, heap_data_type, channel_key_comparison_func>::Peek(const unsigned startingIndex) const
{
HeapChannelAndData heapChannelAndData = heap.Peek(startingIndex);
return heapChannelAndData.data;
}
template <class channel_key_type, class heap_data_type, int (*channel_key_comparison_func)(const channel_key_type&, const channel_key_type&)>
void OrderedChannelHeap<channel_key_type, heap_data_type, channel_key_comparison_func>::AddChannel(const channel_key_type &channelID, const double weight)
{
QueueAndWeight *qaw = RakNet::OP_NEW<QueueAndWeight>( _FILE_AND_LINE_ );
qaw->weight=weight;
qaw->signalDeletion=false;
map.SetNew(channelID, qaw);
}
template <class channel_key_type, class heap_data_type, int (*channel_key_comparison_func)(const channel_key_type&, const channel_key_type&)>
void OrderedChannelHeap<channel_key_type, heap_data_type, channel_key_comparison_func>::RemoveChannel(channel_key_type channelID)
{
if (map.Has(channelID))
{
unsigned i;
i=map.GetIndexAtKey(channelID);
if (map[i]->randResultQueue.Size()==0)
{
RakNet::OP_DELETE(map[i], _FILE_AND_LINE_);
map.RemoveAtIndex(i);
}
else
{
// Signal this channel for deletion later, because the heap has nodes with this channel right now
map[i]->signalDeletion=true;
}
}
}
template <class channel_key_type, class heap_data_type, int (*channel_key_comparison_func)(const channel_key_type&, const channel_key_type&)>
unsigned OrderedChannelHeap<channel_key_type, heap_data_type, channel_key_comparison_func>::Size(void) const
{
return heap.Size();
}
template <class channel_key_type, class heap_data_type, int (*channel_key_comparison_func)(const channel_key_type&, const channel_key_type&)>
heap_data_type& OrderedChannelHeap<channel_key_type, heap_data_type, channel_key_comparison_func>::operator[]( const unsigned int position ) const
{
return heap[position].data;
}
template <class channel_key_type, class heap_data_type, int (*channel_key_comparison_func)(const channel_key_type&, const channel_key_type&)>
unsigned OrderedChannelHeap<channel_key_type, heap_data_type, channel_key_comparison_func>::ChannelSize(const channel_key_type &channelID)
{
QueueAndWeight *queueAndWeight=map.Get(channelID);
return queueAndWeight->randResultQueue.Size();
}
template <class channel_key_type, class heap_data_type, int (*channel_key_comparison_func)(const channel_key_type&, const channel_key_type&)>
void OrderedChannelHeap<channel_key_type, heap_data_type, channel_key_comparison_func>::Clear(void)
{
unsigned i;
for (i=0; i < map.Size(); i++)
RakNet::OP_DELETE(map[i], _FILE_AND_LINE_);
map.Clear(_FILE_AND_LINE_);
heap.Clear(_FILE_AND_LINE_);
}
}
#endif

View File

@@ -0,0 +1,263 @@
/// \file DS_OrderedList.h
/// \internal
/// \brief Quicksort ordered list.
///
/// This file is part of RakNet Copyright 2003 Jenkins Software LLC
///
/// Usage of RakNet is subject to the appropriate license agreement.
#include "DS_List.hpp"
#include "RakMemoryOverride.hpp"
#include "Export.hpp"
#ifndef __ORDERED_LIST_H
#define __ORDERED_LIST_H
/// The namespace DataStructures was only added to avoid compiler errors for commonly named data structures
/// As these data structures are stand-alone, you can use them outside of RakNet for your own projects if you wish.
namespace DataStructures
{
template <class key_type, class data_type>
int defaultOrderedListComparison(const key_type &a, const data_type &b)
{
if (a<b) return -1; if (a==b) return 0; return 1;
}
/// \note IMPORTANT! If you use defaultOrderedListComparison then call IMPLEMENT_DEFAULT_COMPARISON or you will get an unresolved external linker error.
template <class key_type, class data_type, int (*default_comparison_function)(const key_type&, const data_type&)=defaultOrderedListComparison<key_type, data_type> >
class RAK_DLL_EXPORT OrderedList
{
public:
static void IMPLEMENT_DEFAULT_COMPARISON(void) {DataStructures::defaultOrderedListComparison<key_type, data_type>(key_type(),data_type());}
OrderedList();
~OrderedList();
OrderedList( const OrderedList& original_copy );
OrderedList& operator= ( const OrderedList& original_copy );
/// comparisonFunction must take a key_type and a data_type and return <0, ==0, or >0
/// If the data type has comparison operators already defined then you can just use defaultComparison
bool HasData(const key_type &key, int (*cf)(const key_type&, const data_type&)=default_comparison_function) const;
// GetIndexFromKey returns where the insert should go at the same time checks if it is there
unsigned GetIndexFromKey(const key_type &key, bool *objectExists, int (*cf)(const key_type&, const data_type&)=default_comparison_function) const;
data_type GetElementFromKey(const key_type &key, int (*cf)(const key_type&, const data_type&)=default_comparison_function) const;
bool GetElementFromKey(const key_type &key, data_type &element, int (*cf)(const key_type&, const data_type&)=default_comparison_function) const;
unsigned Insert(const key_type &key, const data_type &data, bool assertOnDuplicate, const char *file, unsigned int line, int (*cf)(const key_type&, const data_type&)=default_comparison_function);
unsigned Remove(const key_type &key, int (*cf)(const key_type&, const data_type&)=default_comparison_function);
unsigned RemoveIfExists(const key_type &key, int (*cf)(const key_type&, const data_type&)=default_comparison_function);
data_type& operator[] ( const unsigned int position ) const;
void RemoveAtIndex(const unsigned index);
void InsertAtIndex(const data_type &data, const unsigned index, const char *file, unsigned int line);
void InsertAtEnd(const data_type &data, const char *file, unsigned int line);
void RemoveFromEnd(const unsigned num=1);
void Clear(bool doNotDeallocate, const char *file, unsigned int line);
unsigned Size(void) const;
protected:
DataStructures::List<data_type> orderedList;
};
template <class key_type, class data_type, int (*default_comparison_function)(const key_type&, const data_type&)>
OrderedList<key_type, data_type, default_comparison_function>::OrderedList()
{
}
template <class key_type, class data_type, int (*default_comparison_function)(const key_type&, const data_type&)>
OrderedList<key_type, data_type, default_comparison_function>::~OrderedList()
{
Clear(false, _FILE_AND_LINE_);
}
template <class key_type, class data_type, int (*default_comparison_function)(const key_type&, const data_type&)>
OrderedList<key_type, data_type, default_comparison_function>::OrderedList( const OrderedList& original_copy )
{
orderedList=original_copy.orderedList;
}
template <class key_type, class data_type, int (*default_comparison_function)(const key_type&, const data_type&)>
OrderedList<key_type, data_type, default_comparison_function>& OrderedList<key_type, data_type, default_comparison_function>::operator= ( const OrderedList& original_copy )
{
orderedList=original_copy.orderedList;
return *this;
}
template <class key_type, class data_type, int (*default_comparison_function)(const key_type&, const data_type&)>
bool OrderedList<key_type, data_type, default_comparison_function>::HasData(const key_type &key, int (*cf)(const key_type&, const data_type&)) const
{
bool objectExists;
GetIndexFromKey(key, &objectExists, cf);
return objectExists;
}
template <class key_type, class data_type, int (*default_comparison_function)(const key_type&, const data_type&)>
data_type OrderedList<key_type, data_type, default_comparison_function>::GetElementFromKey(const key_type &key, int (*cf)(const key_type&, const data_type&)) const
{
bool objectExists;
unsigned index;
index = GetIndexFromKey(key, &objectExists, cf);
RakAssert(objectExists);
return orderedList[index];
}
template <class key_type, class data_type, int (*default_comparison_function)(const key_type&, const data_type&)>
bool OrderedList<key_type, data_type, default_comparison_function>::GetElementFromKey(const key_type &key, data_type &element, int (*cf)(const key_type&, const data_type&)) const
{
bool objectExists;
unsigned index;
index = GetIndexFromKey(key, &objectExists, cf);
if (objectExists)
element = orderedList[index];
return objectExists;
}
template <class key_type, class data_type, int (*default_comparison_function)(const key_type&, const data_type&)>
unsigned OrderedList<key_type, data_type, default_comparison_function>::GetIndexFromKey(const key_type &key, bool *objectExists, int (*cf)(const key_type&, const data_type&)) const
{
int index, upperBound, lowerBound;
int res;
if (orderedList.Size()==0)
{
*objectExists=false;
return 0;
}
upperBound=(int)orderedList.Size()-1;
lowerBound=0;
index = (int)orderedList.Size()/2;
#ifdef _MSC_VER
#pragma warning( disable : 4127 ) // warning C4127: conditional expression is constant
#endif
while (1)
{
res = cf(key,orderedList[index]);
if (res==0)
{
*objectExists=true;
return index;
}
else if (res<0)
{
upperBound=index-1;
}
else// if (res>0)
{
lowerBound=index+1;
}
index=lowerBound+(upperBound-lowerBound)/2;
if (lowerBound>upperBound)
{
*objectExists=false;
return lowerBound; // No match
}
}
}
template <class key_type, class data_type, int (*default_comparison_function)(const key_type&, const data_type&)>
unsigned OrderedList<key_type, data_type, default_comparison_function>::Insert(const key_type &key, const data_type &data, bool assertOnDuplicate, const char *file, unsigned int line, int (*cf)(const key_type&, const data_type&))
{
(void) assertOnDuplicate;
bool objectExists;
unsigned index;
index = GetIndexFromKey(key, &objectExists, cf);
// Don't allow duplicate insertion.
if (objectExists)
{
// This is usually a bug!
RakAssert(assertOnDuplicate==false);
return (unsigned)-1;
}
if (index>=orderedList.Size())
{
orderedList.Insert(data, file, line);
return orderedList.Size()-1;
}
else
{
orderedList.Insert(data,index, file, line);
return index;
}
}
template <class key_type, class data_type, int (*default_comparison_function)(const key_type&, const data_type&)>
unsigned OrderedList<key_type, data_type, default_comparison_function>::Remove(const key_type &key, int (*cf)(const key_type&, const data_type&))
{
bool objectExists;
unsigned index;
index = GetIndexFromKey(key, &objectExists, cf);
// Can't find the element to remove if this assert hits
// RakAssert(objectExists==true);
if (objectExists==false)
{
RakAssert(objectExists==true);
return 0;
}
orderedList.RemoveAtIndex(index);
return index;
}
template <class key_type, class data_type, int (*default_comparison_function)(const key_type&, const data_type&)>
unsigned OrderedList<key_type, data_type, default_comparison_function>::RemoveIfExists(const key_type &key, int (*cf)(const key_type&, const data_type&))
{
bool objectExists;
unsigned index;
index = GetIndexFromKey(key, &objectExists, cf);
// Can't find the element to remove if this assert hits
if (objectExists==false)
return 0;
orderedList.RemoveAtIndex(index);
return index;
}
template <class key_type, class data_type, int (*default_comparison_function)(const key_type&, const data_type&)>
void OrderedList<key_type, data_type, default_comparison_function>::RemoveAtIndex(const unsigned index)
{
orderedList.RemoveAtIndex(index);
}
template <class key_type, class data_type, int (*default_comparison_function)(const key_type&, const data_type&)>
void OrderedList<key_type, data_type, default_comparison_function>::InsertAtIndex(const data_type &data, const unsigned index, const char *file, unsigned int line)
{
orderedList.Insert(data, index, file, line);
}
template <class key_type, class data_type, int (*default_comparison_function)(const key_type&, const data_type&)>
void OrderedList<key_type, data_type, default_comparison_function>::InsertAtEnd(const data_type &data, const char *file, unsigned int line)
{
orderedList.Insert(data, file, line);
}
template <class key_type, class data_type, int (*default_comparison_function)(const key_type&, const data_type&)>
void OrderedList<key_type, data_type, default_comparison_function>::RemoveFromEnd(const unsigned num)
{
orderedList.RemoveFromEnd(num);
}
template <class key_type, class data_type, int (*default_comparison_function)(const key_type&, const data_type&)>
void OrderedList<key_type, data_type, default_comparison_function>::Clear(bool doNotDeallocate, const char *file, unsigned int line)
{
orderedList.Clear(doNotDeallocate, file, line);
}
template <class key_type, class data_type, int (*default_comparison_function)(const key_type&, const data_type&)>
data_type& OrderedList<key_type, data_type, default_comparison_function>::operator[]( const unsigned int position ) const
{
return orderedList[position];
}
template <class key_type, class data_type, int (*default_comparison_function)(const key_type&, const data_type&)>
unsigned OrderedList<key_type, data_type, default_comparison_function>::Size(void) const
{
return orderedList.Size();
}
}
#endif

View File

@@ -0,0 +1,435 @@
/// \file DS_Queue.h
/// \internal
/// \brief A queue used by RakNet.
///
/// This file is part of RakNet Copyright 2003 Jenkins Software LLC
///
/// Usage of RakNet is subject to the appropriate license agreement.
#ifndef __QUEUE_H
#define __QUEUE_H
// Template classes have to have all the code in the header file
#include "RakAssert.hpp"
#include "Export.hpp"
#include "RakMemoryOverride.hpp"
/// The namespace DataStructures was only added to avoid compiler errors for commonly named data structures
/// As these data structures are stand-alone, you can use them outside of RakNet for your own projects if you wish.
namespace DataStructures
{
/// \brief A queue implemented as an array with a read and write index.
template <class queue_type>
class RAK_DLL_EXPORT Queue
{
public:
Queue();
~Queue();
Queue( Queue& original_copy );
bool operator= ( const Queue& original_copy );
void Push( const queue_type& input, const char *file, unsigned int line );
void PushAtHead( const queue_type& input, unsigned index, const char *file, unsigned int line );
queue_type& operator[] ( unsigned int position ) const; // Not a normal thing you do with a queue but can be used for efficiency
void RemoveAtIndex( unsigned int position ); // Not a normal thing you do with a queue but can be used for efficiency
inline queue_type Peek( void ) const;
inline queue_type PeekTail( void ) const;
inline queue_type Pop( void );
// Debug: Set pointer to 0, for memory leak detection
inline queue_type PopDeref( void );
inline unsigned int Size( void ) const;
inline bool IsEmpty(void) const;
inline unsigned int AllocationSize( void ) const;
inline void Clear( const char *file, unsigned int line );
void Compress( const char *file, unsigned int line );
bool Find ( queue_type q );
void ClearAndForceAllocation( int size, const char *file, unsigned int line ); // Force a memory allocation to a certain larger size
private:
queue_type* array;
unsigned int head; // Array index for the head of the queue
unsigned int tail; // Array index for the tail of the queue
unsigned int allocation_size;
};
template <class queue_type>
inline unsigned int Queue<queue_type>::Size( void ) const
{
if ( head <= tail )
return tail -head;
else
return allocation_size -head + tail;
}
template <class queue_type>
inline bool Queue<queue_type>::IsEmpty(void) const
{
return head==tail;
}
template <class queue_type>
inline unsigned int Queue<queue_type>::AllocationSize( void ) const
{
return allocation_size;
}
template <class queue_type>
Queue<queue_type>::Queue()
{
//allocation_size = 16;
//array = RakNet::OP_NEW_ARRAY<queue_type>(allocation_size, _FILE_AND_LINE_ );
allocation_size = 0;
array=0;
head = 0;
tail = 0;
}
template <class queue_type>
Queue<queue_type>::~Queue()
{
if (allocation_size>0)
RakNet::OP_DELETE_ARRAY(array, _FILE_AND_LINE_);
}
template <class queue_type>
inline queue_type Queue<queue_type>::Pop( void )
{
#ifdef _DEBUG
RakAssert( head != tail);
#endif
//head=(head+1) % allocation_size;
if ( ++head == allocation_size )
head = 0;
if ( head == 0 )
return ( queue_type ) array[ allocation_size -1 ];
return ( queue_type ) array[ head -1 ];
}
template <class queue_type>
inline queue_type Queue<queue_type>::PopDeref( void )
{
if ( ++head == allocation_size )
head = 0;
queue_type q;
if ( head == 0 )
{
q=array[ allocation_size -1 ];
array[ allocation_size -1 ]=0;
return q;
}
q=array[ head -1 ];
array[ head -1 ]=0;
return q;
}
template <class queue_type>
void Queue<queue_type>::PushAtHead( const queue_type& input, unsigned index, const char *file, unsigned int line )
{
RakAssert(index <= Size());
// Just force a reallocation, will be overwritten
Push(input, file, line );
if (Size()==1)
return;
unsigned writeIndex, readIndex, trueWriteIndex, trueReadIndex;
writeIndex=Size()-1;
readIndex=writeIndex-1;
while (readIndex >= index)
{
if ( head + writeIndex >= allocation_size )
trueWriteIndex = head + writeIndex - allocation_size;
else
trueWriteIndex = head + writeIndex;
if ( head + readIndex >= allocation_size )
trueReadIndex = head + readIndex - allocation_size;
else
trueReadIndex = head + readIndex;
array[trueWriteIndex]=array[trueReadIndex];
if (readIndex==0)
break;
writeIndex--;
readIndex--;
}
if ( head + index >= allocation_size )
trueWriteIndex = head + index - allocation_size;
else
trueWriteIndex = head + index;
array[trueWriteIndex]=input;
}
template <class queue_type>
inline queue_type Queue<queue_type>::Peek( void ) const
{
#ifdef _DEBUG
RakAssert( head != tail );
#endif
return ( queue_type ) array[ head ];
}
template <class queue_type>
inline queue_type Queue<queue_type>::PeekTail( void ) const
{
#ifdef _DEBUG
RakAssert( head != tail );
#endif
if (tail!=0)
return ( queue_type ) array[ tail-1 ];
else
return ( queue_type ) array[ allocation_size-1 ];
}
template <class queue_type>
void Queue<queue_type>::Push( const queue_type& input, const char *file, unsigned int line )
{
if ( allocation_size == 0 )
{
array = RakNet::OP_NEW_ARRAY<queue_type>(16, file, line );
head = 0;
tail = 1;
array[ 0 ] = input;
allocation_size = 16;
return ;
}
array[ tail++ ] = input;
if ( tail == allocation_size )
tail = 0;
if ( tail == head )
{
// unsigned int index=tail;
// Need to allocate more memory.
queue_type * new_array;
new_array = RakNet::OP_NEW_ARRAY<queue_type>(allocation_size * 2, file, line );
#ifdef _DEBUG
RakAssert( new_array );
#endif
if (new_array==0)
return;
for ( unsigned int counter = 0; counter < allocation_size; ++counter )
new_array[ counter ] = array[ ( head + counter ) % ( allocation_size ) ];
head = 0;
tail = allocation_size;
allocation_size *= 2;
// Delete the old array and move the pointer to the new array
RakNet::OP_DELETE_ARRAY(array, file, line);
array = new_array;
}
}
template <class queue_type>
Queue<queue_type>::Queue( Queue& original_copy )
{
// Allocate memory for copy
if ( original_copy.Size() == 0 )
{
allocation_size = 0;
}
else
{
array = RakNet::OP_NEW_ARRAY<queue_type >( original_copy.Size() + 1 , _FILE_AND_LINE_ );
for ( unsigned int counter = 0; counter < original_copy.Size(); ++counter )
array[ counter ] = original_copy.array[ ( original_copy.head + counter ) % ( original_copy.allocation_size ) ];
head = 0;
tail = original_copy.Size();
allocation_size = original_copy.Size() + 1;
}
}
template <class queue_type>
bool Queue<queue_type>::operator= ( const Queue& original_copy )
{
if ( ( &original_copy ) == this )
return false;
Clear(_FILE_AND_LINE_);
// Allocate memory for copy
if ( original_copy.Size() == 0 )
{
allocation_size = 0;
}
else
{
array = RakNet::OP_NEW_ARRAY<queue_type >( original_copy.Size() + 1 , _FILE_AND_LINE_ );
for ( unsigned int counter = 0; counter < original_copy.Size(); ++counter )
array[ counter ] = original_copy.array[ ( original_copy.head + counter ) % ( original_copy.allocation_size ) ];
head = 0;
tail = original_copy.Size();
allocation_size = original_copy.Size() + 1;
}
return true;
}
template <class queue_type>
inline void Queue<queue_type>::Clear ( const char *file, unsigned int line )
{
if ( allocation_size == 0 )
return ;
if (allocation_size > 32)
{
RakNet::OP_DELETE_ARRAY(array, file, line);
allocation_size = 0;
}
head = 0;
tail = 0;
}
template <class queue_type>
void Queue<queue_type>::Compress ( const char *file, unsigned int line )
{
queue_type* new_array;
unsigned int newAllocationSize;
if (allocation_size==0)
return;
newAllocationSize=1;
while (newAllocationSize <= Size())
newAllocationSize<<=1; // Must be a better way to do this but I'm too dumb to figure it out quickly :)
new_array = RakNet::OP_NEW_ARRAY<queue_type >(newAllocationSize, file, line );
for (unsigned int counter=0; counter < Size(); ++counter)
new_array[counter] = array[(head + counter)%(allocation_size)];
tail=Size();
allocation_size=newAllocationSize;
head=0;
// Delete the old array and move the pointer to the new array
RakNet::OP_DELETE_ARRAY(array, file, line);
array=new_array;
}
template <class queue_type>
bool Queue<queue_type>::Find ( queue_type q )
{
if ( allocation_size == 0 )
return false;
unsigned int counter = head;
while ( counter != tail )
{
if ( array[ counter ] == q )
return true;
counter = ( counter + 1 ) % allocation_size;
}
return false;
}
template <class queue_type>
void Queue<queue_type>::ClearAndForceAllocation( int size, const char *file, unsigned int line )
{
RakNet::OP_DELETE_ARRAY(array, file, line);
if (size>0)
array = RakNet::OP_NEW_ARRAY<queue_type>(size, file, line );
else
array=0;
allocation_size = size;
head = 0;
tail = 0;
}
template <class queue_type>
inline queue_type& Queue<queue_type>::operator[] ( unsigned int position ) const
{
#ifdef _DEBUG
RakAssert( position < Size() );
#endif
//return array[(head + position) % allocation_size];
if ( head + position >= allocation_size )
return array[ head + position - allocation_size ];
else
return array[ head + position ];
}
template <class queue_type>
void Queue<queue_type>::RemoveAtIndex( unsigned int position )
{
#ifdef _DEBUG
RakAssert( position < Size() );
RakAssert( head != tail );
#endif
if ( head == tail || position >= Size() )
return ;
unsigned int index;
unsigned int next;
//index = (head + position) % allocation_size;
if ( head + position >= allocation_size )
index = head + position - allocation_size;
else
index = head + position;
//next = (index + 1) % allocation_size;
next = index + 1;
if ( next == allocation_size )
next = 0;
while ( next != tail )
{
// Overwrite the previous element
array[ index ] = array[ next ];
index = next;
//next = (next + 1) % allocation_size;
if ( ++next == allocation_size )
next = 0;
}
// Move the tail back
if ( tail == 0 )
tail = allocation_size - 1;
else
--tail;
}
} // End namespace
#endif

View File

@@ -0,0 +1,103 @@
/// \file DS_QueueLinkedList.h
/// \internal
/// \brief A queue implemented as a linked list.
///
/// This file is part of RakNet Copyright 2003 Jenkins Software LLC
///
/// Usage of RakNet is subject to the appropriate license agreement.
#ifndef __QUEUE_LINKED_LIST_H
#define __QUEUE_LINKED_LIST_H
#include "DS_LinkedList.hpp"
#include "Export.hpp"
#include "RakMemoryOverride.hpp"
/// The namespace DataStructures was only added to avoid compiler errors for commonly named data structures
/// As these data structures are stand-alone, you can use them outside of RakNet for your own projects if you wish.
namespace DataStructures
{
/// \brief A queue implemented using a linked list. Rarely used.
template <class QueueType>
class RAK_DLL_EXPORT QueueLinkedList
{
public:
QueueLinkedList();
QueueLinkedList( const QueueLinkedList& original_copy );
bool operator= ( const QueueLinkedList& original_copy );
QueueType Pop( void );
QueueType& Peek( void );
QueueType& EndPeek( void );
void Push( const QueueType& input );
unsigned int Size( void );
void Clear( void );
void Compress( void );
private:
LinkedList<QueueType> data;
};
template <class QueueType>
QueueLinkedList<QueueType>::QueueLinkedList()
{
}
template <class QueueType>
inline unsigned int QueueLinkedList<QueueType>::Size()
{
return data.Size();
}
template <class QueueType>
inline QueueType QueueLinkedList<QueueType>::Pop( void )
{
data.Beginning();
return ( QueueType ) data.Pop();
}
template <class QueueType>
inline QueueType& QueueLinkedList<QueueType>::Peek( void )
{
data.Beginning();
return ( QueueType ) data.Peek();
}
template <class QueueType>
inline QueueType& QueueLinkedList<QueueType>::EndPeek( void )
{
data.End();
return ( QueueType ) data.Peek();
}
template <class QueueType>
void QueueLinkedList<QueueType>::Push( const QueueType& input )
{
data.End();
data.Add( input );
}
template <class QueueType>
QueueLinkedList<QueueType>::QueueLinkedList( const QueueLinkedList& original_copy )
{
data = original_copy.data;
}
template <class QueueType>
bool QueueLinkedList<QueueType>::operator= ( const QueueLinkedList& original_copy )
{
if ( ( &original_copy ) == this )
return false;
data = original_copy.data;
}
template <class QueueType>
void QueueLinkedList<QueueType>::Clear ( void )
{
data.Clear();
}
} // End namespace
#endif

View File

@@ -0,0 +1,236 @@
/// \file DS_RangeList.h
/// \internal
/// \brief A queue implemented as a linked list.
///
/// This file is part of RakNet Copyright 2003 Jenkins Software LLC
///
/// Usage of RakNet is subject to the appropriate license agreement.
#ifndef __RANGE_LIST_H
#define __RANGE_LIST_H
#include "DS_OrderedList.hpp"
#include "BitStream.hpp"
#include "RakMemoryOverride.hpp"
#include "RakAssert.hpp"
namespace DataStructures
{
template <class range_type>
struct RangeNode
{
RangeNode() {}
~RangeNode() {}
RangeNode(range_type min, range_type max) {minIndex=min; maxIndex=max;}
range_type minIndex;
range_type maxIndex;
};
template <class range_type>
int RangeNodeComp(const range_type &a, const RangeNode<range_type> &b)
{
if (a<b.minIndex)
return -1;
if (a==b.minIndex)
return 0;
return 1;
}
template <class range_type>
class RAK_DLL_EXPORT RangeList
{
public:
RangeList();
~RangeList();
void Insert(range_type index);
void Clear(void);
unsigned Size(void) const;
unsigned RangeSum(void) const;
RakNet::BitSize_t Serialize(RakNet::BitStream *in, RakNet::BitSize_t maxBits, bool clearSerialized);
bool Deserialize(RakNet::BitStream *out);
DataStructures::OrderedList<range_type, RangeNode<range_type> , RangeNodeComp<range_type> > ranges;
};
template <class range_type>
RakNet::BitSize_t RangeList<range_type>::Serialize(RakNet::BitStream *in, RakNet::BitSize_t maxBits, bool clearSerialized)
{
RakAssert(ranges.Size() < (unsigned short)-1);
RakNet::BitStream tempBS;
RakNet::BitSize_t bitsWritten;
unsigned short countWritten;
unsigned i;
countWritten=0;
bitsWritten=0;
for (i=0; i < ranges.Size(); i++)
{
if ((int)sizeof(unsigned short)*8+bitsWritten+(int)sizeof(range_type)*8*2+1>maxBits)
break;
unsigned char minEqualsMax;
if (ranges[i].minIndex==ranges[i].maxIndex)
minEqualsMax=1;
else
minEqualsMax=0;
tempBS.Write(minEqualsMax); // Use one byte, intead of one bit, for speed, as this is done a lot
tempBS.Write(ranges[i].minIndex);
bitsWritten+=sizeof(range_type)*8+8;
if (ranges[i].minIndex!=ranges[i].maxIndex)
{
tempBS.Write(ranges[i].maxIndex);
bitsWritten+=sizeof(range_type)*8;
}
countWritten++;
}
in->AlignWriteToByteBoundary();
RakNet::BitSize_t before=in->GetWriteOffset();
in->Write(countWritten);
bitsWritten+=in->GetWriteOffset()-before;
// RAKNET_DEBUG_PRINTF("%i ", in->GetNumberOfBitsUsed());
in->Write(&tempBS, tempBS.GetNumberOfBitsUsed());
// RAKNET_DEBUG_PRINTF("%i %i \n", tempBS.GetNumberOfBitsUsed(),in->GetNumberOfBitsUsed());
if (clearSerialized && countWritten)
{
unsigned rangeSize=ranges.Size();
for (i=0; i < rangeSize-countWritten; i++)
{
ranges[i]=ranges[i+countWritten];
}
ranges.RemoveFromEnd(countWritten);
}
return bitsWritten;
}
template <class range_type>
bool RangeList<range_type>::Deserialize(RakNet::BitStream *out)
{
ranges.Clear(true, _FILE_AND_LINE_);
unsigned short count;
out->AlignReadToByteBoundary();
out->Read(count);
unsigned short i;
range_type min,max;
unsigned char maxEqualToMin=0;
for (i=0; i < count; i++)
{
out->Read(maxEqualToMin);
if (out->Read(min)==false)
return false;
if (maxEqualToMin==false)
{
if (out->Read(max)==false)
return false;
if (max<min)
return false;
}
else
max=min;
ranges.InsertAtEnd(RangeNode<range_type>(min,max), _FILE_AND_LINE_);
}
return true;
}
template <class range_type>
RangeList<range_type>::RangeList()
{
RangeNodeComp<range_type>(0, RangeNode<range_type>());
}
template <class range_type>
RangeList<range_type>::~RangeList()
{
Clear();
}
template <class range_type>
void RangeList<range_type>::Insert(range_type index)
{
if (ranges.Size()==0)
{
ranges.Insert(index, RangeNode<range_type>(index, index), true, _FILE_AND_LINE_);
return;
}
bool objectExists;
unsigned insertionIndex=ranges.GetIndexFromKey(index, &objectExists);
if (insertionIndex==ranges.Size())
{
if (index == ranges[insertionIndex-1].maxIndex+(range_type)1)
ranges[insertionIndex-1].maxIndex++;
else if (index > ranges[insertionIndex-1].maxIndex+(range_type)1)
{
// Insert at end
ranges.Insert(index, RangeNode<range_type>(index, index), true, _FILE_AND_LINE_);
}
return;
}
if (index < ranges[insertionIndex].minIndex-(range_type)1)
{
// Insert here
ranges.InsertAtIndex(RangeNode<range_type>(index, index), insertionIndex, _FILE_AND_LINE_);
return;
}
else if (index == ranges[insertionIndex].minIndex-(range_type)1)
{
// Decrease minIndex and join left
ranges[insertionIndex].minIndex--;
if (insertionIndex>0 && ranges[insertionIndex-1].maxIndex+(range_type)1==ranges[insertionIndex].minIndex)
{
ranges[insertionIndex-1].maxIndex=ranges[insertionIndex].maxIndex;
ranges.RemoveAtIndex(insertionIndex);
}
return;
}
else if (index >= ranges[insertionIndex].minIndex && index <= ranges[insertionIndex].maxIndex)
{
// Already exists
return;
}
else if (index == ranges[insertionIndex].maxIndex+(range_type)1)
{
// Increase maxIndex and join right
ranges[insertionIndex].maxIndex++;
if (insertionIndex<ranges.Size()-1 && ranges[insertionIndex+(range_type)1].minIndex==ranges[insertionIndex].maxIndex+(range_type)1)
{
ranges[insertionIndex+1].minIndex=ranges[insertionIndex].minIndex;
ranges.RemoveAtIndex(insertionIndex);
}
return;
}
}
template <class range_type>
void RangeList<range_type>::Clear(void)
{
ranges.Clear(true, _FILE_AND_LINE_);
}
template <class range_type>
unsigned RangeList<range_type>::Size(void) const
{
return ranges.Size();
}
template <class range_type>
unsigned RangeList<range_type>::RangeSum(void) const
{
unsigned sum=0,i;
for (i=0; i < ranges.Size(); i++)
sum+=ranges[i].maxIndex-ranges[i].minIndex+1;
return sum;
}
}
#endif

1128
third-party/RakNet/src/RakNet/DS_Table.cpp vendored Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,343 @@
/// \file DS_Table.h
///
/// This file is part of RakNet Copyright 2003 Jenkins Software LLC
///
/// Usage of RakNet is subject to the appropriate license agreement.
#ifndef __TABLE_H
#define __TABLE_H
#ifdef _MSC_VER
#pragma warning( push )
#endif
#include "DS_List.hpp"
#include "DS_BPlusTree.hpp"
#include "RakMemoryOverride.hpp"
#include "Export.hpp"
#include "RakString.hpp"
#define _TABLE_BPLUS_TREE_ORDER 16
#define _TABLE_MAX_COLUMN_NAME_LENGTH 64
/// The namespace DataStructures was only added to avoid compiler errors for commonly named data structures
/// As these data structures are stand-alone, you can use them outside of RakNet for your own projects if you wish.
namespace DataStructures
{
/// \brief Holds a set of columns, a set of rows, and rows times columns cells.
/// \details The table data structure is useful if you want to store a set of structures and perform queries on those structures.<BR>
/// This is a relatively simple and fast implementation of the types of tables commonly used in databases.<BR>
/// See TableSerializer to serialize data members of the table.<BR>
/// See LightweightDatabaseClient and LightweightDatabaseServer to transmit the table over the network.
class RAK_DLL_EXPORT Table
{
public:
enum ColumnType
{
// Cell::i used
NUMERIC,
// Cell::c used to hold a null terminated string.
STRING,
// Cell::c holds data. Cell::i holds data length of c in bytes.
BINARY,
// Cell::c holds data. Not deallocated. Set manually by assigning ptr.
POINTER,
};
/// Holds the actual data in the table
// Note: If this structure is changed the struct in the swig files need to be changed as well
struct RAK_DLL_EXPORT Cell
{
Cell();
~Cell();
Cell(double numericValue, char *charValue, void *ptr, ColumnType type);
void SetByType(double numericValue, char *charValue, void *ptr, ColumnType type);
void Clear(void);
/// Numeric
void Set(int input);
void Set(unsigned int input);
void Set(double input);
/// String
void Set(const char *input);
/// Binary
void Set(const char *input, int inputLength);
/// Pointer
void SetPtr(void* p);
/// Numeric
void Get(int *output);
void Get(double *output);
/// String
void Get(char *output);
/// Binary
void Get(char *output, int *outputLength);
RakNet::RakString ToString(ColumnType columnType);
// assignment operator and copy constructor
Cell& operator = ( const Cell& input );
Cell( const Cell & input);
ColumnType EstimateColumnType(void) const;
bool isEmpty;
double i;
char *c;
void *ptr;
};
/// Stores the name and type of the column
/// \internal
// Note: If this structure is changed the struct in the swig files need to be changed as well
struct RAK_DLL_EXPORT ColumnDescriptor
{
ColumnDescriptor();
~ColumnDescriptor();
ColumnDescriptor(const char cn[_TABLE_MAX_COLUMN_NAME_LENGTH],ColumnType ct);
char columnName[_TABLE_MAX_COLUMN_NAME_LENGTH];
ColumnType columnType;
};
/// Stores the list of cells for this row, and a special flag used for internal sorting
// Note: If this structure is changed the struct in the swig files need to be changed as well
struct RAK_DLL_EXPORT Row
{
// list of cells
DataStructures::List<Cell*> cells;
/// Numeric
void UpdateCell(unsigned columnIndex, double value);
/// String
void UpdateCell(unsigned columnIndex, const char *str);
/// Binary
void UpdateCell(unsigned columnIndex, int byteLength, const char *data);
};
// Operations to perform for cell comparison
enum FilterQueryType
{
QF_EQUAL,
QF_NOT_EQUAL,
QF_GREATER_THAN,
QF_GREATER_THAN_EQ,
QF_LESS_THAN,
QF_LESS_THAN_EQ,
QF_IS_EMPTY,
QF_NOT_EMPTY,
};
// Compare the cell value for a row at columnName to the cellValue using operation.
// Note: If this structure is changed the struct in the swig files need to be changed as well
struct RAK_DLL_EXPORT FilterQuery
{
FilterQuery();
~FilterQuery();
FilterQuery(unsigned column, Cell *cell, FilterQueryType op);
// If columnName is specified, columnIndex will be looked up using it.
char columnName[_TABLE_MAX_COLUMN_NAME_LENGTH];
unsigned columnIndex;
Cell *cellValue;
FilterQueryType operation;
};
/// Increasing or decreasing sort order
enum SortQueryType
{
QS_INCREASING_ORDER,
QS_DECREASING_ORDER,
};
// Sort on increasing or decreasing order for a particular column
// Note: If this structure is changed the struct in the swig files need to be changed as well
struct RAK_DLL_EXPORT SortQuery
{
/// The index of the table column we are sorting on
unsigned columnIndex;
/// See SortQueryType
SortQueryType operation;
};
// Constructor
Table();
// Destructor
~Table();
/// \brief Adds a column to the table
/// \param[in] columnName The name of the column
/// \param[in] columnType What type of data this column will hold
/// \return The index of the new column
unsigned AddColumn(const char columnName[_TABLE_MAX_COLUMN_NAME_LENGTH], ColumnType columnType);
/// \brief Removes a column by index
/// \param[in] columnIndex The index of the column to remove
void RemoveColumn(unsigned columnIndex);
/// \brief Gets the index of a column by name
/// \details Column indices are stored in the order they are added.
/// \param[in] columnName The name of the column
/// \return The index of the column, or (unsigned)-1 if no such column
unsigned ColumnIndex(char columnName[_TABLE_MAX_COLUMN_NAME_LENGTH]) const;
unsigned ColumnIndex(const char *columnName) const;
/// \brief Gives the string name of the column at a certain index
/// \param[in] index The index of the column
/// \return The name of the column, or 0 if an invalid index
char* ColumnName(unsigned index) const;
/// \brief Returns the type of a column, referenced by index
/// \param[in] index The index of the column
/// \return The type of the column
ColumnType GetColumnType(unsigned index) const;
/// Returns the number of columns
/// \return The number of columns in the table
unsigned GetColumnCount(void) const;
/// Returns the number of rows
/// \return The number of rows in the table
unsigned GetRowCount(void) const;
/// \brief Adds a row to the table
/// \details New rows are added with empty values for all cells. However, if you specify initialCelLValues you can specify initial values
/// It's up to you to ensure that the values in the specific cells match the type of data used by that row
/// rowId can be considered the primary key for the row. It is much faster to lookup a row by its rowId than by searching keys.
/// rowId must be unique
/// Rows are stored in sorted order in the table, using rowId as the sort key
/// \param[in] rowId The UNIQUE primary key for the row. This can never be changed.
/// \param[in] initialCellValues Initial values to give the row (optional)
/// \return The newly added row
Table::Row* AddRow(unsigned rowId);
Table::Row* AddRow(unsigned rowId, DataStructures::List<Cell> &initialCellValues);
Table::Row* AddRow(unsigned rowId, DataStructures::List<Cell*> &initialCellValues, bool copyCells=false);
/// \brief Removes a row specified by rowId.
/// \param[in] rowId The ID of the row
/// \return true if the row was deleted. False if not.
bool RemoveRow(unsigned rowId);
/// \brief Removes all the rows with IDs that the specified table also has.
/// \param[in] tableContainingRowIDs The IDs of the rows
void RemoveRows(Table *tableContainingRowIDs);
/// \brief Updates a particular cell in the table.
/// \note If you are going to update many cells of a particular row, it is more efficient to call GetRow and perform the operations on the row directly.
/// \note Row pointers do not change, so you can also write directly to the rows for more efficiency.
/// \param[in] rowId The ID of the row
/// \param[in] columnIndex The column of the cell
/// \param[in] value The data to set
bool UpdateCell(unsigned rowId, unsigned columnIndex, int value);
bool UpdateCell(unsigned rowId, unsigned columnIndex, char *str);
bool UpdateCell(unsigned rowId, unsigned columnIndex, int byteLength, char *data);
bool UpdateCellByIndex(unsigned rowIndex, unsigned columnIndex, int value);
bool UpdateCellByIndex(unsigned rowIndex, unsigned columnIndex, char *str);
bool UpdateCellByIndex(unsigned rowIndex, unsigned columnIndex, int byteLength, char *data);
/// \brief Note this is much less efficient to call than GetRow, then working with the cells directly.
/// Numeric, string, binary
void GetCellValueByIndex(unsigned rowIndex, unsigned columnIndex, int *output);
void GetCellValueByIndex(unsigned rowIndex, unsigned columnIndex, char *output);
void GetCellValueByIndex(unsigned rowIndex, unsigned columnIndex, char *output, int *outputLength);
/// \brief Gets a row. More efficient to do this and access Row::cells than to repeatedly call GetCell.
/// You can also update cells in rows from this function.
/// \param[in] rowId The ID of the row
/// \return The desired row, or 0 if no such row.
Row* GetRowByID(unsigned rowId) const;
/// \brief Gets a row at a specific index.
/// rowIndex should be less than GetRowCount()
/// \param[in] rowIndex The index of the row
/// \param[out] key The ID of the row returned
/// \return The desired row, or 0 if no such row.
Row* GetRowByIndex(unsigned rowIndex, unsigned *key) const;
/// \brief Queries the table, optionally returning only a subset of columns and rows.
/// \param[in] columnSubset An array of column indices. Only columns in this array are returned. Pass 0 for all columns
/// \param[in] numColumnSubset The number of elements in \a columnSubset
/// \param[in] inclusionFilters An array of FilterQuery. All filters must pass for the row to be returned.
/// \param[in] numInclusionFilters The number of elements in \a inclusionFilters
/// \param[in] rowIds An arrow of row IDs. Only these rows with these IDs are returned. Pass 0 for all rows.
/// \param[in] numRowIDs The number of elements in \a rowIds
/// \param[out] result The result of the query. If no rows are returned, the table will only have columns.
void QueryTable(unsigned *columnIndicesSubset, unsigned numColumnSubset, FilterQuery *inclusionFilters, unsigned numInclusionFilters, unsigned *rowIds, unsigned numRowIDs, Table *result);
/// \brief Sorts the table by rows
/// \details You can sort the table in ascending or descending order on one or more columns
/// Columns have precedence in the order they appear in the \a sortQueries array
/// If a row cell on column n has the same value as a a different row on column n, then the row will be compared on column n+1
/// \param[in] sortQueries A list of SortQuery structures, defining the sorts to perform on the table
/// \param[in] numColumnSubset The number of elements in \a numSortQueries
/// \param[out] out The address of an array of Rows, which will receive the sorted output. The array must be long enough to contain all returned rows, up to GetRowCount()
void SortTable(Table::SortQuery *sortQueries, unsigned numSortQueries, Table::Row** out);
/// \brief Frees all memory in the table.
void Clear(void);
/// \brief Prints out the names of all the columns.
/// \param[out] out A pointer to an array of bytes which will hold the output.
/// \param[in] outLength The size of the \a out array
/// \param[in] columnDelineator What character to print to delineate columns
void PrintColumnHeaders(char *out, int outLength, char columnDelineator) const;
/// \brief Writes a text representation of the row to \a out.
/// \param[out] out A pointer to an array of bytes which will hold the output.
/// \param[in] outLength The size of the \a out array
/// \param[in] columnDelineator What character to print to delineate columns
/// \param[in] printDelineatorForBinary Binary output is not printed. True to still print the delineator.
/// \param[in] inputRow The row to print
void PrintRow(char *out, int outLength, char columnDelineator, bool printDelineatorForBinary, Table::Row* inputRow) const;
/// \brief Direct access to make things easier.
const DataStructures::List<ColumnDescriptor>& GetColumns(void) const;
/// \brief Direct access to make things easier.
const DataStructures::BPlusTree<unsigned, Row*, _TABLE_BPLUS_TREE_ORDER>& GetRows(void) const;
/// \brief Get the head of a linked list containing all the row data.
DataStructures::Page<unsigned, Row*, _TABLE_BPLUS_TREE_ORDER> * GetListHead(void);
/// \brief Get the first free row id.
/// This could be made more efficient.
unsigned GetAvailableRowId(void) const;
Table& operator = ( const Table& input );
protected:
Table::Row* AddRowColumns(unsigned rowId, Row *row, DataStructures::List<unsigned> columnIndices);
void DeleteRow(Row *row);
void QueryRow(DataStructures::List<unsigned> &inclusionFilterColumnIndices, DataStructures::List<unsigned> &columnIndicesToReturn, unsigned key, Table::Row* row, FilterQuery *inclusionFilters, Table *result);
// 16 is arbitrary and is the order of the BPlus tree. Higher orders are better for searching while lower orders are better for
// Insertions and deletions.
DataStructures::BPlusTree<unsigned, Row*, _TABLE_BPLUS_TREE_ORDER> rows;
// Columns in the table.
DataStructures::List<ColumnDescriptor> columns;
};
}
#ifdef _MSC_VER
#pragma warning( pop )
#endif
#endif

View File

@@ -0,0 +1,143 @@
/// \file DS_ThreadsafeAllocatingQueue.h
/// \internal
/// A threadsafe queue, that also uses a memory pool for allocation
#ifndef __THREADSAFE_ALLOCATING_QUEUE
#define __THREADSAFE_ALLOCATING_QUEUE
#include "DS_Queue.hpp"
#include "SimpleMutex.hpp"
#include "DS_MemoryPool.hpp"
// #if defined(new)
// #pragma push_macro("new")
// #undef new
// #define RMO_NEW_UNDEF_ALLOCATING_QUEUE
// #endif
namespace DataStructures
{
template <class structureType>
class RAK_DLL_EXPORT ThreadsafeAllocatingQueue
{
public:
// Queue operations
void Push(structureType *s);
structureType *PopInaccurate(void);
structureType *Pop(void);
void SetPageSize(int size);
bool IsEmpty(void);
// Memory pool operations
structureType *Allocate(const char *file, unsigned int line);
void Deallocate(structureType *s, const char *file, unsigned int line);
void Clear(const char *file, unsigned int line);
protected:
MemoryPool<structureType> memoryPool;
RakNet::SimpleMutex memoryPoolMutex;
Queue<structureType*> queue;
RakNet::SimpleMutex queueMutex;
};
template <class structureType>
void ThreadsafeAllocatingQueue<structureType>::Push(structureType *s)
{
queueMutex.Lock();
queue.Push(s, _FILE_AND_LINE_ );
queueMutex.Unlock();
}
template <class structureType>
structureType *ThreadsafeAllocatingQueue<structureType>::PopInaccurate(void)
{
structureType *s;
if (queue.IsEmpty())
return 0;
queueMutex.Lock();
if (queue.IsEmpty()==false)
s=queue.Pop();
else
s=0;
queueMutex.Unlock();
return s;
}
template <class structureType>
structureType *ThreadsafeAllocatingQueue<structureType>::Pop(void)
{
structureType *s;
queueMutex.Lock();
if (queue.IsEmpty())
{
queueMutex.Unlock();
return 0;
}
s=queue.Pop();
queueMutex.Unlock();
return s;
}
template <class structureType>
structureType *ThreadsafeAllocatingQueue<structureType>::Allocate(const char *file, unsigned int line)
{
structureType *s;
memoryPoolMutex.Lock();
s=memoryPool.Allocate(file, line);
memoryPoolMutex.Unlock();
// Call new operator, memoryPool doesn't do this
s = new ((void*)s) structureType;
return s;
}
template <class structureType>
void ThreadsafeAllocatingQueue<structureType>::Deallocate(structureType *s, const char *file, unsigned int line)
{
// Call delete operator, memory pool doesn't do this
s->~structureType();
memoryPoolMutex.Lock();
memoryPool.Release(s, file, line);
memoryPoolMutex.Unlock();
}
template <class structureType>
void ThreadsafeAllocatingQueue<structureType>::Clear(const char *file, unsigned int line)
{
memoryPoolMutex.Lock();
for (unsigned int i=0; i < queue.Size(); i++)
{
queue[i]->~structureType();
memoryPool.Release(queue[i], file, line);
}
queue.Clear(file, line);
memoryPoolMutex.Unlock();
memoryPoolMutex.Lock();
memoryPool.Clear(file, line);
memoryPoolMutex.Unlock();
}
template <class structureType>
void ThreadsafeAllocatingQueue<structureType>::SetPageSize(int size)
{
memoryPool.SetPageSize(size);
}
template <class structureType>
bool ThreadsafeAllocatingQueue<structureType>::IsEmpty(void)
{
bool isEmpty;
queueMutex.Lock();
isEmpty=queue.IsEmpty();
queueMutex.Unlock();
return isEmpty;
}
};
// #if defined(RMO_NEW_UNDEF_ALLOCATING_QUEUE)
// #pragma pop_macro("new")
// #undef RMO_NEW_UNDEF_ALLOCATING_QUEUE
// #endif
#endif

View File

@@ -0,0 +1,98 @@
/// \file DS_Tree.h
/// \internal
/// \brief Just a regular tree
///
/// This file is part of RakNet Copyright 2003 Jenkins Software LLC
///
/// Usage of RakNet is subject to the appropriate license agreement.
#ifndef __DS_TREE_H
#define __DS_TREE_H
#include "Export.hpp"
#include "DS_List.hpp"
#include "DS_Queue.hpp"
#include "RakMemoryOverride.hpp"
/// The namespace DataStructures was only added to avoid compiler errors for commonly named data structures
/// As these data structures are stand-alone, you can use them outside of RakNet for your own projects if you wish.
namespace DataStructures
{
template <class TreeType>
class RAK_DLL_EXPORT Tree
{
public:
Tree();
Tree(TreeType &inputData);
~Tree();
void LevelOrderTraversal(DataStructures::List<Tree*> &output);
void AddChild(TreeType &newData);
void DeleteDecendants(void);
TreeType data;
DataStructures::List<Tree *> children;
};
template <class TreeType>
Tree<TreeType>::Tree()
{
}
template <class TreeType>
Tree<TreeType>::Tree(TreeType &inputData)
{
data=inputData;
}
template <class TreeType>
Tree<TreeType>::~Tree()
{
DeleteDecendants();
}
template <class TreeType>
void Tree<TreeType>::LevelOrderTraversal(DataStructures::List<Tree*> &output)
{
unsigned i;
Tree<TreeType> *node;
DataStructures::Queue<Tree<TreeType>*> queue;
for (i=0; i < children.Size(); i++)
queue.Push(children[i]);
while (queue.Size())
{
node=queue.Pop();
output.Insert(node, _FILE_AND_LINE_);
for (i=0; i < node->children.Size(); i++)
queue.Push(node->children[i]);
}
}
template <class TreeType>
void Tree<TreeType>::AddChild(TreeType &newData)
{
children.Insert(RakNet::OP_NEW<Tree>(newData, _FILE_AND_LINE_));
}
template <class TreeType>
void Tree<TreeType>::DeleteDecendants(void)
{
/*
DataStructures::List<Tree*> output;
LevelOrderTraversal(output);
unsigned i;
for (i=0; i < output.Size(); i++)
RakNet::OP_DELETE(output[i], _FILE_AND_LINE_);
*/
// Already recursive to do this
unsigned int i;
for (i=0; i < children.Size(); i++)
RakNet::OP_DELETE(children[i], _FILE_AND_LINE_);
}
}
#endif

View File

@@ -0,0 +1,537 @@
/// \file DS_WeightedGraph.h
/// \internal
/// \brief Weighted graph.
/// \details I'm assuming the indices are complex map types, rather than sequential numbers (which could be implemented much more efficiently).
///
/// This file is part of RakNet Copyright 2003 Jenkins Software LLC
///
/// Usage of RakNet is subject to the appropriate license agreement.
#ifndef __WEIGHTED_GRAPH_H
#define __WEIGHTED_GRAPH_H
#include "DS_OrderedList.hpp"
#include "DS_Map.hpp"
#include "DS_Heap.hpp"
#include "DS_Queue.hpp"
#include "DS_Tree.hpp"
#include "RakAssert.hpp"
#include "RakMemoryOverride.hpp"
#ifdef _DEBUG
#include <stdio.h>
#endif
#ifdef _MSC_VER
#pragma warning( push )
#endif
/// The namespace DataStructures was only added to avoid compiler errors for commonly named data structures
/// As these data structures are stand-alone, you can use them outside of RakNet for your own projects if you wish.
namespace DataStructures
{
template <class node_type, class weight_type, bool allow_unlinkedNodes>
class RAK_DLL_EXPORT WeightedGraph
{
public:
static void IMPLEMENT_DEFAULT_COMPARISON(void) {DataStructures::defaultMapKeyComparison<node_type>(node_type(),node_type());}
WeightedGraph();
~WeightedGraph();
WeightedGraph( const WeightedGraph& original_copy );
WeightedGraph& operator= ( const WeightedGraph& original_copy );
void AddNode(const node_type &node);
void RemoveNode(const node_type &node);
void AddConnection(const node_type &node1, const node_type &node2, weight_type weight);
void RemoveConnection(const node_type &node1, const node_type &node2);
bool HasConnection(const node_type &node1, const node_type &node2);
void Print(void);
void Clear(void);
bool GetShortestPath(DataStructures::List<node_type> &path, node_type startNode, node_type endNode, weight_type INFINITE_WEIGHT);
bool GetSpanningTree(DataStructures::Tree<node_type> &outTree, DataStructures::List<node_type> *inputNodes, node_type startNode, weight_type INFINITE_WEIGHT );
unsigned GetNodeCount(void) const;
unsigned GetConnectionCount(unsigned nodeIndex) const;
void GetConnectionAtIndex(unsigned nodeIndex, unsigned connectionIndex, node_type &outNode, weight_type &outWeight) const;
node_type GetNodeAtIndex(unsigned nodeIndex) const;
protected:
void ClearDijkstra(void);
void GenerateDisjktraMatrix(node_type startNode, weight_type INFINITE_WEIGHT);
DataStructures::Map<node_type, DataStructures::Map<node_type, weight_type> *> adjacencyLists;
// All these variables are for path finding with Dijkstra
// 08/23/06 Won't compile as a DLL inside this struct
// struct
// {
bool isValidPath;
node_type rootNode;
DataStructures::OrderedList<node_type, node_type> costMatrixIndices;
weight_type *costMatrix;
node_type *leastNodeArray;
// } dijkstra;
struct NodeAndParent
{
DataStructures::Tree<node_type>*node;
DataStructures::Tree<node_type>*parent;
};
};
template <class node_type, class weight_type, bool allow_unlinkedNodes>
WeightedGraph<node_type, weight_type, allow_unlinkedNodes>::WeightedGraph()
{
isValidPath=false;
costMatrix=0;
}
template <class node_type, class weight_type, bool allow_unlinkedNodes>
WeightedGraph<node_type, weight_type, allow_unlinkedNodes>::~WeightedGraph()
{
Clear();
}
template <class node_type, class weight_type, bool allow_unlinkedNodes>
WeightedGraph<node_type, weight_type, allow_unlinkedNodes>::WeightedGraph( const WeightedGraph& original_copy )
{
adjacencyLists=original_copy.adjacencyLists;
isValidPath=original_copy.isValidPath;
if (isValidPath)
{
rootNode=original_copy.rootNode;
costMatrixIndices=original_copy.costMatrixIndices;
costMatrix = RakNet::OP_NEW_ARRAY<weight_type>(costMatrixIndices.Size() * costMatrixIndices.Size(), _FILE_AND_LINE_ );
leastNodeArray = RakNet::OP_NEW_ARRAY<node_type>(costMatrixIndices.Size(), _FILE_AND_LINE_ );
memcpy(costMatrix, original_copy.costMatrix, costMatrixIndices.Size() * costMatrixIndices.Size() * sizeof(weight_type));
memcpy(leastNodeArray, original_copy.leastNodeArray, costMatrixIndices.Size() * sizeof(weight_type));
}
}
template <class node_type, class weight_type, bool allow_unlinkedNodes>
WeightedGraph<node_type, weight_type, allow_unlinkedNodes>& WeightedGraph<node_type, weight_type, allow_unlinkedNodes>::operator=( const WeightedGraph& original_copy )
{
adjacencyLists=original_copy.adjacencyLists;
isValidPath=original_copy.isValidPath;
if (isValidPath)
{
rootNode=original_copy.rootNode;
costMatrixIndices=original_copy.costMatrixIndices;
costMatrix = RakNet::OP_NEW_ARRAY<weight_type>(costMatrixIndices.Size() * costMatrixIndices.Size(), _FILE_AND_LINE_ );
leastNodeArray = RakNet::OP_NEW_ARRAY<node_type>(costMatrixIndices.Size(), _FILE_AND_LINE_ );
memcpy(costMatrix, original_copy.costMatrix, costMatrixIndices.Size() * costMatrixIndices.Size() * sizeof(weight_type));
memcpy(leastNodeArray, original_copy.leastNodeArray, costMatrixIndices.Size() * sizeof(weight_type));
}
return *this;
}
template <class node_type, class weight_type, bool allow_unlinkedNodes>
void WeightedGraph<node_type, weight_type, allow_unlinkedNodes>::AddNode(const node_type &node)
{
adjacencyLists.SetNew(node, RakNet::OP_NEW<DataStructures::Map<node_type, weight_type> >( _FILE_AND_LINE_) );
}
template <class node_type, class weight_type, bool allow_unlinkedNodes>
void WeightedGraph<node_type, weight_type, allow_unlinkedNodes>::RemoveNode(const node_type &node)
{
unsigned i;
DataStructures::Queue<node_type> removeNodeQueue;
removeNodeQueue.Push(node, _FILE_AND_LINE_ );
while (removeNodeQueue.Size())
{
RakNet::OP_DELETE(adjacencyLists.Pop(removeNodeQueue.Pop()), _FILE_AND_LINE_);
// Remove this node from all of the other lists as well
for (i=0; i < adjacencyLists.Size(); i++)
{
adjacencyLists[i]->Delete(node);
#ifdef _MSC_VER
#pragma warning( disable : 4127 ) // warning C4127: conditional expression is constant
#endif
if (allow_unlinkedNodes==false && adjacencyLists[i]->Size()==0)
removeNodeQueue.Push(adjacencyLists.GetKeyAtIndex(i), _FILE_AND_LINE_ );
}
}
ClearDijkstra();
}
template <class node_type, class weight_type, bool allow_unlinkedNodes>
bool WeightedGraph<node_type, weight_type, allow_unlinkedNodes>::HasConnection(const node_type &node1, const node_type &node2)
{
if (node1==node2)
return false;
if (adjacencyLists.Has(node1)==false)
return false;
return adjacencyLists.Get(node1)->Has(node2);
}
template <class node_type, class weight_type, bool allow_unlinkedNodes>
void WeightedGraph<node_type, weight_type, allow_unlinkedNodes>::AddConnection(const node_type &node1, const node_type &node2, weight_type weight)
{
if (node1==node2)
return;
if (adjacencyLists.Has(node1)==false)
AddNode(node1);
adjacencyLists.Get(node1)->Set(node2, weight);
if (adjacencyLists.Has(node2)==false)
AddNode(node2);
adjacencyLists.Get(node2)->Set(node1, weight);
}
template <class node_type, class weight_type, bool allow_unlinkedNodes>
void WeightedGraph<node_type, weight_type, allow_unlinkedNodes>::RemoveConnection(const node_type &node1, const node_type &node2)
{
adjacencyLists.Get(node2)->Delete(node1);
adjacencyLists.Get(node1)->Delete(node2);
#ifdef _MSC_VER
#pragma warning( disable : 4127 ) // warning C4127: conditional expression is constant
#endif
if (allow_unlinkedNodes==false) // If we do not allow _unlinked nodes, then if there are no connections, remove the node
{
if (adjacencyLists.Get(node1)->Size()==0)
RemoveNode(node1); // Will also remove node1 from the adjacency list of node2
if (adjacencyLists.Has(node2) && adjacencyLists.Get(node2)->Size()==0)
RemoveNode(node2);
}
ClearDijkstra();
}
template <class node_type, class weight_type, bool allow_unlinkedNodes>
void WeightedGraph<node_type, weight_type, allow_unlinkedNodes>::Clear(void)
{
unsigned i;
for (i=0; i < adjacencyLists.Size(); i++)
RakNet::OP_DELETE(adjacencyLists[i], _FILE_AND_LINE_);
adjacencyLists.Clear();
ClearDijkstra();
}
template <class node_type, class weight_type, bool allow_unlinkedNodes>
bool WeightedGraph<node_type, weight_type, allow_unlinkedNodes>::GetShortestPath(DataStructures::List<node_type> &path, node_type startNode, node_type endNode, weight_type INFINITE_WEIGHT)
{
path.Clear(false, _FILE_AND_LINE_);
if (startNode==endNode)
{
path.Insert(startNode, _FILE_AND_LINE_);
path.Insert(endNode, _FILE_AND_LINE_);
return true;
}
if (isValidPath==false || rootNode!=startNode)
{
ClearDijkstra();
GenerateDisjktraMatrix(startNode, INFINITE_WEIGHT);
}
// return the results
bool objectExists;
unsigned col,row;
weight_type currentWeight;
DataStructures::Queue<node_type> outputQueue;
col=costMatrixIndices.GetIndexFromKey(endNode, &objectExists);
if (costMatrixIndices.Size()<2)
{
return false;
}
if (objectExists==false)
{
return false;
}
node_type vertex;
row=costMatrixIndices.Size()-2;
if (row==0)
{
path.Insert(startNode, _FILE_AND_LINE_);
path.Insert(endNode, _FILE_AND_LINE_);
return true;
}
currentWeight=costMatrix[row*adjacencyLists.Size() + col];
if (currentWeight==INFINITE_WEIGHT)
{
// No path
return true;
}
vertex=endNode;
outputQueue.PushAtHead(vertex, 0, _FILE_AND_LINE_);
row--;
#ifdef _MSC_VER
#pragma warning( disable : 4127 ) // warning C4127: conditional expression is constant
#endif
while (1)
{
while (costMatrix[row*adjacencyLists.Size() + col] == currentWeight)
{
if (row==0)
{
path.Insert(startNode, _FILE_AND_LINE_);
while (!outputQueue.Empty())
path.Insert(outputQueue.Pop(), _FILE_AND_LINE_);
return true;
}
--row;
}
vertex=leastNodeArray[row];
outputQueue.PushAtHead(vertex, 0, _FILE_AND_LINE_);
if (row==0)
break;
col=costMatrixIndices.GetIndexFromKey(vertex, &objectExists);
currentWeight=costMatrix[row*adjacencyLists.Size() + col];
}
path.Insert(startNode, _FILE_AND_LINE_);
while (!outputQueue.Empty())
path.Insert(outputQueue.Pop(), _FILE_AND_LINE_);
return true;
}
template <class node_type, class weight_type, bool allow_unlinkedNodes>
node_type WeightedGraph<node_type, weight_type, allow_unlinkedNodes>::GetNodeAtIndex(unsigned nodeIndex) const
{
return adjacencyLists.GetKeyAtIndex(nodeIndex);
}
template <class node_type, class weight_type, bool allow_unlinkedNodes>
unsigned WeightedGraph<node_type, weight_type, allow_unlinkedNodes>::GetNodeCount(void) const
{
return adjacencyLists.Size();
}
template <class node_type, class weight_type, bool allow_unlinkedNodes>
unsigned WeightedGraph<node_type, weight_type, allow_unlinkedNodes>::GetConnectionCount(unsigned nodeIndex) const
{
return adjacencyLists[nodeIndex]->Size();
}
template <class node_type, class weight_type, bool allow_unlinkedNodes>
void WeightedGraph<node_type, weight_type, allow_unlinkedNodes>::GetConnectionAtIndex(unsigned nodeIndex, unsigned connectionIndex, node_type &outNode, weight_type &outWeight) const
{
outWeight=adjacencyLists[nodeIndex]->operator[](connectionIndex);
outNode=adjacencyLists[nodeIndex]->GetKeyAtIndex(connectionIndex);
}
template <class node_type, class weight_type, bool allow_unlinkedNodes>
bool WeightedGraph<node_type, weight_type, allow_unlinkedNodes>::GetSpanningTree(DataStructures::Tree<node_type> &outTree, DataStructures::List<node_type> *inputNodes, node_type startNode, weight_type INFINITE_WEIGHT )
{
// Find the shortest path from the start node to each of the input nodes. Add this path to a new WeightedGraph if the result is reachable
DataStructures::List<node_type> path;
DataStructures::WeightedGraph<node_type, weight_type, allow_unlinkedNodes> outGraph;
bool res;
unsigned i,j;
for (i=0; i < inputNodes->Size(); i++)
{
res=GetShortestPath(path, startNode, (*inputNodes)[i], INFINITE_WEIGHT);
if (res && path.Size()>0)
{
for (j=0; j < path.Size()-1; j++)
{
// Don't bother looking up the weight
outGraph.AddConnection(path[j], path[j+1], INFINITE_WEIGHT);
}
}
}
// Copy the graph to a tree.
DataStructures::Queue<NodeAndParent> nodesToProcess;
DataStructures::Tree<node_type> *current;
DataStructures::Map<node_type, weight_type> *adjacencyList;
node_type key;
NodeAndParent nap, nap2;
outTree.DeleteDecendants();
outTree.data=startNode;
current=&outTree;
if (outGraph.adjacencyLists.Has(startNode)==false)
return false;
adjacencyList = outGraph.adjacencyLists.Get(startNode);
for (i=0; i < adjacencyList->Size(); i++)
{
nap2.node=RakNet::OP_NEW<DataStructures::Tree<node_type> >( _FILE_AND_LINE_ );
nap2.node->data=adjacencyList->GetKeyAtIndex(i);
nap2.parent=current;
nodesToProcess.Push(nap2, _FILE_AND_LINE_ );
current->children.Insert(nap2.node, _FILE_AND_LINE_);
}
while (nodesToProcess.Size())
{
nap=nodesToProcess.Pop();
current=nap.node;
adjacencyList = outGraph.adjacencyLists.Get(nap.node->data);
for (i=0; i < adjacencyList->Size(); i++)
{
key=adjacencyList->GetKeyAtIndex(i);
if (key!=nap.parent->data)
{
nap2.node=RakNet::OP_NEW<DataStructures::Tree<node_type> >( _FILE_AND_LINE_ );
nap2.node->data=key;
nap2.parent=current;
nodesToProcess.Push(nap2, _FILE_AND_LINE_ );
current->children.Insert(nap2.node, _FILE_AND_LINE_);
}
}
}
return true;
}
template <class node_type, class weight_type, bool allow_unlinkedNodes>
void WeightedGraph<node_type, weight_type, allow_unlinkedNodes>::GenerateDisjktraMatrix(node_type startNode, weight_type INFINITE_WEIGHT)
{
if (adjacencyLists.Size()==0)
return;
costMatrix = RakNet::OP_NEW_ARRAY<weight_type>(adjacencyLists.Size() * adjacencyLists.Size(), _FILE_AND_LINE_ );
leastNodeArray = RakNet::OP_NEW_ARRAY<node_type>(adjacencyLists.Size(), _FILE_AND_LINE_ );
node_type currentNode;
unsigned col, row, row2, openSetIndex;
node_type adjacentKey;
unsigned adjacentIndex;
weight_type edgeWeight, currentNodeWeight, adjacentNodeWeight;
DataStructures::Map<node_type, weight_type> *adjacencyList;
DataStructures::Heap<weight_type, node_type, false> minHeap;
DataStructures::Map<node_type, weight_type> openSet;
for (col=0; col < adjacencyLists.Size(); col++)
{
// This should be already sorted, so it's a bit inefficient to do an insertion sort, but what the heck
costMatrixIndices.Insert(adjacencyLists.GetKeyAtIndex(col),adjacencyLists.GetKeyAtIndex(col), true, _FILE_AND_LINE_);
}
for (col=0; col < adjacencyLists.Size() * adjacencyLists.Size(); col++)
costMatrix[col]=INFINITE_WEIGHT;
currentNode=startNode;
row=0;
currentNodeWeight=0;
rootNode=startNode;
// Clear the starting node column
if (adjacencyLists.Size())
{
adjacentIndex=adjacencyLists.GetIndexAtKey(startNode);
for (row2=0; row2 < adjacencyLists.Size(); row2++)
costMatrix[row2*adjacencyLists.Size() + adjacentIndex]=0;
}
while (row < adjacencyLists.Size()-1)
{
adjacencyList = adjacencyLists.Get(currentNode);
// Go through all connections from the current node. If the new weight is less than the current weight, then update that weight.
for (col=0; col < adjacencyList->Size(); col++)
{
edgeWeight=(*adjacencyList)[col];
adjacentKey=adjacencyList->GetKeyAtIndex(col);
adjacentIndex=adjacencyLists.GetIndexAtKey(adjacentKey);
adjacentNodeWeight=costMatrix[row*adjacencyLists.Size() + adjacentIndex];
if (currentNodeWeight + edgeWeight < adjacentNodeWeight)
{
// Update the weight for the adjacent node
for (row2=row; row2 < adjacencyLists.Size(); row2++)
costMatrix[row2*adjacencyLists.Size() + adjacentIndex]=currentNodeWeight + edgeWeight;
openSet.Set(adjacentKey, currentNodeWeight + edgeWeight);
}
}
// Find the lowest in the open set
minHeap.Clear(true,_FILE_AND_LINE_);
for (openSetIndex=0; openSetIndex < openSet.Size(); openSetIndex++)
minHeap.Push(openSet[openSetIndex], openSet.GetKeyAtIndex(openSetIndex),_FILE_AND_LINE_);
/*
unsigned i,j;
for (i=0; i < adjacencyLists.Size()-1; i++)
{
for (j=0; j < adjacencyLists.Size(); j++)
{
RAKNET_DEBUG_PRINTF("%2i ", costMatrix[i*adjacencyLists.Size() + j]);
}
RAKNET_DEBUG_PRINTF("Node=%i", leastNodeArray[i]);
RAKNET_DEBUG_PRINTF("\n");
}
*/
if (minHeap.Size()==0)
{
// Unreachable nodes
isValidPath=true;
return;
}
currentNodeWeight=minHeap.PeekWeight(0);
leastNodeArray[row]=minHeap.Pop(0);
currentNode=leastNodeArray[row];
openSet.Delete(currentNode);
row++;
}
/*
#ifdef _DEBUG
unsigned i,j;
for (i=0; i < adjacencyLists.Size()-1; i++)
{
for (j=0; j < adjacencyLists.Size(); j++)
{
RAKNET_DEBUG_PRINTF("%2i ", costMatrix[i*adjacencyLists.Size() + j]);
}
RAKNET_DEBUG_PRINTF("Node=%i", leastNodeArray[i]);
RAKNET_DEBUG_PRINTF("\n");
}
#endif
*/
isValidPath=true;
}
template <class node_type, class weight_type, bool allow_unlinkedNodes>
void WeightedGraph<node_type, weight_type, allow_unlinkedNodes>::ClearDijkstra(void)
{
if (isValidPath)
{
isValidPath=false;
RakNet::OP_DELETE_ARRAY(costMatrix, _FILE_AND_LINE_);
RakNet::OP_DELETE_ARRAY(leastNodeArray, _FILE_AND_LINE_);
costMatrixIndices.Clear(false, _FILE_AND_LINE_);
}
}
template <class node_type, class weight_type, bool allow_unlinkedNodes>
void WeightedGraph<node_type, weight_type, allow_unlinkedNodes>::Print(void)
{
#ifdef _DEBUG
unsigned i,j;
for (i=0; i < adjacencyLists.Size(); i++)
{
//RAKNET_DEBUG_PRINTF("%i connected to ", i);
RAKNET_DEBUG_PRINTF("%s connected to ", adjacencyLists.GetKeyAtIndex(i).systemAddress.ToString());
if (adjacencyLists[i]->Size()==0)
RAKNET_DEBUG_PRINTF("<Empty>");
else
{
for (j=0; j < adjacencyLists[i]->Size(); j++)
// RAKNET_DEBUG_PRINTF("%i (%.2f) ", adjacencyLists.GetIndexAtKey(adjacencyLists[i]->GetKeyAtIndex(j)), (float) adjacencyLists[i]->operator[](j) );
RAKNET_DEBUG_PRINTF("%s (%.2f) ", adjacencyLists[i]->GetKeyAtIndex(j).systemAddress.ToString(), (float) adjacencyLists[i]->operator[](j) );
}
RAKNET_DEBUG_PRINTF("\n");
}
#endif
}
}
#ifdef _MSC_VER
#pragma warning( pop )
#endif
#endif

View File

@@ -0,0 +1,63 @@
#include "DataCompressor.hpp"
#include "DS_HuffmanEncodingTree.hpp"
#include "RakAssert.hpp"
#include <string.h> // Use string.h rather than memory.h for a console
using namespace RakNet;
STATIC_FACTORY_DEFINITIONS(DataCompressor,DataCompressor)
void DataCompressor::Compress( unsigned char *userData, unsigned sizeInBytes, RakNet::BitStream * output )
{
// Don't use this for small files as you will just make them bigger!
RakAssert(sizeInBytes > 2048);
unsigned int frequencyTable[ 256 ];
unsigned int i;
memset(frequencyTable,0,256*sizeof(unsigned int));
for (i=0; i < sizeInBytes; i++)
++frequencyTable[userData[i]];
HuffmanEncodingTree tree;
BitSize_t writeOffset1, writeOffset2, bitsUsed1, bitsUsed2;
tree.GenerateFromFrequencyTable(frequencyTable);
output->WriteCompressed(sizeInBytes);
for (i=0; i < 256; i++)
output->WriteCompressed(frequencyTable[i]);
output->AlignWriteToByteBoundary();
writeOffset1=output->GetWriteOffset();
output->Write((unsigned int)0); // Dummy value
bitsUsed1=output->GetNumberOfBitsUsed();
tree.EncodeArray(userData, sizeInBytes, output);
bitsUsed2=output->GetNumberOfBitsUsed();
writeOffset2=output->GetWriteOffset();
output->SetWriteOffset(writeOffset1);
output->Write(bitsUsed2-bitsUsed1); // Go back and write how many bits were used for the encoding
output->SetWriteOffset(writeOffset2);
}
unsigned DataCompressor::DecompressAndAllocate( RakNet::BitStream * input, unsigned char **output )
{
HuffmanEncodingTree tree;
unsigned int bitsUsed, destinationSizeInBytes;
unsigned int decompressedBytes;
unsigned int frequencyTable[ 256 ];
unsigned i;
input->ReadCompressed(destinationSizeInBytes);
for (i=0; i < 256; i++)
input->ReadCompressed(frequencyTable[i]);
input->AlignReadToByteBoundary();
if (input->Read(bitsUsed)==false)
{
// Read error
#ifdef _DEBUG
RakAssert(0);
#endif
return 0;
}
*output = (unsigned char*) rakMalloc_Ex(destinationSizeInBytes, _FILE_AND_LINE_);
tree.GenerateFromFrequencyTable(frequencyTable);
decompressedBytes=tree.DecodeArray(input, bitsUsed, destinationSizeInBytes, *output );
RakAssert(decompressedBytes==destinationSizeInBytes);
return destinationSizeInBytes;
}

View File

@@ -0,0 +1,33 @@
/// \file DataCompressor.h
/// \brief DataCompressor does compression on a block of data.
/// \details Not very good compression, but it's small and fast so is something you can use per-message at runtime.
///
/// This file is part of RakNet Copyright 2003 Jenkins Software LLC
///
/// Usage of RakNet is subject to the appropriate license agreement.
#ifndef __DATA_COMPRESSOR_H
#define __DATA_COMPRESSOR_H
#include "RakMemoryOverride.hpp"
#include "DS_HuffmanEncodingTree.hpp"
#include "Export.hpp"
namespace RakNet
{
/// \brief Does compression on a block of data. Not very good compression, but it's small and fast so is something you can compute at runtime.
class RAK_DLL_EXPORT DataCompressor
{
public:
// GetInstance() and DestroyInstance(instance*)
STATIC_FACTORY_DECLARATIONS(DataCompressor)
static void Compress( unsigned char *userData, unsigned sizeInBytes, RakNet::BitStream * output );
static unsigned DecompressAndAllocate( RakNet::BitStream * input, unsigned char **output );
};
} // namespace RakNet
#endif

View File

@@ -0,0 +1,242 @@
#include "NativeFeatureIncludes.hpp"
#if _RAKNET_SUPPORT_DirectoryDeltaTransfer==1 && _RAKNET_SUPPORT_FileOperations==1
#include "DirectoryDeltaTransfer.hpp"
#include "FileList.hpp"
#include "StringCompressor.hpp"
#include "RakPeerInterface.hpp"
#include "FileListTransfer.hpp"
#include "FileListTransferCBInterface.hpp"
#include "BitStream.hpp"
#include "MessageIdentifiers.hpp"
#include "FileOperations.hpp"
#include "IncrementalReadInterface.hpp"
using namespace RakNet;
#ifdef _MSC_VER
#pragma warning( push )
#endif
class DDTCallback : public FileListTransferCBInterface
{
public:
unsigned subdirLen;
char outputSubdir[512];
FileListTransferCBInterface *onFileCallback;
DDTCallback() {}
virtual ~DDTCallback() {}
virtual bool OnFile(OnFileStruct *onFileStruct)
{
char fullPathToDir[1024];
if (onFileStruct->fileName && onFileStruct->fileData && subdirLen < strlen(onFileStruct->fileName))
{
strcpy(fullPathToDir, outputSubdir);
strcat(fullPathToDir, onFileStruct->fileName+subdirLen);
WriteFileWithDirectories(fullPathToDir, (char*)onFileStruct->fileData, (unsigned int ) onFileStruct->byteLengthOfThisFile);
}
else
fullPathToDir[0]=0;
return onFileCallback->OnFile(onFileStruct);
}
virtual void OnFileProgress(FileProgressStruct *fps)
{
char fullPathToDir[1024];
if (fps->onFileStruct->fileName && subdirLen < strlen(fps->onFileStruct->fileName))
{
strcpy(fullPathToDir, outputSubdir);
strcat(fullPathToDir, fps->onFileStruct->fileName+subdirLen);
}
else
fullPathToDir[0]=0;
onFileCallback->OnFileProgress(fps);
}
virtual bool OnDownloadComplete(DownloadCompleteStruct *dcs)
{
return onFileCallback->OnDownloadComplete(dcs);
}
};
STATIC_FACTORY_DEFINITIONS(DirectoryDeltaTransfer,DirectoryDeltaTransfer);
DirectoryDeltaTransfer::DirectoryDeltaTransfer()
{
applicationDirectory[0]=0;
fileListTransfer=0;
availableUploads = RakNet::OP_NEW<FileList>( _FILE_AND_LINE_ );
priority=HIGH_PRIORITY;
orderingChannel=0;
incrementalReadInterface=0;
}
DirectoryDeltaTransfer::~DirectoryDeltaTransfer()
{
RakNet::OP_DELETE(availableUploads, _FILE_AND_LINE_);
}
void DirectoryDeltaTransfer::SetFileListTransferPlugin(FileListTransfer *flt)
{
if (fileListTransfer)
{
DataStructures::List<FileListProgress*> fileListProgressList;
fileListTransfer->GetCallbacks(fileListProgressList);
unsigned int i;
for (i=0; i < fileListProgressList.Size(); i++)
availableUploads->RemoveCallback(fileListProgressList[i]);
}
fileListTransfer=flt;
if (flt)
{
DataStructures::List<FileListProgress*> fileListProgressList;
flt->GetCallbacks(fileListProgressList);
unsigned int i;
for (i=0; i < fileListProgressList.Size(); i++)
availableUploads->AddCallback(fileListProgressList[i]);
}
else
{
availableUploads->ClearCallbacks();
}
}
void DirectoryDeltaTransfer::SetApplicationDirectory(const char *pathToApplication)
{
if (pathToApplication==0 || pathToApplication[0]==0)
applicationDirectory[0]=0;
else
{
strncpy(applicationDirectory, pathToApplication, 510);
if (applicationDirectory[strlen(applicationDirectory)-1]!='/' && applicationDirectory[strlen(applicationDirectory)-1]!='\\')
strcat(applicationDirectory, "/");
applicationDirectory[511]=0;
}
}
void DirectoryDeltaTransfer::SetUploadSendParameters(PacketPriority _priority, char _orderingChannel)
{
priority=_priority;
orderingChannel=_orderingChannel;
}
void DirectoryDeltaTransfer::AddUploadsFromSubdirectory(const char *subdir)
{
availableUploads->AddFilesFromDirectory(applicationDirectory, subdir, true, false, true, FileListNodeContext(0,0));
}
unsigned short DirectoryDeltaTransfer::DownloadFromSubdirectory(FileList &localFiles, const char *subdir, const char *outputSubdir, bool prependAppDirToOutputSubdir, SystemAddress host, FileListTransferCBInterface *onFileCallback, PacketPriority _priority, char _orderingChannel, FileListProgress *cb)
{
RakAssert(host!=UNASSIGNED_SYSTEM_ADDRESS);
DDTCallback *transferCallback;
localFiles.AddCallback(cb);
// Prepare the callback data
transferCallback = RakNet::OP_NEW<DDTCallback>( _FILE_AND_LINE_ );
if (subdir && subdir[0])
{
transferCallback->subdirLen=(unsigned int)strlen(subdir);
if (subdir[transferCallback->subdirLen-1]!='/' && subdir[transferCallback->subdirLen-1]!='\\')
transferCallback->subdirLen++;
}
else
transferCallback->subdirLen=0;
if (prependAppDirToOutputSubdir)
strcpy(transferCallback->outputSubdir, applicationDirectory);
else
transferCallback->outputSubdir[0]=0;
if (outputSubdir)
strcat(transferCallback->outputSubdir, outputSubdir);
if (transferCallback->outputSubdir[strlen(transferCallback->outputSubdir)-1]!='/' && transferCallback->outputSubdir[strlen(transferCallback->outputSubdir)-1]!='\\')
strcat(transferCallback->outputSubdir, "/");
transferCallback->onFileCallback=onFileCallback;
// Setup the transfer plugin to get the response to this download request
unsigned short setId = fileListTransfer->SetupReceive(transferCallback, true, host);
// Send to the host, telling it to process this request
RakNet::BitStream outBitstream;
outBitstream.Write((MessageID)ID_DDT_DOWNLOAD_REQUEST);
outBitstream.Write(setId);
StringCompressor::Instance()->EncodeString(subdir, 256, &outBitstream);
StringCompressor::Instance()->EncodeString(outputSubdir, 256, &outBitstream);
localFiles.Serialize(&outBitstream);
SendUnified(&outBitstream, _priority, RELIABLE_ORDERED, _orderingChannel, host, false);
return setId;
}
unsigned short DirectoryDeltaTransfer::DownloadFromSubdirectory(const char *subdir, const char *outputSubdir, bool prependAppDirToOutputSubdir, SystemAddress host, FileListTransferCBInterface *onFileCallback, PacketPriority _priority, char _orderingChannel, FileListProgress *cb)
{
FileList localFiles;
// Get a hash of all the files that we already have (if any)
localFiles.AddFilesFromDirectory(prependAppDirToOutputSubdir ? applicationDirectory : 0, outputSubdir, true, false, true, FileListNodeContext(0,0));
return DownloadFromSubdirectory(localFiles, subdir, outputSubdir, prependAppDirToOutputSubdir, host, onFileCallback, _priority, _orderingChannel, cb);
}
void DirectoryDeltaTransfer::GenerateHashes(FileList &localFiles, const char *outputSubdir, bool prependAppDirToOutputSubdir)
{
localFiles.AddFilesFromDirectory(prependAppDirToOutputSubdir ? applicationDirectory : 0, outputSubdir, true, false, true, FileListNodeContext(0,0));
}
void DirectoryDeltaTransfer::ClearUploads(void)
{
availableUploads->Clear();
}
void DirectoryDeltaTransfer::OnDownloadRequest(Packet *packet)
{
char subdir[256];
char remoteSubdir[256];
RakNet::BitStream inBitstream(packet->data, packet->length, false);
FileList remoteFileHash;
FileList delta;
unsigned short setId;
inBitstream.IgnoreBits(8);
inBitstream.Read(setId);
StringCompressor::Instance()->DecodeString(subdir, 256, &inBitstream);
StringCompressor::Instance()->DecodeString(remoteSubdir, 256, &inBitstream);
if (remoteFileHash.Deserialize(&inBitstream)==false)
{
#ifdef _DEBUG
RakAssert(0);
#endif
return;
}
availableUploads->GetDeltaToCurrent(&remoteFileHash, &delta, subdir, remoteSubdir);
if (incrementalReadInterface==0)
delta.PopulateDataFromDisk(applicationDirectory, true, false, true);
else
delta.FlagFilesAsReferences();
// This will call the ddtCallback interface that was passed to FileListTransfer::SetupReceive on the remote system
fileListTransfer->Send(&delta, rakPeerInterface, packet->systemAddress, setId, priority, orderingChannel, incrementalReadInterface, chunkSize);
}
PluginReceiveResult DirectoryDeltaTransfer::OnReceive(Packet *packet)
{
switch (packet->data[0])
{
case ID_DDT_DOWNLOAD_REQUEST:
OnDownloadRequest(packet);
return RR_STOP_PROCESSING_AND_DEALLOCATE;
}
return RR_CONTINUE_PROCESSING;
}
unsigned DirectoryDeltaTransfer::GetNumberOfFilesForUpload(void) const
{
return availableUploads->fileList.Size();
}
void DirectoryDeltaTransfer::SetDownloadRequestIncrementalReadInterface(IncrementalReadInterface *_incrementalReadInterface, unsigned int _chunkSize)
{
incrementalReadInterface=_incrementalReadInterface;
chunkSize=_chunkSize;
}
#ifdef _MSC_VER
#pragma warning( pop )
#endif
#endif // _RAKNET_SUPPORT_*

View File

@@ -0,0 +1,164 @@
/// \file DirectoryDeltaTransfer.h
/// \brief Simple class to send changes between directories.
/// \details In essence, a simple autopatcher that can be used for transmitting levels, skins, etc.
///
/// This file is part of RakNet Copyright 2003 Jenkins Software LLC
///
/// Usage of RakNet is subject to the appropriate license agreement.
#include "NativeFeatureIncludes.hpp"
#if _RAKNET_SUPPORT_DirectoryDeltaTransfer==1 && _RAKNET_SUPPORT_FileOperations==1
#ifndef __DIRECTORY_DELTA_TRANSFER_H
#define __DIRECTORY_DELTA_TRANSFER_H
#include "RakMemoryOverride.hpp"
#include "RakNetTypes.hpp"
#include "Export.hpp"
#include "PluginInterface2.hpp"
#include "DS_Map.hpp"
#include "PacketPriority.hpp"
/// \defgroup DIRECTORY_DELTA_TRANSFER_GROUP DirectoryDeltaTransfer
/// \brief Simple class to send changes between directories
/// \details
/// \ingroup PLUGINS_GROUP
/// \brief Simple class to send changes between directories. In essence, a simple autopatcher that can be used for transmitting levels, skins, etc.
/// \details
/// \sa AutopatcherClient class for database driven patching, including binary deltas and search by date.
///
/// To use, first set the path to your application. For example "C:/Games/MyRPG/"<BR>
/// To allow other systems to download files, call AddUploadsFromSubdirectory, where the parameter is a path relative<BR>
/// to the path to your application. This includes subdirectories.<BR>
/// For example:<BR>
/// SetApplicationDirectory("C:/Games/MyRPG/");<BR>
/// AddUploadsFromSubdirectory("Mods/Skins/");<BR>
/// would allow downloads from<BR>
/// "C:/Games/MyRPG/Mods/Skins/*.*" as well as "C:/Games/MyRPG/Mods/Skins/Level1/*.*"<BR>
/// It would NOT allow downloads from C:/Games/MyRPG/Levels, nor would it allow downloads from C:/Windows<BR>
/// While pathToApplication can be anything you want, applicationSubdirectory must match either partially or fully between systems.
/// \ingroup DIRECTORY_DELTA_TRANSFER_GROUP
namespace RakNet
{
/// Forward declarations
class RakPeerInterface;
class FileList;
struct Packet;
struct InternalPacket;
struct DownloadRequest;
class FileListTransfer;
class FileListTransferCBInterface;
class FileListProgress;
class IncrementalReadInterface;
class RAK_DLL_EXPORT DirectoryDeltaTransfer : public PluginInterface2
{
public:
// GetInstance() and DestroyInstance(instance*)
STATIC_FACTORY_DECLARATIONS(DirectoryDeltaTransfer)
// Constructor
DirectoryDeltaTransfer();
// Destructor
virtual ~DirectoryDeltaTransfer();
/// \brief This plugin has a dependency on the FileListTransfer plugin, which it uses to actually send the files.
/// \details So you need an instance of that plugin registered with RakPeerInterface, and a pointer to that interface should be passed here.
/// \param[in] flt A pointer to a registered instance of FileListTransfer
void SetFileListTransferPlugin(FileListTransfer *flt);
/// \brief Set the local root directory to base all file uploads and downloads off of.
/// \param[in] pathToApplication This path will be prepended to \a applicationSubdirectory in AddUploadsFromSubdirectory to find the actual path on disk.
void SetApplicationDirectory(const char *pathToApplication);
/// \brief What parameters to use for the RakPeerInterface::Send() call when uploading files.
/// \param[in] _priority See RakPeerInterface::Send()
/// \param[in] _orderingChannel See RakPeerInterface::Send()
void SetUploadSendParameters(PacketPriority _priority, char _orderingChannel);
/// \brief Add all files in the specified subdirectory recursively.
/// \details \a subdir is appended to \a pathToApplication in SetApplicationDirectory().
/// All files in the resultant directory and subdirectories are then hashed so that users can download them.
/// \pre You must call SetFileListTransferPlugin with a valid FileListTransfer plugin
/// \param[in] subdir Concatenated with pathToApplication to form the final path from which to allow uploads.
void AddUploadsFromSubdirectory(const char *subdir);
/// \brief Downloads files from the matching parameter \a subdir in AddUploadsFromSubdirectory.
/// \details \a subdir must contain all starting characters in \a subdir in AddUploadsFromSubdirectory
/// Therefore,
/// AddUploadsFromSubdirectory("Levels/Level1/"); would allow you to download using DownloadFromSubdirectory("Levels/Level1/Textures/"...
/// but it would NOT allow you to download from DownloadFromSubdirectory("Levels/"... or DownloadFromSubdirectory("Levels/Level2/"...
/// \pre You must call SetFileListTransferPlugin with a valid FileListTransfer plugin
/// \note Blocking. Will block while hashes of the local files are generated
/// \param[in] subdir A directory passed to AddUploadsFromSubdirectory on the remote system. The passed dir can be more specific than the remote dir.
/// \param[in] outputSubdir The directory to write the output to. Usually this will match \a subdir but it can be different if you want.
/// \param[in] prependAppDirToOutputSubdir True to prepend outputSubdir with pathToApplication when determining the final output path. Usually you want this to be true.
/// \param[in] host The address of the remote system to send the message to.
/// \param[in] onFileCallback Callback to call per-file (optional). When fileIndex+1==setCount in the callback then the download is done
/// \param[in] _priority See RakPeerInterface::Send()
/// \param[in] _orderingChannel See RakPeerInterface::Send()
/// \param[in] cb Callback to get progress updates. Pass 0 to not use.
/// \return A set ID, identifying this download set. Returns 65535 on host unreachable.
unsigned short DownloadFromSubdirectory(const char *subdir, const char *outputSubdir, bool prependAppDirToOutputSubdir, SystemAddress host, FileListTransferCBInterface *onFileCallback, PacketPriority _priority, char _orderingChannel, FileListProgress *cb);
/// \brief Downloads files from the matching parameter \a subdir in AddUploadsFromSubdirectory.
/// \details \a subdir must contain all starting characters in \a subdir in AddUploadsFromSubdirectory
/// Therefore,
/// AddUploadsFromSubdirectory("Levels/Level1/"); would allow you to download using DownloadFromSubdirectory("Levels/Level1/Textures/"...
/// but it would NOT allow you to download from DownloadFromSubdirectory("Levels/"... or DownloadFromSubdirectory("Levels/Level2/"...
/// \pre You must call SetFileListTransferPlugin with a valid FileListTransfer plugin
/// \note Nonblocking, but requires call to GenerateHashes()
/// \param[in] localFiles Hashes of local files already on the harddrive. Populate with GenerateHashes(), which you may wish to call from a thread
/// \param[in] subdir A directory passed to AddUploadsFromSubdirectory on the remote system. The passed dir can be more specific than the remote dir.
/// \param[in] outputSubdir The directory to write the output to. Usually this will match \a subdir but it can be different if you want.
/// \param[in] prependAppDirToOutputSubdir True to prepend outputSubdir with pathToApplication when determining the final output path. Usually you want this to be true.
/// \param[in] host The address of the remote system to send the message to.
/// \param[in] onFileCallback Callback to call per-file (optional). When fileIndex+1==setCount in the callback then the download is done
/// \param[in] _priority See RakPeerInterface::Send()
/// \param[in] _orderingChannel See RakPeerInterface::Send()
/// \param[in] cb Callback to get progress updates. Pass 0 to not use.
/// \return A set ID, identifying this download set. Returns 65535 on host unreachable.
unsigned short DownloadFromSubdirectory(FileList &localFiles, const char *subdir, const char *outputSubdir, bool prependAppDirToOutputSubdir, SystemAddress host, FileListTransferCBInterface *onFileCallback, PacketPriority _priority, char _orderingChannel, FileListProgress *cb);
/// Hash files already on the harddrive, in preparation for a call to DownloadFromSubdirectory(). Passed to second version of DownloadFromSubdirectory()
/// This is slow, and it is exposed so you can call it from a thread before calling DownloadFromSubdirectory()
/// \param[out] localFiles List of hashed files populated from \a outputSubdir and \a prependAppDirToOutputSubdir
/// \param[in] outputSubdir The directory to write the output to. Usually this will match \a subdir but it can be different if you want.
/// \param[in] prependAppDirToOutputSubdir True to prepend outputSubdir with pathToApplication when determining the final output path. Usually you want this to be true.
void GenerateHashes(FileList &localFiles, const char *outputSubdir, bool prependAppDirToOutputSubdir);
/// \brief Clear all allowed uploads previously set with AddUploadsFromSubdirectory
void ClearUploads(void);
/// \brief Returns how many files are available for upload
/// \return How many files are available for upload
unsigned GetNumberOfFilesForUpload(void) const;
/// \brief Normally, if a remote system requests files, those files are all loaded into memory and sent immediately.
/// \details This function allows the files to be read in incremental chunks, saving memory
/// \param[in] _incrementalReadInterface If a file in \a fileList has no data, filePullInterface will be used to read the file in chunks of size \a chunkSize
/// \param[in] _chunkSize How large of a block of a file to send at once
void SetDownloadRequestIncrementalReadInterface(IncrementalReadInterface *_incrementalReadInterface, unsigned int _chunkSize);
/// \internal For plugin handling
virtual PluginReceiveResult OnReceive(Packet *packet);
protected:
void OnDownloadRequest(Packet *packet);
char applicationDirectory[512];
FileListTransfer *fileListTransfer;
FileList *availableUploads;
PacketPriority priority;
char orderingChannel;
IncrementalReadInterface *incrementalReadInterface;
unsigned int chunkSize;
};
} // namespace RakNet
#endif
#endif // _RAKNET_SUPPORT_*

236
third-party/RakNet/src/RakNet/DynDNS.cpp vendored Normal file
View File

@@ -0,0 +1,236 @@
#include "NativeFeatureIncludes.hpp"
#if _RAKNET_SUPPORT_DynDNS==1 && _RAKNET_SUPPORT_TCPInterface==1
#include "TCPInterface.hpp"
#include "SocketLayer.hpp"
#include "DynDNS.hpp"
#include "GetTime.hpp"
using namespace RakNet;
struct DynDnsResult
{
const char *description;
const char *code;
DynDnsResultCode resultCode;
};
DynDnsResult resultTable[13] =
{
// See http://www.dyndns.com/developers/specs/flow.pdf
{"DNS update success.\nPlease wait up to 60 seconds for the change to take effect.\n", "good", RC_SUCCESS}, // Even with success, it takes time for the cache to update!
{"No change", "nochg", RC_NO_CHANGE},
{"Host has been blocked. You will need to contact DynDNS to reenable.", "abuse", RC_ABUSE},
{"Useragent is blocked", "badagent", RC_BAD_AGENT},
{"Username/password pair bad", "badauth", RC_BAD_AUTH},
{"Bad system parameter", "badsys", RC_BAD_SYS},
{"DNS inconsistency", "dnserr", RC_DNS_ERROR},
{"Paid account feature", "!donator", RC_NOT_DONATOR},
{"No such host in system", "nohost", RC_NO_HOST},
{"Invalid hostname format", "notfqdn", RC_NOT_FQDN},
{"Serious error", "numhost", RC_NUM_HOST},
{"This host exists, but does not belong to you", "!yours", RC_NOT_YOURS},
{"911", "911", RC_911},
};
DynDNS::DynDNS()
{
connectPhase=CP_IDLE;
tcp=0;
}
DynDNS::~DynDNS()
{
if (tcp)
RakNet::OP_DELETE(tcp, _FILE_AND_LINE_);
}
void DynDNS::Stop(void)
{
tcp->Stop();
connectPhase = CP_IDLE;
RakNet::OP_DELETE(tcp, _FILE_AND_LINE_);
tcp=0;
}
// newIPAddress is optional - if left out, DynDNS will use whatever it receives
void DynDNS::UpdateHostIP(const char *dnsHost, const char *newIPAddress, const char *usernameAndPassword )
{
myIPStr[0]=0;
if (tcp==0)
tcp = RakNet::OP_NEW<TCPInterface>(_FILE_AND_LINE_);
connectPhase = CP_IDLE;
host = dnsHost;
if (tcp->Start(0, 1)==false)
{
SetCompleted(RC_TCP_FAILED_TO_START, "TCP failed to start");
return;
}
connectPhase = CP_CONNECTING_TO_CHECKIP;
tcp->Connect("checkip.dyndns.org", 80, false);
// See https://www.dyndns.com/developers/specs/syntax.html
getString="GET /nic/update?hostname=";
getString+=dnsHost;
if (newIPAddress)
{
getString+="&myip=";
getString+=newIPAddress;
}
getString+="&wildcard=NOCHG&mx=NOCHG&backmx=NOCHG HTTP/1.0\n";
getString+="Host: members.dyndns.org\n";
getString+="Authorization: Basic ";
char outputData[512];
TCPInterface::Base64Encoding(usernameAndPassword, (int) strlen(usernameAndPassword), outputData);
getString+=outputData;
getString+="User-Agent: Jenkins Software LLC - PC - 1.0\n\n";
}
void DynDNS::Update(void)
{
if (connectPhase==CP_IDLE)
return;
serverAddress=tcp->HasFailedConnectionAttempt();
if (serverAddress!=UNASSIGNED_SYSTEM_ADDRESS)
{
SetCompleted(RC_TCP_DID_NOT_CONNECT, "Could not connect to DynDNS");
return;
}
serverAddress=tcp->HasCompletedConnectionAttempt();
if (serverAddress!=UNASSIGNED_SYSTEM_ADDRESS)
{
if (connectPhase == CP_CONNECTING_TO_CHECKIP)
{
checkIpAddress=serverAddress;
connectPhase = CP_WAITING_FOR_CHECKIP_RESPONSE;
tcp->Send("GET\n\n", (unsigned int) strlen("GET\n\n"), serverAddress, false); // Needs 2 newlines! This is not documented and wasted a lot of my time
}
else
{
connectPhase = CP_WAITING_FOR_DYNDNS_RESPONSE;
tcp->Send(getString.C_String(), (unsigned int) getString.GetLength(), serverAddress, false);
}
phaseTimeout=RakNet::GetTime()+1000;
}
if (connectPhase==CP_WAITING_FOR_CHECKIP_RESPONSE && RakNet::GetTime()>phaseTimeout)
{
connectPhase = CP_CONNECTING_TO_DYNDNS;
tcp->CloseConnection(checkIpAddress);
tcp->Connect("members.dyndns.org", 80, false);
}
else if (connectPhase==CP_WAITING_FOR_DYNDNS_RESPONSE && RakNet::GetTime()>phaseTimeout)
{
SetCompleted(RC_DYNDNS_TIMEOUT, "DynDNS did not respond");
return;
}
Packet *packet = tcp->Receive();
if (packet)
{
if (connectPhase==CP_WAITING_FOR_DYNDNS_RESPONSE)
{
unsigned int i;
char *result;
result=strstr((char*) packet->data, "Connection: close");
if (result!=0)
{
result+=strlen("Connection: close");
while (*result && (*result=='\r') || (*result=='\n') || (*result==' ') )
result++;
for (i=0; i < 13; i++)
{
if (strncmp(resultTable[i].code, result, strlen(resultTable[i].code))==0)
{
if (resultTable[i].resultCode==RC_SUCCESS)
{
// Read my external IP into myIPStr
// Advance until we hit a number
while (*result && ((*result<'0') || (*result>'9')) )
result++;
if (*result)
{
SystemAddress parser;
parser.FromString(result);
parser.ToString(false, myIPStr);
}
}
tcp->DeallocatePacket(packet);
SetCompleted(resultTable[i].resultCode, resultTable[i].description);
break;
}
}
if (i==13)
{
tcp->DeallocatePacket(packet);
SetCompleted(RC_UNKNOWN_RESULT, "DynDNS returned unknown result");
}
}
else
{
tcp->DeallocatePacket(packet);
SetCompleted(RC_PARSING_FAILURE, "Parsing failure on returned string from DynDNS");
}
return;
}
else
{
/*
HTTP/1.1 200 OK
Content-Type: text/html
Server: DynDNS-CheckIP/1.0
Connection: close
Cache-Control: no-cache
Pragma: no-cache
Content-Length: 105
<html><head><title>Current IP Check</title></head><body>Current IP Address: 98.1
89.219.22</body></html>
Connection to host lost.
*/
char *result;
result=strstr((char*) packet->data, "Current IP Address: ");
if (result!=0)
{
result+=strlen("Current IP Address: ");
SystemAddress myIp;
myIp.FromString(result);
myIp.ToString(false, myIPStr);
// Resolve DNS we are setting. If equal to current then abort
const char *existingHost = ( char* ) SocketLayer::DomainNameToIP( host.C_String() );
if (existingHost && strcmp(existingHost, myIPStr)==0)
{
// DynDNS considers setting the IP to what it is already set abuse
tcp->DeallocatePacket(packet);
SetCompleted(RC_DNS_ALREADY_SET, "No action needed");
return;
}
}
tcp->DeallocatePacket(packet);
tcp->CloseConnection(packet->systemAddress);
connectPhase = CP_CONNECTING_TO_DYNDNS;
tcp->Connect("members.dyndns.org", 80, false);
}
}
if (tcp->HasLostConnection()!=UNASSIGNED_SYSTEM_ADDRESS)
{
if (connectPhase==CP_WAITING_FOR_DYNDNS_RESPONSE)
{
SetCompleted(RC_CONNECTION_LOST_WITHOUT_RESPONSE, "Connection lost to DynDNS during GET operation");
}
}
}
#endif // _RAKNET_SUPPORT_DynDNS

100
third-party/RakNet/src/RakNet/DynDNS.hpp vendored Normal file
View File

@@ -0,0 +1,100 @@
/// \file DynDNS.h
/// \brief Helper to class to update DynDNS
/// This can be used to determine what permissions are should be allowed to the other system
///
/// This file is part of RakNet Copyright 2003 Jenkins Software LLC
///
/// Usage of RakNet is subject to the appropriate license agreement.
#include "NativeFeatureIncludes.hpp"
#if _RAKNET_SUPPORT_DynDNS==1 && _RAKNET_SUPPORT_TCPInterface==1
class TCPInterface;
#ifndef __DYN_DNS_H
#define __DYN_DNS_H
namespace RakNet
{
enum DynDnsResultCode
{
// ----- Success -----
RC_SUCCESS,
RC_DNS_ALREADY_SET, // RakNet detects no action is needed
// ----- Ignorable failure (treat same as success) -----
RC_NO_CHANGE, // DynDNS detects no action is needed (treated as abuse though)
// ----- User error -----
RC_NOT_DONATOR, // You have to pay to do this
RC_NO_HOST, // This host does not exist at all
RC_BAD_AUTH, // You set the wrong password
RC_NOT_YOURS, // This is not your host
// ----- Permanent failure -----
RC_ABUSE, // Your host has been blocked, too many failures disable your account
RC_TCP_FAILED_TO_START, // TCP port already in use
RC_TCP_DID_NOT_CONNECT, // DynDNS down?
RC_UNKNOWN_RESULT, // DynDNS returned a result code that was not documented as of 12/4/2010 on http://www.dyndns.com/developers/specs/flow.pdf
RC_PARSING_FAILURE, // Can't read the result returned, format change?
RC_CONNECTION_LOST_WITHOUT_RESPONSE, // Lost the connection to DynDNS while communicating
RC_BAD_AGENT, // ???
RC_BAD_SYS, // ???
RC_DNS_ERROR, // ???
RC_NOT_FQDN, // ???
RC_NUM_HOST, // ???
RC_911, // ???
RC_DYNDNS_TIMEOUT, // DynDNS did not respond
};
// Can only process one at a time with the current implementation
class RAK_DLL_EXPORT DynDNS
{
public:
DynDNS();
~DynDNS();
// Pass 0 for newIPAddress to autodetect whatever you are uploading from
// usernameAndPassword should be in the format username:password
void UpdateHostIP(const char *dnsHost, const char *newIPAddress, const char *usernameAndPassword );
void Update(void);
// Output
bool IsRunning(void) const {return connectPhase!=CP_IDLE;}
bool IsCompleted(void) const {return connectPhase==CP_IDLE;}
RakNet::DynDnsResultCode GetCompletedResultCode(void) {return result;}
const char *GetCompletedDescription(void) const {return resultDescription;}
bool WasResultSuccessful(void) const {return result==RC_SUCCESS || result==RC_DNS_ALREADY_SET || result==RC_NO_CHANGE;}
char *GetMyPublicIP(void) const {return (char*) myIPStr;} // We get our public IP as part of the process. This is valid once completed
protected:
void Stop(void);
void SetCompleted(RakNet::DynDnsResultCode _result, const char *_resultDescription) {Stop(); result=_result; resultDescription=_resultDescription;}
enum ConnectPhase
{
CP_CONNECTING_TO_CHECKIP,
CP_WAITING_FOR_CHECKIP_RESPONSE,
CP_CONNECTING_TO_DYNDNS,
CP_WAITING_FOR_DYNDNS_RESPONSE,
CP_IDLE,
};
TCPInterface *tcp;
RakNet::RakString getString;
SystemAddress serverAddress;
ConnectPhase connectPhase;
RakNet::RakString host;
RakNet::Time phaseTimeout;
SystemAddress checkIpAddress;
const char *resultDescription;
RakNet::DynDnsResultCode result;
char myIPStr[32];
};
} // namespace RakNet
#endif // __DYN_DNS_H
#endif // _RAKNET_SUPPORT_DynDNS

View File

@@ -0,0 +1,362 @@
#include "NativeFeatureIncludes.hpp"
#if _RAKNET_SUPPORT_EmailSender==1 && _RAKNET_SUPPORT_TCPInterface==1 && _RAKNET_SUPPORT_FileOperations==1
// Useful sites
// http://www.faqs.org\rfcs\rfc2821.html
// http://www2.rad.com\networks/1995/mime/examples.htm
#include "EmailSender.hpp"
#include "TCPInterface.hpp"
#include "GetTime.hpp"
#include "Rand.hpp"
#include "FileList.hpp"
#include "BitStream.hpp"
#include <stdio.h>
#include "RakSleep.hpp"
using namespace RakNet;
STATIC_FACTORY_DEFINITIONS(EmailSender,EmailSender);
const char *EmailSender::Send(const char *hostAddress, unsigned short hostPort, const char *sender, const char *recipient, const char *senderName, const char *recipientName, const char *subject, const char *body, FileList *attachedFiles, bool doPrintf, const char *password)
{
RakNet::Packet *packet;
char query[1024];
TCPInterface tcpInterface;
SystemAddress emailServer;
if (tcpInterface.Start(0, 0)==false)
return "Unknown error starting TCP";
emailServer=tcpInterface.Connect(hostAddress, hostPort,true);
if (emailServer==UNASSIGNED_SYSTEM_ADDRESS)
return "Failed to connect to host";
#if OPEN_SSL_CLIENT_SUPPORT==1
tcpInterface.StartSSLClient(emailServer);
#endif
RakNet::TimeMS timeoutTime = RakNet::GetTimeMS()+3000;
packet=0;
while (RakNet::GetTimeMS() < timeoutTime)
{
packet = tcpInterface.Receive();
if (packet)
{
if (doPrintf)
RAKNET_DEBUG_PRINTF("%s", packet->data);
break;
}
RakSleep(250);
}
if (packet==0)
return "Timeout while waiting for initial data from server.";
tcpInterface.Send("EHLO\r\n", 6, emailServer,false);
const char *response;
bool authenticate=false;
#ifdef _MSC_VER
#pragma warning(disable:4127) // conditional expression is constant
#endif
while (1)
{
response=GetResponse(&tcpInterface, emailServer, doPrintf);
if (response!=0 && strcmp(response, "AUTHENTICATE")==0)
{
authenticate=true;
break;
}
// Something other than continue?
if (response!=0 && strcmp(response, "CONTINUE")!=0)
return response;
// Success?
if (response==0)
break;
}
if (authenticate)
{
sprintf(query, "EHLO %s\r\n", sender);
tcpInterface.Send(query, (unsigned int)strlen(query), emailServer,false);
response=GetResponse(&tcpInterface, emailServer, doPrintf);
if (response!=0)
return response;
if (password==0)
return "Password needed";
char *outputData = RakNet::OP_NEW_ARRAY<char >((const int) (strlen(sender)+strlen(password)+2)*3, _FILE_AND_LINE_ );
RakNet::BitStream bs;
char zero=0;
bs.Write(&zero,1);
bs.Write(sender,(const unsigned int)strlen(sender));
//bs.Write("jms1@jms1.net",(const unsigned int)strlen("jms1@jms1.net"));
bs.Write(&zero,1);
bs.Write(password,(const unsigned int)strlen(password));
bs.Write(&zero,1);
//bs.Write("not.my.real.password",(const unsigned int)strlen("not.my.real.password"));
TCPInterface::Base64Encoding((const char*)bs.GetData(), bs.GetNumberOfBytesUsed(), outputData);
sprintf(query, "AUTH PLAIN %s", outputData);
tcpInterface.Send(query, (unsigned int)strlen(query), emailServer,false);
response=GetResponse(&tcpInterface, emailServer, doPrintf);
if (response!=0)
return response;
}
if (sender)
sprintf(query, "MAIL From: <%s>\r\n", sender);
else
sprintf(query, "MAIL From: <>\r\n");
tcpInterface.Send(query, (unsigned int)strlen(query), emailServer,false);
response=GetResponse(&tcpInterface, emailServer, doPrintf);
if (response!=0)
return response;
if (recipient)
sprintf(query, "RCPT TO: <%s>\r\n", recipient);
else
sprintf(query, "RCPT TO: <>\r\n");
tcpInterface.Send(query, (unsigned int)strlen(query), emailServer,false);
response=GetResponse(&tcpInterface, emailServer, doPrintf);
if (response!=0)
return response;
tcpInterface.Send("DATA\r\n", (unsigned int)strlen("DATA\r\n"), emailServer,false);
// Wait for 354...
response=GetResponse(&tcpInterface, emailServer, doPrintf);
if (response!=0)
return response;
if (subject)
{
sprintf(query, "Subject: %s\r\n", subject);
tcpInterface.Send(query, (unsigned int)strlen(query), emailServer,false);
}
if (senderName)
{
sprintf(query, "From: %s\r\n", senderName);
tcpInterface.Send(query, (unsigned int)strlen(query), emailServer,false);
}
if (recipientName)
{
sprintf(query, "To: %s\r\n", recipientName);
tcpInterface.Send(query, (unsigned int)strlen(query), emailServer,false);
}
const int boundarySize=60;
char boundary[boundarySize+1];
int i,j;
if (attachedFiles && attachedFiles->fileList.Size())
{
rakNetRandom.SeedMT((unsigned int) RakNet::GetTimeMS());
// Random multipart message boundary
for (i=0; i < boundarySize; i++)
boundary[i]=TCPInterface::Base64Map()[rakNetRandom.RandomMT()%64];
boundary[boundarySize]=0;
}
sprintf(query, "MIME-version: 1.0\r\n");
tcpInterface.Send(query, (unsigned int)strlen(query), emailServer,false);
if (attachedFiles && attachedFiles->fileList.Size())
{
sprintf(query, "Content-type: multipart/mixed; BOUNDARY=\"%s\"\r\n\r\n", boundary);
tcpInterface.Send(query, (unsigned int)strlen(query), emailServer,false);
sprintf(query, "This is a multi-part message in MIME format.\r\n\r\n--%s\r\n", boundary);
tcpInterface.Send(query, (unsigned int)strlen(query), emailServer,false);
}
sprintf(query, "Content-Type: text/plain; charset=\"US-ASCII\"\r\n\r\n");
tcpInterface.Send(query, (unsigned int)strlen(query), emailServer,false);
// Write the body of the email, doing some lame shitty shit where I have to make periods at the start of a newline have a second period.
char *newBody;
int bodyLength;
bodyLength=(int)strlen(body);
newBody = (char*) rakMalloc_Ex( bodyLength*3, _FILE_AND_LINE_ );
if (bodyLength>0)
newBody[0]=body[0];
for (i=1, j=1; i < bodyLength; i++)
{
// Transform \n . \r \n into \n . . \r \n
if (i < bodyLength-2 &&
body[i-1]=='\n' &&
body[i+0]=='.' &&
body[i+1]=='\r' &&
body[i+2]=='\n')
{
newBody[j++]='.';
newBody[j++]='.';
newBody[j++]='\r';
newBody[j++]='\n';
i+=2;
}
// Transform \n . . \r \n into \n . . . \r \n
// Having to process .. is a bug in the mail server - the spec says ONLY \r\n.\r\n should be transformed
else if (i <= bodyLength-3 &&
body[i-1]=='\n' &&
body[i+0]=='.' &&
body[i+1]=='.' &&
body[i+2]=='\r' &&
body[i+3]=='\n')
{
newBody[j++]='.';
newBody[j++]='.';
newBody[j++]='.';
newBody[j++]='\r';
newBody[j++]='\n';
i+=3;
}
// Transform \n . \n into \n . . \r \n (this is a bug in the mail server - the spec says do not count \n alone but it does)
else if (i < bodyLength-1 &&
body[i-1]=='\n' &&
body[i+0]=='.' &&
body[i+1]=='\n')
{
newBody[j++]='.';
newBody[j++]='.';
newBody[j++]='\r';
newBody[j++]='\n';
i+=1;
}
// Transform \n . . \n into \n . . . \r \n (this is a bug in the mail server - the spec says do not count \n alone but it does)
// In fact having to process .. is a bug too - because the spec says ONLY \r\n.\r\n should be transformed
else if (i <= bodyLength-2 &&
body[i-1]=='\n' &&
body[i+0]=='.' &&
body[i+1]=='.' &&
body[i+2]=='\n')
{
newBody[j++]='.';
newBody[j++]='.';
newBody[j++]='.';
newBody[j++]='\r';
newBody[j++]='\n';
i+=2;
}
else
newBody[j++]=body[i];
}
newBody[j++]='\r';
newBody[j++]='\n';
tcpInterface.Send(newBody, j, emailServer,false);
rakFree_Ex(newBody, _FILE_AND_LINE_ );
int outputOffset;
// What a pain in the rear. I have to map the binary to printable characters using 6 bits per character.
if (attachedFiles && attachedFiles->fileList.Size())
{
for (i=0; i < (int) attachedFiles->fileList.Size(); i++)
{
// Write boundary
sprintf(query, "\r\n--%s\r\n", boundary);
tcpInterface.Send(query, (unsigned int)strlen(query), emailServer,false);
sprintf(query, "Content-Type: APPLICATION/Octet-Stream; SizeOnDisk=%i; name=\"%s\"\r\nContent-Transfer-Encoding: BASE64\r\nContent-Description: %s\r\n\r\n", attachedFiles->fileList[i].dataLengthBytes, attachedFiles->fileList[i].filename.C_String(), attachedFiles->fileList[i].filename.C_String());
tcpInterface.Send(query, (unsigned int)strlen(query), emailServer,false);
newBody = (char*) rakMalloc_Ex( (size_t) (attachedFiles->fileList[i].dataLengthBytes*3)/2, _FILE_AND_LINE_ );
outputOffset=TCPInterface::Base64Encoding(attachedFiles->fileList[i].data, (int) attachedFiles->fileList[i].dataLengthBytes, newBody);
// Send the base64 mapped file.
tcpInterface.Send(newBody, outputOffset, emailServer,false);
rakFree_Ex(newBody, _FILE_AND_LINE_ );
}
// Write last boundary
sprintf(query, "\r\n--%s--\r\n", boundary);
tcpInterface.Send(query, (unsigned int)strlen(query), emailServer,false);
}
sprintf(query, "\r\n.\r\n");
tcpInterface.Send(query, (unsigned int)strlen(query), emailServer,false);
response=GetResponse(&tcpInterface, emailServer, doPrintf);
if (response!=0)
return response;
tcpInterface.Send("QUIT\r\n", (unsigned int)strlen("QUIT\r\n"), emailServer,false);
RakSleep(30);
if (doPrintf)
{
packet = tcpInterface.Receive();
while (packet)
{
RAKNET_DEBUG_PRINTF("%s", packet->data);
packet = tcpInterface.Receive();
}
}
tcpInterface.Stop();
return 0; // Success
}
const char *EmailSender::GetResponse(TCPInterface *tcpInterface, const SystemAddress &emailServer, bool doPrintf)
{
RakNet::Packet *packet;
RakNet::TimeMS timeout;
timeout=RakNet::GetTimeMS()+5000;
#ifdef _MSC_VER
#pragma warning( disable : 4127 ) // warning C4127: conditional expression is constant
#endif
while (1)
{
if (tcpInterface->HasLostConnection()==emailServer)
return "Connection to server lost.";
packet = tcpInterface->Receive();
if (packet)
{
if (doPrintf)
{
RAKNET_DEBUG_PRINTF("%s", packet->data);
}
#if OPEN_SSL_CLIENT_SUPPORT==1
if (strstr((const char*)packet->data, "220"))
{
tcpInterface->StartSSLClient(packet->systemAddress);
return "AUTHENTICATE"; // OK
}
// if (strstr((const char*)packet->data, "250-AUTH LOGIN PLAIN"))
// {
// tcpInterface->StartSSLClient(packet->systemAddress);
// return "AUTHENTICATE"; // OK
// }
#endif
if (strstr((const char*)packet->data, "235"))
return 0; // Authentication accepted
if (strstr((const char*)packet->data, "354"))
return 0; // Go ahead
#if OPEN_SSL_CLIENT_SUPPORT==1
if (strstr((const char*)packet->data, "250-STARTTLS"))
{
tcpInterface->Send("STARTTLS\r\n", (unsigned int) strlen("STARTTLS\r\n"), packet->systemAddress, false);
return "CONTINUE";
}
#endif
if (strstr((const char*)packet->data, "250"))
return 0; // OK
if (strstr((const char*)packet->data, "550"))
return "Failed on error code 550";
if (strstr((const char*)packet->data, "553"))
return "Failed on error code 553";
}
if (RakNet::GetTimeMS() > timeout)
return "Timed out";
RakSleep(100);
}
}
#endif // _RAKNET_SUPPORT_*

View File

@@ -0,0 +1,58 @@
/// \file EmailSender.h
/// \brief Rudimentary class to send email from code. Don't expect anything fancy.
///
/// This file is part of RakNet Copyright 2003 Jenkins Software LLC
///
/// Usage of RakNet is subject to the appropriate license agreement.
#include "NativeFeatureIncludes.hpp"
#if _RAKNET_SUPPORT_EmailSender==1 && _RAKNET_SUPPORT_TCPInterface==1 && _RAKNET_SUPPORT_FileOperations==1
#ifndef __EMAIL_SENDER_H
#define __EMAIL_SENDER_H
#include "RakNetTypes.hpp"
#include "RakMemoryOverride.hpp"
#include "Export.hpp"
#include "Rand.hpp"
#include "TCPInterface.hpp"
namespace RakNet
{
/// Forward declarations
class FileList;
class TCPInterface;
/// \brief Rudimentary class to send email from code.
class RAK_DLL_EXPORT EmailSender
{
public:
// GetInstance() and DestroyInstance(instance*)
STATIC_FACTORY_DECLARATIONS(EmailSender)
/// \brief Sends an email.
/// \param[in] hostAddress The address of the email server.
/// \param[in] hostPort The port of the email server (usually 25)
/// \param[in] sender The email address you are sending from.
/// \param[in] recipient The email address you are sending to.
/// \param[in] senderName The email address you claim to be sending from
/// \param[in] recipientName The email address you claim to be sending to
/// \param[in] subject Email subject
/// \param[in] body Email body
/// \param[in] attachedFiles List of files to attach to the email. (Can be 0 to send none).
/// \param[in] doPrintf true to output SMTP info to console(for debugging?)
/// \param[in] password Used if the server uses AUTHENTICATE PLAIN over TLS (such as gmail)
/// \return 0 on success, otherwise a string indicating the error message
const char *Send(const char *hostAddress, unsigned short hostPort, const char *sender, const char *recipient, const char *senderName, const char *recipientName, const char *subject, const char *body, FileList *attachedFiles, bool doPrintf, const char *password);
protected:
const char *GetResponse(TCPInterface *tcpInterface, const SystemAddress &emailServer, bool doPrintf);
RakNetRandom rakNetRandom;
};
} // namespace RakNet
#endif
#endif // _RAKNET_SUPPORT_*

View File

@@ -0,0 +1 @@

View File

@@ -0,0 +1,35 @@
#include "FormatString.hpp"
#include "EpochTimeToString.hpp"
#include <stdio.h>
#include <string.h>
#include <stdarg.h>
// localtime
#include <time.h>
#include "LinuxStrings.hpp"
char * EpochTimeToString(long long time)
{
static int textIndex=0;
static char text[4][64];
if (++textIndex==4)
textIndex=0;
struct tm * timeinfo;
time_t t = time;
timeinfo = localtime ( &t );
strftime (text[textIndex],64,"%c.",timeinfo);
/*
time_t
// Copied from the docs
struct tm *newtime;
newtime = _localtime64(& time);
asctime_s( text[textIndex], sizeof(text[textIndex]), newtime );
while (text[textIndex][0] && (text[textIndex][strlen(text[textIndex])-1]=='\n' || text[textIndex][strlen(text[textIndex])-1]=='\r'))
text[textIndex][strlen(text[textIndex])-1]=0;
*/
return text[textIndex];
}

View File

@@ -0,0 +1,15 @@
/// \file EpochTimeToString.h
///
/// This file is part of RakNet Copyright 2003 Jenkins Software LLC
///
/// Usage of RakNet is subject to the appropriate license agreement.
#ifndef __EPOCH_TIME_TO_STRING_H
#define __EPOCH_TIME_TO_STRING_H
#include "Export.hpp"
RAK_DLL_EXPORT char * EpochTimeToString(long long time);
#endif

View File

@@ -0,0 +1,17 @@
// BEGIN Aya CODE
#pragma once
// END Aya CODE
#include "RakNetDefines.hpp"
#if defined(_WIN32) && !(defined(__GNUC__) || defined(__GCCXML__)) && !defined(_RAKNET_LIB) && defined(_RAKNET_DLL)
#define RAK_DLL_EXPORT __declspec(dllexport)
#else
#define RAK_DLL_EXPORT
#endif
#define STATIC_FACTORY_DECLARATIONS(x) static x* GetInstance(void); \
static void DestroyInstance( x *i);
#define STATIC_FACTORY_DEFINITIONS(x,y) x* x::GetInstance(void) {return RakNet::OP_NEW<y>( _FILE_AND_LINE_ );} \
void x::DestroyInstance( x *i) {RakNet::OP_DELETE(( y* ) i, _FILE_AND_LINE_);}

View File

@@ -0,0 +1,798 @@
#include "FileList.hpp"
#if _RAKNET_SUPPORT_FileOperations==1
#include <stdio.h> // RAKNET_DEBUG_PRINTF
#include "RakAssert.hpp"
#if defined(ANDROID)
#include <asm/io.h>
#elif defined(_WIN32) || defined(__CYGWIN__)
#include <io.h>
#elif !defined ( __APPLE__ ) && !defined ( __APPLE_CC__ ) && !defined ( __PPC__ ) && !defined ( __FreeBSD__ ) && !defined ( __S3E__ ) && !defined(__aarch64__)
#include <sys/io.h>
#endif
#ifdef _WIN32
// For mkdir
#include <direct.h>
#else
#include <sys/stat.h>
#endif
//#include "SHA1.hpp"
#include "DS_Queue.hpp"
#include "StringCompressor.hpp"
#include "BitStream.hpp"
#include "FileOperations.hpp"
#include "SuperFastHash.hpp"
#include "RakAssert.hpp"
#include "LinuxStrings.hpp"
#define MAX_FILENAME_LENGTH 512
static const unsigned HASH_LENGTH=4;
using namespace RakNet;
// alloca
#if defined(_WIN32)
#include <malloc.h>
#else
#if !defined ( __FreeBSD__ )
#include <alloca.h>
#endif
#include <unistd.h>
#include <stdlib.h>
#include <sys/stat.h>
#include "_FindFirst.hpp"
#include <stdint.h> //defines intptr_t
#endif
#include "RakAlloca.hpp"
//int RAK_DLL_EXPORT FileListNodeComp( char * const &key, const FileListNode &data )
//{
// return strcmp(key, data.filename);
//}
STATIC_FACTORY_DEFINITIONS(FileListProgress,FileListProgress)
STATIC_FACTORY_DEFINITIONS(FLP_Printf,FLP_Printf)
STATIC_FACTORY_DEFINITIONS(FileList,FileList)
#ifdef _MSC_VER
#pragma warning( push )
#endif
/// First callback called when FileList::AddFilesFromDirectory() starts
void FLP_Printf::OnAddFilesFromDirectoryStarted(FileList *fileList, char *dir) {
(void) fileList;
RAKNET_DEBUG_PRINTF("Adding files from directory %s\n",dir);}
/// Called for each directory, when that directory begins processing
void FLP_Printf::OnDirectory(FileList *fileList, char *dir, unsigned int directoriesRemaining) {
(void) fileList;
RAKNET_DEBUG_PRINTF("Adding %s. %i remaining.\n", dir, directoriesRemaining);}
void FLP_Printf::OnFilePushesComplete( SystemAddress systemAddress, unsigned short setID )
{
(void) setID;
char str[32];
systemAddress.ToString(true, (char*) str);
RAKNET_DEBUG_PRINTF("File pushes complete to %s\n", str);
}
void FLP_Printf::OnSendAborted( SystemAddress systemAddress )
{
char str[32];
systemAddress.ToString(true, (char*) str);
RAKNET_DEBUG_PRINTF("Send aborted to %s\n", str);
}
FileList::FileList()
{
}
FileList::~FileList()
{
Clear();
}
void FileList::AddFile(const char *filepath, const char *filename, FileListNodeContext context)
{
if (filepath==0 || filename==0)
return;
char *data;
//std::fstream file;
//file.open(filename, std::ios::in | std::ios::binary);
FILE *fp = fopen(filepath, "rb");
if (fp==0)
return;
fseek(fp, 0, SEEK_END);
int length = ftell(fp);
fseek(fp, 0, SEEK_SET);
if (length > (int) ((unsigned int)-1 / 8))
{
// If this assert hits, split up your file. You could also change BitSize_t in RakNetTypes.h to unsigned long long but this is not recommended for performance reasons
RakAssert("Cannot add files over 536 MB" && 0);
fclose(fp);
return;
}
bool usedAlloca=false;
if (length < MAX_ALLOCA_STACK_ALLOCATION)
{
data = ( char* ) alloca( length );
usedAlloca=true;
}
else
{
data = (char*) rakMalloc_Ex( length, _FILE_AND_LINE_ );
}
fread(data, 1, length, fp);
AddFile(filename, filepath, data, length, length, context);
fclose(fp);
if (usedAlloca==false)
rakFree_Ex(data, _FILE_AND_LINE_ );
}
void FileList::AddFile(const char *filename, const char *fullPathToFile, const char *data, const unsigned dataLength, const unsigned fileLength, FileListNodeContext context, bool isAReference, bool takeDataPointer)
{
if (filename==0)
return;
if (strlen(filename)>MAX_FILENAME_LENGTH)
{
// Should be enough for anyone
RakAssert(0);
return;
}
// If adding a reference, do not send data
RakAssert(isAReference==false || data==0);
// Avoid duplicate insertions unless the data is different, in which case overwrite the old data
unsigned i;
for (i=0; i<fileList.Size();i++)
{
if (strcmp(fileList[i].filename, filename)==0)
{
if (fileList[i].fileLengthBytes==fileLength && fileList[i].dataLengthBytes==dataLength &&
(dataLength==0 || fileList[i].data==0 ||
memcmp(fileList[i].data, data, dataLength)==0
))
// Exact same file already here
return;
// File of the same name, but different contents, so overwrite
rakFree_Ex(fileList[i].data, _FILE_AND_LINE_ );
fileList.RemoveAtIndex(i);
break;
}
}
FileListNode n;
// size_t fileNameLen = strlen(filename);
if (dataLength && data)
{
if (takeDataPointer)
{
n.data=(char*) data;
}
else
{
n.data=(char*) rakMalloc_Ex( dataLength, _FILE_AND_LINE_ );
memcpy(n.data, data, dataLength);
}
}
else
n.data=0;
n.dataLengthBytes=dataLength;
n.fileLengthBytes=fileLength;
n.isAReference=isAReference;
n.context=context;
if (n.context.dataPtr==0)
n.context.dataPtr=n.data;
if (n.context.dataLength==0)
n.context.dataLength=dataLength;
n.filename=filename;
n.fullPathToFile=fullPathToFile;
fileList.Insert(n, _FILE_AND_LINE_);
}
void FileList::AddFilesFromDirectory(const char *applicationDirectory, const char *subDirectory, bool writeHash, bool writeData, bool recursive, FileListNodeContext context)
{
DataStructures::Queue<char*> dirList;
char root[260];
char fullPath[520];
_finddata_t fileInfo;
intptr_t dir;
FILE *fp;
char *dirSoFar, *fileData;
dirSoFar=(char*) rakMalloc_Ex( 520, _FILE_AND_LINE_ );
if (applicationDirectory)
strcpy(root, applicationDirectory);
else
root[0]=0;
int rootLen=(int)strlen(root);
if (rootLen)
{
strcpy(dirSoFar, root);
if (FixEndingSlash(dirSoFar))
rootLen++;
}
else
dirSoFar[0]=0;
if (subDirectory)
{
strcat(dirSoFar, subDirectory);
FixEndingSlash(dirSoFar);
}
for (unsigned int flpcIndex=0; flpcIndex < fileListProgressCallbacks.Size(); flpcIndex++)
fileListProgressCallbacks[flpcIndex]->OnAddFilesFromDirectoryStarted(this, dirSoFar);
// RAKNET_DEBUG_PRINTF("Adding files from directory %s\n",dirSoFar);
dirList.Push(dirSoFar, _FILE_AND_LINE_ );
while (dirList.Size())
{
dirSoFar=dirList.Pop();
strcpy(fullPath, dirSoFar);
// Changed from *.* to * for Linux compatibility
strcat(fullPath, "*");
dir=_findfirst(fullPath, &fileInfo );
if (dir==-1)
{
_findclose(dir);
rakFree_Ex(dirSoFar, _FILE_AND_LINE_ );
unsigned i;
for (i=0; i < dirList.Size(); i++)
rakFree_Ex(dirList[i], _FILE_AND_LINE_ );
return;
}
// RAKNET_DEBUG_PRINTF("Adding %s. %i remaining.\n", fullPath, dirList.Size());
for (unsigned int flpcIndex=0; flpcIndex < fileListProgressCallbacks.Size(); flpcIndex++)
fileListProgressCallbacks[flpcIndex]->OnDirectory(this, fullPath, dirList.Size());
do
{
// no guarantee these entries are first...
if (strcmp("." , fileInfo.name) == 0 ||
strcmp("..", fileInfo.name) == 0)
{
continue;
}
if ((fileInfo.attrib & (_A_HIDDEN | _A_SUBDIR | _A_SYSTEM))==0)
{
strcpy(fullPath, dirSoFar);
strcat(fullPath, fileInfo.name);
fileData=0;
for (unsigned int flpcIndex=0; flpcIndex < fileListProgressCallbacks.Size(); flpcIndex++)
fileListProgressCallbacks[flpcIndex]->OnFile(this, dirSoFar, fileInfo.name, fileInfo.size);
if (writeData && writeHash)
{
fp = fopen(fullPath, "rb");
if (fp)
{
fileData= (char*) rakMalloc_Ex( fileInfo.size+HASH_LENGTH, _FILE_AND_LINE_ );
fread(fileData+HASH_LENGTH, fileInfo.size, 1, fp);
fclose(fp);
unsigned int hash = SuperFastHash(fileData+HASH_LENGTH, fileInfo.size);
if (RakNet::BitStream::DoEndianSwap())
RakNet::BitStream::ReverseBytesInPlace((unsigned char*) &hash, sizeof(hash));
memcpy(fileData, &hash, HASH_LENGTH);
// sha1.Reset();
// sha1.Update( ( unsigned char* ) fileData+HASH_LENGTH, fileInfo.size );
// sha1.Final();
// memcpy(fileData, sha1.GetHash(), HASH_LENGTH);
// File data and hash
AddFile((const char*)fullPath+rootLen, fullPath, fileData, fileInfo.size+HASH_LENGTH, fileInfo.size, context);
}
}
else if (writeHash)
{
// sha1.Reset();
// sha1.HashFile((char*)fullPath);
// sha1.Final();
unsigned int hash = SuperFastHashFile(fullPath);
if (RakNet::BitStream::DoEndianSwap())
RakNet::BitStream::ReverseBytesInPlace((unsigned char*) &hash, sizeof(hash));
// Hash only
// AddFile((const char*)fullPath+rootLen, (const char*)sha1.GetHash(), HASH_LENGTH, fileInfo.size, context);
AddFile((const char*)fullPath+rootLen, fullPath, (const char*)&hash, HASH_LENGTH, fileInfo.size, context);
}
else if (writeData)
{
fileData= (char*) rakMalloc_Ex( fileInfo.size, _FILE_AND_LINE_ );
fp = fopen(fullPath, "rb");
fread(fileData, fileInfo.size, 1, fp);
fclose(fp);
// File data only
AddFile(fullPath+rootLen, fullPath, fileData, fileInfo.size, fileInfo.size, context);
}
else
{
// Just the filename
AddFile(fullPath+rootLen, fullPath, 0, 0, fileInfo.size, context);
}
if (fileData)
rakFree_Ex(fileData, _FILE_AND_LINE_ );
}
else if ((fileInfo.attrib & _A_SUBDIR) && (fileInfo.attrib & (_A_HIDDEN | _A_SYSTEM))==0 && recursive)
{
char *newDir=(char*) rakMalloc_Ex( 520, _FILE_AND_LINE_ );
strcpy(newDir, dirSoFar);
strcat(newDir, fileInfo.name);
strcat(newDir, "/");
dirList.Push(newDir, _FILE_AND_LINE_ );
}
} while (_findnext(dir, &fileInfo ) != -1);
_findclose(dir);
rakFree_Ex(dirSoFar, _FILE_AND_LINE_ );
}
}
void FileList::Clear(void)
{
unsigned i;
for (i=0; i<fileList.Size(); i++)
{
rakFree_Ex(fileList[i].data, _FILE_AND_LINE_ );
}
fileList.Clear(false, _FILE_AND_LINE_);
}
void FileList::Serialize(RakNet::BitStream *outBitStream)
{
outBitStream->WriteCompressed(fileList.Size());
unsigned i;
for (i=0; i < fileList.Size(); i++)
{
outBitStream->WriteCompressed(fileList[i].context.op);
outBitStream->WriteCompressed(fileList[i].context.fileId);
StringCompressor::Instance()->EncodeString(fileList[i].filename.C_String(), MAX_FILENAME_LENGTH, outBitStream);
bool writeFileData = fileList[i].dataLengthBytes>0==true;
outBitStream->Write(writeFileData);
if (writeFileData)
{
outBitStream->WriteCompressed(fileList[i].dataLengthBytes);
outBitStream->Write(fileList[i].data, fileList[i].dataLengthBytes);
}
outBitStream->Write((bool)(fileList[i].fileLengthBytes==fileList[i].dataLengthBytes));
if (fileList[i].fileLengthBytes!=fileList[i].dataLengthBytes)
outBitStream->WriteCompressed(fileList[i].fileLengthBytes);
}
}
bool FileList::Deserialize(RakNet::BitStream *inBitStream)
{
bool b, dataLenNonZero=false, fileLenMatchesDataLen=false;
char filename[512];
unsigned int fileListSize;
FileListNode n;
b=inBitStream->ReadCompressed(fileListSize);
#ifdef _DEBUG
RakAssert(b);
RakAssert(fileListSize < 10000);
#endif
if (b==false || fileListSize > 10000)
return false; // Sanity check
Clear();
unsigned i;
for (i=0; i < fileListSize; i++)
{
inBitStream->ReadCompressed(n.context.op);
inBitStream->ReadCompressed(n.context.fileId);
StringCompressor::Instance()->DecodeString((char*)filename, MAX_FILENAME_LENGTH, inBitStream);
inBitStream->Read(dataLenNonZero);
if (dataLenNonZero)
{
inBitStream->ReadCompressed(n.dataLengthBytes);
// sanity check
if (n.dataLengthBytes>2000000000)
{
#ifdef _DEBUG
RakAssert(n.dataLengthBytes<=2000000000);
#endif
return false;
}
n.data=(char*) rakMalloc_Ex( (size_t) n.dataLengthBytes, _FILE_AND_LINE_ );
inBitStream->Read(n.data, n.dataLengthBytes);
}
else
{
n.dataLengthBytes=0;
n.data=0;
}
b=inBitStream->Read(fileLenMatchesDataLen);
if (fileLenMatchesDataLen)
n.fileLengthBytes=(unsigned) n.dataLengthBytes;
else
b=inBitStream->ReadCompressed(n.fileLengthBytes);
#ifdef _DEBUG
RakAssert(b);
#endif
if (b==0)
{
Clear();
return false;
}
n.filename=filename;
n.fullPathToFile=filename;
fileList.Insert(n, _FILE_AND_LINE_);
}
return true;
}
void FileList::GetDeltaToCurrent(FileList *input, FileList *output, const char *dirSubset, const char *remoteSubdir)
{
// For all files in this list that do not match the input list, write them to the output list.
// dirSubset allows checking only a portion of the files in this list.
unsigned thisIndex, inputIndex;
unsigned dirSubsetLen, localPathLen, remoteSubdirLen;
bool match;
if (dirSubset)
dirSubsetLen = (unsigned int) strlen(dirSubset);
else
dirSubsetLen = 0;
if (remoteSubdir && remoteSubdir[0])
{
remoteSubdirLen=(unsigned int) strlen(remoteSubdir);
if (IsSlash(remoteSubdir[remoteSubdirLen-1]))
remoteSubdirLen--;
}
else
remoteSubdirLen=0;
for (thisIndex=0; thisIndex < fileList.Size(); thisIndex++)
{
localPathLen = (unsigned int) fileList[thisIndex].filename.GetLength();
while (localPathLen>0)
{
if (IsSlash(fileList[thisIndex].filename[localPathLen-1]))
{
localPathLen--;
break;
}
localPathLen--;
}
// fileList[thisIndex].filename has to match dirSubset and be shorter or equal to it in length.
if (dirSubsetLen>0 &&
(localPathLen<dirSubsetLen ||
_strnicmp(fileList[thisIndex].filename.C_String(), dirSubset, dirSubsetLen)!=0 ||
(localPathLen>dirSubsetLen && IsSlash(fileList[thisIndex].filename[dirSubsetLen])==false)))
continue;
match=false;
for (inputIndex=0; inputIndex < input->fileList.Size(); inputIndex++)
{
// If the filenames, hashes, and lengths match then skip this element in fileList. Otherwise write it to output
if (_stricmp(input->fileList[inputIndex].filename.C_String()+remoteSubdirLen,fileList[thisIndex].filename.C_String()+dirSubsetLen)==0)
{
match=true;
if (input->fileList[inputIndex].fileLengthBytes==fileList[thisIndex].fileLengthBytes &&
input->fileList[inputIndex].dataLengthBytes==fileList[thisIndex].dataLengthBytes &&
memcmp(input->fileList[inputIndex].data,fileList[thisIndex].data,(size_t) fileList[thisIndex].dataLengthBytes)==0)
{
// File exists on both machines and is the same.
break;
}
else
{
// File exists on both machines and is not the same.
output->AddFile(fileList[thisIndex].filename, fileList[thisIndex].fullPathToFile, 0,0, fileList[thisIndex].fileLengthBytes, FileListNodeContext(0,0), false);
break;
}
}
}
if (match==false)
{
// Other system does not have the file at all
output->AddFile(fileList[thisIndex].filename, fileList[thisIndex].fullPathToFile, 0,0, fileList[thisIndex].fileLengthBytes, FileListNodeContext(0,0), false);
}
}
}
void FileList::ListMissingOrChangedFiles(const char *applicationDirectory, FileList *missingOrChangedFiles, bool alwaysWriteHash, bool neverWriteHash)
{
unsigned fileLength;
// CSHA1 sha1;
FILE *fp;
char fullPath[512];
unsigned i;
// char *fileData;
for (i=0; i < fileList.Size(); i++)
{
strcpy(fullPath, applicationDirectory);
FixEndingSlash(fullPath);
strcat(fullPath,fileList[i].filename);
fp=fopen(fullPath, "rb");
if (fp==0)
{
missingOrChangedFiles->AddFile(fileList[i].filename, fileList[i].fullPathToFile, 0, 0, 0, FileListNodeContext(0,0), false);
}
else
{
fseek(fp, 0, SEEK_END);
fileLength = ftell(fp);
fseek(fp, 0, SEEK_SET);
if (fileLength != fileList[i].fileLengthBytes && alwaysWriteHash==false)
{
missingOrChangedFiles->AddFile(fileList[i].filename, fileList[i].fullPathToFile, 0, 0, fileLength, FileListNodeContext(0,0), false);
}
else
{
// fileData= (char*) rakMalloc_Ex( fileLength, _FILE_AND_LINE_ );
// fread(fileData, fileLength, 1, fp);
// sha1.Reset();
// sha1.Update( ( unsigned char* ) fileData, fileLength );
// sha1.Final();
// rakFree_Ex(fileData, _FILE_AND_LINE_ );
unsigned int hash = SuperFastHashFilePtr(fp);
if (RakNet::BitStream::DoEndianSwap())
RakNet::BitStream::ReverseBytesInPlace((unsigned char*) &hash, sizeof(hash));
//if (fileLength != fileList[i].fileLength || memcmp( sha1.GetHash(), fileList[i].data, HASH_LENGTH)!=0)
if (fileLength != fileList[i].fileLengthBytes || memcmp( &hash, fileList[i].data, HASH_LENGTH)!=0)
{
if (neverWriteHash==false)
// missingOrChangedFiles->AddFile((const char*)fileList[i].filename, (const char*)sha1.GetHash(), HASH_LENGTH, fileLength, 0);
missingOrChangedFiles->AddFile((const char*)fileList[i].filename, (const char*)fileList[i].fullPathToFile, (const char *) &hash, HASH_LENGTH, fileLength, FileListNodeContext(0,0), false);
else
missingOrChangedFiles->AddFile((const char*)fileList[i].filename, (const char*)fileList[i].fullPathToFile, 0, 0, fileLength, FileListNodeContext(0,0), false);
}
}
fclose(fp);
}
}
}
void FileList::PopulateDataFromDisk(const char *applicationDirectory, bool writeFileData, bool writeFileHash, bool removeUnknownFiles)
{
FILE *fp;
char fullPath[512];
unsigned i;
// CSHA1 sha1;
i=0;
while (i < fileList.Size())
{
rakFree_Ex(fileList[i].data, _FILE_AND_LINE_ );
strcpy(fullPath, applicationDirectory);
FixEndingSlash(fullPath);
strcat(fullPath,fileList[i].filename.C_String());
fp=fopen(fullPath, "rb");
if (fp)
{
if (writeFileHash || writeFileData)
{
fseek(fp, 0, SEEK_END);
fileList[i].fileLengthBytes = ftell(fp);
fseek(fp, 0, SEEK_SET);
if (writeFileHash)
{
if (writeFileData)
{
// Hash + data so offset the data by HASH_LENGTH
fileList[i].data=(char*) rakMalloc_Ex( fileList[i].fileLengthBytes+HASH_LENGTH, _FILE_AND_LINE_ );
fread(fileList[i].data+HASH_LENGTH, fileList[i].fileLengthBytes, 1, fp);
// sha1.Reset();
// sha1.Update((unsigned char*)fileList[i].data+HASH_LENGTH, fileList[i].fileLength);
// sha1.Final();
unsigned int hash = SuperFastHash(fileList[i].data+HASH_LENGTH, fileList[i].fileLengthBytes);
if (RakNet::BitStream::DoEndianSwap())
RakNet::BitStream::ReverseBytesInPlace((unsigned char*) &hash, sizeof(hash));
// memcpy(fileList[i].data, sha1.GetHash(), HASH_LENGTH);
memcpy(fileList[i].data, &hash, HASH_LENGTH);
}
else
{
// Hash only
fileList[i].dataLengthBytes=HASH_LENGTH;
if (fileList[i].fileLengthBytes < HASH_LENGTH)
fileList[i].data=(char*) rakMalloc_Ex( HASH_LENGTH, _FILE_AND_LINE_ );
else
fileList[i].data=(char*) rakMalloc_Ex( fileList[i].fileLengthBytes, _FILE_AND_LINE_ );
fread(fileList[i].data, fileList[i].fileLengthBytes, 1, fp);
// sha1.Reset();
// sha1.Update((unsigned char*)fileList[i].data, fileList[i].fileLength);
// sha1.Final();
unsigned int hash = SuperFastHash(fileList[i].data, fileList[i].fileLengthBytes);
if (RakNet::BitStream::DoEndianSwap())
RakNet::BitStream::ReverseBytesInPlace((unsigned char*) &hash, sizeof(hash));
// memcpy(fileList[i].data, sha1.GetHash(), HASH_LENGTH);
memcpy(fileList[i].data, &hash, HASH_LENGTH);
}
}
else
{
// Data only
fileList[i].dataLengthBytes=fileList[i].fileLengthBytes;
fileList[i].data=(char*) rakMalloc_Ex( fileList[i].fileLengthBytes, _FILE_AND_LINE_ );
fread(fileList[i].data, fileList[i].fileLengthBytes, 1, fp);
}
fclose(fp);
i++;
}
else
{
fileList[i].data=0;
fileList[i].dataLengthBytes=0;
}
}
else
{
if (removeUnknownFiles)
{
fileList.RemoveAtIndex(i);
}
else
i++;
}
}
}
void FileList::FlagFilesAsReferences(void)
{
for (unsigned int i=0; i < fileList.Size(); i++)
{
fileList[i].isAReference=true;
fileList[i].dataLengthBytes=fileList[i].fileLengthBytes;
}
}
void FileList::WriteDataToDisk(const char *applicationDirectory)
{
char fullPath[512];
unsigned i,j;
for (i=0; i < fileList.Size(); i++)
{
strcpy(fullPath, applicationDirectory);
FixEndingSlash(fullPath);
strcat(fullPath,fileList[i].filename.C_String());
// Security - Don't allow .. in the filename anywhere so you can't write outside of the root directory
for (j=1; j < fileList[i].filename.GetLength(); j++)
{
if (fileList[i].filename[j]=='.' && fileList[i].filename[j-1]=='.')
{
#ifdef _DEBUG
RakAssert(0);
#endif
// Just cancel the write entirely
return;
}
}
WriteFileWithDirectories(fullPath, fileList[i].data, (unsigned int) fileList[i].dataLengthBytes);
}
}
#ifdef _MSC_VER
#pragma warning( disable : 4966 ) // unlink declared deprecated by Microsoft in order to make it harder to be cross platform. I don't agree it's deprecated.
#endif
void FileList::DeleteFiles(const char *applicationDirectory)
{
char fullPath[512];
unsigned i,j;
for (i=0; i < fileList.Size(); i++)
{
// The filename should not have .. in the path - if it does ignore it
for (j=1; j < fileList[i].filename.GetLength(); j++)
{
if (fileList[i].filename[j]=='.' && fileList[i].filename[j-1]=='.')
{
#ifdef _DEBUG
RakAssert(0);
#endif
// Just cancel the deletion entirely
return;
}
}
strcpy(fullPath, applicationDirectory);
FixEndingSlash(fullPath);
strcat(fullPath, fileList[i].filename.C_String());
// BEGIN Aya CHANGES
#ifdef _WIN32
int result = _unlink(fullPath);
#else
int result = unlink(fullPath);
#endif
// END Aya CHANGES
if (result!=0)
{
RAKNET_DEBUG_PRINTF("FileList::DeleteFiles: unlink (%s) failed.\n", fullPath);
}
}
}
void FileList::AddCallback(FileListProgress *cb)
{
if (cb==0)
return;
if ((unsigned int) fileListProgressCallbacks.GetIndexOf(cb)==(unsigned int)-1)
fileListProgressCallbacks.Push(cb, _FILE_AND_LINE_);
}
void FileList::RemoveCallback(FileListProgress *cb)
{
unsigned int idx = fileListProgressCallbacks.GetIndexOf(cb);
if (idx!=(unsigned int) -1)
fileListProgressCallbacks.RemoveAtIndex(idx);
}
void FileList::ClearCallbacks(void)
{
fileListProgressCallbacks.Clear(true, _FILE_AND_LINE_);
}
void FileList::GetCallbacks(DataStructures::List<FileListProgress*> &callbacks)
{
callbacks = fileListProgressCallbacks;
}
bool FileList::FixEndingSlash(char *str)
{
#ifdef _WIN32
if (str[strlen(str)-1]!='/' && str[strlen(str)-1]!='\\')
{
strcat(str, "\\"); // Only \ works with system commands, used by AutopatcherClient
return true;
}
#else
if (str[strlen(str)-1]!='\\' && str[strlen(str)-1]!='/')
{
strcat(str, "/"); // Only / works with Linux
return true;
}
#endif
return false;
}
#ifdef _MSC_VER
#pragma warning( pop )
#endif
#endif // _RAKNET_SUPPORT_FileOperations

View File

@@ -0,0 +1,258 @@
/// \file FileList.h
///
/// This file is part of RakNet Copyright 2003 Jenkins Software LLC
///
/// Usage of RakNet is subject to the appropriate license agreement.
#include "NativeFeatureIncludes.hpp"
#if _RAKNET_SUPPORT_FileOperations==1
#ifndef __FILE_LIST
#define __FILE_LIST
#include "Export.hpp"
#include "DS_List.hpp"
#include "RakMemoryOverride.hpp"
#include "RakNetTypes.hpp"
#include "FileListNodeContext.hpp"
#include "RakString.hpp"
#ifdef _MSC_VER
#pragma warning( push )
#endif
namespace RakNet
{
class BitStream;
}
namespace RakNet
{
/// Forward declarations
class RakPeerInterface;
class FileList;
/// Represents once instance of a file
struct FileListNode
{
/// Name of the file
RakNet::RakString filename;
/// Full path to the file, which may be different than filename
RakNet::RakString fullPathToFile;
/// File data (may be null if not ready)
char *data;
/// Length of \a data. May be greater than fileLength if prepended with a file hash
BitSize_t dataLengthBytes;
/// Length of the file
unsigned fileLengthBytes;
/// User specific data for whatever, describing this file.
FileListNodeContext context;
/// If true, data and dataLengthBytes should be empty. This is just storing the filename
bool isAReference;
};
/// Callback interface set with FileList::SetCallback() in case you want progress notifications when FileList::AddFilesFromDirectory() is called
class RAK_DLL_EXPORT FileListProgress
{
public:
// GetInstance() and DestroyInstance(instance*)
STATIC_FACTORY_DECLARATIONS(FileListProgress)
FileListProgress() {}
virtual ~FileListProgress() {}
/// First callback called when FileList::AddFilesFromDirectory() starts
virtual void OnAddFilesFromDirectoryStarted(FileList *fileList, char *dir) {
(void) fileList;
(void) dir;
}
/// Called for each directory, when that directory begins processing
virtual void OnDirectory(FileList *fileList, char *dir, unsigned int directoriesRemaining) {
(void) fileList;
(void) dir;
(void) directoriesRemaining;
}
/// Called for each file, when that file begins processing
virtual void OnFile(FileList *fileList, char *dir, char *fileName, unsigned int fileSize) {
(void) fileList;
(void) dir;
(void) fileName;
(void) fileSize;
}
/// \brief This function is called when we are sending a file to a remote system.
/// \param[in] fileName The name of the file being sent
/// \param[in] fileLengthBytes How long the file is
/// \param[in] offset The offset in bytes into the file that we are sending
/// \param[in] bytesBeingSent How many bytes we are sending this push
/// \param[in] done If this file is now done with this push
/// \param[in] targetSystem Who we are sending to
virtual void OnFilePush(const char *fileName, unsigned int fileLengthBytes, unsigned int offset, unsigned int bytesBeingSent, bool done, SystemAddress targetSystem, unsigned short setId)
{
(void) fileName;
(void) fileLengthBytes;
(void) offset;
(void) bytesBeingSent;
(void) done;
(void) targetSystem;
(void) setId;
}
/// \brief This function is called when all files have been read and are being transferred to a remote system
virtual void OnFilePushesComplete( SystemAddress systemAddress, unsigned short setId )
{
(void) systemAddress;
(void) setId;
}
/// \brief This function is called when a send to a system was aborted (probably due to disconnection)
virtual void OnSendAborted( SystemAddress systemAddress )
{
(void) systemAddress;
}
};
/// Implementation of FileListProgress to use RAKNET_DEBUG_PRINTF
class RAK_DLL_EXPORT FLP_Printf : public FileListProgress
{
public:
// GetInstance() and DestroyInstance(instance*)
STATIC_FACTORY_DECLARATIONS(FLP_Printf)
FLP_Printf() {}
virtual ~FLP_Printf() {}
/// First callback called when FileList::AddFilesFromDirectory() starts
virtual void OnAddFilesFromDirectoryStarted(FileList *fileList, char *dir);
/// Called for each directory, when that directory begins processing
virtual void OnDirectory(FileList *fileList, char *dir, unsigned int directoriesRemaining);
/// \brief This function is called when all files have been transferred to a particular remote system
virtual void OnFilePushesComplete( SystemAddress systemAddress, unsigned short setID );
/// \brief This function is called when a send to a system was aborted (probably due to disconnection)
virtual void OnSendAborted( SystemAddress systemAddress );
};
class RAK_DLL_EXPORT FileList
{
public:
// GetInstance() and DestroyInstance(instance*)
STATIC_FACTORY_DECLARATIONS(FileList)
FileList();
~FileList();
/// \brief Add all the files at a given directory.
/// \param[in] applicationDirectory The first part of the path. This is not stored as part of the filename. Use \ as the path delineator.
/// \param[in] subDirectory The rest of the path to the file. This is stored as a prefix to the filename
/// \param[in] writeHash The first 4 bytes is a hash of the file, with the remainder the actual file data (should \a writeData be true)
/// \param[in] writeData Write the contents of each file
/// \param[in] recursive Whether or not to visit subdirectories
/// \param[in] context User defined byte to store with each file. Use for whatever you want.
void AddFilesFromDirectory(const char *applicationDirectory, const char *subDirectory, bool writeHash, bool writeData, bool recursive, FileListNodeContext context);
/// Deallocate all memory
void Clear(void);
/// Write all encoded data into a bitstream
void Serialize(RakNet::BitStream *outBitStream);
/// Read all encoded data from a bitstream. Clear() is called before deserializing.
bool Deserialize(RakNet::BitStream *inBitStream);
/// \brief Given the existing set of files, search applicationDirectory for the same files.
/// \details For each file that is missing or different, add that file to \a missingOrChangedFiles. Note: the file contents are not written, and only the hash if written if \a alwaysWriteHash is true
/// alwaysWriteHash and neverWriteHash are optimizations to avoid reading the file contents to generate the hash if not necessary because the file is missing or has different lengths anyway.
/// \param[in] applicationDirectory The first part of the path. This is not stored as part of the filename. Use \ as the path delineator.
/// \param[out] missingOrChangedFiles Output list written to
/// \param[in] alwaysWriteHash If true, and neverWriteHash is false, will hash the file content of the file on disk, and write that as the file data with a length of SHA1_LENGTH bytes. If false, if the file length is different, will only write the filename.
/// \param[in] neverWriteHash If true, will never write the hash, even if available. If false, will write the hash if the file lengths are the same and it was forced to do a comparison.
void ListMissingOrChangedFiles(const char *applicationDirectory, FileList *missingOrChangedFiles, bool alwaysWriteHash, bool neverWriteHash);
/// \brief Return the files that need to be written to make \a input match this current FileList.
/// \details Specify dirSubset to only consider files that start with this path
/// specify remoteSubdir to assume that all filenames in input start with this path, so strip it off when comparing filenames.
/// \param[in] input Full list of files
/// \param[out] output Files that we need to match input
/// \param[in] dirSubset If the filename does not start with this path, just skip this file.
/// \param[in] remoteSubdir Remove this from the filenames of \a input when comparing to existing filenames.
void GetDeltaToCurrent(FileList *input, FileList *output, const char *dirSubset, const char *remoteSubdir);
/// \brief Assuming FileList contains a list of filenames presumably without data, read the data for these filenames
/// \param[in] applicationDirectory Prepend this path to each filename. Trailing slash will be added if necessary. Use \ as the path delineator.
/// \param[in] writeFileData True to read and store the file data. The first SHA1_LENGTH bytes will contain the hash if \a writeFileHash is true
/// \param[in] writeFileHash True to read and store the hash of the file data. The first SHA1_LENGTH bytes will contain the hash if \a writeFileHash is true
/// \param[in] removeUnknownFiles If a file does not exist on disk but is in the file list, remove it from the file list?
void PopulateDataFromDisk(const char *applicationDirectory, bool writeFileData, bool writeFileHash, bool removeUnknownFiles);
/// By default, GetDeltaToCurrent tags files as non-references, meaning they are assumed to be populated later
/// This tags all files as references, required for IncrementalReadInterface to process them incrementally
void FlagFilesAsReferences(void);
/// \brief Write all files to disk, prefixing the paths with applicationDirectory
/// \param[in] applicationDirectory path prefix
void WriteDataToDisk(const char *applicationDirectory);
/// \brief Add a file, given data already in memory.
/// \param[in] filename Name of a file, optionally prefixed with a partial or complete path. Use \ as the path delineator.
/// \param[in] fullPathToFile Full path to the file on disk
/// \param[in] data Contents to write
/// \param[in] dataLength length of the data, which may be greater than fileLength should you prefix extra data, such as the hash
/// \param[in] fileLength Length of the file
/// \param[in] context User defined byte to store with each file. Use for whatever you want.
/// \param[in] isAReference Means that this is just a reference to a file elsewhere - does not actually have any data
/// \param[in] takeDataPointer If true, do not allocate dataLength. Just take the pointer passed to the \a data parameter
void AddFile(const char *filename, const char *fullPathToFile, const char *data, const unsigned dataLength, const unsigned fileLength, FileListNodeContext context, bool isAReference=false, bool takeDataPointer=false);
/// \brief Add a file, reading it from disk.
/// \param[in] filepath Complete path to the file, including the filename itself
/// \param[in] filename filename to store internally, anything you want, but usually either the complete path or a subset of the complete path.
/// \param[in] context User defined byte to store with each file. Use for whatever you want.
void AddFile(const char *filepath, const char *filename, FileListNodeContext context);
/// \brief Delete all files stored in the file list.
/// \param[in] applicationDirectory Prefixed to the path to each filename. Use \ as the path delineator.
void DeleteFiles(const char *applicationDirectory);
/// \brief Adds a callback to get progress reports about what the file list instances do.
/// \param[in] cb A pointer to an externally defined instance of FileListProgress. This pointer is held internally, so should remain valid as long as this class is valid.
void AddCallback(FileListProgress *cb);
/// \brief Removes a callback
/// \param[in] cb A pointer to an externally defined instance of FileListProgress that was previously added with AddCallback()
void RemoveCallback(FileListProgress *cb);
/// \brief Removes all callbacks
void ClearCallbacks(void);
/// Returns all callbacks added with AddCallback()
/// \param[out] callbacks The list is set to the list of callbacks
void GetCallbacks(DataStructures::List<FileListProgress*> &callbacks);
// Here so you can read it, but don't modify it
DataStructures::List<FileListNode> fileList;
static bool FixEndingSlash(char *str);
protected:
DataStructures::List<FileListProgress*> fileListProgressCallbacks;
};
} // namespace RakNet
#ifdef _MSC_VER
#pragma warning( pop )
#endif
#endif
#endif // _RAKNET_SUPPORT_FileOperations

View File

@@ -0,0 +1,39 @@
/// \file FileListNodeContext.h
///
/// This file is part of RakNet Copyright 2003 Jenkins Software LLC
///
/// Usage of RakNet is subject to the appropriate license agreement.
#ifndef __FILE_LIST_NODE_CONTEXT_H
#define __FILE_LIST_NODE_CONTEXT_H
#include "BitStream.hpp"
struct FileListNodeContext
{
FileListNodeContext() {dataPtr=0; dataLength=0;}
FileListNodeContext(unsigned char o, unsigned int f) : op(o), fileId(f) {dataPtr=0; dataLength=0;}
~FileListNodeContext() {}
unsigned char op;
unsigned int fileId;
void *dataPtr;
unsigned int dataLength;
};
inline RakNet::BitStream& operator<<(RakNet::BitStream& out, FileListNodeContext& in)
{
out.Write(in.op);
out.Write(in.fileId);
return out;
}
inline RakNet::BitStream& operator>>(RakNet::BitStream& in, FileListNodeContext& out)
{
in.Read(out.op);
bool success = in.Read(out.fileId);
(void) success;
assert(success);
return in;
}
#endif

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,173 @@
/// \file FileListTransfer.h
/// \brief A plugin to provide a simple way to compress and incrementally send the files in the FileList structure.
///
/// This file is part of RakNet Copyright 2003 Jenkins Software LLC
///
/// Usage of RakNet is subject to the appropriate license agreement.
#include "NativeFeatureIncludes.hpp"
#if _RAKNET_SUPPORT_FileListTransfer==1 && _RAKNET_SUPPORT_FileOperations==1
#ifndef __FILE_LIST_TRANFER_H
#define __FILE_LIST_TRANFER_H
#include "RakNetTypes.hpp"
#include "Export.hpp"
#include "PluginInterface2.hpp"
#include "DS_Map.hpp"
#include "RakNetTypes.hpp"
#include "PacketPriority.hpp"
#include "RakMemoryOverride.hpp"
#include "FileList.hpp"
#include "DS_Queue.hpp"
#include "SimpleMutex.hpp"
#include "ThreadPool.hpp"
namespace RakNet
{
/// Forward declarations
class IncrementalReadInterface;
class FileListTransferCBInterface;
class FileListProgress;
struct FileListReceiver;
/// \defgroup FILE_LIST_TRANSFER_GROUP FileListTransfer
/// \brief A plugin to provide a simple way to compress and incrementally send the files in the FileList structure.
/// \details
/// \ingroup PLUGINS_GROUP
/// \brief A plugin to provide a simple way to compress and incrementally send the files in the FileList structure.
/// \details Similar to the DirectoryDeltaTransfer plugin, except that it doesn't send deltas based on pre-existing files or actually write the files to disk.
///
/// Usage:
/// Call SetupReceive to allow one file set to arrive. The value returned by FileListTransfer::SetupReceive()
/// is the setID that is allowed.
/// It's up to you to transmit this value to the other system, along with information indicating what kind of files you want to get.
/// The other system should then prepare a FileList and call FileListTransfer::Send(), passing the return value of FileListTransfer::SetupReceive()
/// as the \a setID parameter to FileListTransfer::Send()
/// \ingroup FILE_LIST_TRANSFER_GROUP
class RAK_DLL_EXPORT FileListTransfer : public PluginInterface2
{
public:
// GetInstance() and DestroyInstance(instance*)
STATIC_FACTORY_DECLARATIONS(FileListTransfer)
FileListTransfer();
virtual ~FileListTransfer();
/// \brief Optionally start worker threads when using _incrementalReadInterface for the Send() operation
/// \param[in] numThreads how many worker threads to start
/// \param[in] threadPriority Passed to the thread creation routine. Use THREAD_PRIORITY_NORMAL for Windows. For Linux based systems, you MUST pass something reasonable based on the thread priorities for your application.
void StartIncrementalReadThreads(int numThreads, int threadPriority=-99999);
/// \brief Allows one corresponding Send() call from another system to arrive.
/// \param[in] handler The class to call on each file
/// \param[in] deleteHandler True to delete the handler when it is no longer needed. False to not do so.
/// \param[in] allowedSender Which system to allow files from.
/// \return A set ID value, which should be passed as the \a setID value to the Send() call on the other system. This value will be returned in the callback and is unique per file set. Returns 65535 on failure (not connected to sender)
unsigned short SetupReceive(FileListTransferCBInterface *handler, bool deleteHandler, SystemAddress allowedSender);
/// \brief Send the FileList structure to another system, which must have previously called SetupReceive().
/// \param[in] fileList A list of files. The data contained in FileList::data will be sent incrementally and compressed among all files in the set
/// \param[in] rakPeer The instance of RakNet to use to send the message. Pass 0 to use the instance the plugin is attached to
/// \param[in] recipient The address of the system to send to
/// \param[in] setID The return value of SetupReceive() which was previously called on \a recipient
/// \param[in] priority Passed to RakPeerInterface::Send()
/// \param[in] orderingChannel Passed to RakPeerInterface::Send()
/// \param[in] _incrementalReadInterface If a file in \a fileList has no data, _incrementalReadInterface will be used to read the file in chunks of size \a chunkSize
/// \param[in] _chunkSize How large of a block of a file to send at once
void Send(FileList *fileList, RakNet::RakPeerInterface *rakPeer, SystemAddress recipient, unsigned short setID, PacketPriority priority, char orderingChannel, IncrementalReadInterface *_incrementalReadInterface=0, unsigned int _chunkSize=262144*4*16);
/// Return number of files waiting to go out to a particular address
unsigned int GetPendingFilesToAddress(SystemAddress recipient);
/// \brief Stop a download.
void CancelReceive(unsigned short setId);
/// \brief Remove all handlers associated with a particular system address.
void RemoveReceiver(SystemAddress systemAddress);
/// \brief Is a handler passed to SetupReceive still running?
bool IsHandlerActive(unsigned short setId);
/// \brief Adds a callback to get progress reports about what the file list instances do.
/// \param[in] cb A pointer to an externally defined instance of FileListProgress. This pointer is held internally, so should remain valid as long as this class is valid.
void AddCallback(FileListProgress *cb);
/// \brief Removes a callback
/// \param[in] cb A pointer to an externally defined instance of FileListProgress that was previously added with AddCallback()
void RemoveCallback(FileListProgress *cb);
/// \brief Removes all callbacks
void ClearCallbacks(void);
/// Returns all callbacks added with AddCallback()
/// \param[out] callbacks The list is set to the list of callbacks
void GetCallbacks(DataStructures::List<FileListProgress*> &callbacks);
/// \internal For plugin handling
virtual PluginReceiveResult OnReceive(Packet *packet);
/// \internal For plugin handling
virtual void OnRakPeerShutdown(void);
/// \internal For plugin handling
virtual void OnClosedConnection(const SystemAddress &systemAddress, RakNetGUID rakNetGUID, PI2_LostConnectionReason lostConnectionReason );
/// \internal For plugin handling
virtual void Update(void);
protected:
bool DecodeSetHeader(Packet *packet);
bool DecodeFile(Packet *packet, bool fullFile);
void Clear(void);
void OnReferencePush(Packet *packet, bool fullFile);
void OnReferencePushAck(Packet *packet);
void SendIRIToAddress(SystemAddress systemAddress);
DataStructures::Map<unsigned short, FileListReceiver*> fileListReceivers;
unsigned short setId;
DataStructures::List<FileListProgress*> fileListProgressCallbacks;
struct FileToPush
{
FileListNode fileListNode;
PacketPriority packetPriority;
char orderingChannel;
unsigned int currentOffset;
unsigned short setID;
unsigned int setIndex;
IncrementalReadInterface *incrementalReadInterface;
unsigned int chunkSize;
};
struct FileToPushRecipient
{
unsigned int refCount;
SimpleMutex refCountMutex;
void DeleteThis(void);
void AddRef(void);
void Deref(void);
SystemAddress systemAddress;
DataStructures::Queue<FileToPush*> filesToPush;
};
DataStructures::List< FileToPushRecipient* > fileToPushRecipientList;
SimpleMutex fileToPushRecipientListMutex;
void RemoveFromList(FileToPushRecipient *ftpr);
struct ThreadData
{
FileListTransfer *fileListTransfer;
SystemAddress systemAddress;
};
ThreadPool<ThreadData, int> threadPool;
friend int SendIRIToAddressCB(FileListTransfer::ThreadData threadData, bool *returnOutput, void* perThreadData);
};
} // namespace RakNet
#endif
#endif // _RAKNET_SUPPORT_*

View File

@@ -0,0 +1,154 @@
/// \file FileListTransferCBInterface.h
///
/// This file is part of RakNet Copyright 2003 Jenkins Software LLC
///
/// Usage of RakNet is subject to the appropriate license agreement.
#ifndef __FILE_LIST_TRANSFER_CALLBACK_INTERFACE_H
#define __FILE_LIST_TRANSFER_CALLBACK_INTERFACE_H
#include "RakMemoryOverride.hpp"
#include "FileListNodeContext.hpp"
#ifdef _MSC_VER
#pragma warning( push )
#endif
namespace RakNet
{
/// \brief Used by FileListTransfer plugin as a callback for when we get a file.
/// \details You get the last file when fileIndex==numberOfFilesInThisSet
/// \sa FileListTransfer
class FileListTransferCBInterface
{
public:
// Note: If this structure is changed the struct in the swig files need to be changed as well
struct OnFileStruct
{
/// \brief The index into the set of files, from 0 to numberOfFilesInThisSet
unsigned fileIndex;
/// \brief The name of the file
char fileName[512];
/// \brief The data pointed to by the file
char *fileData;
/// \brief The actual length of this file.
BitSize_t byteLengthOfThisFile;
/// \brief How many bytes of this file has been downloaded
BitSize_t bytesDownloadedForThisFile;
/// \brief Files are transmitted in sets, where more than one set of files can be transmitted at the same time.
/// \details This is the identifier for the set, which is returned by FileListTransfer::SetupReceive
unsigned short setID;
/// \brief The number of files that are in this set.
unsigned numberOfFilesInThisSet;
/// \brief The total length of the transmitted files for this set, after being uncompressed
unsigned byteLengthOfThisSet;
/// \brief The total length, in bytes, downloaded for this set.
unsigned bytesDownloadedForThisSet;
/// \brief User data passed to one of the functions in the FileList class.
/// \details However, on error, this is instead changed to one of the enumerations in the PatchContext structure.
FileListNodeContext context;
/// \brief Who sent this file
SystemAddress senderSystemAddress;
/// \brief Who sent this file. Not valid when using TCP, only RakPeer (UDP)
RakNetGUID senderGuid;
};
// Note: If this structure is changed the struct in the swig files need to be changed as well
struct FileProgressStruct
{
/// \param[out] onFileStruct General information about this file, such as the filename and the first \a partLength bytes. You do NOT need to save this data yourself. The complete file will arrive normally.
OnFileStruct *onFileStruct;
/// \param[out] partCount The zero based index into partTotal. The percentage complete done of this file is 100 * (partCount+1)/partTotal
unsigned int partCount;
/// \param[out] partTotal The total number of parts this file was split into. Each part will be roughly the MTU size, minus the UDP header and RakNet headers
unsigned int partTotal;
/// \param[out] dataChunkLength How many bytes long firstDataChunk and iriDataChunk are
unsigned int dataChunkLength;
/// \param[out] firstDataChunk The first \a partLength of the final file. If you store identifying information about the file in the first \a partLength bytes, you can read them while the download is taking place. If this hasn't arrived yet, firstDataChunk will be 0
char *firstDataChunk;
/// \param[out] iriDataChunk If the remote system is sending this file using IncrementalReadInterface, then this is the chunk we just downloaded. It will not exist in memory after this callback. You should either store this to disk, or in memory. If it is 0, then the file is smaller than one chunk, and will be held in memory automatically
char *iriDataChunk;
/// \param[out] iriWriteOffset Offset in bytes from the start of the file for the data pointed to by iriDataChunk
unsigned int iriWriteOffset;
/// \param[out] Who sent this file
SystemAddress senderSystemAddress;
/// \param[out] Who sent this file. Not valid when using TCP, only RakPeer (UDP)
RakNetGUID senderGuid;
/// \param[in] allocateIrIDataChunkAutomatically If true, then RakNet will hold iriDataChunk for you and return it in OnFile. Defaults to true
bool allocateIrIDataChunkAutomatically;
};
struct DownloadCompleteStruct
{
/// \brief Files are transmitted in sets, where more than one set of files can be transmitted at the same time.
/// \details This is the identifier for the set, which is returned by FileListTransfer::SetupReceive
unsigned short setID;
/// \brief The number of files that are in this set.
unsigned numberOfFilesInThisSet;
/// \brief The total length of the transmitted files for this set, after being uncompressed
unsigned byteLengthOfThisSet;
/// \brief Who sent this file
SystemAddress senderSystemAddress;
/// \brief Who sent this file. Not valid when using TCP, only RakPeer (UDP)
RakNetGUID senderGuid;
};
FileListTransferCBInterface() {}
virtual ~FileListTransferCBInterface() {}
/// \brief Got a file.
/// \details This structure is only valid for the duration of this function call.
/// \return Return true to have RakNet delete the memory allocated to hold this file for this function call.
virtual bool OnFile(OnFileStruct *onFileStruct)=0;
/// \brief Got part of a big file internally in RakNet
/// \details This is called in one of two circumstances: Either the transport layer is returning ID_PROGRESS_NOTIFICATION, or you got a block via IncrementalReadInterface
/// If the transport layer is returning ID_PROGRESS_NOTIFICATION (see RakPeer::SetSplitMessageProgressInterval()) then FileProgressStruct::iriDataChunk will be 0.
/// If this is a block via IncrementalReadInterface, then iriDataChunk will point to the block just downloaded.
/// If not using IncrementalReadInterface, then you only care about partCount and partTotal to tell how far the download has progressed. YOu can use firstDataChunk to read the first part of the file if desired. The file is usable when you get the OnFile callback.
/// If using IncrementalReadInterface and you let RakNet buffer the files in memory (default), then it is the same as above. The file is usable when you get the OnFile callback.
/// If using IncrementalReadInterface and you do not let RakNet buffer the files in memory, then set allocateIrIDataChunkAutomatically to false. Write the file to disk whenever you get OnFileProgress and iriDataChunk is not 0, and ignore OnFile.
virtual void OnFileProgress(FileProgressStruct *fps)=0;
/// \brief Called while the handler is active by FileListTransfer
/// \details Return false when you are done with the class.
/// At that point OnDereference will be called and the class will no longer be maintained by the FileListTransfer plugin.
virtual bool Update(void) {return true;}
/// \brief Called when the download is completed.
/// \details If you are finished with this class, return false.
/// At that point OnDereference will be called and the class will no longer be maintained by the FileListTransfer plugin.
/// Otherwise return true, and Update will continue to be called.
virtual bool OnDownloadComplete(DownloadCompleteStruct *dcs) {(void) dcs; return false;}
/// \brief This function is called when this instance is about to be dereferenced by the FileListTransfer plugin.
/// \details Update will no longer be called.
/// It will will be deleted automatically if true was passed to FileListTransfer::SetupReceive::deleteHandler
/// Otherwise it is up to you to delete it yourself.
virtual void OnDereference(void) {}
};
} // namespace RakNet
#ifdef _MSC_VER
#pragma warning( pop )
#endif
#endif

View File

@@ -0,0 +1,175 @@
#include "FileOperations.hpp"
#if _RAKNET_SUPPORT_FileOperations==1
#include "RakMemoryOverride.hpp"
#include "_FindFirst.hpp" // For linux
#include <stdio.h>
#include <string.h>
#ifdef _WIN32
// For mkdir
#include <direct.h>
#include <io.h>
#else
#include <sys/stat.h>
#include <unistd.h>
#include "_FindFirst.hpp"
#endif
#include "errno.h"
#ifdef _MSC_VER
#pragma warning( push )
#endif
#ifdef _MSC_VER
#pragma warning( disable : 4966 ) // mkdir declared deprecated by Microsoft in order to make it harder to be cross platform. I don't agree it's deprecated.
#endif
bool WriteFileWithDirectories( const char *path, char *data, unsigned dataLength )
{
int index;
FILE *fp;
char *pathCopy;
int res;
if ( path == 0 || path[ 0 ] == 0 )
return false;
pathCopy = (char*) rakMalloc_Ex( strlen( path ) + 1, _FILE_AND_LINE_ );
strcpy( pathCopy, path );
// Ignore first / if there is one
if (pathCopy[0])
{
index = 1;
while ( pathCopy[ index ] )
{
if ( pathCopy[ index ] == '/' || pathCopy[ index ] == '\\')
{
pathCopy[ index ] = 0;
// BEING Aya CHANGES
#ifdef _WIN32
res = _mkdir( pathCopy );
#else
res = mkdir( pathCopy, 0744 );
#endif
// END Aya CHANGES
if (res<0 && errno!=EEXIST && errno!=EACCES)
{
rakFree_Ex(pathCopy, _FILE_AND_LINE_ );
return false;
}
pathCopy[ index ] = '/';
}
index++;
}
}
if (data)
{
fp = fopen( path, "wb" );
if ( fp == 0 )
{
rakFree_Ex(pathCopy, _FILE_AND_LINE_ );
return false;
}
fwrite( data, 1, dataLength, fp );
fclose( fp );
}
else
{
// BEING Aya CHANGES
#ifdef _WIN32
res = _mkdir( pathCopy );
#else
res = mkdir( pathCopy, 0744 );
#endif
// END Aya CHANGES
if (res<0 && errno!=EEXIST)
{
rakFree_Ex(pathCopy, _FILE_AND_LINE_ );
return false;
}
}
rakFree_Ex(pathCopy, _FILE_AND_LINE_ );
return true;
}
bool IsSlash(unsigned char c)
{
return c=='/' || c=='\\';
}
void AddSlash( char *input )
{
if (input==0 || input[0]==0)
return;
int lastCharIndex=(int) strlen(input)-1;
if (input[lastCharIndex]=='\\')
input[lastCharIndex]='/';
else if (input[lastCharIndex]!='/')
{
input[lastCharIndex+1]='/';
input[lastCharIndex+2]=0;
}
}
bool DirectoryExists(const char *directory)
{
_finddata_t fileInfo;
intptr_t dir;
char baseDirWithStars[560];
strcpy(baseDirWithStars, directory);
AddSlash(baseDirWithStars);
strcat(baseDirWithStars, "*.*");
dir=_findfirst(baseDirWithStars, &fileInfo );
if (dir==-1)
return false;
_findclose(dir);
return true;
}
void QuoteIfSpaces(char *str)
{
unsigned i;
bool hasSpace=false;
for (i=0; str[i]; i++)
{
if (str[i]==' ')
{
hasSpace=true;
break;
}
}
if (hasSpace)
{
int len=(int)strlen(str);
memmove(str+1, str, len);
str[0]='\"';
str[len]='\"';
str[len+1]=0;
}
}
unsigned int GetFileLength(const char *path)
{
FILE *fp = fopen(path, "rb");
if (fp==0) return 0;
fseek(fp, 0, SEEK_END);
unsigned int fileLength = ftell(fp);
fclose(fp);
return fileLength;
}
#ifdef _MSC_VER
#pragma warning( pop )
#endif
#endif // _RAKNET_SUPPORT_FileOperations

View File

@@ -0,0 +1,24 @@
/// \file FileOperations.h
///
/// This file is part of RakNet Copyright 2003 Jenkins Software LLC
///
/// Usage of RakNet is subject to the appropriate license agreement.
#include "NativeFeatureIncludes.hpp"
#if _RAKNET_SUPPORT_FileOperations==1
#ifndef __FILE_OPERATIONS_H
#define __FILE_OPERATIONS_H
#include "Export.hpp"
bool RAK_DLL_EXPORT WriteFileWithDirectories( const char *path, char *data, unsigned dataLength );
bool RAK_DLL_EXPORT IsSlash(unsigned char c);
void RAK_DLL_EXPORT AddSlash( char *input );
void RAK_DLL_EXPORT QuoteIfSpaces(char *str);
bool RAK_DLL_EXPORT DirectoryExists(const char *directory);
unsigned int RAK_DLL_EXPORT GetFileLength(const char *path);
#endif
#endif // _RAKNET_SUPPORT_FileOperations

View File

@@ -0,0 +1,30 @@
#include "FormatString.hpp"
#include <stdio.h>
#include <string.h>
#include <stdarg.h>
#include "LinuxStrings.hpp"
char * FormatString(const char *format, ...)
{
static int textIndex=0;
static char text[4][8096];
va_list ap;
va_start(ap, format);
if (++textIndex==4)
textIndex=0;
_vsnprintf(text[textIndex], 8096, format, ap);
va_end(ap);
text[textIndex][8096-1]=0;
return text[textIndex];
}
char * FormatStringTS(char *output, const char *format, ...)
{
va_list ap;
va_start(ap, format);
_vsnprintf(output, 512, format, ap);
va_end(ap);
return output;
}

View File

@@ -0,0 +1,22 @@
/// \file FormatString.h
///
/// This file is part of RakNet Copyright 2003 Jenkins Software LLC
///
/// Usage of RakNet is subject to the appropriate license agreement.
#ifndef __FORMAT_STRING_H
#define __FORMAT_STRING_H
#include "Export.hpp"
extern "C" {
char * FormatString(const char *format, ...);
}
// Threadsafe
extern "C" {
char * FormatStringTS(char *output, const char *format, ...);
}
#endif

View File

@@ -0,0 +1,552 @@
/// \file
///
/// This file is part of RakNet Copyright 2003 Jenkins Software LLC
///
/// Usage of RakNet is subject to the appropriate license agreement.
#include "NativeFeatureIncludes.hpp"
#if _RAKNET_SUPPORT_FullyConnectedMesh2==1
#include "FullyConnectedMesh2.hpp"
#include "RakPeerInterface.hpp"
#include "MessageIdentifiers.hpp"
#include "BitStream.hpp"
#include "RakAssert.hpp"
#include "GetTime.hpp"
#include "Rand.hpp"
#include "DS_OrderedList.hpp"
using namespace RakNet;
int FCM2ParticipantComp( const FullyConnectedMesh2::FCM2Participant &key, const FullyConnectedMesh2::FCM2Participant &data )
{
if (key.fcm2Guid < data.fcm2Guid)
return -1;
if (key.fcm2Guid > data.fcm2Guid)
return 1;
return 0;
}
STATIC_FACTORY_DEFINITIONS(FullyConnectedMesh2,FullyConnectedMesh2);
FullyConnectedMesh2::FullyConnectedMesh2()
{
startupTime=0;
totalConnectionCount=0;
ourFCMGuid=0;
autoParticipateConnections=true;
connectOnNewRemoteConnections=true;
hostRakNetGuid=UNASSIGNED_RAKNET_GUID;
}
FullyConnectedMesh2::~FullyConnectedMesh2()
{
Clear();
}
RakNetGUID FullyConnectedMesh2::GetConnectedHost(void) const
{
if (ourFCMGuid==0)
return UNASSIGNED_RAKNET_GUID;
return hostRakNetGuid;
}
SystemAddress FullyConnectedMesh2::GetConnectedHostAddr(void) const
{
if (ourFCMGuid==0)
return UNASSIGNED_SYSTEM_ADDRESS;
return rakPeerInterface->GetSystemAddressFromGuid(hostRakNetGuid);
}
RakNetGUID FullyConnectedMesh2::GetHostSystem(void) const
{
if (ourFCMGuid==0)
return rakPeerInterface->GetGuidFromSystemAddress(UNASSIGNED_SYSTEM_ADDRESS);
return hostRakNetGuid;
}
bool FullyConnectedMesh2::IsHostSystem(void) const
{
return GetHostSystem()==rakPeerInterface->GetGuidFromSystemAddress(UNASSIGNED_SYSTEM_ADDRESS);
}
void FullyConnectedMesh2::GetHostOrder(DataStructures::List<RakNetGUID> &hostList)
{
hostList.Clear(true, _FILE_AND_LINE_);
if (ourFCMGuid==0 || fcm2ParticipantList.Size()==0)
{
hostList.Push(rakPeerInterface->GetMyGUID(), _FILE_AND_LINE_);
return;
}
FCM2Participant fcm2;
fcm2.fcm2Guid=ourFCMGuid;
fcm2.rakNetGuid=rakPeerInterface->GetMyGUID();
DataStructures::OrderedList<FCM2Participant, FCM2Participant, FCM2ParticipantComp> olist;
olist.Insert(fcm2, fcm2, true, _FILE_AND_LINE_);
for (unsigned int i=0; i < fcm2ParticipantList.Size(); i++)
olist.Insert(fcm2ParticipantList[i], fcm2ParticipantList[i], true, _FILE_AND_LINE_);
for (unsigned int i=0; i < olist.Size(); i++)
{
hostList.Push(olist[i].rakNetGuid, _FILE_AND_LINE_);
}
}
bool FullyConnectedMesh2::IsConnectedHost(void) const
{
return GetConnectedHost()==rakPeerInterface->GetGuidFromSystemAddress(UNASSIGNED_SYSTEM_ADDRESS);
}
void FullyConnectedMesh2::SetAutoparticipateConnections(bool b)
{
autoParticipateConnections=b;
}
void FullyConnectedMesh2::ResetHostCalculation(void)
{
hostRakNetGuid=UNASSIGNED_RAKNET_GUID;
startupTime=RakNet::GetTimeUS();
totalConnectionCount=0;
ourFCMGuid=0;
for (unsigned int i=0; i < fcm2ParticipantList.Size(); i++)
SendFCMGuidRequest(fcm2ParticipantList[i].rakNetGuid);
}
bool FullyConnectedMesh2::AddParticipantInternal( RakNetGUID rakNetGuid, FCM2Guid theirFCMGuid )
{
for (unsigned int i=0; i < fcm2ParticipantList.Size(); i++)
{
if (fcm2ParticipantList[i].rakNetGuid==rakNetGuid)
{
if (theirFCMGuid!=0)
fcm2ParticipantList[i].fcm2Guid=theirFCMGuid;
return false;
}
}
FCM2Participant participant;
participant.rakNetGuid=rakNetGuid;
participant.fcm2Guid=theirFCMGuid;
fcm2ParticipantList.Push(participant,_FILE_AND_LINE_);
SendFCMGuidRequest(rakNetGuid);
return true;
}
void FullyConnectedMesh2::AddParticipant( RakNetGUID rakNetGuid )
{
if (rakPeerInterface->GetConnectionState(rakPeerInterface->GetSystemAddressFromGuid(rakNetGuid))!=IS_CONNECTED)
{
#ifdef DEBUG_FCM2
printf("AddParticipant to %s failed (not connected)\n", rakNetGuid.ToString());
#endif
return;
}
AddParticipantInternal(rakNetGuid,0);
}
void FullyConnectedMesh2::GetParticipantList(DataStructures::List<RakNetGUID> &participantList)
{
participantList.Clear(true, _FILE_AND_LINE_);
unsigned int i;
for (i=0; i < fcm2ParticipantList.Size(); i++)
participantList.Push(fcm2ParticipantList[i].rakNetGuid, _FILE_AND_LINE_);
}
PluginReceiveResult FullyConnectedMesh2::OnReceive(Packet *packet)
{
switch (packet->data[0])
{
case ID_REMOTE_NEW_INCOMING_CONNECTION:
{
if (connectOnNewRemoteConnections)
ConnectToRemoteNewIncomingConnections(packet);
}
break;
case ID_FCM2_REQUEST_FCMGUID:
OnRequestFCMGuid(packet);
return RR_STOP_PROCESSING_AND_DEALLOCATE;
case ID_FCM2_RESPOND_CONNECTION_COUNT:
OnRespondConnectionCount(packet);
return RR_STOP_PROCESSING_AND_DEALLOCATE;
case ID_FCM2_INFORM_FCMGUID:
OnInformFCMGuid(packet);
return RR_STOP_PROCESSING_AND_DEALLOCATE;
case ID_FCM2_UPDATE_MIN_TOTAL_CONNECTION_COUNT:
OnUpdateMinTotalConnectionCount(packet);
return RR_STOP_PROCESSING_AND_DEALLOCATE;
case ID_FCM2_NEW_HOST:
if (packet->wasGeneratedLocally==false)
return RR_STOP_PROCESSING_AND_DEALLOCATE;
break;
}
return RR_CONTINUE_PROCESSING;
}
void FullyConnectedMesh2::OnRakPeerStartup(void)
{
Clear();
startupTime=RakNet::GetTimeUS();
}
void FullyConnectedMesh2::OnAttach(void)
{
Clear();
// In case Startup() was called first
if (rakPeerInterface->IsActive())
startupTime=RakNet::GetTimeUS();
}
void FullyConnectedMesh2::OnRakPeerShutdown(void)
{
Clear();
startupTime=0;
}
void FullyConnectedMesh2::OnClosedConnection(const SystemAddress &systemAddress, RakNetGUID rakNetGUID, PI2_LostConnectionReason lostConnectionReason )
{
(void) lostConnectionReason;
(void) systemAddress;
(void) rakNetGUID;
unsigned int idx;
for (idx=0; idx < fcm2ParticipantList.Size(); idx++)
{
if (fcm2ParticipantList[idx].rakNetGuid==rakNetGUID)
{
fcm2ParticipantList[idx]=fcm2ParticipantList[fcm2ParticipantList.Size()-1];
#ifdef DEBUG_FCM2
printf("Popping participant %s\n", fcm2ParticipantList[fcm2ParticipantList.Size()-1].rakNetGuid.ToString());
#endif
fcm2ParticipantList.Pop();
if (rakNetGUID==hostRakNetGuid && ourFCMGuid!=0)
{
if (fcm2ParticipantList.Size()==0)
{
hostRakNetGuid=rakPeerInterface->GetMyGUID();
hostFCM2Guid=ourFCMGuid;
}
else
{
CalculateHost(&hostRakNetGuid, &hostFCM2Guid);
}
PushNewHost(hostRakNetGuid, rakNetGUID);
}
return;
}
}
}
RakNet::TimeUS FullyConnectedMesh2::GetElapsedRuntime(void)
{
RakNet::TimeUS curTime=RakNet::GetTimeUS();
if (curTime>startupTime)
return curTime-startupTime;
else
return 0;
}
void FullyConnectedMesh2::OnNewConnection(const SystemAddress &systemAddress, RakNetGUID rakNetGUID, bool isIncoming)
{
(void) isIncoming;
(void) rakNetGUID;
(void) systemAddress;
if (autoParticipateConnections)
AddParticipant(rakNetGUID);
}
void FullyConnectedMesh2::Clear(void)
{
fcm2ParticipantList.Clear(false, _FILE_AND_LINE_);
totalConnectionCount=0;
ourFCMGuid=0;
lastPushedHost=UNASSIGNED_RAKNET_GUID;
}
void FullyConnectedMesh2::PushNewHost(const RakNetGUID &guid, RakNetGUID oldHost)
{
Packet *p = AllocatePacketUnified(sizeof(MessageID)+sizeof(oldHost));
RakNet::BitStream bs(p->data,p->length,false);
bs.SetWriteOffset(0);
bs.Write((MessageID)ID_FCM2_NEW_HOST);
bs.Write(oldHost);
p->systemAddress=rakPeerInterface->GetSystemAddressFromGuid(guid);
p->systemAddress.systemIndex=(SystemIndex)-1;
p->guid=guid;
p->wasGeneratedLocally=true;
rakPeerInterface->PushBackPacket(p, true);
lastPushedHost=guid;
}
void FullyConnectedMesh2::SendFCMGuidRequest(RakNetGUID rakNetGuid)
{
if (rakNetGuid==rakPeerInterface->GetGuidFromSystemAddress(UNASSIGNED_SYSTEM_ADDRESS))
return;
RakNet::BitStream bsOut;
bsOut.Write((MessageID)ID_FCM2_REQUEST_FCMGUID);
if (ourFCMGuid==0)
{
bsOut.Write(false);
bsOut.Write(GetElapsedRuntime());
}
else
{
bsOut.Write(true);
bsOut.Write(totalConnectionCount);
bsOut.Write(ourFCMGuid);
}
rakPeerInterface->Send(&bsOut,HIGH_PRIORITY,RELIABLE_ORDERED,0,rakNetGuid,false);
}
void FullyConnectedMesh2::SendOurFCMGuid(SystemAddress addr)
{
RakNet::BitStream bsOut;
bsOut.Write((MessageID)ID_FCM2_INFORM_FCMGUID);
RakAssert(ourFCMGuid!=0); // Can't inform others of our FCM2Guid if it's unset!
bsOut.Write(ourFCMGuid);
bsOut.Write(totalConnectionCount);
rakPeerInterface->Send(&bsOut,HIGH_PRIORITY,RELIABLE_ORDERED,0,addr,false);
}
void FullyConnectedMesh2::SendConnectionCountResponse(SystemAddress addr, unsigned int responseTotalConnectionCount)
{
RakNet::BitStream bsOut;
bsOut.Write((MessageID)ID_FCM2_RESPOND_CONNECTION_COUNT);
bsOut.Write(responseTotalConnectionCount);
rakPeerInterface->Send(&bsOut,HIGH_PRIORITY,RELIABLE_ORDERED,0,addr,false);
}
void FullyConnectedMesh2::AssignOurFCMGuid(void)
{
// Only assigned once ever
RakAssert(ourFCMGuid==0);
unsigned int randomNumber = randomMT();
randomNumber ^= (unsigned int) (RakNet::GetTimeUS() & 0xFFFFFFFF);
randomNumber ^= (unsigned int) (rakPeerInterface->GetGuidFromSystemAddress(UNASSIGNED_SYSTEM_ADDRESS).g & 0xFFFFFFFF);
ourFCMGuid |= randomNumber;
uint64_t reponse64 = totalConnectionCount;
ourFCMGuid |= reponse64<<32;
}
void FullyConnectedMesh2::CalculateHost(RakNetGUID *rakNetGuid, FCM2Guid *fcm2Guid)
{
// Can't calculate host without knowing our own
RakAssert(ourFCMGuid!=0);
// Can't calculate host without being connected to anyone else
RakAssert(fcm2ParticipantList.Size()>0);
// Return the lowest value of all FCM2Guid
FCM2Guid lowestFCMGuid=ourFCMGuid;
// SystemAddress associatedSystemAddress=UNASSIGNED_SYSTEM_ADDRESS;
RakNetGUID associatedRakNetGuid=rakPeerInterface->GetGuidFromSystemAddress(UNASSIGNED_SYSTEM_ADDRESS);
unsigned int idx;
for (idx=0; idx < fcm2ParticipantList.Size(); idx++)
{
if (fcm2ParticipantList[idx].fcm2Guid!=0 && fcm2ParticipantList[idx].fcm2Guid<lowestFCMGuid)
{
lowestFCMGuid=fcm2ParticipantList[idx].fcm2Guid;
associatedRakNetGuid=fcm2ParticipantList[idx].rakNetGuid;
}
}
*rakNetGuid=associatedRakNetGuid;
*fcm2Guid=lowestFCMGuid;
}
void FullyConnectedMesh2::OnRequestFCMGuid(Packet *packet)
{
RakNet::BitStream bsIn(packet->data,packet->length,false);
bsIn.IgnoreBytes(sizeof(MessageID));
bool hasRemoteFCMGuid=false;
bsIn.Read(hasRemoteFCMGuid);
RakNet::TimeUS senderElapsedRuntime=0;
unsigned int remoteTotalConnectionCount=0;
FCM2Guid theirFCMGuid=0;
if (hasRemoteFCMGuid)
{
bsIn.Read(remoteTotalConnectionCount);
bsIn.Read(theirFCMGuid);
}
else
{
bsIn.Read(senderElapsedRuntime);
}
AddParticipantInternal(packet->guid,theirFCMGuid);
if (ourFCMGuid==0)
{
if (hasRemoteFCMGuid==false)
{
// Nobody has a fcmGuid
RakNet::TimeUS ourElapsedRuntime = GetElapsedRuntime();
if (ourElapsedRuntime>senderElapsedRuntime)
{
// We are probably host
SendConnectionCountResponse(packet->systemAddress, 2);
}
else
{
// They are probably host
SendConnectionCountResponse(packet->systemAddress, 1);
}
}
else
{
// They have a fcmGuid, we do not
IncrementTotalConnectionCount(remoteTotalConnectionCount+1);
AssignOurFCMGuid();
unsigned int idx;
for (idx=0; idx < fcm2ParticipantList.Size(); idx++)
SendOurFCMGuid(rakPeerInterface->GetSystemAddressFromGuid(fcm2ParticipantList[idx].rakNetGuid));
}
}
else
{
if (hasRemoteFCMGuid==false)
{
// We have a fcmGuid they do not
SendConnectionCountResponse(packet->systemAddress, totalConnectionCount+1);
}
else
{
// We both have fcmGuids
IncrementTotalConnectionCount(remoteTotalConnectionCount);
SendOurFCMGuid(packet->systemAddress);
}
}
CalculateAndPushHost();
}
void FullyConnectedMesh2::OnRespondConnectionCount(Packet *packet)
{
RakNet::BitStream bsIn(packet->data,packet->length,false);
bsIn.IgnoreBytes(sizeof(MessageID));
unsigned int responseTotalConnectionCount;
bsIn.Read(responseTotalConnectionCount);
IncrementTotalConnectionCount(responseTotalConnectionCount);
bool wasAssigned;
if (ourFCMGuid==0)
{
wasAssigned=true;
AssignOurFCMGuid();
}
else
wasAssigned=false;
// 1 is returned to give us lower priority, but the actual minimum is 2
IncrementTotalConnectionCount(2);
if (wasAssigned==true)
{
unsigned int idx;
for (idx=0; idx < fcm2ParticipantList.Size(); idx++)
SendOurFCMGuid(rakPeerInterface->GetSystemAddressFromGuid(fcm2ParticipantList[idx].rakNetGuid));
CalculateAndPushHost();
}
}
void FullyConnectedMesh2::OnInformFCMGuid(Packet *packet)
{
RakNet::BitStream bsIn(packet->data,packet->length,false);
bsIn.IgnoreBytes(sizeof(MessageID));
FCM2Guid theirFCMGuid;
unsigned int theirTotalConnectionCount;
bsIn.Read(theirFCMGuid);
bsIn.Read(theirTotalConnectionCount);
IncrementTotalConnectionCount(theirTotalConnectionCount);
if (AddParticipantInternal(packet->guid,theirFCMGuid))
{
// 1/19/2010 - Relay increased total connection count in case new participant only connects to part of the mesh
unsigned int idx;
RakNet::BitStream bsOut;
bsOut.Write((MessageID)ID_FCM2_UPDATE_MIN_TOTAL_CONNECTION_COUNT);
bsOut.Write(totalConnectionCount);
for (idx=0; idx < fcm2ParticipantList.Size(); idx++)
{
if (packet->guid!=fcm2ParticipantList[idx].rakNetGuid)
rakPeerInterface->Send(&bsOut,HIGH_PRIORITY,RELIABLE_ORDERED,0,fcm2ParticipantList[idx].rakNetGuid,false);
}
}
if (ourFCMGuid==0)
{
AssignOurFCMGuid();
unsigned int idx;
for (idx=0; idx < fcm2ParticipantList.Size(); idx++)
SendOurFCMGuid(rakPeerInterface->GetSystemAddressFromGuid(fcm2ParticipantList[idx].rakNetGuid));
}
CalculateAndPushHost();
}
void FullyConnectedMesh2::OnUpdateMinTotalConnectionCount(Packet *packet)
{
RakNet::BitStream bsIn(packet->data,packet->length,false);
bsIn.IgnoreBytes(sizeof(MessageID));
unsigned int newMin;
bsIn.Read(newMin);
IncrementTotalConnectionCount(newMin);
}
void FullyConnectedMesh2::GetParticipantCount(unsigned int *participantListSize) const
{
*participantListSize=fcm2ParticipantList.Size();
}
unsigned int FullyConnectedMesh2::GetParticipantCount(void) const
{
return fcm2ParticipantList.Size();
}
void FullyConnectedMesh2::CalculateAndPushHost(void)
{
RakNetGUID newHostGuid;
FCM2Guid newFcmGuid;
if (ParticipantListComplete())
{
CalculateHost(&newHostGuid, &newFcmGuid);
if (newHostGuid!=lastPushedHost)
{
hostRakNetGuid=newHostGuid;
hostFCM2Guid=newFcmGuid;
PushNewHost(hostRakNetGuid, hostRakNetGuid);
}
}
}
bool FullyConnectedMesh2::ParticipantListComplete(void)
{
for (unsigned int i=0; i < fcm2ParticipantList.Size(); i++)
{
if (fcm2ParticipantList[i].fcm2Guid==0)
return false;
}
return true;
}
void FullyConnectedMesh2::IncrementTotalConnectionCount(unsigned int i)
{
if (i>totalConnectionCount)
{
totalConnectionCount=i;
// printf("totalConnectionCount=%i\n",i);
}
}
void FullyConnectedMesh2::SetConnectOnNewRemoteConnection(bool attemptConnection, RakNet::RakString pw)
{
connectOnNewRemoteConnections=attemptConnection;
connectionPassword=pw;
}
void FullyConnectedMesh2::ConnectToRemoteNewIncomingConnections(Packet *packet)
{
unsigned int count;
RakNet::BitStream bsIn(packet->data, packet->length, false);
bsIn.IgnoreBytes(sizeof(MessageID));
bsIn.Read(count);
SystemAddress remoteAddress;
RakNetGUID remoteGuid;
char str[64];
for (unsigned int i=0; i < count; i++)
{
bsIn.Read(remoteAddress);
bsIn.Read(remoteGuid);
remoteAddress.ToString(false,str);
rakPeerInterface->Connect(str,remoteAddress.GetPort(),connectionPassword.C_String(),(int) connectionPassword.GetLength());
}
}
unsigned int FullyConnectedMesh2::GetTotalConnectionCount(void) const
{
return totalConnectionCount;
}
#endif // _RAKNET_SUPPORT_*

View File

@@ -0,0 +1,253 @@
/// \file FullyConnectedMesh2.h
/// \brief Fully connected mesh plugin, revision 2.
/// \details This will connect RakPeer to all connecting peers, and all peers the connecting peer knows about.
///
/// This file is part of RakNet Copyright 2003 Jenkins Software LLC
///
/// Usage of RakNet is subject to the appropriate license agreement.
#include "NativeFeatureIncludes.hpp"
#if _RAKNET_SUPPORT_FullyConnectedMesh2==1
#ifndef __FULLY_CONNECTED_MESH_2_H
#define __FULLY_CONNECTED_MESH_2_H
#include "PluginInterface2.hpp"
#include "RakMemoryOverride.hpp"
#include "NativeTypes.hpp"
#include "DS_List.hpp"
#include "RakString.hpp"
typedef int64_t FCM2Guid;
namespace RakNet
{
/// Forward declarations
class RakPeerInterface;
/// \brief Fully connected mesh plugin, revision 2
/// \details This will connect RakPeer to all connecting peers, and all peers the connecting peer knows about.<BR>
/// It will also calculate which system has been running longest, to find out who should be host, if you need one system to act as a host
/// \pre You must also install the ConnectionGraph2 plugin in order to use SetConnectOnNewRemoteConnection()
/// \ingroup FULLY_CONNECTED_MESH_GROUP
class RAK_DLL_EXPORT FullyConnectedMesh2 : public PluginInterface2
{
public:
// GetInstance() and DestroyInstance(instance*)
STATIC_FACTORY_DECLARATIONS(FullyConnectedMesh2)
FullyConnectedMesh2();
virtual ~FullyConnectedMesh2();
/// When the message ID_REMOTE_NEW_INCOMING_CONNECTION arrives, we try to connect to that system
/// If \a attemptConnection is false, you can manually connect to all systems listed in ID_REMOTE_NEW_INCOMING_CONNECTION with ConnectToRemoteNewIncomingConnections()
/// \note This will not work on any console. It will also not work if NAT punchthrough is needed. Generally, this should be false and you should connect manually. It is here for legacy reasons.
/// \param[in] attemptConnection If true, we try to connect to any systems we are notified about with ID_REMOTE_NEW_INCOMING_CONNECTION, which comes from the ConnectionGraph2 plugin. Defaults to true.
/// \param[in] pw The password to use to connect with. Only used if \a attemptConnection is true
void SetConnectOnNewRemoteConnection(bool attemptConnection, RakNet::RakString pw);
/// \brief The connected host is whichever system we are connected to that has been running the longest.
/// \details Will return UNASSIGNED_RAKNET_GUID if we are not connected to anyone, or if we are connected and are calculating the host
/// If includeCalculating is true, will return the estimated calculated host as long as the calculation is nearly complete
/// includeCalculating should be true if you are taking action based on another system becoming host, because not all host calculations may complete at the exact same time
/// \sa ConnectionGraph2::GetLowestAveragePingSystem() . If you need one system in the peer to peer group to relay data, have the host call this function after host migration, and use that system
/// \return System address of whichever system is host.
RakNetGUID GetConnectedHost(void) const;
SystemAddress GetConnectedHostAddr(void) const;
/// \return System address of whichever system is host. Always returns something, even though it may be our own system.
RakNetGUID GetHostSystem(void) const;
/// \return If our system is host
bool IsHostSystem(void) const;
/// Get the list of connected systems, from oldest connected to newest
/// This is also the order that the hosts will be chosen in
void GetHostOrder(DataStructures::List<RakNetGUID> &hostList);
/// \param[in] includeCalculating If true, and we are currently calculating a new host, return the new host if the calculation is nearly complete
/// \return If our system is host
bool IsConnectedHost(void) const;
/// \brief Automatically add new connections to the fully connected mesh.
/// Each remote system that you want to check should be added as a participant, either through SetAutoparticipateConnections() or by calling this function
/// \details Defaults to true.
/// \param[in] b As stated
void SetAutoparticipateConnections(bool b);
/// Clear our own host order, and recalculate as if we had just reconnected
/// Call this to reset the running time of the host just before joining/creating a game room for networking
void ResetHostCalculation(void);
/// \brief if SetAutoparticipateConnections() is called with false, then you need to use AddParticipant before these systems will be added to the mesh
/// FullyConnectedMesh2 will track who is the who host among a fully connected mesh of participants
/// Each remote system that you want to check should be added as a participant, either through SetAutoparticipateConnections() or by calling this function
/// \param[in] participant The new participant
void AddParticipant(RakNetGUID rakNetGuid);
/// Get the participants added with AddParticipant()
/// \param[out] participantList Participants added with AddParticipant();
void GetParticipantList(DataStructures::List<RakNetGUID> &participantList);
/// Connect to all systems from ID_REMOTE_NEW_INCOMING_CONNECTION
/// You can call this if SetConnectOnNewRemoteConnection is false
/// \param[in] packet The packet containing ID_REMOTE_NEW_INCOMING_CONNECTION
/// \param[in] connectionPassword Password passed to RakPeerInterface::Connect()
/// \param[in] connectionPasswordLength Password length passed to RakPeerInterface::Connect()
void ConnectToRemoteNewIncomingConnections(Packet *packet);
/// \brief Clear all memory and reset everything
void Clear(void);
unsigned int GetParticipantCount(void) const;
void GetParticipantCount(unsigned int *participantListSize) const;
/// \internal
RakNet::TimeUS GetElapsedRuntime(void);
/// \internal
virtual PluginReceiveResult OnReceive(Packet *packet);
/// \internal
virtual void OnRakPeerStartup(void);
/// \internal
virtual void OnAttach(void);
/// \internal
virtual void OnRakPeerShutdown(void);
/// \internal
virtual void OnClosedConnection(const SystemAddress &systemAddress, RakNetGUID rakNetGUID, PI2_LostConnectionReason lostConnectionReason );
/// \internal
virtual void OnNewConnection(const SystemAddress &systemAddress, RakNetGUID rakNetGUID, bool isIncoming);
/// \internal
struct FCM2Participant
{
FCM2Participant() {}
FCM2Participant(const FCM2Guid &_fcm2Guid, const RakNetGUID &_rakNetGuid) : fcm2Guid(_fcm2Guid), rakNetGuid(_rakNetGuid) {}
// Low half is a random number.
// High half is the order we connected in (totalConnectionCount)
FCM2Guid fcm2Guid;
RakNetGUID rakNetGuid;
};
/// \internal for debugging
unsigned int GetTotalConnectionCount(void) const;
protected:
void PushNewHost(const RakNetGUID &guid, RakNetGUID oldHost);
void SendOurFCMGuid(SystemAddress addr);
void SendFCMGuidRequest(RakNetGUID rakNetGuid);
void SendConnectionCountResponse(SystemAddress addr, unsigned int responseTotalConnectionCount);
void OnRequestFCMGuid(Packet *packet);
void OnRespondConnectionCount(Packet *packet);
void OnInformFCMGuid(Packet *packet);
void OnUpdateMinTotalConnectionCount(Packet *packet);
void AssignOurFCMGuid(void);
void CalculateHost(RakNetGUID *rakNetGuid, FCM2Guid *fcm2Guid);
bool AddParticipantInternal( RakNetGUID rakNetGuid, FCM2Guid theirFCMGuid );
void CalculateAndPushHost(void);
bool ParticipantListComplete(void);
void IncrementTotalConnectionCount(unsigned int i);
// Used to track how long RakNet has been running. This is so we know who has been running longest
RakNet::TimeUS startupTime;
// Option for SetAutoparticipateConnections
bool autoParticipateConnections;
// totalConnectionCount is roughly maintained across all systems, and increments by 1 each time a new system connects to the mesh
// It is always kept at the highest known value
// It is used as the high 4 bytes for new FCMGuids. This causes newer values of FCM2Guid to be higher than lower values. The lowest value is the host.
unsigned int totalConnectionCount;
// Our own ourFCMGuid. Starts at unassigned (0). Assigned once we send ID_FCM2_REQUEST_FCMGUID and get back ID_FCM2_RESPOND_CONNECTION_COUNT
FCM2Guid ourFCMGuid;
/// List of systems we know the FCM2Guid for
DataStructures::List<FCM2Participant> fcm2ParticipantList;
RakNetGUID lastPushedHost;
// Optimization: Store last calculated host in these variables.
RakNetGUID hostRakNetGuid;
FCM2Guid hostFCM2Guid;
RakNet::RakString connectionPassword;
bool connectOnNewRemoteConnections;
};
} // namespace RakNet
/*
Startup()
ourFCMGuid=unknown
totalConnectionCount=0
Set startupTime
AddParticipant()
if (sender by guid is a participant)
return;
AddParticipantInternal(guid);
if (ourFCMGuid==unknown)
Send to that system a request for their fcmGuid, totalConnectionCount. Inform startupTime.
else
Send to that system a request for their fcmGuid. Inform total connection count, our fcmGuid
OnRequestGuid()
if (sender by guid is not a participant)
{
// They added us as a participant, but we didn't add them. This can be caused by lag where both participants are not added at the same time.
// It doesn't affect the outcome as long as we still process the data
AddParticipantInternal(guid);
}
if (ourFCMGuid==unknown)
{
if (includedStartupTime)
{
// Nobody has a fcmGuid
if (their startup time is greater than our startup time)
ReplyConnectionCount(1);
else
ReplyConnectionCount(2);
}
else
{
// They have a fcmGuid, we do not
SetMaxTotalConnectionCount(remoteCount);
AssignTheirGuid()
GenerateOurGuid();
SendOurGuid(all);
}
}
else
{
if (includedStartupTime)
{
// We have a fcmGuid they do not
ReplyConnectionCount(totalConnectionCount+1);
SendOurGuid(sender);
}
else
{
// We both have fcmGuids
SetMaxTotalConnectionCount(remoteCount);
AssignTheirGuid();
SendOurGuid(sender);
}
}
OnReplyConnectionCount()
SetMaxTotalConnectionCount(remoteCount);
GenerateOurGuid();
SendOurGuid(allParticipants);
OnReceiveTheirGuid()
AssignTheirGuid()
*/
#endif
#endif // _RAKNET_SUPPORT_*

View File

@@ -0,0 +1,220 @@
/// \file
///
/// This file is part of RakNet Copyright 2003 Jenkins Software LLC
///
/// Usage of RakNet is subject to the appropriate license agreement.
#if defined(_WIN32)
#include "WindowsIncludes.hpp"
// To call timeGetTime
// on Code::Blocks, this needs to be libwinmm.a instead
// #pragma comment(lib, "Winmm.lib") LINK AGAINST WINMM IN PROJECT SETTINGS!
#endif
#include "GetTime.hpp"
#if defined(_WIN32)
DWORD mProcMask;
DWORD mSysMask;
HANDLE mThread;
#else
#include <sys/time.h>
#include <unistd.h>
RakNet::TimeUS initialTime;
#endif
static bool initialized=false;
#if defined(GET_TIME_SPIKE_LIMIT) && GET_TIME_SPIKE_LIMIT>0
#include "SimpleMutex.hpp"
RakNet::TimeUS lastNormalizedReturnedValue=0;
RakNet::TimeUS lastNormalizedInputValue=0;
/// This constraints timer forward jumps to 1 second, and does not let it jump backwards
/// See http://support.microsoft.com/kb/274323 where the timer can sometimes jump forward by hours or days
/// This also has the effect where debugging a sending system won't treat the time spent halted past 1 second as elapsed network time
RakNet::TimeUS NormalizeTime(RakNet::TimeUS timeIn)
{
RakNet::TimeUS diff, lastNormalizedReturnedValueCopy;
static RakNet::SimpleMutex mutex;
mutex.Lock();
if (timeIn>=lastNormalizedInputValue)
{
diff = timeIn-lastNormalizedInputValue;
if (diff > GET_TIME_SPIKE_LIMIT)
lastNormalizedReturnedValue+=GET_TIME_SPIKE_LIMIT;
else
lastNormalizedReturnedValue+=diff;
}
else
lastNormalizedReturnedValue+=GET_TIME_SPIKE_LIMIT;
lastNormalizedInputValue=timeIn;
lastNormalizedReturnedValueCopy=lastNormalizedReturnedValue;
mutex.Unlock();
return lastNormalizedReturnedValueCopy;
}
#endif // #if defined(GET_TIME_SPIKE_LIMIT) && GET_TIME_SPIKE_LIMIT>0
RakNet::Time RakNet::GetTime( void )
{
return (RakNet::Time)(GetTimeUS()/1000);
}
RakNet::TimeMS RakNet::GetTimeMS( void )
{
return (RakNet::TimeMS)(GetTimeUS()/1000);
}
#if defined(_WIN32)
RakNet::TimeUS GetTimeUS_Windows( void )
{
if ( initialized == false)
{
initialized = true;
// Save the current process
#if !defined(_WIN32_WCE)
HANDLE mProc = GetCurrentProcess();
// Get the current Affinity
#if _MSC_VER >= 1400 && (defined (_M_X64) || defined(_M_ARM64))
GetProcessAffinityMask(mProc, (PDWORD_PTR)&mProcMask, (PDWORD_PTR)&mSysMask);
#else
GetProcessAffinityMask(mProc, &mProcMask, &mSysMask);
#endif
mThread = GetCurrentThread();
#endif // _WIN32_WCE
}
// 9/26/2010 In China running LuDaShi, QueryPerformanceFrequency has to be called every time because CPU clock speeds can be different
RakNet::TimeUS curTime;
LARGE_INTEGER PerfVal;
LARGE_INTEGER yo1;
QueryPerformanceFrequency( &yo1 );
QueryPerformanceCounter( &PerfVal );
__int64 quotient, remainder;
quotient=((PerfVal.QuadPart) / yo1.QuadPart);
remainder=((PerfVal.QuadPart) % yo1.QuadPart);
curTime = (RakNet::TimeUS) quotient*(RakNet::TimeUS)1000000 + (remainder*(RakNet::TimeUS)1000000 / yo1.QuadPart);
#if defined(GET_TIME_SPIKE_LIMIT) && GET_TIME_SPIKE_LIMIT>0
return NormalizeTime(curTime);
#else
return curTime;
#endif // #if defined(GET_TIME_SPIKE_LIMIT) && GET_TIME_SPIKE_LIMIT>0
}
#elif defined(__GNUC__) || defined(__GCCXML__) || defined(__S3E__)
RakNet::TimeUS GetTimeUS_Linux( void )
{
timeval tp;
if ( initialized == false)
{
gettimeofday( &tp, 0 );
initialized=true;
// I do this because otherwise RakNet::Time in milliseconds won't work as it will underflow when dividing by 1000 to do the conversion
initialTime = ( tp.tv_sec ) * (RakNet::TimeUS) 1000000 + ( tp.tv_usec );
}
// GCC
RakNet::TimeUS curTime;
gettimeofday( &tp, 0 );
curTime = ( tp.tv_sec ) * (RakNet::TimeUS) 1000000 + ( tp.tv_usec );
#if defined(GET_TIME_SPIKE_LIMIT) && GET_TIME_SPIKE_LIMIT>0
return NormalizeTime(curTime - initialTime);
#else
return curTime - initialTime;
#endif // #if defined(GET_TIME_SPIKE_LIMIT) && GET_TIME_SPIKE_LIMIT>0
}
#endif
RakNet::TimeUS RakNet::GetTimeUS( void )
{
#if defined(_WIN32)
return GetTimeUS_Windows();
#else
return GetTimeUS_Linux();
#endif
}
bool RakNet::GreaterThan(RakNet::Time a, RakNet::Time b)
{
// a > b?
const RakNet::Time halfSpan =(RakNet::Time) (((RakNet::Time)(const RakNet::Time)-1)/(RakNet::Time)2);
return b!=a && b-a>halfSpan;
}
bool RakNet::LessThan(RakNet::Time a, RakNet::Time b)
{
// a < b?
const RakNet::Time halfSpan = ((RakNet::Time)(const RakNet::Time)-1)/(RakNet::Time)2;
return b!=a && b-a<halfSpan;
}

View File

@@ -0,0 +1,35 @@
/// \file GetTime.h
/// \brief Returns the value from QueryPerformanceCounter. This is the function RakNet uses to represent time. This time won't match the time returned by GetTimeCount(). See http://www.jenkinssoftware.com/forum/index.php?topic=2798.0
///
/// This file is part of RakNet Copyright 2003 Jenkins Software LLC
///
/// Usage of RakNet is subject to the appropriate license agreement.
#ifndef __GET_TIME_H
#define __GET_TIME_H
#include "Export.hpp"
#include "RakNetTime.hpp" // For RakNet::TimeMS
namespace RakNet
{
/// Same as GetTimeMS
/// Holds the time in either a 32 or 64 bit variable, depending on __GET_TIME_64BIT
RakNet::Time RAK_DLL_EXPORT GetTime( void );
/// Return the time as 32 bit
/// \note The maximum delta between returned calls is 1 second - however, RakNet calls this constantly anyway. See NormalizeTime() in the cpp.
RakNet::TimeMS RAK_DLL_EXPORT GetTimeMS( void );
/// Return the time as 64 bit
/// \note The maximum delta between returned calls is 1 second - however, RakNet calls this constantly anyway. See NormalizeTime() in the cpp.
RakNet::TimeUS RAK_DLL_EXPORT GetTimeUS( void );
/// a > b?
extern RAK_DLL_EXPORT bool GreaterThan(RakNet::Time a, RakNet::Time b);
/// a < b?
extern RAK_DLL_EXPORT bool LessThan(RakNet::Time a, RakNet::Time b);
}
#endif

View File

@@ -0,0 +1,27 @@
#if defined(_WIN32)
#include <conio.h> /* getche() */
#elif defined(__S3E__)
#else
#include "Getche.hpp"
char getche()
{
struct termios oldt,
newt;
char ch;
tcgetattr( STDIN_FILENO, &oldt );
newt = oldt;
newt.c_lflag &= ~( ICANON | ECHO );
tcsetattr( STDIN_FILENO, TCSANOW, &newt );
ch = getchar();
tcsetattr( STDIN_FILENO, TCSANOW, &oldt );
return ch;
}
#endif

View File

@@ -0,0 +1,11 @@
#if defined(_WIN32)
#include <conio.h> /* getche() */
#else
#include <termios.h>
#include <stdio.h>
#include <unistd.h>
char getche();
#endif

25
third-party/RakNet/src/RakNet/Gets.cpp vendored Normal file
View File

@@ -0,0 +1,25 @@
#include <stdio.h>
#include <string.h>
#ifdef __cplusplus
extern "C" {
#endif
char * Gets ( char * str, int num )
{
fgets(str, num, stdin);
if (str[0]=='\n' || str[0]=='\r')
str[0]=0;
size_t len=strlen(str);
if (len>0 && (str[len-1]=='\n' || str[len-1]=='\r'))
str[len-1]=0;
if (len>1 && (str[len-2]=='\n' || str[len-2]=='\r'))
str[len-2]=0;
return str;
}
#ifdef __cplusplus
}
#endif

14
third-party/RakNet/src/RakNet/Gets.hpp vendored Normal file
View File

@@ -0,0 +1,14 @@
#ifndef __GETS__H_
#define __GETS__H_
#ifdef __cplusplus
extern "C" {
#endif
char * Gets ( char * str, int num );
#ifdef __cplusplus
}
#endif
#endif

View File

@@ -0,0 +1,191 @@
#include "RakAssert.hpp"
#include "GridSectorizer.hpp"
//#include <stdlib.h>
#include <math.h>
GridSectorizer::GridSectorizer()
{
grid=0;
}
GridSectorizer::~GridSectorizer()
{
if (grid)
RakNet::OP_DELETE_ARRAY(grid, _FILE_AND_LINE_);
}
void GridSectorizer::Init(const float _maxCellWidth, const float _maxCellHeight, const float minX, const float minY, const float maxX, const float maxY)
{
RakAssert(_maxCellWidth > 0.0f && _maxCellHeight > 0.0f);
if (grid)
RakNet::OP_DELETE_ARRAY(grid, _FILE_AND_LINE_);
cellOriginX=minX;
cellOriginY=minY;
gridWidth=maxX-minX;
gridHeight=maxY-minY;
gridCellWidthCount=(int) ceil(gridWidth/_maxCellWidth);
gridCellHeightCount=(int) ceil(gridHeight/_maxCellHeight);
// Make the cells slightly smaller, so we allocate an extra unneeded cell if on the edge. This way we don't go outside the array on rounding errors.
cellWidth=gridWidth/gridCellWidthCount;
cellHeight=gridHeight/gridCellHeightCount;
invCellWidth = 1.0f / cellWidth;
invCellHeight = 1.0f / cellHeight;
#ifdef _USE_ORDERED_LIST
grid = RakNet::OP_NEW<DataStructures::OrderedList<void*, void*>>(gridCellWidthCount*gridCellHeightCount, _FILE_AND_LINE_ );
DataStructures::OrderedList<void*,void*>::IMPLEMENT_DEFAULT_COMPARISON();
#else
grid = RakNet::OP_NEW_ARRAY<DataStructures::List<void*> >(gridCellWidthCount*gridCellHeightCount, _FILE_AND_LINE_ );
#endif
}
void GridSectorizer::AddEntry(void *entry, const float minX, const float minY, const float maxX, const float maxY)
{
RakAssert(cellWidth>0.0f);
RakAssert(minX < maxX && minY < maxY);
int xStart, yStart, xEnd, yEnd, xCur, yCur;
xStart=WorldToCellXOffsetAndClamped(minX);
yStart=WorldToCellYOffsetAndClamped(minY);
xEnd=WorldToCellXOffsetAndClamped(maxX);
yEnd=WorldToCellYOffsetAndClamped(maxY);
for (xCur=xStart; xCur <= xEnd; ++xCur)
{
for (yCur=yStart; yCur <= yEnd; ++yCur)
{
#ifdef _USE_ORDERED_LIST
grid[yCur*gridCellWidthCount+xCur].Insert(entry,entry, true);
#else
grid[yCur*gridCellWidthCount+xCur].Insert(entry, _FILE_AND_LINE_);
#endif
}
}
}
#ifdef _USE_ORDERED_LIST
void GridSectorizer::RemoveEntry(void *entry, const float minX, const float minY, const float maxX, const float maxY)
{
RakAssert(cellWidth>0.0f);
RakAssert(minX <= maxX && minY <= maxY);
int xStart, yStart, xEnd, yEnd, xCur, yCur;
xStart=WorldToCellXOffsetAndClamped(minX);
yStart=WorldToCellYOffsetAndClamped(minY);
xEnd=WorldToCellXOffsetAndClamped(maxX);
yEnd=WorldToCellYOffsetAndClamped(maxY);
for (xCur=xStart; xCur <= xEnd; ++xCur)
{
for (yCur=yStart; yCur <= yEnd; ++yCur)
{
grid[yCur*gridCellWidthCount+xCur].RemoveIfExists(entry);
}
}
}
void GridSectorizer::MoveEntry(void *entry, const float sourceMinX, const float sourceMinY, const float sourceMaxX, const float sourceMaxY,
const float destMinX, const float destMinY, const float destMaxX, const float destMaxY)
{
RakAssert(cellWidth>0.0f);
RakAssert(sourceMinX < sourceMaxX && sourceMinY < sourceMaxY);
RakAssert(destMinX < destMaxX && destMinY < destMaxY);
if (PositionCrossesCells(sourceMinX, sourceMinY, destMinX, destMinY)==false &&
PositionCrossesCells(destMinX, destMinY, destMinX, destMinY)==false)
return;
int xStartSource, yStartSource, xEndSource, yEndSource;
int xStartDest, yStartDest, xEndDest, yEndDest;
int xCur, yCur;
xStartSource=WorldToCellXOffsetAndClamped(sourceMinX);
yStartSource=WorldToCellYOffsetAndClamped(sourceMinY);
xEndSource=WorldToCellXOffsetAndClamped(sourceMaxX);
yEndSource=WorldToCellYOffsetAndClamped(sourceMaxY);
xStartDest=WorldToCellXOffsetAndClamped(destMinX);
yStartDest=WorldToCellYOffsetAndClamped(destMinY);
xEndDest=WorldToCellXOffsetAndClamped(destMaxX);
yEndDest=WorldToCellYOffsetAndClamped(destMaxY);
// Remove source that is not in dest
for (xCur=xStartSource; xCur <= xEndSource; ++xCur)
{
for (yCur=yStartSource; yCur <= yEndSource; ++yCur)
{
if (xCur < xStartDest || xCur > xEndDest ||
yCur < yStartDest || yCur > yEndDest)
{
grid[yCur*gridCellWidthCount+xCur].RemoveIfExists(entry);
}
}
}
// Add dest that is not in source
for (xCur=xStartDest; xCur <= xEndDest; ++xCur)
{
for (yCur=yStartDest; yCur <= yEndDest; ++yCur)
{
if (xCur < xStartSource || xCur > xEndSource ||
yCur < yStartSource || yCur > yEndSource)
{
grid[yCur*gridCellWidthCount+xCur].Insert(entry,entry, true);
}
}
}
}
#endif
void GridSectorizer::GetEntries(DataStructures::List<void*>& intersectionList, const float minX, const float minY, const float maxX, const float maxY)
{
#ifdef _USE_ORDERED_LIST
DataStructures::OrderedList<void*, void*>* cell;
#else
DataStructures::List<void*>* cell;
#endif
int xStart, yStart, xEnd, yEnd, xCur, yCur;
unsigned index;
xStart=WorldToCellXOffsetAndClamped(minX);
yStart=WorldToCellYOffsetAndClamped(minY);
xEnd=WorldToCellXOffsetAndClamped(maxX);
yEnd=WorldToCellYOffsetAndClamped(maxY);
intersectionList.Clear(true, _FILE_AND_LINE_);
for (xCur=xStart; xCur <= xEnd; ++xCur)
{
for (yCur=yStart; yCur <= yEnd; ++yCur)
{
cell = grid+yCur*gridCellWidthCount+xCur;
for (index=0; index < cell->Size(); ++index)
intersectionList.Insert(cell->operator [](index), _FILE_AND_LINE_);
}
}
}
bool GridSectorizer::PositionCrossesCells(const float originX, const float originY, const float destinationX, const float destinationY) const
{
return originX/cellWidth!=destinationX/cellWidth || originY/cellHeight!=destinationY/cellHeight;
}
int GridSectorizer::WorldToCellX(const float input) const
{
return (int)((input-cellOriginX)*invCellWidth);
}
int GridSectorizer::WorldToCellY(const float input) const
{
return (int)((input-cellOriginY)*invCellHeight);
}
int GridSectorizer::WorldToCellXOffsetAndClamped(const float input) const
{
int cell=WorldToCellX(input);
cell = cell > 0 ? cell : 0; // __max(cell,0);
cell = gridCellWidthCount-1 < cell ? gridCellWidthCount-1 : cell; // __min(gridCellWidthCount-1, cell);
return cell;
}
int GridSectorizer::WorldToCellYOffsetAndClamped(const float input) const
{
int cell=WorldToCellY(input);
cell = cell > 0 ? cell : 0; // __max(cell,0);
cell = gridCellHeightCount-1 < cell ? gridCellHeightCount-1 : cell; // __min(gridCellHeightCount-1, cell);
return cell;
}
void GridSectorizer::Clear(void)
{
int cur;
int count = gridCellWidthCount*gridCellHeightCount;
for (cur=0; cur<count;cur++)
grid[cur].Clear(true, _FILE_AND_LINE_);
}

View File

@@ -0,0 +1,68 @@
#ifndef _GRID_SECTORIZER_H
#define _GRID_SECTORIZER_H
//#define _USE_ORDERED_LIST
#include "RakMemoryOverride.hpp"
#ifdef _USE_ORDERED_LIST
#include "DS_OrderedList.hpp"
#else
#include "DS_List.hpp"
#endif
class GridSectorizer
{
public:
GridSectorizer();
~GridSectorizer();
// _cellWidth, _cellHeight is the width and height of each cell in world units
// minX, minY, maxX, maxY are the world dimensions (can be changed to dynamically allocate later if needed)
void Init(const float _maxCellWidth, const float _maxCellHeight, const float minX, const float minY, const float maxX, const float maxY);
// Adds a pointer to the grid with bounding rectangle dimensions
void AddEntry(void *entry, const float minX, const float minY, const float maxX, const float maxY);
#ifdef _USE_ORDERED_LIST
// Removes a pointer, as above
void RemoveEntry(void *entry, const float minX, const float minY, const float maxX, const float maxY);
// Adds and removes in one pass, more efficient than calling both functions consecutively
void MoveEntry(void *entry, const float sourceMinX, const float sourceMinY, const float sourceMaxX, const float sourceMaxY,
const float destMinX, const float destMinY, const float destMaxX, const float destMaxY);
#endif
// Adds to intersectionList all entries in a certain radius
void GetEntries(DataStructures::List<void*>& intersectionList, const float minX, const float minY, const float maxX, const float maxY);
void Clear(void);
protected:
int WorldToCellX(const float input) const;
int WorldToCellY(const float input) const;
int WorldToCellXOffsetAndClamped(const float input) const;
int WorldToCellYOffsetAndClamped(const float input) const;
// Returns true or false if a position crosses cells in the grid. If false, you don't need to move entries
bool PositionCrossesCells(const float originX, const float originY, const float destinationX, const float destinationY) const;
float cellOriginX, cellOriginY;
float cellWidth, cellHeight;
float invCellWidth, invCellHeight;
float gridWidth, gridHeight;
int gridCellWidthCount, gridCellHeightCount;
// int gridWidth, gridHeight;
#ifdef _USE_ORDERED_LIST
DataStructures::OrderedList<void*, void*>* grid;
#else
DataStructures::List<void*>* grid;
#endif
};
#endif

View File

@@ -0,0 +1,313 @@
/// \file
/// \brief Contains HTTPConnection, used to communicate with web servers
///
/// This file is part of RakNet Copyright 2008 Kevin Jenkins.
///
/// Usage of RakNet is subject to the appropriate license agreement.
/// Creative Commons Licensees are subject to the
/// license found at
/// http://creativecommons.org/licenses/by-nc/2.5/
/// Single application licensees are subject to the license found at
/// http://www.jenkinssoftware.com/SingleApplicationLicense.html
/// Custom license users are subject to the terms therein.
/// GPL license users are subject to the GNU General Public
/// License as published by the Free
/// Software Foundation; either version 2 of the License, or (at your
/// option) any later version.
#include "NativeFeatureIncludes.hpp"
#if _RAKNET_SUPPORT_HTTPConnection==1 && _RAKNET_SUPPORT_TCPInterface==1
#include "TCPInterface.hpp"
#include "HTTPConnection.hpp"
#include "RakSleep.hpp"
#include "RakString.hpp"
#include "RakAssert.hpp"
#include <string.h>
#include <stdlib.h>
#include <stdio.h>
#include <stdlib.h>
using namespace RakNet;
STATIC_FACTORY_DEFINITIONS(HTTPConnection,HTTPConnection);
HTTPConnection::HTTPConnection() : connectionState(CS_NONE)
{
tcp=0;
}
void HTTPConnection::Init(TCPInterface* _tcp, const char *_host, unsigned short _port)
{
tcp=_tcp;
host=_host;
port=_port;
}
void HTTPConnection::Post(const char *remote_path, const char *data, const char *_contentType)
{
OutgoingCommand op;
op.contentType=_contentType;
op.data=data;
op.remotePath=remote_path;
op.isPost=true;
outgoingCommand.Push(op, _FILE_AND_LINE_ );
//printf("Adding outgoing post\n");
}
void HTTPConnection::Get(const char *path)
{
OutgoingCommand op;
op.remotePath=path;
op.isPost=false;
outgoingCommand.Push(op, _FILE_AND_LINE_ );
}
bool HTTPConnection::HasBadResponse(int *code, RakNet::RakString *data)
{
if(badResponses.IsEmpty())
return false;
if (code)
*code = badResponses.Peek().code;
if (data)
*data = badResponses.Pop().data;
return true;
}
void HTTPConnection::CloseConnection()
{
connectionState=CS_DISCONNECTING;
}
void HTTPConnection::Update(void)
{
SystemAddress sa;
sa = tcp->HasCompletedConnectionAttempt();
while (sa!=UNASSIGNED_SYSTEM_ADDRESS)
{
// printf("Connected\n");
connectionState=CS_CONNECTED;
server=sa;
sa = tcp->HasCompletedConnectionAttempt();
}
sa = tcp->HasFailedConnectionAttempt();
while (sa!=UNASSIGNED_SYSTEM_ADDRESS)
{
//printf("Failed connected\n");
CloseConnection();
sa = tcp->HasFailedConnectionAttempt();
}
sa = tcp->HasLostConnection();
while (sa!=UNASSIGNED_SYSTEM_ADDRESS)
{
//printf("Lost connection\n");
CloseConnection();
sa = tcp->HasLostConnection();
}
switch (connectionState)
{
case CS_NONE:
{
if (outgoingCommand.IsEmpty())
return;
//printf("Connecting\n");
server = tcp->Connect(host, port, false);
connectionState = CS_CONNECTING;
}
break;
case CS_DISCONNECTING:
{
if (tcp->ReceiveHasPackets()==false)
{
if (incomingData.IsEmpty()==false)
{
results.Push(incomingData, _FILE_AND_LINE_ );
}
incomingData.Clear();
tcp->CloseConnection(server);
connectionState=CS_NONE;
}
}
break;
case CS_CONNECTING:
{
}
break;
case CS_CONNECTED:
{
//printf("Connected\n");
if (outgoingCommand.IsEmpty())
{
//printf("Closed connection (nothing to do)\n");
CloseConnection();
return;
}
#if OPEN_SSL_CLIENT_SUPPORT==1
tcp->StartSSLClient(server);
#endif
//printf("Sending request\n");
currentProcessingCommand = outgoingCommand.Pop();
RakString request;
if (currentProcessingCommand.isPost)
{
request.Set("POST %s HTTP/1.0\r\n"
"Host: %s:%i\r\n"
"Content-Type: %s\r\n"
"Content-Length: %u\r\n"
"\r\n"
"%s",
currentProcessingCommand.remotePath.C_String(),
host.C_String(),
port,
currentProcessingCommand.contentType.C_String(),
(unsigned) currentProcessingCommand.data.GetLength(),
currentProcessingCommand.data.C_String());
}
else
{
// request.Set("GET %s\r\n", host.C_String());
// http://www.jenkinssoftware.com/forum/index.php?topic=4601.0;topicseen
request.Set("GET %s HTTP/1.0\r\n"
"Host: %s:%i\r\n"
"\r\n",
currentProcessingCommand.remotePath.C_String(),
host.C_String(),
port);
}
// printf(request.C_String());
// request.URLEncode();
tcp->Send(request.C_String(), (unsigned int) request.GetLength(), server,false);
connectionState=CS_PROCESSING;
}
break;
case CS_PROCESSING:
{
}
}
// if (connectionState==CS_PROCESSING && currentProcessingCommand.data.IsEmpty()==false)
// outgoingCommand.PushAtHead(currentProcessingCommand);
}
bool HTTPConnection::HasRead(void) const
{
return results.IsEmpty()==false;
}
RakString HTTPConnection::Read(void)
{
if (results.IsEmpty())
return RakString();
RakNet::RakString resultStr = results.Pop();
// const char *start_of_body = strstr(resultStr.C_String(), "\r\n\r\n");
const char *start_of_body = strpbrk(resultStr.C_String(), "\001\002\003%");
if(start_of_body)
return RakNet::RakString::NonVariadic(start_of_body);
else
return resultStr;
}
SystemAddress HTTPConnection::GetServerAddress(void) const
{
return server;
}
void HTTPConnection::ProcessTCPPacket(Packet *packet)
{
RakAssert(packet);
// read all the packets possible
if(packet->systemAddress == server)
{
if(incomingData.GetLength() == 0)
{
int response_code = atoi((char *)packet->data + strlen("HTTP/1.0 "));
if(response_code > 299)
{
badResponses.Push(BadResponse(packet->data, response_code), _FILE_AND_LINE_ );
//printf("Closed connection (Bad response 2)\n");
CloseConnection();
return;
}
}
RakNet::RakString incomingTemp = RakNet::RakString::NonVariadic((const char*) packet->data);
incomingTemp.URLDecode();
incomingData += incomingTemp;
// printf((const char*) packet->data);
// printf("\n");
RakAssert(strlen((char *)packet->data) == packet->length); // otherwise it contains Null bytes
const char *start_of_body = strstr(incomingData, "\r\n\r\n");
// besides having the server close the connection, they may
// provide a length header and supply that many bytes
if(
// Why was start_of_body here? Makes the GET command fail
// start_of_body &&
connectionState == CS_PROCESSING)
{
/*
// The stupid programmer that wrote this originally didn't think that just because the header contains this value doesn't mean you got the whole message
if (strstr((const char*) packet->data, "\r\nConnection: close\r\n"))
{
CloseConnection();
}
else
{
*/
long length_of_headers;
if (start_of_body)
{
length_of_headers = (long)(start_of_body + 4 - incomingData.C_String());
const char *length_header = strstr(incomingData, "\r\nLength: ");
if(length_header)
{
long length = atol(length_header + 10) + length_of_headers;
if((long) incomingData.GetLength() >= length)
{
//printf("Closed connection (Got all data due to length header)\n");
CloseConnection();
}
}
}
else
{
// No processing needed
}
//}
}
}
}
bool HTTPConnection::IsBusy(void) const
{
return connectionState != CS_NONE;
}
int HTTPConnection::GetState(void) const
{
return connectionState;
}
HTTPConnection::~HTTPConnection(void)
{
if (tcp)
tcp->CloseConnection(server);
}
#endif // _RAKNET_SUPPORT_*

View File

@@ -0,0 +1,176 @@
/// \file HTTPConnection.h
/// \brief Contains HTTPConnection, used to communicate with web servers
///
/// This file is part of RakNet Copyright 2008 Kevin Jenkins.
///
/// Usage of RakNet is subject to the appropriate license agreement.
/// Creative Commons Licensees are subject to the
/// license found at
/// http://creativecommons.org/licenses/by-nc/2.5/
/// Single application licensees are subject to the license found at
/// http://www.jenkinssoftware.com/SingleApplicationLicense.html
/// Custom license users are subject to the terms therein.
/// GPL license users are subject to the GNU General Public
/// License as published by the Free
/// Software Foundation; either version 2 of the License, or (at your
/// option) any later version.
#include "NativeFeatureIncludes.hpp"
#if _RAKNET_SUPPORT_HTTPConnection==1 && _RAKNET_SUPPORT_TCPInterface==1
#ifndef __HTTP_CONNECTION
#define __HTTP_CONNECTION
#include "Export.hpp"
#include "RakString.hpp"
#include "RakMemoryOverride.hpp"
#include "RakNetTypes.hpp"
#include "DS_Queue.hpp"
namespace RakNet
{
/// Forward declarations
class TCPInterface;
struct SystemAddress;
/// \brief Use HTTPConnection to communicate with a web server.
/// \details Start an instance of TCPInterface via the Start() command.
/// Instantiate a new instance of HTTPConnection, and associate TCPInterface with the class in the constructor.
/// Use Post() to send commands to the web server, and ProcessDataPacket() to update the connection with packets returned from TCPInterface that have the system address of the web server
/// This class will handle connecting and reconnecting as necessary.
///
/// Note that only one Post() can be handled at a time.
class RAK_DLL_EXPORT HTTPConnection
{
public:
// GetInstance() and DestroyInstance(instance*)
STATIC_FACTORY_DECLARATIONS(HTTPConnection)
/// Returns a HTTP object associated with this tcp connection
HTTPConnection();
virtual ~HTTPConnection();
/// \pre tcp should already be started
void Init(TCPInterface *_tcp, const char *host, unsigned short port=80);
/// Submit data to the HTTP server
/// HTTP only allows one request at a time per connection
///
/// \pre IsBusy()==false
/// \param path the path on the remote server you want to POST to. For example "index.html"
/// \param data A NULL terminated string to submit to the server
/// \param contentType "Content-Type:" passed to post.
void Post(const char *path, const char *data, const char *_contentType="application/x-www-form-urlencoded");
/// Get a file from a webserver
/// \param path the path on the remote server you want to GET from. For example "index.html"
void Get(const char *path);
/// Is there a Read result ready?
bool HasRead(void) const;
/// Get one result from the server
/// \pre HasResult must return true
RakNet::RakString Read(void);
/// Call periodically to do time-based updates
void Update(void);
/// Returns the address of the server we are connected to
SystemAddress GetServerAddress(void) const;
/// Process an HTTP data packet returned from TCPInterface
/// Returns true when we have gotten all the data from the HTTP server.
/// If this returns true then it's safe to Post() another request
/// Deallocate the packet as usual via TCPInterface
/// \param packet NULL or a packet associated with our host and port
void ProcessTCPPacket(Packet *packet);
/// Results of HTTP requests. Standard response codes are < 999
/// ( define HTTP codes and our internal codes as needed )
enum ResponseCodes { NoBody=1001, OK=200, Deleted=1002 };
HTTPConnection& operator=(const HTTPConnection& rhs){(void) rhs; return *this;}
/// Encapsulates a raw HTTP response and response code
struct BadResponse
{
public:
BadResponse() {code=0;}
BadResponse(const unsigned char *_data, int _code)
: data((const char *)_data), code(_code) {}
BadResponse(const char *_data, int _code)
: data(_data), code(_code) {}
operator int () const { return code; }
RakNet::RakString data;
int code; // ResponseCodes
};
/// Queued events of failed exchanges with the HTTP server
bool HasBadResponse(int *code, RakNet::RakString *data);
/// Returns false if the connection is not doing anything else
bool IsBusy(void) const;
/// \internal
int GetState(void) const;
struct OutgoingCommand
{
RakNet::RakString remotePath;
RakNet::RakString data;
RakNet::RakString contentType;
bool isPost;
};
DataStructures::Queue<OutgoingCommand> outgoingCommand;
OutgoingCommand currentProcessingCommand;
private:
SystemAddress server;
TCPInterface *tcp;
RakNet::RakString host;
unsigned short port;
DataStructures::Queue<BadResponse> badResponses;
enum ConnectionState
{
CS_NONE,
CS_DISCONNECTING,
CS_CONNECTING,
CS_CONNECTED,
CS_PROCESSING,
} connectionState;
RakNet::RakString incomingData;
DataStructures::Queue<RakNet::RakString> results;
void CloseConnection();
/*
enum { RAK_HTTP_INITIAL,
RAK_HTTP_STARTING,
RAK_HTTP_CONNECTING,
RAK_HTTP_ESTABLISHED,
RAK_HTTP_REQUEST_SENT,
RAK_HTTP_IDLE } state;
RakNet::RakString outgoing, incoming, path, contentType;
void Process(Packet *packet); // the workhorse
// this helps check the various status lists in TCPInterface
typedef SystemAddress (TCPInterface::*StatusCheckFunction)(void);
bool InList(StatusCheckFunction func);
*/
};
} // namespace RakNet
#endif
#endif // _RAKNET_SUPPORT_*

View File

@@ -0,0 +1,15 @@
#include "IncrementalReadInterface.hpp"
#include <stdio.h>
using namespace RakNet;
unsigned int IncrementalReadInterface::GetFilePart( const char *filename, unsigned int startReadBytes, unsigned int numBytesToRead, void *preallocatedDestination, FileListNodeContext context)
{
FILE *fp = fopen(filename, "rb");
if (fp==0)
return 0;
fseek(fp,startReadBytes,SEEK_SET);
unsigned int numRead = (unsigned int) fread(preallocatedDestination,1,numBytesToRead, fp);
fclose(fp);
return numRead;
}

View File

@@ -0,0 +1,28 @@
#ifndef __INCREMENTAL_READ_INTERFACE_H
#define __INCREMENTAL_READ_INTERFACE_H
#include "FileListNodeContext.hpp"
#include "Export.hpp"
namespace RakNet
{
class RAK_DLL_EXPORT IncrementalReadInterface
{
public:
IncrementalReadInterface() {}
virtual ~IncrementalReadInterface() {}
/// Read part of a file into \a destination
/// Return the number of bytes written. Return 0 when file is done.
/// \param[in] filename Filename to read
/// \param[in] startReadBytes What offset from the start of the file to read from
/// \param[in] numBytesToRead How many bytes to read. This is also how many bytes have been allocated to preallocatedDestination
/// \param[out] preallocatedDestination Write your data here
/// \return The number of bytes read, or 0 if none
virtual unsigned int GetFilePart( const char *filename, unsigned int startReadBytes, unsigned int numBytesToRead, void *preallocatedDestination, FileListNodeContext context);
};
} // namespace RakNet
#endif

View File

@@ -0,0 +1,131 @@
/// \file
/// \brief \b [Internal] A class which stores a user message, and all information associated with sending and receiving that message.
///
/// This file is part of RakNet Copyright 2003 Jenkins Software LLC
///
/// Usage of RakNet is subject to the appropriate license agreement.
/// Creative Commons Licensees are subject to the
/// license found at
/// http://creativecommons.org/licenses/by-nc/2.5/
/// Single application licensees are subject to the license found at
/// http://www.jenkinssoftware.com/SingleApplicationLicense.html
/// Custom license users are subject to the terms therein.
/// GPL license users are subject to the GNU General Public
/// License as published by the Free
/// Software Foundation; either version 2 of the License, or (at your
/// option) any later version.
#ifndef __INTERNAL_PACKET_H
#define __INTERNAL_PACKET_H
#include "PacketPriority.hpp"
#include "RakNetTypes.hpp"
#include "RakMemoryOverride.hpp"
#include "RakNetDefines.hpp"
#include "NativeTypes.hpp"
#include "RakNetDefines.hpp"
#if USE_SLIDING_WINDOW_CONGESTION_CONTROL!=1
#include "CCRakNetUDT.hpp"
#else
#include "CCRakNetSlidingWindow.hpp"
#endif
namespace RakNet {
typedef uint16_t SplitPacketIdType;
typedef uint32_t SplitPacketIndexType;
/// This is the counter used for holding packet numbers, so we can detect duplicate packets. It should be large enough that if the variables
/// Internally assumed to be 4 bytes, but written as 3 bytes in ReliabilityLayer::WriteToBitStreamFromInternalPacket
typedef uint24_t MessageNumberType;
/// This is the counter used for holding ordered packet numbers, so we can detect out-of-order packets. It should be large enough that if the variables
/// were to wrap, the newly wrapped values would no longer be in use. Warning: Too large of a value wastes bandwidth!
typedef MessageNumberType OrderingIndexType;
typedef RakNet::TimeUS RemoteSystemTimeType;
struct InternalPacketFixedSizeTransmissionHeader
{
/// A unique numerical identifier given to this user message. Used to identify reliable messages on the network
MessageNumberType reliableMessageNumber;
///The ID used as identification for ordering messages. Also included in sequenced messages
OrderingIndexType orderingIndex;
// Used only with sequenced messages
OrderingIndexType sequencingIndex;
///What ordering channel this packet is on, if the reliability type uses ordering channels
unsigned char orderingChannel;
///The ID of the split packet, if we have split packets. This is the maximum number of split messages we can send simultaneously per connection.
SplitPacketIdType splitPacketId;
///If this is a split packet, the index into the array of subsplit packets
SplitPacketIndexType splitPacketIndex;
///The size of the array of subsplit packets
SplitPacketIndexType splitPacketCount;;
///How many bits long the data is
BitSize_t dataBitLength;
///What type of reliability algorithm to use with this packet
PacketReliability reliability;
// Not endian safe
// unsigned char priority : 3;
// unsigned char reliability : 5;
};
/// Used in InternalPacket when pointing to sharedDataBlock, rather than allocating itself
struct InternalPacketRefCountedData
{
unsigned char *sharedDataBlock;
unsigned int refCount;
};
/// Holds a user message, and related information
/// Don't use a constructor or destructor, due to the memory pool I am using
struct InternalPacket : public InternalPacketFixedSizeTransmissionHeader
{
/// Identifies the order in which this number was sent. Used locally
MessageNumberType messageInternalOrder;
/// Has this message number been assigned yet? We don't assign until the message is actually sent.
/// This fixes a bug where pre-determining message numbers and then sending a message on a different channel creates a huge gap.
/// This causes performance problems and causes those messages to timeout.
bool messageNumberAssigned;
/// Was this packet number used this update to track windowing drops or increases? Each packet number is only used once per update.
// bool allowWindowUpdate;
///When this packet was created
RakNet::TimeUS creationTime;
///The resendNext time to take action on this packet
RakNet::TimeUS nextActionTime;
// Size of the header when encoded into a bitstream
BitSize_t headerLength;
/// Buffer is a pointer to the actual data, assuming this packet has data at all
unsigned char *data;
/// How to alloc and delete the data member
enum AllocationScheme
{
/// Data is allocated using rakMalloc. Just free it
NORMAL,
/// data points to a larger block of data, where the larger block is reference counted. internalPacketRefCountedData is used in this case
REF_COUNTED,
/// If allocation scheme is STACK, data points to stackData and should not be deallocated
/// This is only used when sending. Received packets are deallocated in RakPeer
STACK
} allocationScheme;
InternalPacketRefCountedData *refCountedData;
/// How many attempts we made at sending this message
// unsigned char timesSent;
/// The priority level of this packet
PacketPriority priority;
/// If the reliability type requires a receipt, then return this number with it
uint32_t sendReceiptSerial;
// Used for the resend queue
// Linked list implementation so I can remove from the list via a pointer, without finding it in the list
InternalPacket *resendPrev, *resendNext,*unreliablePrev,*unreliableNext;
unsigned char stackData[128];
};
} // namespace RakNet
#endif

52
third-party/RakNet/src/RakNet/Itoa.cpp vendored Normal file
View File

@@ -0,0 +1,52 @@
#ifdef __cplusplus
extern "C" {
#endif
// Fast itoa from http://www.jb.man.ac.uk/~slowe/cpp/itoa.html for Linux since it seems like Linux doesn't support this function.
// I modified it to remove the std dependencies.
char* Itoa( int value, char* result, int base )
{
// check that the base if valid
if (base < 2 || base > 16) { *result = 0; return result; }
char* out = result;
int quotient = value;
int absQModB;
do {
// KevinJ - get rid of this dependency
//*out = "0123456789abcdef"[ std::abs( quotient % base ) ];
absQModB=quotient % base;
if (absQModB < 0)
absQModB=-absQModB;
*out = "0123456789abcdef"[ absQModB ];
++out;
quotient /= base;
} while ( quotient );
// Only apply negative sign for base 10
if ( value < 0 && base == 10) *out++ = '-';
// KevinJ - get rid of this dependency
// std::reverse( result, out );
*out = 0;
// KevinJ - My own reverse code
char *start = result;
char temp;
out--;
while (start < out)
{
temp=*start;
*start=*out;
*out=temp;
start++;
out--;
}
return result;
}
#ifdef __cplusplus
}
#endif

15
third-party/RakNet/src/RakNet/Itoa.hpp vendored Normal file
View File

@@ -0,0 +1,15 @@
#ifndef __RAK_ITOA_H
#define __RAK_ITOA_H
#ifdef __cplusplus
extern "C" {
#endif
char* Itoa( int value, char* result, int base );
#ifdef __cplusplus
}
#endif
#endif

84
third-party/RakNet/src/RakNet/Kbhit.hpp vendored Normal file
View File

@@ -0,0 +1,84 @@
/*****************************************************************************
kbhit() and getch() for Linux/UNIX
Chris Giese <geezer@execpc.com> http://my.execpc.com/~geezer
Release date: ?
This code is public domain (no copyright).
You can do whatever you want with it.
*****************************************************************************/
#if defined(_WIN32)
#include <conio.h> /* kbhit(), getch() */
#else
#include <sys/time.h> /* struct timeval, select() */
/* ICANON, ECHO, TCSANOW, struct termios */
#include <termios.h> /* tcgetattr(), tcsetattr() */
#include <stdlib.h> /* atexit(), exit() */
#include <unistd.h> /* read() */
#include <stdio.h> /* printf() */
#include <string.h> /* memcpy */
static struct termios g_old_kbd_mode;
/*****************************************************************************
*****************************************************************************/
static void cooked(void)
{
tcsetattr(0, TCSANOW, &g_old_kbd_mode);
}
/*****************************************************************************
*****************************************************************************/
static void raw(void)
{
static char init;
/**/
struct termios new_kbd_mode;
if(init)
return;
/* put keyboard (stdin, actually) in raw, unbuffered mode */
tcgetattr(0, &g_old_kbd_mode);
memcpy(&new_kbd_mode, &g_old_kbd_mode, sizeof(struct termios));
new_kbd_mode.c_lflag &= ~(ICANON /*| ECHO */ );
new_kbd_mode.c_cc[VTIME] = 0;
new_kbd_mode.c_cc[VMIN] = 1;
tcsetattr(0, TCSANOW, &new_kbd_mode);
/* when we exit, go back to normal, "cooked" mode */
atexit(cooked);
init = 1;
}
/*****************************************************************************
*****************************************************************************/
static int kbhit(void)
{
struct timeval timeout;
fd_set read_handles;
int status;
raw();
/* check stdin (fd 0) for activity */
FD_ZERO(&read_handles);
FD_SET(0, &read_handles);
timeout.tv_sec = timeout.tv_usec = 0;
status = select(0 + 1, &read_handles, NULL, NULL, &timeout);
if(status < 0)
{
printf("select() failed in kbhit()\n");
exit(1);
}
return status;
}
/*****************************************************************************
*****************************************************************************/
static int getch(void)
{
unsigned char temp;
raw();
/* stdin = fd 0 */
if(read(0, &temp, 1) != 1)
return 0;
return temp;
}
#endif

View File

@@ -0,0 +1,26 @@
#if (defined(__GNUC__) || defined(__ARMCC_VERSION) || defined(__GCCXML__) || defined(__S3E__)) && !defined(_WIN32)
#include <string.h>
#ifndef _stricmp
int _stricmp(const char* s1, const char* s2)
{
return strcasecmp(s1,s2);
}
#endif
int _strnicmp(const char* s1, const char* s2, size_t n)
{
return strncasecmp(s1,s2,n);
}
#ifndef __APPLE__
char *_strlwr(char * str )
{
if (str==0)
return 0;
for (int i=0; str[i]; i++)
{
if (str[i]>='A' && str[i]<='Z')
str[i]+='a'-'A';
}
return str;
}
#endif
#endif

View File

@@ -0,0 +1,26 @@
#ifndef _GCC_WIN_STRINGS
#define _GCC_WIN_STRINGS
#if (defined(__GNUC__) || defined(__GCCXML__) || defined(__S3E__)) && !defined(_WIN32)
#ifndef _stricmp
int _stricmp(const char* s1, const char* s2);
#endif
int _strnicmp(const char* s1, const char* s2, size_t n);
#ifndef __APPLE__
char *_strlwr(char * str ); //this won't compile on OSX for some reason
#endif
#ifndef _vsnprintf
#define _vsnprintf vsnprintf
#endif
#endif
#endif // _GCC_WIN_STRINGS

View File

@@ -0,0 +1,42 @@
#include "LocklessTypes.hpp"
using namespace RakNet;
LocklessUint32_t::LocklessUint32_t()
{
value=0;
}
LocklessUint32_t::LocklessUint32_t(uint32_t initial)
{
value=initial;
}
uint32_t LocklessUint32_t::Increment(void)
{
#ifdef _WIN32
return (uint32_t) InterlockedIncrement(&value);
#elif defined(ANDROID) || defined(__S3E__)
uint32_t v;
mutex.Lock();
++value;
v=value;
mutex.Unlock();
return v;
#else
return __sync_fetch_and_add (&value, (uint32_t) 1);
#endif
}
uint32_t LocklessUint32_t::Decrement(void)
{
#ifdef _WIN32
return (uint32_t) InterlockedDecrement(&value);
#elif defined(ANDROID) || defined(__S3E__)
uint32_t v;
mutex.Lock();
--value;
v=value;
mutex.Unlock();
return v;
#else
return __sync_fetch_and_add (&value, (uint32_t) -1);
#endif
}

View File

@@ -0,0 +1,40 @@
#ifndef __LOCKLESS_TYPES_H
#define __LOCKLESS_TYPES_H
#include "Export.hpp"
#include "NativeTypes.hpp"
#include "WindowsIncludes.hpp"
#if defined(ANDROID) || defined(__S3E__)
// __sync_fetch_and_add not supported apparently
#include "SimpleMutex.hpp"
#endif
namespace RakNet
{
class RAK_DLL_EXPORT LocklessUint32_t
{
public:
LocklessUint32_t();
explicit LocklessUint32_t(uint32_t initial);
// Returns variable value after changing it
uint32_t Increment(void);
// Returns variable value after changing it
uint32_t Decrement(void);
volatile uint32_t GetValue(void) const {return value;}
protected:
#ifdef _WIN32
volatile LONG value;
#elif defined(ANDROID) || defined(__S3E__)
// __sync_fetch_and_add not supported apparently
SimpleMutex mutex;
uint32_t value;
#else
volatile uint32_t value;
#endif
};
};
#endif

View File

@@ -0,0 +1,279 @@
#include "NativeFeatureIncludes.hpp"
#if _RAKNET_SUPPORT_LogCommandParser==1
#include "LogCommandParser.hpp"
#include "TransportInterface.hpp"
#include <memory.h>
#include <stdio.h>
#include <string.h>
#include <stdarg.h>
#include "LinuxStrings.hpp"
#ifdef _MSC_VER
#pragma warning( push )
#endif
using namespace RakNet;
STATIC_FACTORY_DEFINITIONS(LogCommandParser,LogCommandParser);
LogCommandParser::LogCommandParser()
{
RegisterCommand(CommandParserInterface::VARIABLE_NUMBER_OF_PARAMETERS,"Subscribe","[<ChannelName>] - Subscribes to a named channel, or all channels");
RegisterCommand(CommandParserInterface::VARIABLE_NUMBER_OF_PARAMETERS,"Unsubscribe","[<ChannelName>] - Unsubscribes from a named channel, or all channels");
memset(channelNames,0,sizeof(channelNames));
}
LogCommandParser::~LogCommandParser()
{
}
bool LogCommandParser::OnCommand(const char *command, unsigned numParameters, char **parameterList, TransportInterface *transport, const SystemAddress &systemAddress, const char *originalString)
{
(void) originalString;
if (strcmp(command, "Subscribe")==0)
{
unsigned channelIndex;
if (numParameters==0)
{
Subscribe(systemAddress, 0);
transport->Send(systemAddress, "Subscribed to all channels.\r\n");
}
else if (numParameters==1)
{
if ((channelIndex=Subscribe(systemAddress, parameterList[0]))!=(unsigned)-1)
{
transport->Send(systemAddress, "You are now subscribed to channel %s.\r\n", channelNames[channelIndex]);
}
else
{
transport->Send(systemAddress, "Cannot find channel %s.\r\n", parameterList[0]);
PrintChannels(systemAddress, transport);
}
}
else
{
transport->Send(systemAddress, "Subscribe takes either 0 or 1 parameters.\r\n");
}
}
else if (strcmp(command, "Unsubscribe")==0)
{
unsigned channelIndex;
if (numParameters==0)
{
Unsubscribe(systemAddress, 0);
transport->Send(systemAddress, "Unsubscribed from all channels.\r\n");
}
else if (numParameters==1)
{
if ((channelIndex=Unsubscribe(systemAddress, parameterList[0]))!=(unsigned)-1)
{
transport->Send(systemAddress, "You are now unsubscribed from channel %s.\r\n", channelNames[channelIndex]);
}
else
{
transport->Send(systemAddress, "Cannot find channel %s.\r\n", parameterList[0]);
PrintChannels(systemAddress, transport);
}
}
else
{
transport->Send(systemAddress, "Unsubscribe takes either 0 or 1 parameters.\r\n");
}
}
return true;
}
const char *LogCommandParser::GetName(void) const
{
return "Logger";
}
void LogCommandParser::SendHelp(TransportInterface *transport, const SystemAddress &systemAddress)
{
transport->Send(systemAddress, "The logger will accept user log data via the Log(...) function.\r\n");
transport->Send(systemAddress, "Each log is associated with a named channel.\r\n");
transport->Send(systemAddress, "You can subscribe to or unsubscribe from named channels.\r\n");
PrintChannels(systemAddress, transport);
}
void LogCommandParser::AddChannel(const char *channelName)
{
unsigned channelIndex=0;
channelIndex = GetChannelIndexFromName(channelName);
// Each channel can only be added once.
RakAssert(channelIndex==(unsigned)-1);
unsigned i;
for (i=0; i < 32; i++)
{
if (channelNames[i]==0)
{
// Assuming a persistent static string.
channelNames[i]=channelName;
return;
}
}
// No more available channels - max 32 with this implementation where I save subscribed channels with bit operations
RakAssert(0);
}
void LogCommandParser::WriteLog(const char *channelName, const char *format, ...)
{
if (channelName==0 || format==0)
return;
unsigned channelIndex;
channelIndex = GetChannelIndexFromName(channelName);
if (channelIndex==(unsigned)-1)
{
AddChannel(channelName);
}
char text[REMOTE_MAX_TEXT_INPUT];
va_list ap;
va_start(ap, format);
_vsnprintf(text, REMOTE_MAX_TEXT_INPUT, format, ap);
va_end(ap);
text[REMOTE_MAX_TEXT_INPUT-1]=0;
// Make sure that text ends in \r\n
int textLen;
textLen=(int)strlen(text);
if (textLen==0)
return;
if (text[textLen-1]=='\n')
{
text[textLen-1]=0;
}
if (textLen < REMOTE_MAX_TEXT_INPUT-4)
strcat(text, "\r\n");
else
{
text[textLen-3]='\r';
text[textLen-2]='\n';
text[textLen-1]=0;
}
// For each user that subscribes to this channel, send to them.
unsigned i;
for (i=0; i < remoteUsers.Size(); i++)
{
if (remoteUsers[i].channels & (1 << channelIndex))
{
trans->Send(remoteUsers[i].systemAddress, text);
}
}
}
void LogCommandParser::PrintChannels(const SystemAddress &systemAddress, TransportInterface *transport) const
{
unsigned i;
bool anyChannels=false;
transport->Send(systemAddress, "CHANNELS:\r\n");
for (i=0; i < 32; i++)
{
if (channelNames[i])
{
transport->Send(systemAddress, "%i. %s\r\n", i+1,channelNames[i]);
anyChannels=true;
}
}
if (anyChannels==false)
transport->Send(systemAddress, "None.\r\n");
}
void LogCommandParser::OnNewIncomingConnection(const SystemAddress &systemAddress, TransportInterface *transport)
{
(void) systemAddress;
(void) transport;
}
void LogCommandParser::OnConnectionLost(const SystemAddress &systemAddress, TransportInterface *transport)
{
(void) transport;
Unsubscribe(systemAddress, 0);
}
unsigned LogCommandParser::Unsubscribe(const SystemAddress &systemAddress, const char *channelName)
{
unsigned i;
for (i=0; i < remoteUsers.Size(); i++)
{
if (remoteUsers[i].systemAddress==systemAddress)
{
if (channelName==0)
{
// Unsubscribe from all and delete this user.
remoteUsers[i]=remoteUsers[remoteUsers.Size()-1];
remoteUsers.RemoveFromEnd();
return 0;
}
else
{
unsigned channelIndex;
channelIndex = GetChannelIndexFromName(channelName);
if (channelIndex!=(unsigned)-1)
{
remoteUsers[i].channels&=0xFFFF ^ (1<<channelIndex); // Unset this bit
}
return channelIndex;
}
}
}
return (unsigned)-1;
}
unsigned LogCommandParser::Subscribe(const SystemAddress &systemAddress, const char *channelName)
{
unsigned i;
unsigned channelIndex=(unsigned)-1;
if (channelName)
{
channelIndex = GetChannelIndexFromName(channelName);
if (channelIndex==(unsigned)-1)
return channelIndex;
}
for (i=0; i < remoteUsers.Size(); i++)
{
if (remoteUsers[i].systemAddress==systemAddress)
{
if (channelName)
remoteUsers[i].channels|=1<<channelIndex; // Set this bit for an existing user
else
remoteUsers[i].channels=0xFFFF;
return channelIndex;
}
}
// Make a new user
SystemAddressAndChannel newUser;
newUser.systemAddress = systemAddress;
if (channelName)
newUser.channels=1<<channelIndex;
else
newUser.channels=0xFFFF;
remoteUsers.Insert(newUser, _FILE_AND_LINE_);
return channelIndex;
}
unsigned LogCommandParser::GetChannelIndexFromName(const char *channelName)
{
unsigned i;
for (i=0; i < 32; i++)
{
if (channelNames[i]==0)
return (unsigned) -1;
if (_stricmp(channelNames[i], channelName)==0)
return i;
}
return (unsigned)-1;
}
void LogCommandParser::OnTransportChange(TransportInterface *transport)
{
// I don't want users to have to pass TransportInterface *transport to Log.
trans=transport;
}
#ifdef _MSC_VER
#pragma warning( pop )
#endif
#endif // _RAKNET_SUPPORT_*

View File

@@ -0,0 +1,120 @@
/// \file
/// \brief Contains LogCommandParser , Used to send logs to connected consoles
///
/// This file is part of RakNet Copyright 2003 Jenkins Software LLC
///
/// Usage of RakNet is subject to the appropriate license agreement.
#include "NativeFeatureIncludes.hpp"
#if _RAKNET_SUPPORT_LogCommandParser==1
#ifndef __LOG_COMMAND_PARSER
#define __LOG_COMMAND_PARSER
#include "CommandParserInterface.hpp"
#include "Export.hpp"
namespace RakNet
{
/// Forward declarations
class RakPeerInterface;
/// \brief Adds the ability to send logging output to a remote console
class RAK_DLL_EXPORT LogCommandParser : public CommandParserInterface
{
public:
// GetInstance() and DestroyInstance(instance*)
STATIC_FACTORY_DECLARATIONS(LogCommandParser)
LogCommandParser();
~LogCommandParser();
/// Given \a command with parameters \a parameterList , do whatever processing you wish.
/// \param[in] command The command to process
/// \param[in] numParameters How many parameters were passed along with the command
/// \param[in] parameterList The list of parameters. parameterList[0] is the first parameter and so on.
/// \param[in] transport The transport interface we can use to write to
/// \param[in] systemAddress The player that sent this command.
/// \param[in] originalString The string that was actually sent over the network, in case you want to do your own parsing
bool OnCommand(const char *command, unsigned numParameters, char **parameterList, TransportInterface *transport, const SystemAddress &systemAddress, const char *originalString);
/// You are responsible for overriding this function and returning a static string, which will identifier your parser.
/// This should return a static string
/// \return The name that you return.
const char *GetName(void) const;
/// A callback for when you are expected to send a brief description of your parser to \a systemAddress
/// \param[in] transport The transport interface we can use to write to
/// \param[in] systemAddress The player that requested help.
void SendHelp(TransportInterface *transport, const SystemAddress &systemAddress);
/// All logs must be associated with a channel. This is a filter so that remote clients only get logs for a system they care about.
// If you call Log with a channel that is unknown, that channel will automatically be added
/// \param[in] channelName A persistent string naming the channel. Don't deallocate this string.
void AddChannel(const char *channelName);
/// Write a log to a channel.
/// Logs are not buffered, so only remote consoles connected and subscribing at the time you write will get the output.
/// \param[in] format Same as RAKNET_DEBUG_PRINTF()
/// \param[in] ... Same as RAKNET_DEBUG_PRINTF()
void WriteLog(const char *channelName, const char *format, ...);
/// A callback for when \a systemAddress has connected to us.
/// \param[in] systemAddress The player that has connected.
/// \param[in] transport The transport interface that sent us this information. Can be used to send messages to this or other players.
void OnNewIncomingConnection(const SystemAddress &systemAddress, TransportInterface *transport);
/// A callback for when \a systemAddress has disconnected, either gracefully or forcefully
/// \param[in] systemAddress The player that has disconnected.
/// \param[in] transport The transport interface that sent us this information.
void OnConnectionLost(const SystemAddress &systemAddress, TransportInterface *transport);
/// This is called every time transport interface is registered. If you want to save a copy of the TransportInterface pointer
/// This is the place to do it
/// \param[in] transport The new TransportInterface
void OnTransportChange(TransportInterface *transport);
protected:
/// Sends the currently active channels to the user
/// \param[in] systemAddress The player to send to
/// \param[in] transport The transport interface to use to send the channels
void PrintChannels(const SystemAddress &systemAddress, TransportInterface *transport) const;
/// Unsubscribe a user from a channel (or from all channels)
/// \param[in] systemAddress The player to unsubscribe to
/// \param[in] channelName If 0, then unsubscribe from all channels. Otherwise unsubscribe from the named channel
unsigned Unsubscribe(const SystemAddress &systemAddress, const char *channelName);
/// Subscribe a user to a channel (or to all channels)
/// \param[in] systemAddress The player to subscribe to
/// \param[in] channelName If 0, then subscribe from all channels. Otherwise subscribe to the named channel
unsigned Subscribe(const SystemAddress &systemAddress, const char *channelName);
/// Given the name of a channel, return the index into channelNames where it is located
/// \param[in] channelName The name of the channel
unsigned GetChannelIndexFromName(const char *channelName);
/// One of these structures is created per player
struct SystemAddressAndChannel
{
/// The ID of the player
SystemAddress systemAddress;
/// Bitwise representations of the channels subscribed to. If bit 0 is set, then we subscribe to channelNames[0] and so on.
unsigned channels;
};
/// The list of remote users. Added to when users subscribe, removed when they disconnect or unsubscribe
DataStructures::List<SystemAddressAndChannel> remoteUsers;
/// Names of the channels at each bit, or 0 for an unused channel
const char *channelNames[32];
/// This is so I can save the current transport provider, solely so I can use it without having the user pass it to Log
TransportInterface *trans;
};
} // namespace RakNet
#endif
#endif // _RAKNET_SUPPORT_*

View File

@@ -0,0 +1,31 @@
/// \file
/// \brief \b [Internal] Defines the default maximum transfer unit.
///
/// This file is part of RakNet Copyright 2003 Jenkins Software LLC
///
/// Usage of RakNet is subject to the appropriate license agreement.
#ifndef MAXIMUM_MTU_SIZE
/// \li \em 17914 16 Mbit/Sec Token Ring
/// \li \em 4464 4 Mbits/Sec Token Ring
/// \li \em 4352 FDDI
/// \li \em 1500. The largest Ethernet packet size \b recommended. This is the typical setting for non-PPPoE, non-VPN connections. The default value for NETGEAR routers, adapters and switches.
/// \li \em 1492. The size PPPoE prefers.
/// \li \em 1472. Maximum size to use for pinging. (Bigger packets are fragmented.)
/// \li \em 1468. The size DHCP prefers.
/// \li \em 1460. Usable by AOL if you don't have large email attachments, etc.
/// \li \em 1430. The size VPN and PPTP prefer.
/// \li \em 1400. Maximum size for AOL DSL.
/// \li \em 576. Typical value to connect to dial-up ISPs.
/// The largest value for an UDP datagram
#define MAXIMUM_MTU_SIZE 1492
#define MINIMUM_MTU_SIZE 400
#endif

View File

@@ -0,0 +1,408 @@
#include "NativeFeatureIncludes.hpp"
#if _RAKNET_SUPPORT_MessageFilter==1
#include "MessageFilter.hpp"
#include "RakAssert.hpp"
#include "GetTime.hpp"
#include "MessageIdentifiers.hpp"
#include "RakAssert.hpp"
#include "RakPeerInterface.hpp"
#include "PacketizedTCP.hpp"
#include "BitStream.hpp"
#ifdef _MSC_VER
#pragma warning( push )
#endif
using namespace RakNet;
int RakNet::MessageFilterStrComp( char *const &key,char *const &data )
{
return strcmp(key,data);
}
int RakNet::FilterSetComp( const int &key, FilterSet * const &data )
{
if (key < data->filterSetID)
return -1;
else if (key==data->filterSetID)
return 0;
else
return 1;
}
STATIC_FACTORY_DEFINITIONS(MessageFilter,MessageFilter);
MessageFilter::MessageFilter()
{
whenLastTimeoutCheck=RakNet::GetTime();
}
MessageFilter::~MessageFilter()
{
Clear();
}
void MessageFilter::SetAutoAddNewConnectionsToFilter(int filterSetID)
{
autoAddNewConnectionsToFilter=filterSetID;
}
void MessageFilter::SetAllowMessageID(bool allow, int messageIDStart, int messageIDEnd,int filterSetID)
{
RakAssert(messageIDStart <= messageIDEnd);
FilterSet *filterSet = GetFilterSetByID(filterSetID);
int i;
for (i=messageIDStart; i <= messageIDEnd; ++i)
filterSet->allowedIDs[i]=allow;
}
void MessageFilter::SetAllowRPC4(bool allow, const char* uniqueID, int filterSetID)
{
FilterSet *filterSet = GetFilterSetByID(filterSetID);
bool objectExists;
unsigned int idx = filterSet->allowedRPC4.GetIndexFromKey(uniqueID, &objectExists);
if (allow)
{
if (objectExists==false)
{
filterSet->allowedRPC4.InsertAtIndex(uniqueID, idx, _FILE_AND_LINE_);
filterSet->allowedIDs[ID_RPC_PLUGIN]=true;
}
}
else
{
if (objectExists==true)
{
filterSet->allowedRPC4.RemoveAtIndex(idx);
if (filterSet->allowedRPC4.Size()==0)
{
filterSet->allowedIDs[ID_RPC_PLUGIN]=false;
}
}
}
}
void MessageFilter::SetActionOnDisallowedMessage(bool kickOnDisallowed, bool banOnDisallowed, RakNet::TimeMS banTimeMS, int filterSetID)
{
FilterSet *filterSet = GetFilterSetByID(filterSetID);
filterSet->kickOnDisallowedMessage=kickOnDisallowed;
filterSet->disallowedMessageBanTimeMS=banTimeMS;
filterSet->banOnDisallowedMessage=banOnDisallowed;
}
void MessageFilter::SetDisallowedMessageCallback(int filterSetID, void *userData, void (*invalidMessageCallback)(RakPeerInterface *peer, AddressOrGUID systemAddress, int filterSetID, void *userData, unsigned char messageID))
{
FilterSet *filterSet = GetFilterSetByID(filterSetID);
filterSet->invalidMessageCallback=invalidMessageCallback;
filterSet->disallowedCallbackUserData=userData;
}
void MessageFilter::SetTimeoutCallback(int filterSetID, void *userData, void (*invalidMessageCallback)(RakPeerInterface *peer, AddressOrGUID systemAddress, int filterSetID, void *userData))
{
FilterSet *filterSet = GetFilterSetByID(filterSetID);
filterSet->timeoutCallback=invalidMessageCallback;
filterSet->timeoutUserData=userData;
}
void MessageFilter::SetFilterMaxTime(int allowedTimeMS, bool banOnExceed, RakNet::TimeMS banTimeMS, int filterSetID)
{
FilterSet *filterSet = GetFilterSetByID(filterSetID);
filterSet->maxMemberTimeMS=allowedTimeMS;
filterSet->banOnFilterTimeExceed=banOnExceed;
filterSet->timeExceedBanTimeMS=banTimeMS;
}
int MessageFilter::GetSystemFilterSet(AddressOrGUID systemAddress)
{
// bool objectExists;
// unsigned index = systemList.GetIndexFromKey(systemAddress, &objectExists);
// if (objectExists==false)
// return -1;
// else
// return systemList[index].filter->filterSetID;
DataStructures::HashIndex index = systemList.GetIndexOf(systemAddress);
if (index.IsInvalid())
return -1;
else
return systemList.ItemAtIndex(index).filter->filterSetID;
}
void MessageFilter::SetSystemFilterSet(AddressOrGUID addressOrGUID, int filterSetID)
{
// Allocate this filter set if it doesn't exist.
RakAssert(addressOrGUID.IsUndefined()==false);
// bool objectExists;
// unsigned index = systemList.GetIndexFromKey(addressOrGUID, &objectExists);
// if (objectExists==false)
DataStructures::HashIndex index = systemList.GetIndexOf(addressOrGUID);
if (index.IsInvalid())
{
if (filterSetID<0)
return;
FilteredSystem filteredSystem;
filteredSystem.filter = GetFilterSetByID(filterSetID);
// filteredSystem.addressOrGUID=addressOrGUID;
filteredSystem.timeEnteredThisSet=RakNet::GetTimeMS();
// systemList.Insert(addressOrGUID, filteredSystem, true, _FILE_AND_LINE_);
systemList.Push(addressOrGUID,filteredSystem,_FILE_AND_LINE_);
}
else
{
if (filterSetID>=0)
{
FilterSet *filterSet = GetFilterSetByID(filterSetID);
systemList.ItemAtIndex(index).timeEnteredThisSet=RakNet::GetTimeMS();
systemList.ItemAtIndex(index).filter=filterSet;
}
else
{
systemList.RemoveAtIndex(index, _FILE_AND_LINE_);
}
}
}
unsigned MessageFilter::GetSystemCount(int filterSetID) const
{
if (filterSetID==-1)
{
return systemList.Size();
}
else
{
unsigned i;
unsigned count=0;
DataStructures::List< FilteredSystem > itemList;
DataStructures::List< AddressOrGUID > keyList;
systemList.GetAsList(itemList, keyList, _FILE_AND_LINE_);
for (i=0; i < itemList.Size(); i++)
if (itemList[i].filter->filterSetID==filterSetID)
++count;
return count;
}
}
unsigned MessageFilter::GetFilterSetCount(void) const
{
return filterList.Size();
}
int MessageFilter::GetFilterSetIDByIndex(unsigned index)
{
return filterList[index]->filterSetID;
}
void MessageFilter::DeleteFilterSet(int filterSetID)
{
FilterSet *filterSet;
bool objectExists;
unsigned i,index;
index = filterList.GetIndexFromKey(filterSetID, &objectExists);
if (objectExists)
{
filterSet=filterList[index];
DeallocateFilterSet(filterSet);
filterList.RemoveAtIndex(index);
DataStructures::List< FilteredSystem > itemList;
DataStructures::List< AddressOrGUID > keyList;
systemList.GetAsList(itemList, keyList, _FILE_AND_LINE_);
for (i=0; i < itemList.Size(); i++)
{
if (itemList[i].filter==filterSet)
{
systemList.Remove(keyList[i], _FILE_AND_LINE_);
}
}
/*
// Don't reference this pointer any longer
i=0;
while (i < systemList.Size())
{
if (systemList[i].filter==filterSet)
systemList.RemoveAtIndex(i);
else
++i;
}
*/
}
}
void MessageFilter::Clear(void)
{
unsigned i;
systemList.Clear(_FILE_AND_LINE_);
for (i=0; i < filterList.Size(); i++)
DeallocateFilterSet(filterList[i]);
filterList.Clear(false, _FILE_AND_LINE_);
}
void MessageFilter::DeallocateFilterSet(FilterSet* filterSet)
{
RakNet::OP_DELETE(filterSet, _FILE_AND_LINE_);
}
FilterSet* MessageFilter::GetFilterSetByID(int filterSetID)
{
RakAssert(filterSetID>=0);
bool objectExists;
unsigned index;
index = filterList.GetIndexFromKey(filterSetID, &objectExists);
if (objectExists)
return filterList[index];
else
{
FilterSet *newFilterSet = RakNet::OP_NEW<FilterSet>( _FILE_AND_LINE_ );
memset(newFilterSet->allowedIDs, 0, MESSAGE_FILTER_MAX_MESSAGE_ID * sizeof(bool));
newFilterSet->banOnFilterTimeExceed=false;
newFilterSet->kickOnDisallowedMessage=false;
newFilterSet->banOnDisallowedMessage=false;
newFilterSet->disallowedMessageBanTimeMS=0;
newFilterSet->timeExceedBanTimeMS=0;
newFilterSet->maxMemberTimeMS=0;
newFilterSet->filterSetID=filterSetID;
newFilterSet->invalidMessageCallback=0;
newFilterSet->timeoutCallback=0;
newFilterSet->timeoutUserData=0;
filterList.Insert(filterSetID, newFilterSet, true, _FILE_AND_LINE_);
return newFilterSet;
}
}
void MessageFilter::OnInvalidMessage(FilterSet *filterSet, AddressOrGUID systemAddress, unsigned char messageID)
{
if (filterSet->invalidMessageCallback)
filterSet->invalidMessageCallback(rakPeerInterface, systemAddress, filterSet->filterSetID, filterSet->disallowedCallbackUserData, messageID);
if (filterSet->banOnDisallowedMessage && rakPeerInterface)
{
char str1[64];
systemAddress.systemAddress.ToString(false, str1);
rakPeerInterface->AddToBanList(str1, filterSet->disallowedMessageBanTimeMS);
}
if (filterSet->kickOnDisallowedMessage)
{
if (rakPeerInterface)
rakPeerInterface->CloseConnection(systemAddress, true, 0);
#if _RAKNET_SUPPORT_PacketizedTCP==1 && _RAKNET_SUPPORT_TCPInterface==1
else
packetizedTCP->CloseConnection(systemAddress.systemAddress);
#endif
}
}
void MessageFilter::Update(void)
{
// Update all timers for all systems. If those systems' filter sets are expired, take the appropriate action.
RakNet::Time curTime = RakNet::GetTime();
if (GreaterThan(curTime - 1000, whenLastTimeoutCheck))
{
DataStructures::List< FilteredSystem > itemList;
DataStructures::List< AddressOrGUID > keyList;
systemList.GetAsList(itemList, keyList, _FILE_AND_LINE_);
unsigned int index;
for (index=0; index < itemList.Size(); index++)
{
if (itemList[index].filter &&
itemList[index].filter->maxMemberTimeMS>0 &&
curTime-itemList[index].timeEnteredThisSet >= itemList[index].filter->maxMemberTimeMS)
{
if (itemList[index].filter->timeoutCallback)
itemList[index].filter->timeoutCallback(rakPeerInterface, keyList[index], itemList[index].filter->filterSetID, itemList[index].filter->timeoutUserData);
if (itemList[index].filter->banOnFilterTimeExceed && rakPeerInterface)
{
char str1[64];
keyList[index].ToString(false, str1);
rakPeerInterface->AddToBanList(str1, itemList[index].filter->timeExceedBanTimeMS);
}
if (rakPeerInterface)
rakPeerInterface->CloseConnection(keyList[index], true, 0);
#if _RAKNET_SUPPORT_PacketizedTCP==1 && _RAKNET_SUPPORT_TCPInterface==1
else
packetizedTCP->CloseConnection(keyList[index].systemAddress);
#endif
systemList.Remove(keyList[index], _FILE_AND_LINE_);
}
}
whenLastTimeoutCheck=curTime+1000;
}
}
void MessageFilter::OnNewConnection(const SystemAddress &systemAddress, RakNetGUID rakNetGUID, bool isIncoming)
{
(void) systemAddress;
(void) rakNetGUID;
(void) isIncoming;
AddressOrGUID aog;
aog.rakNetGuid=rakNetGUID;
aog.systemAddress=systemAddress;
// New system, automatically assign to filter set if appropriate
if (autoAddNewConnectionsToFilter>=0 && systemList.HasData(aog)==false)
SetSystemFilterSet(aog, autoAddNewConnectionsToFilter);
}
void MessageFilter::OnClosedConnection(const SystemAddress &systemAddress, RakNetGUID rakNetGUID, PI2_LostConnectionReason lostConnectionReason )
{
(void) rakNetGUID;
(void) lostConnectionReason;
AddressOrGUID aog;
aog.rakNetGuid=rakNetGUID;
aog.systemAddress=systemAddress;
// Lost system, remove from the list
systemList.Remove(aog, _FILE_AND_LINE_);
}
PluginReceiveResult MessageFilter::OnReceive(Packet *packet)
{
DataStructures::HashIndex index;
unsigned char messageId;
switch (packet->data[0])
{
case ID_NEW_INCOMING_CONNECTION:
case ID_CONNECTION_REQUEST_ACCEPTED:
case ID_CONNECTION_LOST:
case ID_DISCONNECTION_NOTIFICATION:
case ID_CONNECTION_ATTEMPT_FAILED:
case ID_NO_FREE_INCOMING_CONNECTIONS:
case ID_IP_RECENTLY_CONNECTED:
case ID_CONNECTION_BANNED:
case ID_INVALID_PASSWORD:
case ID_UNCONNECTED_PONG:
case ID_ALREADY_CONNECTED:
case ID_ADVERTISE_SYSTEM:
case ID_REMOTE_DISCONNECTION_NOTIFICATION:
case ID_REMOTE_CONNECTION_LOST:
case ID_REMOTE_NEW_INCOMING_CONNECTION:
case ID_DOWNLOAD_PROGRESS:
break;
default:
if (packet->data[0]==ID_TIMESTAMP)
{
if (packet->length<sizeof(MessageID) + sizeof(RakNet::TimeMS))
return RR_STOP_PROCESSING_AND_DEALLOCATE; // Invalid message
messageId=packet->data[sizeof(MessageID) + sizeof(RakNet::TimeMS)];
}
else
messageId=packet->data[0];
// If this system is filtered, check if this message is allowed. If not allowed, return RR_STOP_PROCESSING_AND_DEALLOCATE
// index = systemList.GetIndexFromKey(packet->addressOrGUID, &objectExists);
index = systemList.GetIndexOf(packet);
if (index.IsInvalid())
break;
if (systemList.ItemAtIndex(index).filter->allowedIDs[messageId]==false)
{
OnInvalidMessage(systemList.ItemAtIndex(index).filter, packet, packet->data[0]);
return RR_STOP_PROCESSING_AND_DEALLOCATE;
}
if (packet->data[0]==ID_RPC_PLUGIN)
{
RakNet::BitStream bsIn(packet->data,packet->length,false);
bsIn.IgnoreBytes(2);
RakNet::RakString functionName;
bsIn.ReadCompressed(functionName);
if (systemList.ItemAtIndex(index).filter->allowedRPC4.HasData(functionName)==false)
{
OnInvalidMessage(systemList.ItemAtIndex(index).filter, packet, packet->data[0]);
return RR_STOP_PROCESSING_AND_DEALLOCATE;
}
}
break;
}
return RR_CONTINUE_PROCESSING;
}
#ifdef _MSC_VER
#pragma warning( pop )
#endif
#endif // _RAKNET_SUPPORT_*

View File

@@ -0,0 +1,191 @@
/// \file
/// \brief Message filter plugin. Assigns systems to FilterSets. Each FilterSet limits what messages are allowed. This is a security related plugin.
///
/// This file is part of RakNet Copyright 2003 Jenkins Software LLC
///
/// Usage of RakNet is subject to the appropriate license agreement.
#include "NativeFeatureIncludes.hpp"
#if _RAKNET_SUPPORT_MessageFilter==1
#ifndef __MESSAGE_FILTER_PLUGIN_H
#define __MESSAGE_FILTER_PLUGIN_H
#include "RakNetTypes.hpp"
#include "PluginInterface2.hpp"
#include "DS_OrderedList.hpp"
#include "DS_Hash.hpp"
#include "Export.hpp"
/// MessageIdentifier (ID_*) values shoudln't go higher than this. Change it if you do.
#define MESSAGE_FILTER_MAX_MESSAGE_ID 256
namespace RakNet
{
/// Forward declarations
class RakPeerInterface;
/// \internal Has to be public so some of the shittier compilers can use it.
int RAK_DLL_EXPORT MessageFilterStrComp( char *const &key,char *const &data );
/// \internal Has to be public so some of the shittier compilers can use it.
struct FilterSet
{
bool banOnFilterTimeExceed;
bool kickOnDisallowedMessage;
bool banOnDisallowedMessage;
RakNet::TimeMS disallowedMessageBanTimeMS;
RakNet::TimeMS timeExceedBanTimeMS;
RakNet::TimeMS maxMemberTimeMS;
void (*invalidMessageCallback)(RakPeerInterface *peer, AddressOrGUID systemAddress, int filterSetID, void *userData, unsigned char messageID);
void *disallowedCallbackUserData;
void (*timeoutCallback)(RakPeerInterface *peer, AddressOrGUID systemAddress, int filterSetID, void *userData);
void *timeoutUserData;
int filterSetID;
bool allowedIDs[MESSAGE_FILTER_MAX_MESSAGE_ID];
DataStructures::OrderedList<RakNet::RakString,RakNet::RakString> allowedRPC4;
};
/// \internal Has to be public so some of the shittier compilers can use it.
int RAK_DLL_EXPORT FilterSetComp( const int &key, FilterSet * const &data );
/// \internal Has to be public so some of the shittier compilers can use it.
struct FilteredSystem
{
FilterSet *filter;
RakNet::TimeMS timeEnteredThisSet;
};
/// \defgroup MESSAGEFILTER_GROUP MessageFilter
/// \brief Remote incoming packets from unauthorized systems
/// \details
/// \ingroup PLUGINS_GROUP
/// \brief Assigns systems to FilterSets. Each FilterSet limits what kinds of messages are allowed.
/// \details The MessageFilter plugin is used for security where you limit what systems can send what kind of messages.<BR>
/// You implicitly define FilterSets, and add allowed message IDs to these FilterSets.<BR>
/// You then add systems to these filters, such that those systems are limited to sending what the filters allows.<BR>
/// You can automatically assign systems to a filter.<BR>
/// You can automatically kick and possibly ban users that stay in a filter too long, or send the wrong message.<BR>
/// Each system is a member of either zero or one filters.<BR>
/// Add this plugin before any plugin you wish to filter (most likely just add this plugin before any other).
/// \ingroup MESSAGEFILTER_GROUP
class RAK_DLL_EXPORT MessageFilter : public PluginInterface2
{
public:
// GetInstance() and DestroyInstance(instance*)
STATIC_FACTORY_DECLARATIONS(MessageFilter)
MessageFilter();
virtual ~MessageFilter();
// --------------------------------------------------------------------------------------------
// User functions
// --------------------------------------------------------------------------------------------
/// Automatically add all new systems to a particular filter
/// Defaults to -1
/// \param[in] filterSetID Which filter to add new systems to. <0 for do not add.
void SetAutoAddNewConnectionsToFilter(int filterSetID);
/// Allow a range of message IDs
/// Always allowed by default: ID_CONNECTION_REQUEST_ACCEPTED through ID_DOWNLOAD_PROGRESS
/// Usually you specify a range to make it easier to add new enumerations without having to constantly refer back to this function.
/// \param[in] allow True to allow this message ID, false to disallow. By default, all messageIDs except the noted types are disallowed. This includes messages from other plugins!
/// \param[in] messageIDStart The first ID_* message to allow in the range. Inclusive.
/// \param[in] messageIDEnd The last ID_* message to allow in the range. Inclusive.
/// \param[in] filterSetID A user defined ID to represent a filter set. If no filter with this ID exists, one will be created with default settings.
void SetAllowMessageID(bool allow, int messageIDStart, int messageIDEnd,int filterSetID);
/// Allow a specific RPC4 call
/// \pre MessageFilter must be attached before RPC4
/// \param[in] uniqueID Identifier passed to RegisterFunction()
/// \param[in] filterSetID A user defined ID to represent a filter set. If no filter with this ID exists, one will be created with default settings.
void SetAllowRPC4(bool allow, const char* uniqueID, int filterSetID);
/// What action to take on a disallowed message. You can kick or not. You can add them to the ban list for some time
/// By default no action is taken. The message is simply ignored.
/// param[in] 0 for permanent ban, >0 for ban time in milliseconds.
/// \param[in] kickOnDisallowed kick the system that sent a disallowed message.
/// \param[in] banOnDisallowed ban the system that sent a disallowed message. See \a banTimeMS for the ban duration
/// \param[in] banTimeMS Passed to the milliseconds parameter of RakPeer::AddToBanList.
/// \param[in] filterSetID A user defined ID to represent a filter set. If no filter with this ID exists, one will be created with default settings.
void SetActionOnDisallowedMessage(bool kickOnDisallowed, bool banOnDisallowed, RakNet::TimeMS banTimeMS, int filterSetID);
/// Set a user callback to be called on an invalid message for a particular filterSet
/// \param[in] filterSetID A user defined ID to represent a filter set. If no filter with this ID exists, one will be created with default settings.
/// \param[in] userData A pointer passed with the callback
/// \param[in] invalidMessageCallback A pointer to a C function to be called back with the specified parameters.
void SetDisallowedMessageCallback(int filterSetID, void *userData, void (*invalidMessageCallback)(RakPeerInterface *peer, AddressOrGUID addressOrGUID, int filterSetID, void *userData, unsigned char messageID));
/// Set a user callback to be called when a user is disconnected due to SetFilterMaxTime
/// \param[in] filterSetID A user defined ID to represent a filter set. If no filter with this ID exists, one will be created with default settings.
/// \param[in] userData A pointer passed with the callback
/// \param[in] invalidMessageCallback A pointer to a C function to be called back with the specified parameters.
void SetTimeoutCallback(int filterSetID, void *userData, void (*invalidMessageCallback)(RakPeerInterface *peer, AddressOrGUID addressOrGUID, int filterSetID, void *userData));
/// Limit how long a connection can stay in a particular filterSetID. After this time, the connection is kicked and possibly banned.
/// By default there is no limit to how long a connection can stay in a particular filter set.
/// \param[in] allowedTimeMS How many milliseconds to allow a connection to stay in this filter set.
/// \param[in] banOnExceed True or false to ban the system, or not, when \a allowedTimeMS is exceeded
/// \param[in] banTimeMS Passed to the milliseconds parameter of RakPeer::AddToBanList.
/// \param[in] filterSetID A user defined ID to represent a filter set. If no filter with this ID exists, one will be created with default settings.
void SetFilterMaxTime(int allowedTimeMS, bool banOnExceed, RakNet::TimeMS banTimeMS, int filterSetID);
/// Get the filterSetID a system is using. Returns -1 for none.
/// \param[in] addressOrGUID The system we are referring to
int GetSystemFilterSet(AddressOrGUID addressOrGUID);
/// Assign a system to a filter set.
/// Systems are automatically added to filter sets (or not) based on SetAutoAddNewConnectionsToFilter()
/// This function is used to change the filter set a system is using, to add it to a new filter set, or to remove it from all existin filter sets.
/// \param[in] addressOrGUID The system we are referring to
/// \param[in] filterSetID A user defined ID to represent a filter set. If no filter with this ID exists, one will be created with default settings. If -1, the system will be removed from all filter sets.
void SetSystemFilterSet(AddressOrGUID addressOrGUID, int filterSetID);
/// Returns the number of systems subscribed to a particular filter set
/// Using anything other than -1 for \a filterSetID is slow, so you should store the returned value.
/// \param[in] filterSetID The filter set to limit to. Use -1 for none (just returns the total number of filter systems in that case).
unsigned GetSystemCount(int filterSetID) const;
/// Returns the total number of filter sets.
/// \return The total number of filter sets.
unsigned GetFilterSetCount(void) const;
/// Returns the ID of a filter set, by index
/// \param[in] An index between 0 and GetFilterSetCount()-1 inclusive
int GetFilterSetIDByIndex(unsigned index);
/// Delete a FilterSet. All systems formerly subscribed to this filter are now unrestricted.
/// \param[in] filterSetID The ID of the filter set to delete.
void DeleteFilterSet(int filterSetID);
// --------------------------------------------------------------------------------------------
// Packet handling functions
// --------------------------------------------------------------------------------------------
virtual void Update(void);
virtual PluginReceiveResult OnReceive(Packet *packet);
virtual void OnNewConnection(const SystemAddress &systemAddress, RakNetGUID rakNetGUID, bool isIncoming);
virtual void OnClosedConnection(const SystemAddress &systemAddress, RakNetGUID rakNetGUID, PI2_LostConnectionReason lostConnectionReason );
protected:
void Clear(void);
void DeallocateFilterSet(FilterSet *filterSet);
FilterSet* GetFilterSetByID(int filterSetID);
void OnInvalidMessage(FilterSet *filterSet, AddressOrGUID systemAddress, unsigned char messageID);
DataStructures::OrderedList<int, FilterSet*, FilterSetComp> filterList;
// Change to guid
DataStructures::Hash<AddressOrGUID, FilteredSystem, 2048, AddressOrGUID::ToInteger> systemList;
int autoAddNewConnectionsToFilter;
RakNet::Time whenLastTimeoutCheck;
};
} // namespace RakNet
#endif
#endif // _RAKNET_SUPPORT_*

View File

@@ -0,0 +1,403 @@
/// \file
/// \brief All the message identifiers used by RakNet. Message identifiers comprise the first byte of any message.
///
/// This file is part of RakNet Copyright 2003 Jenkins Software LLC
///
/// Usage of RakNet is subject to the appropriate license agreement.
#ifndef __MESSAGE_IDENTIFIERS_H
#define __MESSAGE_IDENTIFIERS_H
#if defined(RAKNET_USE_CUSTOM_PACKET_IDS)
#include "CustomPacketIdentifiers.hpp"
#else
enum OutOfBandIdentifiers
{
ID_NAT_ESTABLISH_UNIDIRECTIONAL,
ID_NAT_ESTABLISH_BIDIRECTIONAL,
ID_NAT_TYPE_DETECT,
ID_ROUTER_2_REPLY_TO_SENDER_PORT,
ID_ROUTER_2_REPLY_TO_SPECIFIED_PORT,
ID_ROUTER_2_MINI_PUNCH_REPLY,
ID_ROUTER_2_MINI_PUNCH_REPLY_BOUNCE,
ID_XBOX_360_VOICE,
ID_XBOX_360_GET_NETWORK_ROOM,
ID_XBOX_360_RETURN_NETWORK_ROOM,
};
/// You should not edit the file MessageIdentifiers.h as it is a part of RakNet static library
/// To define your own message id, define an enum following the code example that follows.
///
/// \code
/// enum {
/// ID_MYPROJECT_MSG_1 = ID_USER_PACKET_ENUM,
/// ID_MYPROJECT_MSG_2,
/// ...
/// };
/// \endcode
///
/// \note All these enumerations should be casted to (unsigned char) before writing them to RakNet::BitStream
enum DefaultMessageIDTypes
{
//
// RESERVED TYPES - DO NOT CHANGE THESE
// All types from RakPeer
//
/// These types are never returned to the user.
/// Ping from a connected system. Update timestamps (internal use only)
ID_CONNECTED_PING,
/// Ping from an unconnected system. Reply but do not update timestamps. (internal use only)
ID_UNCONNECTED_PING,
/// Ping from an unconnected system. Only reply if we have open connections. Do not update timestamps. (internal use only)
ID_UNCONNECTED_PING_OPEN_CONNECTIONS,
/// Pong from a connected system. Update timestamps (internal use only)
ID_CONNECTED_PONG,
/// A reliable packet to detect lost connections (internal use only)
ID_DETECT_LOST_CONNECTIONS,
/// C2S: Initial query: Header(1), OfflineMesageID(16), Protocol number(1), Pad(toMTU), sent with no fragment set.
/// If protocol fails on server, returns ID_INCOMPATIBLE_PROTOCOL_VERSION to client
ID_OPEN_CONNECTION_REQUEST_1,
/// S2C: Header(1), OfflineMesageID(16), server GUID(8), HasSecurity(1), Cookie(4, if HasSecurity)
/// , public key (if do security is true), MTU(2). If public key fails on client, returns ID_PUBLIC_KEY_MISMATCH
ID_OPEN_CONNECTION_REPLY_1,
/// C2S: Header(1), OfflineMesageID(16), Cookie(4, if HasSecurity is true on the server), clientSupportsSecurity(1 bit),
/// handshakeChallenge (if has security on both server and client), remoteBindingAddress(6), MTU(2), client GUID(8)
/// Connection slot allocated if cookie is valid, server is not full, GUID and IP not already in use.
ID_OPEN_CONNECTION_REQUEST_2,
/// S2C: Header(1), OfflineMesageID(16), server GUID(8), mtu(2), doSecurity(1 bit), handshakeAnswer (if do security is true)
ID_OPEN_CONNECTION_REPLY_2,
/// C2S: Header(1), GUID(8), Timestamp, HasSecurity(1), Proof(32)
ID_CONNECTION_REQUEST,
/// RakPeer - Remote system requires secure connections, pass a public key to RakPeerInterface::Connect()
ID_REMOTE_SYSTEM_REQUIRES_PUBLIC_KEY,
/// RakPeer - We passed a public key to RakPeerInterface::Connect(), but the other system did not have security turned on
ID_OUR_SYSTEM_REQUIRES_SECURITY,
/// RakPeer - Wrong public key passed to RakPeerInterface::Connect()
ID_PUBLIC_KEY_MISMATCH,
/// RakPeer - Same as ID_ADVERTISE_SYSTEM, but intended for internal use rather than being passed to the user.
/// Second byte indicates type. Used currently for NAT punchthrough for receiver port advertisement. See ID_NAT_ADVERTISE_RECIPIENT_PORT
ID_OUT_OF_BAND_INTERNAL,
/// If RakPeerInterface::Send() is called where PacketReliability contains _WITH_ACK_RECEIPT, then on a later call to
/// RakPeerInterface::Receive() you will get ID_SND_RECEIPT_ACKED or ID_SND_RECEIPT_LOSS. The message will be 5 bytes long,
/// and bytes 1-4 inclusive will contain a number in native order containing a number that identifies this message.
/// This number will be returned by RakPeerInterface::Send() or RakPeerInterface::SendList(). ID_SND_RECEIPT_ACKED means that
/// the message arrived
ID_SND_RECEIPT_ACKED,
/// If RakPeerInterface::Send() is called where PacketReliability contains UNRELIABLE_WITH_ACK_RECEIPT, then on a later call to
/// RakPeerInterface::Receive() you will get ID_SND_RECEIPT_ACKED or ID_SND_RECEIPT_LOSS. The message will be 5 bytes long,
/// and bytes 1-4 inclusive will contain a number in native order containing a number that identifies this message. This number
/// will be returned by RakPeerInterface::Send() or RakPeerInterface::SendList(). ID_SND_RECEIPT_LOSS means that an ack for the
/// message did not arrive (it may or may not have been delivered, probably not). On disconnect or shutdown, you will not get
/// ID_SND_RECEIPT_LOSS for unsent messages, you should consider those messages as all lost.
ID_SND_RECEIPT_LOSS,
//
// USER TYPES - DO NOT CHANGE THESE
//
/// RakPeer - In a client/server environment, our connection request to the server has been accepted.
ID_CONNECTION_REQUEST_ACCEPTED,
/// RakPeer - Sent to the player when a connection request cannot be completed due to inability to connect.
ID_CONNECTION_ATTEMPT_FAILED,
/// RakPeer - Sent a connect request to a system we are currently connected to.
ID_ALREADY_CONNECTED,
/// RakPeer - A remote system has successfully connected.
ID_NEW_INCOMING_CONNECTION,
/// RakPeer - The system we attempted to connect to is not accepting new connections.
ID_NO_FREE_INCOMING_CONNECTIONS,
/// RakPeer - The system specified in Packet::systemAddress has disconnected from us. For the client, this would mean the
/// server has shutdown.
ID_DISCONNECTION_NOTIFICATION,
/// RakPeer - Reliable packets cannot be delivered to the system specified in Packet::systemAddress. The connection to that
/// system has been closed.
ID_CONNECTION_LOST,
/// RakPeer - We are banned from the system we attempted to connect to.
ID_CONNECTION_BANNED,
/// RakPeer - The remote system is using a password and has refused our connection because we did not set the correct password.
ID_INVALID_PASSWORD,
// RAKNET_PROTOCOL_VERSION in RakNetVersion.h does not match on the remote system what we have on our system
// This means the two systems cannot communicate.
// The 2nd byte of the message contains the value of RAKNET_PROTOCOL_VERSION for the remote system
ID_INCOMPATIBLE_PROTOCOL_VERSION,
// Means that this IP address connected recently, and can't connect again as a security measure. See
/// RakPeer::SetLimitIPConnectionFrequency()
ID_IP_RECENTLY_CONNECTED,
/// RakPeer - The sizeof(RakNetTime) bytes following this byte represent a value which is automatically modified by the difference
/// in system times between the sender and the recipient. Requires that you call SetOccasionalPing.
ID_TIMESTAMP,
/// RakPeer - Pong from an unconnected system. First byte is ID_UNCONNECTED_PONG, second sizeof(RakNet::TimeMS) bytes is the ping,
/// following bytes is system specific enumeration data.
/// Read using bitstreams
ID_UNCONNECTED_PONG,
/// RakPeer - Inform a remote system of our IP/Port. On the recipient, all data past ID_ADVERTISE_SYSTEM is whatever was passed to
/// the data parameter
ID_ADVERTISE_SYSTEM,
// RakPeer - Downloading a large message. Format is ID_DOWNLOAD_PROGRESS (MessageID), partCount (unsigned int),
/// partTotal (unsigned int),
/// partLength (unsigned int), first part data (length <= MAX_MTU_SIZE). See the three parameters partCount, partTotal
/// and partLength in OnFileProgress in FileListTransferCBInterface.h
ID_DOWNLOAD_PROGRESS,
/// ConnectionGraph2 plugin - In a client/server environment, a client other than ourselves has disconnected gracefully.
/// Packet::systemAddress is modified to reflect the systemAddress of this client.
ID_REMOTE_DISCONNECTION_NOTIFICATION,
/// ConnectionGraph2 plugin - In a client/server environment, a client other than ourselves has been forcefully dropped.
/// Packet::systemAddress is modified to reflect the systemAddress of this client.
ID_REMOTE_CONNECTION_LOST,
/// ConnectionGraph2 plugin: Bytes 1-4 = count. for (count items) contains {SystemAddress, RakNetGUID, 2 byte ping}
ID_REMOTE_NEW_INCOMING_CONNECTION,
/// FileListTransfer plugin - Setup data
ID_FILE_LIST_TRANSFER_HEADER,
/// FileListTransfer plugin - A file
ID_FILE_LIST_TRANSFER_FILE,
// Ack for reference push, to send more of the file
ID_FILE_LIST_REFERENCE_PUSH_ACK,
/// DirectoryDeltaTransfer plugin - Request from a remote system for a download of a directory
ID_DDT_DOWNLOAD_REQUEST,
/// RakNetTransport plugin - Transport provider message, used for remote console
ID_TRANSPORT_STRING,
/// ReplicaManager plugin - Create an object
ID_REPLICA_MANAGER_CONSTRUCTION,
/// ReplicaManager plugin - Changed scope of an object
ID_REPLICA_MANAGER_SCOPE_CHANGE,
/// ReplicaManager plugin - Serialized data of an object
ID_REPLICA_MANAGER_SERIALIZE,
/// ReplicaManager plugin - New connection, about to send all world objects
ID_REPLICA_MANAGER_DOWNLOAD_STARTED,
/// ReplicaManager plugin - Finished downloading all serialized objects
ID_REPLICA_MANAGER_DOWNLOAD_COMPLETE,
/// RakVoice plugin - Open a communication channel
ID_RAKVOICE_OPEN_CHANNEL_REQUEST,
/// RakVoice plugin - Communication channel accepted
ID_RAKVOICE_OPEN_CHANNEL_REPLY,
/// RakVoice plugin - Close a communication channel
ID_RAKVOICE_CLOSE_CHANNEL,
/// RakVoice plugin - Voice data
ID_RAKVOICE_DATA,
/// Autopatcher plugin - Get a list of files that have changed since a certain date
ID_AUTOPATCHER_GET_CHANGELIST_SINCE_DATE,
/// Autopatcher plugin - A list of files to create
ID_AUTOPATCHER_CREATION_LIST,
/// Autopatcher plugin - A list of files to delete
ID_AUTOPATCHER_DELETION_LIST,
/// Autopatcher plugin - A list of files to get patches for
ID_AUTOPATCHER_GET_PATCH,
/// Autopatcher plugin - A list of patches for a list of files
ID_AUTOPATCHER_PATCH_LIST,
/// Autopatcher plugin - Returned to the user: An error from the database repository for the autopatcher.
ID_AUTOPATCHER_REPOSITORY_FATAL_ERROR,
/// Autopatcher plugin - Finished getting all files from the autopatcher
ID_AUTOPATCHER_FINISHED_INTERNAL,
ID_AUTOPATCHER_FINISHED,
/// Autopatcher plugin - Returned to the user: You must restart the application to finish patching.
ID_AUTOPATCHER_RESTART_APPLICATION,
/// NATPunchthrough plugin: internal
ID_NAT_PUNCHTHROUGH_REQUEST,
/// NATPunchthrough plugin: internal
ID_NAT_GROUP_PUNCHTHROUGH_REQUEST,
/// NATPunchthrough plugin: internal
ID_NAT_GROUP_PUNCHTHROUGH_REPLY,
/// NATPunchthrough plugin: internal
ID_NAT_CONNECT_AT_TIME,
/// NATPunchthrough plugin: internal
ID_NAT_GET_MOST_RECENT_PORT,
/// NATPunchthrough plugin: internal
ID_NAT_CLIENT_READY,
/// NATPunchthrough plugin: internal
ID_NAT_GROUP_PUNCHTHROUGH_FAILURE_NOTIFICATION,
/// NATPunchthrough plugin: Destination system is not connected to the server. Bytes starting at offset 1 contains the
/// RakNetGUID destination field of NatPunchthroughClient::OpenNAT().
ID_NAT_TARGET_NOT_CONNECTED,
/// NATPunchthrough plugin: Destination system is not responding to ID_NAT_GET_MOST_RECENT_PORT. Possibly the plugin is not installed.
/// Bytes starting at offset 1 contains the RakNetGUID destination field of NatPunchthroughClient::OpenNAT().
ID_NAT_TARGET_UNRESPONSIVE,
/// NATPunchthrough plugin: The server lost the connection to the destination system while setting up punchthrough.
/// Possibly the plugin is not installed. Bytes starting at offset 1 contains the RakNetGUID destination
/// field of NatPunchthroughClient::OpenNAT().
ID_NAT_CONNECTION_TO_TARGET_LOST,
/// NATPunchthrough plugin: This punchthrough is already in progress. Possibly the plugin is not installed.
/// Bytes starting at offset 1 contains the RakNetGUID destination field of NatPunchthroughClient::OpenNAT().
ID_NAT_ALREADY_IN_PROGRESS,
/// NATPunchthrough plugin: This message is generated on the local system, and does not come from the network.
/// packet::guid contains the destination field of NatPunchthroughClient::OpenNAT(). Byte 1 contains 1 if you are the sender, 0 if not
ID_NAT_PUNCHTHROUGH_FAILED,
/// NATPunchthrough plugin: Punchthrough succeeded. See packet::systemAddress and packet::guid. Byte 1 contains 1 if you are the sender,
/// 0 if not. You can now use RakPeer::Connect() or other calls to communicate with this system.
ID_NAT_PUNCHTHROUGH_SUCCEEDED,
/// NATPunchthrough plugin: OpenNATGroup failed.
/// packet::guid contains the facilitator field of NatPunchthroughClient::OpenNAT()
/// Data format starts at byte 1:<BR>
/// (char) passedSystemsCount,<BR>
/// (RakNetGuid, SystemAddress) (for passedSystemsCount),<BR>
/// (char) ignoredSystemsCount (caused by ID_NAT_TARGET_NOT_CONNECTED, ID_NAT_CONNECTION_TO_TARGET_LOST, ID_NAT_TARGET_UNRESPONSIVE),<BR>
/// RakNetGuid (for ignoredSystemsCount),<BR>
/// (char) failedSystemsCount,<BR>
/// RakNetGuid (for failedSystemsCount)<BR>
ID_NAT_GROUP_PUNCH_FAILED,
/// NATPunchthrough plugin: OpenNATGroup succeeded.
/// packet::guid contains the facilitator field of NatPunchthroughClient::OpenNAT()
/// See ID_NAT_GROUP_PUNCH_FAILED for data format
ID_NAT_GROUP_PUNCH_SUCCEEDED,
/// ReadyEvent plugin - Set the ready state for a particular system
/// First 4 bytes after the message contains the id
ID_READY_EVENT_SET,
/// ReadyEvent plugin - Unset the ready state for a particular system
/// First 4 bytes after the message contains the id
ID_READY_EVENT_UNSET,
/// All systems are in state ID_READY_EVENT_SET
/// First 4 bytes after the message contains the id
ID_READY_EVENT_ALL_SET,
/// \internal, do not process in your game
/// ReadyEvent plugin - Request of ready event state - used for pulling data when newly connecting
ID_READY_EVENT_QUERY,
/// Lobby packets. Second byte indicates type.
ID_LOBBY_GENERAL,
// RPC3, RPC4Plugin error
ID_RPC_REMOTE_ERROR,
/// Plugin based replacement for RPC system
ID_RPC_PLUGIN,
/// FileListTransfer transferring large files in chunks that are read only when needed, to save memory
ID_FILE_LIST_REFERENCE_PUSH,
/// Force the ready event to all set
ID_READY_EVENT_FORCE_ALL_SET,
/// Rooms function
ID_ROOMS_EXECUTE_FUNC,
ID_ROOMS_LOGON_STATUS,
ID_ROOMS_HANDLE_CHANGE,
/// Lobby2 message
ID_LOBBY2_SEND_MESSAGE,
ID_LOBBY2_SERVER_ERROR,
/// Informs user of a new host GUID. Packet::Guid contains this new host RakNetGuid. The old host can be read out using BitStream->Read(RakNetGuid) starting on byte 1
/// This is not returned until connected to a remote system
/// If the oldHost is UNASSIGNED_RAKNET_GUID, then this is the first time the host has been determined
ID_FCM2_NEW_HOST,
/// \internal For FullyConnectedMesh2 plugin
ID_FCM2_REQUEST_FCMGUID,
/// \internal For FullyConnectedMesh2 plugin
ID_FCM2_RESPOND_CONNECTION_COUNT,
/// \internal For FullyConnectedMesh2 plugin
ID_FCM2_INFORM_FCMGUID,
/// \internal For FullyConnectedMesh2 plugin
ID_FCM2_UPDATE_MIN_TOTAL_CONNECTION_COUNT,
/// UDP proxy messages. Second byte indicates type.
ID_UDP_PROXY_GENERAL,
/// SQLite3Plugin - execute
ID_SQLite3_EXEC,
/// SQLite3Plugin - Remote database is unknown
ID_SQLite3_UNKNOWN_DB,
/// Events happening with SQLiteClientLoggerPlugin
ID_SQLLITE_LOGGER,
/// Sent to NatTypeDetectionServer
ID_NAT_TYPE_DETECTION_REQUEST,
/// Sent to NatTypeDetectionClient. Byte 1 contains the type of NAT detected.
ID_NAT_TYPE_DETECTION_RESULT,
/// Used by the router2 plugin
ID_ROUTER_2_INTERNAL,
/// No path is available or can be established to the remote system
/// Packet::guid contains the endpoint guid that we were trying to reach
ID_ROUTER_2_FORWARDING_NO_PATH,
/// \brief You can now call connect, ping, or other operations to the destination system.
///
/// Connect as follows:
///
/// RakNet::BitStream bs(packet->data, packet->length, false);
/// bs.IgnoreBytes(sizeof(MessageID));
/// RakNetGUID endpointGuid;
/// bs.Read(endpointGuid);
/// unsigned short sourceToDestPort;
/// bs.Read(sourceToDestPort);
/// char ipAddressString[32];
/// packet->systemAddress.ToString(false, ipAddressString);
/// rakPeerInterface->Connect(ipAddressString, sourceToDestPort, 0,0);
ID_ROUTER_2_FORWARDING_ESTABLISHED,
/// The IP address for a forwarded connection has changed
/// Read endpointGuid and port as per ID_ROUTER_2_FORWARDING_ESTABLISHED
ID_ROUTER_2_REROUTED,
/// \internal Used by the team balancer plugin
ID_TEAM_BALANCER_INTERNAL,
/// Cannot switch to the desired team because it is full. However, if someone on that team leaves, you will
/// get ID_TEAM_BALANCER_SET_TEAM later. Byte 1 contains the team you requested to join. Following bytes contain NetworkID of which member.
ID_TEAM_BALANCER_REQUESTED_TEAM_CHANGE_PENDING,
/// Cannot switch to the desired team because all teams are locked. However, if someone on that team leaves,
/// you will get ID_TEAM_BALANCER_SET_TEAM later. Byte 1 contains the team you requested to join.
ID_TEAM_BALANCER_TEAMS_LOCKED,
/// Team balancer plugin informing you of your team. Byte 1 contains the team you requested to join. Following bytes contain NetworkID of which member.
ID_TEAM_BALANCER_TEAM_ASSIGNED,
/// Gamebryo Lightspeed integration
ID_LIGHTSPEED_INTEGRATION,
/// XBOX integration
ID_XBOX_LOBBY,
/// The password we used to challenge the other system passed, meaning the other system has called TwoWayAuthentication::AddPassword() with the same password we passed to TwoWayAuthentication::Challenge()
/// You can read the identifier used to challenge as follows:
/// RakNet::BitStream bs(packet->data, packet->length, false); bs.IgnoreBytes(sizeof(RakNet::MessageID)); RakNet::RakString password; bs.Read(password);
ID_TWO_WAY_AUTHENTICATION_INCOMING_CHALLENGE_SUCCESS,
ID_TWO_WAY_AUTHENTICATION_OUTGOING_CHALLENGE_SUCCESS,
/// A remote system sent us a challenge using TwoWayAuthentication::Challenge(), and the challenge failed.
/// If the other system must pass the challenge to stay connected, you should call RakPeer::CloseConnection() to terminate the connection to the other system.
ID_TWO_WAY_AUTHENTICATION_INCOMING_CHALLENGE_FAILURE,
/// The other system did not add the password we used to TwoWayAuthentication::AddPassword()
/// You can read the identifier used to challenge as follows:
/// RakNet::BitStream bs(packet->data, packet->length, false); bs.IgnoreBytes(sizeof(MessageID)); RakNet::RakString password; bs.Read(password);
ID_TWO_WAY_AUTHENTICATION_OUTGOING_CHALLENGE_FAILURE,
/// The other system did not respond within a timeout threshhold. Either the other system is not running the plugin or the other system was blocking on some operation for a long time.
/// You can read the identifier used to challenge as follows:
/// RakNet::BitStream bs(packet->data, packet->length, false); bs.IgnoreBytes(sizeof(MessageID)); RakNet::RakString password; bs.Read(password);
ID_TWO_WAY_AUTHENTICATION_OUTGOING_CHALLENGE_TIMEOUT,
/// \internal
ID_TWO_WAY_AUTHENTICATION_NEGOTIATION,
/// CloudClient / CloudServer
ID_CLOUD_POST_REQUEST,
ID_CLOUD_RELEASE_REQUEST,
ID_CLOUD_GET_REQUEST,
ID_CLOUD_GET_RESPONSE,
ID_CLOUD_UNSUBSCRIBE_REQUEST,
ID_CLOUD_SERVER_TO_SERVER_COMMAND,
ID_CLOUD_SUBSCRIPTION_NOTIFICATION,
// So I can add more without changing user enumerations
ID_RESERVED_1,
ID_RESERVED_2,
ID_RESERVED_3,
ID_RESERVED_4,
ID_RESERVED_5,
ID_RESERVED_6,
ID_RESERVED_7,
ID_RESERVED_8,
ID_RESERVED_9,
// For the user to use. Start your first enumeration at this value.
ID_USER_PACKET_ENUM,
//-------------------------------------------------------------------------------------------------------------
};
#endif // RAKNET_USE_CUSTOM_PACKET_IDS
#endif

Some files were not shown because too many files have changed in this diff Show More