diff --git a/ChangeLog b/ChangeLog new file mode 100644 index 0000000..712930c --- /dev/null +++ b/ChangeLog @@ -0,0 +1,14 @@ +Unity Proxy Server + +2.0.1f1 (25 Feb 2011) + +- (common) Added option for launching to background (daemon mode) on Linux. +- (common) PID file now written after launch. +- (common) Launch script for Linux added (start/stop/restart), see config/unity-proxyserver. +- (common) Command line parameters modified to match the other servers. +- (common) All servers now use same logging facility +- Problem when parsing server proxied RPC messages fixed. + +2.0.0f1 (22 Sep 2011) + +- (common) Upgraded to use RakNet version 3.72 diff --git a/Common/Log.cpp b/Common/Log.cpp new file mode 100644 index 0000000..04cffb2 --- /dev/null +++ b/Common/Log.cpp @@ -0,0 +1,149 @@ +#include "Log.h" +#include +#include +#include + +int Log::sDebugLevel = kFullDebug; +FILE* Log::outputStream = stdout; +char* Log::logfile; +bool Log::printStats; + +Log::~Log() +{ + if (outputStream) + fclose(outputStream); +} + +void Log::print_timestamp(const char* msg) +{ + if (!outputStream) + return; + time_t rawtime; + struct tm * timeinfo; + time ( &rawtime ); + timeinfo = localtime ( &rawtime ); + fprintf(outputStream, "%02d-%02d-%d %02d:%02d:%02d\t%s\t",timeinfo->tm_mday, 1+timeinfo->tm_mon, 1900+timeinfo->tm_year, timeinfo->tm_hour, timeinfo->tm_min, timeinfo->tm_sec, msg); +} + +void Log::print_log(const char* format, ...) +{ + if (!outputStream) + return; + print_timestamp("LOG");; + va_list va; + va_start( va, format ); + vfprintf(outputStream, format, va); +} + +void Log::warn_log(const char* format, ...) +{ + if (sDebugLevel >= kWarnings) + { + if (!outputStream) + return; + print_timestamp("WARN");; + va_list va; + va_start( va, format ); + vfprintf(outputStream, format, va); + } +} + +void Log::info_log(const char* format, ...) +{ + if (sDebugLevel >= kInformational) + { + if (!outputStream) + return; + print_timestamp("INFO");; + va_list va; + va_start( va, format ); + vfprintf(outputStream, format, va); + } +} + +void Log::debug_log(const char* format, ...) +{ + if (sDebugLevel >= kFullDebug) + { + if (!outputStream) + return; + print_timestamp("DEBUG"); + va_list va; + va_start( va, format ); + vfprintf(outputStream, format, va); + } +} + +void Log::stats_log(const char* format, ...) +{ + if (sDebugLevel >= kOnlyErrors && printStats) + { + if (!outputStream) + return; + print_timestamp("STATS"); + va_list va; + va_start( va, format ); + vfprintf(outputStream, format, va); + } +} + + +void Log::startup_log(const char* format, ...) +{ + if (!outputStream) + return; + print_timestamp(""); + va_list va; + va_start( va, format ); + vfprintf(outputStream, format, va); +} + +void Log::error_log(const char* format, ...) +{ + if (!outputStream) + return; + print_timestamp("ERROR"); + va_list va; + va_start( va, format ); + vfprintf(outputStream, format, va); +} + +bool Log::EnableFileLogging(char* file) +{ + logfile = file; + outputStream = fopen(logfile, "a"); +#ifndef WIN32 + setlinebuf(outputStream); +#endif + return true; +} + +void Log::RotateLogFile(int sig) +{ + if (logfile != NULL) + { + char savedLogFile[MAX_LOG_NAME_SIZE]; + fclose(outputStream); // Does a flush internally + time_t currentTime = time(0); + if (strftime( savedLogFile, MAX_LOG_NAME_SIZE, "masterserver_%d%m%y%H%M%S.log", localtime(¤tTime) ) == 0) + print_log("Error creating new log file"); + rename(logfile, savedLogFile); + outputStream = fopen(logfile, "a"); +#ifndef WIN32 + setlinebuf(outputStream); +#endif + } + else + { + print_log("Log file name not set, cannot rotate"); + } +} + +long Log::GetLogSize() +{ + //int position = 0; + //if (fgetpos(outputStream, (fpos_t*)&position) != 0) + // Log::error_log("Error reading log file size\n"); + return ftell(outputStream); + //return position; +} diff --git a/Common/Log.h b/Common/Log.h new file mode 100644 index 0000000..98a165c --- /dev/null +++ b/Common/Log.h @@ -0,0 +1,46 @@ +#pragma once +#include + +class Log +{ + private: + static char* logfile; + static FILE* outputStream; + static const int MAX_LOG_NAME_SIZE = 50; + + public: + static int sDebugLevel; + static bool printStats; + + Log(); + ~Log(); + + static void print_log(const char* format, ...); + + static void error_log(const char* format, ...); + static void info_log(const char* format, ...); + static void warn_log(const char* format, ...); + static void debug_log(const char* format, ...); + static void stats_log(const char* format, ...); + + static void startup_log(const char* format, ...); + static bool EnableFileLogging(char* file); + static void RotateLogFile(int sig); + static long GetLogSize(); + + private: + static void print_timestamp(const char* msg); +}; + +// Debug levels used when logging +enum { + // Only print critical error messages (nice clean log) + kOnlyErrors, + // Print probably harmless warning messages + kWarnings, + // Print various informational messages + kInformational, + // Print a lot of per event log messages (logs will be huge) + kFullDebug +}; + diff --git a/Common/Utility.cpp b/Common/Utility.cpp new file mode 100644 index 0000000..35e633b --- /dev/null +++ b/Common/Utility.cpp @@ -0,0 +1,34 @@ +#include "Log.h" +#ifndef WIN32 +#include +#include + +bool WriteProcessID(char* processFullPath, char* pidFile, int bufSize) +{ + int pid = getpid(); + int lastSlash = -1; + for (size_t i=0; i 0)) + { + perror("Failed to write to pid file\n"); + return false; + } + fclose(pidHandle); + } + return true; +} +#endif diff --git a/Common/Utility.h b/Common/Utility.h new file mode 100644 index 0000000..e280c18 --- /dev/null +++ b/Common/Utility.h @@ -0,0 +1,2 @@ + +bool WriteProcessID(char* processFullPath, char* pidFile, int bufSize); diff --git a/MRB-ChangeList.txt b/MRB-ChangeList.txt new file mode 100644 index 0000000..9aa287f --- /dev/null +++ b/MRB-ChangeList.txt @@ -0,0 +1,12 @@ +Sept. 26, 2012: +- Makefile: Build ProxyServer.cpp with no debug symbols by default as it may affect performance. + +Sept. 18, 2012: +- RakPeer.cpp: Allow up to 256 (arbitrary higher number) server listen ports instead of just 32 +- ProxyServer.cpp: Few more fixes: 1. Specifying a larger range of ports other than the default works now 2. Use array delete when deleting array 3. Allocate correct number of sockets for the number of ports requested (needed one more for the listen port) + +Aug. 24, 2012: +- ProxyServer.cpp,h: Close proxy connections to clients when host of that port disconnects. + +Aug. 22, 2012: +- ProxyServer.cpp: ProxyServer bug fixes: 1. Use the correct Receive function so we don't sometimes miss RPCs. 2. Immediately fetch next packet after processing packets from clients, so we don't slowly build more and more latency as unprocessed packets build up. 3. Deallocate client packets after they are processed. \ No newline at end of file diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..0079986 --- /dev/null +++ b/Makefile @@ -0,0 +1,105 @@ +# Unity Proxy Server Makefile +# ------------------------------------- + +CC = g++ +DEFINES = -DUNITY_PROXYSERVER +CFLAGS = -Wall -lpthread $(DEFINES) +DEBUG = -ggdb +INCLUDE = . +PROGRAMNAME = ProxyServer +PROGRAMSOURCES = ProxyServer.cpp + +# ------------------------------------- + +RAKNET_INCLUDE = RakNet/Sources + +RAKNET_SOURCES = \ +$(RAKNET_INCLUDE)/RakNetworkFactory.cpp\ +$(RAKNET_INCLUDE)/BitStream.cpp\ +$(RAKNET_INCLUDE)/GetTime.cpp\ +$(RAKNET_INCLUDE)/RakPeer.cpp\ +$(RAKNET_INCLUDE)/BitStream_NoTemplate.cpp\ +$(RAKNET_INCLUDE)/RakSleep.cpp\ +$(RAKNET_INCLUDE)/CheckSum.cpp\ +$(RAKNET_INCLUDE)/Rand.cpp\ +$(RAKNET_INCLUDE)/ReliabilityLayer.cpp\ +$(RAKNET_INCLUDE)/LinuxStrings.cpp\ +$(RAKNET_INCLUDE)/ConsoleServer.cpp\ +$(RAKNET_INCLUDE)/Router.cpp\ +$(RAKNET_INCLUDE)/DS_BytePool.cpp\ +$(RAKNET_INCLUDE)/MessageFilter.cpp\ +$(RAKNET_INCLUDE)/SHA1.cpp\ +$(RAKNET_INCLUDE)/DS_ByteQueue.cpp\ +$(RAKNET_INCLUDE)/SimpleMutex.cpp\ +$(RAKNET_INCLUDE)/DS_HuffmanEncodingTree.cpp\ +$(RAKNET_INCLUDE)/NetworkIDManager.cpp\ +$(RAKNET_INCLUDE)/SocketLayer.cpp\ +$(RAKNET_INCLUDE)/DS_Table.cpp\ +$(RAKNET_INCLUDE)/NetworkIDObject.cpp\ +$(RAKNET_INCLUDE)/StringCompressor.cpp\ +$(RAKNET_INCLUDE)/DataBlockEncryptor.cpp\ +$(RAKNET_INCLUDE)/StringTable.cpp\ +$(RAKNET_INCLUDE)/DataCompressor.cpp\ +$(RAKNET_INCLUDE)/PacketFileLogger.cpp\ +$(RAKNET_INCLUDE)/SystemAddressList.cpp\ +$(RAKNET_INCLUDE)/DirectoryDeltaTransfer.cpp\ +$(RAKNET_INCLUDE)/PacketLogger.cpp\ +$(RAKNET_INCLUDE)/TCPInterface.cpp\ +$(RAKNET_INCLUDE)/EmailSender.cpp\ +$(RAKNET_INCLUDE)/TableSerializer.cpp\ +$(RAKNET_INCLUDE)/EncodeClassName.cpp\ +$(RAKNET_INCLUDE)/RPCMap.cpp\ +$(RAKNET_INCLUDE)/TelnetTransport.cpp\ +$(RAKNET_INCLUDE)/ExtendedOverlappedPool.cpp\ +$(RAKNET_INCLUDE)/RakNetCommandParser.cpp\ +$(RAKNET_INCLUDE)/ThreadsafePacketLogger.cpp\ +$(RAKNET_INCLUDE)/FileList.cpp\ +$(RAKNET_INCLUDE)/RakNetStatistics.cpp\ +$(RAKNET_INCLUDE)/_FindFirst.cpp\ +$(RAKNET_INCLUDE)/FileListTransfer.cpp\ +$(RAKNET_INCLUDE)/RakNetTransport.cpp\ +$(RAKNET_INCLUDE)/rijndael.cpp\ +$(RAKNET_INCLUDE)/FileOperations.cpp\ +$(RAKNET_INCLUDE)/RakNetTypes.cpp\ +$(RAKNET_INCLUDE)/BigInt.cpp\ +$(RAKNET_INCLUDE)/CCRakNetUDT.cpp\ +$(RAKNET_INCLUDE)/RakNetSocket.cpp\ +$(RAKNET_INCLUDE)/RakString.cpp\ +$(RAKNET_INCLUDE)/RSACrypt.cpp\ +$(RAKNET_INCLUDE)/RakMemoryOverride.cpp\ +$(RAKNET_INCLUDE)/SignaledEvent.cpp\ +$(RAKNET_INCLUDE)/SuperFastHash.cpp\ +$(RAKNET_INCLUDE)/PluginInterface2.cpp\ +$(RAKNET_INCLUDE)/Itoa.cpp\ +$(RAKNET_INCLUDE)/RakThread.cpp\ +$(RAKNET_INCLUDE)/NatPunchthroughClient.cpp + +RAKNET_OBJECTS = $(RAKNET_SOURCES:.cpp=.o) + +COMMON_INCLUDE = Common + +COMMON_SOURCES = \ +$(COMMON_INCLUDE)/Log.cpp\ +$(COMMON_INCLUDE)/Utility.cpp + +COMMON_OBJECTS = $(COMMON_SOURCES:.cpp=.o) + +all: $(COMMON_SOURCES) $(RAKNET_SOURCES) $(PROGRAMNAME) + +$(PROGRAMNAME): $(COMMON_OBJECTS) $(RAKNET_OBJECTS) +# $(CC) $(DEBUG) -I$(INCLUDE) -I$(RAKNET_INCLUDE) -I$(COMMON_INCLUDE) $(CFLAGS) $(COMMON_OBJECTS) $(RAKNET_OBJECTS) $(PROGRAMSOURCES) -o $(PROGRAMNAME) + $(CC) -I$(INCLUDE) -I$(RAKNET_INCLUDE) -I$(COMMON_INCLUDE) $(CFLAGS) $(COMMON_OBJECTS) $(RAKNET_OBJECTS) $(PROGRAMSOURCES) -o $(PROGRAMNAME) + chmod +x $(PROGRAMNAME) + +clean: + rm -f $(PROGRAMNAME) + +cleanall: + rm -f $(PROGRAMNAME) + rm -f $(RAKNET_OBJECTS) + rm -f $(COMMON_OBJECTS) + +# Compile all RakNet cpp source files (use commented line instead for DEBUG symbols!) +.cpp.o: + $(CC) -c -Wall -I$(INCLUDE) -I$(COMMON_INCLUDE) $(DEFINES) $< -o $@ +# $(CC) $(DEBUG) -c -Wall -I$(INCLUDE) -I$(COMMON_INCLUDE) $(DEFINES) $< -o $@ diff --git a/ProxyServer.cpp b/ProxyServer.cpp new file mode 100644 index 0000000..5fb8132 --- /dev/null +++ b/ProxyServer.cpp @@ -0,0 +1,906 @@ +#include "ProxyServer.h" +#include "Log.h" +#include "Utility.h" +#include "BitStream.h" +#include "StringCompressor.h" + +#include "RakPeerInterface.h" +#include "RakNetworkFactory.h" +#include "RakSleep.h" +#include "MessageIdentifiers.h" +#include "NatPunchthroughClient.h" +#include "SocketLayer.h" + +#ifdef WIN32 +#include +#include +#include +#include +#include +#else +#include +#include +#include +#endif + +#include +#include +#include +#include +#include +#include + +typedef std::list RelayQueue; +typedef std::map RelayMap; +typedef std::map ServerMap; +typedef std::list ServerPorts; + +RakPeerInterface *peer; +bool quit; +ServerPorts serverPorts; +ServerPorts usedPorts; +ServerMap serverMap; +RelayMap relayMap; +RelayQueue queue; +NatPunchthroughClient natPunchthrough; +SystemAddress facilitatorAddress = UNASSIGNED_SYSTEM_ADDRESS; + +//MRB 8.27.12 -- list of peers and which port they are using, so we can disconnect them if the port owner disconnects +typedef std::list PortUsers; +PortUsers portUsers; + +char* logfile = "proxyserver.log"; +const int fileBufSize = 1024; +char pidFile[fileBufSize]; + +void shutdown(int sig) +{ + Log::print_log("Shutting down\n\n"); + quit = true; +} + +void usage() +{ + printf("\nAccepted parameters are:\n\t" + "-p\tListen port (1-65535)\n\t" + "-d\tDaemon mode, run in the background\n\t" + "-l\tUse given log file\n\t" + "-e\tDebug level (0=OnlyErrors, 1=Warnings, 2=Informational(default), 2=FullDebug)\n\t" + "-c\tConnection count\n\t" + "-r\tRange of ports for proxied servers\n\t" + "-f\tFacilitator address(IP:port)\n\t" + "-i\tPassword for all connections\n\t" + "If any parameter is omitted the default value is used.\n"); +} + +void MsgClientInit(Packet *packet, SystemAddress targetAddress, char *password, int passwordLength, bool useNat, int clientVersion) +{ + + if (!peer->IsConnected(targetAddress)) + { + if (useNat) + { + Log::print_log("Doing NAT punch through to %s\n", targetAddress.ToString()); + // TODO: IMPLEMENT + //natPunchthrough.OpenNAT(<#int destination#>, <#int facilitator#>); + //natPunchthrough.Connect(targetAddress.ToString(false), targetAddress.port, password, passwordLength, facilitatorAddress); + } + else + { + Log::print_log("Connecting directly to server %s\n", targetAddress.ToString()); + peer->Connect(targetAddress.ToString(false), targetAddress.port, password, passwordLength, 0); + } + + // New packet will be ID(1) + SystemAddress(6) + ID(1) + proxy version(4) + client version(4) = 16 bytes + RelayItem item; + RakNet::BitStream stream; + stream.Write((unsigned char)ID_PROXY_MESSAGE); + stream.Write(packet->systemAddress); + stream.Write((unsigned char)ID_REQUEST_CLIENT_INIT); + stream.Write((int)PROXY_SERVER_PROTOCOL_VERSION); + stream.Write((int)clientVersion); + Log::print_log("Buffering client init, packet size is %d\n", stream.GetNumberOfBytesUsed()); + if (Log::sDebugLevel == kInformational) + { + for (int i=0; i<=stream.GetNumberOfBytesUsed(); i++) + { + printf("%x", stream.GetData()[i]); + } + printf("\n"); + } + item.packet = new char[stream.GetNumberOfBytesUsed()]; + memcpy(item.packet, stream.GetData(), stream.GetNumberOfBytesUsed()); + item.length = stream.GetNumberOfBytesUsed(); + item.target = targetAddress; + + queue.push_back(item); + Log::print_log("Target address %s, not connected. Sending connect request.\n", targetAddress.ToString()); + return; + } + else + { + RakNet::BitStream stream; + stream.Write((unsigned char)ID_PROXY_MESSAGE); + stream.Write(packet->systemAddress); + stream.Write((unsigned char)ID_REQUEST_CLIENT_INIT); + stream.Write((int)PROXY_SERVER_PROTOCOL_VERSION); + stream.Write(clientVersion); + + peer->Send(&stream, HIGH_PRIORITY, RELIABLE_ORDERED, 0, targetAddress, false); + char tmpip[32]; + strcpy(tmpip, packet->systemAddress.ToString()); + Log::print_log("Proxying client init message to server at %s, sender is %s\n", targetAddress.ToString(), tmpip); + } +} + +void MsgClientRelay(RakNet::BitStream &bitStream, Packet *packet, SystemAddress targetAddress) +{ + // If target address is not connected to us (the proxy), then we need to connect first + if (!peer->IsConnected(targetAddress)) + { + // If not connected we need to connect and queue the message for transmission + peer->Connect(targetAddress.ToString(false), targetAddress.port, NULL, 0, 0); + + // Current bitstream has ID(1) + SystemAddress(6) + int(4) = 11 prepended bytes + // New will prepend a proxy relay message ID(1) + SystemAddress(6) of sender = 7 prepended bytes + // Total message size thus decreases by 11 - 7 = 4 bytes + RelayItem item; + item.packet = new char[packet->length-4]; + item.packet[0] = ID_PROXY_MESSAGE; + memcpy(item.packet+1, (void*)(&packet->systemAddress), 6); + memcpy(item.packet+7, packet->data+11, packet->length-11); + item.length = packet->length-4; + item.target = targetAddress; + + queue.push_back(item); + Log::print_log("Target address %s, not connected. Sending connect request.\n", targetAddress.ToString()); + } + else + { + // Now we need to prepend proxy message ID + sender address to original message + // packet struct). + RakNet::BitStream stream; + stream.Write((unsigned char)ID_PROXY_MESSAGE); + stream.Write(packet->systemAddress); + stream.WriteBits(bitStream.GetData()+1, bitStream.GetNumberOfBitsUsed()-8, false); + + peer->Send(&stream, HIGH_PRIORITY, RELIABLE_ORDERED, 0, targetAddress, false); + char tmpip[32]; + strcpy(tmpip, packet->systemAddress.ToString()); + //Log::print_log("Proxying relay message to server at %s, sender is %s\n", targetAddress.ToString(), tmpip); + } +} + +void MsgClientRelayPassthrough(RakNet::BitStream &bitStream, Packet *packet, SystemAddress targetAddress) +{ + // Now we need to prepend proxy message ID + sender address to original message + RakNet::BitStream stream; + stream.Write((unsigned char)ID_PROXY_MESSAGE); + stream.Write(packet->systemAddress); + stream.WriteBits(bitStream.GetData(), bitStream.GetNumberOfBitsUsed(), false); + + peer->Send(&stream, HIGH_PRIORITY, RELIABLE_ORDERED, 0, targetAddress, false); +} + + +char* IDtoString(const int ID) +{ + static char tmp[32]; + switch (ID) + { + case ID_RPC: + strcpy(tmp, "ID_RPC"); + break; + case ID_TIMESTAMP: + strcpy(tmp, "ID_TIMESTAMP"); + break; + case ID_STATE_UPDATE: + strcpy(tmp, "ID_STATE_UPDATE"); + break; + case ID_STATE_INITIAL: + strcpy(tmp, "ID_STATE_INITIAL"); + break; + case ID_CLIENT_INIT: + strcpy(tmp,"ID_CLIENT_INIT"); + break; + case ID_REQUEST_CLIENT_INIT: + strcpy(tmp,"ID_REQUEST_CLIENT_INIT"); + break; + default: + sprintf(tmp, "Unknown ID %d", ID); + } + return tmp; +} + +void DebugServerRelay() +{ + Log::print_log("Connection count is %d\n", peer->NumberOfConnections()); + Log::print_log("Used ports: "); + if (Log::sDebugLevel == kInformational) + { + for (ServerPorts::iterator i = usedPorts.begin(); i != usedPorts.end(); i++) + printf("%d ", *i); + printf("\n"); + } + Log::print_log("Server ports: "); + if (Log::sDebugLevel == kInformational) + { + for (ServerPorts::iterator i = serverPorts.begin(); i != serverPorts.end(); i++) + printf("%d ", *i); + printf("\n"); + } + Log::print_log("Server map: "); + if (Log::sDebugLevel == kInformational) + { + for (ServerMap::iterator i = serverMap.begin(); i != serverMap.end(); i++) + printf("[%d %s] ", i->first, i->second.ToString()); + printf("\n"); + } +} + +void DebugClientRelay() +{ + Log::print_log("Connection count is %d\n", peer->NumberOfConnections()); + Log::print_log("Relay map: "); + if (Log::sDebugLevel == kInformational) + { + for (RelayMap::iterator i = relayMap.begin(); i != relayMap.end(); i++) + printf("[%s %s] ", i->first.ToString(), i->second.ToString()); + printf("\n"); + } + Log::print_log("Relay queue: "); + if (Log::sDebugLevel == kInformational) + { + for (RelayQueue::iterator i = queue.begin(); i != queue.end(); i++) + printf("%s ", ((RelayItem)*i).target.ToString()); + printf("\n"); + } +} + +// Check through relay map and disconnect all clients which were connected +// or attempting connection to this server +void CleanClient(SystemAddress serverAddress) +{ + RelayMap::iterator i = relayMap.begin(); + while (i != relayMap.end()) + { + if (i->second == serverAddress) + { + Log::print_log("Disconnecting client %s\n", i->first.ToString()); + peer->CloseConnection(i->first, true); + relayMap.erase(i++); + } + else + i++; + } +} + +//MRB 8.27.12 -- disconnect any peers that were using this port, called when the port owner disconnects +void DisconnectPeersUsingPort(int port) +{ + PortUsers::iterator next = portUsers.begin(); + while (next != portUsers.end()) + { + PortUser &item = *next; + if (port == item.port) + { + Log::debug_log("Disconnecting peer %s from port %d\n", item.userAddress.ToString(), port); + + peer->CloseConnection(item.userAddress, true); + + next = portUsers.erase(next); + } + else + next++; + } + +} + +void CleanQueue(SystemAddress removeMe) +{ + Log::debug_log("Cleaning %s\n", removeMe.ToString()); + + // If this is a server relay + for (ServerMap::iterator i=serverMap.begin(); i!=serverMap.end(); i++) + { + if (i->second == removeMe) + { + int freePort = i->first; + serverMap.erase(i); + ServerPorts::iterator result = find( usedPorts.begin(), usedPorts.end(), freePort ); + if( result != usedPorts.end() ) + { + Log::debug_log("Freeing server port %d\n", freePort); + usedPorts.erase(result); + // Add to the back of the list so the port is more likely not to be immediately reused + serverPorts.push_back(freePort); + + DisconnectPeersUsingPort(freePort); //MRB 8.27.12 -- disconnect any peers that were using this port + } + else + { + Log::error_log("Failed to find server port %d in list\n", freePort); + } + DebugServerRelay(); + return; + } + } + + // Process client relay disconnection + + if (!queue.empty()) + { + RelayQueue::iterator next = queue.begin(); + for (RelayQueue::iterator i = next; i != queue.end(); i=next) + { + RelayItem &item = *i; + next++; + if (removeMe == item.target) + { + Log::debug_log("Removing queued message to target at %s\n", removeMe.ToString()); + queue.erase(i); + } + } + } + // Notify server that the client has disconnected + RakNet::BitStream stream; + stream.Write((unsigned char)ID_PROXY_MESSAGE); + stream.Write(removeMe); + stream.Write((unsigned char)ID_DISCONNECTION_NOTIFICATION); + if (!peer->Send(&stream, HIGH_PRIORITY, RELIABLE_ORDERED, 0, relayMap[removeMe], false)) + { + Log::error_log("Failed to send clean disconnect notification for client %s\n", removeMe.ToString()); + } + + // If this is on the receiving end of a client address, then this is a server + bool isServer = false; + std::queue remove; + RelayMap::iterator i; + for (i=relayMap.begin(); i!=relayMap.end(); i++) + { + if (i->second == removeMe) + { + isServer = true; + remove.push(i->first); + } + } + // If this is a server scroll through all addresses in the relay map and disconnect the clients. + if (isServer) + { + Log::debug_log("%s is a server\n", removeMe.ToString()); + while (!remove.empty()) + { + Log::print_log("Disconnecting client %s\n", remove.front().ToString()); + relayMap.erase(remove.front()); + peer->CloseConnection(remove.front(), true); + remove.pop(); + } + } + + // If the server is in the relay map and there is a connection to him and there is + // no one else in the relay map using that server, then its ok to remove and disconnect + if (relayMap.count(removeMe)) + { + Log::debug_log("Count is %d\n", relayMap.count(removeMe)); + // Grab the address of the server + i = relayMap.find(removeMe); + SystemAddress targetServer = i->second; + // Remove instance of server accociated with disconnected client + relayMap.erase(removeMe); + // Check if anyone else is using the same server + bool inUse = false; + for (i=relayMap.begin(); i!=relayMap.end(); i++) + { + if (i->second == targetServer) + inUse = true; + } + // No one else is using the clients server, then its ok to diconnect + if (!inUse) + { + Log::print_log("Diconnecting from unused server %s\n", targetServer.ToString()); + peer->CloseConnection(targetServer, true); + } + } + DebugClientRelay(); +} + +int main(int argc, char *argv[]) +{ + quit = false; + int connectionCount = 1000; + int listenPort = 10746; + int startPort = 50110; + int endPort = 50120; + int defaultFacilitatorPort = 50005; + int portCount = endPort - startPort + 1; + bool useLogFile = false; + bool daemonMode = false; + + // Default debug level is informational, so you see an overview of whats going on. + Log::sDebugLevel = kInformational; + +#ifndef WIN32 + setlinebuf(stdout); +#endif + std::map addresses; + peer = RakNetworkFactory::GetRakPeerInterface(); + + for (int i = 1; i < argc; i++) + { + if (strlen(argv[i]) == 2 && argc>=i+1) + { + switch (argv[i][1]) + { + case 'd': + { + daemonMode = true; + break; + } + case 'p': + listenPort = atoi(argv[i+1]); + i++; + if (listenPort < 1 || listenPort > 65535) + { + fprintf(stderr, "Listen port is invalid, should be between 0 and 65535.\nIt is also advisable to use a number above well known ports (>1024).\n"); + return 1; + } + break; + case 'c': + { + connectionCount = atoi(argv[i+1]); + i++; + if (connectionCount < 0) + { + fprintf(stderr, "Connection count must be higher than 0.\n"); + return 1; + } + break; + } + case 'l': + { + useLogFile = Log::EnableFileLogging(logfile); + break; + } + case 'e': + { + int debugLevel = atoi(argv[i+1]); + Log::sDebugLevel = debugLevel; + i++; + if (debugLevel < 0 || debugLevel > 9) + { + fprintf(stderr, "Log level can be 0(errors), 1(warnings), 2(informational), 9(debug)\n"); + return 1; + } + break; + } + case 'i': + { + peer->SetIncomingPassword(argv[i+1], strlen(argv[i+1])); + i++; //ULY 170608: Fix silly parsing issue. + break; + } + case 'r': + { + std::string range(argv[i+1]); + std::string::size_type seperator = range.find(":"); + if (seperator!= std::string::npos) { + startPort = atoi(range.substr(0,seperator).c_str()); + endPort = atoi(range.substr(seperator+1,range.length()).c_str()); + portCount = endPort - startPort + 1; //MRB 9.18.12: Update number of ports being used! + } + i++; + break; + } + case 'f': + { + std::string facilitatorString(argv[i+1]); + std::string::size_type seperator = facilitatorString.find(":"); + if (seperator!= std::string::npos) { + facilitatorAddress.SetBinaryAddress(facilitatorString.substr(0,seperator).c_str()); + facilitatorAddress.port = atoi(facilitatorString.substr(seperator+1,facilitatorString.length()).c_str()); + } + i++; + break; + } + case '?': + usage(); + return 0; + default: + printf("Parsing error, unknown parameter %s\n\n", argv[i]); + usage(); + return 1; + } + } + else + { + printf("Parsing error, incorrect parameters\n\n"); + usage(); + return 1; + } + } + +#ifndef WIN32 + if (daemonMode) + { + printf("Running in daemon mode, file logging enabled...\n"); + if (!useLogFile) + useLogFile = Log::EnableFileLogging(logfile); + // Don't change cwd to / + // Beware that log/pid files are placed wherever this was launched from + daemon(1, 0); + } + + if (!WriteProcessID(argv[0], &pidFile[0], fileBufSize)) + perror("Warning, failed to write own PID value to PID file\n"); +#endif + + if (facilitatorAddress == UNASSIGNED_SYSTEM_ADDRESS) + { + char* address; + address = const_cast(SocketLayer::Instance()->DomainNameToIP("facilitator.unity3d.com")); + if (address) + { + facilitatorAddress.SetBinaryAddress(address); + } + else + { + Log::error_log("Cannot resolve facilitator address"); + return 1; + } + facilitatorAddress.port = defaultFacilitatorPort; + } + + Log::startup_log("Proxy Server version %s\n", PROXY_SERVER_VERSION); + Log::startup_log("Listen port set to %d\n", listenPort); + Log::startup_log("Server relay ports set to %d to %d (%d ports)\n", startPort, endPort, portCount); + Log::startup_log("Using facilitator at %s\n", facilitatorAddress.ToString()); + SocketDescriptor *sds = new SocketDescriptor[portCount+1]; //MRB 9.18.12: +1 to allow for listenPort socket + sds[0] = SocketDescriptor(listenPort, 0); + int port = startPort; + for (int i=1; i<=portCount; i++) //MRB 9.18.12: 'less than equal' instead of 'less than' so endPort is actually used + { + sds[i] = SocketDescriptor(port, 0); + serverPorts.push_back(port++); + } + bool r = peer->Startup(connectionCount, 10, sds, portCount+1); //MRB 9.18.12: +1 to allow for listenPort socket + + if (!r) + { + Log::error_log("Some of the relay ports are in use. Please specify some other ports by -r xxxx:xxxx"); //ULY 170608: Report port in use. + } + + delete[] sds; //MRB 9.18.12: Use array delete... undefined behavior otherwise + + peer->SetMaximumIncomingConnections(connectionCount); + + // Register signal handler + if (signal(SIGINT, shutdown) == SIG_ERR || signal(SIGTERM, shutdown) == SIG_ERR) + Log::error_log("Problem setting up signal handler"); + else + Log::startup_log("To exit the proxy server press Ctrl-C\n----------------------------------------------------\n"); + + // Set up connection to facilitator + Log::print_log("Connecting to %s\n", facilitatorAddress.ToString()); + if (!peer->Connect(facilitatorAddress.ToString(false), facilitatorAddress.port,0,0)) + { + Log::error_log("Failed to connect to NAT facilitator at %s\n", facilitatorAddress.ToString()); + } + else + { + Log::print_log("Sent connect request to facilitator at %s\n", facilitatorAddress.ToString()); + } + peer->AttachPlugin(&natPunchthrough); + + Packet *packet; + while (!quit) + { +ReceiveAnotherPacket: //MRB 8.21.12 -- added goto label (see below 'MRB' comments for explanation) + packet=peer->ReceiveIgnoreRPC(); + while (packet) + { + Log::debug_log("Received packet on port %u\n", packet->rcvPort); + if (packet->rcvPort != listenPort) + { + // Client trying to connect to an invalid address + if (packet->rcvPort == 0 && packet->data[0] == ID_CONNECTION_ATTEMPT_FAILED) + { + CleanClient(packet->systemAddress); + break; + } + // DEBUG: Sanity check + ServerPorts::iterator result = find( usedPorts.begin(), usedPorts.end(), packet->rcvPort ); + if( result == usedPorts.end()) + { + Log::error_log("Communication received on uninitialized server port %d\n", packet->rcvPort); + int IDlocation = 0; + if (packet->data[0] == ID_TIMESTAMP) + IDlocation = 5; + Log::error_log("Rejected message from client at %s, ID of message is %s\n", packet->systemAddress.ToString(), IDtoString(packet->data[IDlocation])); + break; + } + + + //MRB 8.27.12 -- keep track of who is using this port, add to our port user list on any new connections and remove from list on disconnects + PortUser portUser; + portUser.userAddress = packet->systemAddress; + portUser.port = packet->rcvPort; + + if (packet->data[0] == ID_NEW_INCOMING_CONNECTION) + { + portUsers.push_back(portUser); + break; + } + + if (packet->data[0] == ID_DISCONNECTION_NOTIFICATION || packet->data[0] == ID_CONNECTION_LOST) + { + // DEBUG + Log::print_log("%s has diconnected\n", packet->systemAddress.ToString()); + DebugServerRelay(); + + //MRB 8.27.12 -- peer is no longer using this port + portUsers.remove(portUser); + } + + SystemAddress targetAddress; + RakNet::BitStream bitStream(packet->data, packet->length, false); + bitStream.IgnoreBits(8); // Ignore the ID_... + + // Lookup target address from map + if (serverMap.count(packet->rcvPort) != 0) + targetAddress = serverMap[packet->rcvPort]; + else + Log::error_log("Error: Relay failed for client at %s, server address not found\n", packet->systemAddress.ToString()); + + char tmp[32]; + strcpy(tmp, packet->systemAddress.ToString()); + int IDlocation = 0; + if (packet->data[0] == ID_TIMESTAMP) + IDlocation = 5; + Log::debug_log("Relaying for client at %s, to server at %s, ID of relayed message is %s\n", tmp, targetAddress.ToString(), IDtoString(packet->data[IDlocation])); + + MsgClientRelayPassthrough(bitStream, packet, targetAddress); + break; + } + switch (packet->data[0]) + { + case ID_DISCONNECTION_NOTIFICATION: + Log::print_log("%s has diconnected\n", packet->systemAddress.ToString()); + CleanQueue(packet->systemAddress); + CleanClient(packet->systemAddress); + break; + case ID_CONNECTION_LOST: + Log::print_log("Connection to %s lost\n", packet->systemAddress.ToString()); + CleanQueue(packet->systemAddress); + CleanClient(packet->systemAddress); + break; + case ID_NEW_INCOMING_CONNECTION: + Log::print_log("New connection established to %s\n", packet->systemAddress.ToString()); + break; + case ID_CONNECTION_REQUEST_ACCEPTED: + Log::print_log("Connected to %s\n", packet->systemAddress.ToString()); + if (!queue.empty()) + { + int totalSize = queue.size(); + Log::debug_log("Relay queue has %d elements\n", queue.size()); + + // Check whole queue and send everything with this server as target + RelayQueue::iterator next = queue.begin(); + while (next != queue.end()) + { + RelayItem &item = *next; + if (packet->systemAddress == item.target) + { + RakNet::BitStream bitStream; + bitStream.Write(item.packet, item.length); + //for (int i=0; i<=item.length; i++) + // printf("%x", item.packet[i]); + //printf("\n"); + peer->Send(&bitStream, HIGH_PRIORITY, RELIABLE_ORDERED, 0, item.target, false); + + Log::debug_log("Sending queued message to target at %s\n", item.target.ToString()); + + //delete[] item->packet; + next = queue.erase(next); + } + else + next++; + } + + Log::debug_log("%d elements sent from queue to target\n", totalSize - queue.size()); + } + break; + case ID_CONNECTION_ATTEMPT_FAILED: + { + Log::error_log("Failed to connect to %s\n", packet->systemAddress.ToString()); + CleanQueue(packet->systemAddress); + CleanClient(packet->systemAddress); + break; + } + case ID_ALREADY_CONNECTED: + { + break; + } + case ID_NAT_TARGET_NOT_CONNECTED: + { + SystemAddress systemAddress; + RakNet::BitStream bitStream(packet->data, packet->length, false); + bitStream.IgnoreBits(8); // Ignore the ID_... + bitStream.Read(systemAddress); + Log::error_log("NAT target %s is not connected to the facilitator\n", systemAddress.ToString()); + CleanClient(systemAddress); + break; + } + case ID_NAT_CONNECTION_TO_TARGET_LOST: + { + SystemAddress systemAddress; + RakNet::BitStream bitStream(packet->data, packet->length, false); + bitStream.IgnoreBits(8); // Ignore the ID_... + bitStream.Read(systemAddress); + Log::error_log("NAT connection to %s lost\n", systemAddress.ToString()); + CleanClient(systemAddress); + break; + } + case ID_PROXY_INIT_MESSAGE: + { + SystemAddress targetAddress; + int proxyVersion; // Proxy protocol version of the connecting client + int clientVersion; // Network protocol version of the connecting client + char *password; + int passwordLength = 0; + bool useNat; + RakNet::BitStream bitStream(packet->data, packet->length, false); + bitStream.IgnoreBits(8); // Ignore the ID_... + bitStream.Read(proxyVersion); + // Check target address of relayed message, memorize it for future use + bitStream.Read(targetAddress); + if (bitStream.ReadBit()) + { + bitStream.Read(passwordLength); + password = new char[passwordLength]; + bitStream.Read(password, passwordLength); + } + bitStream.Read(useNat); + bitStream.Read(clientVersion); + relayMap[packet->systemAddress] = targetAddress; + + char tmp[32]; + strcpy(tmp, targetAddress.ToString()); + Log::print_log("Received relay init message from %s, target is %s, proxy protocol version %d, network protocol version %d\n", packet->systemAddress.ToString(), tmp, proxyVersion, clientVersion); + + MsgClientInit(packet, targetAddress, password, passwordLength, useNat, clientVersion); + if (passwordLength > 0) + delete[] password; + } + break; + case ID_PROXY_SERVER_INIT: + { + //SystemAddress targetAddress; + int proxyVersion; + + RakNet::BitStream bitStream(packet->data, packet->length, false); + bitStream.IgnoreBits(8); // Ignore the ID_... + bitStream.Read(proxyVersion); + + Log::print_log("Received server init message from %s, proxy protocol version %d\n", packet->systemAddress.ToString(), proxyVersion); + + unsigned short freePort = 0; + RakNet::BitStream responseStream; + if (serverPorts.size() > 0) + { + freePort = serverPorts.front(); + serverPorts.pop_front(); + usedPorts.push_front(freePort); + responseStream.Write((unsigned char)ID_PROXY_SERVER_INIT); + responseStream.Write((int)PROXY_SERVER_PROTOCOL_VERSION); + responseStream.Write(freePort); + peer->Send(&responseStream, HIGH_PRIORITY, RELIABLE_ORDERED, 0, packet->systemAddress, false); + serverMap[freePort] = packet->systemAddress; + Log::print_log("Server %s assigned port %d\n", packet->systemAddress.ToString(), freePort); + } + else + { + responseStream.Write((unsigned char)ID_PROXY_SERVER_INIT); + responseStream.Write((int)PROXY_SERVER_PROTOCOL_VERSION); + responseStream.Write((unsigned short)0); + peer->Send(&responseStream, HIGH_PRIORITY, RELIABLE_ORDERED, 0, packet->systemAddress, false); + Log::error_log("Server %s rejected as no server port is free\n", packet->systemAddress.ToString()); + } + //relayMap[packet->systemAddress] = targetAddress; + } + break; + // Relay message from clients + case ID_PROXY_CLIENT_MESSAGE: + { + SystemAddress targetAddress; + RakNet::BitStream bitStream(packet->data, packet->length, false); + bitStream.IgnoreBits(8); // Ignore the ID_... + + // Lookup target address from map + if (relayMap.count(packet->systemAddress) != 0) + targetAddress = relayMap[packet->systemAddress]; + else + Log::error_log("Error: Relay failed for client at %s, target address not found\n", packet->systemAddress.ToString()); + + char tmp[32]; + strcpy(tmp, packet->systemAddress.ToString()); + int IDlocation = 1; + if (packet->data[1] == ID_TIMESTAMP) + IDlocation = 6; + Log::debug_log("Relaying for client at %s, to server at %s, ID of relayed message is %s\n", tmp, targetAddress.ToString(), IDtoString(packet->data[IDlocation])); + + MsgClientRelay(bitStream, packet, targetAddress); + } + break; + // Relay message from servers + case ID_PROXY_SERVER_MESSAGE: + { + SystemAddress clientAddress; + RakNet::BitStream bitStream(packet->data, packet->length, false); + bitStream.IgnoreBits(8); // Ignore the ID_... + + // To what client should this message be relayed to + bitStream.Read(clientAddress); + + peer->Send(reinterpret_cast(packet->data+7), packet->length-7, HIGH_PRIORITY, RELIABLE_ORDERED, 0, clientAddress, false); + + char tmp[32]; + strcpy(tmp, packet->systemAddress.ToString()); + int IDlocation = 7; + if (packet->data[7] == ID_TIMESTAMP) + IDlocation = 12; + Log::debug_log("Relaying for server at %s, to client at %s, ID of relayed message is %s\n", tmp, clientAddress.ToString(), IDtoString(packet->data[IDlocation])); + } + break; + case ID_INVALID_PASSWORD: + { + SystemAddress clientAddress = UNASSIGNED_SYSTEM_ADDRESS; + // A server rejected connection, need to find appropriate client address and notify him + for (RelayMap::iterator i = relayMap.begin(); i != relayMap.end(); i++) { + if ((*i).second == packet->systemAddress) { + clientAddress = (*i).first; + break; + } + } + if (clientAddress != UNASSIGNED_SYSTEM_ADDRESS) { + peer->Send(reinterpret_cast(packet->data), packet->length, HIGH_PRIORITY, RELIABLE_ORDERED, 0, clientAddress, false); + char tmp[32]; + strcpy(tmp, packet->systemAddress.ToString()); + Log::error_log("Send invalid password from %s notification to client at %s\n", tmp, clientAddress.ToString()); + } + else + { + Log::error_log("Failed to relay invalid password notification to any client for server at %s\n", packet->systemAddress.ToString()); + } + } + break; + // Relay message from proxies + default: + Log::error_log("Unknown ID %d from %s\n", packet->data[0], packet->systemAddress.ToString()); + } + peer->DeallocatePacket(packet); + //packet=peer->Receive(); //MRB 8.21.12 -- Wrong Receive function to call. This function processes RPC packets internally and doesn't return them to caller, so we never get a chance to relay them. + packet=peer->ReceiveIgnoreRPC(); //MRB 8.21.12 -- This is the correct function to call, as was done above at the top of this while loop. + } //end while, loops back to process next packet + + //MRB 8.21.12 -- New section to deal with case where packet was received on non-listen port, which exits the loop after processing the packet + if (packet) + { + peer->DeallocatePacket(packet); //Packets received on non-listen ports were never Deallocated! + goto ReceiveAnotherPacket; //We need to check for more packets before going to sleep. Otherwise, if we have continuous incoming traffic, packets get buffered and we slowly build in more and more latency. + } + //MRB 8.21.12 -- section end + + RakSleep(30); + } + + if (pidFile) + { + if (remove(pidFile) != 0) + fprintf(stderr, "Failed to remove PID file at %s\n", pidFile); + } + peer->Shutdown(100,0); + RakNetworkFactory::DestroyRakPeerInterface(peer); + + return 0; +} + diff --git a/ProxyServer.h b/ProxyServer.h new file mode 100644 index 0000000..9df23c1 --- /dev/null +++ b/ProxyServer.h @@ -0,0 +1,41 @@ +#pragma once +#include "RakNetTypes.h" +#include "MessageIdentifiers.h" + +#define PROXY_SERVER_PROTOCOL_VERSION 2 +#define PROXY_SERVER_VERSION "2.0.0b2" + +enum { + ID_STATE_UPDATE = ID_USER_PACKET_ENUM, + ID_STATE_INITIAL, + ID_CLIENT_INIT, + ID_REMOVE_RPCS, + ID_REQUEST_CLIENT_INIT, + ID_PROXY_INIT_MESSAGE, + ID_PROXY_CLIENT_MESSAGE, + ID_PROXY_SERVER_MESSAGE, + ID_PROXY_MESSAGE, + ID_PROXY_SERVER_INIT +}; + + +struct RelayItem +{ + char* packet; + int length; + SystemAddress target; +}; + +//MRB 8.27.12 -- Struct to keep track of peers connected on given port, so we can disconnect them if the port owner disconnects. +struct PortUser +{ + SystemAddress userAddress; + int port; + + bool operator==( const PortUser& right ) const + { + return right.userAddress == userAddress + && right.port == port; + } +}; + diff --git a/README.md b/README.md index c605564..405fcdc 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,33 @@ # UnityProxyServer -Cloned from the official Unity Proxy Server, with some bugs fixed. +A bug-fix version of Unity Proxy Server. + +It's based on `gecko938`'s [bug-fix version](https://forum.unity3d.com/threads/networking-proxy-and-rpc-calls.136861/). + +It can be compiled in Visual Studio 2017. + +## Bug Fix + +### By Ulysses: + +* Argument parsing bug when specifying password. +* Show clear log when overlay ports are already in use. (Even a single port being occupied will cause the server refuse to run!) +* Make it compile-able on Visual Studio. + +### By gecko938(MRB): + +Sept. 26, 2012: + +- Makefile: Build ProxyServer.cpp with no debug symbols by default as it may affect performance. + +Sept. 18, 2012: + +- RakPeer.cpp: Allow up to 256 (arbitrary higher number) server listen ports instead of just 32 +- ProxyServer.cpp: Few more fixes: 1. Specifying a larger range of ports other than the default works now 2. Use array delete when deleting array 3. Allocate correct number of sockets for the number of ports requested (needed one more for the listen port) + +Aug. 24, 2012: + +- ProxyServer.cpp,h: Close proxy connections to clients when host of that port disconnects. + +Aug. 22, 2012: + +- ProxyServer.cpp: ProxyServer bug fixes: 1. Use the correct Receive function so we don't sometimes miss RPCs. 2. Immediately fetch next packet after processing packets from clients, so we don't slowly build more and more latency as unprocessed packets build up. 3. Deallocate client packets after they are processed. diff --git a/RakNet/Sources/AsynchronousFileIO.cpp b/RakNet/Sources/AsynchronousFileIO.cpp new file mode 100644 index 0000000..8f5ed0b --- /dev/null +++ b/RakNet/Sources/AsynchronousFileIO.cpp @@ -0,0 +1,314 @@ +/// \file +/// +/// This file is part of RakNet Copyright 2003 Jenkins Software LLC +/// +/// Usage of RakNet is subject to the appropriate license agreement. + + +// No longer used as I no longer support IO Completion ports +/* + +#ifdef __USE_IO_COMPLETION_PORTS + +#include "AsynchronousFileIO.h" +#include "ClientContextStruct.h" +#include +#include "ExtendedOverlappedPool.h" +#include +#include "RakAssert.h" + +// All these are used for the Read callback. For general Asynch file IO you would change these +#include "RakNetTypes.h" + +class RakPeer; + +#ifdef _WIN32 +extern void __stdcall ProcessNetworkPacket( unsigned int binaryAddress, unsigned short port, const char *data, int length, RakPeer *rakPeer ); +#else +extern void ProcessNetworkPacket( unsigned int binaryAddress, unsigned short port, const char *data, int length, RakPeer *rakPeer ); +#endif + +AsynchronousFileIO AsynchronousFileIO::I; + +AsynchronousFileIO::AsynchronousFileIO() +{ + userCount = 0; + threadCount = 0; + completionPort = NULL; + + // Determine how many processors are on the system. + GetSystemInfo( &systemInfo ); +} + +void AsynchronousFileIO::IncreaseUserCount() +{ + userCountMutex.Lock(); + ++userCount; + + if ( userCount == 1 ) + { + + // Create the completion port that will be used by all the worker + // threads. + completionPort = CreateIoCompletionPort( INVALID_HANDLE_VALUE, NULL, 0, systemInfo.dwNumberOfProcessors * 2 ); + + if ( completionPort == NULL ) + { + userCount = 0; + userCountMutex.Unlock(); + return ; + } + + UINT nThreadID; + HANDLE workerHandle; + + // Create worker threads + + // One worker thread per processor + + for ( DWORD i = 0; i < systemInfo.dwNumberOfProcessors * 2; i++ ) + // In debug just make one worker thread so it's easier to trace + //for ( i = 0; i < systemInfo.dwNumberOfProcessors * 1; i++ ) + { + workerHandle = ( HANDLE ) _beginthreadex( NULL, // Security + 0, // Stack size - use default + ThreadPoolFunc, // Thread fn entry point + ( void* ) completionPort, // Param for thread + 0, // Init flag + &nThreadID ); // Thread address + + + // Feel free to comment this out for regular thread priority + SetThreadPriority( workerHandle, THREAD_PRIORITY_HIGHEST ); + + CloseHandle( workerHandle ); + } + + + // Wait for the threads to start + while ( threadCount < systemInfo.dwNumberOfProcessors * 2 ) + Sleep( 0 ); + } + + userCountMutex.Unlock(); +} + +void AsynchronousFileIO::DecreaseUserCount() +{ + userCountMutex.Lock(); + + assert( userCount > 0 ); + + if ( userCount == 0 ) + return ; + + userCount--; + + if ( userCount == 0 ) + Shutdown(); + + userCountMutex.Unlock(); +} + +void AsynchronousFileIO::Shutdown( void ) +{ + killThreads = true; + + if ( completionPort != NULL ) + for ( DWORD i = 0; i < systemInfo.dwNumberOfProcessors * 2; i++ ) + PostQueuedCompletionStatus( completionPort, 0, 0 , 0 ); + + // Kill worker threads + while ( threadCount > 0 ) + Sleep( 0 ); + + if ( completionPort != NULL ) + CloseHandle( completionPort ); +} + +int AsynchronousFileIO::GetUserCount( void ) +{ + return userCount; +} + +AsynchronousFileIO::~AsynchronousFileIO() +{ + if ( threadCount > 0 ) + Shutdown(); +} + +bool AsynchronousFileIO::AssociateSocketWithCompletionPort( SOCKET socket, DWORD dwCompletionKey ) +{ + HANDLE h = CreateIoCompletionPort( ( HANDLE ) socket, completionPort, dwCompletionKey, 0 ); + return h == completionPort; +} + +BOOL ReadAsynch( HANDLE handle, ExtendedOverlappedStruct *extended ) +{ + BOOL success; + extended->read = true; + success = ReadFile( handle, extended->data, extended->length, 0, ( LPOVERLAPPED ) extended ); + + if ( !success ) + { + DWORD dwErrCode = GetLastError(); + + if ( dwErrCode != ERROR_IO_PENDING ) + { +#if defined(_WIN32) && !defined(_XBOX) && defined(_DEBUG) + LPVOID messageBuffer; + FormatMessage( FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, + NULL, dwErrCode, MAKELANGID( LANG_NEUTRAL, SUBLANG_DEFAULT ), // Default language + ( LPTSTR ) & messageBuffer, 0, NULL ); + // something has gone wrong here... + RAKNET_DEBUG_PRINTF( "ReadFile failed:Error code - %d\n%s", dwErrCode, messageBuffer ); + //Free the buffer. + LocalFree( messageBuffer ); +#endif + + return FALSE; + } + } + + return TRUE; +} + +void WriteAsynch( HANDLE handle, ExtendedOverlappedStruct *extended ) +{ + //RAKNET_DEBUG_PRINTF("Beginning asynch write of %i bytes.\n",extended->length); + //for (int i=0; i < extended->length && i < 10; i++) + // RAKNET_DEBUG_PRINTF("%i ", extended->data[i]); + //RAKNET_DEBUG_PRINTF("\n\n"); + BOOL success; + extended->read = false; + success = WriteFile( handle, extended->data, extended->length, 0, ( LPOVERLAPPED ) extended ); + + if ( !success ) + { + DWORD dwErrCode = GetLastError(); + + if ( dwErrCode != ERROR_IO_PENDING ) + { +#if defined(_WIN32) && !defined(_XBOX) && defined(_DEBUG) + LPVOID messageBuffer; + FormatMessage( FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, + NULL, dwErrCode, MAKELANGID( LANG_NEUTRAL, SUBLANG_DEFAULT ), // Default language + ( LPTSTR ) & messageBuffer, 0, NULL ); + // something has gone wrong here... + RAKNET_DEBUG_PRINTF( "WriteFile failed:Error code - %d\n%s", dwErrCode, messageBuffer ); + + //Free the buffer. + LocalFree( messageBuffer ); +#endif + + } + } +} + +unsigned __stdcall ThreadPoolFunc( LPVOID arguments ) +{ + DWORD dwIoSize; + ClientContextStruct* lpClientContext; + ExtendedOverlappedStruct* lpOverlapped; + LPOVERLAPPED temp; + BOOL bError; + + HANDLE *completionPort = ( HANDLE * ) arguments; + AsynchronousFileIO::Instance()->threadCount++; + + while ( 1 ) + { + // Get a completed IO request. + BOOL returnValue = GetQueuedCompletionStatus( + completionPort, + &dwIoSize, + ( LPDWORD ) & lpClientContext, + &temp, INFINITE ); + + lpOverlapped = ( ExtendedOverlappedStruct* ) temp; + + DWORD dwIOError = GetLastError(); + + if ( lpOverlapped == 0 ) + break; // Cancelled thread + + if ( !returnValue && dwIOError != WAIT_TIMEOUT ) + { + if ( dwIOError != ERROR_OPERATION_ABORTED ) + { + // Print all but this very common error message +#if defined(_WIN32) && !defined(_XBOX) && defined(_DEBUG) + LPVOID messageBuffer; + FormatMessage( FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, + NULL, dwIOError, MAKELANGID( LANG_NEUTRAL, SUBLANG_DEFAULT ), // Default language + ( LPTSTR ) & messageBuffer, 0, NULL ); + // something has gone wrong here... + RAKNET_DEBUG_PRINTF( "GetQueuedCompletionStatus failed:Error code - %d\n%s", dwIOError, messageBuffer ); + + //Free the buffer. + LocalFree( messageBuffer ); +#endif + + } + +HANDLE_ERROR: + // Some kind of error. Erase the data for this call + bError = true; + + // This socket is no longer used + + if ( lpOverlapped ) + RakNet::OP_DELETE(lpOverlapped, __FILE__, __LINE__); + + if ( lpClientContext ) + RakNet::OP_DELETE(lpClientContext, __FILE__, __LINE__); + + // If we are killing the threads, then we keep posting fake completion statuses until we get a fake one through the queue (i.e. lpOverlapped==0 as above) + // This way we delete all the data from the real calls before exiting the thread + if ( AsynchronousFileIO::Instance()->killThreads ) + { + PostQueuedCompletionStatus( completionPort, 0, 0, 0 ); + } + + } + + else + bError = false; + + if ( !bError ) + { + if ( returnValue && NULL != lpOverlapped && NULL != lpClientContext ) + { + if ( lpOverlapped->read == true ) + { + assert( dwIoSize > 0 ); + + ProcessNetworkPacket( lpOverlapped->binaryAddress, lpOverlapped->port, lpOverlapped->data, dwIoSize, lpOverlapped->rakPeer ); + + // Issue a new read so we always have one outstanding read per socket + // Finished a read. Reuse the overlapped pointer + bError = ReadAsynch( lpClientContext->handle, lpOverlapped ); + + if ( !bError ) + goto HANDLE_ERROR; // Windows is super unreliable! + } + + else + { + // AsynchronousFileIO::Instance()->Write(lpClientContext); + // Finished a write + ExtendedOverlappedPool::Instance()->ReleasePointer( lpOverlapped ); + } + } + + else + assert( 0 ); + } + } + + AsynchronousFileIO::Instance()->threadCount--; + return 0; +} + +#endif +*/ diff --git a/RakNet/Sources/AsynchronousFileIO.h b/RakNet/Sources/AsynchronousFileIO.h new file mode 100644 index 0000000..1bf82e8 --- /dev/null +++ b/RakNet/Sources/AsynchronousFileIO.h @@ -0,0 +1,82 @@ +/// \file +/// \brief \b [Internal] deprecated, used for windows back when I supported IO completion ports. +/// +/// This file is part of RakNet Copyright 2003 Jenkins Software LLC +/// +/// Usage of RakNet is subject to the appropriate license agreement. + + +// No longer used as I no longer support IO Completion ports +/* +#ifdef __USE_IO_COMPLETION_PORTS + +#ifndef __ASYNCHRONOUS_FILE_IO_H +#define __ASYNCHRONOUS_FILE_IO_H + +#ifdef _XBOX +#elif 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 +//#include "WindowsIncludes.h" +#endif +#include "SimpleMutex.h" + +struct ExtendedOverlappedStruct; + +/// Provides asynch file input and ouput, either for sockets or files +class AsynchronousFileIO +{ + +public: + + /// Default Constructor + AsynchronousFileIO(); + + // Destructor + ~AsynchronousFileIO(); + + +/// Associate a socket with a completion port +/// \param[in] socket the socket used for communication +/// \param[in] dwCompletionKey the completion port key + bool AssociateSocketWithCompletionPort( SOCKET socket, DWORD dwCompletionKey );if + + /// Singleton instance + static inline AsynchronousFileIO* Instance() + { + return & I; + } + + /// Increase the number of users of this instance + void IncreaseUserCount( void ); + + /// Decrease the number of users of this instance + void DecreaseUserCount( void ); + + /// Stop using asynchronous IO + void Shutdown( void ); + + /// Get the number of user of the instance + int GetUserCount( void ); + + unsigned threadCount; + bool killThreads; + +private: + HANDLE completionPort; + SimpleMutex userCountMutex; + SYSTEM_INFO systemInfo; + int userCount; + + static AsynchronousFileIO I; +}; + +unsigned __stdcall ThreadPoolFunc( LPVOID arguments ); + +void WriteAsynch( HANDLE handle, ExtendedOverlappedStruct *extended ); + +BOOL ReadAsynch( HANDLE handle, ExtendedOverlappedStruct *extended ); + +#endif +*/ diff --git a/RakNet/Sources/AutoRPC.cpp b/RakNet/Sources/AutoRPC.cpp new file mode 100644 index 0000000..6c1f2ca --- /dev/null +++ b/RakNet/Sources/AutoRPC.cpp @@ -0,0 +1,704 @@ +#include "NativeFeatureIncludes.h" +#if _RAKNET_SUPPORT_AutoRPC==1 + +#include "AutoRPC.h" +#include "RakMemoryOverride.h" +#include "RakAssert.h" +#include "StringCompressor.h" +#include "BitStream.h" +//#include "Types.h" +#include "RakPeerInterface.h" +#include "MessageIdentifiers.h" +#include "NetworkIDObject.h" +#include "NetworkIDManager.h" +#include + +using namespace RakNet; + +#ifdef _MSC_VER +#pragma warning( push ) +#endif + +int AutoRPC::RemoteRPCFunctionComp( const RPCIdentifier &key, const RemoteRPCFunction &data ) +{ + if (key.isObjectMember==false && data.identifier.isObjectMember==true) + return -1; + if (key.isObjectMember==true && data.identifier.isObjectMember==false) + return 1; + return strcmp(key.uniqueIdentifier, data.identifier.uniqueIdentifier); +} + +AutoRPC::AutoRPC() +{ + currentExecution[0]=0; + networkIdManager=0; + outgoingTimestamp=0; + outgoingPriority=HIGH_PRIORITY; + outgoingReliability=RELIABLE_ORDERED; + outgoingOrderingChannel=0; + outgoingBroadcast=true; + incomingTimeStamp=0; + DataStructures::Map *>::IMPLEMENT_DEFAULT_COMPARISON(); +} + +AutoRPC::~AutoRPC() +{ + Clear(); +} +void AutoRPC::SetNetworkIDManager(NetworkIDManager *idMan) +{ + networkIdManager=idMan; +} +bool AutoRPC::RegisterFunction(const char *uniqueIdentifier, void *functionPtr, bool isObjectMember, char parameterCount) +{ + if (uniqueIdentifier==0 || functionPtr==0) + { + RakAssert(0); + return false; + } + + RPCIdentifier identifier; + identifier.isObjectMember=isObjectMember; + identifier.uniqueIdentifier=(char*) uniqueIdentifier; + unsigned localIndex = GetLocalFunctionIndex(identifier); + // Already registered? + if (localIndex!=(unsigned)-1 && localFunctions[localIndex].functionPtr!=0) + return false; + if (localIndex!=(unsigned)-1) + { + // Reenable existing + localFunctions[localIndex].functionPtr=functionPtr; + localFunctions[localIndex].parameterCount=parameterCount; + } + else + { + // Add new + LocalRPCFunction func; + func.functionPtr=functionPtr; + func.identifier.isObjectMember=isObjectMember; + func.identifier.uniqueIdentifier = (char*) rakMalloc_Ex(strlen(uniqueIdentifier)+1, __FILE__, __LINE__); + func.parameterCount=parameterCount; + strcpy(func.identifier.uniqueIdentifier, uniqueIdentifier); + localFunctions.Insert(func, __FILE__, __LINE__); + } + return true; +} +bool AutoRPC::UnregisterFunction(const char *uniqueIdentifier, bool isObjectMember) +{ + if (uniqueIdentifier==0) + { + RakAssert(0); + return false; + } + + RPCIdentifier identifier; + identifier.isObjectMember=isObjectMember; + identifier.uniqueIdentifier=(char*) uniqueIdentifier; + unsigned localIndex = GetLocalFunctionIndex(identifier); + // Not registered? + if (localIndex==(unsigned)-1) + return false; + // Leave the id in, in case the function is set again later. That way we keep the same remote index + localFunctions[localIndex].functionPtr=0; + return true; +} +void AutoRPC::SetTimestamp(RakNetTime timeStamp) +{ + outgoingTimestamp=timeStamp; +} +void AutoRPC::SetSendParams(PacketPriority priority, PacketReliability reliability, char orderingChannel) +{ + outgoingPriority=priority; + outgoingReliability=reliability; + outgoingOrderingChannel=orderingChannel; +} +void AutoRPC::SetRecipientAddress(AddressOrGUID systemIdentifier, bool broadcast) +{ + outgoingSystemIdentifier=systemIdentifier; + outgoingBroadcast=broadcast; +} +void AutoRPC::SetRecipientObject(NetworkID networkID) +{ + outgoingNetworkID=networkID; +} +RakNet::BitStream *AutoRPC::SetOutgoingExtraData(void) +{ + return &outgoingExtraData; +} +RakNetTime AutoRPC::GetLastSenderTimestamp(void) const +{ + return incomingTimeStamp; +} +SystemAddress AutoRPC::GetLastSenderAddress(void) const +{ + return incomingSystemAddress; +} +RakPeerInterface *AutoRPC::GetRakPeer(void) const +{ + return rakPeerInterface; +} +const char *AutoRPC::GetCurrentExecution(void) const +{ + return (const char *) currentExecution; +} +RakNet::BitStream *AutoRPC::GetIncomingExtraData(void) +{ + return &incomingExtraData; +} +bool AutoRPC::SendCall(const char *uniqueIdentifier, const char *stack, unsigned int bytesOnStack, char parameterCount) +{ + SystemAddress systemAddr; + RPCIdentifier identifier; + unsigned int outerIndex; + unsigned int innerIndex; + + if (uniqueIdentifier==0) + return false; + + identifier.uniqueIdentifier=(char*) uniqueIdentifier; + identifier.isObjectMember=(outgoingNetworkID!=UNASSIGNED_NETWORK_ID); + + RakNet::BitStream bs; + if (outgoingTimestamp!=0) + { + bs.Write((MessageID)ID_TIMESTAMP); + bs.Write(outgoingTimestamp); + } + bs.Write((MessageID)ID_AUTO_RPC_CALL); + if (parameterCount>=0) + { + bs.Write(true); + bs.Write(parameterCount); + } + else + { + bs.Write(false); + } + bs.WriteCompressed(outgoingExtraData.GetNumberOfBitsUsed()); + bs.Write(&outgoingExtraData); + if (outgoingNetworkID!=UNASSIGNED_NETWORK_ID) + { + bs.Write(true); + bs.Write(outgoingNetworkID); + } + else + { + bs.Write(false); + } + // This is so the call SetWriteOffset works + bs.AlignWriteToByteBoundary(); + BitSize_t writeOffset = bs.GetWriteOffset(); + SystemAddress outgoingSystemAddress; + if (outgoingSystemIdentifier.rakNetGuid!=UNASSIGNED_RAKNET_GUID) + outgoingSystemAddress = rakPeerInterface->GetSystemAddressFromGuid(outgoingSystemIdentifier.rakNetGuid); + else + outgoingSystemAddress = outgoingSystemIdentifier.systemAddress; + if (outgoingBroadcast) + { + unsigned systemIndex; + for (systemIndex=0; systemIndex < rakPeerInterface->GetMaximumNumberOfPeers(); systemIndex++) + { + systemAddr=rakPeerInterface->GetSystemAddressFromIndex(systemIndex); + if (systemAddr!=UNASSIGNED_SYSTEM_ADDRESS && systemAddr!=outgoingSystemAddress) + { + if (GetRemoteFunctionIndex(systemAddr, identifier, &outerIndex, &innerIndex)) + { + // Write a number to identify the function if possible, for faster lookup and less bandwidth + bs.Write(true); + bs.WriteCompressed(remoteFunctions[outerIndex]->operator [](innerIndex).functionIndex); + } + else + { + bs.Write(false); + stringCompressor->EncodeString(uniqueIdentifier, 512, &bs, 0); + } + + bs.WriteCompressed(bytesOnStack); + bs.WriteAlignedBytes((const unsigned char*) stack, bytesOnStack); + SendUnified(&bs, outgoingPriority, outgoingReliability, outgoingOrderingChannel, systemAddr, false); + + // Start writing again after ID_AUTO_RPC_CALL + bs.SetWriteOffset(writeOffset); + } + } + } + else + { + systemAddr = outgoingSystemAddress; + if (systemAddr!=UNASSIGNED_SYSTEM_ADDRESS) + { + if (GetRemoteFunctionIndex(systemAddr, identifier, &outerIndex, &innerIndex)) + { + // Write a number to identify the function if possible, for faster lookup and less bandwidth + bs.Write(true); + bs.WriteCompressed(remoteFunctions[outerIndex]->operator [](innerIndex).functionIndex); + } + else + { + bs.Write(false); + stringCompressor->EncodeString(uniqueIdentifier, 512, &bs, 0); + } + + bs.WriteCompressed(bytesOnStack); + bs.WriteAlignedBytes((const unsigned char*) stack, bytesOnStack); + SendUnified(&bs, outgoingPriority, outgoingReliability, outgoingOrderingChannel, systemAddr, false); + } + else + return false; + } + return true; +} +void AutoRPC::OnAttach(void) +{ + outgoingSystemIdentifier.SetUndefined(); + outgoingNetworkID=UNASSIGNED_NETWORK_ID; + incomingSystemAddress=UNASSIGNED_SYSTEM_ADDRESS; + +} +PluginReceiveResult AutoRPC::OnReceive(Packet *packet) +{ + RakNetTime timestamp=0; + unsigned char packetIdentifier, packetDataOffset; + if ( ( unsigned char ) packet->data[ 0 ] == ID_TIMESTAMP ) + { + if ( packet->length > sizeof( unsigned char ) + sizeof( RakNetTime ) ) + { + packetIdentifier = ( unsigned char ) packet->data[ sizeof( unsigned char ) + sizeof( RakNetTime ) ]; + // Required for proper endian swapping + RakNet::BitStream tsBs(packet->data+sizeof(MessageID),packet->length-1,false); + tsBs.Read(timestamp); + packetDataOffset=sizeof( unsigned char )*2 + sizeof( RakNetTime ); + } + else + return RR_STOP_PROCESSING_AND_DEALLOCATE; + } + else + { + packetIdentifier = ( unsigned char ) packet->data[ 0 ]; + packetDataOffset=sizeof( unsigned char ); + } + + switch (packetIdentifier) + { + case ID_AUTO_RPC_CALL: + incomingTimeStamp=timestamp; + incomingSystemAddress=packet->systemAddress; + OnAutoRPCCall(packet->systemAddress, packet->data+packetDataOffset, packet->length-packetDataOffset); + return RR_STOP_PROCESSING_AND_DEALLOCATE; + case ID_AUTO_RPC_REMOTE_INDEX: + OnRPCRemoteIndex(packet->systemAddress, packet->data+packetDataOffset, packet->length-packetDataOffset); + return RR_STOP_PROCESSING_AND_DEALLOCATE; + case ID_AUTO_RPC_UNKNOWN_REMOTE_INDEX: + OnRPCUnknownRemoteIndex(packet->systemAddress, packet->data+packetDataOffset, packet->length-packetDataOffset, timestamp); + return RR_STOP_PROCESSING_AND_DEALLOCATE; + } + + return RR_CONTINUE_PROCESSING; +} +void AutoRPC::OnClosedConnection(SystemAddress systemAddress, RakNetGUID rakNetGUID, PI2_LostConnectionReason lostConnectionReason ) +{ + (void) rakNetGUID; + (void) lostConnectionReason; + + if (remoteFunctions.Has(systemAddress)) + { + DataStructures::OrderedList *theList = remoteFunctions.Get(systemAddress); + unsigned i; + for (i=0; i < theList->Size(); i++) + { + if (theList->operator [](i).identifier.uniqueIdentifier) + rakFree_Ex(theList->operator [](i).identifier.uniqueIdentifier, __FILE__, __LINE__ ); + } + RakNet::OP_DELETE(theList, __FILE__, __LINE__); + remoteFunctions.Delete(systemAddress); + } +} +void AutoRPC::OnAutoRPCCall(SystemAddress systemAddress, unsigned char *data, unsigned int lengthInBytes) +{ + RakNet::BitStream bs(data,lengthInBytes,false); + + bool hasParameterCount=false; + char parameterCount; + char inputStack[ARPC_MAX_STACK_SIZE]; + NetworkIDObject *networkIdObject; + NetworkID networkId; + bool hasNetworkId=false; + bool hasFunctionIndex=false; + unsigned int functionIndex; + unsigned int bytesOnStack; + char strIdentifier[512]; + int numberOfBitsUsed; + incomingExtraData.Reset(); + bs.Read(hasParameterCount); + if (hasParameterCount) + bs.Read(parameterCount); + else + parameterCount=-1; + bs.ReadCompressed(numberOfBitsUsed); + if (numberOfBitsUsed > (int) incomingExtraData.GetNumberOfBitsAllocated()) + incomingExtraData.AddBitsAndReallocate(numberOfBitsUsed-(int) incomingExtraData.GetNumberOfBitsAllocated()); + bs.ReadBits(incomingExtraData.GetData(), numberOfBitsUsed, false); + incomingExtraData.SetWriteOffset(numberOfBitsUsed); + + +// const unsigned int outputStackSize = ARPC_MAX_STACK_SIZE+128*4; // Enough padding to round up to 4 for each parameter, max 128 parameters +// char outputStack[outputStackSize]; + + bs.Read(hasNetworkId); + if (hasNetworkId) + { + bs.Read(networkId); + if (networkIdManager==0 && (networkIdManager=rakPeerInterface->GetNetworkIDManager())==0) + { + // Failed - Tried to call object member, however, networkIDManager system was never registered + SendError(systemAddress, RPC_ERROR_NETWORK_ID_MANAGER_UNAVAILABLE, ""); + return; + } + networkIdObject = networkIdManager->GET_OBJECT_FROM_ID(networkId); + if (networkIdObject==0) + { + // Failed - Tried to call object member, object does not exist (deleted?) + SendError(systemAddress, RPC_ERROR_OBJECT_DOES_NOT_EXIST, ""); + return; + } + } + else + { + networkIdObject=0; + } + bs.AlignReadToByteBoundary(); + bs.Read(hasFunctionIndex); + if (hasFunctionIndex) + bs.ReadCompressed(functionIndex); + else + stringCompressor->DecodeString(strIdentifier,512,&bs,0); + bs.ReadCompressed(bytesOnStack); + bs.ReadAlignedBytes((unsigned char *) inputStack,bytesOnStack); + if (hasFunctionIndex) + { + if (functionIndex>localFunctions.Size()) + { + // Failed - other system specified a totally invalid index + // Possible causes: Bugs, attempts to crash the system, requested function not registered + SendError(systemAddress, RPC_ERROR_FUNCTION_INDEX_OUT_OF_RANGE, ""); + return; + } + // it was actually a mistake to implement ID_AUTO_RPC_UNKNOWN_REMOTE_INDEX. This hides the more relevant return code RPC_ERROR_FUNCTION_NO_LONGER_REGISTERED and more importantly can result in the calls being out of order since it takes extra communication steps. + /* + if (localFunctions[functionIndex].functionPtr==0) + { + // Failed - Function index lookup failure. Try passing back what was sent to us, and requesting the string + RakNet::BitStream out; + if (incomingTimeStamp!=0) + { + out.Write((MessageID)ID_TIMESTAMP); + out.Write(incomingTimeStamp); + } + out.Write((MessageID)ID_AUTO_RPC_UNKNOWN_REMOTE_INDEX); + if (parameterCount>=0) + { + out.Write(true); + out.Write(parameterCount); + } + else + { + out.Write(false); + } + out.WriteCompressed(functionIndex); + out.WriteCompressed(numberOfBitsUsed); + out.Write(&incomingExtraData); + out.Write(hasNetworkId); + if (hasNetworkId) + out.Write(networkId); + out.WriteCompressed(bytesOnStack); + out.WriteAlignedBytes((const unsigned char*) inputStack, bytesOnStack); + SendUnified(&out, HIGH_PRIORITY, RELIABLE_ORDERED, 0, systemAddress, false); + + return; + } + */ + } + else + { + // Find the registered function with this str + for (functionIndex=0; functionIndex < localFunctions.Size(); functionIndex++) + { + if (localFunctions[functionIndex].identifier.isObjectMember == (networkIdObject!=0) && + strcmp(localFunctions[functionIndex].identifier.uniqueIdentifier, strIdentifier)==0) + { + // SEND RPC MAPPING + RakNet::BitStream outgoingBitstream; + outgoingBitstream.Write((MessageID)ID_AUTO_RPC_REMOTE_INDEX); + outgoingBitstream.Write(hasNetworkId); + outgoingBitstream.WriteCompressed(functionIndex); + stringCompressor->EncodeString(strIdentifier,512,&outgoingBitstream,0); + SendUnified(&outgoingBitstream, HIGH_PRIORITY, RELIABLE_ORDERED, 0, systemAddress, false); + break; + } + } + + if (functionIndex==localFunctions.Size()) + { + for (functionIndex=0; functionIndex < localFunctions.Size(); functionIndex++) + { + if (strcmp(localFunctions[functionIndex].identifier.uniqueIdentifier, strIdentifier)==0) + { + if (localFunctions[functionIndex].identifier.isObjectMember==true && networkIdObject==0) + { + // Failed - Calling C++ function as C function + SendError(systemAddress, RPC_ERROR_CALLING_CPP_AS_C, strIdentifier); + return; + } + + if (localFunctions[functionIndex].identifier.isObjectMember==false && networkIdObject!=0) + { + // Failed - Calling C++ function as C function + SendError(systemAddress, RPC_ERROR_CALLING_C_AS_CPP, strIdentifier); + return; + } + } + } + + SendError(systemAddress, RPC_ERROR_FUNCTION_NOT_REGISTERED, strIdentifier); + return; + } + } + + if (localFunctions[functionIndex].functionPtr==0) + { + // Failed - Function was previously registered, but isn't registered any longer + SendError(systemAddress, RPC_ERROR_FUNCTION_NO_LONGER_REGISTERED, localFunctions[functionIndex].identifier.uniqueIdentifier); + return; + } + + if (bytesOnStack > ARPC_MAX_STACK_SIZE) + { + // Failed - Not enough bytes on predetermined stack. Shouldn't hit this since the sender also uses this value + SendError(systemAddress, RPC_ERROR_STACK_TOO_SMALL, localFunctions[functionIndex].identifier.uniqueIdentifier); + return; + } + + if (localFunctions[functionIndex].parameterCount>=0 && parameterCount>=0 && parameterCount!=localFunctions[functionIndex].parameterCount) + { + // Failed - The number of parameters that this function has was explicitly specified, and does not match up. + SendError(systemAddress, RPC_ERROR_INCORRECT_NUMBER_OF_PARAMETERS, localFunctions[functionIndex].identifier.uniqueIdentifier); + return; + } + + +// unsigned int bytesWritten; +// unsigned char numParameters; +// unsigned int parameterLengths[64]; // 64 is arbitrary, just needs to be more than whatever might be serialized + + + GenRPC::CallParams call; + + if (DeserializeParametersAndBuildCall(call, inputStack, bytesOnStack, this, networkIdObject)==false) + { + // Failed - Couldn't deserialize + SendError(systemAddress, RPC_ERROR_STACK_DESERIALIZATION_FAILED, localFunctions[functionIndex].identifier.uniqueIdentifier); + return; + } + + strncpy(currentExecution, localFunctions[functionIndex].identifier.uniqueIdentifier, sizeof(currentExecution)-1); + + if (!CallWithStack( call, localFunctions[functionIndex].functionPtr)){ + // Failed - Couldn't deserialize + SendError(systemAddress, RPC_ERROR_STACK_DESERIALIZATION_FAILED, currentExecution); + return; + } + + + currentExecution[0]=0; +} +void AutoRPC::OnRPCRemoteIndex(SystemAddress systemAddress, unsigned char *data, unsigned int lengthInBytes) +{ + // A remote system has given us their internal index for a particular function. + // Store it and use it from now on, to save bandwidth and search time + bool objectExists; + char strIdentifier[512]; + unsigned int insertionIndex; + unsigned int remoteIndex; + RemoteRPCFunction newRemoteFunction; + RakNet::BitStream bs(data,lengthInBytes,false); + RPCIdentifier identifier; + bs.Read(identifier.isObjectMember); + bs.ReadCompressed(remoteIndex); + stringCompressor->DecodeString(strIdentifier,512,&bs,0); + identifier.uniqueIdentifier=strIdentifier; + + if (strIdentifier[0]==0) + return; + + DataStructures::OrderedList *theList; + if (remoteFunctions.Has(systemAddress)) + { + theList = remoteFunctions.Get(systemAddress); + insertionIndex=theList->GetIndexFromKey(identifier, &objectExists); + if (objectExists==false) + { + newRemoteFunction.functionIndex=remoteIndex; + newRemoteFunction.identifier.isObjectMember=identifier.isObjectMember; + newRemoteFunction.identifier.uniqueIdentifier = (char*) rakMalloc_Ex(strlen(strIdentifier)+1, __FILE__, __LINE__); + strcpy(newRemoteFunction.identifier.uniqueIdentifier, strIdentifier); + theList->InsertAtIndex(newRemoteFunction, insertionIndex, __FILE__, __LINE__); + } + } + else + { + theList = RakNet::OP_NEW >( __FILE__, __LINE__ ); + + newRemoteFunction.functionIndex=remoteIndex; + newRemoteFunction.identifier.isObjectMember=identifier.isObjectMember; + newRemoteFunction.identifier.uniqueIdentifier = (char*) rakMalloc_Ex(strlen(strIdentifier)+1, __FILE__, __LINE__); + strcpy(newRemoteFunction.identifier.uniqueIdentifier, strIdentifier); + theList->InsertAtEnd(newRemoteFunction, __FILE__, __LINE__); + + remoteFunctions.SetNew(systemAddress,theList); + } +} +void AutoRPC::OnRPCUnknownRemoteIndex(SystemAddress systemAddress, unsigned char *data, unsigned int lengthInBytes, RakNetTime timestamp) +{ + char inputStack[ARPC_MAX_STACK_SIZE]; + NetworkID networkId; + bool hasNetworkId=false; + unsigned int functionIndex; + unsigned int bytesOnStack; + int numberOfBitsUsed; + char parameterCount; + bool hasParameterCount=false; + + RakNet::BitStream extraData; + RakNet::BitStream bs(data,lengthInBytes,false); + bs.Read(hasParameterCount); + if (hasParameterCount) + bs.Read(parameterCount); + bs.ReadCompressed(functionIndex); + bs.ReadCompressed(numberOfBitsUsed); + extraData.AddBitsAndReallocate(numberOfBitsUsed); + bs.ReadBits(extraData.GetData(), numberOfBitsUsed, false); + extraData.SetWriteOffset(numberOfBitsUsed); + bs.Read(hasNetworkId); + if (hasNetworkId) + bs.Read(networkId); + bs.ReadCompressed(bytesOnStack); + bs.ReadAlignedBytes((unsigned char*) inputStack, bytesOnStack); + + unsigned outerIndex; + if (remoteFunctions.Has(systemAddress)) + { + outerIndex = remoteFunctions.GetIndexAtKey(systemAddress); + DataStructures::OrderedList *theList = remoteFunctions[outerIndex]; + unsigned i; + for (i=0; i < theList->Size(); i++) + { + if (theList->operator [](i).functionIndex==functionIndex) + { + RakNet::BitStream out; + // Recover by resending the RPC with the function identifier string this time + if (timestamp!=0) + { + out.Write((MessageID)ID_TIMESTAMP); + out.Write(timestamp); + } + out.Write((MessageID)ID_AUTO_RPC_CALL); + if (parameterCount>=0) + { + out.Write(true); + out.Write(parameterCount); + } + else + { + out.Write(false); + } + out.WriteCompressed(numberOfBitsUsed); + out.Write(&extraData); + out.Write(hasNetworkId); + if (hasNetworkId) + out.Write(networkId); + out.AlignWriteToByteBoundary(); + out.Write(false); + stringCompressor->EncodeString(theList->operator [](i).identifier.uniqueIdentifier, 512, &out, 0); + out.WriteCompressed(bytesOnStack); + out.WriteAlignedBytes((const unsigned char*) inputStack, bytesOnStack); + SendUnified(&out, outgoingPriority, outgoingReliability, outgoingOrderingChannel, systemAddress, false); + return; + } + } + } + + // Failed to recover, inform the user + Packet *p = rakPeerInterface->AllocatePacket(sizeof(MessageID)+sizeof(unsigned char)); + RakNet::BitStream bs2(p->data, sizeof(MessageID)+sizeof(unsigned char), false); + bs2.SetWriteOffset(0); + bs2.Write((MessageID)ID_RPC_REMOTE_ERROR); + bs2.Write((unsigned char)RPC_ERROR_FUNCTION_NO_LONGER_REGISTERED); + stringCompressor->EncodeString("",256,&bs,0); + p->systemAddress=systemAddress; + rakPeerInterface->PushBackPacket(p, false); + +} +void AutoRPC::SendError(SystemAddress target, unsigned char errorCode, const char *functionName) +{ + RakNet::BitStream bs; + bs.Write((MessageID)ID_RPC_REMOTE_ERROR); + bs.Write(errorCode); + stringCompressor->EncodeString(functionName,256,&bs,0); + SendUnified(&bs, HIGH_PRIORITY, RELIABLE_ORDERED, 0, target, false); +} +void AutoRPC::OnRakPeerShutdown(void) +{ + Clear(); +} +void AutoRPC::Clear(void) +{ + unsigned i,j; + for (j=0; j < remoteFunctions.Size(); j++) + { + DataStructures::OrderedList *theList = remoteFunctions[j]; + for (i=0; i < theList->Size(); i++) + { + if (theList->operator [](i).identifier.uniqueIdentifier) + rakFree_Ex(theList->operator [](i).identifier.uniqueIdentifier, __FILE__, __LINE__ ); + } + RakNet::OP_DELETE(theList, __FILE__, __LINE__); + } + for (i=0; i < localFunctions.Size(); i++) + { + if (localFunctions[i].identifier.uniqueIdentifier) + rakFree_Ex(localFunctions[i].identifier.uniqueIdentifier, __FILE__, __LINE__ ); + } + localFunctions.Clear(false, __FILE__, __LINE__); + remoteFunctions.Clear(); + outgoingExtraData.Reset(); + incomingExtraData.Reset(); +} +unsigned AutoRPC::GetLocalFunctionIndex(AutoRPC::RPCIdentifier identifier) +{ + unsigned i; + for (i=0; i < localFunctions.Size(); i++) + { + if (localFunctions[i].identifier.isObjectMember==identifier.isObjectMember && + strcmp(localFunctions[i].identifier.uniqueIdentifier,identifier.uniqueIdentifier)==0) + return i; + } + return (unsigned) -1; +} +bool AutoRPC::GetRemoteFunctionIndex(SystemAddress systemAddress, AutoRPC::RPCIdentifier identifier, unsigned int *outerIndex, unsigned int *innerIndex) +{ + bool objectExists=false; + if (remoteFunctions.Has(systemAddress)) + { + *outerIndex = remoteFunctions.GetIndexAtKey(systemAddress); + DataStructures::OrderedList *theList = remoteFunctions[*outerIndex]; + *innerIndex = theList->GetIndexFromKey(identifier, &objectExists); + } + return objectExists; +} + + +#ifdef _MSC_VER +#pragma warning( pop ) +#endif + +#endif // _RAKNET_SUPPORT_* diff --git a/RakNet/Sources/AutoRPC.h b/RakNet/Sources/AutoRPC.h new file mode 100644 index 0000000..571b5d8 --- /dev/null +++ b/RakNet/Sources/AutoRPC.h @@ -0,0 +1,668 @@ +/// \file AutoRPC.h +/// \brief Automatically serializing and deserializing RPC system. More advanced RPC, but possibly not cross-platform. +/// +/// This file is part of RakNet Copyright 2003 Jenkins Software LLC +/// +/// Usage of RakNet is subject to the appropriate license agreement. + +#include "NativeFeatureIncludes.h" +#if _RAKNET_SUPPORT_AutoRPC==1 + +#ifndef __AUTO_RPC_H +#define __AUTO_RPC_H + +class RakPeerInterface; +class NetworkIDManager; +#include "PluginInterface2.h" +#include "DS_Map.h" +#include "PacketPriority.h" +#include "RakNetTypes.h" +#include "BitStream.h" +#include "Gen_RPC8.h" +#include "RakString.h" + +#ifdef _MSC_VER +#pragma warning( push ) +#endif + +/// \deprecated +/// \defgroup AUTO_RPC_GROUP AutoRPC +/// \brief Deprecated. Uses Assembly to do RPC +/// \details +/// \ingroup PLUGINS_GROUP + +namespace RakNet +{ + +/// Maximum amount of data that can be passed on the stack in a function call +#define ARPC_MAX_STACK_SIZE 65536 + +#if defined (_WIN32) +/// Easier way to get a pointer to a function member of a C++ class +/// \note Recommended you use ARPC_REGISTER_CPP_FUNCTION0 to ARPC_REGISTER_CPP_FUNCTION9 (below) +/// \note ARPC_REGISTER_CPP_FUNCTION is not Linux compatible, and cannot validate the number of parameters is correctly passed. +/// \param[in] autoRPCInstance A pointer to an instance of AutoRPC +/// \param[in] _IDENTIFIER_ C string identifier to use on the remote system to call the function +/// \param[in] _RETURN_ Return value of the function +/// \param[in] _CLASS_ Base-most class of the containing class that contains your function +/// \param[in] _FUNCTION_ Name of the function +/// \param[in] _PARAMS_ Parameter list, include parenthesis +#define ARPC_REGISTER_CPP_FUNCTION(autoRPCInstance, _IDENTIFIER_, _RETURN_, _CLASS_, _FUNCTION_, _PARAMS_) \ +{ \ +union \ +{ \ + _RETURN_ (AUTO_RPC_CALLSPEC _CLASS_::*__memberFunctionPtr)_PARAMS_; \ + void* __voidFunc; \ +}; \ + __memberFunctionPtr=&_CLASS_::_FUNCTION_; \ + (autoRPCInstance)->RegisterFunction(_IDENTIFIER_, __voidFunc, true, -1); \ +} + +/// \internal Used by ARPC_REGISTER_CPP_FUNCTION0 to ARPC_REGISTER_CPP_FUNCTION9 +#define ARPC_REGISTER_CPP_FUNCTIONX(autoRPCInstance, _IDENTIFIER_, _RETURN_, _CLASS_, _FUNCTION_, _PARAMS_, _PARAM_COUNT_) \ + { \ + union \ + { \ + _RETURN_ (AUTO_RPC_CALLSPEC _CLASS_::*__memberFunctionPtr)_PARAMS_; \ + void* __voidFunc; \ +}; \ + __memberFunctionPtr=&_CLASS_::_FUNCTION_; \ + (autoRPCInstance)->RegisterFunction(_IDENTIFIER_, __voidFunc, true, _PARAM_COUNT_); \ +} + +/// Same as ARPC_REGISTER_CPP_FUNCTION, but specifies how many parameters the function has +#define ARPC_REGISTER_CPP_FUNCTION0(autoRPCInstance, _IDENTIFIER_, _RETURN_, _CLASS_, _FUNCTION_) (autoRPCInstance)->RegisterFunction(_IDENTIFIER_, __voidFunc, true, 0); +#define ARPC_REGISTER_CPP_FUNCTION1(autoRPCInstance, _IDENTIFIER_, _RETURN_, _CLASS_, _FUNCTION_, _PARAMS1_) ARPC_REGISTER_CPP_FUNCTIONX(autoRPCInstance,_IDENTIFIER_,_RETURN_,_CLASS_,_FUNCTION_,(_PARAMS1_), 0) +#define ARPC_REGISTER_CPP_FUNCTION2(autoRPCInstance, _IDENTIFIER_, _RETURN_, _CLASS_, _FUNCTION_, _PARAMS1_, _PARAMS2_) ARPC_REGISTER_CPP_FUNCTIONX(autoRPCInstance,_IDENTIFIER_,_RETURN_,_CLASS_,_FUNCTION_,(_PARAMS1_,_PARAMS2_), 1) +#define ARPC_REGISTER_CPP_FUNCTION3(autoRPCInstance, _IDENTIFIER_, _RETURN_, _CLASS_, _FUNCTION_, _PARAMS1_, _PARAMS2_, _PARAMS3_) ARPC_REGISTER_CPP_FUNCTIONX(autoRPCInstance,_IDENTIFIER_,_RETURN_,_CLASS_,_FUNCTION_,(_PARAMS1_,_PARAMS2_,_PARAMS3_), 2) +#define ARPC_REGISTER_CPP_FUNCTION4(autoRPCInstance, _IDENTIFIER_, _RETURN_, _CLASS_, _FUNCTION_, _PARAMS1_, _PARAMS2_, _PARAMS3_, _PARAMS4_) ARPC_REGISTER_CPP_FUNCTIONX(autoRPCInstance,_IDENTIFIER_,_RETURN_,_CLASS_,_FUNCTION_,(_PARAMS1_,_PARAMS2_,_PARAMS3_,_PARAMS4_), 3) +#define ARPC_REGISTER_CPP_FUNCTION5(autoRPCInstance, _IDENTIFIER_, _RETURN_, _CLASS_, _FUNCTION_, _PARAMS1_, _PARAMS2_, _PARAMS3_, _PARAMS4_, _PARAMS5_) ARPC_REGISTER_CPP_FUNCTIONX(autoRPCInstance,_IDENTIFIER_,_RETURN_,_CLASS_,_FUNCTION_,(_PARAMS1_,_PARAMS2_,_PARAMS3_,_PARAMS4_,_PARAMS5_), 4) +#define ARPC_REGISTER_CPP_FUNCTION6(autoRPCInstance, _IDENTIFIER_, _RETURN_, _CLASS_, _FUNCTION_, _PARAMS1_, _PARAMS2_, _PARAMS3_, _PARAMS4_, _PARAMS5_, _PARAMS6_) ARPC_REGISTER_CPP_FUNCTIONX(autoRPCInstance,_IDENTIFIER_,_RETURN_,_CLASS_,_FUNCTION_,(_PARAMS1_,_PARAMS2_,_PARAMS3_,_PARAMS4_,_PARAMS5_,_PARAMS6_), 5) +#define ARPC_REGISTER_CPP_FUNCTION7(autoRPCInstance, _IDENTIFIER_, _RETURN_, _CLASS_, _FUNCTION_, _PARAMS1_, _PARAMS2_, _PARAMS3_, _PARAMS4_, _PARAMS5_, _PARAMS6_, _PARAMS7_) ARPC_REGISTER_CPP_FUNCTIONX(autoRPCInstance,_IDENTIFIER_,_RETURN_,_CLASS_,_FUNCTION_,(_PARAMS1_,_PARAMS2_,_PARAMS3_,_PARAMS4_,_PARAMS5_,_PARAMS6_,_PARAMS7_), 6) +#define ARPC_REGISTER_CPP_FUNCTION8(autoRPCInstance, _IDENTIFIER_, _RETURN_, _CLASS_, _FUNCTION_, _PARAMS1_, _PARAMS2_, _PARAMS3_, _PARAMS4_, _PARAMS5_, _PARAMS6_, _PARAMS7_, _PARAMS8_) ARPC_REGISTER_CPP_FUNCTIONX(autoRPCInstance,_IDENTIFIER_,_RETURN_,_CLASS_,_FUNCTION_,(_PARAMS1_,_PARAMS2_,_PARAMS3_,_PARAMS4_,_PARAMS5_,_PARAMS6_,_PARAMS7_,_PARAMS8_), 7) +#define ARPC_REGISTER_CPP_FUNCTION9(autoRPCInstance, _IDENTIFIER_, _RETURN_, _CLASS_, _FUNCTION_, _PARAMS1_, _PARAMS2_, _PARAMS3_, _PARAMS4_, _PARAMS5_, _PARAMS6_, _PARAMS7_, _PARAMS8_, _PARAMS9_) ARPC_REGISTER_CPP_FUNCTIONX(autoRPCInstance,_IDENTIFIER_,_RETURN_,_CLASS_,_FUNCTION_,(_PARAMS1_,_PARAMS2_,_PARAMS3_,_PARAMS4_,_PARAMS5_,_PARAMS6_,_PARAMS7_,_PARAMS8_,_PARAMS9_), 8) + +#else + +#define ARPC_REGISTER_CPP_FUNCTION0(autoRPCInstance, _IDENTIFIER_, _RETURN_, _CLASS_, _FUNCTION_) \ + (autoRPCInstance)->RegisterFunction((_IDENTIFIER_), (void*)(_RETURN_ (*) (_CLASS_*)) &_CLASS_::_FUNCTION_, true, 0 ); + +#define ARPC_REGISTER_CPP_FUNCTION1(autoRPCInstance, _IDENTIFIER_, _RETURN_, _CLASS_, _FUNCTION_, _PARAMS1_) \ + (autoRPCInstance)->RegisterFunction((_IDENTIFIER_), (void*)(_RETURN_ (*) (_CLASS_*, _PARAMS1_ )) &_CLASS_::_FUNCTION_, true, 0 ); + +#define ARPC_REGISTER_CPP_FUNCTION2(autoRPCInstance, _IDENTIFIER_, _RETURN_, _CLASS_, _FUNCTION_, _PARAMS1_, _PARAMS2_) \ + (autoRPCInstance)->RegisterFunction((_IDENTIFIER_), (void*)(_RETURN_ (*) (_CLASS_*, _PARAMS1_, _PARAMS2_ )) &_CLASS_::_FUNCTION_, true, 1 ); + +#define ARPC_REGISTER_CPP_FUNCTION3(autoRPCInstance, _IDENTIFIER_, _RETURN_, _CLASS_, _FUNCTION_, _PARAMS1_, _PARAMS2_, _PARAMS3_) \ + (autoRPCInstance)->RegisterFunction((_IDENTIFIER_), (void*)(_RETURN_ (*) (_CLASS_*, _PARAMS1_, _PARAMS2_, _PARAMS3_ )) &_CLASS_::_FUNCTION_, true, 2 ); + +#define ARPC_REGISTER_CPP_FUNCTION4(autoRPCInstance, _IDENTIFIER_, _RETURN_, _CLASS_, _FUNCTION_, _PARAMS1_, _PARAMS2_, _PARAMS3_, _PARAMS4_) \ + (autoRPCInstance)->RegisterFunction((_IDENTIFIER_), (void*)(_RETURN_ (*) (_CLASS_*, _PARAMS1_, _PARAMS2_, _PARAMS3_, _PARAMS4_ )) &_CLASS_::_FUNCTION_, true, 3 ); + +#define ARPC_REGISTER_CPP_FUNCTION5(autoRPCInstance, _IDENTIFIER_, _RETURN_, _CLASS_, _FUNCTION_, _PARAMS1_, _PARAMS2_, _PARAMS3_, _PARAMS4_, _PARAMS5_) \ + (autoRPCInstance)->RegisterFunction((_IDENTIFIER_), (void*)(_RETURN_ (*) (_CLASS_*, _PARAMS1_, _PARAMS2_, _PARAMS3_, _PARAMS4_, _PARAMS5_ )) &_CLASS_::_FUNCTION_, true, 4 ); + +#define ARPC_REGISTER_CPP_FUNCTION6(autoRPCInstance, _IDENTIFIER_, _RETURN_, _CLASS_, _FUNCTION_, _PARAMS1_, _PARAMS2_, _PARAMS3_, _PARAMS4_, _PARAMS5_, _PARAMS6_) \ + (autoRPCInstance)->RegisterFunction((_IDENTIFIER_), (void*)(_RETURN_ (*) (_CLASS_*, _PARAMS1_, _PARAMS2_, _PARAMS3_, _PARAMS4_, _PARAMS5_, _PARAMS6_ )) &_CLASS_::_FUNCTION_, true, 5 ); + +#define ARPC_REGISTER_CPP_FUNCTION7(autoRPCInstance, _IDENTIFIER_, _RETURN_, _CLASS_, _FUNCTION_, _PARAMS1_, _PARAMS2_, _PARAMS3_, _PARAMS4_, _PARAMS5_, _PARAMS6_, _PARAMS7_) \ + (autoRPCInstance)->RegisterFunction((_IDENTIFIER_), (void*)(_RETURN_ (*) (_CLASS_*, _PARAMS1_, _PARAMS2_, _PARAMS3_, _PARAMS4_, _PARAMS5_, _PARAMS6_, _PARAMS7_ )) &_CLASS_::_FUNCTION_, true, 6 ); + +#define ARPC_REGISTER_CPP_FUNCTION8(autoRPCInstance, _IDENTIFIER_, _RETURN_, _CLASS_, _FUNCTION_, _PARAMS1_, _PARAMS2_, _PARAMS3_, _PARAMS4_, _PARAMS5_, _PARAMS6_, _PARAMS7_, _PARAMS8_) \ + (autoRPCInstance)->RegisterFunction((_IDENTIFIER_), (void*)(_RETURN_ (*) (_CLASS_*, _PARAMS1_, _PARAMS2_, _PARAMS3_, _PARAMS4_, _PARAMS5_, _PARAMS6_, _PARAMS7_, _PARAMS8_ )) &_CLASS_::_FUNCTION_, true, 7 ); + +#define ARPC_REGISTER_CPP_FUNCTION9(autoRPCInstance, _IDENTIFIER_, _RETURN_, _CLASS_, _FUNCTION_, _PARAMS1_, _PARAMS2_, _PARAMS3_, _PARAMS4_, _PARAMS5_, _PARAMS6_, _PARAMS7_, _PARAMS8_, _PARAMS9_) \ + (autoRPCInstance)->RegisterFunction((_IDENTIFIER_), (void*)(_RETURN_ (*) (_CLASS_*, _PARAMS1_, _PARAMS2_, _PARAMS3_, _PARAMS4_, _PARAMS5_, _PARAMS6_, _PARAMS7_, _PARAMS8_, _PARAMS9_ )) &_CLASS_::_FUNCTION_, true, 8 ); + +#endif + +/// Error codes returned by a remote system as to why an RPC function call cannot execute +/// Follows packet ID ID_RPC_REMOTE_ERROR +/// Name of the function will be appended, if available. Read as follows: +/// char outputBuff[256]; +/// stringCompressor->DecodeString(outputBuff,256,&RakNet::BitStream(p->data+sizeof(MessageID)+1,p->length-sizeof(MessageID)-1,false),0); +/// printf("Function: %s\n", outputBuff); +enum RPCErrorCodes +{ + /// AutoRPC::SetNetworkIDManager() was not called, and it must be called to call a C++ object member + RPC_ERROR_NETWORK_ID_MANAGER_UNAVAILABLE, + + /// Cannot execute C++ object member call because the object specified by SetRecipientObject() does not exist on this system + RPC_ERROR_OBJECT_DOES_NOT_EXIST, + + /// Internal error, index optimization for function lookup does not exist + RPC_ERROR_FUNCTION_INDEX_OUT_OF_RANGE, + + /// Named function was not registered with RegisterFunction(). Check your spelling. + RPC_ERROR_FUNCTION_NOT_REGISTERED, + + /// Named function was registered, but later unregistered with UnregisterFunction() and can no longer be called. + RPC_ERROR_FUNCTION_NO_LONGER_REGISTERED, + + /// SetRecipientObject() was not called before Call(), but RegisterFunction() was called with isObjectMember=true + /// If you intended to call a CPP function, call SetRecipientObject() with a valid object first. + RPC_ERROR_CALLING_CPP_AS_C, + + /// SetRecipientObject() was called before Call(), but RegisterFunction() was called with isObjectMember=false + /// If you intended to call a C function, call SetRecipientObject(UNASSIGNED_NETWORK_ID) first. + RPC_ERROR_CALLING_C_AS_CPP, + + /// Internal error, passed stack is bigger than current stack. Check that the version is the same on both systems. + RPC_ERROR_STACK_TOO_SMALL, + + /// Internal error, formatting error with how the stack was serialized + RPC_ERROR_STACK_DESERIALIZATION_FAILED, + + /// The \a parameterCount parameter passed to RegisterFunction() on this system does not match the \a parameterCount parameter passed to SendCall() on the remote system. + RPC_ERROR_INCORRECT_NUMBER_OF_PARAMETERS, +}; + +/// \deprecated See RakNet::RPC3 + +/// The AutoRPC plugin allows you to call remote functions as if they were local functions, using the standard function call syntax +/// No serialization or deserialization is needed. +/// Advantages are that this is easier to use than regular RPC system. +/// Disadvantages is that all parameters must be passable on the stack using memcpy (shallow copy). For other types of parameters, use SetOutgoingExtraData() and GetIncomingExtraData() +/// Pointers are automatically dereferenced and the contents copied with memcpy +/// Use the old system, or regular message passing, if you need greater flexibility +/// \ingroup AUTO_RPC_GROUP +class AutoRPC : public PluginInterface2 +{ +public: + // Constructor + AutoRPC(); + + // Destructor + virtual ~AutoRPC(); + + /// Sets the network ID manager to use for object lookup + /// Required to call C++ object member functions via SetRecipientObject() + /// \param[in] idMan Pointer to the network ID manager to use + void SetNetworkIDManager(NetworkIDManager *idMan); + + /// Registers a function pointer to be callable given an identifier for the pointer + /// \param[in] uniqueIdentifier String identifying the function. Recommended that this is the name of the function + /// \param[in] functionPtr Pointer to the function. For C, just pass the name of the function. For C++, use ARPC_REGISTER_CPP_FUNCTION + /// \param[in] isObjectMember false if a C function. True if a member function of an object (C++) + /// \param[in] parameterCount Optional parameter to tell the system how many parameters this function has. If specified, and the wrong number of parameters are called by the remote system, the call is rejected. -1 indicates undefined + /// \return True on success, false on uniqueIdentifier already used. + bool RegisterFunction(const char *uniqueIdentifier, void *functionPtr, bool isObjectMember, char parameterCount=-1); + + /// Unregisters a function pointer to be callable given an identifier for the pointer + /// \note This is not safe to call while connected + /// \param[in] uniqueIdentifier String identifying the function. + /// \param[in] isObjectMember false if a C function. True if a member function of an object (C++) + /// \return True on success, false on function was not previously or is not currently registered. + bool UnregisterFunction(const char *uniqueIdentifier, bool isObjectMember); + + /// Send or stop sending a timestamp with all following calls to Call() + /// Use GetLastSenderTimestamp() to read the timestamp. + /// \param[in] timeStamp Non-zero to pass this timestamp using the ID_TIMESTAMP system. 0 to clear passing a timestamp. + void SetTimestamp(RakNetTime timeStamp); + + /// Set parameters to pass to RakPeer::Send() for all following calls to Call() + /// Deafults to HIGH_PRIORITY, RELIABLE_ORDERED, ordering channel 0 + /// \param[in] priority See RakPeer::Send() + /// \param[in] reliability See RakPeer::Send() + /// \param[in] orderingChannel See RakPeer::Send() + void SetSendParams(PacketPriority priority, PacketReliability reliability, char orderingChannel); + + /// Set system to send to for all following calls to Call() + /// Defaults to UNASSIGNED_SYSTEM_ADDRESS, broadcast=true + /// \param[in] systemIdentifier See RakPeer::Send() + /// \param[in] broadcast See RakPeer::Send() + void SetRecipientAddress(AddressOrGUID systemIdentifier, bool broadcast); + + /// Set the NetworkID to pass for all following calls to Call() + /// Defaults to UNASSIGNED_NETWORK_ID (none) + /// If set, the remote function will be considered a C++ function, e.g. an object member function + /// If set to UNASSIGNED_NETWORK_ID (none), the remote function will be considered a C function + /// If this is set incorrectly, you will get back either RPC_ERROR_CALLING_C_AS_CPP or RPC_ERROR_CALLING_CPP_AS_C + /// \sa NetworkIDManager + /// \param[in] networkID Returned from NetworkIDObject::GetNetworkID() + void SetRecipientObject(NetworkID networkID); + + /// Write extra data to pass for all following calls to Call() + /// Use BitStream::Reset to clear extra data. Don't forget to do this or you will waste bandwidth. + /// \return A bitstream you can write to to send extra data with each following call to Call() + RakNet::BitStream *SetOutgoingExtraData(void); + + /// If the last received function call has a timestamp included, it is stored and can be retrieved with this function. + /// \return 0 if the last call did not have a timestamp, else non-zero + RakNetTime GetLastSenderTimestamp(void) const; + + /// Returns the system address of the last system to send us a received function call + /// Equivalent to the old system RPCParameters::sender + /// \return Last system to send an RPC call using this system + SystemAddress GetLastSenderAddress(void) const; + + /// Returns the instance of RakPeer this plugin was attached to + RakPeerInterface *GetRakPeer(void) const; + + /// Returns the currently running RPC call identifier, set from RegisterFunction::uniqueIdentifier + /// Returns an empty string "" if none + /// \return which RPC call is currently running + const char *GetCurrentExecution(void) const; + + /// Gets the bitstream written to via SetOutgoingExtraData(). + /// Data is updated with each incoming function call + /// \return A bitstream you can read from with extra data that was written with SetOutgoingExtraData(); + RakNet::BitStream *GetIncomingExtraData(void); + + /// Calls a remote function, using whatever was last passed to SetTimestamp(), SetSendParams(), SetRecipientAddress(), and SetRecipientObject() + /// Passed parameter(s), if any, are passed via memcpy and pushed on the stack for the remote function + /// \note This ONLY works with variables that are passable via memcpy! If you need more flexibility, use SetOutgoingExtraData() and GetIncomingExtraData() + /// \note The this pointer, for this instance of AutoRPC, is pushed as the last parameter on the stack. See AutoRPCSample.ccp for an example of this + /// \param[in] uniqueIdentifier parameter of the same name passed to RegisterFunction() on the remote system + /// \return True on success, false on uniqueIdentifier already used. + bool Call(const char *uniqueIdentifier){ + char stack[ARPC_MAX_STACK_SIZE]; + unsigned int bytesOnStack = GenRPC::BuildStack(stack); + return SendCall(uniqueIdentifier, stack, bytesOnStack, 0); + } + + /// Calls a remote function, using whatever was last passed to SetTimestamp(), SetSendParams(), SetRecipientAddress(), and SetRecipientObject() + /// Passed parameter(s), if any, are passed via memcpy and pushed on the stack for the remote function + /// \note This ONLY works with variables that are passable via memcpy! If you need more flexibility, use SetOutgoingExtraData() and GetIncomingExtraData() + /// \note The this pointer, for this instance of AutoRPC, is pushed as the last parameter on the stack. See AutoRPCSample.ccp for an example of this + /// \param[in] uniqueIdentifier parameter of the same name passed to RegisterFunction() on the remote system + /// \return True on success, false on uniqueIdentifier already used. + template + bool Call(const char *uniqueIdentifier, P1 p1) { + char stack[ARPC_MAX_STACK_SIZE]; + unsigned int bytesOnStack = GenRPC::BuildStack(stack, p1, true); + return SendCall(uniqueIdentifier, stack, bytesOnStack, 1); + } + + /// Calls a remote function, using whatever was last passed to SetTimestamp(), SetSendParams(), SetRecipientAddress(), and SetRecipientObject() + /// Passed parameter(s), if any, are passed via memcpy and pushed on the stack for the remote function + /// \note This ONLY works with variables that are passable via memcpy! If you need more flexibility, use SetOutgoingExtraData() and GetIncomingExtraData() + /// \note The this pointer, for this instance of AutoRPC, is pushed as the last parameter on the stack. See AutoRPCSample.ccp for an example of this + /// \param[in] uniqueIdentifier parameter of the same name passed to RegisterFunction() on the remote system + /// \return True on success, false on uniqueIdentifier already used. + template + bool Call(const char *uniqueIdentifier, P1 p1, P2 p2) { + char stack[ARPC_MAX_STACK_SIZE]; + unsigned int bytesOnStack = GenRPC::BuildStack(stack, p1, p2, true, true); + return SendCall(uniqueIdentifier, stack, bytesOnStack, 2); + } + + /// Calls a remote function, using whatever was last passed to SetTimestamp(), SetSendParams(), SetRecipientAddress(), and SetRecipientObject() + /// Passed parameter(s), if any, are passed via memcpy and pushed on the stack for the remote function + /// \note This ONLY works with variables that are passable via memcpy! If you need more flexibility, use SetOutgoingExtraData() and GetIncomingExtraData() + /// \note The this pointer, for this instance of AutoRPC, is pushed as the last parameter on the stack. See AutoRPCSample.ccp for an example of this + /// \param[in] uniqueIdentifier parameter of the same name passed to RegisterFunction() on the remote system + /// \return True on success, false on uniqueIdentifier already used. + template + bool Call(const char *uniqueIdentifier, P1 p1, P2 p2, P3 p3 ) { + char stack[ARPC_MAX_STACK_SIZE]; + unsigned int bytesOnStack = GenRPC::BuildStack(stack, p1, p2, p3, true, true, true); + return SendCall(uniqueIdentifier, stack, bytesOnStack, 3); + } + + /// Calls a remote function, using whatever was last passed to SetTimestamp(), SetSendParams(), SetRecipientAddress(), and SetRecipientObject() + /// Passed parameter(s), if any, are passed via memcpy and pushed on the stack for the remote function + /// \note This ONLY works with variables that are passable via memcpy! If you need more flexibility, use SetOutgoingExtraData() and GetIncomingExtraData() + /// \note The this pointer, for this instance of AutoRPC, is pushed as the last parameter on the stack. See AutoRPCSample.ccp for an example of this + /// \param[in] uniqueIdentifier parameter of the same name passed to RegisterFunction() on the remote system + /// \return True on success, false on uniqueIdentifier already used. + template + bool Call(const char *uniqueIdentifier, P1 p1, P2 p2, P3 p3, P4 p4 ) { + char stack[ARPC_MAX_STACK_SIZE]; + unsigned int bytesOnStack = GenRPC::BuildStack(stack, p1, p2, p3, p4, true, true, true, true); + return SendCall(uniqueIdentifier, stack, bytesOnStack, 4); + } + + /// Calls a remote function, using whatever was last passed to SetTimestamp(), SetSendParams(), SetRecipientAddress(), and SetRecipientObject() + /// Passed parameter(s), if any, are passed via memcpy and pushed on the stack for the remote function + /// \note This ONLY works with variables that are passable via memcpy! If you need more flexibility, use SetOutgoingExtraData() and GetIncomingExtraData() + /// \note The this pointer, for this instance of AutoRPC, is pushed as the last parameter on the stack. See AutoRPCSample.ccp for an example of this + /// \param[in] uniqueIdentifier parameter of the same name passed to RegisterFunction() on the remote system + /// \return True on success, false on uniqueIdentifier already used. + template + bool Call(const char *uniqueIdentifier, P1 p1, P2 p2, P3 p3, P4 p4, P5 p5 ) { + char stack[ARPC_MAX_STACK_SIZE]; + unsigned int bytesOnStack = GenRPC::BuildStack(stack, p1, p2, p3, p4, p5, true, true, true, true, true); + return SendCall(uniqueIdentifier, stack, bytesOnStack, 5); + } + + /// Calls a remote function, using whatever was last passed to SetTimestamp(), SetSendParams(), SetRecipientAddress(), and SetRecipientObject() + /// Passed parameter(s), if any, are passed via memcpy and pushed on the stack for the remote function + /// \note This ONLY works with variables that are passable via memcpy! If you need more flexibility, use SetOutgoingExtraData() and GetIncomingExtraData() + /// \note The this pointer, for this instance of AutoRPC, is pushed as the last parameter on the stack. See AutoRPCSample.ccp for an example of this + /// \param[in] uniqueIdentifier parameter of the same name passed to RegisterFunction() on the remote system + /// \return True on success, false on uniqueIdentifier already used. + template + bool Call(const char *uniqueIdentifier, P1 p1, P2 p2, P3 p3, P4 p4, P5 p5, P6 p6 ) { + char stack[ARPC_MAX_STACK_SIZE]; + unsigned int bytesOnStack = GenRPC::BuildStack(stack, p1, p2, p3, p4, p5, p6, true, true, true, true, true, true); + return SendCall(uniqueIdentifier, stack, bytesOnStack, 6); + } + + /// Calls a remote function, using whatever was last passed to SetTimestamp(), SetSendParams(), SetRecipientAddress(), and SetRecipientObject() + /// Passed parameter(s), if any, are passed via memcpy and pushed on the stack for the remote function + /// \note This ONLY works with variables that are passable via memcpy! If you need more flexibility, use SetOutgoingExtraData() and GetIncomingExtraData() + /// \note The this pointer, for this instance of AutoRPC, is pushed as the last parameter on the stack. See AutoRPCSample.ccp for an example of this + /// \param[in] uniqueIdentifier parameter of the same name passed to RegisterFunction() on the remote system + /// \return True on success, false on uniqueIdentifier already used. + template + bool Call(const char *uniqueIdentifier, P1 p1, P2 p2, P3 p3, P4 p4, P5 p5, P6 p6, P7 p7 ) { + char stack[ARPC_MAX_STACK_SIZE]; + unsigned int bytesOnStack = GenRPC::BuildStack(stack, p1, p2, p3, p4, p5, p6, p7, true, true, true, true, true, true, true); + return SendCall(uniqueIdentifier, stack, bytesOnStack, 7); + } + + /// Calls a remote function, using whatever was last passed to SetTimestamp(), SetSendParams(), SetRecipientAddress(), and SetRecipientObject() + /// Passed parameter(s), if any, are passed via memcpy and pushed on the stack for the remote function + /// \note This ONLY works with variables that are passable via memcpy! If you need more flexibility, use SetOutgoingExtraData() and GetIncomingExtraData() + /// \note The this pointer, for this instance of AutoRPC, is pushed as the last parameter on the stack. See AutoRPCSample.ccp for an example of this + /// \param[in] uniqueIdentifier parameter of the same name passed to RegisterFunction() on the remote system + /// \return True on success, false on uniqueIdentifier already used. + template + bool Call(const char *uniqueIdentifier, P1 p1, P2 p2, P3 p3, P4 p4, P5 p5, P6 p6, P7 p7, P8 p8 ) { + char stack[ARPC_MAX_STACK_SIZE]; + unsigned int bytesOnStack = GenRPC::BuildStack(stack, p1, p2, p3, p4, p5, p6, p7, p8, true, true, true, true, true, true, true, true); + return SendCall(uniqueIdentifier, stack, bytesOnStack, 8); + } + + /// Calls a remote function, using whatever was last passed to SetTimestamp(), SetSendParams(), SetRecipientAddress(), and SetRecipientObject() + /// Passed parameter(s), if any, are passed via memcpy and pushed on the stack for the remote function + /// \note This ONLY works with variables that are passable via memcpy! If you need more flexibility, use SetOutgoingExtraData() and GetIncomingExtraData() + /// \note The this pointer, for this instance of AutoRPC, is pushed as the last parameter on the stack. See AutoRPCSample.ccp for an example of this + /// \param[in] uniqueIdentifier parameter of the same name passed to RegisterFunction() on the remote system + /// \param[in] timeStamp See SetTimestamp() + /// \param[in] priority See SetSendParams() + /// \param[in] reliability See SetSendParams() + /// \param[in] orderingChannel See SetSendParams() + /// \param[in] systemAddress See SetRecipientAddress() + /// \param[in] broadcast See SetRecipientAddress() + /// \param[in] networkID See SetRecipientObject() + /// \return True on success, false on uniqueIdentifier already used. + bool CallExplicit(const char *uniqueIdentifier, RakNetTime timeStamp, PacketPriority priority, PacketReliability reliability, char orderingChannel, const AddressOrGUID systemIdentifier, bool broadcast, NetworkID networkID){ + SetTimestamp(timeStamp); + SetSendParams(priority, reliability, orderingChannel); + SetRecipientAddress(systemIdentifier, broadcast); + SetRecipientObject(networkID); + char stack[ARPC_MAX_STACK_SIZE]; + unsigned int bytesOnStack = GenRPC::BuildStack(stack); + return SendCall(uniqueIdentifier, stack, bytesOnStack, 0); + } + + /// Calls a remote function, using whatever was last passed to SetTimestamp(), SetSendParams(), SetRecipientAddress(), and SetRecipientObject() + /// Passed parameter(s), if any, are passed via memcpy and pushed on the stack for the remote function + /// \note This ONLY works with variables that are passable via memcpy! If you need more flexibility, use SetOutgoingExtraData() and GetIncomingExtraData() + /// \note The this pointer, for this instance of AutoRPC, is pushed as the last parameter on the stack. See AutoRPCSample.ccp for an example of this + /// \param[in] uniqueIdentifier parameter of the same name passed to RegisterFunction() on the remote system + /// \param[in] timeStamp See SetTimestamp() + /// \param[in] priority See SetSendParams() + /// \param[in] reliability See SetSendParams() + /// \param[in] orderingChannel See SetSendParams() + /// \param[in] systemAddress See SetRecipientAddress() + /// \param[in] broadcast See SetRecipientAddress() + /// \param[in] networkID See SetRecipientObject() + /// \return True on success, false on uniqueIdentifier already used. + template + bool CallExplicit(const char *uniqueIdentifier, RakNetTime timeStamp, PacketPriority priority, PacketReliability reliability, char orderingChannel, const AddressOrGUID systemIdentifier, bool broadcast, NetworkID networkID, P1 p1) { + SetTimestamp(timeStamp); + SetSendParams(priority, reliability, orderingChannel); + SetRecipientAddress(systemIdentifier, broadcast); + SetRecipientObject(networkID); + char stack[ARPC_MAX_STACK_SIZE]; + unsigned int bytesOnStack = GenRPC::BuildStack(stack, p1, true); + return SendCall(uniqueIdentifier, stack, bytesOnStack, 1); + } + + /// Calls a remote function, using whatever was last passed to SetTimestamp(), SetSendParams(), SetRecipientAddress(), and SetRecipientObject() + /// Passed parameter(s), if any, are passed via memcpy and pushed on the stack for the remote function + /// \note This ONLY works with variables that are passable via memcpy! If you need more flexibility, use SetOutgoingExtraData() and GetIncomingExtraData() + /// \note The this pointer, for this instance of AutoRPC, is pushed as the last parameter on the stack. See AutoRPCSample.ccp for an example of this + /// \param[in] uniqueIdentifier parameter of the same name passed to RegisterFunction() on the remote system + /// \param[in] timeStamp See SetTimestamp() + /// \param[in] priority See SetSendParams() + /// \param[in] reliability See SetSendParams() + /// \param[in] orderingChannel See SetSendParams() + /// \param[in] systemAddress See SetRecipientAddress() + /// \param[in] broadcast See SetRecipientAddress() + /// \param[in] networkID See SetRecipientObject() + /// \return True on success, false on uniqueIdentifier already used. + template + bool CallExplicit(const char *uniqueIdentifier, RakNetTime timeStamp, PacketPriority priority, PacketReliability reliability, char orderingChannel, const AddressOrGUID systemIdentifier, bool broadcast, NetworkID networkID, P1 p1, P2 p2) { + SetTimestamp(timeStamp); + SetSendParams(priority, reliability, orderingChannel); + SetRecipientAddress(systemIdentifier, broadcast); + SetRecipientObject(networkID); + char stack[ARPC_MAX_STACK_SIZE]; + unsigned int bytesOnStack = GenRPC::BuildStack(stack, p1, p2, true, true); + return SendCall(uniqueIdentifier, stack, bytesOnStack, 2); + } + + /// Calls a remote function, using whatever was last passed to SetTimestamp(), SetSendParams(), SetRecipientAddress(), and SetRecipientObject() + /// Passed parameter(s), if any, are passed via memcpy and pushed on the stack for the remote function + /// \note This ONLY works with variables that are passable via memcpy! If you need more flexibility, use SetOutgoingExtraData() and GetIncomingExtraData() + /// \note The this pointer, for this instance of AutoRPC, is pushed as the last parameter on the stack. See AutoRPCSample.ccp for an example of this + /// \param[in] uniqueIdentifier parameter of the same name passed to RegisterFunction() on the remote system + /// \param[in] timeStamp See SetTimestamp() + /// \param[in] priority See SetSendParams() + /// \param[in] reliability See SetSendParams() + /// \param[in] orderingChannel See SetSendParams() + /// \param[in] systemAddress See SetRecipientAddress() + /// \param[in] broadcast See SetRecipientAddress() + /// \param[in] networkID See SetRecipientObject() + /// \return True on success, false on uniqueIdentifier already used. + template + bool CallExplicit(const char *uniqueIdentifier, RakNetTime timeStamp, PacketPriority priority, PacketReliability reliability, char orderingChannel, const AddressOrGUID systemIdentifier, bool broadcast, NetworkID networkID, P1 p1, P2 p2, P3 p3 ) { + SetTimestamp(timeStamp); + SetSendParams(priority, reliability, orderingChannel); + SetRecipientAddress(systemIdentifier, broadcast); + SetRecipientObject(networkID); + char stack[ARPC_MAX_STACK_SIZE]; + unsigned int bytesOnStack = GenRPC::BuildStack(stack, p1, p2, p3, true, true, true); + return SendCall(uniqueIdentifier, stack, bytesOnStack, 3); + } + + /// Calls a remote function, using whatever was last passed to SetTimestamp(), SetSendParams(), SetRecipientAddress(), and SetRecipientObject() + /// Passed parameter(s), if any, are passed via memcpy and pushed on the stack for the remote function + /// \note This ONLY works with variables that are passable via memcpy! If you need more flexibility, use SetOutgoingExtraData() and GetIncomingExtraData() + /// \note The this pointer, for this instance of AutoRPC, is pushed as the last parameter on the stack. See AutoRPCSample.ccp for an example of this + /// \param[in] uniqueIdentifier parameter of the same name passed to RegisterFunction() on the remote system + /// \param[in] timeStamp See SetTimestamp() + /// \param[in] priority See SetSendParams() + /// \param[in] reliability See SetSendParams() + /// \param[in] orderingChannel See SetSendParams() + /// \param[in] systemAddress See SetRecipientAddress() + /// \param[in] broadcast See SetRecipientAddress() + /// \param[in] networkID See SetRecipientObject() + /// \return True on success, false on uniqueIdentifier already used. + template + bool CallExplicit(const char *uniqueIdentifier, RakNetTime timeStamp, PacketPriority priority, PacketReliability reliability, char orderingChannel, const AddressOrGUID systemIdentifier, bool broadcast, NetworkID networkID, P1 p1, P2 p2, P3 p3, P4 p4 ) { + SetTimestamp(timeStamp); + SetSendParams(priority, reliability, orderingChannel); + SetRecipientAddress(systemIdentifier, broadcast); + SetRecipientObject(networkID); + char stack[ARPC_MAX_STACK_SIZE]; + unsigned int bytesOnStack = GenRPC::BuildStack(stack, p1, p2, p3, p4, true, true, true, true); + return SendCall(uniqueIdentifier, stack, bytesOnStack, 4); + } + + /// Calls a remote function, using whatever was last passed to SetTimestamp(), SetSendParams(), SetRecipientAddress(), and SetRecipientObject() + /// Passed parameter(s), if any, are passed via memcpy and pushed on the stack for the remote function + /// \note This ONLY works with variables that are passable via memcpy! If you need more flexibility, use SetOutgoingExtraData() and GetIncomingExtraData() + /// \note The this pointer, for this instance of AutoRPC, is pushed as the last parameter on the stack. See AutoRPCSample.ccp for an example of this + /// \param[in] uniqueIdentifier parameter of the same name passed to RegisterFunction() on the remote system + /// \param[in] timeStamp See SetTimestamp() + /// \param[in] priority See SetSendParams() + /// \param[in] reliability See SetSendParams() + /// \param[in] orderingChannel See SetSendParams() + /// \param[in] systemAddress See SetRecipientAddress() + /// \param[in] broadcast See SetRecipientAddress() + /// \param[in] networkID See SetRecipientObject() + /// \return True on success, false on uniqueIdentifier already used. + template + bool CallExplicit(const char *uniqueIdentifier, RakNetTime timeStamp, PacketPriority priority, PacketReliability reliability, char orderingChannel, const AddressOrGUID systemIdentifier, bool broadcast, NetworkID networkID, P1 p1, P2 p2, P3 p3, P4 p4, P5 p5 ) { + SetTimestamp(timeStamp); + SetSendParams(priority, reliability, orderingChannel); + SetRecipientAddress(systemIdentifier, broadcast); + SetRecipientObject(networkID); + char stack[ARPC_MAX_STACK_SIZE]; + unsigned int bytesOnStack = GenRPC::BuildStack(stack, p1, p2, p3, p4, p5, true, true, true, true, true); + return SendCall(uniqueIdentifier, stack, bytesOnStack, 5); + } + + /// Calls a remote function, using whatever was last passed to SetTimestamp(), SetSendParams(), SetRecipientAddress(), and SetRecipientObject() + /// Passed parameter(s), if any, are passed via memcpy and pushed on the stack for the remote function + /// \note This ONLY works with variables that are passable via memcpy! If you need more flexibility, use SetOutgoingExtraData() and GetIncomingExtraData() + /// \note The this pointer, for this instance of AutoRPC, is pushed as the last parameter on the stack. See AutoRPCSample.ccp for an example of this + /// \param[in] uniqueIdentifier parameter of the same name passed to RegisterFunction() on the remote system + /// \param[in] timeStamp See SetTimestamp() + /// \param[in] priority See SetSendParams() + /// \param[in] reliability See SetSendParams() + /// \param[in] orderingChannel See SetSendParams() + /// \param[in] systemAddress See SetRecipientAddress() + /// \param[in] broadcast See SetRecipientAddress() + /// \param[in] networkID See SetRecipientObject() + /// \return True on success, false on uniqueIdentifier already used. + template + bool CallExplicit(const char *uniqueIdentifier, RakNetTime timeStamp, PacketPriority priority, PacketReliability reliability, char orderingChannel, const AddressOrGUID systemIdentifier, bool broadcast, NetworkID networkID, P1 p1, P2 p2, P3 p3, P4 p4, P5 p5, P6 p6 ) { + SetTimestamp(timeStamp); + SetSendParams(priority, reliability, orderingChannel); + SetRecipientAddress(systemIdentifier, broadcast); + SetRecipientObject(networkID); + char stack[ARPC_MAX_STACK_SIZE]; + unsigned int bytesOnStack = GenRPC::BuildStack(stack, p1, p2, p3, p4, p5, p6, true, true, true, true, true, true); + return SendCall(uniqueIdentifier, stack, bytesOnStack, 6); + } + + /// Calls a remote function, using whatever was last passed to SetTimestamp(), SetSendParams(), SetRecipientAddress(), and SetRecipientObject() + /// Passed parameter(s), if any, are passed via memcpy and pushed on the stack for the remote function + /// \note This ONLY works with variables that are passable via memcpy! If you need more flexibility, use SetOutgoingExtraData() and GetIncomingExtraData() + /// \note The this pointer, for this instance of AutoRPC, is pushed as the last parameter on the stack. See AutoRPCSample.ccp for an example of this + /// \param[in] uniqueIdentifier parameter of the same name passed to RegisterFunction() on the remote system + /// \param[in] timeStamp See SetTimestamp() + /// \param[in] priority See SetSendParams() + /// \param[in] reliability See SetSendParams() + /// \param[in] orderingChannel See SetSendParams() + /// \param[in] systemAddress See SetRecipientAddress() + /// \param[in] broadcast See SetRecipientAddress() + /// \param[in] networkID See SetRecipientObject() + /// \return True on success, false on uniqueIdentifier already used. + template + bool CallExplicit(const char *uniqueIdentifier, RakNetTime timeStamp, PacketPriority priority, PacketReliability reliability, char orderingChannel, const AddressOrGUID systemIdentifier, bool broadcast, NetworkID networkID, P1 p1, P2 p2, P3 p3, P4 p4, P5 p5, P6 p6, P7 p7 ) { + SetTimestamp(timeStamp); + SetSendParams(priority, reliability, orderingChannel); + SetRecipientAddress(systemIdentifier, broadcast); + SetRecipientObject(networkID); + char stack[ARPC_MAX_STACK_SIZE]; + unsigned int bytesOnStack = GenRPC::BuildStack(stack, p1, p2, p3, p4, p5, p6, p7, true, true, true, true, true, true, true); + return SendCall(uniqueIdentifier, stack, bytesOnStack, 7); + } + + /// Calls a remote function, using whatever was last passed to SetTimestamp(), SetSendParams(), SetRecipientAddress(), and SetRecipientObject() + /// Passed parameter(s), if any, are passed via memcpy and pushed on the stack for the remote function + /// \note This ONLY works with variables that are passable via memcpy! If you need more flexibility, use SetOutgoingExtraData() and GetIncomingExtraData() + /// \note The this pointer, for this instance of AutoRPC, is pushed as the last parameter on the stack. See AutoRPCSample.ccp for an example of this + /// \param[in] uniqueIdentifier parameter of the same name passed to RegisterFunction() on the remote system + /// \param[in] timeStamp See SetTimestamp() + /// \param[in] priority See SetSendParams() + /// \param[in] reliability See SetSendParams() + /// \param[in] orderingChannel See SetSendParams() + /// \param[in] systemAddress See SetRecipientAddress() + /// \param[in] broadcast See SetRecipientAddress() + /// \param[in] networkID See SetRecipientObject() + /// \return True on success, false on uniqueIdentifier already used. + template + bool CallExplicit(const char *uniqueIdentifier, RakNetTime timeStamp, PacketPriority priority, PacketReliability reliability, char orderingChannel, const AddressOrGUID systemIdentifier, bool broadcast, NetworkID networkID, P1 p1, P2 p2, P3 p3, P4 p4, P5 p5, P6 p6, P7 p7, P8 p8 ) { + SetTimestamp(timeStamp); + SetSendParams(priority, reliability, orderingChannel); + SetRecipientAddress(systemIdentifier, broadcast); + SetRecipientObject(networkID); + char stack[ARPC_MAX_STACK_SIZE]; + unsigned int bytesOnStack = GenRPC::BuildStack(stack, p1, p2, p3, p4, p5, p6, p7, p8, true, true, true, true, true, true, true, true); + return SendCall(uniqueIdentifier, stack, bytesOnStack, 8); + } + + + // If you need more than 8 parameters, just add it here... + + // ---------------------------- ALL INTERNAL AFTER HERE ---------------------------- + + /// \internal + /// Identifies an RPC function, by string identifier and if it is a C or C++ function + struct RPCIdentifier + { + char *uniqueIdentifier; + bool isObjectMember; + }; + + /// \internal + /// The RPC identifier, and a pointer to the function + struct LocalRPCFunction + { + RPCIdentifier identifier; + void *functionPtr; + char parameterCount; + }; + + /// \internal + /// The RPC identifier, and the index of the function on a remote system + struct RemoteRPCFunction + { + RPCIdentifier identifier; + unsigned int functionIndex; + }; + + /// \internal + static int RemoteRPCFunctionComp( const RPCIdentifier &key, const RemoteRPCFunction &data ); + + /// \internal + /// Sends the RPC call, with a given serialized stack + bool SendCall(const char *uniqueIdentifier, const char *stack, unsigned int bytesOnStack, char parameterCount); + +protected: + + // -------------------------------------------------------------------------------------------- + // Packet handling functions + // -------------------------------------------------------------------------------------------- + void OnAttach(void); + virtual PluginReceiveResult OnReceive(Packet *packet); + virtual void OnAutoRPCCall(SystemAddress systemAddress, unsigned char *data, unsigned int lengthInBytes); + virtual void OnRPCRemoteIndex(SystemAddress systemAddress, unsigned char *data, unsigned int lengthInBytes); + virtual void OnRPCUnknownRemoteIndex(SystemAddress systemAddress, unsigned char *data, unsigned int lengthInBytes, RakNetTime timestamp); + virtual void OnClosedConnection(SystemAddress systemAddress, RakNetGUID rakNetGUID, PI2_LostConnectionReason lostConnectionReason ); + virtual void OnRakPeerShutdown(void); + + void Clear(void); + + void SendError(SystemAddress target, unsigned char errorCode, const char *functionName); + unsigned GetLocalFunctionIndex(RPCIdentifier identifier); + bool GetRemoteFunctionIndex(SystemAddress systemAddress, RPCIdentifier identifier, unsigned int *outerIndex, unsigned int *innerIndex); + + + DataStructures::List localFunctions; + DataStructures::Map *> remoteFunctions; + + RakNetTime outgoingTimestamp; + PacketPriority outgoingPriority; + PacketReliability outgoingReliability; + char outgoingOrderingChannel; + AddressOrGUID outgoingSystemIdentifier; + bool outgoingBroadcast; + NetworkID outgoingNetworkID; + RakNet::BitStream outgoingExtraData; + + RakNetTime incomingTimeStamp; + SystemAddress incomingSystemAddress; + RakNet::BitStream incomingExtraData; + + NetworkIDManager *networkIdManager; + char currentExecution[512]; +}; + +} // End namespace + +#endif + +#ifdef _MSC_VER +#pragma warning( pop ) +#endif + +#endif // _RAKNET_SUPPORT_* diff --git a/RakNet/Sources/AutopatcherPatchContext.h b/RakNet/Sources/AutopatcherPatchContext.h new file mode 100644 index 0000000..3082fdc --- /dev/null +++ b/RakNet/Sources/AutopatcherPatchContext.h @@ -0,0 +1,22 @@ +/// +/// 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_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 diff --git a/RakNet/Sources/AutopatcherRepositoryInterface.h b/RakNet/Sources/AutopatcherRepositoryInterface.h new file mode 100644 index 0000000..5a9e3cc --- /dev/null +++ b/RakNet/Sources/AutopatcherRepositoryInterface.h @@ -0,0 +1,47 @@ +/// +/// \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 + +class FileList; +namespace RakNet +{ + class BitStream; +} + +#include "IncrementalReadInterface.h" +#include "SimpleMutex.h" + +/// 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, const char *sinceDate, char currentDate[64])=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, char currentDate[64])=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; +}; + +#endif + diff --git a/RakNet/Sources/BigInt.cpp b/RakNet/Sources/BigInt.cpp new file mode 100644 index 0000000..5d993bb --- /dev/null +++ b/RakNet/Sources/BigInt.cpp @@ -0,0 +1,1813 @@ +#if !defined(_XBOX) && !defined(X360) + +#include "BigInt.h" +#include +#include + +#include "RakAlloca.h" +#include "RakMemoryOverride.h" +#include "Rand.h" + +#if defined(_MSC_VER) && !defined(_DEBUG) && _MSC_VER > 1310 +#include +#endif + +namespace big +{ + static const char Bits256[] = { + 0, 1, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4, 4, 4, 4, 4, + 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, + 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, + 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, + 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, + 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, + 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, + 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, + 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, + 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, + 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, + 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, + 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, + 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, + 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, + 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8 + }; + + // returns the degree of the base 2 monic polynomial + // (the number of bits used to represent the number) + // eg, 0 0 0 0 1 0 1 1 ... => 28 out of 32 used + uint32_t Degree(uint32_t v) + { +//#if defined(_MSC_VER) && !defined(_DEBUG) +// unsigned long index; +// return _BitScanReverse(&index, v) ? (index + 1) : 0; +//#else + uint32_t r, t = v >> 16; + + if (t) r = (r = t >> 8) ? 24 + Bits256[r] : 16 + Bits256[t]; + else r = (r = v >> 8) ? 8 + Bits256[r] : Bits256[v]; + + return r; +//#endif + } + + // returns the number of limbs that are actually used + int LimbDegree(const uint32_t *n, int limbs) + { + while (limbs--) + if (n[limbs]) + return limbs + 1; + + return 0; + } + + // return bits used + uint32_t Degree(const uint32_t *n, int limbs) + { + uint32_t limb_degree = LimbDegree(n, limbs); + if (!limb_degree) return 0; + --limb_degree; + + uint32_t msl_degree = Degree(n[limb_degree]); + + return msl_degree + limb_degree*32; + } + + void Set(uint32_t *lhs, int lhs_limbs, const uint32_t *rhs, int rhs_limbs) + { + int min = lhs_limbs < rhs_limbs ? lhs_limbs : rhs_limbs; + + memcpy(lhs, rhs, min*4); + memset(&lhs[min], 0, (lhs_limbs - min)*4); + } + void Set(uint32_t *lhs, int limbs, const uint32_t *rhs) + { + memcpy(lhs, rhs, limbs*4); + } + void Set32(uint32_t *lhs, int lhs_limbs, const uint32_t rhs) + { + *lhs = rhs; + memset(&lhs[1], 0, (lhs_limbs - 1)*4); + } + +#if defined(__BIG_ENDIAN__) + + // Flip the byte order as needed to make 'n' big-endian for sharing over a network + void ToLittleEndian(uint32_t *n, int limbs) + { + for (int ii = 0; ii < limbs; ++ii) + { + swapLE(n[ii]); + } + } + + // Flip the byte order as needed to make big-endian 'n' use the local byte order + void FromLittleEndian(uint32_t *n, int limbs) + { + // Same operation as ToBigEndian() + ToLittleEndian(n, limbs); + } + +#endif // __BIG_ENDIAN__ + + bool Less(int limbs, const uint32_t *lhs, const uint32_t *rhs) + { + for (int ii = limbs-1; ii >= 0; --ii) + if (lhs[ii] != rhs[ii]) + return lhs[ii] < rhs[ii]; + + return false; + } + bool Greater(int limbs, const uint32_t *lhs, const uint32_t *rhs) + { + for (int ii = limbs-1; ii >= 0; --ii) + if (lhs[ii] != rhs[ii]) + return lhs[ii] > rhs[ii]; + + return false; + } + bool Equal(int limbs, const uint32_t *lhs, const uint32_t *rhs) + { + return 0 == memcmp(lhs, rhs, limbs*4); + } + + bool Less(const uint32_t *lhs, int lhs_limbs, const uint32_t *rhs, int rhs_limbs) + { + if (lhs_limbs > rhs_limbs) + do if (lhs[--lhs_limbs] != 0) return false; while (lhs_limbs > rhs_limbs); + else if (lhs_limbs < rhs_limbs) + do if (rhs[--rhs_limbs] != 0) return true; while (lhs_limbs < rhs_limbs); + + while (lhs_limbs--) if (lhs[lhs_limbs] != rhs[lhs_limbs]) return lhs[lhs_limbs] < rhs[lhs_limbs]; + return false; // equal + } + bool Greater(const uint32_t *lhs, int lhs_limbs, const uint32_t *rhs, int rhs_limbs) + { + if (lhs_limbs > rhs_limbs) + do if (lhs[--lhs_limbs] != 0) return true; while (lhs_limbs > rhs_limbs); + else if (lhs_limbs < rhs_limbs) + do if (rhs[--rhs_limbs] != 0) return false; while (lhs_limbs < rhs_limbs); + + while (lhs_limbs--) if (lhs[lhs_limbs] != rhs[lhs_limbs]) return lhs[lhs_limbs] > rhs[lhs_limbs]; + return false; // equal + } + bool Equal(const uint32_t *lhs, int lhs_limbs, const uint32_t *rhs, int rhs_limbs) + { + if (lhs_limbs > rhs_limbs) + do if (lhs[--lhs_limbs] != 0) return false; while (lhs_limbs > rhs_limbs); + else if (lhs_limbs < rhs_limbs) + do if (rhs[--rhs_limbs] != 0) return false; while (lhs_limbs < rhs_limbs); + + while (lhs_limbs--) if (lhs[lhs_limbs] != rhs[lhs_limbs]) return false; + return true; // equal + } + + bool Greater32(const uint32_t *lhs, int lhs_limbs, uint32_t rhs) + { + if (*lhs > rhs) return true; + while (--lhs_limbs) + if (*++lhs) return true; + return false; + } + bool Equal32(const uint32_t *lhs, int lhs_limbs, uint32_t rhs) + { + if (*lhs != rhs) return false; + while (--lhs_limbs) + if (*++lhs) return false; + return true; // equal + } + + // out = in >>> shift + // Precondition: 0 <= shift < 31 + void ShiftRight(int limbs, uint32_t *out, const uint32_t *in, int shift) + { + if (!shift) + { + Set(out, limbs, in); + return; + } + + uint32_t carry = 0; + + for (int ii = limbs - 1; ii >= 0; --ii) + { + uint32_t r = in[ii]; + + out[ii] = (r >> shift) | carry; + + carry = r << (32 - shift); + } + } + + // {out, carry} = in <<< shift + // Precondition: 0 <= shift < 31 + uint32_t ShiftLeft(int limbs, uint32_t *out, const uint32_t *in, int shift) + { + if (!shift) + { + Set(out, limbs, in); + return 0; + } + + uint32_t carry = 0; + + for (int ii = 0; ii < limbs; ++ii) + { + uint32_t r = in[ii]; + + out[ii] = (r << shift) | carry; + + carry = r >> (32 - shift); + } + + return carry; + } + + // lhs += rhs, return carry out + // precondition: lhs_limbs >= rhs_limbs + uint32_t Add(uint32_t *lhs, int lhs_limbs, const uint32_t *rhs, int rhs_limbs) + { + int ii; + uint64_t r = (uint64_t)lhs[0] + rhs[0]; + lhs[0] = (uint32_t)r; + + for (ii = 1; ii < rhs_limbs; ++ii) + { + r = ((uint64_t)lhs[ii] + rhs[ii]) + (uint32_t)(r >> 32); + lhs[ii] = (uint32_t)r; + } + + for (; ii < lhs_limbs && (uint32_t)(r >>= 32) != 0; ++ii) + { + r += lhs[ii]; + lhs[ii] = (uint32_t)r; + } + + return (uint32_t)(r >> 32); + } + + // out = lhs + rhs, return carry out + // precondition: lhs_limbs >= rhs_limbs + uint32_t Add(uint32_t *out, const uint32_t *lhs, int lhs_limbs, const uint32_t *rhs, int rhs_limbs) + { + int ii; + uint64_t r = (uint64_t)lhs[0] + rhs[0]; + out[0] = (uint32_t)r; + + for (ii = 1; ii < rhs_limbs; ++ii) + { + r = ((uint64_t)lhs[ii] + rhs[ii]) + (uint32_t)(r >> 32); + out[ii] = (uint32_t)r; + } + + for (; ii < lhs_limbs && (uint32_t)(r >>= 32) != 0; ++ii) + { + r += lhs[ii]; + out[ii] = (uint32_t)r; + } + + return (uint32_t)(r >> 32); + } + + // lhs += rhs, return carry out + // precondition: lhs_limbs > 0 + uint32_t Add32(uint32_t *lhs, int lhs_limbs, uint32_t rhs) + { + uint32_t n = lhs[0]; + uint32_t r = n + rhs; + lhs[0] = r; + + if (r >= n) + return 0; + + for (int ii = 1; ii < lhs_limbs; ++ii) + if (++lhs[ii]) + return 0; + + return 1; + } + + // lhs -= rhs, return borrow out + // precondition: lhs_limbs >= rhs_limbs + int32_t Subtract(uint32_t *lhs, int lhs_limbs, const uint32_t *rhs, int rhs_limbs) + { + int ii; + int64_t r = (int64_t)lhs[0] - rhs[0]; + lhs[0] = (uint32_t)r; + + for (ii = 1; ii < rhs_limbs; ++ii) + { + r = ((int64_t)lhs[ii] - rhs[ii]) + (int32_t)(r >> 32); + lhs[ii] = (uint32_t)r; + } + + for (; ii < lhs_limbs && (int32_t)(r >>= 32) != 0; ++ii) + { + r += lhs[ii]; + lhs[ii] = (uint32_t)r; + } + + return (int32_t)(r >> 32); + } + + // out = lhs - rhs, return borrow out + // precondition: lhs_limbs >= rhs_limbs + int32_t Subtract(uint32_t *out, const uint32_t *lhs, int lhs_limbs, const uint32_t *rhs, int rhs_limbs) + { + int ii; + int64_t r = (int64_t)lhs[0] - rhs[0]; + out[0] = (uint32_t)r; + + for (ii = 1; ii < rhs_limbs; ++ii) + { + r = ((int64_t)lhs[ii] - rhs[ii]) + (int32_t)(r >> 32); + out[ii] = (uint32_t)r; + } + + for (; ii < lhs_limbs && (int32_t)(r >>= 32) != 0; ++ii) + { + r += lhs[ii]; + out[ii] = (uint32_t)r; + } + + return (int32_t)(r >> 32); + } + + // lhs -= rhs, return borrow out + // precondition: lhs_limbs > 0, result limbs = lhs_limbs + int32_t Subtract32(uint32_t *lhs, int lhs_limbs, uint32_t rhs) + { + uint32_t n = lhs[0]; + uint32_t r = n - rhs; + lhs[0] = r; + + if (r <= n) + return 0; + + for (int ii = 1; ii < lhs_limbs; ++ii) + if (lhs[ii]--) + return 0; + + return -1; + } + + // lhs = -rhs + void Negate(int limbs, uint32_t *lhs, const uint32_t *rhs) + { + // Propagate negations until carries stop + while (limbs-- > 0 && !(*lhs++ = -(int32_t)(*rhs++))); + + // Then just invert the remaining words + while (limbs-- > 0) *lhs++ = ~(*rhs++); + } + + // n = ~n, only invert bits up to the MSB, but none above that + void BitNot(uint32_t *n, int limbs) + { + limbs = LimbDegree(n, limbs); + if (limbs) + { + uint32_t high = n[--limbs]; + uint32_t high_degree = 32 - Degree(high); + + n[limbs] = ((uint32_t)(~high << high_degree) >> high_degree); + while (limbs--) n[limbs] = ~n[limbs]; + } + } + + // n = ~n, invert all bits, even ones above MSB + void LimbNot(uint32_t *n, int limbs) + { + while (limbs--) *n++ = ~(*n); + } + + // lhs ^= rhs + void Xor(int limbs, uint32_t *lhs, const uint32_t *rhs) + { + while (limbs--) *lhs++ ^= *rhs++; + } + + // Return the carry out from A += B << S + uint32_t AddLeftShift32( + int limbs, // Number of limbs in parameter A and B + uint32_t *A, // Large number + const uint32_t *B, // Large number + uint32_t S) // 32-bit number + { + uint64_t sum = 0; + uint32_t last = 0; + + while (limbs--) + { + uint32_t b = *B++; + + sum = (uint64_t)((b << S) | (last >> (32 - S))) + *A + (uint32_t)(sum >> 32); + + last = b; + *A++ = (uint32_t)sum; + } + + return (uint32_t)(sum >> 32) + (last >> (32 - S)); + } + + // Return the carry out from result = A * B + uint32_t Multiply32( + int limbs, // Number of limbs in parameter A, result + uint32_t *result, // Large number + const uint32_t *A, // Large number + uint32_t B) // 32-bit number + { + uint64_t p = (uint64_t)A[0] * B; + result[0] = (uint32_t)p; + + while (--limbs) + { + p = (uint64_t)*(++A) * B + (uint32_t)(p >> 32); + *(++result) = (uint32_t)p; + } + + return (uint32_t)(p >> 32); + } + + // Return the carry out from X = X * M + A + uint32_t MultiplyAdd32( + int limbs, // Number of limbs in parameter A and B + uint32_t *X, // Large number + uint32_t M, // Large number + uint32_t A) // 32-bit number + { + uint64_t p = (uint64_t)X[0] * M + A; + X[0] = (uint32_t)p; + + while (--limbs) + { + p = (uint64_t)*(++X) * M + (uint32_t)(p >> 32); + *X = (uint32_t)p; + } + + return (uint32_t)(p >> 32); + } + + // Return the carry out from A += B * M + uint32_t AddMultiply32( + int limbs, // Number of limbs in parameter A and B + uint32_t *A, // Large number + const uint32_t *B, // Large number + uint32_t M) // 32-bit number + { + // This function is roughly 85% of the cost of exponentiation +#if defined(ASSEMBLY_INTEL_SYNTAX) && !defined (_XBOX) && !defined(X360) + ASSEMBLY_BLOCK // VS.NET, x86, 32-bit words + { + mov esi, [B] + mov edi, [A] + mov eax, [esi] + mul [M] ; (edx,eax) = [M]*[esi] + add eax, [edi] ; (edx,eax) += [edi] + adc edx, 0 + ; (edx,eax) = [B]*[M] + [A] + + mov [edi], eax + ; [A] = eax + + mov ecx, [limbs] + sub ecx, 1 + jz loop_done +loop_head: + lea esi, [esi + 4] ; ++B + mov eax, [esi] ; eax = [B] + mov ebx, edx ; ebx = last carry + lea edi, [edi + 4] ; ++A + mul [M] ; (edx,eax) = [M]*[esi] + add eax, [edi] ; (edx,eax) += [edi] + adc edx, 0 + add eax, ebx ; (edx,eax) += ebx + adc edx, 0 + ; (edx,eax) = [esi]*[M] + [edi] + (ebx=last carry) + + mov [edi], eax + ; [A] = eax + + sub ecx, 1 + jnz loop_head +loop_done: + mov [M], edx ; Use [M] to copy the carry into C++ land + } + + return M; +#else + // Unrolled first loop + uint64_t p = B[0] * (uint64_t)M + A[0]; + A[0] = (uint32_t)p; + + while (--limbs) + { + p = (*(++B) * (uint64_t)M + *(++A)) + (uint32_t)(p >> 32); + A[0] = (uint32_t)p; + } + + return (uint32_t)(p >> 32); +#endif + } + + // product = x * y + void SimpleMultiply( + int limbs, // Number of limbs in parameters x, y + uint32_t *product, // Large number; buffer size = limbs*2 + const uint32_t *x, // Large number + const uint32_t *y) // Large number + { + // Roughly 25% of the cost of exponentiation + product[limbs] = Multiply32(limbs, product, x, y[0]); + + uint32_t ctr = limbs; + while (--ctr) + { + ++product; + product[limbs] = AddMultiply32(limbs, product, x, (++y)[0]); + } + } + + // product = low half of x * y product + void SimpleMultiplyLowHalf( + int limbs, // Number of limbs in parameters x, y + uint32_t *product, // Large number; buffer size = limbs + const uint32_t *x, // Large number + const uint32_t *y) // Large number + { + Multiply32(limbs, product, x, y[0]); + + while (--limbs) + { + ++product; + ++y; + AddMultiply32(limbs, product, x, y[0]); + } + } + + // product = x ^ 2 + void SimpleSquare( + int limbs, // Number of limbs in parameter x + uint32_t *product, // Large number; buffer size = limbs*2 + const uint32_t *x) // Large number + { + // Seems about 15% faster than SimpleMultiply() in practice + uint32_t *cross_product = (uint32_t*)alloca(limbs*2*4); + + // Calculate square-less and repeat-less cross products + cross_product[limbs] = Multiply32(limbs - 1, cross_product + 1, x + 1, x[0]); + for (int ii = 1; ii < limbs - 1; ++ii) + { + cross_product[limbs + ii] = AddMultiply32(limbs - ii - 1, cross_product + ii*2 + 1, x + ii + 1, x[ii]); + } + + // Calculate square products + for (int ii = 0; ii < limbs; ++ii) + { + uint32_t xi = x[ii]; + uint64_t si = (uint64_t)xi * xi; + product[ii*2] = (uint32_t)si; + product[ii*2+1] = (uint32_t)(si >> 32); + } + + // Multiply the cross product by 2 and add it to the square products + product[limbs*2 - 1] += AddLeftShift32(limbs*2 - 2, product + 1, cross_product + 1, 1); + } + + // product = xy + // memory space for product may not overlap with x,y + void Multiply( + int limbs, // Number of limbs in x,y + uint32_t *product, // Product; buffer size = limbs*2 + const uint32_t *x, // Large number; buffer size = limbs + const uint32_t *y) // Large number; buffer size = limbs + { + // Stop recursing under 640 bits or odd limb count + if (limbs < 30 || (limbs & 1)) + { + SimpleMultiply(limbs, product, x, y); + return; + } + + // Compute high and low products + Multiply(limbs/2, product, x, y); + Multiply(limbs/2, product + limbs, x + limbs/2, y + limbs/2); + + // Compute (x1 + x2), xc = carry out + uint32_t *xsum = (uint32_t*)alloca((limbs/2)*4); + uint32_t xcarry = Add(xsum, x, limbs/2, x + limbs/2, limbs/2); + + // Compute (y1 + y2), yc = carry out + uint32_t *ysum = (uint32_t*)alloca((limbs/2)*4); + uint32_t ycarry = Add(ysum, y, limbs/2, y + limbs/2, limbs/2); + + // Compute (x1 + x2) * (y1 + y2) + uint32_t *cross_product = (uint32_t*)alloca(limbs*4); + Multiply(limbs/2, cross_product, xsum, ysum); + + // Subtract out the high and low products + int32_t cross_carry = Subtract(cross_product, limbs, product, limbs); + cross_carry += Subtract(cross_product, limbs, product + limbs, limbs); + + // Fix the extra high carry bits of the result + if (ycarry) cross_carry += Add(cross_product + limbs/2, limbs/2, xsum, limbs/2); + if (xcarry) cross_carry += Add(cross_product + limbs/2, limbs/2, ysum, limbs/2); + cross_carry += (xcarry & ycarry); + + // Add the cross product into the result + cross_carry += Add(product + limbs/2, limbs*3/2, cross_product, limbs); + + // Add in the fixed high carry bits + if (cross_carry) Add32(product + limbs*3/2, limbs/2, cross_carry); + } + + // product = x^2 + // memory space for product may not overlap with x + void Square( + int limbs, // Number of limbs in x + uint32_t *product, // Product; buffer size = limbs*2 + const uint32_t *x) // Large number; buffer size = limbs + { + // Stop recursing under 1280 bits or odd limb count + if (limbs < 40 || (limbs & 1)) + { + SimpleSquare(limbs, product, x); + return; + } + + // Compute high and low squares + Square(limbs/2, product, x); + Square(limbs/2, product + limbs, x + limbs/2); + + // Generate the cross product + uint32_t *cross_product = (uint32_t*)alloca(limbs*4); + Multiply(limbs/2, cross_product, x, x + limbs/2); + + // Multiply the cross product by 2 and add it to the result + uint32_t cross_carry = AddLeftShift32(limbs, product + limbs/2, cross_product, 1); + + // Roll the carry out up to the highest limb + if (cross_carry) Add32(product + limbs*3/2, limbs/2, cross_carry); + } + + // Returns the remainder of N / divisor for a 32-bit divisor + uint32_t Modulus32( + int limbs, // Number of limbs in parameter N + const uint32_t *N, // Large number, buffer size = limbs + uint32_t divisor) // 32-bit number + { + uint32_t remainder = N[limbs-1] < divisor ? N[limbs-1] : 0; + uint32_t counter = N[limbs-1] < divisor ? limbs-1 : limbs; + + while (counter--) remainder = (uint32_t)((((uint64_t)remainder << 32) | N[counter]) % divisor); + + return remainder; + } + + /* + * 'A' is overwritten with the quotient of the operation + * Returns the remainder of 'A' / divisor for a 32-bit divisor + * + * Does not check for divide-by-zero + */ + uint32_t Divide32( + int limbs, // Number of limbs in parameter A + uint32_t *A, // Large number, buffer size = limbs + uint32_t divisor) // 32-bit number + { + uint64_t r = 0; + for (int ii = limbs-1; ii >= 0; --ii) + { + uint64_t n = (r << 32) | A[ii]; + A[ii] = (uint32_t)(n / divisor); + r = n % divisor; + } + + return (uint32_t)r; + } + + // returns (n ^ -1) Mod 2^32 + uint32_t MulInverse32(uint32_t n) + { + // {u1, g1} = 2^32 / n + uint32_t hb = (~(n - 1) >> 31); + uint32_t u1 = -(int32_t)(0xFFFFFFFF / n + hb); + uint32_t g1 = ((-(int32_t)hb) & (0xFFFFFFFF % n + 1)) - n; + + if (!g1) { + if (n != 1) return 0; + else return 1; + } + + uint32_t q, u = 1, g = n; + + for (;;) { + q = g / g1; + g %= g1; + + if (!g) { + if (g1 != 1) return 0; + else return u1; + } + + u -= q*u1; + q = g1 / g; + g1 %= g; + + if (!g1) { + if (g != 1) return 0; + else return u; + } + + u1 -= q*u; + } + } + + /* + * Computes multiplicative inverse of given number + * Such that: result * u = 1 + * Using Extended Euclid's Algorithm (GCDe) + * + * This is not always possible, so it will return false iff not possible. + */ + bool MulInverse( + int limbs, // Limbs in u and result + const uint32_t *u, // Large number, buffer size = limbs + uint32_t *result) // Large number, buffer size = limbs + { + uint32_t *u1 = (uint32_t*)alloca(limbs*4); + uint32_t *u3 = (uint32_t*)alloca(limbs*4); + uint32_t *v1 = (uint32_t*)alloca(limbs*4); + uint32_t *v3 = (uint32_t*)alloca(limbs*4); + uint32_t *t1 = (uint32_t*)alloca(limbs*4); + uint32_t *t3 = (uint32_t*)alloca(limbs*4); + uint32_t *q = (uint32_t*)alloca((limbs+1)*4); + uint32_t *w = (uint32_t*)alloca((limbs+1)*4); + + // Unrolled first iteration + { + Set32(u1, limbs, 0); + Set32(v1, limbs, 1); + Set(v3, limbs, u); + } + + // Unrolled second iteration + if (!LimbDegree(v3, limbs)) + return false; + + // {q, t3} <- R / v3 + Set32(w, limbs, 0); + w[limbs] = 1; + Divide(w, limbs+1, v3, limbs, q, t3); + + SimpleMultiplyLowHalf(limbs, t1, q, v1); + Add(t1, limbs, u1, limbs); + + for (;;) + { + if (!LimbDegree(t3, limbs)) + { + Set(result, limbs, v1); + return Equal32(v3, limbs, 1); + } + + Divide(v3, limbs, t3, limbs, q, u3); + SimpleMultiplyLowHalf(limbs, u1, q, t1); + Add(u1, limbs, v1, limbs); + + if (!LimbDegree(u3, limbs)) + { + Negate(limbs, result, t1); + return Equal32(t3, limbs, 1); + } + + Divide(t3, limbs, u3, limbs, q, v3); + SimpleMultiplyLowHalf(limbs, v1, q, u1); + Add(v1, limbs, t1, limbs); + + if (!LimbDegree(v3, limbs)) + { + Set(result, limbs, u1); + return Equal32(u3, limbs, 1); + } + + Divide(u3, limbs, v3, limbs, q, t3); + SimpleMultiplyLowHalf(limbs, t1, q, v1); + Add(t1, limbs, u1, limbs); + + if (!LimbDegree(t3, limbs)) + { + Negate(limbs, result, v1); + return Equal32(v3, limbs, 1); + } + + Divide(v3, limbs, t3, limbs, q, u3); + SimpleMultiplyLowHalf(limbs, u1, q, t1); + Add(u1, limbs, v1, limbs); + + if (!LimbDegree(u3, limbs)) + { + Set(result, limbs, t1); + return Equal32(t3, limbs, 1); + } + + Divide(t3, limbs, u3, limbs, q, v3); + SimpleMultiplyLowHalf(limbs, v1, q, u1); + Add(v1, limbs, t1, limbs); + + if (!LimbDegree(v3, limbs)) + { + Negate(limbs, result, u1); + return Equal32(u3, limbs, 1); + } + + Divide(u3, limbs, v3, limbs, q, t3); + SimpleMultiplyLowHalf(limbs, t1, q, v1); + Add(t1, limbs, u1, limbs); + } + } + + // {q, r} = u / v + // q is not u or v + // Return false on divide by zero + bool Divide( + const uint32_t *u, // numerator, size = u_limbs + int u_limbs, + const uint32_t *v, // denominator, size = v_limbs + int v_limbs, + uint32_t *q, // quotient, size = u_limbs + uint32_t *r) // remainder, size = v_limbs + { + // calculate v_used and u_used + int v_used = LimbDegree(v, v_limbs); + if (!v_used) return false; + + int u_used = LimbDegree(u, u_limbs); + + // if u < v, avoid division + if (u_used <= v_used && Less(u, u_used, v, v_used)) + { + // r = u, q = 0 + Set(r, v_limbs, u, u_used); + Set32(q, u_limbs, 0); + return true; + } + + // if v is 32 bits, use faster Divide32 code + if (v_used == 1) + { + // {q, r} = u / v[0] + Set(q, u_limbs, u); + Set32(r, v_limbs, Divide32(u_limbs, q, v[0])); + return true; + } + + // calculate high zero bits in v's high used limb + int shift = 32 - Degree(v[v_used - 1]); + int uu_used = u_used; + if (shift > 0) uu_used++; + + uint32_t *uu = (uint32_t*)alloca(uu_used*4); + uint32_t *vv = (uint32_t*)alloca(v_used*4); + + // shift left to fill high MSB of divisor + if (shift > 0) + { + ShiftLeft(v_used, vv, v, shift); + uu[u_used] = ShiftLeft(u_used, uu, u, shift); + } + else + { + Set(uu, u_used, u); + Set(vv, v_used, v); + } + + int q_high_index = uu_used - v_used; + + if (GreaterOrEqual(uu + q_high_index, v_used, vv, v_used)) + { + Subtract(uu + q_high_index, v_used, vv, v_used); + Set32(q + q_high_index, u_used - q_high_index, 1); + } + else + { + Set32(q + q_high_index, u_used - q_high_index, 0); + } + + uint32_t *vq_product = (uint32_t*)alloca((v_used+1)*4); + + // for each limb, + for (int ii = q_high_index - 1; ii >= 0; --ii) + { + uint64_t q_full = *(uint64_t*)(uu + ii + v_used - 1) / vv[v_used - 1]; + uint32_t q_low = (uint32_t)q_full; + uint32_t q_high = (uint32_t)(q_full >> 32); + + vq_product[v_used] = Multiply32(v_used, vq_product, vv, q_low); + + if (q_high) // it must be '1' + Add(vq_product + 1, v_used, vv, v_used); + + if (Subtract(uu + ii, v_used + 1, vq_product, v_used + 1)) + { + --q_low; + if (Add(uu + ii, v_used + 1, vv, v_used) == 0) + { + --q_low; + Add(uu + ii, v_used + 1, vv, v_used); + } + } + + q[ii] = q_low; + } + + memset(r + v_used, 0, (v_limbs - v_used)*4); + ShiftRight(v_used, r, uu, shift); + + return true; + } + + // r = u % v + // Return false on divide by zero + bool Modulus( + const uint32_t *u, // numerator, size = u_limbs + int u_limbs, + const uint32_t *v, // denominator, size = v_limbs + int v_limbs, + uint32_t *r) // remainder, size = v_limbs + { + // calculate v_used and u_used + int v_used = LimbDegree(v, v_limbs); + if (!v_used) return false; + + int u_used = LimbDegree(u, u_limbs); + + // if u < v, avoid division + if (u_used <= v_used && Less(u, u_used, v, v_used)) + { + // r = u, q = 0 + Set(r, v_limbs, u, u_used); + //Set32(q, u_limbs, 0); + return true; + } + + // if v is 32 bits, use faster Divide32 code + if (v_used == 1) + { + // {q, r} = u / v[0] + //Set(q, u_limbs, u); + Set32(r, v_limbs, Modulus32(u_limbs, u, v[0])); + return true; + } + + // calculate high zero bits in v's high used limb + int shift = 32 - Degree(v[v_used - 1]); + int uu_used = u_used; + if (shift > 0) uu_used++; + + uint32_t *uu = (uint32_t*)alloca(uu_used*4); + uint32_t *vv = (uint32_t*)alloca(v_used*4); + + // shift left to fill high MSB of divisor + if (shift > 0) + { + ShiftLeft(v_used, vv, v, shift); + uu[u_used] = ShiftLeft(u_used, uu, u, shift); + } + else + { + Set(uu, u_used, u); + Set(vv, v_used, v); + } + + int q_high_index = uu_used - v_used; + + if (GreaterOrEqual(uu + q_high_index, v_used, vv, v_used)) + { + Subtract(uu + q_high_index, v_used, vv, v_used); + //Set32(q + q_high_index, u_used - q_high_index, 1); + } + else + { + //Set32(q + q_high_index, u_used - q_high_index, 0); + } + + uint32_t *vq_product = (uint32_t*)alloca((v_used+1)*4); + + // for each limb, + for (int ii = q_high_index - 1; ii >= 0; --ii) + { + uint64_t q_full = *(uint64_t*)(uu + ii + v_used - 1) / vv[v_used - 1]; + uint32_t q_low = (uint32_t)q_full; + uint32_t q_high = (uint32_t)(q_full >> 32); + + vq_product[v_used] = Multiply32(v_used, vq_product, vv, q_low); + + if (q_high) // it must be '1' + Add(vq_product + 1, v_used, vv, v_used); + + if (Subtract(uu + ii, v_used + 1, vq_product, v_used + 1)) + { + //--q_low; + if (Add(uu + ii, v_used + 1, vv, v_used) == 0) + { + //--q_low; + Add(uu + ii, v_used + 1, vv, v_used); + } + } + + //q[ii] = q_low; + } + + memset(r + v_used, 0, (v_limbs - v_used)*4); + ShiftRight(v_used, r, uu, shift); + + return true; + } + + // m_inv ~= 2^(2k)/m + // Generates m_inv parameter of BarrettModulus() + // It is limbs in size, chopping off the 2^k bit + // Only works for m with the high bit set + void BarrettModulusPrecomp( + int limbs, // Number of limbs in m and m_inv + const uint32_t *m, // Modulus, size = limbs + uint32_t *m_inv) // Large number result, size = limbs + { + uint32_t *q = (uint32_t*)alloca((limbs*2+1)*4); + + // q = 2^(2k) + big::Set32(q, limbs*2, 0); + q[limbs*2] = 1; + + // q /= m + big::Divide(q, limbs*2+1, m, limbs, q, m_inv); + + // m_inv = q + Set(m_inv, limbs, q); + } + + // r = x mod m + // Using Barrett's method with precomputed m_inv + void BarrettModulus( + int limbs, // Number of limbs in m and m_inv + const uint32_t *x, // Number to reduce, size = limbs*2 + const uint32_t *m, // Modulus, size = limbs + const uint32_t *m_inv, // R/Modulus, precomputed, size = limbs + uint32_t *result) // Large number result + { + // q2 = x * m_inv + // Skips the low limbs+1 words and some high limbs too + // Needs to partially calculate the next 2 words below for carries + uint32_t *q2 = (uint32_t*)alloca((limbs+3)*4); + int ii, jj = limbs - 1; + + // derived from the fact that m_inv[limbs] was always 1, so m_inv is the same length as modulus now + *(uint64_t*)q2 = (uint64_t)m_inv[jj] * x[jj]; + *(uint64_t*)(q2 + 1) = (uint64_t)q2[1] + x[jj]; + + for (ii = 1; ii < limbs; ++ii) + *(uint64_t*)(q2 + ii + 1) = ((uint64_t)q2[ii + 1] + x[jj + ii]) + AddMultiply32(ii + 1, q2, m_inv + jj - ii, x[jj + ii]); + + *(uint64_t*)(q2 + ii + 1) = ((uint64_t)q2[ii + 1] + x[jj + ii]) + AddMultiply32(ii, q2 + 1, m_inv, x[jj + ii]); + + q2 += 2; + + // r2 = (q3 * m2) mod b^(k+1) + uint32_t *r2 = (uint32_t*)alloca((limbs+1)*4); + + // Skip high words in product, also input limbs are different by 1 + Multiply32(limbs + 1, r2, q2, m[0]); + for (int ii = 1; ii < limbs; ++ii) + AddMultiply32(limbs + 1 - ii, r2 + ii, q2, m[ii]); + + // Correct the error of up to two modulii + uint32_t *r = (uint32_t*)alloca((limbs+1)*4); + if (Subtract(r, x, limbs+1, r2, limbs+1)) + { + while (!Subtract(r, limbs+1, m, limbs)); + } + else + { + while (GreaterOrEqual(r, limbs+1, m, limbs)) + Subtract(r, limbs+1, m, limbs); + } + + Set(result, limbs, r); + } + + // result = (x * y) (Mod modulus) + bool MulMod( + int limbs, // Number of limbs in x,y,modulus + const uint32_t *x, // Large number x + const uint32_t *y, // Large number y + const uint32_t *modulus, // Large number modulus + uint32_t *result) // Large number result + { + uint32_t *product = (uint32_t*)alloca(limbs*2*4); + + Multiply(limbs, product, x, y); + + return Modulus(product, limbs * 2, modulus, limbs, result); + } + + // Convert bigint to string + /* + std::string ToStr(const uint32_t *n, int limbs, int base) + { + limbs = LimbDegree(n, limbs); + if (!limbs) return "0"; + + std::string out; + char ch; + + uint32_t *m = (uint32_t*)alloca(limbs*4); + Set(m, limbs, n, limbs); + + while (limbs) + { + uint32_t mod = Divide32(limbs, m, base); + if (mod <= 9) ch = '0' + mod; + else ch = 'A' + mod - 10; + out = ch + out; + limbs = LimbDegree(m, limbs); + } + + return out; + } + */ + + // Convert string to bigint + // Return 0 if string contains non-digit characters, else number of limbs used + int ToInt(uint32_t *lhs, int max_limbs, const char *rhs, uint32_t base) + { + if (max_limbs < 2) return 0; + + lhs[0] = 0; + int used = 1; + + char ch; + while ((ch = *rhs++)) + { + uint32_t mod; + if (ch >= '0' && ch <= '9') mod = ch - '0'; + else mod = toupper(ch) - 'A' + 10; + if (mod >= base) return 0; + + // lhs *= base + uint32_t carry = MultiplyAdd32(used, lhs, base, mod); + + // react to running out of room + if (carry) + { + if (used >= max_limbs) + return 0; + + lhs[used++] = carry; + } + } + + if (used < max_limbs) + Set32(lhs+used, max_limbs-used, 0); + + return used; + } + + /* + * Computes: result = GCD(a, b) (greatest common divisor) + * + * Length of result is the length of the smallest argument + */ + void GCD( + const uint32_t *a, // Large number, buffer size = a_limbs + int a_limbs, // Size of a + const uint32_t *b, // Large number, buffer size = b_limbs + int b_limbs, // Size of b + uint32_t *result) // Large number, buffer size = min(a, b) + { + int limbs = (a_limbs <= b_limbs) ? a_limbs : b_limbs; + + uint32_t *g = (uint32_t*)alloca(limbs*4); + uint32_t *g1 = (uint32_t*)alloca(limbs*4); + + if (a_limbs <= b_limbs) + { + // g = a, g1 = b (mod a) + Set(g, limbs, a, a_limbs); + Modulus(b, b_limbs, a, a_limbs, g1); + } + else + { + // g = b, g1 = a (mod b) + Set(g, limbs, b, b_limbs); + Modulus(a, a_limbs, b, b_limbs, g1); + } + + for (;;) { + // g = (g mod g1) + Modulus(g, limbs, g1, limbs, g); + + if (!LimbDegree(g, limbs)) { + Set(result, limbs, g1, limbs); + return; + } + + // g1 = (g1 mod g) + Modulus(g1, limbs, g, limbs, g1); + + if (!LimbDegree(g1, limbs)) { + Set(result, limbs, g, limbs); + return; + } + } + } + + /* + * Computes: result = (1/u) (Mod v) + * Such that: result * u (Mod v) = 1 + * Using Extended Euclid's Algorithm (GCDe) + * + * This is not always possible, so it will return false iff not possible. + */ + bool InvMod( + const uint32_t *u, // Large number, buffer size = u_limbs + int u_limbs, // Limbs in u + const uint32_t *v, // Large number, buffer size = limbs + int limbs, // Limbs in modulus(v) and result + uint32_t *result) // Large number, buffer size = limbs + { + uint32_t *u1 = (uint32_t*)alloca(limbs*4); + uint32_t *u3 = (uint32_t*)alloca(limbs*4); + uint32_t *v1 = (uint32_t*)alloca(limbs*4); + uint32_t *v3 = (uint32_t*)alloca(limbs*4); + uint32_t *t1 = (uint32_t*)alloca(limbs*4); + uint32_t *t3 = (uint32_t*)alloca(limbs*4); + uint32_t *q = (uint32_t*)alloca((limbs + u_limbs)*4); + + // Unrolled first iteration + { + Set32(u1, limbs, 0); + Set32(v1, limbs, 1); + Set(u3, limbs, v); + + // v3 = u % v + Modulus(u, u_limbs, v, limbs, v3); + } + + for (;;) + { + if (!LimbDegree(v3, limbs)) + { + Subtract(result, v, limbs, u1, limbs); + return Equal32(u3, limbs, 1); + } + + Divide(u3, limbs, v3, limbs, q, t3); + SimpleMultiplyLowHalf(limbs, t1, q, v1); + Add(t1, limbs, u1, limbs); + + if (!LimbDegree(t3, limbs)) + { + Set(result, limbs, v1); + return Equal32(v3, limbs, 1); + } + + Divide(v3, limbs, t3, limbs, q, u3); + SimpleMultiplyLowHalf(limbs, u1, q, t1); + Add(u1, limbs, v1, limbs); + + if (!LimbDegree(u3, limbs)) + { + Subtract(result, v, limbs, t1, limbs); + return Equal32(t3, limbs, 1); + } + + Divide(t3, limbs, u3, limbs, q, v3); + SimpleMultiplyLowHalf(limbs, v1, q, u1); + Add(v1, limbs, t1, limbs); + + if (!LimbDegree(v3, limbs)) + { + Set(result, limbs, u1); + return Equal32(u3, limbs, 1); + } + + Divide(u3, limbs, v3, limbs, q, t3); + SimpleMultiplyLowHalf(limbs, t1, q, v1); + Add(t1, limbs, u1, limbs); + + if (!LimbDegree(t3, limbs)) + { + Subtract(result, v, limbs, v1, limbs); + return Equal32(v3, limbs, 1); + } + + Divide(v3, limbs, t3, limbs, q, u3); + SimpleMultiplyLowHalf(limbs, u1, q, t1); + Add(u1, limbs, v1, limbs); + + if (!LimbDegree(u3, limbs)) + { + Set(result, limbs, t1); + return Equal32(t3, limbs, 1); + } + + Divide(t3, limbs, u3, limbs, q, v3); + SimpleMultiplyLowHalf(limbs, v1, q, u1); + Add(v1, limbs, t1, limbs); + } + } + + // root = sqrt(square) + // Based on Newton-Raphson iteration: root_n+1 = (root_n + square/root_n) / 2 + // Doubles number of correct bits each iteration + // Precondition: The high limb of square is non-zero + // Returns false if it was unable to determine the root + bool SquareRoot( + int limbs, // Number of limbs in root + const uint32_t *square, // Square to root, size = limbs * 2 + uint32_t *root) // Output root, size = limbs + { + uint32_t *q = (uint32_t*)alloca(limbs*2*4); + uint32_t *r = (uint32_t*)alloca((limbs+1)*4); + + // Take high limbs of square as the initial root guess + Set(root, limbs, square + limbs); + + int ctr = 64; + while (ctr--) + { + // {q, r} = square / root + Divide(square, limbs*2, root, limbs, q, r); + + // root = (root + q) / 2, assuming high limbs of q = 0 + Add(q, limbs+1, root, limbs); + + // Round division up to the nearest bit + // Fixes a problem where root is off by 1 + if (q[0] & 1) Add32(q, limbs+1, 2); + + ShiftRight(limbs+1, q, q, 1); + + // Return success if there was no change + if (Equal(limbs, q, root)) + return true; + + // Else update root and continue + Set(root, limbs, q); + } + + // In practice only takes about 9 iterations, as many as 31 + // Varies slightly as number of limbs increases but not by much + return false; + } + + // Calculates mod_inv from low limb of modulus for Mon*() + uint32_t MonReducePrecomp(uint32_t modulus0) + { + // mod_inv = -M ^ -1 (Mod 2^32) + return MulInverse32(-(int32_t)modulus0); + } + + // Compute n_residue for Montgomery reduction + void MonInputResidue( + const uint32_t *n, // Large number, buffer size = n_limbs + int n_limbs, // Number of limbs in n + const uint32_t *modulus, // Large number, buffer size = m_limbs + int m_limbs, // Number of limbs in modulus + uint32_t *n_residue) // Result, buffer size = m_limbs + { + // p = n * 2^(k*m) + uint32_t *p = (uint32_t*)alloca((n_limbs+m_limbs)*4); + Set(p+m_limbs, n_limbs, n, n_limbs); + Set32(p, m_limbs, 0); + + // n_residue = p (Mod modulus) + Modulus(p, n_limbs+m_limbs, modulus, m_limbs, n_residue); + } + + // result = a * b * r^-1 (Mod modulus) in Montgomery domain + void MonPro( + int limbs, // Number of limbs in each parameter + const uint32_t *a_residue, // Large number, buffer size = limbs + const uint32_t *b_residue, // Large number, buffer size = limbs + const uint32_t *modulus, // Large number, buffer size = limbs + uint32_t mod_inv, // MonReducePrecomp() return + uint32_t *result) // Large number, buffer size = limbs + { + uint32_t *t = (uint32_t*)alloca(limbs*2*4); + + Multiply(limbs, t, a_residue, b_residue); + MonReduce(limbs, t, modulus, mod_inv, result); + } + + // result = a^-1 (Mod modulus) in Montgomery domain + void MonInverse( + int limbs, // Number of limbs in each parameter + const uint32_t *a_residue, // Large number, buffer size = limbs + const uint32_t *modulus, // Large number, buffer size = limbs + uint32_t mod_inv, // MonReducePrecomp() return + uint32_t *result) // Large number, buffer size = limbs + { + Set(result, limbs, a_residue); + MonFinish(limbs, result, modulus, mod_inv); + InvMod(result, limbs, modulus, limbs, result); + MonInputResidue(result, limbs, modulus, limbs, result); + } + + // result = a * r^-1 (Mod modulus) in Montgomery domain + // The result may be greater than the modulus, but this is okay since + // the result is still in the RNS. MonFinish() corrects this at the end. + void MonReduce( + int limbs, // Number of limbs in modulus + uint32_t *s, // Large number, buffer size = limbs*2, gets clobbered + const uint32_t *modulus, // Large number, buffer size = limbs + uint32_t mod_inv, // MonReducePrecomp() return + uint32_t *result) // Large number, buffer size = limbs + { + // This function is roughly 60% of the cost of exponentiation + for (int ii = 0; ii < limbs; ++ii) + { + uint32_t q = s[0] * mod_inv; + s[0] = AddMultiply32(limbs, s, modulus, q); + ++s; + } + + // Add the saved carries + if (Add(result, s, limbs, s - limbs, limbs)) + { + // Reduce the result only when needed + Subtract(result, limbs, modulus, limbs); + } + } + + // result = a * r^-1 (Mod modulus) in Montgomery domain + void MonFinish( + int limbs, // Number of limbs in each parameter + uint32_t *n, // Large number, buffer size = limbs + const uint32_t *modulus, // Large number, buffer size = limbs + uint32_t mod_inv) // MonReducePrecomp() return + { + uint32_t *t = (uint32_t*)alloca(limbs*2*4); + memcpy(t, n, limbs*4); + memset(t + limbs, 0, limbs*4); + + // Reduce the number + MonReduce(limbs, t, modulus, mod_inv, n); + + // Fix MonReduce() results greater than the modulus + if (!Less(limbs, n, modulus)) + Subtract(n, limbs, modulus, limbs); + } + + // Simple internal version without windowing for small exponents + static void SimpleMonExpMod( + const uint32_t *base, // Base for exponentiation, buffer size = mod_limbs + const uint32_t *exponent,// Exponent, buffer size = exponent_limbs + int exponent_limbs, // Number of limbs in exponent + const uint32_t *modulus, // Modulus, buffer size = mod_limbs + int mod_limbs, // Number of limbs in modulus + uint32_t mod_inv, // MonReducePrecomp() return + uint32_t *result) // Result, buffer size = mod_limbs + { + bool set = false; + + uint32_t *temp = (uint32_t*)alloca((mod_limbs*2)*4); + + // Run down exponent bits and use the squaring method + for (int ii = exponent_limbs - 1; ii >= 0; --ii) + { + uint32_t e_i = exponent[ii]; + + for (uint32_t mask = 0x80000000; mask; mask >>= 1) + { + if (set) + { + // result = result^2 + Square(mod_limbs, temp, result); + MonReduce(mod_limbs, temp, modulus, mod_inv, result); + + if (e_i & mask) + { + // result *= base + Multiply(mod_limbs, temp, result, base); + MonReduce(mod_limbs, temp, modulus, mod_inv, result); + } + } + else + { + if (e_i & mask) + { + // result = base + Set(result, mod_limbs, base, mod_limbs); + set = true; + } + } + } + } + } + + // Precompute a window for ExpMod() and MonExpMod() + // Requires 2^window_bits multiplies + uint32_t *PrecomputeWindow(const uint32_t *base, const uint32_t *modulus, int limbs, uint32_t mod_inv, int window_bits) + { + uint32_t *temp = (uint32_t*)alloca(limbs*2*4); + + uint32_t *base_squared = (uint32_t*)alloca(limbs*4); + Square(limbs, temp, base); + MonReduce(limbs, temp, modulus, mod_inv, base_squared); + + // precomputed window starts with 000001, 000011, 000101, 000111, ... + uint32_t k = (1 << (window_bits - 1)); + + uint32_t *window = RakNet::OP_NEW_ARRAY(limbs * k, __FILE__, __LINE__ ); + + uint32_t *cw = window; + Set(window, limbs, base); + + while (--k) + { + // cw+1 = cw * base^2 + Multiply(limbs, temp, cw, base_squared); + MonReduce(limbs, temp, modulus, mod_inv, cw + limbs); + cw += limbs; + } + + return window; + }; + + // Computes: result = base ^ exponent (Mod modulus) + // Using Montgomery multiplication with simple squaring method + // Base parameter must be a Montgomery Residue created with MonInputResidue() + void MonExpMod( + const uint32_t *base, // Base for exponentiation, buffer size = mod_limbs + const uint32_t *exponent,// Exponent, buffer size = exponent_limbs + int exponent_limbs, // Number of limbs in exponent + const uint32_t *modulus, // Modulus, buffer size = mod_limbs + int mod_limbs, // Number of limbs in modulus + uint32_t mod_inv, // MonReducePrecomp() return + uint32_t *result) // Result, buffer size = mod_limbs + { + // Calculate the number of window bits to use (decent approximation..) + int window_bits = Degree(exponent_limbs); + + // If the window bits are too small, might as well just use left-to-right S&M method + if (window_bits < 4) + { + SimpleMonExpMod(base, exponent, exponent_limbs, modulus, mod_limbs, mod_inv, result); + return; + } + + // Precompute a window of the size determined above + uint32_t *window = PrecomputeWindow(base, modulus, mod_limbs, mod_inv, window_bits); + + bool seen_bits = false; + uint32_t e_bits=0, trailing_zeroes=0, used_bits = 0; + + uint32_t *temp = (uint32_t*)alloca((mod_limbs*2)*4); + + for (int ii = exponent_limbs - 1; ii >= 0; --ii) + { + uint32_t e_i = exponent[ii]; + + int wordbits = 32; + while (wordbits--) + { + // If we have been accumulating bits, + if (used_bits) + { + // If this new bit is set, + if (e_i >> 31) + { + e_bits <<= 1; + e_bits |= 1; + + trailing_zeroes = 0; + } + else // the new bit is unset + { + e_bits <<= 1; + + ++trailing_zeroes; + } + + ++used_bits; + + // If we have used up the window bits, + if (used_bits == (uint32_t) window_bits) + { + // Select window index 1011 from "101110" + uint32_t window_index = e_bits >> (trailing_zeroes + 1); + + if (seen_bits) + { + uint32_t ctr = used_bits - trailing_zeroes; + while (ctr--) + { + // result = result^2 + Square(mod_limbs, temp, result); + MonReduce(mod_limbs, temp, modulus, mod_inv, result); + } + + // result = result * window[index] + Multiply(mod_limbs, temp, result, &window[window_index * mod_limbs]); + MonReduce(mod_limbs, temp, modulus, mod_inv, result); + } + else + { + // result = window[index] + Set(result, mod_limbs, &window[window_index * mod_limbs]); + seen_bits = true; + } + + while (trailing_zeroes--) + { + // result = result^2 + Square(mod_limbs, temp, result); + MonReduce(mod_limbs, temp, modulus, mod_inv, result); + } + + used_bits = 0; + } + } + else + { + // If this new bit is set, + if (e_i >> 31) + { + used_bits = 1; + e_bits = 1; + trailing_zeroes = 0; + } + else // the new bit is unset + { + // If we have processed any bits yet, + if (seen_bits) + { + // result = result^2 + Square(mod_limbs, temp, result); + MonReduce(mod_limbs, temp, modulus, mod_inv, result); + } + } + } + + e_i <<= 1; + } + } + + if (used_bits) + { + // Select window index 1011 from "101110" + uint32_t window_index = e_bits >> (trailing_zeroes + 1); + + if (seen_bits) + { + uint32_t ctr = used_bits - trailing_zeroes; + while (ctr--) + { + // result = result^2 + Square(mod_limbs, temp, result); + MonReduce(mod_limbs, temp, modulus, mod_inv, result); + } + + // result = result * window[index] + Multiply(mod_limbs, temp, result, &window[window_index * mod_limbs]); + MonReduce(mod_limbs, temp, modulus, mod_inv, result); + } + else + { + // result = window[index] + Set(result, mod_limbs, &window[window_index * mod_limbs]); + //seen_bits = true; + } + + while (trailing_zeroes--) + { + // result = result^2 + Square(mod_limbs, temp, result); + MonReduce(mod_limbs, temp, modulus, mod_inv, result); + } + + //e_bits = 0; + } + + RakNet::OP_DELETE_ARRAY(window, __FILE__, __LINE__); + } + + // Computes: result = base ^ exponent (Mod modulus) + // Using Montgomery multiplication with simple squaring method + void ExpMod( + const uint32_t *base, // Base for exponentiation, buffer size = base_limbs + int base_limbs, // Number of limbs in base + const uint32_t *exponent,// Exponent, buffer size = exponent_limbs + int exponent_limbs, // Number of limbs in exponent + const uint32_t *modulus, // Modulus, buffer size = mod_limbs + int mod_limbs, // Number of limbs in modulus + uint32_t mod_inv, // MonReducePrecomp() return + uint32_t *result) // Result, buffer size = mod_limbs + { + uint32_t *mon_base = (uint32_t*)alloca(mod_limbs*4); + MonInputResidue(base, base_limbs, modulus, mod_limbs, mon_base); + + MonExpMod(mon_base, exponent, exponent_limbs, modulus, mod_limbs, mod_inv, result); + + MonFinish(mod_limbs, result, modulus, mod_inv); + } + + // returns b ^ e (Mod m) + uint32_t ExpMod(uint32_t b, uint32_t e, uint32_t m) + { + // validate arguments + if (b == 0 || m <= 1) return 0; + if (e == 0) return 1; + + // find high bit of exponent + uint32_t mask = 0x80000000; + while ((e & mask) == 0) mask >>= 1; + + // seen 1 set bit, so result = base so far + uint32_t r = b; + + while (mask >>= 1) + { + // VS.NET does a poor job recognizing that the division + // is just an IDIV with a 32-bit dividend (not 64-bit) :-( + + // r = r^2 (mod m) + r = (uint32_t)(((uint64_t)r * r) % m); + + // if exponent bit is set, r = r*b (mod m) + if (e & mask) r = (uint32_t)(((uint64_t)r * b) % m); + } + + return r; + } + + // Rabin-Miller method for finding a strong pseudo-prime + // Preconditions: High bit and low bit of n = 1 + bool RabinMillerPrimeTest( + const uint32_t *n, // Number to check for primality + int limbs, // Number of limbs in n + uint32_t k) // Confidence level (40 is pretty good) + { + // n1 = n - 1 + uint32_t *n1 = (uint32_t *)alloca(limbs*4); + Set(n1, limbs, n); + Subtract32(n1, limbs, 1); + + // d = n1 + uint32_t *d = (uint32_t *)alloca(limbs*4); + Set(d, limbs, n1); + + // remove factors of two from d + while (!(d[0] & 1)) + ShiftRight(limbs, d, d, 1); + + uint32_t *a = (uint32_t *)alloca(limbs*4); + uint32_t *t = (uint32_t *)alloca(limbs*4); + uint32_t *p = (uint32_t *)alloca((limbs*2)*4); + uint32_t n_inv = MonReducePrecomp(n[0]); + + // iterate k times + while (k--) + { + //do Random::ref()->generate(a, limbs*4); + do fillBufferMT(a,limbs*4); + while (GreaterOrEqual(a, limbs, n, limbs)); + + // a = a ^ d (Mod n) + ExpMod(a, limbs, d, limbs, n, limbs, n_inv, a); + + Set(t, limbs, d); + while (!Equal(limbs, t, n1) && + !Equal32(a, limbs, 1) && + !Equal(limbs, a, n1)) + { + // a = a^2 (Mod n), non-critical path + Square(limbs, p, a); + Modulus(p, limbs*2, n, limbs, a); + + // t <<= 1 + ShiftLeft(limbs, t, t, 1); + } + + if (!Equal(limbs, a, n1) && !(t[0] & 1)) return false; + } + + return true; + } + + // Generate a strong pseudo-prime using the Rabin-Miller primality test + void GenerateStrongPseudoPrime( + uint32_t *n, // Output prime + int limbs) // Number of limbs in n + { + do { + fillBufferMT(n,limbs*4); + n[limbs-1] |= 0x80000000; + n[0] |= 1; + } while (!RabinMillerPrimeTest(n, limbs, 40)); // 40 iterations + } +} + +#endif diff --git a/RakNet/Sources/BigInt.h b/RakNet/Sources/BigInt.h new file mode 100644 index 0000000..59681d1 --- /dev/null +++ b/RakNet/Sources/BigInt.h @@ -0,0 +1,418 @@ +/// +/// This file is part of RakNet Copyright 2003 Jenkins Software LLC +/// +/// Usage of RakNet is subject to the appropriate license agreement. +/// + +#if !defined(_XBOX) && !defined(X360) + +/* + * BigInts are stored as 32-bit integer arrays. + * Each integer in the array is referred to as a limb ala GMP. + * Lower numbered limbs are less significant to the number represented. + * eg, limb 0 is the least significant limb. + * Also known as little-endian digit order + */ + +#ifndef BIG_INT_HPP +#define BIG_INT_HPP + +#include "Platform.h" +#include "NativeTypes.h" +//#include + +namespace big +{ + // returns the degree of the base 2 monic polynomial + // (the number of bits used to represent the number) + // eg, 0 0 0 0 1 0 1 1 ... => 28 out of 32 used + uint32_t Degree(uint32_t v); + + // returns the number of limbs that are actually used + int LimbDegree(const uint32_t *n, int limbs); + + // return bits used + uint32_t Degree(const uint32_t *n, int limbs); + + // lhs = rhs (unequal limbs) + void Set(uint32_t *lhs, int lhs_limbs, const uint32_t *rhs, int rhs_limbs); + + // lhs = rhs (equal limbs) + void Set(uint32_t *lhs, int limbs, const uint32_t *rhs); + + // lhs = rhs (32-bit extension) + void Set32(uint32_t *lhs, int lhs_limbs, const uint32_t rhs); + +#if defined(__BIG_ENDIAN__) + // Flip the byte order as needed to make 'n' big-endian for sharing over a network + void ToLittleEndian(uint32_t *n, int limbs); + + // Flip the byte order as needed to make big-endian 'n' use the local byte order + void FromLittleEndian(uint32_t *n, int limbs); +#else + inline void ToLittleEndian(uint32_t *, int) {} + inline void FromLittleEndian(uint32_t *, int) {} +#endif + + // Comparisons where both operands have the same number of limbs + bool Less(int limbs, const uint32_t *lhs, const uint32_t *rhs); + bool Greater(int limbs, const uint32_t *lhs, const uint32_t *rhs); + bool Equal(int limbs, const uint32_t *lhs, const uint32_t *rhs); + + // lhs < rhs + bool Less(const uint32_t *lhs, int lhs_limbs, const uint32_t *rhs, int rhs_limbs); + + // lhs >= rhs + inline bool GreaterOrEqual(const uint32_t *lhs, int lhs_limbs, const uint32_t *rhs, int rhs_limbs) + { + return !Less(lhs, lhs_limbs, rhs, rhs_limbs); + } + + // lhs > rhs + bool Greater(const uint32_t *lhs, int lhs_limbs, const uint32_t *rhs, int rhs_limbs); + + // lhs <= rhs + inline bool LessOrEqual(const uint32_t *lhs, int lhs_limbs, const uint32_t *rhs, int rhs_limbs) + { + return !Greater(lhs, lhs_limbs, rhs, rhs_limbs); + } + + // lhs > rhs + bool Greater32(const uint32_t *lhs, int lhs_limbs, uint32_t rhs); + + // lhs <= rhs + inline bool LessOrEqual32(const uint32_t *lhs, int lhs_limbs, uint32_t rhs) + { + return !Greater32(lhs, lhs_limbs, rhs); + } + + // lhs == rhs + bool Equal(const uint32_t *lhs, int lhs_limbs, const uint32_t *rhs, int rhs_limbs); + + // lhs == rhs + bool Equal32(const uint32_t *lhs, int lhs_limbs, uint32_t rhs); + + // out = in >>> shift + // Precondition: 0 <= shift < 31 + void ShiftRight(int limbs, uint32_t *out, const uint32_t *in, int shift); + + // {out, carry} = in <<< shift + // Precondition: 0 <= shift < 31 + uint32_t ShiftLeft(int limbs, uint32_t *out, const uint32_t *in, int shift); + + // lhs += rhs, return carry out + // precondition: lhs_limbs >= rhs_limbs + uint32_t Add(uint32_t *lhs, int lhs_limbs, const uint32_t *rhs, int rhs_limbs); + + // out = lhs + rhs, return carry out + // precondition: lhs_limbs >= rhs_limbs + uint32_t Add(uint32_t *out, const uint32_t *lhs, int lhs_limbs, const uint32_t *rhs, int rhs_limbs); + + // lhs += rhs, return carry out + // precondition: lhs_limbs > 0 + uint32_t Add32(uint32_t *lhs, int lhs_limbs, uint32_t rhs); + + // lhs -= rhs, return borrow out + // precondition: lhs_limbs >= rhs_limbs + int32_t Subtract(uint32_t *lhs, int lhs_limbs, const uint32_t *rhs, int rhs_limbs); + + // out = lhs - rhs, return borrow out + // precondition: lhs_limbs >= rhs_limbs + int32_t Subtract(uint32_t *out, const uint32_t *lhs, int lhs_limbs, const uint32_t *rhs, int rhs_limbs); + + // lhs -= rhs, return borrow out + // precondition: lhs_limbs > 0, result limbs = lhs_limbs + int32_t Subtract32(uint32_t *lhs, int lhs_limbs, uint32_t rhs); + + // lhs = -rhs + void Negate(int limbs, uint32_t *lhs, const uint32_t *rhs); + + // n = ~n, only invert bits up to the MSB, but none above that + void BitNot(uint32_t *n, int limbs); + + // n = ~n, invert all bits, even ones above MSB + void LimbNot(uint32_t *n, int limbs); + + // lhs ^= rhs + void Xor(int limbs, uint32_t *lhs, const uint32_t *rhs); + + // Return the carry out from A += B << S + uint32_t AddLeftShift32( + int limbs, // Number of limbs in parameter A and B + uint32_t *A, // Large number + const uint32_t *B, // Large number + uint32_t S); // 32-bit number + + // Return the carry out from result = A * B + uint32_t Multiply32( + int limbs, // Number of limbs in parameter A, result + uint32_t *result, // Large number + const uint32_t *A, // Large number + uint32_t B); // 32-bit number + + // Return the carry out from X = X * M + A + uint32_t MultiplyAdd32( + int limbs, // Number of limbs in parameter A and B + uint32_t *X, // Large number + uint32_t M, // Large number + uint32_t A); // 32-bit number + + // Return the carry out from A += B * M + uint32_t AddMultiply32( + int limbs, // Number of limbs in parameter A and B + uint32_t *A, // Large number + const uint32_t *B, // Large number + uint32_t M); // 32-bit number + + // product = x * y + void SimpleMultiply( + int limbs, // Number of limbs in parameters x, y + uint32_t *product, // Large number; buffer size = limbs*2 + const uint32_t *x, // Large number + const uint32_t *y); // Large number + + // product = x ^ 2 + void SimpleSquare( + int limbs, // Number of limbs in parameter x + uint32_t *product, // Large number; buffer size = limbs*2 + const uint32_t *x); // Large number + + // product = xy + // memory space for product may not overlap with x,y + void Multiply( + int limbs, // Number of limbs in x,y + uint32_t *product, // Product; buffer size = limbs*2 + const uint32_t *x, // Large number; buffer size = limbs + const uint32_t *y); // Large number; buffer size = limbs + + // product = low half of x * y product + void SimpleMultiplyLowHalf( + int limbs, // Number of limbs in parameters x, y + uint32_t *product, // Large number; buffer size = limbs + const uint32_t *x, // Large number + const uint32_t *y); // Large number + + // product = x^2 + // memory space for product may not overlap with x + void Square( + int limbs, // Number of limbs in x + uint32_t *product, // Product; buffer size = limbs*2 + const uint32_t *x); // Large number; buffer size = limbs + + // Returns the remainder of N / divisor for a 32-bit divisor + uint32_t Modulus32( + int limbs, // Number of limbs in parameter N + const uint32_t *N, // Large number, buffer size = limbs + uint32_t divisor); // 32-bit number + + /* + * 'A' is overwritten with the quotient of the operation + * Returns the remainder of 'A' / divisor for a 32-bit divisor + * + * Does not check for divide-by-zero + */ + uint32_t Divide32( + int limbs, // Number of limbs in parameter A + uint32_t *A, // Large number, buffer size = limbs + uint32_t divisor); // 32-bit number + + // returns (n ^ -1) Mod 2^32 + uint32_t MulInverse32(uint32_t n); + + /* + * Computes multiplicative inverse of given number + * Such that: result * u = 1 + * Using Extended Euclid's Algorithm (GCDe) + * + * This is not always possible, so it will return false iff not possible. + */ + bool MulInverse( + int limbs, // Limbs in u and result + const uint32_t *u, // Large number, buffer size = limbs + uint32_t *result); // Large number, buffer size = limbs + + // {q, r} = u / v + // Return false on divide by zero + bool Divide( + const uint32_t *u, // numerator, size = u_limbs + int u_limbs, + const uint32_t *v, // denominator, size = v_limbs + int v_limbs, + uint32_t *q, // quotient, size = u_limbs + uint32_t *r); // remainder, size = v_limbs + + // r = u % v + // Return false on divide by zero + bool Modulus( + const uint32_t *u, // numerator, size = u_limbs + int u_limbs, + const uint32_t *v, // denominator, size = v_limbs + int v_limbs, + uint32_t *r); // remainder, size = v_limbs + + // m_inv ~= 2^(2k)/m + // Generates m_inv parameter of BarrettModulus() + // It is limbs in size, chopping off the 2^k bit + // Only works for m with the high bit set + void BarrettModulusPrecomp( + int limbs, // Number of limbs in m and m_inv + const uint32_t *m, // Modulus, size = limbs + uint32_t *m_inv); // Large number result, size = limbs + + // r = x mod m + // Using Barrett's method with precomputed m_inv + void BarrettModulus( + int limbs, // Number of limbs in m and m_inv + const uint32_t *x, // Number to reduce, size = limbs*2 + const uint32_t *m, // Modulus, size = limbs + const uint32_t *m_inv, // R/Modulus, precomputed, size = limbs + uint32_t *result); // Large number result + + // result = (x * y) (Mod modulus) + bool MulMod( + int limbs, // Number of limbs in x,y,modulus + const uint32_t *x, // Large number x + const uint32_t *y, // Large number y + const uint32_t *modulus, // Large number modulus + uint32_t *result); // Large number result + + // Convert string to bigint + // Return 0 if string contains non-digit characters, else number of limbs used + int ToInt(uint32_t *lhs, int max_limbs, const char *rhs, uint32_t base = 10); + + /* + * Computes: result = (1/u) (Mod v) + * Such that: result * u (Mod v) = 1 + * Using Extended Euclid's Algorithm (GCDe) + * + * This is not always possible, so it will return false iff not possible. + */ + bool InvMod( + const uint32_t *u, // Large number, buffer size = u_limbs + int u_limbs, // Limbs in u + const uint32_t *v, // Large number, buffer size = limbs + int limbs, // Limbs in modulus and result + uint32_t *result); // Large number, buffer size = limbs + + /* + * Computes: result = GCD(a, b) (greatest common divisor) + * + * Length of result is the length of the smallest argument + */ + void GCD( + const uint32_t *a, // Large number, buffer size = a_limbs + int a_limbs, // Size of a + const uint32_t *b, // Large number, buffer size = b_limbs + int b_limbs, // Size of b + uint32_t *result); // Large number, buffer size = min(a, b) + + // root = sqrt(square) + // Based on Newton-Raphson iteration: root_n+1 = (root_n + square/root_n) / 2 + // Doubles number of correct bits each iteration + // Precondition: The high limb of square is non-zero + // Returns false if it was unable to determine the root + bool SquareRoot( + int limbs, // Number of limbs in root + const uint32_t *square, // Square to root, size = limbs * 2 + uint32_t *root); // Output root, size = limbs + + // Calculates mod_inv from low limb of modulus for Mon*() + uint32_t MonReducePrecomp(uint32_t modulus0); + + // Compute n_residue for Montgomery reduction + void MonInputResidue( + const uint32_t *n, // Large number, buffer size = n_limbs + int n_limbs, // Number of limbs in n + const uint32_t *modulus, // Large number, buffer size = m_limbs + int m_limbs, // Number of limbs in modulus + uint32_t *n_residue); // Result, buffer size = m_limbs + + // result = a * b * r^-1 (Mod modulus) in Montgomery domain + void MonPro( + int limbs, // Number of limbs in each parameter + const uint32_t *a_residue, // Large number, buffer size = limbs + const uint32_t *b_residue, // Large number, buffer size = limbs + const uint32_t *modulus, // Large number, buffer size = limbs + uint32_t mod_inv, // MonReducePrecomp() return + uint32_t *result); // Large number, buffer size = limbs + + // result = a^-1 (Mod modulus) in Montgomery domain + void MonInverse( + int limbs, // Number of limbs in each parameter + const uint32_t *a_residue, // Large number, buffer size = limbs + const uint32_t *modulus, // Large number, buffer size = limbs + uint32_t mod_inv, // MonReducePrecomp() return + uint32_t *result); // Large number, buffer size = limbs + + // result = a * r^-1 (Mod modulus) in Montgomery domain + // The result may be greater than the modulus, but this is okay since + // the result is still in the RNS. MonFinish() corrects this at the end. + void MonReduce( + int limbs, // Number of limbs in each parameter + uint32_t *s, // Large number, buffer size = limbs*2, gets clobbered + const uint32_t *modulus, // Large number, buffer size = limbs + uint32_t mod_inv, // MonReducePrecomp() return + uint32_t *result); // Large number, buffer size = limbs + + // result = a * r^-1 (Mod modulus) in Montgomery domain + void MonFinish( + int limbs, // Number of limbs in each parameter + uint32_t *n, // Large number, buffer size = limbs + const uint32_t *modulus, // Large number, buffer size = limbs + uint32_t mod_inv); // MonReducePrecomp() return + + // Computes: result = base ^ exponent (Mod modulus) + // Using Montgomery multiplication with simple squaring method + // Base parameter must be a Montgomery Residue created with MonInputResidue() + void MonExpMod( + const uint32_t *base, // Base for exponentiation, buffer size = mod_limbs + const uint32_t *exponent,// Exponent, buffer size = exponent_limbs + int exponent_limbs, // Number of limbs in exponent + const uint32_t *modulus, // Modulus, buffer size = mod_limbs + int mod_limbs, // Number of limbs in modulus + uint32_t mod_inv, // MonReducePrecomp() return + uint32_t *result); // Result, buffer size = mod_limbs + + // Computes: result = base ^ exponent (Mod modulus) + // Using Montgomery multiplication with simple squaring method + void ExpMod( + const uint32_t *base, // Base for exponentiation, buffer size = base_limbs + int base_limbs, // Number of limbs in base + const uint32_t *exponent,// Exponent, buffer size = exponent_limbs + int exponent_limbs, // Number of limbs in exponent + const uint32_t *modulus, // Modulus, buffer size = mod_limbs + int mod_limbs, // Number of limbs in modulus + uint32_t mod_inv, // MonReducePrecomp() return + uint32_t *result); // Result, buffer size = mod_limbs + + // Computes: result = base ^ exponent (Mod modulus=mod_p*mod_q) + // Using Montgomery multiplication with Chinese Remainder Theorem + void ExpCRT( + const uint32_t *base, // Base for exponentiation, buffer size = base_limbs + int base_limbs, // Number of limbs in base + const uint32_t *exponent,// Exponent, buffer size = exponent_limbs + int exponent_limbs, // Number of limbs in exponent + const uint32_t *mod_p, // Large number, factorization of modulus, buffer size = p_limbs + uint32_t p_inv, // MonModInv() return + const uint32_t *mod_q, // Large number, factorization of modulus, buffer size = q_limbs + uint32_t q_inv, // MonModInv() return + const uint32_t *pinvq, // Large number, InvMod(p, q) precalculated, buffer size = phi_limbs + int mod_limbs, // Number of limbs in p, q and phi + uint32_t *result); // Result, buffer size = mod_limbs*2 + + // Rabin-Miller method for finding a strong pseudo-prime + // Preconditions: High bit and low bit of n = 1 + bool RabinMillerPrimeTest( + const uint32_t *n, // Number to check for primality + int limbs, // Number of limbs in n + uint32_t k); // Confidence level (40 is pretty good) + + // Generate a strong pseudo-prime using the Rabin-Miller primality test + void GenerateStrongPseudoPrime( + uint32_t *n, // Output prime + int limbs); // Number of limbs in n +} + +#endif // include guard + +#endif diff --git a/RakNet/Sources/BitStream.cpp b/RakNet/Sources/BitStream.cpp new file mode 100644 index 0000000..e1ccb26 --- /dev/null +++ b/RakNet/Sources/BitStream.cpp @@ -0,0 +1,1084 @@ +/// \file +/// +/// This file is part of RakNet Copyright 2003 Jenkins Software LLC +/// +/// Usage of RakNet is subject to the appropriate license agreement. + + +#if defined(_MSC_VER) && _MSC_VER < 1299 // VC6 doesn't support template specialization +#include "BitStream_NoTemplate.cpp" +#else + +#include "BitStream.h" +#include +#include +#include + +#include "SocketIncludes.h" + +#if defined(_XBOX) || defined(X360) + +#elif defined(_WIN32) +#include // htonl +#include +#include +#include +#elif defined(_PS3) || defined(__PS3__) || defined(SN_TARGET_PS3) + +#else +#include +#include +#include +#include +#endif + +// MSWin uses _copysign, others use copysign... +#ifndef _WIN32 +#define _copysign copysign +#endif + + + +using namespace RakNet; + +#ifdef _MSC_VER +#pragma warning( push ) +#endif + +BitStream::BitStream() +{ + numberOfBitsUsed = 0; + //numberOfBitsAllocated = 32 * 8; + numberOfBitsAllocated = BITSTREAM_STACK_ALLOCATION_SIZE * 8; + readOffset = 0; + //data = ( unsigned char* ) rakMalloc_Ex( 32, __FILE__, __LINE__ ); + data = ( unsigned char* ) stackData; + +#ifdef _DEBUG + // RakAssert( data ); +#endif + //memset(data, 0, 32); + copyData = true; +} + +BitStream::BitStream( const unsigned int initialBytesToAllocate ) +{ + numberOfBitsUsed = 0; + readOffset = 0; + if (initialBytesToAllocate <= BITSTREAM_STACK_ALLOCATION_SIZE) + { + data = ( unsigned char* ) stackData; + numberOfBitsAllocated = BITSTREAM_STACK_ALLOCATION_SIZE * 8; + } + else + { + data = ( unsigned char* ) rakMalloc_Ex( (size_t) initialBytesToAllocate, __FILE__, __LINE__ ); + numberOfBitsAllocated = initialBytesToAllocate << 3; + } +#ifdef _DEBUG + RakAssert( data ); +#endif + // memset(data, 0, initialBytesToAllocate); + copyData = true; +} + +BitStream::BitStream( unsigned char* _data, const unsigned int lengthInBytes, bool _copyData ) +{ + numberOfBitsUsed = lengthInBytes << 3; + readOffset = 0; + copyData = _copyData; + numberOfBitsAllocated = lengthInBytes << 3; + + if ( copyData ) + { + if ( lengthInBytes > 0 ) + { + if (lengthInBytes < BITSTREAM_STACK_ALLOCATION_SIZE) + { + data = ( unsigned char* ) stackData; + numberOfBitsAllocated = BITSTREAM_STACK_ALLOCATION_SIZE << 3; + } + else + { + data = ( unsigned char* ) rakMalloc_Ex( (size_t) lengthInBytes, __FILE__, __LINE__ ); + } +#ifdef _DEBUG + RakAssert( data ); +#endif + memcpy( data, _data, (size_t) lengthInBytes ); + } + else + data = 0; + } + else + data = ( unsigned char* ) _data; +} + +// Use this if you pass a pointer copy to the constructor (_copyData==false) and want to overallocate to prevent reallocation +void BitStream::SetNumberOfBitsAllocated( const BitSize_t lengthInBits ) +{ +#ifdef _DEBUG + RakAssert( lengthInBits >= ( BitSize_t ) numberOfBitsAllocated ); +#endif + numberOfBitsAllocated = lengthInBits; +} + +BitStream::~BitStream() +{ + if ( copyData && numberOfBitsAllocated > (BITSTREAM_STACK_ALLOCATION_SIZE << 3)) + rakFree_Ex( data , __FILE__, __LINE__ ); // Use realloc and free so we are more efficient than delete and new for resizing +} + +void BitStream::Reset( void ) +{ + // Note: Do NOT reallocate memory because BitStream is used + // in places to serialize/deserialize a buffer. Reallocation + // is a dangerous operation (may result in leaks). + + if ( numberOfBitsUsed > 0 ) + { + // memset(data, 0, BITS_TO_BYTES(numberOfBitsUsed)); + } + + // Don't free memory here for speed efficiency + //free(data); // Use realloc and free so we are more efficient than delete and new for resizing + numberOfBitsUsed = 0; + + //numberOfBitsAllocated=8; + readOffset = 0; + + //data=(unsigned char*)rakMalloc_Ex(1, __FILE__, __LINE__); + // if (numberOfBitsAllocated>0) + // memset(data, 0, BITS_TO_BYTES(numberOfBitsAllocated)); +} + +// Write an array or casted stream +void BitStream::Write( const char* inputByteArray, const unsigned int numberOfBytes ) +{ + if (numberOfBytes==0) + return; + + // Optimization: + if ((numberOfBitsUsed & 7) == 0) + { + AddBitsAndReallocate( BYTES_TO_BITS(numberOfBytes) ); + memcpy(data+BITS_TO_BYTES(numberOfBitsUsed), inputByteArray, (size_t) numberOfBytes); + numberOfBitsUsed+=BYTES_TO_BITS(numberOfBytes); + } + else + { + WriteBits( ( unsigned char* ) inputByteArray, numberOfBytes * 8, true ); + } + +} +void BitStream::Write( BitStream *bitStream) +{ + Write(bitStream, bitStream->GetNumberOfBitsUsed()); +} +void BitStream::Write( BitStream *bitStream, BitSize_t numberOfBits ) +{ + AddBitsAndReallocate( numberOfBits ); + BitSize_t numberOfBitsMod8; + + if ((bitStream->GetReadOffset()&7)==0 && (numberOfBitsUsed&7)==0) + { + int readOffsetBytes=bitStream->GetReadOffset()/8; + int numBytes=numberOfBits/8; + memcpy(data + (numberOfBitsUsed >> 3), bitStream->GetData()+readOffsetBytes, numBytes); + numberOfBits-=BYTES_TO_BITS(numBytes); + bitStream->SetReadOffset(BYTES_TO_BITS(numBytes+readOffsetBytes)); + numberOfBitsUsed+=BYTES_TO_BITS(numBytes); + } + + while (numberOfBits-->0 && bitStream->readOffset + 1 <= bitStream->numberOfBitsUsed) + { + numberOfBitsMod8 = numberOfBitsUsed & 7; + if ( numberOfBitsMod8 == 0 ) + { + // New byte + if (bitStream->data[ bitStream->readOffset >> 3 ] & ( 0x80 >> ( bitStream->readOffset & 7 ) ) ) + { + // Write 1 + data[ numberOfBitsUsed >> 3 ] = 0x80; + } + else + { + // Write 0 + data[ numberOfBitsUsed >> 3 ] = 0; + } + + } + else + { + // Existing byte + if (bitStream->data[ bitStream->readOffset >> 3 ] & ( 0x80 >> ( bitStream->readOffset & 7 ) ) ) + data[ numberOfBitsUsed >> 3 ] |= 0x80 >> ( numberOfBitsMod8 ); // Set the bit to 1 + // else 0, do nothing + } + + bitStream->readOffset++; + numberOfBitsUsed++; + } +} +void BitStream::Write( BitStream &bitStream, BitSize_t numberOfBits ) +{ + Write(&bitStream, numberOfBits); +} +void BitStream::Write( BitStream &bitStream ) +{ + Write(&bitStream); +} +bool BitStream::Read( BitStream *bitStream, BitSize_t numberOfBits ) +{ + if (GetNumberOfUnreadBits() < numberOfBits) + return false; + bitStream->Write(this, numberOfBits); + return true; +} +bool BitStream::Read( BitStream *bitStream ) +{ + bitStream->Write(this); + return true; +} +bool BitStream::Read( BitStream &bitStream, BitSize_t numberOfBits ) +{ + if (GetNumberOfUnreadBits() < numberOfBits) + return false; + bitStream.Write(this, numberOfBits); + return true; +} +bool BitStream::Read( BitStream &bitStream ) +{ + bitStream.Write(this); + return true; +} + +// Read an array or casted stream +bool BitStream::Read( char* outByteArray, const unsigned int numberOfBytes ) +{ + // Optimization: + if ((readOffset & 7) == 0) + { + if ( readOffset + ( numberOfBytes << 3 ) > numberOfBitsUsed ) + return false; + + // Write the data + memcpy( outByteArray, data + ( readOffset >> 3 ), (size_t) numberOfBytes ); + + readOffset += numberOfBytes << 3; + return true; + } + else + { + return ReadBits( ( unsigned char* ) outByteArray, numberOfBytes * 8 ); + } +} + +// Sets the read pointer back to the beginning of your data. +void BitStream::ResetReadPointer( void ) +{ + readOffset = 0; +} + +// Sets the write pointer back to the beginning of your data. +void BitStream::ResetWritePointer( void ) +{ + numberOfBitsUsed = 0; +} + +// Write a 0 +void BitStream::Write0( void ) +{ + AddBitsAndReallocate( 1 ); + + // New bytes need to be zeroed + if ( ( numberOfBitsUsed & 7 ) == 0 ) + data[ numberOfBitsUsed >> 3 ] = 0; + + numberOfBitsUsed++; +} + +// Write a 1 +void BitStream::Write1( void ) +{ + AddBitsAndReallocate( 1 ); + + BitSize_t numberOfBitsMod8 = numberOfBitsUsed & 7; + + if ( numberOfBitsMod8 == 0 ) + data[ numberOfBitsUsed >> 3 ] = 0x80; + else + data[ numberOfBitsUsed >> 3 ] |= 0x80 >> ( numberOfBitsMod8 ); // Set the bit to 1 + + numberOfBitsUsed++; +} + +// Returns true if the next data read is a 1, false if it is a 0 +bool BitStream::ReadBit( void ) +{ + bool result = ( data[ readOffset >> 3 ] & ( 0x80 >> ( readOffset & 7 ) ) ) !=0; + readOffset++; + return result; +} + +// Align the bitstream to the byte boundary and then write the specified number of bits. +// This is faster than WriteBits but wastes the bits to do the alignment and requires you to call +// SetReadToByteAlignment at the corresponding read position +void BitStream::WriteAlignedBytes( const unsigned char* inByteArray, const unsigned int numberOfBytesToWrite ) +{ + AlignWriteToByteBoundary(); + Write((const char*) inByteArray, numberOfBytesToWrite); +} +void BitStream::EndianSwapBytes( int byteOffset, int length ) +{ + if (DoEndianSwap()) + { + ReverseBytesInPlace(data+byteOffset, length); + } +} +/// Aligns the bitstream, writes inputLength, and writes input. Won't write beyond maxBytesToWrite +void BitStream::WriteAlignedBytesSafe( const char *inByteArray, const unsigned int inputLength, const unsigned int maxBytesToWrite ) +{ + if (inByteArray==0 || inputLength==0) + { + WriteCompressed((unsigned int)0); + return; + } + WriteCompressed(inputLength); + WriteAlignedBytes((const unsigned char*) inByteArray, inputLength < maxBytesToWrite ? inputLength : maxBytesToWrite); +} + +// Read bits, starting at the next aligned bits. Note that the modulus 8 starting offset of the +// sequence must be the same as was used with WriteBits. This will be a problem with packet coalescence +// unless you byte align the coalesced packets. +bool BitStream::ReadAlignedBytes( unsigned char* inOutByteArray, const unsigned int numberOfBytesToRead ) +{ +#ifdef _DEBUG + RakAssert( numberOfBytesToRead > 0 ); +#endif + + if ( numberOfBytesToRead <= 0 ) + return false; + + // Byte align + AlignReadToByteBoundary(); + + if ( readOffset + ( numberOfBytesToRead << 3 ) > numberOfBitsUsed ) + return false; + + // Write the data + memcpy( inOutByteArray, data + ( readOffset >> 3 ), (size_t) numberOfBytesToRead ); + + readOffset += numberOfBytesToRead << 3; + + return true; +} +bool BitStream::ReadAlignedBytesSafe( char *inOutByteArray, int &inputLength, const int maxBytesToRead ) +{ + return ReadAlignedBytesSafe(inOutByteArray,(unsigned int&) inputLength,(unsigned int)maxBytesToRead); +} +bool BitStream::ReadAlignedBytesSafe( char *inOutByteArray, unsigned int &inputLength, const unsigned int maxBytesToRead ) +{ + if (ReadCompressed(inputLength)==false) + return false; + if (inputLength > maxBytesToRead) + inputLength=maxBytesToRead; + if (inputLength==0) + return true; + return ReadAlignedBytes((unsigned char*) inOutByteArray, inputLength); +} +bool BitStream::ReadAlignedBytesSafeAlloc( char **outByteArray, int &inputLength, const unsigned int maxBytesToRead ) +{ + return ReadAlignedBytesSafeAlloc(outByteArray,(unsigned int&) inputLength, maxBytesToRead); +} +bool BitStream::ReadAlignedBytesSafeAlloc( char ** outByteArray, unsigned int &inputLength, const unsigned int maxBytesToRead ) +{ + rakFree_Ex(*outByteArray, __FILE__, __LINE__ ); + *outByteArray=0; + if (ReadCompressed(inputLength)==false) + return false; + if (inputLength > maxBytesToRead) + inputLength=maxBytesToRead; + if (inputLength==0) + return true; + *outByteArray = (char*) rakMalloc_Ex( (size_t) inputLength, __FILE__, __LINE__ ); + return ReadAlignedBytes((unsigned char*) *outByteArray, inputLength); +} + +// Write numberToWrite bits from the input source +void BitStream::WriteBits( const unsigned char* inByteArray, BitSize_t numberOfBitsToWrite, const bool rightAlignedBits ) +{ +// if (numberOfBitsToWrite<=0) +// return; + + AddBitsAndReallocate( numberOfBitsToWrite ); + + const BitSize_t numberOfBitsUsedMod8 = numberOfBitsUsed & 7; + + // If currently aligned and numberOfBits is a multiple of 8, just memcpy for speed + if (numberOfBitsUsedMod8==0 && (numberOfBitsToWrite&7)==0) + { + memcpy( data + ( numberOfBitsUsed >> 3 ), inByteArray, numberOfBitsToWrite>>3); + numberOfBitsUsed+=numberOfBitsToWrite; + return; + } + + unsigned char dataByte; + const unsigned char* inputPtr=inByteArray; + + // Faster to put the while at the top surprisingly enough + while ( numberOfBitsToWrite > 0 ) + //do + { + dataByte = *( inputPtr++ ); + + if ( numberOfBitsToWrite < 8 && rightAlignedBits ) // rightAlignedBits means in the case of a partial byte, the bits are aligned from the right (bit 0) rather than the left (as in the normal internal representation) + dataByte <<= 8 - numberOfBitsToWrite; // shift left to get the bits on the left, as in our internal representation + + // Writing to a new byte each time + if ( numberOfBitsUsedMod8 == 0 ) + * ( data + ( numberOfBitsUsed >> 3 ) ) = dataByte; + else + { + // Copy over the new data. + *( data + ( numberOfBitsUsed >> 3 ) ) |= dataByte >> ( numberOfBitsUsedMod8 ); // First half + + if ( 8 - ( numberOfBitsUsedMod8 ) < 8 && 8 - ( numberOfBitsUsedMod8 ) < numberOfBitsToWrite ) // If we didn't write it all out in the first half (8 - (numberOfBitsUsed%8) is the number we wrote in the first half) + { + *( data + ( numberOfBitsUsed >> 3 ) + 1 ) = (unsigned char) ( dataByte << ( 8 - ( numberOfBitsUsedMod8 ) ) ); // Second half (overlaps byte boundary) + } + } + + if ( numberOfBitsToWrite >= 8 ) + { + numberOfBitsUsed += 8; + numberOfBitsToWrite -= 8; + } + else + { + numberOfBitsUsed += numberOfBitsToWrite; + numberOfBitsToWrite=0; + } + } + // } while(numberOfBitsToWrite>0); +} + +// Set the stream to some initial data. For internal use +void BitStream::SetData( unsigned char *inByteArray ) +{ + data=inByteArray; + copyData=false; +} + +// Assume the input source points to a native type, compress and write it +void BitStream::WriteCompressed( const unsigned char* inByteArray, + const unsigned int size, const bool unsignedData ) +{ + BitSize_t currentByte = ( size >> 3 ) - 1; // PCs + + unsigned char byteMatch; + + if ( unsignedData ) + { + byteMatch = 0; + } + + else + { + byteMatch = 0xFF; + } + + // Write upper bytes with a single 1 + // From high byte to low byte, if high byte is a byteMatch then write a 1 bit. Otherwise write a 0 bit and then write the remaining bytes + while ( currentByte > 0 ) + { + if ( inByteArray[ currentByte ] == byteMatch ) // If high byte is byteMatch (0 of 0xff) then it would have the same value shifted + { + bool b = true; + Write( b ); + } + else + { + // Write the remainder of the data after writing 0 + bool b = false; + Write( b ); + + WriteBits( inByteArray, ( currentByte + 1 ) << 3, true ); + // currentByte--; + + + return ; + } + + currentByte--; + } + + // If the upper half of the last byte is a 0 (positive) or 16 (negative) then write a 1 and the remaining 4 bits. Otherwise write a 0 and the 8 bites. + if ( ( unsignedData && ( ( *( inByteArray + currentByte ) ) & 0xF0 ) == 0x00 ) || + ( unsignedData == false && ( ( *( inByteArray + currentByte ) ) & 0xF0 ) == 0xF0 ) ) + { + bool b = true; + Write( b ); + WriteBits( inByteArray + currentByte, 4, true ); + } + + else + { + bool b = false; + Write( b ); + WriteBits( inByteArray + currentByte, 8, true ); + } +} + +// Read numberOfBitsToRead bits to the output source +// alignBitsToRight should be set to true to convert internal bitstream data to userdata +// It should be false if you used WriteBits with rightAlignedBits false +bool BitStream::ReadBits( unsigned char *inOutByteArray, BitSize_t numberOfBitsToRead, const bool alignBitsToRight ) +{ +#ifdef _DEBUG + // RakAssert( numberOfBitsToRead > 0 ); +#endif + if (numberOfBitsToRead<=0) + return false; + + if ( readOffset + numberOfBitsToRead > numberOfBitsUsed ) + return false; + + + const BitSize_t readOffsetMod8 = readOffset & 7; + + // If currently aligned and numberOfBits is a multiple of 8, just memcpy for speed + if (readOffsetMod8==0 && (numberOfBitsToRead&7)==0) + { + memcpy( inOutByteArray, data + ( readOffset >> 3 ), numberOfBitsToRead>>3); + readOffset+=numberOfBitsToRead; + return true; + } + + + + BitSize_t offset = 0; + + memset( inOutByteArray, 0, (size_t) BITS_TO_BYTES( numberOfBitsToRead ) ); + + while ( numberOfBitsToRead > 0 ) + { + *( inOutByteArray + offset ) |= *( data + ( readOffset >> 3 ) ) << ( readOffsetMod8 ); // First half + + if ( readOffsetMod8 > 0 && numberOfBitsToRead > 8 - ( readOffsetMod8 ) ) // If we have a second half, we didn't read enough bytes in the first half + *( inOutByteArray + offset ) |= *( data + ( readOffset >> 3 ) + 1 ) >> ( 8 - ( readOffsetMod8 ) ); // Second half (overlaps byte boundary) + + if (numberOfBitsToRead>=8) + { + numberOfBitsToRead -= 8; + readOffset += 8; + offset++; + } + else + { + int neg = (int) numberOfBitsToRead - 8; + + if ( neg < 0 ) // Reading a partial byte for the last byte, shift right so the data is aligned on the right + { + + if ( alignBitsToRight ) + * ( inOutByteArray + offset ) >>= -neg; + + readOffset += 8 + neg; + } + else + readOffset += 8; + + offset++; + + numberOfBitsToRead=0; + } + } + + return true; +} + +// Assume the input source points to a compressed native type. Decompress and read it +bool BitStream::ReadCompressed( unsigned char* inOutByteArray, + const unsigned int size, const bool unsignedData ) +{ + unsigned int currentByte = ( size >> 3 ) - 1; + + + unsigned char byteMatch, halfByteMatch; + + if ( unsignedData ) + { + byteMatch = 0; + halfByteMatch = 0; + } + + else + { + byteMatch = 0xFF; + halfByteMatch = 0xF0; + } + + // Upper bytes are specified with a single 1 if they match byteMatch + // From high byte to low byte, if high byte is a byteMatch then write a 1 bit. Otherwise write a 0 bit and then write the remaining bytes + while ( currentByte > 0 ) + { + // If we read a 1 then the data is byteMatch. + + bool b; + + if ( Read( b ) == false ) + return false; + + if ( b ) // Check that bit + { + inOutByteArray[ currentByte ] = byteMatch; + currentByte--; + } + else + { + // Read the rest of the bytes + + if ( ReadBits( inOutByteArray, ( currentByte + 1 ) << 3 ) == false ) + return false; + + return true; + } + } + + // All but the first bytes are byteMatch. If the upper half of the last byte is a 0 (positive) or 16 (negative) then what we read will be a 1 and the remaining 4 bits. + // Otherwise we read a 0 and the 8 bytes + //RakAssert(readOffset+1 <=numberOfBitsUsed); // If this assert is hit the stream wasn't long enough to read from + if ( readOffset + 1 > numberOfBitsUsed ) + return false; + + bool b; + + if ( Read( b ) == false ) + return false; + + if ( b ) // Check that bit + { + + if ( ReadBits( inOutByteArray + currentByte, 4 ) == false ) + return false; + + inOutByteArray[ currentByte ] |= halfByteMatch; // We have to set the high 4 bits since these are set to 0 by ReadBits + } + else + { + if ( ReadBits( inOutByteArray + currentByte, 8 ) == false ) + return false; + } + + return true; +} + +// Reallocates (if necessary) in preparation of writing numberOfBitsToWrite +void BitStream::AddBitsAndReallocate( const BitSize_t numberOfBitsToWrite ) +{ + BitSize_t newNumberOfBitsAllocated = numberOfBitsToWrite + numberOfBitsUsed; + + if ( numberOfBitsToWrite + numberOfBitsUsed > 0 && ( ( numberOfBitsAllocated - 1 ) >> 3 ) < ( ( newNumberOfBitsAllocated - 1 ) >> 3 ) ) // If we need to allocate 1 or more new bytes + { +#ifdef _DEBUG + // If this assert hits then we need to specify true for the third parameter in the constructor + // It needs to reallocate to hold all the data and can't do it unless we allocated to begin with + // Often hits if you call Write or Serialize on a read-only bitstream + RakAssert( copyData == true ); +#endif + + // Less memory efficient but saves on news and deletes + /// Cap to 1 meg buffer to save on huge allocations + newNumberOfBitsAllocated = ( numberOfBitsToWrite + numberOfBitsUsed ) * 2; + if (newNumberOfBitsAllocated - ( numberOfBitsToWrite + numberOfBitsUsed ) > 1048576 ) + newNumberOfBitsAllocated = numberOfBitsToWrite + numberOfBitsUsed + 1048576; + + // BitSize_t newByteOffset = BITS_TO_BYTES( numberOfBitsAllocated ); + // Use realloc and free so we are more efficient than delete and new for resizing + BitSize_t amountToAllocate = BITS_TO_BYTES( newNumberOfBitsAllocated ); + if (data==(unsigned char*)stackData) + { + if (amountToAllocate > BITSTREAM_STACK_ALLOCATION_SIZE) + { + data = ( unsigned char* ) rakMalloc_Ex( (size_t) amountToAllocate, __FILE__, __LINE__ ); + + // need to copy the stack data over to our new memory area too + memcpy ((void *)data, (void *)stackData, (size_t) BITS_TO_BYTES( numberOfBitsAllocated )); + } + } + else + { + data = ( unsigned char* ) rakRealloc_Ex( data, (size_t) amountToAllocate, __FILE__, __LINE__ ); + } + +#ifdef _DEBUG + RakAssert( data ); // Make sure realloc succeeded +#endif + // memset(data+newByteOffset, 0, ((newNumberOfBitsAllocated-1)>>3) - ((numberOfBitsAllocated-1)>>3)); // Set the new data block to 0 + } + + if ( newNumberOfBitsAllocated > numberOfBitsAllocated ) + numberOfBitsAllocated = newNumberOfBitsAllocated; +} +BitSize_t BitStream::GetNumberOfBitsAllocated(void) const +{ + return numberOfBitsAllocated; +} +void BitStream::PadWithZeroToByteLength( unsigned int bytes ) +{ + if (GetNumberOfBytesUsed() < bytes) + { + AlignWriteToByteBoundary(); + unsigned int numToWrite = bytes - GetNumberOfBytesUsed(); + AddBitsAndReallocate( BYTES_TO_BITS(numToWrite) ); + memset(data+BITS_TO_BYTES(numberOfBitsUsed), 0, (size_t) numToWrite); + numberOfBitsUsed+=BYTES_TO_BITS(numToWrite); + } +} + +// Should hit if reads didn't match writes +void BitStream::AssertStreamEmpty( void ) +{ + RakAssert( readOffset == numberOfBitsUsed ); +} +void BitStream::PrintBits( char *out ) const +{ + if ( numberOfBitsUsed <= 0 ) + { + strcpy(out, "No bits\n" ); + return; + } + + unsigned int strIndex=0; + for ( BitSize_t counter = 0; counter < BITS_TO_BYTES( numberOfBitsUsed ); counter++ ) + { + BitSize_t stop; + + if ( counter == ( numberOfBitsUsed - 1 ) >> 3 ) + stop = 8 - ( ( ( numberOfBitsUsed - 1 ) & 7 ) + 1 ); + else + stop = 0; + + for ( BitSize_t counter2 = 7; counter2 >= stop; counter2-- ) + { + if ( ( data[ counter ] >> counter2 ) & 1 ) + out[strIndex++]='1'; + else + out[strIndex++]='0'; + + if (counter2==0) + break; + } + + out[strIndex++]=' '; + } + + out[strIndex++]='\n'; + + out[strIndex++]=0; +} +void BitStream::PrintBits( void ) const +{ + char out[2048]; + PrintBits(out); + RAKNET_DEBUG_PRINTF(out); +} +void BitStream::PrintHex( char *out ) const +{ + BitSize_t i; + for ( i=0; i < GetNumberOfBytesUsed(); i++) + { + sprintf(out+i*3, "%02x ", data[i]); + } +} +void BitStream::PrintHex( void ) const +{ + char out[2048]; + PrintHex(out); + RAKNET_DEBUG_PRINTF(out); +} + +// Exposes the data for you to look at, like PrintBits does. +// Data will point to the stream. Returns the length in bits of the stream. +BitSize_t BitStream::CopyData( unsigned char** _data ) const +{ +#ifdef _DEBUG + RakAssert( numberOfBitsUsed > 0 ); +#endif + + *_data = (unsigned char*) rakMalloc_Ex( (size_t) BITS_TO_BYTES( numberOfBitsUsed ), __FILE__, __LINE__ ); + memcpy( *_data, data, sizeof(unsigned char) * (size_t) ( BITS_TO_BYTES( numberOfBitsUsed ) ) ); + return numberOfBitsUsed; +} + +// Ignore data we don't intend to read +void BitStream::IgnoreBits( const BitSize_t numberOfBits ) +{ + readOffset += numberOfBits; +} + +void BitStream::IgnoreBytes( const unsigned int numberOfBytes ) +{ + IgnoreBits(BYTES_TO_BITS(numberOfBytes)); +} + +// Move the write pointer to a position on the array. Dangerous if you don't know what you are doing! +// Doesn't work with non-aligned data! +void BitStream::SetWriteOffset( const BitSize_t offset ) +{ + numberOfBitsUsed = offset; +} + +/* +BitSize_t BitStream::GetWriteOffset( void ) const +{ +return numberOfBitsUsed; +} + +// Returns the length in bits of the stream +BitSize_t BitStream::GetNumberOfBitsUsed( void ) const +{ +return GetWriteOffset(); +} + +// Returns the length in bytes of the stream +BitSize_t BitStream::GetNumberOfBytesUsed( void ) const +{ +return BITS_TO_BYTES( numberOfBitsUsed ); +} + +// Returns the number of bits into the stream that we have read +BitSize_t BitStream::GetReadOffset( void ) const +{ +return readOffset; +} + + +// Sets the read bit index +void BitStream::SetReadOffset( const BitSize_t newReadOffset ) +{ +readOffset=newReadOffset; +} + +// Returns the number of bits left in the stream that haven't been read +BitSize_t BitStream::GetNumberOfUnreadBits( void ) const +{ +return numberOfBitsUsed - readOffset; +} +// Exposes the internal data +unsigned char* BitStream::GetData( void ) const +{ +return data; +} + +*/ +// If we used the constructor version with copy data off, this makes sure it is set to on and the data pointed to is copied. +void BitStream::AssertCopyData( void ) +{ + if ( copyData == false ) + { + copyData = true; + + if ( numberOfBitsAllocated > 0 ) + { + unsigned char * newdata = ( unsigned char* ) rakMalloc_Ex( (size_t) BITS_TO_BYTES( numberOfBitsAllocated ), __FILE__, __LINE__ ); +#ifdef _DEBUG + + RakAssert( data ); +#endif + + memcpy( newdata, data, (size_t) BITS_TO_BYTES( numberOfBitsAllocated ) ); + data = newdata; + } + + else + data = 0; + } +} +bool BitStream::IsNetworkOrderInternal(void) +{ +#if defined(_PS3) || defined(__PS3__) || defined(SN_TARGET_PS3) + +#else + static const bool isNetworkOrder=(htonl(12345) == 12345); + return isNetworkOrder; +#endif +} +void BitStream::ReverseBytes(unsigned char *inByteArray, unsigned char *inOutByteArray, const unsigned int length) +{ + for (BitSize_t i=0; i < length; i++) + inOutByteArray[i]=inByteArray[length-i-1]; +} +void BitStream::ReverseBytesInPlace(unsigned char *inOutData,const unsigned int length) +{ + unsigned char temp; + BitSize_t i; + for (i=0; i < (length>>1); i++) + { + temp = inOutData[i]; + inOutData[i]=inOutData[length-i-1]; + inOutData[length-i-1]=temp; + } +} + +bool BitStream::Read(char *varString) +{ + return RakString::Deserialize(varString,this); +} +bool BitStream::Read(unsigned char *varString) +{ + return RakString::Deserialize((char*) varString,this); +} +void BitStream::WriteAlignedVar8(const char *inByteArray) +{ + RakAssert((numberOfBitsUsed&7)==0); + AddBitsAndReallocate(1*8); + data[( numberOfBitsUsed >> 3 ) + 0] = inByteArray[0]; + numberOfBitsUsed+=1*8; +} +bool BitStream::ReadAlignedVar8(char *inOutByteArray) +{ + RakAssert((readOffset&7)==0); + if ( readOffset + 1*8 > numberOfBitsUsed ) + return false; + + inOutByteArray[0] = data[( readOffset >> 3 ) + 0]; + readOffset+=1*8; + return true; +} +void BitStream::WriteAlignedVar16(const char *inByteArray) +{ + RakAssert((numberOfBitsUsed&7)==0); + AddBitsAndReallocate(2*8); +#ifndef __BITSTREAM_NATIVE_END + if (DoEndianSwap()) + { + data[( numberOfBitsUsed >> 3 ) + 0] = inByteArray[1]; + data[( numberOfBitsUsed >> 3 ) + 1] = inByteArray[0]; + } + else +#endif + { + data[( numberOfBitsUsed >> 3 ) + 0] = inByteArray[0]; + data[( numberOfBitsUsed >> 3 ) + 1] = inByteArray[1]; + } + + numberOfBitsUsed+=2*8; +} +bool BitStream::ReadAlignedVar16(char *inOutByteArray) +{ + RakAssert((readOffset&7)==0); + if ( readOffset + 2*8 > numberOfBitsUsed ) + return false; +#ifndef __BITSTREAM_NATIVE_END + if (DoEndianSwap()) + { + inOutByteArray[0] = data[( readOffset >> 3 ) + 1]; + inOutByteArray[1] = data[( readOffset >> 3 ) + 0]; + } + else +#endif + { + inOutByteArray[0] = data[( readOffset >> 3 ) + 0]; + inOutByteArray[1] = data[( readOffset >> 3 ) + 1]; + } + + readOffset+=2*8; + return true; +} +void BitStream::WriteAlignedVar32(const char *inByteArray) +{ + RakAssert((numberOfBitsUsed&7)==0); + AddBitsAndReallocate(4*8); +#ifndef __BITSTREAM_NATIVE_END + if (DoEndianSwap()) + { + data[( numberOfBitsUsed >> 3 ) + 0] = inByteArray[3]; + data[( numberOfBitsUsed >> 3 ) + 1] = inByteArray[2]; + data[( numberOfBitsUsed >> 3 ) + 2] = inByteArray[1]; + data[( numberOfBitsUsed >> 3 ) + 3] = inByteArray[0]; + } + else +#endif + { + data[( numberOfBitsUsed >> 3 ) + 0] = inByteArray[0]; + data[( numberOfBitsUsed >> 3 ) + 1] = inByteArray[1]; + data[( numberOfBitsUsed >> 3 ) + 2] = inByteArray[2]; + data[( numberOfBitsUsed >> 3 ) + 3] = inByteArray[3]; + } + + numberOfBitsUsed+=4*8; +} +bool BitStream::ReadAlignedVar32(char *inOutByteArray) +{ + RakAssert((readOffset&7)==0); + if ( readOffset + 4*8 > numberOfBitsUsed ) + return false; +#ifndef __BITSTREAM_NATIVE_END + if (DoEndianSwap()) + { + inOutByteArray[0] = data[( readOffset >> 3 ) + 3]; + inOutByteArray[1] = data[( readOffset >> 3 ) + 2]; + inOutByteArray[2] = data[( readOffset >> 3 ) + 1]; + inOutByteArray[3] = data[( readOffset >> 3 ) + 0]; + } + else +#endif + { + inOutByteArray[0] = data[( readOffset >> 3 ) + 0]; + inOutByteArray[1] = data[( readOffset >> 3 ) + 1]; + inOutByteArray[2] = data[( readOffset >> 3 ) + 2]; + inOutByteArray[3] = data[( readOffset >> 3 ) + 3]; + } + + readOffset+=4*8; + return true; +} +bool BitStream::ReadFloat16( float &outFloat, float floatMin, float floatMax ) +{ + unsigned short percentile; + if (Read(percentile)) + { + RakAssert(floatMax>floatMin); + outFloat = floatMin + ((float) percentile / 65535.0f) * (floatMax-floatMin); + if (outFloatfloatMax) + outFloat=floatMax; + return true; + } + return false; +} +bool BitStream::SerializeFloat16(bool writeToBitstream, float &inOutFloat, float floatMin, float floatMax) +{ + if (writeToBitstream) + WriteFloat16(inOutFloat, floatMin, floatMax); + else + return ReadFloat16(inOutFloat, floatMin, floatMax); + return true; +} +void BitStream::WriteFloat16( float inOutFloat, float floatMin, float floatMax ) +{ + RakAssert(floatMax>floatMin); + if (inOutFloat>floatMax+.001) + { + RakAssert(inOutFloat<=floatMax+.001); + } + if (inOutFloat=floatMin-.001); + } + float percentile=65535.0f * (inOutFloat-floatMin)/(floatMax-floatMin); + if (percentile<0.0) + percentile=0.0; + if (percentile>65535.0f) + percentile=65535.0f; + Write((unsigned short)percentile); +} + +#ifdef _MSC_VER +#pragma warning( pop ) +#endif + +#endif // #if _MSC_VER < 1299 diff --git a/RakNet/Sources/BitStream.h b/RakNet/Sources/BitStream.h new file mode 100644 index 0000000..0a781c0 --- /dev/null +++ b/RakNet/Sources/BitStream.h @@ -0,0 +1,1927 @@ +/// \file BitStream.h +/// \brief This class allows you to write and read native types as a string of bits. +/// \details BitStream is used extensively throughout RakNet and is designed to be used by users as well. +/// +/// This file is part of RakNet Copyright 2003 Jenkins Software LLC +/// +/// Usage of RakNet is subject to the appropriate license agreement. +/// + +#if defined(_MSC_VER) && _MSC_VER < 1299 // VC6 doesn't support template specialization +#include "BitStream_NoTemplate.h" +#else + +#ifndef __BITSTREAM_H +#define __BITSTREAM_H + +#include "RakMemoryOverride.h" +#include "RakNetDefines.h" +#include "Export.h" +#include "RakNetTypes.h" +#include "RakString.h" +#include "RakAssert.h" +#include +#include + +#ifdef _MSC_VER +#pragma warning( push ) +#endif + +// MSWin uses _copysign, others use copysign... +#ifndef _WIN32 +#define _copysign copysign +#endif + +/// The namespace RakNet is not consistently used. It's only purpose is to avoid compiler errors for classes whose names are very common. +/// For the most part I've tried to avoid this simply by using names very likely to be unique for my classes. +namespace RakNet +{ + /// This class allows you to write and read native types as a string of bits. BitStream is used extensively throughout RakNet and is designed to be used by users as well. + /// \sa BitStreamSample.txt + class RAK_DLL_EXPORT BitStream + { + + public: + /// Default Constructor + BitStream(); + + /// \brief Create the bitstream, with some number of bytes to immediately allocate. + /// \details There is no benefit to calling this, unless you know exactly how many bytes you need and it is greater than BITSTREAM_STACK_ALLOCATION_SIZE. + /// In that case all it does is save you one or more realloc calls. + /// \param[in] initialBytesToAllocate the number of bytes to pre-allocate. + BitStream( const unsigned int initialBytesToAllocate ); + + /// \brief Initialize the BitStream, immediately setting the data it contains to a predefined pointer. + /// \details Set \a _copyData to true if you want to make an internal copy of the data you are passing. Set it to false to just save a pointer to the data. + /// You shouldn't call Write functions with \a _copyData as false, as this will write to unallocated memory + /// 99% of the time you will use this function to cast Packet::data to a bitstream for reading, in which case you should write something as follows: + /// \code + /// RakNet::BitStream bs(packet->data, packet->length, false); + /// \endcode + /// \param[in] _data An array of bytes. + /// \param[in] lengthInBytes Size of the \a _data. + /// \param[in] _copyData true or false to make a copy of \a _data or not. + BitStream( unsigned char* _data, const unsigned int lengthInBytes, bool _copyData ); + + // Destructor + ~BitStream(); + + /// Resets the bitstream for reuse. + void Reset( void ); + + /// \brief Bidirectional serialize/deserialize any integral type to/from a bitstream. + /// \details Undefine __BITSTREAM_NATIVE_END if you need endian swapping. + /// \param[in] writeToBitstream true to write from your data to this bitstream. False to read from this bitstream and write to your data + /// \param[in] inOutTemplateVar The value to write + /// \return true if \a writeToBitstream is true. true if \a writeToBitstream is false and the read was successful. false if \a writeToBitstream is false and the read was not successful. + template + bool Serialize(bool writeToBitstream, templateType &inOutTemplateVar); + + /// \brief Bidirectional serialize/deserialize any integral type to/from a bitstream. + /// \details If the current value is different from the last value + /// the current value will be written. Otherwise, a single bit will be written + /// \param[in] writeToBitstream true to write from your data to this bitstream. False to read from this bitstream and write to your data + /// \param[in] inOutCurrentValue The current value to write + /// \param[in] lastValue The last value to compare against. Only used if \a writeToBitstream is true. + /// \return true if \a writeToBitstream is true. true if \a writeToBitstream is false and the read was successful. false if \a writeToBitstream is false and the read was not successful. + template + bool SerializeDelta(bool writeToBitstream, templateType &inOutCurrentValue, templateType lastValue); + + /// \brief Bidirectional version of SerializeDelta when you don't know what the last value is, or there is no last value. + /// \param[in] writeToBitstream true to write from your data to this bitstream. False to read from this bitstream and write to your data + /// \param[in] inOutCurrentValue The current value to write + /// \return true if \a writeToBitstream is true. true if \a writeToBitstream is false and the read was successful. false if \a writeToBitstream is false and the read was not successful. + template + bool SerializeDelta(bool writeToBitstream, templateType &inOutCurrentValue); + + /// \brief Bidirectional serialize/deserialize any integral type to/from a bitstream. + /// \details Undefine __BITSTREAM_NATIVE_END if you need endian swapping. + /// If you are not using __BITSTREAM_NATIVE_END the opposite is true for types larger than 1 byte + /// For floating point, this is lossy, using 2 bytes for a float and 4 for a double. The range must be between -1 and +1. + /// For non-floating point, this is lossless, but only has benefit if you use less than half the range of the type + /// \param[in] writeToBitstream true to write from your data to this bitstream. False to read from this bitstream and write to your data + /// \param[in] inOutTemplateVar The value to write + /// \return true if \a writeToBitstream is true. true if \a writeToBitstream is false and the read was successful. false if \a writeToBitstream is false and the read was not successful. + template + bool SerializeCompressed(bool writeToBitstream, templateType &inOutTemplateVar); + + /// \brief Bidirectional serialize/deserialize any integral type to/from a bitstream. + /// \details If the current value is different from the last value + /// the current value will be written. Otherwise, a single bit will be written + /// For floating point, this is lossy, using 2 bytes for a float and 4 for a double. The range must be between -1 and +1. + /// For non-floating point, this is lossless, but only has benefit if you use less than half the range of the type + /// If you are not using __BITSTREAM_NATIVE_END the opposite is true for types larger than 1 byte + /// \param[in] writeToBitstream true to write from your data to this bitstream. False to read from this bitstream and write to your data + /// \param[in] inOutCurrentValue The current value to write + /// \param[in] lastValue The last value to compare against. Only used if \a writeToBitstream is true. + /// \return true if \a writeToBitstream is true. true if \a writeToBitstream is false and the read was successful. false if \a writeToBitstream is false and the read was not successful. + template + bool SerializeCompressedDelta(bool writeToBitstream, templateType &inOutCurrentValue, templateType lastValue); + + /// \brief Save as SerializeCompressedDelta(templateType ¤tValue, templateType lastValue) when we have an unknown second parameter + /// \return true on data read. False on insufficient data in bitstream + template + bool SerializeCompressedDelta(bool writeToBitstream, templateType &inOutTemplateVar); + + /// \brief Bidirectional serialize/deserialize an array or casted stream or raw data. This does NOT do endian swapping. + /// \param[in] writeToBitstream true to write from your data to this bitstream. False to read from this bitstream and write to your data + /// \param[in] inOutByteArray a byte buffer + /// \param[in] numberOfBytes the size of \a input in bytes + /// \return true if \a writeToBitstream is true. true if \a writeToBitstream is false and the read was successful. false if \a writeToBitstream is false and the read was not successful. + bool Serialize(bool writeToBitstream, char* inOutByteArray, const unsigned int numberOfBytes ); + + /// \brief Serialize a float into 2 bytes, spanning the range between \a floatMin and \a floatMax + /// \param[in] inOutFloat The float to write + /// \param[in] floatMin Predetermined minimum value of f + /// \param[in] floatMax Predetermined maximum value of f + bool SerializeFloat16(bool writeToBitstream, float &inOutFloat, float floatMin, float floatMax); + + /// \brief Bidirectional serialize/deserialize a normalized 3D vector, using (at most) 4 bytes + 3 bits instead of 12-24 bytes. + /// \details Will further compress y or z axis aligned vectors. + /// Accurate to 1/32767.5. + /// \param[in] writeToBitstream true to write from your data to this bitstream. False to read from this bitstream and write to your data + /// \param[in] x x + /// \param[in] y y + /// \param[in] z z + /// \return true if \a writeToBitstream is true. true if \a writeToBitstream is false and the read was successful. false if \a writeToBitstream is false and the read was not successful. + template // templateType for this function must be a float or double + bool SerializeNormVector(bool writeToBitstream, templateType &x, templateType &y, templateType &z ); + + /// \brief Bidirectional serialize/deserialize a vector, using 10 bytes instead of 12. + /// \details Loses accuracy to about 3/10ths and only saves 2 bytes, so only use if accuracy is not important. + /// \param[in] writeToBitstream true to write from your data to this bitstream. False to read from this bitstream and write to your data + /// \param[in] x x + /// \param[in] y y + /// \param[in] z z + /// \return true if \a writeToBitstream is true. true if \a writeToBitstream is false and the read was successful. false if \a writeToBitstream is false and the read was not successful. + template // templateType for this function must be a float or double + bool SerializeVector(bool writeToBitstream, templateType &x, templateType &y, templateType &z ); + + /// \brief Bidirectional serialize/deserialize a normalized quaternion in 6 bytes + 4 bits instead of 16 bytes. Slightly lossy. + /// \param[in] writeToBitstream true to write from your data to this bitstream. False to read from this bitstream and write to your data + /// \param[in] w w + /// \param[in] x x + /// \param[in] y y + /// \param[in] z z + /// \return true if \a writeToBitstream is true. true if \a writeToBitstream is false and the read was successful. false if \a writeToBitstream is false and the read was not successful. + template // templateType for this function must be a float or double + bool SerializeNormQuat(bool writeToBitstream, templateType &w, templateType &x, templateType &y, templateType &z); + + /// \brief Bidirectional serialize/deserialize an orthogonal matrix by creating a quaternion, and writing 3 components of the quaternion in 2 bytes each. + /// \details Use 6 bytes instead of 36 + /// Lossy, although the result is renormalized + /// \return true on success, false on failure. + template // templateType for this function must be a float or double + bool SerializeOrthMatrix( + bool writeToBitstream, + templateType &m00, templateType &m01, templateType &m02, + templateType &m10, templateType &m11, templateType &m12, + templateType &m20, templateType &m21, templateType &m22 ); + + /// \brief Bidirectional serialize/deserialize numberToSerialize bits to/from the input. + /// \details Right aligned data means in the case of a partial byte, the bits are aligned + /// from the right (bit 0) rather than the left (as in the normal + /// internal representation) You would set this to true when + /// writing user data, and false when copying bitstream data, such + /// as writing one bitstream to another + /// \param[in] writeToBitstream true to write from your data to this bitstream. False to read from this bitstream and write to your data + /// \param[in] inOutByteArray The data + /// \param[in] numberOfBitsToSerialize The number of bits to write + /// \param[in] rightAlignedBits if true data will be right aligned + /// \return true if \a writeToBitstream is true. true if \a writeToBitstream is false and the read was successful. false if \a writeToBitstream is false and the read was not successful. + bool SerializeBits(bool writeToBitstream, unsigned char* inOutByteArray, const BitSize_t numberOfBitsToSerialize, const bool rightAlignedBits = true ); + + /// \brief Write any integral type to a bitstream. + /// \details Undefine __BITSTREAM_NATIVE_END if you need endian swapping. + /// \param[in] inTemplateVar The value to write + // TODO - RakNet 4 Remove write, use only the WriteRef version, but rename it to Write + template + void Write(templateType inTemplateVar); + template + void WriteRef(const templateType &inTemplateVar); + + /// \brief Write the dereferenced pointer to any integral type to a bitstream. + /// \details Undefine __BITSTREAM_NATIVE_END if you need endian swapping. + /// \param[in] inTemplateVar The value to write + template + void WritePtr(templateType *inTemplateVar); + + /// \brief Write any integral type to a bitstream. + /// \details If the current value is different from the last value + /// the current value will be written. Otherwise, a single bit will be written + /// \param[in] currentValue The current value to write + /// \param[in] lastValue The last value to compare against + template + void WriteDelta(templateType currentValue, templateType lastValue); + + /// \brief WriteDelta when you don't know what the last value is, or there is no last value. + /// \param[in] currentValue The current value to write + template + void WriteDelta(templateType currentValue); + + /// \brief Write any integral type to a bitstream. + /// \details Undefine __BITSTREAM_NATIVE_END if you need endian swapping. + /// If you are not using __BITSTREAM_NATIVE_END the opposite is true for types larger than 1 byte + /// For floating point, this is lossy, using 2 bytes for a float and 4 for a double. The range must be between -1 and +1. + /// For non-floating point, this is lossless, but only has benefit if you use less than half the range of the type + /// \param[in] inTemplateVar The value to write + template + void WriteCompressed(templateType inTemplateVar); + + /// \brief Write any integral type to a bitstream. + /// \details If the current value is different from the last value + /// the current value will be written. Otherwise, a single bit will be written + /// For floating point, this is lossy, using 2 bytes for a float and 4 for a double. The range must be between -1 and +1. + /// For non-floating point, this is lossless, but only has benefit if you use less than half the range of the type + /// If you are not using __BITSTREAM_NATIVE_END the opposite is true for types larger than 1 byte + /// \param[in] currentValue The current value to write + /// \param[in] lastValue The last value to compare against + template + void WriteCompressedDelta(templateType currentValue, templateType lastValue); + + /// \brief Save as WriteCompressedDelta(templateType currentValue, templateType lastValue) when we have an unknown second parameter + template + void WriteCompressedDelta(templateType currentValue); + + /// \brief Read any integral type from a bitstream. + /// \details Define __BITSTREAM_NATIVE_END if you need endian swapping. + /// \param[in] outTemplateVar The value to read + /// \return true on success, false on failure. + template + bool Read(templateType &outTemplateVar); + + /// \brief Read into a pointer to any integral type from a bitstream. + /// \details Define __BITSTREAM_NATIVE_END if you need endian swapping. + /// \param[in] outTemplateVar The value to read + /// \return true on success, false on failure. + template + bool ReadPtr(templateType *outTemplateVar); + + /// \brief Read any integral type from a bitstream. + /// \details If the written value differed from the value compared against in the write function, + /// var will be updated. Otherwise it will retain the current value. + /// ReadDelta is only valid from a previous call to WriteDelta + /// \param[in] outTemplateVar The value to read + /// \return true on success, false on failure. + template + bool ReadDelta(templateType &outTemplateVar); + + /// \brief Read any integral type from a bitstream. + /// \details Undefine __BITSTREAM_NATIVE_END if you need endian swapping. + /// For floating point, this is lossy, using 2 bytes for a float and 4 for a double. The range must be between -1 and +1. + /// For non-floating point, this is lossless, but only has benefit if you use less than half the range of the type + /// If you are not using __BITSTREAM_NATIVE_END the opposite is true for types larger than 1 byte + /// \param[in] outTemplateVar The value to read + /// \return true on success, false on failure. + template + bool ReadCompressed(templateType &outTemplateVar); + + /// \brief Read any integral type from a bitstream. + /// \details If the written value differed from the value compared against in the write function, + /// var will be updated. Otherwise it will retain the current value. + /// the current value will be updated. + /// For floating point, this is lossy, using 2 bytes for a float and 4 for a double. The range must be between -1 and +1. + /// For non-floating point, this is lossless, but only has benefit if you use less than half the range of the type + /// If you are not using __BITSTREAM_NATIVE_END the opposite is true for types larger than 1 byte + /// ReadCompressedDelta is only valid from a previous call to WriteDelta + /// \param[in] outTemplateVar The value to read + /// \return true on success, false on failure. + template + bool ReadCompressedDelta(templateType &outTemplateVar); + + /// \brief Read one bitstream to another. + /// \param[in] numberOfBits bits to read + /// \param bitStream the bitstream to read into from + /// \return true on success, false on failure. + bool Read( BitStream *bitStream, BitSize_t numberOfBits ); + bool Read( BitStream *bitStream ); + bool Read( BitStream &bitStream, BitSize_t numberOfBits ); + bool Read( BitStream &bitStream ); + + /// \brief Write an array or casted stream or raw data. This does NOT do endian swapping. + /// \param[in] inputByteArray a byte buffer + /// \param[in] numberOfBytes the size of \a input in bytes + void Write( const char* inputByteArray, const unsigned int numberOfBytes ); + + /// \brief Write one bitstream to another. + /// \param[in] numberOfBits bits to write + /// \param bitStream the bitstream to copy from + void Write( BitStream *bitStream, BitSize_t numberOfBits ); + void Write( BitStream *bitStream ); + void Write( BitStream &bitStream, BitSize_t numberOfBits ); + void Write( BitStream &bitStream );\ + + /// \brief Write a float into 2 bytes, spanning the range between \a floatMin and \a floatMax + /// \param[in] x The float to write + /// \param[in] floatMin Predetermined minimum value of f + /// \param[in] floatMax Predetermined maximum value of f + void WriteFloat16( float x, float floatMin, float floatMax ); + + /// \brief Write a normalized 3D vector, using (at most) 4 bytes + 3 bits instead of 12-24 bytes. + /// \details Will further compress y or z axis aligned vectors. + /// Accurate to 1/32767.5. + /// \param[in] x x + /// \param[in] y y + /// \param[in] z z + template // templateType for this function must be a float or double + void WriteNormVector( templateType x, templateType y, templateType z ); + + /// \brief Write a vector, using 10 bytes instead of 12. + /// \details Loses accuracy to about 3/10ths and only saves 2 bytes, + /// so only use if accuracy is not important. + /// \param[in] x x + /// \param[in] y y + /// \param[in] z z + template // templateType for this function must be a float or double + void WriteVector( templateType x, templateType y, templateType z ); + + /// \brief Write a normalized quaternion in 6 bytes + 4 bits instead of 16 bytes. Slightly lossy. + /// \param[in] w w + /// \param[in] x x + /// \param[in] y y + /// \param[in] z z + template // templateType for this function must be a float or double + void WriteNormQuat( templateType w, templateType x, templateType y, templateType z); + + /// \brief Write an orthogonal matrix by creating a quaternion, and writing 3 components of the quaternion in 2 bytes each. + /// \details Use 6 bytes instead of 36 + /// Lossy, although the result is renormalized + template // templateType for this function must be a float or double + void WriteOrthMatrix( + templateType m00, templateType m01, templateType m02, + templateType m10, templateType m11, templateType m12, + templateType m20, templateType m21, templateType m22 ); + + /// \brief Read an array or casted stream of byte. + /// \details The array is raw data. There is no automatic endian conversion with this function + /// \param[in] output The result byte array. It should be larger than @em numberOfBytes. + /// \param[in] numberOfBytes The number of byte to read + /// \return true on success false if there is some missing bytes. + bool Read( char* output, const unsigned int numberOfBytes ); + + /// \brief Read a float into 2 bytes, spanning the range between \a floatMin and \a floatMax + /// \param[in] outFloat The float to read + /// \param[in] floatMin Predetermined minimum value of f + /// \param[in] floatMax Predetermined maximum value of f + bool ReadFloat16( float &outFloat, float floatMin, float floatMax ); + + /// \brief Read a normalized 3D vector, using (at most) 4 bytes + 3 bits instead of 12-24 bytes. + /// \details Will further compress y or z axis aligned vectors. + /// Accurate to 1/32767.5. + /// \param[in] x x + /// \param[in] y y + /// \param[in] z z + /// \return true on success, false on failure. + template // templateType for this function must be a float or double + bool ReadNormVector( templateType &x, templateType &y, templateType &z ); + + /// \brief Read 3 floats or doubles, using 10 bytes, where those float or doubles comprise a vector. + /// \details Loses accuracy to about 3/10ths and only saves 2 bytes, + /// so only use if accuracy is not important. + /// \param[in] x x + /// \param[in] y y + /// \param[in] z z + /// \return true on success, false on failure. + template // templateType for this function must be a float or double + bool ReadVector( templateType &x, templateType &y, templateType &z ); + + /// \brief Read a normalized quaternion in 6 bytes + 4 bits instead of 16 bytes. + /// \param[in] w w + /// \param[in] x x + /// \param[in] y y + /// \param[in] z z + /// \return true on success, false on failure. + template // templateType for this function must be a float or double + bool ReadNormQuat( templateType &w, templateType &x, templateType &y, templateType &z); + + /// \brief Read an orthogonal matrix from a quaternion, reading 3 components of the quaternion in 2 bytes each and extrapolatig the 4th. + /// \details Use 6 bytes instead of 36 + /// Lossy, although the result is renormalized + /// \return true on success, false on failure. + template // templateType for this function must be a float or double + bool ReadOrthMatrix( + templateType &m00, templateType &m01, templateType &m02, + templateType &m10, templateType &m11, templateType &m12, + templateType &m20, templateType &m21, templateType &m22 ); + + /// \brief Sets the read pointer back to the beginning of your data. + void ResetReadPointer( void ); + + /// \brief Sets the write pointer back to the beginning of your data. + void ResetWritePointer( void ); + + /// \brief This is good to call when you are done with the stream to make + /// sure you didn't leave any data left over void + void AssertStreamEmpty( void ); + + /// \brief RAKNET_DEBUG_PRINTF the bits in the stream. Great for debugging. + void PrintBits( char *out ) const; + void PrintBits( void ) const; + void PrintHex( char *out ) const; + void PrintHex( void ) const; + + /// \brief Ignore data we don't intend to read + /// \param[in] numberOfBits The number of bits to ignore + void IgnoreBits( const BitSize_t numberOfBits ); + + /// \brief Ignore data we don't intend to read + /// \param[in] numberOfBits The number of bytes to ignore + void IgnoreBytes( const unsigned int numberOfBytes ); + + /// \brief Move the write pointer to a position on the array. + /// \param[in] offset the offset from the start of the array. + /// \attention + /// \details Dangerous if you don't know what you are doing! + /// For efficiency reasons you can only write mid-stream if your data is byte aligned. + void SetWriteOffset( const BitSize_t offset ); + + /// \brief Returns the length in bits of the stream + inline BitSize_t GetNumberOfBitsUsed( void ) const {return GetWriteOffset();} + inline BitSize_t GetWriteOffset( void ) const {return numberOfBitsUsed;} + + /// \brief Returns the length in bytes of the stream + inline BitSize_t GetNumberOfBytesUsed( void ) const {return BITS_TO_BYTES( numberOfBitsUsed );} + + /// \brief Returns the number of bits into the stream that we have read + inline BitSize_t GetReadOffset( void ) const {return readOffset;} + + /// \brief Sets the read bit index + void SetReadOffset( const BitSize_t newReadOffset ) {readOffset=newReadOffset;} + + /// \brief Returns the number of bits left in the stream that haven't been read + inline BitSize_t GetNumberOfUnreadBits( void ) const {return numberOfBitsUsed - readOffset;} + + /// \brief Makes a copy of the internal data for you \a _data will point to + /// the stream. Partial bytes are left aligned. + /// \param[out] _data The allocated copy of GetData() + /// \return The length in bits of the stream. + BitSize_t CopyData( unsigned char** _data ) const; + + /// \internal + /// Set the stream to some initial data. + void SetData( unsigned char *inByteArray ); + + /// Gets the data that BitStream is writing to / reading from. + /// Partial bytes are left aligned. + /// \return A pointer to the internal state + inline unsigned char* GetData( void ) const {return data;} + + /// \brief Write numberToWrite bits from the input source. + /// \details Right aligned data means in the case of a partial byte, the bits are aligned + /// from the right (bit 0) rather than the left (as in the normal + /// internal representation) You would set this to true when + /// writing user data, and false when copying bitstream data, such + /// as writing one bitstream to another. + /// \param[in] inByteArray The data + /// \param[in] numberOfBitsToWrite The number of bits to write + /// \param[in] rightAlignedBits if true data will be right aligned + void WriteBits( const unsigned char* inByteArray, BitSize_t numberOfBitsToWrite, const bool rightAlignedBits = true ); + + /// \brief Align the bitstream to the byte boundary and then write the + /// specified number of bits. + /// \details This is faster than WriteBits but + /// wastes the bits to do the alignment and requires you to call + /// ReadAlignedBits at the corresponding read position. + /// \param[in] inByteArray The data + /// \param[in] numberOfBytesToWrite The size of input. + void WriteAlignedBytes( const unsigned char *inByteArray, const unsigned int numberOfBytesToWrite ); + + // Endian swap bytes already in the bitstream + void EndianSwapBytes( int byteOffset, int length ); + + /// \brief Aligns the bitstream, writes inputLength, and writes input. Won't write beyond maxBytesToWrite + /// \param[in] inByteArray The data + /// \param[in] inputLength The size of input. + /// \param[in] maxBytesToWrite Max bytes to write + void WriteAlignedBytesSafe( const char *inByteArray, const unsigned int inputLength, const unsigned int maxBytesToWrite ); + + /// \brief Read bits, starting at the next aligned bits. + /// \details Note that the modulus 8 starting offset of the sequence must be the same as + /// was used with WriteBits. This will be a problem with packet + /// coalescence unless you byte align the coalesced packets. + /// \param[in] inOutByteArray The byte array larger than @em numberOfBytesToRead + /// \param[in] numberOfBytesToRead The number of byte to read from the internal state + /// \return true if there is enough byte. + bool ReadAlignedBytes( unsigned char *inOutByteArray, const unsigned int numberOfBytesToRead ); + + /// \brief Reads what was written by WriteAlignedBytesSafe. + /// \param[in] inOutByteArray The data + /// \param[in] maxBytesToRead Maximum number of bytes to read + /// \return true on success, false on failure. + bool ReadAlignedBytesSafe( char *inOutByteArray, int &inputLength, const int maxBytesToRead ); + bool ReadAlignedBytesSafe( char *inOutByteArray, unsigned int &inputLength, const unsigned int maxBytesToRead ); + + /// \brief Same as ReadAlignedBytesSafe() but allocates the memory for you using new, rather than assuming it is safe to write to + /// \param[in] outByteArray outByteArray will be deleted if it is not a pointer to 0 + /// \return true on success, false on failure. + bool ReadAlignedBytesSafeAlloc( char **outByteArray, int &inputLength, const unsigned int maxBytesToRead ); + bool ReadAlignedBytesSafeAlloc( char **outByteArray, unsigned int &inputLength, const unsigned int maxBytesToRead ); + + /// \brief Align the next write and/or read to a byte boundary. + /// \details This can be used to 'waste' bits to byte align for efficiency reasons It + /// can also be used to force coalesced bitstreams to start on byte + /// boundaries so so WriteAlignedBits and ReadAlignedBits both + /// calculate the same offset when aligning. + inline void AlignWriteToByteBoundary( void ) {numberOfBitsUsed += 8 - ( (( numberOfBitsUsed - 1 ) & 7) + 1 );} + + /// \brief Align the next write and/or read to a byte boundary. + /// \details This can be used to 'waste' bits to byte align for efficiency reasons It + /// can also be used to force coalesced bitstreams to start on byte + /// boundaries so so WriteAlignedBits and ReadAlignedBits both + /// calculate the same offset when aligning. + inline void AlignReadToByteBoundary( void ) {readOffset += 8 - ( (( readOffset - 1 ) & 7 ) + 1 );} + + /// \brief Read \a numberOfBitsToRead bits to the output source. + /// \details alignBitsToRight should be set to true to convert internal + /// bitstream data to userdata. It should be false if you used + /// WriteBits with rightAlignedBits false + /// \param[in] inOutByteArray The resulting bits array + /// \param[in] numberOfBitsToRead The number of bits to read + /// \param[in] alignBitsToRight if true bits will be right aligned. + /// \return true if there is enough bits to read + bool ReadBits( unsigned char *inOutByteArray, BitSize_t numberOfBitsToRead, const bool alignBitsToRight = true ); + + /// \brief Write a 0 + void Write0( void ); + + /// \brief Write a 1 + void Write1( void ); + + /// \brief Reads 1 bit and returns true if that bit is 1 and false if it is 0. + bool ReadBit( void ); + + /// \brief If we used the constructor version with copy data off, this + /// *makes sure it is set to on and the data pointed to is copied. + void AssertCopyData( void ); + + /// \brief Use this if you pass a pointer copy to the constructor + /// *(_copyData==false) and want to overallocate to prevent + /// reallocation. + void SetNumberOfBitsAllocated( const BitSize_t lengthInBits ); + + /// \brief Reallocates (if necessary) in preparation of writing numberOfBitsToWrite + void AddBitsAndReallocate( const BitSize_t numberOfBitsToWrite ); + + /// \internal + /// \return How many bits have been allocated internally + BitSize_t GetNumberOfBitsAllocated(void) const; + + /// \brief Read strings, non reference. + bool Read(char *varString); + bool Read(unsigned char *varString); + + /// Write zeros until the bitstream is filled up to \a bytes + void PadWithZeroToByteLength( unsigned int bytes ); + + /// \internal Unrolled inner loop, for when performance is critical + void WriteAlignedVar8(const char *inByteArray); + /// \internal Unrolled inner loop, for when performance is critical + bool ReadAlignedVar8(char *inOutByteArray); + /// \internal Unrolled inner loop, for when performance is critical + void WriteAlignedVar16(const char *inByteArray); + /// \internal Unrolled inner loop, for when performance is critical + bool ReadAlignedVar16(char *inOutByteArray); + /// \internal Unrolled inner loop, for when performance is critical + void WriteAlignedVar32(const char *inByteArray); + /// \internal Unrolled inner loop, for when performance is critical + bool ReadAlignedVar32(char *inOutByteArray); + + /// ---- Member function template specialization declarations ---- + // Used for VC7 +#if defined(_MSC_VER) && _MSC_VER == 1300 + /// Write a bool to a bitstream. + /// \param[in] var The value to write + template <> + void WriteRef(const bool &var); + + /// Write a systemAddress to a bitstream + /// \param[in] var The value to write + template <> + void WriteRef(const SystemAddress &var); + + /// Write a uint24_t to a bitstream + /// \param[in] var The value to write + template <> + void WriteRef(const uint24_t &var); + + /// Write a RakNetGUID to a bitsteam + /// \param[in] var The value to write + template <> + void WriteRef(const RakNetGuid &var); + + /// Write an networkID to a bitstream + /// \param[in] var The value to write + template <> + void WriteRef(const NetworkID &var); + + /// Write a string to a bitstream + /// \param[in] var The value to write + template <> + void WriteRef(const char* const &var); + template <> + void WriteRef(const unsigned char* const &var); + template <> + void WriteRef(char* const &var); + template <> + void WriteRef(unsigned char* const &var); + template <> + void WriteRef(const RakString &var); + + /// \brief Write a systemAddress. + /// \details If the current value is different from the last value + /// the current value will be written. Otherwise, a single bit will be written + /// \param[in] currentValue The current value to write + /// \param[in] lastValue The last value to compare against + template <> + void WriteDelta(SystemAddress currentValue, SystemAddress lastValue); + + template <> + void WriteDelta(uint24_t currentValue, uint24_t lastValue); + + template <> + void WriteDelta(RakNetGUID currentValue, RakNetGUID lastValue); + + /// \brief Write an networkID. + /// \details If the current value is different from the last value + /// the current value will be written. Otherwise, a single bit will be written + /// \param[in] currentValue The current value to write + /// \param[in] lastValue The last value to compare against + template <> + void WriteDelta(NetworkID currentValue, NetworkID lastValue); + + /// \brief Write a bool delta. + /// \details Same thing as just calling Write + /// \param[in] currentValue The current value to write + /// \param[in] lastValue The last value to compare against + template <> + void WriteDelta(bool currentValue, bool lastValue); + + template <> + void WriteCompressed(SystemAddress var); + + template <> + void WriteCompressed(uint24_t var); + + template <> + void WriteCompressed(RakNetGUID var); + + template <> + void WriteCompressed(NetworkID var); + + template <> + void WriteCompressed(bool var); + + /// For values between -1 and 1 + template <> + void WriteCompressed(float var); + + /// For values between -1 and 1 + template <> + void WriteCompressed(double var); + + /// Compressed string + template <> + void WriteCompressed(const char* var); + template <> + void WriteCompressed(const unsigned char* var); + template <> + void WriteCompressed(char* var); + template <> + void WriteCompressed(unsigned char* var); + template <> + void WriteCompressed(RakString var); + + /// \brief Write a bool delta. + /// \details Same thing as just calling Write + /// \param[in] currentValue The current value to write + /// \param[in] lastValue The last value to compare against + template <> + void WriteCompressedDelta(bool currentValue, bool lastValue); + + /// \brief Save as WriteCompressedDelta(bool currentValue, templateType lastValue) + /// when we have an unknown second bool + template <> + void WriteCompressedDelta(bool currentValue); + + /// \brief Read a bool from a bitstream. + /// \param[in] var The value to read + /// \return true on success, false on failure. + template <> + bool Read(bool &var); + + /// \brief Read a systemAddress from a bitstream. + /// \param[in] var The value to read + /// \return true on success, false on failure. + template <> + bool Read(SystemAddress &var); + + template <> + bool Read(uint24_t &var); + + template <> + bool Read(RakNetGUID &var); + + /// \brief Read an NetworkID from a bitstream. + /// \param[in] var The value to read + /// \return true on success, false on failure. + template <> + bool Read(NetworkID &var); + + /// \brief Read a String from a bitstream. + /// \param[in] var The value to read + /// \return true on success, false on failure. + template <> + bool Read(char *&var); + template <> + bool Read(unsigned char *&var); + template <> + bool Read(RakString &var); + + /// \brief Read a bool from a bitstream. + /// \param[in] var The value to read + /// \return true on success, false on failure. + template <> + bool ReadDelta(bool &var); + + template <> + bool ReadCompressed(SystemAddress &var); + + template <> + bool ReadCompressed(uint24_t &var); + + template <> + bool ReadCompressed(RakNetGUID &var); + + template <> + bool ReadCompressed(NetworkID &var); + + template <> + bool ReadCompressed(bool &var); + + template <> + bool ReadCompressed(float &var); + + /// For values between -1 and 1 + /// \return true on success, false on failure. + template <> + bool ReadCompressed(double &var); + + template <> + bool ReadCompressed(char* &var); + template <> + bool ReadCompressed(unsigned char *&var); + template <> + bool ReadCompressed(RakString &var); + + /// \brief Read a bool from a bitstream. + /// \param[in] var The value to read + /// \return true on success, false on failure. + template <> + bool ReadCompressedDelta(bool &var); +#endif + + inline static bool DoEndianSwap(void) { +#ifndef __BITSTREAM_NATIVE_END + return IsNetworkOrder()==false; +#else + return false; +#endif + } + inline static bool IsBigEndian(void) + { + return IsNetworkOrder(); + } + inline static bool IsNetworkOrder(void) {static const bool r = IsNetworkOrderInternal(); return r;} + // Not inline, won't compile on PC due to winsock include errors + static bool IsNetworkOrderInternal(void); + static void ReverseBytes(unsigned char *inByteArray, unsigned char *inOutByteArray, const unsigned int length); + static void ReverseBytesInPlace(unsigned char *inOutData,const unsigned int length); + + private: + + BitStream( const BitStream &invalid) { + (void) invalid; + RakAssert(0); + } + + /// \brief Assume the input source points to a native type, compress and write it. + void WriteCompressed( const unsigned char* inByteArray, const unsigned int size, const bool unsignedData ); + + /// \brief Assume the input source points to a compressed native type. Decompress and read it. + bool ReadCompressed( unsigned char* inOutByteArray, const unsigned int size, const bool unsignedData ); + + + BitSize_t numberOfBitsUsed; + + BitSize_t numberOfBitsAllocated; + + BitSize_t readOffset; + + unsigned char *data; + + /// true if the internal buffer is copy of the data passed to the constructor + bool copyData; + + /// BitStreams that use less than BITSTREAM_STACK_ALLOCATION_SIZE use the stack, rather than the heap to store data. It switches over if BITSTREAM_STACK_ALLOCATION_SIZE is exceeded + unsigned char stackData[BITSTREAM_STACK_ALLOCATION_SIZE]; + }; + + template + inline bool BitStream::Serialize(bool writeToBitstream, templateType &inOutTemplateVar) + { + if (writeToBitstream) + Write(inOutTemplateVar); + else + return Read(inOutTemplateVar); + return true; + } + + template + inline bool BitStream::SerializeDelta(bool writeToBitstream, templateType &inOutCurrentValue, templateType lastValue) + { + if (writeToBitstream) + WriteDelta(inOutCurrentValue, lastValue); + else + return ReadDelta(inOutCurrentValue); + return true; + } + + template + inline bool BitStream::SerializeDelta(bool writeToBitstream, templateType &inOutCurrentValue) + { + if (writeToBitstream) + WriteDelta(inOutCurrentValue); + else + return ReadDelta(inOutCurrentValue); + return true; + } + + template + inline bool BitStream::SerializeCompressed(bool writeToBitstream, templateType &inOutTemplateVar) + { + if (writeToBitstream) + WriteCompressed(inOutTemplateVar); + else + return ReadCompressed(inOutTemplateVar); + return true; + } + + template + inline bool BitStream::SerializeCompressedDelta(bool writeToBitstream, templateType &inOutCurrentValue, templateType lastValue) + { + if (writeToBitstream) + WriteCompressedDelta(inOutCurrentValue,lastValue); + else + return ReadCompressedDelta(inOutCurrentValue); + return true; + } +//Stoppedhere + template + inline bool BitStream::SerializeCompressedDelta(bool writeToBitstream, templateType &inOutCurrentValue) + { + if (writeToBitstream) + WriteCompressedDelta(inOutCurrentValue); + else + return ReadCompressedDelta(inOutCurrentValue); + return true; + } + + inline bool BitStream::Serialize(bool writeToBitstream, char* inOutByteArray, const unsigned int numberOfBytes ) + { + if (writeToBitstream) + Write(inOutByteArray, numberOfBytes); + else + return Read(inOutByteArray, numberOfBytes); + return true; + } + + template + inline bool BitStream::SerializeNormVector(bool writeToBitstream, templateType &x, templateType &y, templateType &z ) + { + if (writeToBitstream) + WriteNormVector(x,y,z); + else + return ReadNormVector(x,y,z); + return true; + } + + template + inline bool BitStream::SerializeVector(bool writeToBitstream, templateType &x, templateType &y, templateType &z ) + { + if (writeToBitstream) + WriteVector(x,y,z); + else + return ReadVector(x,y,z); + return true; + } + + template + inline bool BitStream::SerializeNormQuat(bool writeToBitstream, templateType &w, templateType &x, templateType &y, templateType &z) + { + if (writeToBitstream) + WriteNormQuat(w,x,y,z); + else + return ReadNormQuat(w,x,y,z); + return true; + } + + template + inline bool BitStream::SerializeOrthMatrix( + bool writeToBitstream, + templateType &m00, templateType &m01, templateType &m02, + templateType &m10, templateType &m11, templateType &m12, + templateType &m20, templateType &m21, templateType &m22 ) + { + if (writeToBitstream) + WriteOrthMatrix(m00,m01,m02,m10,m11,m12,m20,m21,m22); + else + return ReadOrthMatrix(m00,m01,m02,m10,m11,m12,m20,m21,m22); + return true; + } + + inline bool BitStream::SerializeBits(bool writeToBitstream, unsigned char* inOutByteArray, const BitSize_t numberOfBitsToSerialize, const bool rightAlignedBits ) + { + if (writeToBitstream) + WriteBits(inOutByteArray,numberOfBitsToSerialize,rightAlignedBits); + else + return ReadBits(inOutByteArray,numberOfBitsToSerialize,rightAlignedBits); + return true; + } + + template + inline void BitStream::Write(templateType inTemplateVar) + { + WriteRef(inTemplateVar); + } + + template + inline void BitStream::WriteRef(const templateType &inTemplateVar) + { +#ifdef _MSC_VER +#pragma warning(disable:4127) // conditional expression is constant +#endif + if (sizeof(inTemplateVar)==1) + WriteBits( ( unsigned char* ) & inTemplateVar, sizeof( templateType ) * 8, true ); + else + { +#ifndef __BITSTREAM_NATIVE_END + if (DoEndianSwap()) + { + unsigned char output[sizeof(templateType)]; + ReverseBytes((unsigned char*)&inTemplateVar, output, sizeof(templateType)); + WriteBits( ( unsigned char* ) output, sizeof(templateType) * 8, true ); + } + else +#endif + WriteBits( ( unsigned char* ) & inTemplateVar, sizeof(templateType) * 8, true ); + } + } + + template + inline void BitStream::WritePtr(templateType *inTemplateVar) + { +#ifdef _MSC_VER +#pragma warning(disable:4127) // conditional expression is constant +#endif + if (sizeof(templateType)==1) + WriteBits( ( unsigned char* ) inTemplateVar, sizeof( templateType ) * 8, true ); + else + { +#ifndef __BITSTREAM_NATIVE_END + if (DoEndianSwap()) + { + unsigned char output[sizeof(templateType)]; + ReverseBytes((unsigned char*) inTemplateVar, output, sizeof(templateType)); + WriteBits( ( unsigned char* ) output, sizeof(templateType) * 8, true ); + } + else +#endif + WriteBits( ( unsigned char* ) inTemplateVar, sizeof(templateType) * 8, true ); + } + } + + /// \brief Write a bool to a bitstream. + /// \param[in] inTemplateVar The value to write + template <> + inline void BitStream::WriteRef(const bool &inTemplateVar) + { + if ( inTemplateVar ) + Write1(); + else + Write0(); + } + + + /// \brief Write a systemAddress to a bitstream. + /// \param[in] inTemplateVar The value to write + template <> + inline void BitStream::WriteRef(const SystemAddress &inTemplateVar) + { + // Hide the address so routers don't modify it + SystemAddress var2=inTemplateVar; + var2.binaryAddress=~inTemplateVar.binaryAddress; + // Don't endian swap the address + WriteBits((unsigned char*)&var2.binaryAddress, sizeof(var2.binaryAddress)*8, true); + Write(var2.port); + } + + template <> + inline void BitStream::WriteRef(const uint24_t &inTemplateVar) + { + AlignWriteToByteBoundary(); + AddBitsAndReallocate(3*8); + + if (IsBigEndian()==false) + { + data[( numberOfBitsUsed >> 3 ) + 0] = ((char *)&inTemplateVar.val)[0]; + data[( numberOfBitsUsed >> 3 ) + 1] = ((char *)&inTemplateVar.val)[1]; + data[( numberOfBitsUsed >> 3 ) + 2] = ((char *)&inTemplateVar.val)[2]; + } + else + { + data[( numberOfBitsUsed >> 3 ) + 0] = ((char *)&inTemplateVar.val)[3]; + data[( numberOfBitsUsed >> 3 ) + 1] = ((char *)&inTemplateVar.val)[2]; + data[( numberOfBitsUsed >> 3 ) + 2] = ((char *)&inTemplateVar.val)[1]; + } + + numberOfBitsUsed+=3*8; + } + + template <> + inline void BitStream::WriteRef(const RakNetGUID &inTemplateVar) + { + Write(inTemplateVar.g); + } + + /// \brief Write an networkID to a bitstream. + /// \param[in] inTemplateVar The value to write + template <> + inline void BitStream::WriteRef(const NetworkID &inTemplateVar) + { +#if NETWORK_ID_SUPPORTS_PEER_TO_PEER==1 + RakAssert(NetworkID::IsPeerToPeerMode()); +// if (NetworkID::IsPeerToPeerMode()) // Use the function rather than directly access the member or DLL users will get an undefined external error + { + if (inTemplateVar.guid!=UNASSIGNED_RAKNET_GUID) + { + Write(true); + Write(inTemplateVar.guid); + } + else + Write(false); + if (inTemplateVar.systemAddress!=UNASSIGNED_SYSTEM_ADDRESS) + { + Write(true); + Write(inTemplateVar.systemAddress); + } + else + Write(false); + } +#endif + /* + Write(var.guid); + Write(var.systemAddress); + */ + Write(inTemplateVar.localSystemAddress); + } + + /// \brief Write a string to a bitstream. + /// \param[in] var The value to write + template <> + inline void BitStream::WriteRef(const RakString &inTemplateVar) + { + inTemplateVar.Serialize(this); + } + template <> + inline void BitStream::WriteRef(const char * const &inStringVar) + { + RakString::Serialize(inStringVar, this); + } + template <> + inline void BitStream::WriteRef(const unsigned char * const &inTemplateVar) + { + Write((const char*)inTemplateVar); + } + template <> + inline void BitStream::WriteRef(char * const &inTemplateVar) + { + Write((const char*)inTemplateVar); + } + template <> + inline void BitStream::WriteRef(unsigned char * const &inTemplateVar) + { + Write((const char*)inTemplateVar); + } + + /// \brief Write any integral type to a bitstream. + /// \details If the current value is different from the last value + /// the current value will be written. Otherwise, a single bit will be written + /// \param[in] currentValue The current value to write + /// \param[in] lastValue The last value to compare against + template + inline void BitStream::WriteDelta(templateType currentValue, templateType lastValue) + { + if (currentValue==lastValue) + { + Write(false); + } + else + { + Write(true); + Write(currentValue); + } + } + + /* + /// \brief Write a systemAddress. + /// \details If the current value is different from the last value + /// the current value will be written. Otherwise, a single bit will be written + /// \param[in] currentValue The current value to write + /// \param[in] lastValue The last value to compare against + template <> + inline void BitStream::WriteDelta(SystemAddress currentValue, SystemAddress lastValue) + { + if (currentValue==lastValue) + { + Write(false); + } + else + { + Write(true); + Write(currentValue); + } + } + + template <> + inline void BitStream::WriteDelta(RakNetGUID currentValue, RakNetGUID lastValue) + { + if (currentValue==lastValue) + { + Write(false); + } + else + { + Write(true); + Write(currentValue); + } + } + + /// \brief Write a systemAddress. + /// \details If the current value is different from the last value + /// the current value will be written. Otherwise, a single bit will be written + /// \param[in] currentValue The current value to write + /// \param[in] lastValue The last value to compare against + template <> + inline void BitStream::WriteDelta(NetworkID currentValue, NetworkID lastValue) + { + if (currentValue==lastValue) + { + Write(false); + } + else + { + Write(true); + Write(currentValue); + } + } + */ + + /// \brief Write a bool delta. Same thing as just calling Write + /// \param[in] currentValue The current value to write + /// \param[in] lastValue The last value to compare against + template <> + inline void BitStream::WriteDelta(bool currentValue, bool lastValue) + { + (void) lastValue; + + Write(currentValue); + } + + /// \brief WriteDelta when you don't know what the last value is, or there is no last value. + /// \param[in] currentValue The current value to write + template + inline void BitStream::WriteDelta(templateType currentValue) + { + Write(true); + Write(currentValue); + } + + /// \brief Write any integral type to a bitstream. + /// \details Undefine __BITSTREAM_NATIVE_END if you need endian swapping. + /// For floating point, this is lossy, using 2 bytes for a float and 4 for a double. The range must be between -1 and +1. + /// For non-floating point, this is lossless, but only has benefit if you use less than half the range of the type + /// If you are not using __BITSTREAM_NATIVE_END the opposite is true for types larger than 1 byte + /// \param[in] inTemplateVar The value to write + template + inline void BitStream::WriteCompressed(templateType inTemplateVar) + { +#ifdef _MSC_VER +#pragma warning(disable:4127) // conditional expression is constant +#endif + if (sizeof(inTemplateVar)==1) + WriteCompressed( ( unsigned char* ) & inTemplateVar, sizeof( templateType ) * 8, true ); + else + { +#ifndef __BITSTREAM_NATIVE_END +#ifdef _MSC_VER +#pragma warning(disable:4244) // '=' : conversion from 'unsigned long' to 'unsigned short', possible loss of data +#endif + + if (DoEndianSwap()) + { + unsigned char output[sizeof(templateType)]; + ReverseBytes((unsigned char*)&inTemplateVar, output, sizeof(templateType)); + WriteCompressed( ( unsigned char* ) output, sizeof(templateType) * 8, true ); + } + else +#endif + WriteCompressed( ( unsigned char* ) & inTemplateVar, sizeof(templateType) * 8, true ); + } + } + + template <> + inline void BitStream::WriteCompressed(SystemAddress inTemplateVar) + { + Write(inTemplateVar); + } + + template <> + inline void BitStream::WriteCompressed(RakNetGUID inTemplateVar) + { + Write(inTemplateVar); + } + + template <> + inline void BitStream::WriteCompressed(uint24_t var) + { + Write(var); + } + + template <> + inline void BitStream::WriteCompressed(NetworkID inTemplateVar) + { + Write(inTemplateVar); + } + + template <> + inline void BitStream::WriteCompressed(bool inTemplateVar) + { + Write(inTemplateVar); + } + + /// For values between -1 and 1 + template <> + inline void BitStream::WriteCompressed(float inTemplateVar) + { + RakAssert(inTemplateVar > -1.01f && inTemplateVar < 1.01f); + if (inTemplateVar < -1.0f) + inTemplateVar=-1.0f; + if (inTemplateVar > 1.0f) + inTemplateVar=1.0f; + Write((unsigned short)((inTemplateVar+1.0f)*32767.5f)); + } + + /// For values between -1 and 1 + template <> + inline void BitStream::WriteCompressed(double inTemplateVar) + { + RakAssert(inTemplateVar > -1.01 && inTemplateVar < 1.01); + if (inTemplateVar < -1.0f) + inTemplateVar=-1.0f; + if (inTemplateVar > 1.0f) + inTemplateVar=1.0f; +#ifdef _DEBUG + RakAssert(sizeof(unsigned long)==4); +#endif + Write((unsigned long)((inTemplateVar+1.0)*2147483648.0)); + } + + /// Compress the string + template <> + inline void BitStream::WriteCompressed(RakString inTemplateVar) + { + inTemplateVar.SerializeCompressed(this,0,false); + } + template <> + inline void BitStream::WriteCompressed(const char * inStringVar) + { + RakString::SerializeCompressed(inStringVar,this,0,false); + } + template <> + inline void BitStream::WriteCompressed(const unsigned char * inTemplateVar) + { + WriteCompressed((const char*) inTemplateVar); + } + template <> + inline void BitStream::WriteCompressed(char * inTemplateVar) + { + WriteCompressed((const char*) inTemplateVar); + } + template <> + inline void BitStream::WriteCompressed(unsigned char * inTemplateVar) + { + WriteCompressed((const char*) inTemplateVar); + } + + /// \brief Write any integral type to a bitstream. + /// \details If the current value is different from the last value + /// the current value will be written. Otherwise, a single bit will be written + /// For floating point, this is lossy, using 2 bytes for a float and 4 for a double. The range must be between -1 and +1. + /// For non-floating point, this is lossless, but only has benefit if you use less than half the range of the type + /// If you are not using __BITSTREAM_NATIVE_END the opposite is true for types larger than 1 byte + /// \param[in] currentValue The current value to write + /// \param[in] lastValue The last value to compare against + template + inline void BitStream::WriteCompressedDelta(templateType currentValue, templateType lastValue) + { + if (currentValue==lastValue) + { + Write(false); + } + else + { + Write(true); + WriteCompressed(currentValue); + } + } + + /// \brief Write a bool delta. Same thing as just calling Write + /// \param[in] currentValue The current value to write + /// \param[in] lastValue The last value to compare against + template <> + inline void BitStream::WriteCompressedDelta(bool currentValue, bool lastValue) + { + (void) lastValue; + + Write(currentValue); + } + + /// \brief Save as WriteCompressedDelta(templateType currentValue, templateType lastValue) + /// when we have an unknown second parameter + template + inline void BitStream::WriteCompressedDelta(templateType currentValue) + { + Write(true); + WriteCompressed(currentValue); + } + + /// \brief Save as WriteCompressedDelta(bool currentValue, templateType lastValue) + /// when we have an unknown second bool + template <> + inline void BitStream::WriteCompressedDelta(bool currentValue) + { + Write(currentValue); + } + + /// \brief Read any integral type from a bitstream. Define __BITSTREAM_NATIVE_END if you need endian swapping. + /// \param[in] outTemplateVar The value to read + template + inline bool BitStream::Read(templateType &outTemplateVar) + { +#ifdef _MSC_VER +#pragma warning(disable:4127) // conditional expression is constant +#endif + if (sizeof(outTemplateVar)==1) + return ReadBits( ( unsigned char* ) &outTemplateVar, sizeof(templateType) * 8, true ); + else + { +#ifndef __BITSTREAM_NATIVE_END +#ifdef _MSC_VER +#pragma warning(disable:4244) // '=' : conversion from 'unsigned long' to 'unsigned short', possible loss of data +#endif + if (DoEndianSwap()) + { + unsigned char output[sizeof(templateType)]; + if (ReadBits( ( unsigned char* ) output, sizeof(templateType) * 8, true )) + { + ReverseBytes(output, (unsigned char*)&outTemplateVar, sizeof(templateType)); + return true; + } + return false; + } + else +#endif + return ReadBits( ( unsigned char* ) & outTemplateVar, sizeof(templateType) * 8, true ); + } + } + + template + inline bool BitStream::ReadPtr(templateType *outTemplateVar) + { +#ifdef _MSC_VER +#pragma warning(disable:4127) // conditional expression is constant +#endif + if (sizeof(templateType)==1) + return ReadBits( ( unsigned char* ) outTemplateVar, sizeof(templateType) * 8, true ); + else + { +#ifndef __BITSTREAM_NATIVE_END +#ifdef _MSC_VER +#pragma warning(disable:4244) // '=' : conversion from 'unsigned long' to 'unsigned short', possible loss of data +#endif + if (DoEndianSwap()) + { + unsigned char output[sizeof(templateType)]; + if (ReadBits( ( unsigned char* ) output, sizeof(templateType) * 8, true )) + { + ReverseBytes(output, (unsigned char*)outTemplateVar, sizeof(templateType)); + return true; + } + return false; + } + else +#endif + return ReadBits( ( unsigned char* ) outTemplateVar, sizeof(templateType) * 8, true ); + } + } + + /// \brief Read a bool from a bitstream. + /// \param[in] outTemplateVar The value to read + template <> + inline bool BitStream::Read(bool &outTemplateVar) + { + if ( readOffset + 1 > numberOfBitsUsed ) + return false; + + if ( data[ readOffset >> 3 ] & ( 0x80 >> ( readOffset & 7 ) ) ) // Is it faster to just write it out here? + outTemplateVar = true; + else + outTemplateVar = false; + + // Has to be on a different line for Mac + readOffset++; + + return true; + } + + /// \brief Read a systemAddress from a bitstream. + /// \param[in] outTemplateVar The value to read + template <> + inline bool BitStream::Read(SystemAddress &outTemplateVar) + { + // Read(var.binaryAddress); + // Don't endian swap the address + ReadBits( ( unsigned char* ) & outTemplateVar.binaryAddress, sizeof(outTemplateVar.binaryAddress) * 8, true ); + // Unhide the IP address, done to prevent routers from changing it + outTemplateVar.binaryAddress=~outTemplateVar.binaryAddress; + return Read(outTemplateVar.port); + } + + template <> + inline bool BitStream::Read(uint24_t &outTemplateVar) + { + AlignReadToByteBoundary(); + if ( readOffset + 3*8 > numberOfBitsUsed ) + return false; + + if (IsBigEndian()==false) + { + ((char *)&outTemplateVar.val)[0]=data[ (readOffset >> 3) + 0]; + ((char *)&outTemplateVar.val)[1]=data[ (readOffset >> 3) + 1]; + ((char *)&outTemplateVar.val)[2]=data[ (readOffset >> 3) + 2]; + ((char *)&outTemplateVar.val)[3]=0; + } + else + { + + ((char *)&outTemplateVar.val)[3]=data[ (readOffset >> 3) + 0]; + ((char *)&outTemplateVar.val)[2]=data[ (readOffset >> 3) + 1]; + ((char *)&outTemplateVar.val)[1]=data[ (readOffset >> 3) + 2]; + ((char *)&outTemplateVar.val)[0]=0; + } + + readOffset+=3*8; + return true; + } + + template <> + inline bool BitStream::Read(RakNetGUID &outTemplateVar) + { + return Read(outTemplateVar.g); + } + + /// \brief Read an networkID from a bitstream. + /// \param[in] outTemplateVar The value to read + template <> + inline bool BitStream::Read(NetworkID &outTemplateVar) + { +#if NETWORK_ID_SUPPORTS_PEER_TO_PEER==1 + RakAssert(NetworkID::IsPeerToPeerMode()); + //if (NetworkID::IsPeerToPeerMode()) // Use the function rather than directly access the member or DLL users will get an undefined external error + { + bool hasGuid, hasSystemAddress; + Read(hasGuid); + if (hasGuid) + Read(outTemplateVar.guid); + else + outTemplateVar.guid=UNASSIGNED_RAKNET_GUID; + Read(hasSystemAddress); + if (hasSystemAddress) + Read(outTemplateVar.systemAddress); + else + outTemplateVar.systemAddress=UNASSIGNED_SYSTEM_ADDRESS; + } +#endif + /* + Read(var.guid); + Read(var.systemAddress); + */ + return Read(outTemplateVar.localSystemAddress); + } + + /// \brief Read an networkID from a bitstream. + /// \param[in] outTemplateVar The value to read + template <> + inline bool BitStream::Read(RakString &outTemplateVar) + { + return outTemplateVar.Deserialize(this); + } + template <> + inline bool BitStream::Read(char *&varString) + { + return RakString::Deserialize(varString,this); + } + template <> + inline bool BitStream::Read(unsigned char *&varString) + { + return RakString::Deserialize((char*) varString,this); + } + + /// \brief Read any integral type from a bitstream. + /// \details If the written value differed from the value compared against in the write function, + /// var will be updated. Otherwise it will retain the current value. + /// ReadDelta is only valid from a previous call to WriteDelta + /// \param[in] outTemplateVar The value to read + template + inline bool BitStream::ReadDelta(templateType &outTemplateVar) + { + bool dataWritten; + bool success; + success=Read(dataWritten); + if (dataWritten) + success=Read(outTemplateVar); + return success; + } + + /// \brief Read a bool from a bitstream. + /// \param[in] outTemplateVar The value to read + template <> + inline bool BitStream::ReadDelta(bool &outTemplateVar) + { + return Read(outTemplateVar); + } + + /// \brief Read any integral type from a bitstream. + /// \details Undefine __BITSTREAM_NATIVE_END if you need endian swapping. + /// For floating point, this is lossy, using 2 bytes for a float and 4 for a double. The range must be between -1 and +1. + /// For non-floating point, this is lossless, but only has benefit if you use less than half the range of the type + /// If you are not using __BITSTREAM_NATIVE_END the opposite is true for types larger than 1 byte + /// \param[in] outTemplateVar The value to read + template + inline bool BitStream::ReadCompressed(templateType &outTemplateVar) + { +#ifdef _MSC_VER +#pragma warning(disable:4127) // conditional expression is constant +#endif + if (sizeof(outTemplateVar)==1) + return ReadCompressed( ( unsigned char* ) &outTemplateVar, sizeof(templateType) * 8, true ); + else + { +#ifndef __BITSTREAM_NATIVE_END + if (DoEndianSwap()) + { + unsigned char output[sizeof(templateType)]; + if (ReadCompressed( ( unsigned char* ) output, sizeof(templateType) * 8, true )) + { + ReverseBytes(output, (unsigned char*)&outTemplateVar, sizeof(templateType)); + return true; + } + return false; + } + else +#endif + return ReadCompressed( ( unsigned char* ) & outTemplateVar, sizeof(templateType) * 8, true ); + } + } + + template <> + inline bool BitStream::ReadCompressed(SystemAddress &outTemplateVar) + { + return Read(outTemplateVar); + } + + template <> + inline bool BitStream::ReadCompressed(uint24_t &outTemplateVar) + { + return Read(outTemplateVar); + } + + template <> + inline bool BitStream::ReadCompressed(RakNetGUID &outTemplateVar) + { + return Read(outTemplateVar); + } + + template <> + inline bool BitStream::ReadCompressed(NetworkID &outTemplateVar) + { + return Read(outTemplateVar); + } + + template <> + inline bool BitStream::ReadCompressed(bool &outTemplateVar) + { + return Read(outTemplateVar); + } + + /// For values between -1 and 1 + template <> + inline bool BitStream::ReadCompressed(float &outTemplateVar) + { + unsigned short compressedFloat; + if (Read(compressedFloat)) + { + outTemplateVar = ((float)compressedFloat / 32767.5f - 1.0f); + return true; + } + return false; + } + + /// For values between -1 and 1 + template <> + inline bool BitStream::ReadCompressed(double &outTemplateVar) + { + unsigned long compressedFloat; + if (Read(compressedFloat)) + { + outTemplateVar = ((double)compressedFloat / 2147483648.0 - 1.0); + return true; + } + return false; + } + + /// For strings + template <> + inline bool BitStream::ReadCompressed(RakString &outTemplateVar) + { + return outTemplateVar.DeserializeCompressed(this,false); + } + template <> + inline bool BitStream::ReadCompressed(char *&outTemplateVar) + { + return RakString::DeserializeCompressed(outTemplateVar,this,false); + } + template <> + inline bool BitStream::ReadCompressed(unsigned char *&outTemplateVar) + { + return RakString::DeserializeCompressed((char*) outTemplateVar,this,false); + } + + /// \brief Read any integral type from a bitstream. + /// \details If the written value differed from the value compared against in the write function, + /// var will be updated. Otherwise it will retain the current value. + /// the current value will be updated. + /// For floating point, this is lossy, using 2 bytes for a float and 4 for a double. The range must be between -1 and +1. + /// For non-floating point, this is lossless, but only has benefit if you use less than half the range of the type + /// If you are not using __BITSTREAM_NATIVE_END the opposite is true for types larger than 1 byte + /// ReadCompressedDelta is only valid from a previous call to WriteDelta + /// \param[in] outTemplateVar The value to read + template + inline bool BitStream::ReadCompressedDelta(templateType &outTemplateVar) + { + bool dataWritten; + bool success; + success=Read(dataWritten); + if (dataWritten) + success=ReadCompressed(outTemplateVar); + return success; + } + + /// \brief Read a bool from a bitstream. + /// \param[in] outTemplateVar The value to read + template <> + inline bool BitStream::ReadCompressedDelta(bool &outTemplateVar) + { + return Read(outTemplateVar); + } + + template // templateType for this function must be a float or double + void BitStream::WriteNormVector( templateType x, templateType y, templateType z ) + { +#ifdef _DEBUG + RakAssert(x <= 1.01 && y <= 1.01 && z <= 1.01 && x >= -1.01 && y >= -1.01 && z >= -1.01); +#endif + + WriteFloat16((float)x,-1.0f,1.0f); + WriteFloat16((float)y,-1.0f,1.0f); + WriteFloat16((float)z,-1.0f,1.0f); + } + + template // templateType for this function must be a float or double + void BitStream::WriteVector( templateType x, templateType y, templateType z ) + { + templateType magnitude = sqrt(x * x + y * y + z * z); + Write((float)magnitude); + if (magnitude > 0.00001f) + { + WriteCompressed((float)(x/magnitude)); + WriteCompressed((float)(y/magnitude)); + WriteCompressed((float)(z/magnitude)); + // Write((unsigned short)((x/magnitude+1.0f)*32767.5f)); + // Write((unsigned short)((y/magnitude+1.0f)*32767.5f)); + // Write((unsigned short)((z/magnitude+1.0f)*32767.5f)); + } + } + + template // templateType for this function must be a float or double + void BitStream::WriteNormQuat( templateType w, templateType x, templateType y, templateType z) + { + Write((bool)(w<0.0)); + Write((bool)(x<0.0)); + Write((bool)(y<0.0)); + Write((bool)(z<0.0)); + Write((unsigned short)(fabs(x)*65535.0)); + Write((unsigned short)(fabs(y)*65535.0)); + Write((unsigned short)(fabs(z)*65535.0)); + // Leave out w and calculate it on the target + } + + template // templateType for this function must be a float or double + void BitStream::WriteOrthMatrix( + templateType m00, templateType m01, templateType m02, + templateType m10, templateType m11, templateType m12, + templateType m20, templateType m21, templateType m22 ) + { + + double qw; + double qx; + double qy; + double qz; + + // Convert matrix to quat + // http://www.euclideanspace.com/maths/geometry/rotations/conversions/matrixToQuaternion/ + float sum; + sum = 1 + m00 + m11 + m22; + if (sum < 0.0f) sum=0.0f; + qw = sqrt( sum ) / 2; + sum = 1 + m00 - m11 - m22; + if (sum < 0.0f) sum=0.0f; + qx = sqrt( sum ) / 2; + sum = 1 - m00 + m11 - m22; + if (sum < 0.0f) sum=0.0f; + qy = sqrt( sum ) / 2; + sum = 1 - m00 - m11 + m22; + if (sum < 0.0f) sum=0.0f; + qz = sqrt( sum ) / 2; + if (qw < 0.0) qw=0.0; + if (qx < 0.0) qx=0.0; + if (qy < 0.0) qy=0.0; + if (qz < 0.0) qz=0.0; + qx = _copysign( (double) qx, (double) (m21 - m12) ); + qy = _copysign( (double) qy, (double) (m02 - m20) ); + qz = _copysign( (double) qz, (double) (m10 - m01) ); + + WriteNormQuat(qw,qx,qy,qz); + } + + template // templateType for this function must be a float or double + bool BitStream::ReadNormVector( templateType &x, templateType &y, templateType &z ) + { + float xIn,yIn,zIn; + ReadFloat16(xIn,-1.0f,1.0f); + ReadFloat16(yIn,-1.0f,1.0f); + ReadFloat16(zIn,-1.0f,1.0f); + x=xIn; + y=yIn; + z=zIn; + return true; + } + + template // templateType for this function must be a float or double + bool BitStream::ReadVector( templateType &x, templateType &y, templateType &z ) + { + float magnitude; + //unsigned short sx,sy,sz; + if (!Read(magnitude)) + return false; + if (magnitude>0.00001f) + { + // Read(sx); + // Read(sy); + // if (!Read(sz)) + // return false; + // x=((float)sx / 32767.5f - 1.0f) * magnitude; + // y=((float)sy / 32767.5f - 1.0f) * magnitude; + // z=((float)sz / 32767.5f - 1.0f) * magnitude; + float cx,cy,cz; + ReadCompressed(cx); + ReadCompressed(cy); + if (!ReadCompressed(cz)) + return false; + x=cx; + y=cy; + z=cz; + x*=magnitude; + y*=magnitude; + z*=magnitude; + } + else + { + x=0.0; + y=0.0; + z=0.0; + } + return true; + } + + template // templateType for this function must be a float or double + bool BitStream::ReadNormQuat( templateType &w, templateType &x, templateType &y, templateType &z) + { + bool cwNeg, cxNeg, cyNeg, czNeg; + unsigned short cx,cy,cz; + Read(cwNeg); + Read(cxNeg); + Read(cyNeg); + Read(czNeg); + Read(cx); + Read(cy); + if (!Read(cz)) + return false; + + // Calculate w from x,y,z + x=(templateType)(cx/65535.0); + y=(templateType)(cy/65535.0); + z=(templateType)(cz/65535.0); + if (cxNeg) x=-x; + if (cyNeg) y=-y; + if (czNeg) z=-z; + float difference = 1.0f - x*x - y*y - z*z; + if (difference < 0.0f) + difference=0.0f; + w = (templateType)(sqrt(difference)); + if (cwNeg) + w=-w; + + return true; + } + + template // templateType for this function must be a float or double + bool BitStream::ReadOrthMatrix( + templateType &m00, templateType &m01, templateType &m02, + templateType &m10, templateType &m11, templateType &m12, + templateType &m20, templateType &m21, templateType &m22 ) + { + float qw,qx,qy,qz; + if (!ReadNormQuat(qw,qx,qy,qz)) + return false; + + // Quat to orthogonal rotation matrix + // http://www.euclideanspace.com/maths/geometry/rotations/conversions/quaternionToMatrix/index.htm + double sqw = (double)qw*(double)qw; + double sqx = (double)qx*(double)qx; + double sqy = (double)qy*(double)qy; + double sqz = (double)qz*(double)qz; + m00 = (templateType)(sqx - sqy - sqz + sqw); // since sqw + sqx + sqy + sqz =1 + m11 = (templateType)(-sqx + sqy - sqz + sqw); + m22 = (templateType)(-sqx - sqy + sqz + sqw); + + double tmp1 = (double)qx*(double)qy; + double tmp2 = (double)qz*(double)qw; + m10 = (templateType)(2.0 * (tmp1 + tmp2)); + m01 = (templateType)(2.0 * (tmp1 - tmp2)); + + tmp1 = (double)qx*(double)qz; + tmp2 = (double)qy*(double)qw; + m20 =(templateType)(2.0 * (tmp1 - tmp2)); + m02 = (templateType)(2.0 * (tmp1 + tmp2)); + tmp1 = (double)qy*(double)qz; + tmp2 = (double)qx*(double)qw; + m21 = (templateType)(2.0 * (tmp1 + tmp2)); + m12 = (templateType)(2.0 * (tmp1 - tmp2)); + + return true; + } + + template + BitStream& operator<<(BitStream& out, templateType& c) + { + out.WriteRef(c); + return out; + } + template + BitStream& operator>>(BitStream& in, templateType& c) + { + bool success = in.Read(c); + RakAssert(success); + return in; + } + +} + +#ifdef _MSC_VER +#pragma warning( pop ) +#endif + +#endif + +#endif // VC6 diff --git a/RakNet/Sources/BitStream_NoTemplate.cpp b/RakNet/Sources/BitStream_NoTemplate.cpp new file mode 100644 index 0000000..81074d6 --- /dev/null +++ b/RakNet/Sources/BitStream_NoTemplate.cpp @@ -0,0 +1,1115 @@ +/// \file +/// +/// This file is part of RakNet Copyright 2003 Jenkins Software LLC +/// +/// Usage of RakNet is subject to the appropriate license agreement. + + +#if defined(_MSC_VER) && _MSC_VER < 1299 // VC6 doesn't support template specialization + +#include "BitStream.h" +#include +#include +#include +#include +#include +#include +#if defined(_XBOX) || defined(X360) + +#elif defined(_WIN32) +#include // htonl +#elif defined(_CONSOLE_2) +#include "PS3Includes.h" +#else +#include +#endif + +// MSWin uses _copysign, others use copysign... +#ifndef _WIN32 +#define _copysign copysign +#endif + + + +using namespace RakNet; + +#ifdef _MSC_VER +#pragma warning( push ) +#endif + +BitStream::BitStream() +{ + numberOfBitsUsed = 0; + //numberOfBitsAllocated = 32 * 8; + numberOfBitsAllocated = BITSTREAM_STACK_ALLOCATION_SIZE * 8; + readOffset = 0; + //data = ( unsigned char* ) rakMalloc_Ex( 32, __FILE__, __LINE__ ); + data = ( unsigned char* ) stackData; + +#ifdef _DEBUG + // RakAssert( data ); +#endif + //memset(data, 0, 32); + copyData = true; +} + +BitStream::BitStream( const unsigned int initialBytesToAllocate ) +{ + numberOfBitsUsed = 0; + readOffset = 0; + if (initialBytesToAllocate <= BITSTREAM_STACK_ALLOCATION_SIZE) + { + data = ( unsigned char* ) stackData; + numberOfBitsAllocated = BITSTREAM_STACK_ALLOCATION_SIZE * 8; + } + else + { + data = ( unsigned char* ) rakMalloc_Ex( (size_t) initialBytesToAllocate, __FILE__, __LINE__ ); + numberOfBitsAllocated = initialBytesToAllocate << 3; + } +#ifdef _DEBUG + RakAssert( data ); +#endif + // memset(data, 0, initialBytesToAllocate); + copyData = true; +} + +BitStream::BitStream( unsigned char* _data, const unsigned int lengthInBytes, bool _copyData ) +{ + numberOfBitsUsed = lengthInBytes << 3; + readOffset = 0; + copyData = _copyData; + numberOfBitsAllocated = lengthInBytes << 3; + + if ( copyData ) + { + if ( lengthInBytes > 0 ) + { + if (lengthInBytes < BITSTREAM_STACK_ALLOCATION_SIZE) + { + data = ( unsigned char* ) stackData; + numberOfBitsAllocated = BITSTREAM_STACK_ALLOCATION_SIZE << 3; + } + else + { + data = ( unsigned char* ) rakMalloc_Ex( (size_t) lengthInBytes, __FILE__, __LINE__ ); + } +#ifdef _DEBUG + RakAssert( data ); +#endif + memcpy( data, _data, (size_t) lengthInBytes ); + } + else + data = 0; + } + else + data = ( unsigned char* ) _data; +} + +// Use this if you pass a pointer copy to the constructor (_copyData==false) and want to overallocate to prevent reallocation +void BitStream::SetNumberOfBitsAllocated( const BitSize_t lengthInBits ) +{ +#ifdef _DEBUG + RakAssert( lengthInBits >= ( BitSize_t ) numberOfBitsAllocated ); +#endif + numberOfBitsAllocated = lengthInBits; +} + +BitStream::~BitStream() +{ + if ( copyData && numberOfBitsAllocated > (BITSTREAM_STACK_ALLOCATION_SIZE << 3)) + rakFree_Ex( data , __FILE__, __LINE__ ); // Use realloc and free so we are more efficient than delete and new for resizing +} + +void BitStream::Reset( void ) +{ + // Note: Do NOT reallocate memory because BitStream is used + // in places to serialize/deserialize a buffer. Reallocation + // is a dangerous operation (may result in leaks). + + if ( numberOfBitsUsed > 0 ) + { + // memset(data, 0, BITS_TO_BYTES(numberOfBitsUsed)); + } + + // Don't free memory here for speed efficiency + //free(data); // Use realloc and free so we are more efficient than delete and new for resizing + numberOfBitsUsed = 0; + + //numberOfBitsAllocated=8; + readOffset = 0; + + //data=(unsigned char*)rakMalloc_Ex(1, __FILE__, __LINE__); + // if (numberOfBitsAllocated>0) + // memset(data, 0, BITS_TO_BYTES(numberOfBitsAllocated)); +} + +// Write an array or casted stream +void BitStream::Write( const char* input, const unsigned int numberOfBytes ) +{ + if (numberOfBytes==0) + return; + + // Optimization: + if ((numberOfBitsUsed & 7) == 0) + { + AddBitsAndReallocate( BYTES_TO_BITS(numberOfBytes) ); + memcpy(data+BITS_TO_BYTES(numberOfBitsUsed), input, (size_t) numberOfBytes); + numberOfBitsUsed+=BYTES_TO_BITS(numberOfBytes); + } + else + { + WriteBits( ( unsigned char* ) input, numberOfBytes * 8, true ); + } + +} +void BitStream::Write( BitStream *bitStream) +{ + Write(bitStream, bitStream->GetNumberOfBitsUsed()); +} +void BitStream::Write( BitStream *bitStream, BitSize_t numberOfBits ) +{ + AddBitsAndReallocate( numberOfBits ); + BitSize_t numberOfBitsMod8; + + if ((bitStream->GetReadOffset()&7)==0 && (numberOfBitsUsed&7)==0) + { + int readOffsetBytes=bitStream->GetReadOffset()/8; + int numBytes=numberOfBits/8; + memcpy(data + (numberOfBitsUsed >> 3), bitStream->GetData()+readOffsetBytes, numBytes); + numberOfBits-=BYTES_TO_BITS(numBytes); + bitStream->SetReadOffset(BYTES_TO_BITS(numBytes+readOffsetBytes)); + numberOfBitsUsed+=BYTES_TO_BITS(numBytes); + } + + while (numberOfBits-->0 && bitStream->readOffset + 1 <= bitStream->numberOfBitsUsed) + { + numberOfBitsMod8 = numberOfBitsUsed & 7; + if ( numberOfBitsMod8 == 0 ) + { + // New byte + if (bitStream->data[ bitStream->readOffset >> 3 ] & ( 0x80 >> ( bitStream->readOffset & 7 ) ) ) + { + // Write 1 + data[ numberOfBitsUsed >> 3 ] = 0x80; + } + else + { + // Write 0 + data[ numberOfBitsUsed >> 3 ] = 0; + } + + } + else + { + // Existing byte + if (bitStream->data[ bitStream->readOffset >> 3 ] & ( 0x80 >> ( bitStream->readOffset & 7 ) ) ) + data[ numberOfBitsUsed >> 3 ] |= 0x80 >> ( numberOfBitsMod8 ); // Set the bit to 1 + // else 0, do nothing + } + + bitStream->readOffset++; + numberOfBitsUsed++; + } +} +void BitStream::Write( BitStream &bitStream, BitSize_t numberOfBits ) +{ + Write(&bitStream, numberOfBits); +} +void BitStream::Write( BitStream &bitStream ) +{ + Write(&bitStream); +} +bool BitStream::Read( BitStream *bitStream, BitSize_t numberOfBits ) +{ + if (GetNumberOfUnreadBits() < numberOfBits) + return false; + bitStream->Write(this, numberOfBits); + return true; +} +bool BitStream::Read( BitStream *bitStream ) +{ + bitStream->Write(this); + return true; +} +bool BitStream::Read( BitStream &bitStream, BitSize_t numberOfBits ) +{ + if (GetNumberOfUnreadBits() < numberOfBits) + return false; + bitStream.Write(this, numberOfBits); + return true; +} +bool BitStream::Read( BitStream &bitStream ) +{ + bitStream.Write(this); + return true; +} + +// Read an array or casted stream +bool BitStream::Read( char* output, const unsigned int numberOfBytes ) +{ + // Optimization: + if ((readOffset & 7) == 0) + { + if ( readOffset + ( numberOfBytes << 3 ) > numberOfBitsUsed ) + return false; + + // Write the data + memcpy( output, data + ( readOffset >> 3 ), (size_t) numberOfBytes ); + + readOffset += numberOfBytes << 3; + return true; + } + else + { + return ReadBits( ( unsigned char* ) output, numberOfBytes * 8 ); + } +} + +// Sets the read pointer back to the beginning of your data. +void BitStream::ResetReadPointer( void ) +{ + readOffset = 0; +} + +// Sets the write pointer back to the beginning of your data. +void BitStream::ResetWritePointer( void ) +{ + numberOfBitsUsed = 0; +} + +// Write a 0 +void BitStream::Write0( void ) +{ + AddBitsAndReallocate( 1 ); + + // New bytes need to be zeroed + if ( ( numberOfBitsUsed & 7 ) == 0 ) + data[ numberOfBitsUsed >> 3 ] = 0; + + numberOfBitsUsed++; +} + +// Write a 1 +void BitStream::Write1( void ) +{ + AddBitsAndReallocate( 1 ); + + BitSize_t numberOfBitsMod8 = numberOfBitsUsed & 7; + + if ( numberOfBitsMod8 == 0 ) + data[ numberOfBitsUsed >> 3 ] = 0x80; + else + data[ numberOfBitsUsed >> 3 ] |= 0x80 >> ( numberOfBitsMod8 ); // Set the bit to 1 + + numberOfBitsUsed++; +} + +// Returns true if the next data read is a 1, false if it is a 0 +bool BitStream::ReadBit( void ) +{ + bool result = ( data[ readOffset >> 3 ] & ( 0x80 >> ( readOffset & 7 ) ) ) !=0; + readOffset++; + return result; +} + +// Align the bitstream to the byte boundary and then write the specified number of bits. +// This is faster than WriteBits but wastes the bits to do the alignment and requires you to call +// SetReadToByteAlignment at the corresponding read position +void BitStream::WriteAlignedBytes( const unsigned char* input, const unsigned int numberOfBytesToWrite ) +{ + AlignWriteToByteBoundary(); + Write((const char*) input, numberOfBytesToWrite); +} +void BitStream::EndianSwapBytes( int byteOffset, int length ) +{ + if (DoEndianSwap()) + { + ReverseBytesInPlace(data+byteOffset, length); + } +} +/// Aligns the bitstream, writes inputLength, and writes input. Won't write beyond maxBytesToWrite +void BitStream::WriteAlignedBytesSafe( const char *input, const unsigned int inputLength, const unsigned int maxBytesToWrite ) +{ + if (input==0 || inputLength==0) + { + WriteCompressed((unsigned int)0); + return; + } + WriteCompressed(inputLength); + WriteAlignedBytes((const unsigned char*) input, inputLength < maxBytesToWrite ? inputLength : maxBytesToWrite); +} + +// Read bits, starting at the next aligned bits. Note that the modulus 8 starting offset of the +// sequence must be the same as was used with WriteBits. This will be a problem with packet coalescence +// unless you byte align the coalesced packets. +bool BitStream::ReadAlignedBytes( unsigned char* output, const unsigned int numberOfBytesToRead ) +{ +#ifdef _DEBUG + RakAssert( numberOfBytesToRead > 0 ); +#endif + + if ( numberOfBytesToRead <= 0 ) + return false; + + // Byte align + AlignReadToByteBoundary(); + + if ( readOffset + ( numberOfBytesToRead << 3 ) > numberOfBitsUsed ) + return false; + + // Write the data + memcpy( output, data + ( readOffset >> 3 ), (size_t) numberOfBytesToRead ); + + readOffset += numberOfBytesToRead << 3; + + return true; +} +bool BitStream::ReadAlignedBytesSafe( char *input, int &inputLength, const int maxBytesToRead ) +{ + return ReadAlignedBytesSafe(input,(unsigned int&) inputLength,(unsigned int)maxBytesToRead); +} +bool BitStream::ReadAlignedBytesSafe( char *input, unsigned int &inputLength, const unsigned int maxBytesToRead ) +{ + if (ReadCompressed(inputLength)==false) + return false; + if (inputLength > maxBytesToRead) + inputLength=maxBytesToRead; + if (inputLength==0) + return true; + return ReadAlignedBytes((unsigned char*) input, inputLength); +} +bool BitStream::ReadAlignedBytesSafeAlloc( char **input, int &inputLength, const unsigned int maxBytesToRead ) +{ + return ReadAlignedBytesSafeAlloc(input,(unsigned int&) inputLength, maxBytesToRead); +} +bool BitStream::ReadAlignedBytesSafeAlloc( char **input, unsigned int &inputLength, const unsigned int maxBytesToRead ) +{ + rakFree_Ex(*input, __FILE__, __LINE__ ); + *input=0; + if (ReadCompressed(inputLength)==false) + return false; + if (inputLength > maxBytesToRead) + inputLength=maxBytesToRead; + if (inputLength==0) + return true; + *input = (char*) rakMalloc_Ex( (size_t) inputLength, __FILE__, __LINE__ ); + return ReadAlignedBytes((unsigned char*) *input, inputLength); +} + +// Write numberToWrite bits from the input source +void BitStream::WriteBits( const unsigned char* input, BitSize_t numberOfBitsToWrite, const bool rightAlignedBits ) +{ + // if (numberOfBitsToWrite<=0) + // return; + + AddBitsAndReallocate( numberOfBitsToWrite ); + + const BitSize_t numberOfBitsUsedMod8 = numberOfBitsUsed & 7; + + // If currently aligned and numberOfBits is a multiple of 8, just memcpy for speed + if (numberOfBitsUsedMod8==0 && (numberOfBitsToWrite&7)==0) + { + memcpy( data + ( numberOfBitsUsed >> 3 ), input, numberOfBitsToWrite>>3); + numberOfBitsUsed+=numberOfBitsToWrite; + return; + } + + unsigned char dataByte; + const unsigned char* inputPtr=input; + + // Faster to put the while at the top surprisingly enough + while ( numberOfBitsToWrite > 0 ) + //do + { + dataByte = *( inputPtr++ ); + + if ( numberOfBitsToWrite < 8 && rightAlignedBits ) // rightAlignedBits means in the case of a partial byte, the bits are aligned from the right (bit 0) rather than the left (as in the normal internal representation) + dataByte <<= 8 - numberOfBitsToWrite; // shift left to get the bits on the left, as in our internal representation + + // Writing to a new byte each time + if ( numberOfBitsUsedMod8 == 0 ) + * ( data + ( numberOfBitsUsed >> 3 ) ) = dataByte; + else + { + // Copy over the new data. + *( data + ( numberOfBitsUsed >> 3 ) ) |= dataByte >> ( numberOfBitsUsedMod8 ); // First half + + if ( 8 - ( numberOfBitsUsedMod8 ) < 8 && 8 - ( numberOfBitsUsedMod8 ) < numberOfBitsToWrite ) // If we didn't write it all out in the first half (8 - (numberOfBitsUsed%8) is the number we wrote in the first half) + { + *( data + ( numberOfBitsUsed >> 3 ) + 1 ) = (unsigned char) ( dataByte << ( 8 - ( numberOfBitsUsedMod8 ) ) ); // Second half (overlaps byte boundary) + } + } + + if ( numberOfBitsToWrite >= 8 ) + { + numberOfBitsUsed += 8; + numberOfBitsToWrite -= 8; + } + else + { + numberOfBitsUsed += numberOfBitsToWrite; + numberOfBitsToWrite=0; + } + } + // } while(numberOfBitsToWrite>0); +} + +// Set the stream to some initial data. For internal use +void BitStream::SetData( unsigned char *input ) +{ + data=input; + copyData=false; +} + +// Assume the input source points to a native type, compress and write it +void BitStream::WriteCompressed( const unsigned char* input, + const unsigned int size, const bool unsignedData ) +{ + BitSize_t currentByte = ( size >> 3 ) - 1; // PCs + + unsigned char byteMatch; + + if ( unsignedData ) + { + byteMatch = 0; + } + + else + { + byteMatch = 0xFF; + } + + // Write upper bytes with a single 1 + // From high byte to low byte, if high byte is a byteMatch then write a 1 bit. Otherwise write a 0 bit and then write the remaining bytes + while ( currentByte > 0 ) + { + if ( input[ currentByte ] == byteMatch ) // If high byte is byteMatch (0 of 0xff) then it would have the same value shifted + { + bool b = true; + Write( b ); + } + else + { + // Write the remainder of the data after writing 0 + bool b = false; + Write( b ); + + WriteBits( input, ( currentByte + 1 ) << 3, true ); + // currentByte--; + + + return ; + } + + currentByte--; + } + + // If the upper half of the last byte is a 0 (positive) or 16 (negative) then write a 1 and the remaining 4 bits. Otherwise write a 0 and the 8 bites. + if ( ( unsignedData && ( ( *( input + currentByte ) ) & 0xF0 ) == 0x00 ) || + ( unsignedData == false && ( ( *( input + currentByte ) ) & 0xF0 ) == 0xF0 ) ) + { + bool b = true; + Write( b ); + WriteBits( input + currentByte, 4, true ); + } + + else + { + bool b = false; + Write( b ); + WriteBits( input + currentByte, 8, true ); + } +} + +// Read numberOfBitsToRead bits to the output source +// alignBitsToRight should be set to true to convert internal bitstream data to userdata +// It should be false if you used WriteBits with rightAlignedBits false +bool BitStream::ReadBits( unsigned char *output, BitSize_t numberOfBitsToRead, const bool alignBitsToRight ) +{ +#ifdef _DEBUG + // RakAssert( numberOfBitsToRead > 0 ); +#endif + if (numberOfBitsToRead<=0) + return false; + + if ( readOffset + numberOfBitsToRead > numberOfBitsUsed ) + return false; + + + const BitSize_t readOffsetMod8 = readOffset & 7; + + // If currently aligned and numberOfBits is a multiple of 8, just memcpy for speed + if (readOffsetMod8==0 && (numberOfBitsToRead&7)==0) + { + memcpy( output, data + ( readOffset >> 3 ), numberOfBitsToRead>>3); + readOffset+=numberOfBitsToRead; + return true; + } + + + + BitSize_t offset = 0; + + memset( output, 0, (size_t) BITS_TO_BYTES( numberOfBitsToRead ) ); + + while ( numberOfBitsToRead > 0 ) + { + *( output + offset ) |= *( data + ( readOffset >> 3 ) ) << ( readOffsetMod8 ); // First half + + if ( readOffsetMod8 > 0 && numberOfBitsToRead > 8 - ( readOffsetMod8 ) ) // If we have a second half, we didn't read enough bytes in the first half + *( output + offset ) |= *( data + ( readOffset >> 3 ) + 1 ) >> ( 8 - ( readOffsetMod8 ) ); // Second half (overlaps byte boundary) + + if (numberOfBitsToRead>=8) + { + numberOfBitsToRead -= 8; + readOffset += 8; + offset++; + } + else + { + int neg = (int) numberOfBitsToRead - 8; + + if ( neg < 0 ) // Reading a partial byte for the last byte, shift right so the data is aligned on the right + { + + if ( alignBitsToRight ) + * ( output + offset ) >>= -neg; + + readOffset += 8 + neg; + } + else + readOffset += 8; + + offset++; + + numberOfBitsToRead=0; + } + } + + return true; +} + +// Assume the input source points to a compressed native type. Decompress and read it +bool BitStream::ReadCompressed( unsigned char* output, + const unsigned int size, const bool unsignedData ) +{ + unsigned int currentByte = ( size >> 3 ) - 1; + + + unsigned char byteMatch, halfByteMatch; + + if ( unsignedData ) + { + byteMatch = 0; + halfByteMatch = 0; + } + + else + { + byteMatch = 0xFF; + halfByteMatch = 0xF0; + } + + // Upper bytes are specified with a single 1 if they match byteMatch + // From high byte to low byte, if high byte is a byteMatch then write a 1 bit. Otherwise write a 0 bit and then write the remaining bytes + while ( currentByte > 0 ) + { + // If we read a 1 then the data is byteMatch. + + bool b; + + if ( Read( b ) == false ) + return false; + + if ( b ) // Check that bit + { + output[ currentByte ] = byteMatch; + currentByte--; + } + else + { + // Read the rest of the bytes + + if ( ReadBits( output, ( currentByte + 1 ) << 3 ) == false ) + return false; + + return true; + } + } + + // All but the first bytes are byteMatch. If the upper half of the last byte is a 0 (positive) or 16 (negative) then what we read will be a 1 and the remaining 4 bits. + // Otherwise we read a 0 and the 8 bytes + //RakAssert(readOffset+1 <=numberOfBitsUsed); // If this assert is hit the stream wasn't long enough to read from + if ( readOffset + 1 > numberOfBitsUsed ) + return false; + + bool b; + + if ( Read( b ) == false ) + return false; + + if ( b ) // Check that bit + { + + if ( ReadBits( output + currentByte, 4 ) == false ) + return false; + + output[ currentByte ] |= halfByteMatch; // We have to set the high 4 bits since these are set to 0 by ReadBits + } + else + { + if ( ReadBits( output + currentByte, 8 ) == false ) + return false; + } + + return true; +} + +// Reallocates (if necessary) in preparation of writing numberOfBitsToWrite +void BitStream::AddBitsAndReallocate( const BitSize_t numberOfBitsToWrite ) +{ + BitSize_t newNumberOfBitsAllocated = numberOfBitsToWrite + numberOfBitsUsed; + + if ( numberOfBitsToWrite + numberOfBitsUsed > 0 && ( ( numberOfBitsAllocated - 1 ) >> 3 ) < ( ( newNumberOfBitsAllocated - 1 ) >> 3 ) ) // If we need to allocate 1 or more new bytes + { +#ifdef _DEBUG + // If this assert hits then we need to specify true for the third parameter in the constructor + // It needs to reallocate to hold all the data and can't do it unless we allocated to begin with + // Often hits if you call Write or Serialize on a read-only bitstream + RakAssert( copyData == true ); +#endif + + // Less memory efficient but saves on news and deletes + /// Cap to 1 meg buffer to save on huge allocations + newNumberOfBitsAllocated = ( numberOfBitsToWrite + numberOfBitsUsed ) * 2; + if (newNumberOfBitsAllocated - ( numberOfBitsToWrite + numberOfBitsUsed ) > 1048576 ) + newNumberOfBitsAllocated = numberOfBitsToWrite + numberOfBitsUsed + 1048576; + + // BitSize_t newByteOffset = BITS_TO_BYTES( numberOfBitsAllocated ); + // Use realloc and free so we are more efficient than delete and new for resizing + BitSize_t amountToAllocate = BITS_TO_BYTES( newNumberOfBitsAllocated ); + if (data==(unsigned char*)stackData) + { + if (amountToAllocate > BITSTREAM_STACK_ALLOCATION_SIZE) + { + data = ( unsigned char* ) rakMalloc_Ex( (size_t) amountToAllocate, __FILE__, __LINE__ ); + + // need to copy the stack data over to our new memory area too + memcpy ((void *)data, (void *)stackData, (size_t) BITS_TO_BYTES( numberOfBitsAllocated )); + } + } + else + { + data = ( unsigned char* ) rakRealloc_Ex( data, (size_t) amountToAllocate, __FILE__, __LINE__ ); + } + +#ifdef _DEBUG + RakAssert( data ); // Make sure realloc succeeded +#endif + // memset(data+newByteOffset, 0, ((newNumberOfBitsAllocated-1)>>3) - ((numberOfBitsAllocated-1)>>3)); // Set the new data block to 0 + } + + if ( newNumberOfBitsAllocated > numberOfBitsAllocated ) + numberOfBitsAllocated = newNumberOfBitsAllocated; +} +BitSize_t BitStream::GetNumberOfBitsAllocated(void) const +{ + return numberOfBitsAllocated; +} +void BitStream::PadWithZeroToByteLength( unsigned int bytes ) +{ + if (GetNumberOfBytesUsed() < bytes) + { + AlignWriteToByteBoundary(); + unsigned int numToWrite = bytes - GetNumberOfBytesUsed(); + AddBitsAndReallocate( BYTES_TO_BITS(numToWrite) ); + memset(data+BITS_TO_BYTES(numberOfBitsUsed), 0, (size_t) numToWrite); + numberOfBitsUsed+=BYTES_TO_BITS(numToWrite); + } +} + +// Should hit if reads didn't match writes +void BitStream::AssertStreamEmpty( void ) +{ + RakAssert( readOffset == numberOfBitsUsed ); +} +void BitStream::PrintBits( char *out ) const +{ + if ( numberOfBitsUsed <= 0 ) + { + strcpy(out, "No bits\n" ); + return; + } + + unsigned int strIndex=0; + for ( BitSize_t counter = 0; counter < BITS_TO_BYTES( numberOfBitsUsed ); counter++ ) + { + BitSize_t stop; + + if ( counter == ( numberOfBitsUsed - 1 ) >> 3 ) + stop = 8 - ( ( ( numberOfBitsUsed - 1 ) & 7 ) + 1 ); + else + stop = 0; + + for ( BitSize_t counter2 = 7; counter2 >= stop; counter2-- ) + { + if ( ( data[ counter ] >> counter2 ) & 1 ) + out[strIndex++]='1'; + else + out[strIndex++]='0'; + + if (counter2==0) + break; + } + + out[strIndex++]=' '; + } + + out[strIndex++]='\n'; + + out[strIndex++]=0; +} +void BitStream::PrintBits( void ) const +{ + char out[2048]; + PrintBits(out); + RAKNET_DEBUG_PRINTF(out); +} +void BitStream::PrintHex( char *out ) const +{ + BitSize_t i; + for ( i=0; i < GetNumberOfBytesUsed(); i++) + { + sprintf(out+i*3, "%02x ", data[i]); + } +} +void BitStream::PrintHex( void ) const +{ + char out[2048]; + PrintHex(out); + RAKNET_DEBUG_PRINTF(out); +} + +// Exposes the data for you to look at, like PrintBits does. +// Data will point to the stream. Returns the length in bits of the stream. +BitSize_t BitStream::CopyData( unsigned char** _data ) const +{ +#ifdef _DEBUG + RakAssert( numberOfBitsUsed > 0 ); +#endif + + *_data = (unsigned char*) rakMalloc_Ex( (size_t) BITS_TO_BYTES( numberOfBitsUsed ), __FILE__, __LINE__ ); + memcpy( *_data, data, sizeof(unsigned char) * (size_t) ( BITS_TO_BYTES( numberOfBitsUsed ) ) ); + return numberOfBitsUsed; +} + +// Ignore data we don't intend to read +void BitStream::IgnoreBits( const BitSize_t numberOfBits ) +{ + readOffset += numberOfBits; +} + +void BitStream::IgnoreBytes( const unsigned int numberOfBytes ) +{ + IgnoreBits(BYTES_TO_BITS(numberOfBytes)); +} + +// Move the write pointer to a position on the array. Dangerous if you don't know what you are doing! +// Doesn't work with non-aligned data! +void BitStream::SetWriteOffset( const BitSize_t offset ) +{ + numberOfBitsUsed = offset; +} + +/* +BitSize_t BitStream::GetWriteOffset( void ) const +{ +return numberOfBitsUsed; +} + +// Returns the length in bits of the stream +BitSize_t BitStream::GetNumberOfBitsUsed( void ) const +{ +return GetWriteOffset(); +} + +// Returns the length in bytes of the stream +BitSize_t BitStream::GetNumberOfBytesUsed( void ) const +{ +return BITS_TO_BYTES( numberOfBitsUsed ); +} + +// Returns the number of bits into the stream that we have read +BitSize_t BitStream::GetReadOffset( void ) const +{ +return readOffset; +} + + +// Sets the read bit index +void BitStream::SetReadOffset( const BitSize_t newReadOffset ) +{ +readOffset=newReadOffset; +} + +// Returns the number of bits left in the stream that haven't been read +BitSize_t BitStream::GetNumberOfUnreadBits( void ) const +{ +return numberOfBitsUsed - readOffset; +} +// Exposes the internal data +unsigned char* BitStream::GetData( void ) const +{ +return data; +} + +*/ +// If we used the constructor version with copy data off, this makes sure it is set to on and the data pointed to is copied. +void BitStream::AssertCopyData( void ) +{ + if ( copyData == false ) + { + copyData = true; + + if ( numberOfBitsAllocated > 0 ) + { + unsigned char * newdata = ( unsigned char* ) rakMalloc_Ex( (size_t) BITS_TO_BYTES( numberOfBitsAllocated ), __FILE__, __LINE__ ); +#ifdef _DEBUG + + RakAssert( data ); +#endif + + memcpy( newdata, data, (size_t) BITS_TO_BYTES( numberOfBitsAllocated ) ); + data = newdata; + } + + else + data = 0; + } +} +bool BitStream::IsNetworkOrderInternal(void) +{ +#if defined(_PS3) || defined(__PS3__) || defined(SN_TARGET_PS3) + +#else + static const bool isNetworkOrder=(htonl(12345) == 12345); + return isNetworkOrder; +#endif +} +void BitStream::ReverseBytes(unsigned char *input, unsigned char *output, const unsigned int length) +{ + for (BitSize_t i=0; i < length; i++) + output[i]=input[length-i-1]; +} +void BitStream::ReverseBytesInPlace(unsigned char *data,const unsigned int length) +{ + unsigned char temp; + BitSize_t i; + for (i=0; i < (length>>1); i++) + { + temp = data[i]; + data[i]=data[length-i-1]; + data[length-i-1]=temp; + } +} + +void BitStream::WriteAlignedVar8(const char *input) +{ + RakAssert((numberOfBitsUsed&7)==0); + AddBitsAndReallocate(1*8); + data[( numberOfBitsUsed >> 3 ) + 0] = input[0]; + numberOfBitsUsed+=1*8; +} +bool BitStream::ReadAlignedVar8(char *output) +{ + RakAssert((readOffset&7)==0); + if ( readOffset + 1*8 > numberOfBitsUsed ) + return false; + + output[0] = data[( readOffset >> 3 ) + 0]; + readOffset+=1*8; + return true; +} +void BitStream::WriteAlignedVar16(const char *input) +{ + RakAssert((numberOfBitsUsed&7)==0); + AddBitsAndReallocate(2*8); +#ifndef __BITSTREAM_NATIVE_END + if (DoEndianSwap()) + { + data[( numberOfBitsUsed >> 3 ) + 0] = input[1]; + data[( numberOfBitsUsed >> 3 ) + 1] = input[0]; + } + else +#endif + { + data[( numberOfBitsUsed >> 3 ) + 0] = input[0]; + data[( numberOfBitsUsed >> 3 ) + 1] = input[1]; + } + + numberOfBitsUsed+=2*8; +} +bool BitStream::ReadAlignedVar16(char *output) +{ + RakAssert((readOffset&7)==0); + if ( readOffset + 2*8 > numberOfBitsUsed ) + return false; +#ifndef __BITSTREAM_NATIVE_END + if (DoEndianSwap()) + { + output[0] = data[( readOffset >> 3 ) + 1]; + output[1] = data[( readOffset >> 3 ) + 0]; + } + else +#endif + { + output[0] = data[( readOffset >> 3 ) + 0]; + output[1] = data[( readOffset >> 3 ) + 1]; + } + + readOffset+=2*8; + return true; +} +void BitStream::WriteAlignedVar32(const char *input) +{ + RakAssert((numberOfBitsUsed&7)==0); + AddBitsAndReallocate(4*8); +#ifndef __BITSTREAM_NATIVE_END + if (DoEndianSwap()) + { + data[( numberOfBitsUsed >> 3 ) + 0] = input[3]; + data[( numberOfBitsUsed >> 3 ) + 1] = input[2]; + data[( numberOfBitsUsed >> 3 ) + 2] = input[1]; + data[( numberOfBitsUsed >> 3 ) + 3] = input[0]; + } + else +#endif + { + data[( numberOfBitsUsed >> 3 ) + 0] = input[0]; + data[( numberOfBitsUsed >> 3 ) + 1] = input[1]; + data[( numberOfBitsUsed >> 3 ) + 2] = input[2]; + data[( numberOfBitsUsed >> 3 ) + 3] = input[3]; + } + + numberOfBitsUsed+=4*8; +} +bool BitStream::ReadAlignedVar32(char *output) +{ + RakAssert((readOffset&7)==0); + if ( readOffset + 4*8 > numberOfBitsUsed ) + return false; +#ifndef __BITSTREAM_NATIVE_END + if (DoEndianSwap()) + { + output[0] = data[( readOffset >> 3 ) + 3]; + output[1] = data[( readOffset >> 3 ) + 2]; + output[2] = data[( readOffset >> 3 ) + 1]; + output[3] = data[( readOffset >> 3 ) + 0]; + } + else +#endif + { + output[0] = data[( readOffset >> 3 ) + 0]; + output[1] = data[( readOffset >> 3 ) + 1]; + output[2] = data[( readOffset >> 3 ) + 2]; + output[3] = data[( readOffset >> 3 ) + 3]; + } + + readOffset+=4*8; + return true; +} +bool BitStream::ReadFloat16( float &f, float floatMin, float floatMax ) +{ + unsigned short percentile; + if (Read(percentile)) + { + RakAssert(floatMax>floatMin); + f = floatMin + ((float) percentile / 65535.0f) * (floatMax-floatMin); + if (ffloatMax) + f=floatMax; + return true; + } + return false; +} +bool BitStream::SerializeFloat16(bool writeToBitstream, float &f, float floatMin, float floatMax) +{ + if (writeToBitstream) + WriteFloat16(f, floatMin, floatMax); + else + return ReadFloat16(f, floatMin, floatMax); + return true; +} +void BitStream::WriteFloat16( float f, float floatMin, float floatMax ) +{ + RakAssert(floatMax>floatMin); + if (f>floatMax+.001) + { + RakAssert(f<=floatMax+.001); + } + if (f=floatMin-.001); + } + float percentile=65535.0f * (f-floatMin)/(floatMax-floatMin); + if (percentile<0.0) + percentile=0.0; + if (percentile>65535.0f) + percentile=65535.0f; + Write((unsigned short)percentile); +} + +void BitStream::Write(const uint24_t &var) +{ + AlignWriteToByteBoundary(); + AddBitsAndReallocate(3*8); + + if (IsBigEndian()==false) + { + data[( numberOfBitsUsed >> 3 ) + 0] = ((char *)&var.val)[0]; + data[( numberOfBitsUsed >> 3 ) + 1] = ((char *)&var.val)[1]; + data[( numberOfBitsUsed >> 3 ) + 2] = ((char *)&var.val)[2]; + } + else + { + data[( numberOfBitsUsed >> 3 ) + 0] = ((char *)&var.val)[3]; + data[( numberOfBitsUsed >> 3 ) + 1] = ((char *)&var.val)[2]; + data[( numberOfBitsUsed >> 3 ) + 2] = ((char *)&var.val)[1]; + } + + numberOfBitsUsed+=3*8; +} + +bool BitStream::Read(uint24_t &var) +{ + AlignReadToByteBoundary(); + if ( readOffset + 3*8 > numberOfBitsUsed ) + return false; + + if (IsBigEndian()==false) + { + ((char *)&var.val)[0]=data[ (readOffset >> 3) + 0]; + ((char *)&var.val)[1]=data[ (readOffset >> 3) + 1]; + ((char *)&var.val)[2]=data[ (readOffset >> 3) + 2]; + ((char *)&var.val)[3]=0; + } + else + { + + ((char *)&var.val)[3]=data[ (readOffset >> 3) + 0]; + ((char *)&var.val)[2]=data[ (readOffset >> 3) + 1]; + ((char *)&var.val)[1]=data[ (readOffset >> 3) + 2]; + ((char *)&var.val)[0]=0; + } + + readOffset+=3*8; + return true; +} + +#ifdef _MSC_VER +#pragma warning( pop ) +#endif + +#endif diff --git a/RakNet/Sources/BitStream_NoTemplate.h b/RakNet/Sources/BitStream_NoTemplate.h new file mode 100644 index 0000000..497e0e3 --- /dev/null +++ b/RakNet/Sources/BitStream_NoTemplate.h @@ -0,0 +1,942 @@ +/// \file BitStream_NoTemplate.h +/// \brief This class allows you to write and read native types as a string of bits. BitStream is used extensively throughout RakNet and is designed to be used by users as well. +/// +/// This file is part of RakNet Copyright 2003 Jenkins Software LLC +/// +/// Usage of RakNet is subject to the appropriate license agreement. + + +#if defined(_MSC_VER) && _MSC_VER < 1299 // VC6 doesn't support template specialization + +#ifndef __BITSTREAM_H +#define __BITSTREAM_H + +#include "RakMemoryOverride.h" +#include "RakNetDefines.h" +#include "Export.h" +#include "RakNetTypes.h" +#include "RakAssert.h" +#include "RakString.h" +#if defined(_PS3) || defined(__PS3__) || defined(SN_TARGET_PS3) +#include +#else +#include +#endif +#include + +#ifdef _MSC_VER +#pragma warning( push ) +#endif + +/// Arbitrary size, just picking something likely to be larger than most packets +#define BITSTREAM_STACK_ALLOCATION_SIZE 256 + +/// The namespace RakNet is not consistently used. It's only purpose is to avoid compiler errors for classes whose names are very common. +/// For the most part I've tried to avoid this simply by using names very likely to be unique for my classes. +namespace RakNet +{ + /// This class allows you to write and read native types as a string of bits. BitStream is used extensively throughout RakNet and is designed to be used by users as well. + /// \sa BitStreamSample.txt + class RAK_DLL_EXPORT BitStream + { + + public: + /// Default Constructor + BitStream(); + + /// \brief Create the bitstream, with some number of bytes to immediately allocate. + /// \details There is no benefit to calling this, unless you know exactly how many bytes you need and it is greater than BITSTREAM_STACK_ALLOCATION_SIZE. + /// In that case all it does is save you one or more realloc calls. + /// \param[in] initialBytesToAllocate the number of bytes to pre-allocate. + BitStream( const unsigned int initialBytesToAllocate ); + + /// \brief Initialize the BitStream, immediately setting the data it contains to a predefined pointer. + /// \details Set \a _copyData to true if you want to make an internal copy of the data you are passing. Set it to false to just save a pointer to the data. + /// You shouldn't call Write functions with \a _copyData as false, as this will write to unallocated memory + /// 99% of the time you will use this function to cast Packet::data to a bitstream for reading, in which case you should write something as follows: + /// \code + /// RakNet::BitStream bs(packet->data, packet->length, false); + /// \endcode + /// \param[in] _data An array of bytes. + /// \param[in] lengthInBytes Size of the \a _data. + /// \param[in] _copyData true or false to make a copy of \a _data or not. + BitStream( unsigned char* _data, const unsigned int lengthInBytes, bool _copyData ); + + // Destructor + ~BitStream(); + + /// Resets the bitstream for reuse. + void Reset( void ); + + /// \brief Bidirectional serialize/deserialize any integral type to/from a bitstream. Undefine __BITSTREAM_NATIVE_END if you need endian swapping. + /// \param[in] writeToBitstream true to write from your data to this bitstream. False to read from this bitstream and write to your data + /// \param[in] var The value to write + /// \return true if \a writeToBitstream is true. true if \a writeToBitstream is false and the read was successful. false if \a writeToBitstream is false and the read was not successful. + bool Serialize(bool writeToBitstream, bool &var){if (writeToBitstream)Write(var);else return Read(var); return true;} + bool Serialize(bool writeToBitstream, unsigned char &var){if (writeToBitstream)Write(var);else return Read(var); return true;} + bool Serialize(bool writeToBitstream, char &var){if (writeToBitstream)Write(var);else return Read(var); return true;} + bool Serialize(bool writeToBitstream, unsigned short &var){if (writeToBitstream)Write(var);else return Read(var); return true;} + bool Serialize(bool writeToBitstream, short &var){if (writeToBitstream)Write(var);else return Read(var); return true;} + bool Serialize(bool writeToBitstream, unsigned int &var){if (writeToBitstream)Write(var);else return Read(var); return true;} + bool Serialize(bool writeToBitstream, int &var){if (writeToBitstream)Write(var);else return Read(var); return true;} + bool Serialize(bool writeToBitstream, unsigned long &var){if (writeToBitstream)Write(var);else return Read(var); return true;} + bool Serialize(bool writeToBitstream, long &var){if (writeToBitstream)Write(var);else return Read(var); return true;} + bool Serialize(bool writeToBitstream, long long &var){if (writeToBitstream)Write(var);else return Read(var); return true;} + bool Serialize(bool writeToBitstream, unsigned long long &var){if (writeToBitstream)Write(var);else return Read(var); return true;} + bool Serialize(bool writeToBitstream, float &var){if (writeToBitstream)Write(var);else return Read(var); return true;} + bool Serialize(bool writeToBitstream, double &var){if (writeToBitstream)Write(var);else return Read(var); return true;} + bool Serialize(bool writeToBitstream, long double &var){if (writeToBitstream)Write(var);else return Read(var); return true;} + bool Serialize(bool writeToBitstream, char* var){if (writeToBitstream)Write(var);else return Read(var); return true;} + bool Serialize(bool writeToBitstream, unsigned char* var){if (writeToBitstream)Write(var);else return Read(var); return true;} + bool Serialize(bool writeToBitstream, RakNet::RakString &var){if (writeToBitstream) Write(var);else return Read(var); return true;} + bool Serialize(bool writeToBitstream, uint24_t &var){if (writeToBitstream)Write(var);else return Read(var); return true;} + bool Serialize(bool writeToBitstream, RakNetGUID &var){if (writeToBitstream)Write(var);else return Read(var); return true;} + + /// \brief Serialize a float into 2 bytes, spanning the range between \a floatMin and \a floatMax + /// \param[in] f The float to write + /// \param[in] floatMin Predetermined minimum value of f + /// \param[in] floatMax Predetermined maximum value of f + bool SerializeFloat16(bool writeToBitstream, float &f, float floatMin, float floatMax); + + /// \brief Bidirectional serialize/deserialize any integral type to/from a bitstream. If the current value is different from the last value + /// the current value will be written. Otherwise, a single bit will be written + /// \param[in] writeToBitstream true to write from your data to this bitstream. False to read from this bitstream and write to your data + /// \param[in] currentValue The current value to write + /// \param[in] lastValue The last value to compare against. Only used if \a writeToBitstream is true. + /// \return true if \a writeToBitstream is true. true if \a writeToBitstream is false and the read was successful. false if \a writeToBitstream is false and the read was not successful. + bool SerializeDelta(bool writeToBitstream, bool ¤tValue, bool lastValue){if (writeToBitstream) WriteDelta(currentValue, lastValue); else return ReadDelta(currentValue);return true;} + bool SerializeDelta(bool writeToBitstream, unsigned char ¤tValue, unsigned char lastValue){if (writeToBitstream) WriteDelta(currentValue, lastValue); else return ReadDelta(currentValue);return true;} + bool SerializeDelta(bool writeToBitstream, char ¤tValue, char lastValue){if (writeToBitstream) WriteDelta(currentValue, lastValue); else return ReadDelta(currentValue);return true;} + bool SerializeDelta(bool writeToBitstream, unsigned short ¤tValue, unsigned short lastValue){if (writeToBitstream) WriteDelta(currentValue, lastValue); else return ReadDelta(currentValue);return true;} + bool SerializeDelta(bool writeToBitstream, short ¤tValue, short lastValue){if (writeToBitstream) WriteDelta(currentValue, lastValue); else return ReadDelta(currentValue);return true;} + bool SerializeDelta(bool writeToBitstream, unsigned int ¤tValue, unsigned int lastValue){if (writeToBitstream) WriteDelta(currentValue, lastValue); else return ReadDelta(currentValue);return true;} + bool SerializeDelta(bool writeToBitstream, int ¤tValue, int lastValue){if (writeToBitstream) WriteDelta(currentValue, lastValue); else return ReadDelta(currentValue);return true;} + bool SerializeDelta(bool writeToBitstream, unsigned long ¤tValue, unsigned long lastValue){if (writeToBitstream) WriteDelta(currentValue, lastValue); else return ReadDelta(currentValue);return true;} + bool SerializeDelta(bool writeToBitstream, long long ¤tValue, long long lastValue){if (writeToBitstream) WriteDelta(currentValue, lastValue); else return ReadDelta(currentValue);return true;} + bool SerializeDelta(bool writeToBitstream, unsigned long long ¤tValue, unsigned long long lastValue){if (writeToBitstream) WriteDelta(currentValue, lastValue); else return ReadDelta(currentValue);return true;} + bool SerializeDelta(bool writeToBitstream, float ¤tValue, float lastValue){if (writeToBitstream) WriteDelta(currentValue, lastValue); else return ReadDelta(currentValue);return true;} + bool SerializeDelta(bool writeToBitstream, double ¤tValue, double lastValue){if (writeToBitstream) WriteDelta(currentValue, lastValue); else return ReadDelta(currentValue);return true;} + bool SerializeDelta(bool writeToBitstream, long double ¤tValue, long double lastValue){if (writeToBitstream) WriteDelta(currentValue, lastValue); else return ReadDelta(currentValue);return true;} + bool SerializeDelta(bool writeToBitstream, char* currentValue, const char* lastValue){if (writeToBitstream) WriteDelta(currentValue, lastValue); else return ReadDelta(currentValue);return true;} + bool SerializeDelta(bool writeToBitstream, unsigned char* currentValue, const unsigned char* lastValue){if (writeToBitstream) WriteDelta(currentValue, lastValue); else return ReadDelta(currentValue);return true;} + bool SerializeDelta(bool writeToBitstream, RakNet::RakString ¤tValue, const RakNet::RakString &lastValue){if (writeToBitstream) WriteDelta(currentValue, lastValue); else return ReadDelta(currentValue);return true;} + bool SerializeDelta(bool writeToBitstream, uint24_t ¤tValue, const uint24_t lastValue){if (writeToBitstream) WriteDelta(currentValue, lastValue); else return ReadDelta(currentValue);return true;} + bool SerializeDelta(bool writeToBitstream, RakNetGUID ¤tValue, const RakNetGUID lastValue){if (writeToBitstream) WriteDelta(currentValue, lastValue); else return ReadDelta(currentValue);return true;} + + /// \brief Bidirectional version of SerializeDelta when you don't know what the last value is, or there is no last value. + /// \param[in] writeToBitstream true to write from your data to this bitstream. False to read from this bitstream and write to your data + /// \param[in] currentValue The current value to write + /// \return true if \a writeToBitstream is true. true if \a writeToBitstream is false and the read was successful. false if \a writeToBitstream is false and the read was not successful. + bool SerializeDelta(bool writeToBitstream, bool ¤tValue){if (writeToBitstream) WriteDelta(currentValue); else return ReadDelta(currentValue);return true;} + bool SerializeDelta(bool writeToBitstream, unsigned char ¤tValue){if (writeToBitstream) WriteDelta(currentValue); else return ReadDelta(currentValue);return true;} + bool SerializeDelta(bool writeToBitstream, char ¤tValue){if (writeToBitstream) WriteDelta(currentValue); else return ReadDelta(currentValue);return true;} + bool SerializeDelta(bool writeToBitstream, unsigned short ¤tValue){if (writeToBitstream) WriteDelta(currentValue); else return ReadDelta(currentValue);return true;} + bool SerializeDelta(bool writeToBitstream, short ¤tValue){if (writeToBitstream) WriteDelta(currentValue); else return ReadDelta(currentValue);return true;} + bool SerializeDelta(bool writeToBitstream, unsigned int ¤tValue){if (writeToBitstream) WriteDelta(currentValue); else return ReadDelta(currentValue);return true;} + bool SerializeDelta(bool writeToBitstream, int ¤tValue){if (writeToBitstream) WriteDelta(currentValue); else return ReadDelta(currentValue);return true;} + bool SerializeDelta(bool writeToBitstream, unsigned long ¤tValue){if (writeToBitstream) WriteDelta(currentValue); else return ReadDelta(currentValue);return true;} + bool SerializeDelta(bool writeToBitstream, long long ¤tValue){if (writeToBitstream) WriteDelta(currentValue); else return ReadDelta(currentValue);return true;} + bool SerializeDelta(bool writeToBitstream, unsigned long long ¤tValue){if (writeToBitstream) WriteDelta(currentValue); else return ReadDelta(currentValue);return true;} + bool SerializeDelta(bool writeToBitstream, float ¤tValue){if (writeToBitstream) WriteDelta(currentValue); else return ReadDelta(currentValue);return true;} + bool SerializeDelta(bool writeToBitstream, double ¤tValue){if (writeToBitstream) WriteDelta(currentValue); else return ReadDelta(currentValue);return true;} + bool SerializeDelta(bool writeToBitstream, long double ¤tValue){if (writeToBitstream) WriteDelta(currentValue); else return ReadDelta(currentValue);return true;} + bool SerializeDelta(bool writeToBitstream, char* currentValue){if (writeToBitstream) WriteDelta(currentValue); else return ReadDelta(currentValue);return true;} + bool SerializeDelta(bool writeToBitstream, unsigned char* currentValue){if (writeToBitstream) WriteDelta(currentValue); else return ReadDelta(currentValue);return true;} + bool SerializeDelta(bool writeToBitstream, RakNet::RakString ¤tValue){if (writeToBitstream) WriteDelta(currentValue); else return ReadDelta(currentValue);return true;} + bool SerializeDelta(bool writeToBitstream, uint24_t ¤tValue){if (writeToBitstream) WriteDelta(currentValue); else return ReadDelta(currentValue);return true;} + bool SerializeDelta(bool writeToBitstream, RakNetGUID ¤tValue){if (writeToBitstream) WriteDelta(currentValue); else return ReadDelta(currentValue);return true;} + + /// \brief Bidirectional serialize/deserialize any integral type to/from a bitstream. Undefine __BITSTREAM_NATIVE_END if you need endian swapping. + /// \details If you are not using __BITSTREAM_NATIVE_END the opposite is true for types larger than 1 byte + /// For floating point, this is lossy, using 2 bytes for a float and 4 for a double. The range must be between -1 and +1. + /// For non-floating point, this is lossless, but only has benefit if you use less than half the range of the type + /// \param[in] writeToBitstream true to write from your data to this bitstream. False to read from this bitstream and write to your data + /// \param[in] var The value to write + /// \return true if \a writeToBitstream is true. true if \a writeToBitstream is false and the read was successful. false if \a writeToBitstream is false and the read was not successful. + bool SerializeCompressed(bool writeToBitstream, bool &var){if (writeToBitstream)WriteCompressed(var);else return ReadCompressed(var); return true;} + bool SerializeCompressed(bool writeToBitstream, unsigned char &var){if (writeToBitstream)WriteCompressed(var);else return ReadCompressed(var); return true;} + bool SerializeCompressed(bool writeToBitstream, char &var){if (writeToBitstream)WriteCompressed(var);else return ReadCompressed(var); return true;} + bool SerializeCompressed(bool writeToBitstream, unsigned short &var){if (writeToBitstream)WriteCompressed(var);else return ReadCompressed(var); return true;} + bool SerializeCompressed(bool writeToBitstream, short &var){if (writeToBitstream)WriteCompressed(var);else return ReadCompressed(var); return true;} + bool SerializeCompressed(bool writeToBitstream, unsigned int &var){if (writeToBitstream)WriteCompressed(var);else return ReadCompressed(var); return true;} + bool SerializeCompressed(bool writeToBitstream, int &var){if (writeToBitstream)WriteCompressed(var);else return ReadCompressed(var); return true;} + bool SerializeCompressed(bool writeToBitstream, unsigned long &var){if (writeToBitstream)WriteCompressed(var);else return ReadCompressed(var); return true;} + bool SerializeCompressed(bool writeToBitstream, long &var){if (writeToBitstream)WriteCompressed(var);else return ReadCompressed(var); return true;} + bool SerializeCompressed(bool writeToBitstream, long long &var){if (writeToBitstream)WriteCompressed(var);else return ReadCompressed(var); return true;} + bool SerializeCompressed(bool writeToBitstream, unsigned long long &var){if (writeToBitstream)WriteCompressed(var);else return ReadCompressed(var); return true;} + bool SerializeCompressed(bool writeToBitstream, float &var){if (writeToBitstream)WriteCompressed(var);else return ReadCompressed(var); return true;} + bool SerializeCompressed(bool writeToBitstream, double &var){if (writeToBitstream)WriteCompressed(var);else return ReadCompressed(var); return true;} + bool SerializeCompressed(bool writeToBitstream, long double &var){if (writeToBitstream)WriteCompressed(var);else return ReadCompressed(var); return true;} + bool SerializeCompressed(bool writeToBitstream, char* var){if (writeToBitstream)WriteCompressed(var);else return ReadCompressed(var); return true;} + bool SerializeCompressed(bool writeToBitstream, unsigned char* var){if (writeToBitstream)WriteCompressed(var);else return ReadCompressed(var); return true;} + bool SerializeCompressed(bool writeToBitstream, RakNet::RakString &var){if (writeToBitstream)WriteCompressed(var);else return ReadCompressed(var); return true;} + bool SerializeCompressed(bool writeToBitstream, uint24_t &var){if (writeToBitstream)WriteCompressed(var);else return ReadCompressed(var); return true;} + bool SerializeCompressed(bool writeToBitstream, RakNetGUID &var){if (writeToBitstream)WriteCompressed(var);else return ReadCompressed(var); return true;} + + /// \brief Bidirectional serialize/deserialize any integral type to/from a bitstream. + /// \details If the current value is different from the last value the current value will be written. + /// Otherwise, a single bit will be written + /// For floating point, this is lossy, using 2 bytes for a float and 4 for a double. The range must be between -1 and +1. + /// For non-floating point, this is lossless, but only has benefit if you use less than half the range of the type + /// If you are not using __BITSTREAM_NATIVE_END the opposite is true for types larger than 1 byte + /// \param[in] writeToBitstream true to write from your data to this bitstream. False to read from this bitstream and write to your data + /// \param[in] currentValue The current value to write + /// \param[in] lastValue The last value to compare against. Only used if \a writeToBitstream is true. + /// \return true if \a writeToBitstream is true. true if \a writeToBitstream is false and the read was successful. false if \a writeToBitstream is false and the read was not successful. + bool SerializeCompressedDelta(bool writeToBitstream, bool ¤tValue, bool lastValue){if (writeToBitstream) WriteCompressedDelta(currentValue, lastValue); else return ReadCompressedDelta(currentValue);return true;} + bool SerializeCompressedDelta(bool writeToBitstream, unsigned char ¤tValue, unsigned char lastValue){if (writeToBitstream) WriteCompressedDelta(currentValue, lastValue); else return ReadCompressedDelta(currentValue);return true;} + bool SerializeCompressedDelta(bool writeToBitstream, char ¤tValue, char lastValue){if (writeToBitstream) WriteCompressedDelta(currentValue, lastValue); else return ReadCompressedDelta(currentValue);return true;} + bool SerializeCompressedDelta(bool writeToBitstream, unsigned short ¤tValue, unsigned short lastValue){if (writeToBitstream) WriteCompressedDelta(currentValue, lastValue); else return ReadCompressedDelta(currentValue);return true;} + bool SerializeCompressedDelta(bool writeToBitstream, short ¤tValue, short lastValue){if (writeToBitstream) WriteCompressedDelta(currentValue, lastValue); else return ReadCompressedDelta(currentValue);return true;} + bool SerializeCompressedDelta(bool writeToBitstream, unsigned int ¤tValue, unsigned int lastValue){if (writeToBitstream) WriteCompressedDelta(currentValue, lastValue); else return ReadCompressedDelta(currentValue);return true;} + bool SerializeCompressedDelta(bool writeToBitstream, int ¤tValue, int lastValue){if (writeToBitstream) WriteCompressedDelta(currentValue, lastValue); else return ReadCompressedDelta(currentValue);return true;} + bool SerializeCompressedDelta(bool writeToBitstream, unsigned long ¤tValue, unsigned long lastValue){if (writeToBitstream) WriteCompressedDelta(currentValue, lastValue); else return ReadCompressedDelta(currentValue);return true;} + bool SerializeCompressedDelta(bool writeToBitstream, long long ¤tValue, long long lastValue){if (writeToBitstream) WriteCompressedDelta(currentValue, lastValue); else return ReadCompressedDelta(currentValue);return true;} + bool SerializeCompressedDelta(bool writeToBitstream, unsigned long long ¤tValue, unsigned long long lastValue){if (writeToBitstream) WriteCompressedDelta(currentValue, lastValue); else return ReadCompressedDelta(currentValue);return true;} + bool SerializeCompressedDelta(bool writeToBitstream, float ¤tValue, float lastValue){if (writeToBitstream) WriteCompressedDelta(currentValue, lastValue); else return ReadCompressedDelta(currentValue);return true;} + bool SerializeCompressedDelta(bool writeToBitstream, double ¤tValue, double lastValue){if (writeToBitstream) WriteCompressedDelta(currentValue, lastValue); else return ReadCompressedDelta(currentValue);return true;} + bool SerializeCompressedDelta(bool writeToBitstream, long double ¤tValue, long double lastValue){if (writeToBitstream) WriteCompressedDelta(currentValue, lastValue); else return ReadCompressedDelta(currentValue);return true;} + bool SerializeCompressedDelta(bool writeToBitstream, char*currentValue, const char* lastValue){if (writeToBitstream) WriteCompressedDelta(currentValue, lastValue); else return ReadCompressedDelta(currentValue);return true;} + bool SerializeCompressedDelta(bool writeToBitstream, unsigned char* currentValue, const unsigned char* lastValue){if (writeToBitstream) WriteCompressedDelta(currentValue, lastValue); else return ReadCompressedDelta(currentValue);return true;} + bool SerializeCompressedDelta(bool writeToBitstream, RakNet::RakString ¤tValue, const RakNet::RakString &lastValue){if (writeToBitstream) WriteCompressedDelta(currentValue, lastValue); else return ReadCompressedDelta(currentValue);return true;} + bool SerializeCompressedDelta(bool writeToBitstream, uint24_t ¤tValue, uint24_t &lastValue){if (writeToBitstream) WriteCompressedDelta(currentValue, lastValue); else return ReadCompressedDelta(currentValue);return true;} + bool SerializeCompressedDelta(bool writeToBitstream, RakNetGUID ¤tValue, RakNetGUID &lastValue){if (writeToBitstream) WriteCompressedDelta(currentValue, lastValue); else return ReadCompressedDelta(currentValue);return true;} + + /// Save as SerializeCompressedDelta(templateType ¤tValue, templateType lastValue) when we have an unknown second parameter + bool SerializeCompressedDelta(bool writeToBitstream, bool &var){if (writeToBitstream)WriteCompressedDelta(var);else return ReadCompressedDelta(var); return true;} + bool SerializeCompressedDelta(bool writeToBitstream, unsigned char &var){if (writeToBitstream)WriteCompressedDelta(var);else return ReadCompressedDelta(var); return true;} + bool SerializeCompressedDelta(bool writeToBitstream, char &var){if (writeToBitstream)WriteCompressedDelta(var);else return ReadCompressedDelta(var); return true;} + bool SerializeCompressedDelta(bool writeToBitstream, unsigned short &var){if (writeToBitstream)WriteCompressedDelta(var);else return ReadCompressedDelta(var); return true;} + bool SerializeCompressedDelta(bool writeToBitstream, short &var){if (writeToBitstream)WriteCompressedDelta(var);else return ReadCompressedDelta(var); return true;} + bool SerializeCompressedDelta(bool writeToBitstream, unsigned int &var){if (writeToBitstream)WriteCompressedDelta(var);else return ReadCompressedDelta(var); return true;} + bool SerializeCompressedDelta(bool writeToBitstream, int &var){if (writeToBitstream)WriteCompressedDelta(var);else return ReadCompressedDelta(var); return true;} + bool SerializeCompressedDelta(bool writeToBitstream, unsigned long &var){if (writeToBitstream)WriteCompressedDelta(var);else return ReadCompressedDelta(var); return true;} + bool SerializeCompressedDelta(bool writeToBitstream, long &var){if (writeToBitstream)WriteCompressedDelta(var);else return ReadCompressedDelta(var); return true;} + bool SerializeCompressedDelta(bool writeToBitstream, long long &var){if (writeToBitstream)WriteCompressedDelta(var);else return ReadCompressedDelta(var); return true;} + bool SerializeCompressedDelta(bool writeToBitstream, unsigned long long &var){if (writeToBitstream)WriteCompressedDelta(var);else return ReadCompressedDelta(var); return true;} + bool SerializeCompressedDelta(bool writeToBitstream, float &var){if (writeToBitstream)WriteCompressedDelta(var);else return ReadCompressedDelta(var); return true;} + bool SerializeCompressedDelta(bool writeToBitstream, double &var){if (writeToBitstream)WriteCompressedDelta(var);else return ReadCompressedDelta(var); return true;} + bool SerializeCompressedDelta(bool writeToBitstream, long double &var){if (writeToBitstream)WriteCompressedDelta(var);else return ReadCompressedDelta(var); return true;} + bool SerializeCompressedDelta(bool writeToBitstream, char* var){if (writeToBitstream)WriteCompressedDelta(var);else return ReadCompressedDelta(var); return true;} + bool SerializeCompressedDelta(bool writeToBitstream, unsigned char* var){if (writeToBitstream)WriteCompressedDelta(var);else return ReadCompressedDelta(var); return true;} + bool SerializeCompressedDelta(bool writeToBitstream, RakNet::RakString &var){if (writeToBitstream)WriteCompressedDelta(var);else return ReadCompressedDelta(var); return true;} + bool SerializeCompressedDelta(bool writeToBitstream, uint24_t &var){if (writeToBitstream)WriteCompressedDelta(var);else return ReadCompressedDelta(var); return true;} + bool SerializeCompressedDelta(bool writeToBitstream, RakNetGUID &var){if (writeToBitstream)WriteCompressedDelta(var);else return ReadCompressedDelta(var); return true;} + + /// \brief Bidirectional serialize/deserialize an array or casted stream or raw data. This does NOT do endian swapping. + /// \param[in] writeToBitstream true to write from your data to this bitstream. False to read from this bitstream and write to your data + /// \param[in] input a byte buffer + /// \param[in] numberOfBytes the size of \a input in bytes + /// \return true if \a writeToBitstream is true. true if \a writeToBitstream is false and the read was successful. false if \a writeToBitstream is false and the read was not successful. + bool Serialize(bool writeToBitstream, char* input, const int numberOfBytes ); + + /// \brief Bidirectional serialize/deserialize a normalized 3D vector, using (at most) 4 bytes + 3 bits instead of 12-24 bytes. + /// \details Will further compress y or z axis aligned vectors. + /// Accurate to 1/32767.5. + /// \param[in] writeToBitstream true to write from your data to this bitstream. False to read from this bitstream and write to your data + /// \param[in] x x + /// \param[in] y y + /// \param[in] z z + /// \return true if \a writeToBitstream is true. true if \a writeToBitstream is false and the read was successful. false if \a writeToBitstream is false and the read was not successful. + bool SerializeNormVector(bool writeToBitstream, float &x, float &y, float z ){if (writeToBitstream) WriteNormVector(x,y,z); else return ReadNormVector(x,y,z); return true;} + bool SerializeNormVector(bool writeToBitstream, double &x, double &y, double &z ){if (writeToBitstream) WriteNormVector(x,y,z); else return ReadNormVector(x,y,z); return true;} + + /// \brief Bidirectional serialize/deserialize a vector, using 10 bytes instead of 12. + /// \details Loses accuracy to about 3/10ths and only saves 2 bytes, so only use if accuracy is not important. + /// \param[in] writeToBitstream true to write from your data to this bitstream. False to read from this bitstream and write to your data + /// \param[in] x x + /// \param[in] y y + /// \param[in] z z + /// \return true if \a writeToBitstream is true. true if \a writeToBitstream is false and the read was successful. false if \a writeToBitstream is false and the read was not successful. + bool SerializeVector(bool writeToBitstream, float &x, float &y, float &z ){if (writeToBitstream) WriteVector(x,y,z); else return ReadVector(x,y,z); return true;} + bool SerializeVector(bool writeToBitstream, double &x, double &y, double &z ){if (writeToBitstream) WriteVector(x,y,z); else return ReadVector(x,y,z); return true;} + + /// \brief Bidirectional serialize/deserialize a normalized quaternion in 6 bytes + 4 bits instead of 16 bytes. Slightly lossy. + /// \param[in] writeToBitstream true to write from your data to this bitstream. False to read from this bitstream and write to your data + /// \param[in] w w + /// \param[in] x x + /// \param[in] y y + /// \param[in] z z + /// \return true if \a writeToBitstream is true. true if \a writeToBitstream is false and the read was successful. false if \a writeToBitstream is false and the read was not successful. + bool SerializeNormQuat(bool writeToBitstream, float &w, float &x, float &y, float &z){if (writeToBitstream) WriteNormQuat(w,x,y,z); else return ReadNormQuat(w,x,y,z); return true;} + bool SerializeNormQuat(bool writeToBitstream, double &w, double &x, double &y, double &z){if (writeToBitstream) WriteNormQuat(w,x,y,z); else return ReadNormQuat(w,x,y,z); return true;} + + /// \brief Bidirectional serialize/deserialize an orthogonal matrix by creating a quaternion, and writing 3 components of the quaternion in 2 bytes each + /// for 6 bytes instead of 36 + /// Lossy, although the result is renormalized + bool SerializeOrthMatrix( + bool writeToBitstream, + float &m00, float &m01, float &m02, + float &m10, float &m11, float &m12, + float &m20, float &m21, float &m22 ){if (writeToBitstream) WriteOrthMatrix(m00,m01,m02,m10,m11,m12,m20,m21,m22); else return ReadOrthMatrix(m00,m01,m02,m10,m11,m12,m20,m21,m22); return true;} + bool SerializeOrthMatrix( + bool writeToBitstream, + double &m00, double &m01, double &m02, + double &m10, double &m11, double &m12, + double &m20, double &m21, double &m22 ){if (writeToBitstream) WriteOrthMatrix(m00,m01,m02,m10,m11,m12,m20,m21,m22); else return ReadOrthMatrix(m00,m01,m02,m10,m11,m12,m20,m21,m22); return true;} + + /// \brief Bidirectional serialize/deserialize numberToSerialize bits to/from the input. + /// \details Right aligned data means in the case of a partial byte, the bits are aligned + /// from the right (bit 0) rather than the left (as in the normal + /// internal representation) You would set this to true when + /// writing user data, and false when copying bitstream data, such + /// as writing one bitstream to another + /// \param[in] writeToBitstream true to write from your data to this bitstream. False to read from this bitstream and write to your data + /// \param[in] input The data + /// \param[in] numberOfBitsToSerialize The number of bits to write + /// \param[in] rightAlignedBits if true data will be right aligned + /// \return true if \a writeToBitstream is true. true if \a writeToBitstream is false and the read was successful. false if \a writeToBitstream is false and the read was not successful. + bool SerializeBits(bool writeToBitstream, unsigned char* input, int numberOfBitsToSerialize, const bool rightAlignedBits = true ); + + /// Write any integral type to a bitstream. Undefine __BITSTREAM_NATIVE_END if you need endian swapping. + /// \param[in] var The value to write + void Write(bool var){if ( var ) Write1(); else Write0();} + void Write(unsigned char var){WriteBits( ( unsigned char* ) & var, sizeof( unsigned char ) * 8, true );} + void Write(char var){WriteBits( ( unsigned char* ) & var, sizeof( char ) * 8, true );} + void Write(unsigned short var) {if (DoEndianSwap()){unsigned char output[sizeof(unsigned short)]; ReverseBytes((unsigned char*)&var, output, sizeof(unsigned short)); WriteBits( ( unsigned char* ) output, sizeof(unsigned short) * 8, true );return;} WriteBits( ( unsigned char* ) & var, sizeof(unsigned short) * 8, true );} + void Write(short var) {if (DoEndianSwap()){unsigned char output[sizeof(short)]; ReverseBytes((unsigned char*)&var, output, sizeof(short)); WriteBits( ( unsigned char* ) output, sizeof(short) * 8, true );return;} WriteBits( ( unsigned char* ) & var, sizeof(short) * 8, true );} + void Write(unsigned int var) {if (DoEndianSwap()){unsigned char output[sizeof(unsigned int)]; ReverseBytes((unsigned char*)&var, output, sizeof(unsigned int)); WriteBits( ( unsigned char* ) output, sizeof(unsigned int) * 8, true );return;} WriteBits( ( unsigned char* ) & var, sizeof(unsigned int) * 8, true );} + void Write(int var) {if (DoEndianSwap()){unsigned char output[sizeof(int)]; ReverseBytes((unsigned char*)&var, output, sizeof(int)); WriteBits( ( unsigned char* ) output, sizeof(int) * 8, true );return;} WriteBits( ( unsigned char* ) & var, sizeof(int) * 8, true );} + void Write(unsigned long var) {if (DoEndianSwap()){unsigned char output[sizeof(unsigned long)]; ReverseBytes((unsigned char*)&var, output, sizeof(unsigned long)); WriteBits( ( unsigned char* ) output, sizeof(unsigned long) * 8, true );return;} WriteBits( ( unsigned char* ) & var, sizeof(unsigned long) * 8, true );} + void Write(long var) {if (DoEndianSwap()){unsigned char output[sizeof(long)]; ReverseBytes((unsigned char*)&var, output, sizeof(long)); WriteBits( ( unsigned char* ) output, sizeof(long) * 8, true );return;} WriteBits( ( unsigned char* ) & var, sizeof(long) * 8, true );} + void Write(long long var) {if (DoEndianSwap()){unsigned char output[sizeof(long long)]; ReverseBytes((unsigned char*)&var, output, sizeof(long long)); WriteBits( ( unsigned char* ) output, sizeof(long long) * 8, true );return;} WriteBits( ( unsigned char* ) & var, sizeof(long long) * 8, true );} + void Write(unsigned long long var) {if (DoEndianSwap()){unsigned char output[sizeof(unsigned long long)]; ReverseBytes((unsigned char*)&var, output, sizeof(unsigned long long)); WriteBits( ( unsigned char* ) output, sizeof(unsigned long long) * 8, true );return;} WriteBits( ( unsigned char* ) & var, sizeof(unsigned long long) * 8, true );} + void Write(float var) {if (DoEndianSwap()){unsigned char output[sizeof(float)]; ReverseBytes((unsigned char*)&var, output, sizeof(float)); WriteBits( ( unsigned char* ) output, sizeof(float) * 8, true );return;} WriteBits( ( unsigned char* ) & var, sizeof(float) * 8, true );} + void Write(double var) {if (DoEndianSwap()){unsigned char output[sizeof(double)]; ReverseBytes((unsigned char*)&var, output, sizeof(double)); WriteBits( ( unsigned char* ) output, sizeof(double) * 8, true );return;} WriteBits( ( unsigned char* ) & var, sizeof(double) * 8, true );} + void Write(long double var) {if (DoEndianSwap()){unsigned char output[sizeof(long double)]; ReverseBytes((unsigned char*)&var, output, sizeof(long double)); WriteBits( ( unsigned char* ) output, sizeof(long double) * 8, true );return;} WriteBits( ( unsigned char* ) & var, sizeof(long double) * 8, true );} + void Write(const char* var) {RakString::Serialize(var, this);} + void Write(const unsigned char* var) {RakString::Serialize((const char*) var, this);} + void Write(const RakNet::RakString &var) {var.Serialize(this);} + void Write(const uint24_t &var); + void Write(const RakNetGUID &var) {if (DoEndianSwap()){unsigned char output[sizeof(uint64_t)]; ReverseBytes((unsigned char*)&var, output, sizeof(uint64_t)); WriteBits( ( unsigned char* ) output, sizeof(uint64_t) * 8, true );return;} WriteBits( ( unsigned char* ) & var, sizeof(uint64_t) * 8, true );} + void Write(void* var) {if (DoEndianSwap()){unsigned char output[sizeof(void*)]; ReverseBytes((unsigned char*)&var, output, sizeof(void*)); WriteBits( ( unsigned char* ) output, sizeof(void*) * 8, true );return;} WriteBits( ( unsigned char* ) & var, sizeof(void*) * 8, true );} + void Write(SystemAddress var){WriteBits( ( unsigned char* ) & var.binaryAddress, sizeof(var.binaryAddress) * 8, true ); Write(var.port);} + void Write(NetworkID var){if (NetworkID::IsPeerToPeerMode()) Write(var.systemAddress); Write(var.localSystemAddress);} + + /// \brief Write any integral type to a bitstream. + /// \details If the current value is different from the last value + /// the current value will be written. Otherwise, a single bit will be written + /// \param[in] currentValue The current value to write + /// \param[in] lastValue The last value to compare against + void WriteDelta(bool currentValue, bool lastValue){ + #pragma warning(disable:4100) // warning C4100: 'peer' : unreferenced formal parameter + Write(currentValue); + } + void WriteDelta(unsigned char currentValue, unsigned char lastValue){if (currentValue==lastValue) {Write(false);} else {Write(true); Write(currentValue);}} + void WriteDelta(char currentValue, char lastValue){if (currentValue==lastValue) {Write(false);} else {Write(true); Write(currentValue);}} + void WriteDelta(unsigned short currentValue, unsigned short lastValue){if (currentValue==lastValue) {Write(false);} else {Write(true); Write(currentValue);}} + void WriteDelta(short currentValue, short lastValue){if (currentValue==lastValue) {Write(false);} else {Write(true); Write(currentValue);}} + void WriteDelta(unsigned int currentValue, unsigned int lastValue){if (currentValue==lastValue) {Write(false);} else {Write(true); Write(currentValue);}} + void WriteDelta(int currentValue, int lastValue){if (currentValue==lastValue) {Write(false);} else {Write(true); Write(currentValue);}} + void WriteDelta(unsigned long currentValue, unsigned long lastValue){if (currentValue==lastValue) {Write(false);} else {Write(true); Write(currentValue);}} + void WriteDelta(long currentValue, long lastValue){if (currentValue==lastValue) {Write(false);} else {Write(true); Write(currentValue);}} + void WriteDelta(long long currentValue, long long lastValue){if (currentValue==lastValue) {Write(false);} else {Write(true); Write(currentValue);}} + void WriteDelta(unsigned long long currentValue, unsigned long long lastValue){if (currentValue==lastValue) {Write(false);} else {Write(true); Write(currentValue);}} + void WriteDelta(float currentValue, float lastValue){if (currentValue==lastValue) {Write(false);} else {Write(true); Write(currentValue);}} + void WriteDelta(double currentValue, double lastValue){if (currentValue==lastValue) {Write(false);} else {Write(true); Write(currentValue);}} + void WriteDelta(long double currentValue, long double lastValue){if (currentValue==lastValue) {Write(false);} else {Write(true); Write(currentValue);}} + void WriteDelta(const char* currentValue, const char* lastValue){if (currentValue==lastValue) {Write(false);} else {Write(true); Write(currentValue);}} + void WriteDelta(const unsigned char* currentValue, const unsigned char* lastValue){if (currentValue==lastValue) {Write(false);} else {Write(true); Write(currentValue);}} + void WriteDelta(char* currentValue, char* lastValue){if (currentValue==lastValue) {Write(false);} else {Write(true); Write(currentValue);}} + void WriteDelta(unsigned char* currentValue, unsigned char* lastValue){if (currentValue==lastValue) {Write(false);} else {Write(true); Write(currentValue);}} + void WriteDelta(const RakNet::RakString ¤tValue, const RakNet::RakString &lastValue){if (currentValue==lastValue) {Write(false);} else {Write(true); Write(currentValue);}} + void WriteDelta(const uint24_t ¤tValue, const uint24_t &lastValue){if (currentValue==lastValue) {Write(false);} else {Write(true); Write(currentValue);}} + void WriteDelta(const RakNetGUID ¤tValue, const RakNetGUID &lastValue){if (currentValue==lastValue) {Write(false);} else {Write(true); Write(currentValue);}} + void WriteDelta(SystemAddress currentValue, SystemAddress lastValue){if (currentValue==lastValue) {Write(false);} else {Write(true); Write(currentValue);}} + void WriteDelta(NetworkID currentValue, NetworkID lastValue){if (currentValue==lastValue) {Write(false);} else {Write(true); Write(currentValue);}} + + /// WriteDelta when you don't know what the last value is, or there is no last value. + /// \param[in] currentValue The current value to write + void WriteDelta(bool var){Write(var);} + void WriteDelta(unsigned char var){Write(true); Write(var);} + void WriteDelta(char var){Write(true); Write(var);} + void WriteDelta(unsigned short var){Write(true); Write(var);} + void WriteDelta(short var){Write(true); Write(var);} + void WriteDelta(unsigned int var){Write(true); Write(var);} + void WriteDelta(int var){Write(true); Write(var);} + void WriteDelta(unsigned long var){Write(true); Write(var);} + void WriteDelta(long var){Write(true); Write(var);} + void WriteDelta(long long var){Write(true); Write(var);} + void WriteDelta(unsigned long long var){Write(true); Write(var);} + void WriteDelta(float var){Write(true); Write(var);} + void WriteDelta(double var){Write(true); Write(var);} + void WriteDelta(long double var){Write(true); Write(var);} + void WriteDelta(const char* var){Write(true); Write(var);} + void WriteDelta(const unsigned char* var){Write(true); Write(var);} + void WriteDelta(char* var){Write(true); Write(var);} + void WriteDelta(unsigned char* var){Write(true); Write(var);} + void WriteDelta(const RakNet::RakString &var){Write(true); Write(var);} + void WriteDelta(const uint24_t &var){Write(true); Write(var);} + void WriteDelta(const RakNetGUID &var){Write(true); Write(var);} + void WriteDelta(SystemAddress var){Write(true); Write(var);} + void WriteDelta(NetworkID var){Write(true); Write(var);} + + /// \brief Write any integral type to a bitstream. + /// \details Undefine __BITSTREAM_NATIVE_END if you need endian swapping. + /// If you are not using __BITSTREAM_NATIVE_END the opposite is true for types larger than 1 byte + /// For floating point, this is lossy, using 2 bytes for a float and 4 for a double. The range must be between -1 and +1. + /// For non-floating point, this is lossless, but only has benefit if you use less than half the range of the type + /// \param[in] var The value to write + void WriteCompressed(bool var) {Write(var);} + void WriteCompressed(unsigned char var) {WriteCompressed( ( unsigned char* ) & var, sizeof( unsigned char ) * 8, true );} + void WriteCompressed(char var) {WriteCompressed( (unsigned char* ) & var, sizeof( unsigned char ) * 8, true );} + void WriteCompressed(unsigned short var) {if (DoEndianSwap()) {unsigned char output[sizeof(unsigned short)]; ReverseBytes((unsigned char*)&var, output, sizeof(unsigned short)); WriteCompressed( ( unsigned char* ) output, sizeof(unsigned short) * 8, true );} else WriteCompressed( ( unsigned char* ) & var, sizeof(unsigned short) * 8, true );} + void WriteCompressed(short var) {if (DoEndianSwap()) {unsigned char output[sizeof(short)]; ReverseBytes((unsigned char*)&var, output, sizeof(short)); WriteCompressed( ( unsigned char* ) output, sizeof(short) * 8, true );} else WriteCompressed( ( unsigned char* ) & var, sizeof(short) * 8, true );} + void WriteCompressed(unsigned int var) {if (DoEndianSwap()) {unsigned char output[sizeof(unsigned int)]; ReverseBytes((unsigned char*)&var, output, sizeof(unsigned int)); WriteCompressed( ( unsigned char* ) output, sizeof(unsigned int) * 8, true );} else WriteCompressed( ( unsigned char* ) & var, sizeof(unsigned int) * 8, true );} + void WriteCompressed(int var) {if (DoEndianSwap()) { unsigned char output[sizeof(int)]; ReverseBytes((unsigned char*)&var, output, sizeof(int)); WriteCompressed( ( unsigned char* ) output, sizeof(int) * 8, true );} else WriteCompressed( ( unsigned char* ) & var, sizeof(int) * 8, true );} + void WriteCompressed(unsigned long var) {if (DoEndianSwap()) {unsigned char output[sizeof(unsigned long)]; ReverseBytes((unsigned char*)&var, output, sizeof(unsigned long)); WriteCompressed( ( unsigned char* ) output, sizeof(unsigned long) * 8, true );} else WriteCompressed( ( unsigned char* ) & var, sizeof(unsigned long) * 8, true );} + void WriteCompressed(long var) {if (DoEndianSwap()) {unsigned char output[sizeof(long)]; ReverseBytes((unsigned char*)&var, output, sizeof(long)); WriteCompressed( ( unsigned char* ) output, sizeof(long) * 8, true );} else WriteCompressed( ( unsigned char* ) & var, sizeof(long) * 8, true );} + void WriteCompressed(long long var) {if (DoEndianSwap()) {unsigned char output[sizeof(long long)]; ReverseBytes((unsigned char*)&var, output, sizeof(long long)); WriteCompressed( ( unsigned char* ) output, sizeof(long long) * 8, true );} else WriteCompressed( ( unsigned char* ) & var, sizeof(long long) * 8, true );} + void WriteCompressed(unsigned long long var) {if (DoEndianSwap()) { unsigned char output[sizeof(unsigned long long)]; ReverseBytes((unsigned char*)&var, output, sizeof(unsigned long long)); WriteCompressed( ( unsigned char* ) output, sizeof(unsigned long long) * 8, true );} else WriteCompressed( ( unsigned char* ) & var, sizeof(unsigned long long) * 8, true );} + void WriteCompressed(float var) {RakAssert(var > -1.01f && var < 1.01f); if (var < -1.0f) var=-1.0f; if (var > 1.0f) var=1.0f; Write((unsigned short)((var+1.0f)*32767.5f));} + void WriteCompressed(double var) {RakAssert(var > -1.01 && var < 1.01); if (var < -1.0) var=-1.0; if (var > 1.0) var=1.0; Write((unsigned long)((var+1.0)*2147483648.0));} + void WriteCompressed(long double var) {RakAssert(var > -1.01 && var < 1.01); if (var < -1.0) var=-1.0; if (var > 1.0) var=1.0; Write((unsigned long)((var+1.0)*2147483648.0));} + void WriteCompressed(const char* var) {RakString::SerializeCompressed(var,this);} + void WriteCompressed(const unsigned char* var) {RakString::SerializeCompressed((const char*) var,this);} + void WriteCompressed(const RakNet::RakString &var) {var.SerializeCompressed(this);} + void WriteCompressed(const uint24_t &var) {Write(var);} + void WriteCompressed(const RakNetGUID &var) {if (DoEndianSwap()) {unsigned char output[sizeof(uint64_t)]; ReverseBytes((unsigned char*)&var, output, sizeof(uint64_t)); WriteCompressed( ( unsigned char* ) output, sizeof(uint64_t) * 8, true );} else WriteCompressed( ( unsigned char* ) & var, sizeof(uint64_t) * 8, true );} + + /// \brief Write any integral type to a bitstream. + /// \details If the current value is different from the last value + /// the current value will be written. Otherwise, a single bit will be written + /// For floating point, this is lossy, using 2 bytes for a float and 4 for a double. The range must be between -1 and +1. + /// For non-floating point, this is lossless, but only has benefit if you use less than half the range of the type + /// If you are not using __BITSTREAM_NATIVE_END the opposite is true for types larger than 1 byte + /// \param[in] currentValue The current value to write + /// \param[in] lastValue The last value to compare against + void WriteCompressedDelta(bool currentValue, bool lastValue) + { + #pragma warning(disable:4100) // warning C4100: 'peer' : unreferenced formal parameter + Write(currentValue); + } + void WriteCompressedDelta(unsigned char currentValue, unsigned char lastValue){if (currentValue==lastValue) {Write(false);} else { Write(true); WriteCompressed(currentValue);}} + void WriteCompressedDelta(char currentValue, char lastValue){if (currentValue==lastValue) {Write(false);} else { Write(true); WriteCompressed(currentValue);}} + void WriteCompressedDelta(unsigned short currentValue, unsigned short lastValue){if (currentValue==lastValue) {Write(false);} else { Write(true); WriteCompressed(currentValue);}} + void WriteCompressedDelta(short currentValue, short lastValue){if (currentValue==lastValue) {Write(false);} else { Write(true); WriteCompressed(currentValue);}} + void WriteCompressedDelta(unsigned int currentValue, unsigned int lastValue){if (currentValue==lastValue) {Write(false);} else { Write(true); WriteCompressed(currentValue);}} + void WriteCompressedDelta(int currentValue, int lastValue){if (currentValue==lastValue) {Write(false);} else { Write(true); WriteCompressed(currentValue);}} + void WriteCompressedDelta(unsigned long currentValue, unsigned long lastValue){if (currentValue==lastValue) {Write(false);} else { Write(true); WriteCompressed(currentValue);}} + void WriteCompressedDelta(long currentValue, long lastValue){if (currentValue==lastValue) {Write(false);} else { Write(true); WriteCompressed(currentValue);}} + void WriteCompressedDelta(long long currentValue, long long lastValue){if (currentValue==lastValue) {Write(false);} else { Write(true); WriteCompressed(currentValue);}} + void WriteCompressedDelta(unsigned long long currentValue, unsigned long long lastValue){if (currentValue==lastValue) {Write(false);} else { Write(true); WriteCompressed(currentValue);}} + void WriteCompressedDelta(float currentValue, float lastValue){if (currentValue==lastValue) {Write(false);} else { Write(true); WriteCompressed(currentValue);}} + void WriteCompressedDelta(double currentValue, double lastValue){if (currentValue==lastValue) {Write(false);} else { Write(true); WriteCompressed(currentValue);}} + void WriteCompressedDelta(long double currentValue, long double lastValue){if (currentValue==lastValue) {Write(false);} else { Write(true); WriteCompressed(currentValue);}} + void WriteCompressedDelta(const char* currentValue, const char* lastValue){if (currentValue==lastValue) {Write(false);} else { Write(true); WriteCompressed(currentValue);}} + void WriteCompressedDelta(const unsigned char* currentValue, const unsigned char* lastValue){if (currentValue==lastValue) {Write(false);} else { Write(true); WriteCompressed(currentValue);}} + void WriteCompressedDelta(char* currentValue, char* lastValue){if (currentValue==lastValue) {Write(false);} else { Write(true); WriteCompressed(currentValue);}} + void WriteCompressedDelta(unsigned char* currentValue, unsigned char* lastValue){if (currentValue==lastValue) {Write(false);} else { Write(true); WriteCompressed(currentValue);}} + void WriteCompressedDelta(const RakNet::RakString ¤tValue, const RakNet::RakString &lastValue){if (currentValue==lastValue) {Write(false);} else { Write(true); WriteCompressed(currentValue);}} + void WriteCompressedDelta(const uint24_t ¤tValue, const uint24_t &lastValue){if (currentValue==lastValue) {Write(false);} else { Write(true); WriteCompressed(currentValue);}} + void WriteCompressedDelta(const RakNetGUID ¤tValue, const RakNetGUID &lastValue){if (currentValue==lastValue) {Write(false);} else { Write(true); WriteCompressed(currentValue);}} + + /// Save as WriteCompressedDelta(templateType currentValue, templateType lastValue) when we have an unknown second parameter + void WriteCompressedDelta(bool var) {Write(var);} + void WriteCompressedDelta(unsigned char var) { Write(true); WriteCompressed(var); } + void WriteCompressedDelta(char var) { Write(true); WriteCompressed(var); } + void WriteCompressedDelta(unsigned short var) { Write(true); WriteCompressed(var); } + void WriteCompressedDelta(short var) { Write(true); WriteCompressed(var); } + void WriteCompressedDelta(unsigned int var) { Write(true); WriteCompressed(var); } + void WriteCompressedDelta(int var) { Write(true); WriteCompressed(var); } + void WriteCompressedDelta(unsigned long var) { Write(true); WriteCompressed(var); } + void WriteCompressedDelta(long var) { Write(true); WriteCompressed(var); } + void WriteCompressedDelta(long long var) { Write(true); WriteCompressed(var); } + void WriteCompressedDelta(unsigned long long var) { Write(true); WriteCompressed(var); } + void WriteCompressedDelta(float var) { Write(true); WriteCompressed(var); } + void WriteCompressedDelta(double var) { Write(true); WriteCompressed(var); } + void WriteCompressedDelta(long double var) { Write(true); WriteCompressed(var); } + void WriteCompressedDelta(const char* var) { Write(true); WriteCompressed(var); } + void WriteCompressedDelta(const unsigned char* var) { Write(true); WriteCompressed(var); } + void WriteCompressedDelta(char* var) { Write(true); WriteCompressed(var); } + void WriteCompressedDelta(unsigned char* var) { Write(true); WriteCompressed(var); } + void WriteCompressedDelta(const RakNet::RakString &var) { Write(true); WriteCompressed(var); } + void WriteCompressedDelta(const uint24_t &var) { Write(true); WriteCompressed(var); } + void WriteCompressedDelta(const RakNetGUID &var) { Write(true); WriteCompressed(var); } + + /// \brief Read any integral type from a bitstream. + /// \details Define __BITSTREAM_NATIVE_END if you need endian swapping. + /// \param[in] var The value to read + bool Read(bool &var){if ( readOffset + 1 > numberOfBitsUsed ) return false; + if ( data[ readOffset >> 3 ] & ( 0x80 >> ( readOffset & 7 ) ) ) + var = true; + else + var = false; + // Has to be on a different line for Mac + readOffset++; + return true; + } + bool Read(unsigned char &var) {return ReadBits( ( unsigned char* ) &var, sizeof(unsigned char) * 8, true );} + bool Read(char &var) {return ReadBits( ( unsigned char* ) &var, sizeof(char) * 8, true );} + bool Read(unsigned short &var) {if (DoEndianSwap()){unsigned char output[sizeof(unsigned short)]; if (ReadBits( ( unsigned char* ) output, sizeof(unsigned short) * 8, true )) { ReverseBytes(output, (unsigned char*)&var, sizeof(unsigned short)); return true;} return false;} else return ReadBits( ( unsigned char* ) & var, sizeof(unsigned short) * 8, true );} + bool Read(short &var) {if (DoEndianSwap()){unsigned char output[sizeof(short)]; if (ReadBits( ( unsigned char* ) output, sizeof(short) * 8, true )) { ReverseBytes(output, (unsigned char*)&var, sizeof(short)); return true;} return false;} else return ReadBits( ( unsigned char* ) & var, sizeof(short) * 8, true );} + bool Read(unsigned int &var) {if (DoEndianSwap()){unsigned char output[sizeof(unsigned int)]; if (ReadBits( ( unsigned char* ) output, sizeof(unsigned int) * 8, true )) { ReverseBytes(output, (unsigned char*)&var, sizeof(unsigned int)); return true;} return false;} else return ReadBits( ( unsigned char* ) & var, sizeof(unsigned int) * 8, true );} + bool Read(int &var) {if (DoEndianSwap()){unsigned char output[sizeof(int)]; if (ReadBits( ( unsigned char* ) output, sizeof(int) * 8, true )) { ReverseBytes(output, (unsigned char*)&var, sizeof(int)); return true;} return false;} else return ReadBits( ( unsigned char* ) & var, sizeof(int) * 8, true );} + bool Read(unsigned long &var) {if (DoEndianSwap()){unsigned char output[sizeof(unsigned long)]; if (ReadBits( ( unsigned char* ) output, sizeof(unsigned long) * 8, true )) { ReverseBytes(output, (unsigned char*)&var, sizeof(unsigned long)); return true;} return false;} else return ReadBits( ( unsigned char* ) & var, sizeof(unsigned long) * 8, true );} + bool Read(long &var) {if (DoEndianSwap()){unsigned char output[sizeof(long)]; if (ReadBits( ( unsigned char* ) output, sizeof(long) * 8, true )) { ReverseBytes(output, (unsigned char*)&var, sizeof(long)); return true;} return false;} else return ReadBits( ( unsigned char* ) & var, sizeof(long) * 8, true );} + bool Read(long long &var) {if (DoEndianSwap()){unsigned char output[sizeof(long long)]; if (ReadBits( ( unsigned char* ) output, sizeof(long long) * 8, true )) { ReverseBytes(output, (unsigned char*)&var, sizeof(long long)); return true;} return false;} else return ReadBits( ( unsigned char* ) & var, sizeof(long long) * 8, true );} + bool Read(unsigned long long &var) {if (DoEndianSwap()){unsigned char output[sizeof(unsigned long long)]; if (ReadBits( ( unsigned char* ) output, sizeof(unsigned long long) * 8, true )) { ReverseBytes(output, (unsigned char*)&var, sizeof(unsigned long long)); return true;} return false;} else return ReadBits( ( unsigned char* ) & var, sizeof(unsigned long long) * 8, true );} + bool Read(float &var) {if (DoEndianSwap()){unsigned char output[sizeof(float)]; if (ReadBits( ( unsigned char* ) output, sizeof(float) * 8, true )) { ReverseBytes(output, (unsigned char*)&var, sizeof(float)); return true;} return false;} else return ReadBits( ( unsigned char* ) & var, sizeof(float) * 8, true );} + bool Read(double &var) {if (DoEndianSwap()){unsigned char output[sizeof(double)]; if (ReadBits( ( unsigned char* ) output, sizeof(double) * 8, true )) { ReverseBytes(output, (unsigned char*)&var, sizeof(double)); return true;} return false;} else return ReadBits( ( unsigned char* ) & var, sizeof(double) * 8, true );} + bool Read(long double &var) {if (DoEndianSwap()){unsigned char output[sizeof(long double)]; if (ReadBits( ( unsigned char* ) output, sizeof(long double) * 8, true )) { ReverseBytes(output, (unsigned char*)&var, sizeof(long double)); return true;} return false;} else return ReadBits( ( unsigned char* ) & var, sizeof(long double) * 8, true );} + bool Read(char* var) {return RakNet::RakString::Deserialize(var,this);} + bool Read(unsigned char* var) {return RakNet::RakString::Deserialize((char*) var,this);} + bool Read(RakString &var) {return var.Deserialize(this);} + bool Read(uint24_t &var); + bool Read(const RakNetGUID &var) {if (DoEndianSwap()){unsigned char output[sizeof(uint64_t)]; if (ReadBits( ( unsigned char* ) output, sizeof(uint64_t) * 8, true )) { ReverseBytes(output, (unsigned char*)&var, sizeof(uint64_t)); return true;} return false;} else return ReadBits( ( unsigned char* ) & var, sizeof(uint64_t) * 8, true );} + bool Read(void* &var) {if (DoEndianSwap()){unsigned char output[sizeof(void*)]; if (ReadBits( ( unsigned char* ) output, sizeof(void*) * 8, true )) { ReverseBytes(output, (unsigned char*)&var, sizeof(void*)); return true;} return false;} else return ReadBits( ( unsigned char* ) & var, sizeof(void*) * 8, true );} + bool Read(SystemAddress &var){ReadBits( ( unsigned char* ) & var.binaryAddress, sizeof(var.binaryAddress) * 8, true ); return Read(var.port);} + bool Read(NetworkID &var){if (NetworkID::IsPeerToPeerMode()) Read(var.systemAddress); return Read(var.localSystemAddress);} + + /// \brief Read any integral type from a bitstream. + /// \details If the written value differed from the value compared against in the write function, + /// var will be updated. Otherwise it will retain the current value. + /// ReadDelta is only valid from a previous call to WriteDelta + /// \param[in] var The value to read + bool ReadDelta(bool &var) {return Read(var);} + bool ReadDelta(unsigned char &var){bool dataWritten; bool success; success=Read(dataWritten); if (dataWritten) success=Read(var); return success;} + bool ReadDelta(char &var){bool dataWritten; bool success; success=Read(dataWritten); if (dataWritten) success=Read(var); return success;} + bool ReadDelta(unsigned short &var){bool dataWritten; bool success; success=Read(dataWritten); if (dataWritten) success=Read(var); return success;} + bool ReadDelta(short &var){bool dataWritten; bool success; success=Read(dataWritten); if (dataWritten) success=Read(var); return success;} + bool ReadDelta(unsigned int &var){bool dataWritten; bool success; success=Read(dataWritten); if (dataWritten) success=Read(var); return success;} + bool ReadDelta(int &var){bool dataWritten; bool success; success=Read(dataWritten); if (dataWritten) success=Read(var); return success;} + bool ReadDelta(unsigned long &var){bool dataWritten; bool success; success=Read(dataWritten); if (dataWritten) success=Read(var); return success;} + bool ReadDelta(long &var){bool dataWritten; bool success; success=Read(dataWritten); if (dataWritten) success=Read(var); return success;} + bool ReadDelta(long long &var){bool dataWritten; bool success; success=Read(dataWritten); if (dataWritten) success=Read(var); return success;} + bool ReadDelta(unsigned long long &var){bool dataWritten; bool success; success=Read(dataWritten); if (dataWritten) success=Read(var); return success;} + bool ReadDelta(float &var){bool dataWritten; bool success; success=Read(dataWritten); if (dataWritten) success=Read(var); return success;} + bool ReadDelta(double &var){bool dataWritten; bool success; success=Read(dataWritten); if (dataWritten) success=Read(var); return success;} + bool ReadDelta(long double &var){bool dataWritten; bool success; success=Read(dataWritten); if (dataWritten) success=Read(var); return success;} + bool ReadDelta(char* var){bool dataWritten; bool success; success=Read(dataWritten); if (dataWritten) success=Read(var); return success;} + bool ReadDelta(unsigned char* var){bool dataWritten; bool success; success=Read(dataWritten); if (dataWritten) success=Read(var); return success;} + bool ReadDelta(RakString &var){bool dataWritten; bool success; success=Read(dataWritten); if (dataWritten) success=Read(var); return success;} + bool ReadDelta(uint24_t &var){bool dataWritten; bool success; success=Read(dataWritten); if (dataWritten) success=Read(var); return success;} + bool ReadDelta(RakNetGUID &var){bool dataWritten; bool success; success=Read(dataWritten); if (dataWritten) success=Read(var); return success;} + bool ReadDelta(SystemAddress &var){bool dataWritten; bool success; success=Read(dataWritten); if (dataWritten) success=Read(var); return success;} + bool ReadDelta(NetworkID &var){bool dataWritten; bool success; success=Read(dataWritten); if (dataWritten) success=Read(var); return success;} + + + /// \brief Read any integral type from a bitstream. + /// \details Undefine __BITSTREAM_NATIVE_END if you need endian swapping. + /// For floating point, this is lossy, using 2 bytes for a float and 4 for a double. The range must be between -1 and +1. + /// For non-floating point, this is lossless, but only has benefit if you use less than half the range of the type + /// If you are not using __BITSTREAM_NATIVE_END the opposite is true for types larger than 1 byte + /// \param[in] var The value to read + bool ReadCompressed(bool &var) {return Read(var);} + bool ReadCompressed(unsigned char &var) {return ReadCompressed( ( unsigned char* ) &var, sizeof(unsigned char) * 8, true );} + bool ReadCompressed(char &var) {return ReadCompressed( ( unsigned char* ) &var, sizeof(unsigned char) * 8, true );} + bool ReadCompressed(unsigned short &var){if (DoEndianSwap()){unsigned char output[sizeof(unsigned short)]; if (ReadCompressed( ( unsigned char* ) output, sizeof(unsigned short) * 8, true )){ReverseBytes(output, (unsigned char*)&var, sizeof(unsigned short)); return true;} return false;}else return ReadCompressed( ( unsigned char* ) & var, sizeof(unsigned short) * 8, true );} + bool ReadCompressed(short &var){if (DoEndianSwap()){unsigned char output[sizeof(short)]; if (ReadCompressed( ( unsigned char* ) output, sizeof(short) * 8, true )){ReverseBytes(output, (unsigned char*)&var, sizeof(short)); return true;} return false;}else return ReadCompressed( ( unsigned char* ) & var, sizeof(short) * 8, true );} + bool ReadCompressed(unsigned int &var){if (DoEndianSwap()){unsigned char output[sizeof(unsigned int)]; if (ReadCompressed( ( unsigned char* ) output, sizeof(unsigned int) * 8, true )){ReverseBytes(output, (unsigned char*)&var, sizeof(unsigned int)); return true;} return false;}else return ReadCompressed( ( unsigned char* ) & var, sizeof(unsigned int) * 8, true );} + bool ReadCompressed(int &var){if (DoEndianSwap()){unsigned char output[sizeof(int)]; if (ReadCompressed( ( unsigned char* ) output, sizeof(int) * 8, true )){ReverseBytes(output, (unsigned char*)&var, sizeof(int)); return true;} return false;}else return ReadCompressed( ( unsigned char* ) & var, sizeof(int) * 8, true );} + bool ReadCompressed(unsigned long &var){if (DoEndianSwap()){unsigned char output[sizeof(unsigned long)]; if (ReadCompressed( ( unsigned char* ) output, sizeof(unsigned long) * 8, true )){ReverseBytes(output, (unsigned char*)&var, sizeof(unsigned long)); return true;} return false;}else return ReadCompressed( ( unsigned char* ) & var, sizeof(unsigned long) * 8, true );} + bool ReadCompressed(long &var){if (DoEndianSwap()){unsigned char output[sizeof(long)]; if (ReadCompressed( ( unsigned char* ) output, sizeof(long) * 8, true )){ReverseBytes(output, (unsigned char*)&var, sizeof(long)); return true;} return false;}else return ReadCompressed( ( unsigned char* ) & var, sizeof(long) * 8, true );} + bool ReadCompressed(long long &var){if (DoEndianSwap()){unsigned char output[sizeof(long long)]; if (ReadCompressed( ( unsigned char* ) output, sizeof(long long) * 8, true )){ReverseBytes(output, (unsigned char*)&var, sizeof(long long)); return true;} return false;}else return ReadCompressed( ( unsigned char* ) & var, sizeof(long long) * 8, true );} + bool ReadCompressed(unsigned long long &var){if (DoEndianSwap()){unsigned char output[sizeof(unsigned long long)]; if (ReadCompressed( ( unsigned char* ) output, sizeof(unsigned long long) * 8, true )){ReverseBytes(output, (unsigned char*)&var, sizeof(unsigned long long)); return true;} return false;}else return ReadCompressed( ( unsigned char* ) & var, sizeof(unsigned long long) * 8, true );} + bool ReadCompressed(float &var){unsigned short compressedFloat; if (Read(compressedFloat)) { var = ((float)compressedFloat / 32767.5f - 1.0f); return true;} return false;} + bool ReadCompressed(double &var) {unsigned long compressedFloat; if (Read(compressedFloat)) { var = ((double)compressedFloat / 2147483648.0 - 1.0); return true; } return false;} + bool ReadCompressed(long double &var) {unsigned long compressedFloat; if (Read(compressedFloat)) { var = ((long double)compressedFloat / 2147483648.0 - 1.0); return true; } return false;} + bool ReadCompressed(char* var) {return RakNet::RakString::DeserializeCompressed(var,this);} + bool ReadCompressed(unsigned char* var) {return RakNet::RakString::DeserializeCompressed((char*) var,this);} + bool ReadCompressed(RakString &var) {return var.DeserializeCompressed(this);} + bool ReadCompressed(uint24_t &var) {return Read(var);} + bool ReadCompressed(const RakNetGUID &var){if (DoEndianSwap()){unsigned char output[sizeof(uint64_t)]; if (ReadCompressed( ( unsigned char* ) output, sizeof(uint64_t) * 8, true )){ReverseBytes(output, (unsigned char*)&var, sizeof(uint64_t)); return true;} return false;}else return ReadCompressed( ( unsigned char* ) & var, sizeof(uint64_t) * 8, true );} + bool ReadCompressed(SystemAddress &var) {return Read(var);} + bool ReadCompressed(NetworkID &var) {return Read(var);} + + /// \brief Read any integral type from a bitstream. + /// \details If the written value differed from the value compared against in the write function, + /// var will be updated. Otherwise it will retain the current value. + /// the current value will be updated. + /// For floating point, this is lossy, using 2 bytes for a float and 4 for a double. The range must be between -1 and +1. + /// For non-floating point, this is lossless, but only has benefit if you use less than half the range of the type + /// If you are not using __BITSTREAM_NATIVE_END the opposite is true for types larger than 1 byte + /// ReadCompressedDelta is only valid from a previous call to WriteDelta + /// \param[in] var The value to read + bool ReadCompressedDelta(bool &var) {return Read(var);} + bool ReadCompressedDelta(unsigned char &var){bool dataWritten; bool success; success=Read(dataWritten); if (dataWritten) success=ReadCompressed(var); return success;} + bool ReadCompressedDelta(char &var){bool dataWritten; bool success; success=Read(dataWritten); if (dataWritten) success=ReadCompressed(var); return success;} + bool ReadCompressedDelta(unsigned short &var){bool dataWritten; bool success; success=Read(dataWritten); if (dataWritten) success=ReadCompressed(var); return success;} + bool ReadCompressedDelta(short &var){bool dataWritten; bool success; success=Read(dataWritten); if (dataWritten) success=ReadCompressed(var); return success;} + bool ReadCompressedDelta(unsigned int &var){bool dataWritten; bool success; success=Read(dataWritten); if (dataWritten) success=ReadCompressed(var); return success;} + bool ReadCompressedDelta(int &var){bool dataWritten; bool success; success=Read(dataWritten); if (dataWritten) success=ReadCompressed(var); return success;} + bool ReadCompressedDelta(unsigned long &var){bool dataWritten; bool success; success=Read(dataWritten); if (dataWritten) success=ReadCompressed(var); return success;} + bool ReadCompressedDelta(long &var){bool dataWritten; bool success; success=Read(dataWritten); if (dataWritten) success=ReadCompressed(var); return success;} + bool ReadCompressedDelta(long long &var){bool dataWritten; bool success; success=Read(dataWritten); if (dataWritten) success=ReadCompressed(var); return success;} + bool ReadCompressedDelta(unsigned long long &var){bool dataWritten; bool success; success=Read(dataWritten); if (dataWritten) success=ReadCompressed(var); return success;} + bool ReadCompressedDelta(float &var){bool dataWritten; bool success; success=Read(dataWritten); if (dataWritten) success=ReadCompressed(var); return success;} + bool ReadCompressedDelta(double &var){bool dataWritten; bool success; success=Read(dataWritten); if (dataWritten) success=ReadCompressed(var); return success;} + bool ReadCompressedDelta(long double &var){bool dataWritten; bool success; success=Read(dataWritten); if (dataWritten) success=ReadCompressed(var); return success;} + bool ReadCompressedDelta(char*var){bool dataWritten; bool success; success=Read(dataWritten); if (dataWritten) success=ReadCompressed(var); return success;} + bool ReadCompressedDelta(unsigned char*var){bool dataWritten; bool success; success=Read(dataWritten); if (dataWritten) success=ReadCompressed(var); return success;} + bool ReadCompressedDelta(RakString &var){bool dataWritten; bool success; success=Read(dataWritten); if (dataWritten) success=ReadCompressed(var); return success;} + bool ReadCompressedDelta(uint24_t &var){bool dataWritten; bool success; success=Read(dataWritten); if (dataWritten) success=ReadCompressed(var); return success;} + bool ReadCompressedDelta(const RakNetGUID &var){bool dataWritten; bool success; success=Read(dataWritten); if (dataWritten) success=ReadCompressed(var); return success;} + + /// \brief Read one bitstream to another. + /// \param[in] numberOfBits bits to read + /// \param bitStream the bitstream to read into from + /// \return true on success, false on failure. + bool Read( BitStream *bitStream, BitSize_t numberOfBits ); + bool Read( BitStream *bitStream ); + bool Read( BitStream &bitStream, BitSize_t numberOfBits ); + bool Read( BitStream &bitStream ); + + /// \brief Write an array or casted stream or raw data. This does NOT do endian swapping. + /// \param[in] input a byte buffer + /// \param[in] numberOfBytes the size of \a input in bytes + void Write( const char* input, const unsigned int numberOfBytes ); + + /// \brief Write one bitstream to another. + /// \param[in] numberOfBits bits to write + /// \param bitStream the bitstream to copy from + void Write( BitStream *bitStream, BitSize_t numberOfBits ); + void Write( BitStream *bitStream ); + void Write( BitStream &bitStream, BitSize_t numberOfBits ); + void Write( BitStream &bitStream );\ + + /// \brief Read a normalized 3D vector, using (at most) 4 bytes + 3 bits instead of 12-24 bytes. + /// \details Will further compress y or z axis aligned vectors. + /// Accurate to 1/32767.5. + /// \param[in] x x + /// \param[in] y y + /// \param[in] z z + void WriteNormVector( float x, float y, float z ); + void WriteNormVector( double x, double y, double z ) {WriteNormVector((float)x,(float)y,(float)z);} + + /// \brief Write a float into 2 bytes, spanning the range between \a floatMin and \a floatMax + /// \param[in] f The float to write + /// \param[in] floatMin Predetermined minimum value of f + /// \param[in] floatMax Predetermined maximum value of f + void WriteFloat16( float f, float floatMin, float floatMax ); + + /// \brief Write a vector, using 10 bytes instead of 12. + /// \details Loses accuracy to about 3/10ths and only saves 2 bytes, so only use if accuracy is not important. + /// \param[in] x x + /// \param[in] y y + /// \param[in] z z + void WriteVector( float x, float y, float z ); + void WriteVector( double x, double y, double z ) {WriteVector((float)x, (float)y, (float)z);} + + /// \brief Write a normalized quaternion in 6 bytes + 4 bits instead of 16 bytes. Slightly lossy. + /// \param[in] w w + /// \param[in] x x + /// \param[in] y y + /// \param[in] z z + void WriteNormQuat( float w, float x, float y, float z); + void WriteNormQuat( double w, double x, double y, double z) {WriteNormQuat((float)w, (float) x, (float) y, (float) z);} + + /// \brief Write an orthogonal matrix by creating a quaternion, and writing 3 components of the quaternion in 2 bytes each + /// for 6 bytes instead of 36 + /// Lossy, although the result is renormalized + void WriteOrthMatrix( + float m00, float m01, float m02, + float m10, float m11, float m12, + float m20, float m21, float m22 ) + { + WriteOrthMatrix((double)m00,(double)m01,(double)m02, + (double)m10,(double)m11,(double)m12, + (double)m20,(double)m21,(double)m22); + } + + void WriteOrthMatrix( + double m00, double m01, double m02, + double m10, double m11, double m12, + double m20, double m21, double m22 ); + + /// \brief Read an array or casted stream of byte. + /// \details The array is raw data. There is no automatic endian conversion with this function + /// \param[in] output The result byte array. It should be larger than @em numberOfBytes. + /// \param[in] numberOfBytes The number of byte to read + /// \return true on success false if there is some missing bytes. + bool Read( char* output, const unsigned int numberOfBytes ); + + /// \brief Read a normalized 3D vector, using (at most) 4 bytes + 3 bits instead of 12-24 bytes. + /// \details Will further compress y or z axis aligned vectors. + /// Accurate to 1/32767.5. + /// \param[in] x x + /// \param[in] y y + /// \param[in] z z + bool ReadNormVector( float &x, float &y, float &z ); + bool ReadNormVector( double &x, double &y, double &z ) {float fx, fy, fz; bool b = ReadNormVector(fx, fy, fz); x=fx; y=fy; z=fz; return b;} + + /// \brief Read a float into 2 bytes, spanning the range between \a floatMin and \a floatMax + /// \param[in] f The float to read + /// \param[in] floatMin Predetermined minimum value of f + /// \param[in] floatMax Predetermined maximum value of f + bool ReadFloat16( float &f, float floatMin, float floatMax ); + + /// \brief Read 3 floats or doubles, using 10 bytes, where those float or doubles comprise a vector. + /// \details Loses accuracy to about 3/10ths and only saves 2 bytes, so only use if accuracy is not important. + /// \param[in] x x + /// \param[in] y y + /// \param[in] z z + bool ReadVector( float x, float y, float z ); + bool ReadVector( double &x, double &y, double &z ) {return ReadVector((float)x, (float)y, (float)z);} + + /// \brief Read a normalized quaternion in 6 bytes + 4 bits instead of 16 bytes. + /// \param[in] w w + /// \param[in] x x + /// \param[in] y y + /// \param[in] z z + bool ReadNormQuat( float &w, float &x, float &y, float &z){double dw, dx, dy, dz; bool b=ReadNormQuat(dw, dx, dy, dz); w=(float)dw; x=(float)dx; y=(float)dy; z=(float)dz; return b;} + bool ReadNormQuat( double &w, double &x, double &y, double &z); + + /// \brief Read an orthogonal matrix from a quaternion, reading 3 components of the quaternion in 2 bytes each and extrapolating the 4th, for 6 bytes instead of 36. + /// \details Lossy, although the result is renormalized + bool ReadOrthMatrix( + float &m00, float &m01, float &m02, + float &m10, float &m11, float &m12, + float &m20, float &m21, float &m22 ); + bool ReadOrthMatrix( + double &m00, double &m01, double &m02, + double &m10, double &m11, double &m12, + double &m20, double &m21, double &m22 ); + + /// Sets the read pointer back to the beginning of your data. + void ResetReadPointer( void ); + + /// Sets the write pointer back to the beginning of your data. + void ResetWritePointer( void ); + + /// This is good to call when you are done with the stream to make + /// sure you didn't leave any data left over void + void AssertStreamEmpty( void ); + + /// \brief RAKNET_DEBUG_PRINTF the bits in the stream. Great for debugging. + void PrintBits( char *out ) const; + void PrintBits( void ) const; + void PrintHex( char *out ) const; + void PrintHex( void ) const; + + /// \brief Ignore data we don't intend to read + /// \param[in] numberOfBits The number of bits to ignore + void IgnoreBits( const BitSize_t numberOfBits ); + + /// \brief Ignore data we don't intend to read + /// \param[in] numberOfBits The number of bytes to ignore + void IgnoreBytes( const unsigned int numberOfBytes ); + + /// \brief Move the write pointer to a position on the array. + /// \param[in] offset the offset from the start of the array. + /// \attention + /// \details Dangerous if you don't know what you are doing! + /// For efficiency reasons you can only write mid-stream if your data is byte aligned. + void SetWriteOffset( const BitSize_t offset ); + + /// \brief Returns the length in bits of the stream + inline BitSize_t GetNumberOfBitsUsed( void ) const {return GetWriteOffset();} + inline BitSize_t GetWriteOffset( void ) const {return numberOfBitsUsed;} + + /// \brief Returns the length in bytes of the stream + inline BitSize_t GetNumberOfBytesUsed( void ) const {return BITS_TO_BYTES( numberOfBitsUsed );} + + /// \brief Returns the number of bits into the stream that we have read + inline BitSize_t GetReadOffset( void ) const {return readOffset;} + + /// \brief Sets the read bit index + void SetReadOffset( const BitSize_t newReadOffset ) {readOffset=newReadOffset;} + + /// \brief Returns the number of bits left in the stream that haven't been read + inline BitSize_t GetNumberOfUnreadBits( void ) const {return numberOfBitsUsed - readOffset;} + + /// \brief Makes a copy of the internal data for you \a _data will point to + /// the stream. + /// \details Returns the length in bits of the stream. Partial + /// bytes are left aligned. + /// \param[out] _data The allocated copy of GetData() + BitSize_t CopyData( unsigned char** _data ) const; + + /// Set the stream to some initial data. + /// \internal + void SetData( unsigned char *input ); + + /// \brief Gets the data that BitStream is writing to / reading from. + /// \details Partial bytes are left aligned. + /// \return A pointer to the internal state + inline unsigned char* GetData( void ) const {return data;} + + /// \brief Write numberToWrite bits from the input source. + /// \details Right aligned data means in the case of a partial byte, the bits are aligned + /// from the right (bit 0) rather than the left (as in the normal + /// internal representation) You would set this to true when + /// writing user data, and false when copying bitstream data, such + /// as writing one bitstream to another. + /// \param[in] input The data + /// \param[in] numberOfBitsToWrite The number of bits to write + /// \param[in] rightAlignedBits if true data will be right aligned + void WriteBits( const unsigned char* input, BitSize_t numberOfBitsToWrite, const bool rightAlignedBits = true ); + + /// \brief Align the bitstream to the byte boundary and then write the + /// specified number of bits. + /// \details This is faster than WriteBits but + /// wastes the bits to do the alignment and requires you to call + /// ReadAlignedBits at the corresponding read position. + /// \param[in] input The data + /// \param[in] numberOfBytesToWrite The size of input. + void WriteAlignedBytes( const unsigned char *input, const unsigned int numberOfBytesToWrite ); + + // Endian swap bytes already in the bitstream + void EndianSwapBytes( int byteOffset, int length ); + + /// \brief Aligns the bitstream, writes inputLength, and writes input. Won't write beyond maxBytesToWrite + /// \param[in] input The data + /// \param[in] inputLength The size of input. + /// \param[in] maxBytesToWrite Max bytes to write + void WriteAlignedBytesSafe( const char *input, const unsigned int inputLength, const unsigned int maxBytesToWrite ); + + /// \brief Read bits, starting at the next aligned bits. + /// \details Note that the modulus 8 starting offset of the sequence must be the same as + /// was used with WriteBits. This will be a problem with packet + /// coalescence unless you byte align the coalesced packets. + /// \param[in] output The byte array larger than @em numberOfBytesToRead + /// \param[in] numberOfBytesToRead The number of byte to read from the internal state + /// \return true if there is enough byte. + bool ReadAlignedBytes( unsigned char *output, const unsigned int numberOfBytesToRead ); + + /// \brief Reads what was written by WriteAlignedBytesSafe. + /// \param[in] input The data + /// \param[in] maxBytesToRead Maximum number of bytes to read + /// \return true on success, false on failure. + bool ReadAlignedBytesSafe( char *input, int &inputLength, const int maxBytesToRead ); + bool ReadAlignedBytesSafe( char *input, unsigned int &inputLength, const unsigned int maxBytesToRead ); + + /// \brief Same as ReadAlignedBytesSafe() but allocates the memory for you using new, rather than assuming it is safe to write to + /// \param[in] input input will be deleted if it is not a pointer to 0 + /// \return true on success, false on failure. + bool ReadAlignedBytesSafeAlloc( char **input, int &inputLength, const unsigned int maxBytesToRead ); + bool ReadAlignedBytesSafeAlloc( char **input, unsigned int &inputLength, const unsigned int maxBytesToRead ); + + /// \brief Align the next write and/or read to a byte boundary. + /// \details This can be used to 'waste' bits to byte align for efficiency reasons It + /// can also be used to force coalesced bitstreams to start on byte + /// boundaries so so WriteAlignedBits and ReadAlignedBits both + /// calculate the same offset when aligning. + inline void AlignWriteToByteBoundary( void ) {numberOfBitsUsed += 8 - ( (( numberOfBitsUsed - 1 ) & 7) + 1 );} + + /// \brief Align the next write and/or read to a byte boundary. + /// \details This can be used to 'waste' bits to byte align for efficiency reasons It + /// can also be used to force coalesced bitstreams to start on byte + /// boundaries so so WriteAlignedBits and ReadAlignedBits both + /// calculate the same offset when aligning. + inline void AlignReadToByteBoundary( void ) {readOffset += 8 - ( (( readOffset - 1 ) & 7 ) + 1 );} + + /// \brief Read \a numberOfBitsToRead bits to the output source. + /// \details alignBitsToRight should be set to true to convert internal + /// bitstream data to userdata. It should be false if you used + /// WriteBits with rightAlignedBits false + /// \param[in] output The resulting bits array + /// \param[in] numberOfBitsToRead The number of bits to read + /// \param[in] alignBitsToRight if true bits will be right aligned. + /// \return true if there is enough bits to read + bool ReadBits( unsigned char *output, BitSize_t numberOfBitsToRead, const bool alignBitsToRight = true ); + + /// Write a 0 + void Write0( void ); + + /// Write a 1 + void Write1( void ); + + /// Reads 1 bit and returns true if that bit is 1 and false if it is 0 + bool ReadBit( void ); + + /// If we used the constructor version with copy data off, this + /// *makes sure it is set to on and the data pointed to is copied. + void AssertCopyData( void ); + + /// \brief Use this if you pass a pointer copy to the constructor + /// *(_copyData==false) and want to overallocate to prevent + /// reallocation. + void SetNumberOfBitsAllocated( const BitSize_t lengthInBits ); + + /// \brief Reallocates (if necessary) in preparation of writing numberOfBitsToWrite + void AddBitsAndReallocate( const BitSize_t numberOfBitsToWrite ); + + /// \internal + /// \return How many bits have been allocated internally + unsigned int GetNumberOfBitsAllocated(void) const; + + /// Write zeros until the bitstream is filled up to \a bytes + void PadWithZeroToByteLength( unsigned int bytes ); + + /// \internal Unrolled inner loop, for when performance is critical + void WriteAlignedVar8(const char *input); + /// \internal Unrolled inner loop, for when performance is critical + bool ReadAlignedVar8(char *output); + /// \internal Unrolled inner loop, for when performance is critical + void WriteAlignedVar16(const char *input); + /// \internal Unrolled inner loop, for when performance is critical + bool ReadAlignedVar16(char *output); + /// \internal Unrolled inner loop, for when performance is critical + void WriteAlignedVar32(const char *input); + /// \internal Unrolled inner loop, for when performance is critical + bool ReadAlignedVar32(char *output); + + inline static bool DoEndianSwap(void) { +#ifndef __BITSTREAM_NATIVE_END + return IsNetworkOrder()==false; +#else + return false; +#endif + } + inline static bool IsBigEndian(void) + { + return IsNetworkOrder(); + } + inline static bool IsNetworkOrder(void) {static const bool r = IsNetworkOrderInternal(); return r;} + // Not inline, won't compile on PC due to winsock include errors + static bool IsNetworkOrderInternal(void); + static void ReverseBytes(unsigned char *input, unsigned char *output, const unsigned int length); + static void ReverseBytesInPlace(unsigned char *data,const unsigned int length); + + private: + + BitStream( const BitStream &invalid) { + #ifdef _MSC_VER + #pragma warning(disable:4100) + // warning C4100: 'invalid' : unreferenced formal parameter + #endif + + } + + /// \brief Assume the input source points to a native type, compress and write it. + void WriteCompressed( const unsigned char* input, const unsigned int size, const bool unsignedData ); + + /// \brief Assume the input source points to a compressed native type. Decompress and read it. + bool ReadCompressed( unsigned char* output, const unsigned int size, const bool unsignedData ); + + + int numberOfBitsUsed; + + int numberOfBitsAllocated; + + int readOffset; + + unsigned char *data; + + /// true if the internal buffer is copy of the data passed to the constructor + bool copyData; + + /// BitStreams that use less than BITSTREAM_STACK_ALLOCATION_SIZE use the stack, rather than the heap to store data. It switches over if BITSTREAM_STACK_ALLOCATION_SIZE is exceeded + unsigned char stackData[BITSTREAM_STACK_ALLOCATION_SIZE]; + }; + + inline bool BitStream::SerializeBits(bool writeToBitstream, unsigned char* input, int numberOfBitsToSerialize, const bool rightAlignedBits ) + { + if (writeToBitstream) + WriteBits(input,numberOfBitsToSerialize,rightAlignedBits); + else + return ReadBits(input,numberOfBitsToSerialize,rightAlignedBits); + return true; + } + + +} + +#ifdef _MSC_VER +#pragma warning( pop ) +#endif + +#endif // VC6 + +#endif diff --git a/RakNet/Sources/CCRakNetUDT.cpp b/RakNet/Sources/CCRakNetUDT.cpp new file mode 100644 index 0000000..c31d53c --- /dev/null +++ b/RakNet/Sources/CCRakNetUDT.cpp @@ -0,0 +1,789 @@ +#include "CCRakNetUDT.h" +#include "Rand.h" +#include "MTUSize.h" +#include +#include +#include +//#include +#include "RakAssert.h" +#include "RakAlloca.h" + +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__,__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==(CCTimeType) 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= oldestUnsentAck + SYN || + estimatedTimeToNextTick+curTime < oldestUnsentAck+rto-RTT; +} +// ---------------------------------------------------------------------------------------------------------------------------- +DatagramSequenceNumberType CCRakNetUDT::GetNextDatagramSequenceNumber(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=oneEighthMedian && + packetArrivalHistory[i] 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-amaxThreshold) + 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__,__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__, __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 (CWNDintervalSize) + 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 below 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 + // Multiple by .02 + + 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.04 - 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; +} +*/ diff --git a/RakNet/Sources/CCRakNetUDT.h b/RakNet/Sources/CCRakNetUDT.h new file mode 100644 index 0000000..84b4705 --- /dev/null +++ b/RakNet/Sources/CCRakNetUDT.h @@ -0,0 +1,387 @@ +#ifndef __CONGESTION_CONTROL_UDT_H +#define __CONGESTION_CONTROL_UDT_H + +#include "NativeTypes.h" +#include "RakNetTime.h" +#include "RakNetTypes.h" +#include "DS_Queue.h" + +/// 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 + + +#if CC_TIME_TYPE_BYTES==8 +typedef RakNetTimeUS CCTimeType; +#else +typedef RakNetTimeMS CCTimeType; +#endif +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) + +namespace RakNet +{ + +/// \brief Encapsulates UDT congestion control, as used by RakNet +/// Requirements: +///
    +///
  1. Each datagram is no more than MAXIMUM_MTU_SIZE, after accounting for the UDP header +///
  2. Each datagram containing a user message has a sequence number which is set after calling OnSendBytes(). Set it by calling GetNextDatagramSequenceNumber() +///
  3. System is designed to be used from a single thread. +///
  4. 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. +///
+/// +/// Recommended: +///
    +///
  1. Call sendto in its own thread. This takes a significant amount of time in high speed networks. +///
+/// +/// Algorithm: +///
    +///
  1. On a new connection, call Init() +///
  2. On a periodic interval (SYN time is the best) call Update(). Also call ShouldSendACKs(), and send buffered ACKS if it returns true. +///
  3. Call OnSendAck() when sending acks. +///
  4. 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 +///
  5. Call OnSendBytes() when sending datagrams. +///
  6. When data arrives, record the sequence number and buffer an ACK for it, to be sent from Update() if ShouldSendACKs() returns true +///
  7. 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. +///
  8. When you get a packet, call OnGotPacket(). If the packet is also either of a packet pair, call OnGotPacketPair() +///
  9. 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. +///
  10. If you get an ACK, remove that message from retransmission. Call OnNonDuplicateAck(). +///
  11. If a message is not ACKed for GetRTOForRetransmission(), resend it. +///
+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 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 ); + + /// 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 outgoing packets, 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 mostRecentPacketPairValue, 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 pingsLastInterval; +}; + +} + +#endif diff --git a/RakNet/Sources/CheckSum.cpp b/RakNet/Sources/CheckSum.cpp new file mode 100644 index 0000000..91ecc3f --- /dev/null +++ b/RakNet/Sources/CheckSum.cpp @@ -0,0 +1,97 @@ +/** +* @file +* @brief CheckSum implementation from http://www.flounder.com/checksum.htm +* +*/ +#include "CheckSum.h" + +/**************************************************************************** +* 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) diff --git a/RakNet/Sources/CheckSum.h b/RakNet/Sources/CheckSum.h new file mode 100644 index 0000000..bd768a1 --- /dev/null +++ b/RakNet/Sources/CheckSum.h @@ -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.h" + +/// 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 diff --git a/RakNet/Sources/ClientContextStruct.h b/RakNet/Sources/ClientContextStruct.h new file mode 100644 index 0000000..ee45022 --- /dev/null +++ b/RakNet/Sources/ClientContextStruct.h @@ -0,0 +1,41 @@ +/// \file +/// \brief \b [Internal] deprecated, back from when I supported IO Completion ports. +/// +/// This file is part of RakNet Copyright 2003 Jenkins Software LLC +/// +/// Usage of RakNet is subject to the appropriate license agreement. + + +#ifndef __CLIENT_CONTEXT_STRUCT_H +#define __CLIENT_CONTEXT_STRUCT_H + +#if defined(_XBOX) || defined(X360) +#elif defined(_WIN32) +//#include "WindowsIncludes.h" +#endif +#include "RakNetTypes.h" +#include "MTUSize.h" + +class RakPeer; + +#ifdef __USE_IO_COMPLETION_PORTS + +struct ClientContextStruct +{ + HANDLE handle; // The socket, also used as a file handle +}; + +struct ExtendedOverlappedStruct +{ + OVERLAPPED overlapped; + char data[ MAXIMUM_MTU_SIZE ]; // Used to hold data to send + int length; // Length of the actual data to send, always under MAXIMUM_MTU_SIZE + unsigned int binaryAddress; + unsigned short port; + RakPeer *rakPeer; + bool read; // Set to true for reads, false for writes +}; + +#endif + +#endif diff --git a/RakNet/Sources/CommandParserInterface.cpp b/RakNet/Sources/CommandParserInterface.cpp new file mode 100644 index 0000000..c1da176 --- /dev/null +++ b/RakNet/Sources/CommandParserInterface.cpp @@ -0,0 +1,168 @@ +#include "CommandParserInterface.h" +#include "TransportInterface.h" +#include +#include "RakAssert.h" +#include +#if defined(_XBOX) || defined(X360) + +#elif 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 +#else +#include +#include +#include +#endif + +#include "LinuxStrings.h" + +#ifdef _MSC_VER +#pragma warning( push ) +#endif + +const unsigned char CommandParserInterface::VARIABLE_NUMBER_OF_PARAMETERS=255; + +int 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, 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__, __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(SystemAddress systemAddress, TransportInterface *transport) +{ + (void) systemAddress; + (void) transport; +} +void CommandParserInterface::OnConnectionLost(SystemAddress systemAddress, TransportInterface *transport) +{ + (void) systemAddress; + (void) transport; +} +void CommandParserInterface::ReturnResult(bool res, const char *command,TransportInterface *transport, 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, SystemAddress systemAddress) +{ + transport->Send(systemAddress, "%s returned %i.\r\n", command, res); +} +void CommandParserInterface::ReturnResult(const char *command, TransportInterface *transport, SystemAddress systemAddress) +{ + transport->Send(systemAddress, "Successfully called %s.\r\n", command); +} +void CommandParserInterface::ReturnResult(char *res, const char *command, TransportInterface *transport, SystemAddress systemAddress) +{ + transport->Send(systemAddress, "%s returned %s.\r\n", command, res); +} +void CommandParserInterface::ReturnResult(SystemAddress res, const char *command, TransportInterface *transport, SystemAddress systemAddress) +{ +#if !defined(_XBOX) && !defined(_X360) + in_addr in; + in.s_addr = systemAddress.binaryAddress; + inet_ntoa( in ); + transport->Send(systemAddress, "%s returned %s %i:%i\r\n", command,inet_ntoa( in ),res.binaryAddress, res.port); +#else + transport->Send(systemAddress, "%s returned %i:%i\r\n", command,res.binaryAddress, res.port); +#endif +} +SystemAddress CommandParserInterface::IntegersToSystemAddress(int binaryAddress, int port) +{ + SystemAddress systemAddress; + systemAddress.binaryAddress=binaryAddress; + systemAddress.port=(unsigned short)port; + return systemAddress; +} + +#ifdef _MSC_VER +#pragma warning( pop ) +#endif + diff --git a/RakNet/Sources/CommandParserInterface.h b/RakNet/Sources/CommandParserInterface.h new file mode 100644 index 0000000..0becfb3 --- /dev/null +++ b/RakNet/Sources/CommandParserInterface.h @@ -0,0 +1,142 @@ +/// \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.h" +#include "RakNetTypes.h" +#include "DS_OrderedList.h" +#include "Export.h" + +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(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(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, 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, 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, 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, SystemAddress systemAddress); + virtual void ReturnResult(char *res, const char *command, TransportInterface *transport, SystemAddress systemAddress); + virtual void ReturnResult(SystemAddress res, const char *command, TransportInterface *transport, SystemAddress systemAddress); + virtual void ReturnResult(int res, const char *command,TransportInterface *transport, 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, SystemAddress systemAddress); + + + /// \brief Since there's no way to specify a systemAddress directly, the user needs to + /// specify both the binary address and port. + /// \details Given those parameters, this returns the corresponding SystemAddress + /// \param[in] binaryAddress The binaryAddress portion of SystemAddress + /// \param[in] port The port portion of SystemAddress + SystemAddress IntegersToSystemAddress(int binaryAddress, int port); + +protected: + DataStructures::OrderedList commandList; +}; + +#endif diff --git a/RakNet/Sources/ConnectionGraph.cpp b/RakNet/Sources/ConnectionGraph.cpp new file mode 100644 index 0000000..23e2d7f --- /dev/null +++ b/RakNet/Sources/ConnectionGraph.cpp @@ -0,0 +1,621 @@ +/// \file +/// +/// This file is part of RakNet Copyright 2003 Jenkins Software LLC +/// +/// Usage of RakNet is subject to the appropriate license agreement. + +#include "NativeFeatureIncludes.h" +#if _RAKNET_SUPPORT_ConnectionGraph==1 + +#include "ConnectionGraph.h" +#include "RakPeerInterface.h" +#include "MessageIdentifiers.h" +#include "BitStream.h" +#include "StringCompressor.h" +#include "GetTime.h" +#include +#include "RakAssert.h" +#include "SHA1.h" + +#ifdef _MSC_VER +#pragma warning( push ) +#endif + +static const int connectionGraphChannel=31; + +ConnectionGraph::SystemAddressAndGroupId::SystemAddressAndGroupId() +{ + +} +ConnectionGraph::SystemAddressAndGroupId::~SystemAddressAndGroupId() +{ + +} +ConnectionGraph::SystemAddressAndGroupId::SystemAddressAndGroupId(SystemAddress _systemAddress, ConnectionGraphGroupID _groupID, RakNetGUID _guid) +{ + systemAddress=_systemAddress; + groupId=_groupID; + guid=_guid; +} +bool ConnectionGraph::SystemAddressAndGroupId::operator==( const ConnectionGraph::SystemAddressAndGroupId& right ) const +{ +// Use system address, because the router plugin takes system addresses in the send list, rather than guids + return systemAddress==right.systemAddress; +} +bool ConnectionGraph::SystemAddressAndGroupId::operator!=( const ConnectionGraph::SystemAddressAndGroupId& right ) const +{ + return systemAddress!=right.systemAddress; +} +bool ConnectionGraph::SystemAddressAndGroupId::operator > ( const ConnectionGraph::SystemAddressAndGroupId& right ) const +{ + return systemAddress>right.systemAddress; +} +bool ConnectionGraph::SystemAddressAndGroupId::operator < ( const ConnectionGraph::SystemAddressAndGroupId& right ) const +{ + return systemAddress::IMPLEMENT_DEFAULT_COMPARISON(); + DataStructures::OrderedList::IMPLEMENT_DEFAULT_COMPARISON(); + DataStructures::OrderedList::IMPLEMENT_DEFAULT_COMPARISON(); + DataStructures::OrderedList::IMPLEMENT_DEFAULT_COMPARISON(); + + subscribedGroups.Insert(0,0, true, __FILE__, __LINE__); +} + +ConnectionGraph::~ConnectionGraph() +{ + if (pw) + RakNet::OP_DELETE_ARRAY(pw, __FILE__, __LINE__); +} + +void ConnectionGraph::SetPassword(const char *password) +{ + if (pw) + { + RakNet::OP_DELETE_ARRAY(pw, __FILE__, __LINE__); + pw=0; + } + + if (password && password[0]) + { + RakAssert(strlen(password)<256); + pw=(char*) rakMalloc_Ex( strlen(password)+1, __FILE__, __LINE__ ); + strcpy(pw, password); + } +} +DataStructures::WeightedGraph *ConnectionGraph::GetGraph(void) +{ + return &graph; +} +void ConnectionGraph::SetAutoAddNewConnections(bool autoAdd) +{ + autoAddNewConnections=autoAdd; +} +void ConnectionGraph::OnRakPeerShutdown(void) +{ + graph.Clear(); + participantList.Clear(false, __FILE__, __LINE__); +// forceBroadcastTime=0; +} +void ConnectionGraph::Update(void) +{ + +// RakNetTime time = RakNet::GetTime(); + + // If the time is past the next weight update time, then refresh all pings of all connected participants and send these out if substantially different. +// if (forceBroadcastTime && time > forceBroadcastTime) +// { +// DataStructures::OrderedList none; + // BroadcastGraphUpdate(none, peer); +// forceBroadcastTime=0; +// } +} +void ConnectionGraph::OnNewConnection(SystemAddress systemAddress, RakNetGUID rakNetGUID, bool isIncoming) +{ + if (isIncoming==false) + { + if (autoAddNewConnections==false) + return; + + RequestConnectionGraph(systemAddress); + + // 0 is the default groupId + AddNewConnection(rakPeerInterface, systemAddress, rakNetGUID, 0); + } + else + { + if (autoAddNewConnections==false) + return; + + // 0 is the default groupId + AddNewConnection(rakPeerInterface, systemAddress, rakNetGUID, 0); + } +} +PluginReceiveResult ConnectionGraph::OnReceive(Packet *packet) +{ + switch (packet->data[0]) + { + case ID_CONNECTION_GRAPH_REQUEST: + OnConnectionGraphRequest(packet); + return RR_STOP_PROCESSING_AND_DEALLOCATE; + case ID_CONNECTION_GRAPH_REPLY: + OnConnectionGraphReply(packet); + return RR_CONTINUE_PROCESSING; + case ID_CONNECTION_GRAPH_UPDATE: + OnConnectionGraphUpdate(packet); + return RR_STOP_PROCESSING_AND_DEALLOCATE; + case ID_CONNECTION_GRAPH_NEW_CONNECTION: + OnNewConnectionInternal(packet); + return RR_STOP_PROCESSING_AND_DEALLOCATE; + // Remove connection lost + case ID_CONNECTION_GRAPH_CONNECTION_LOST: + case ID_CONNECTION_GRAPH_DISCONNECTION_NOTIFICATION: + if (OnConnectionLostInternal(packet, packet->data[0])) + { + if (packet->data[0]==ID_CONNECTION_GRAPH_CONNECTION_LOST) + packet->data[0]=ID_REMOTE_CONNECTION_LOST; + else + packet->data[0]=ID_REMOTE_DISCONNECTION_NOTIFICATION; + return RR_CONTINUE_PROCESSING; // Return this packet to the user + } + return RR_STOP_PROCESSING_AND_DEALLOCATE; + } + + return RR_CONTINUE_PROCESSING; +} +void ConnectionGraph::OnClosedConnection(SystemAddress systemAddress, RakNetGUID rakNetGUID, PI2_LostConnectionReason lostConnectionReason ) +{ + (void) rakNetGUID; + + if (lostConnectionReason==LCR_CLOSED_BY_USER || lostConnectionReason==LCR_DISCONNECTION_NOTIFICATION) + HandleDroppedConnection(rakPeerInterface, systemAddress, ID_CONNECTION_GRAPH_DISCONNECTION_NOTIFICATION); + else + HandleDroppedConnection(rakPeerInterface, systemAddress, ID_CONNECTION_GRAPH_CONNECTION_LOST); +} + +void ConnectionGraph::HandleDroppedConnection(RakPeerInterface *peer, SystemAddress systemAddress, unsigned char packetId) +{ + RakAssert(peer); + RemoveParticipant(systemAddress); + DataStructures::OrderedList ignoreList; + RemoveAndRelayConnection(ignoreList, packetId, systemAddress, peer->GetExternalID(systemAddress), peer); +} +void ConnectionGraph::SetGroupId(ConnectionGraphGroupID groupId) +{ + myGroupId=groupId; +} +void ConnectionGraph::AddNewConnection(RakPeerInterface *peer, SystemAddress systemAddress, RakNetGUID guid, ConnectionGraphGroupID groupId) +{ + if (autoAddNewConnections==false && subscribedGroups.HasData(groupId)==false) + return; + + DataStructures::OrderedList ignoreList; + + SystemAddressAndGroupId first, second; + first.systemAddress=systemAddress; + first.groupId=groupId; + first.guid=guid; + second.systemAddress=peer->GetExternalID(systemAddress); + second.groupId=myGroupId; + second.guid=peer->GetGuidFromSystemAddress(UNASSIGNED_SYSTEM_ADDRESS); + + AddAndRelayConnection(ignoreList, first, second, (unsigned short)peer->GetAveragePing(systemAddress), peer); +} +void ConnectionGraph::SubscribeToGroup(ConnectionGraphGroupID groupId) +{ + subscribedGroups.Insert(groupId, groupId, true, __FILE__, __LINE__); +} +void ConnectionGraph::UnsubscribeFromGroup(ConnectionGraphGroupID groupId) +{ + subscribedGroups.Remove(groupId); +} +void ConnectionGraph::RequestConnectionGraph(SystemAddress systemAddress) +{ + RakNet::BitStream outBitstream; + outBitstream.Write((MessageID)ID_CONNECTION_GRAPH_REQUEST); + stringCompressor->EncodeString(pw,256,&outBitstream); + rakPeerInterface->Send(&outBitstream, LOW_PRIORITY, RELIABLE_ORDERED, connectionGraphChannel, systemAddress, false); + +#ifdef _CONNECTION_GRAPH_DEBUG_PRINT + RAKNET_DEBUG_PRINTF("ID_CONNECTION_GRAPH_REQUEST from %i to %i\n", peer->GetInternalID().port, systemAddress.port); +#endif +} +void ConnectionGraph::OnConnectionGraphRequest(Packet *packet) +{ + char password[256]; + RakNet::BitStream inBitstream(packet->data, packet->length, false); + inBitstream.IgnoreBits(8); + stringCompressor->DecodeString(password,256,&inBitstream); + if (pw && pw[0] && strcmp(pw, password)!=0) + return; + +#ifdef _CONNECTION_GRAPH_DEBUG_PRINT + RAKNET_DEBUG_PRINTF("ID_CONNECTION_GRAPH_REPLY "); +#endif + + RakNet::BitStream outBitstream; + outBitstream.Write((MessageID)ID_CONNECTION_GRAPH_REPLY); + stringCompressor->EncodeString(pw,256,&outBitstream); + SerializeWeightedGraph(&outBitstream, graph); + SendUnified(&outBitstream, LOW_PRIORITY, RELIABLE_ORDERED, connectionGraphChannel, packet->systemAddress, false); + +#ifdef _CONNECTION_GRAPH_DEBUG_PRINT + RAKNET_DEBUG_PRINTF("from %i to %i\n", peer->GetInternalID().port, packet->systemAddress.port); +#endif + + // Add packet->systemAddress to the participant list if it is not already there + AddParticipant(packet->systemAddress); +} +void ConnectionGraph::OnConnectionGraphReply(Packet *packet) +{ + unsigned char password[256]; + RakNet::BitStream inBitstream(packet->data, packet->length, false); + inBitstream.IgnoreBits(8); + stringCompressor->DecodeString((char*)password,256,&inBitstream); + if (pw && pw[0] && strcmp(pw, (const char*)password)!=0) + return; + + // Serialize the weighted graph and send it to them + RakNet::BitStream outBitstream; + outBitstream.Write((MessageID)ID_CONNECTION_GRAPH_UPDATE); + +#ifdef _CONNECTION_GRAPH_DEBUG_PRINT + RAKNET_DEBUG_PRINTF("ID_CONNECTION_GRAPH_UPDATE "); +#endif + + // Send our current graph to the sender + SerializeWeightedGraph(&outBitstream, graph); + + + // Write the systems that have processed this graph so we don't resend to these systems + outBitstream.Write((unsigned short) 1); + outBitstream.Write(rakPeerInterface->GetExternalID(packet->systemAddress)); + +#ifdef _CONNECTION_GRAPH_DEBUG_PRINT + RAKNET_DEBUG_PRINTF("from %i to %i\n", peer->GetInternalID().port, packet->systemAddress.port); +#endif + + SendUnified(&outBitstream, LOW_PRIORITY, RELIABLE_ORDERED, connectionGraphChannel, packet->systemAddress, false); + + // Add packet->systemAddress to the participant list if it is not already there + AddParticipant(packet->systemAddress); + + if (DeserializeWeightedGraph(&inBitstream, rakPeerInterface)==false) + return; + + // Forward the updated graph to all current participants + DataStructures::OrderedList ignoreList; + ignoreList.Insert(packet->systemAddress,packet->systemAddress, true, __FILE__, __LINE__); + BroadcastGraphUpdate(ignoreList, rakPeerInterface); +} +void ConnectionGraph::OnConnectionGraphUpdate(Packet *packet) +{ + // Only accept from participants + if (participantList.HasData(packet->systemAddress)==false) + return; + + RakNet::BitStream inBitstream(packet->data, packet->length, false); + inBitstream.IgnoreBits(8); + + if (DeserializeWeightedGraph(&inBitstream, rakPeerInterface)==false) + return; + + DataStructures::OrderedList ignoreList; + DeserializeIgnoreList(ignoreList, &inBitstream); + + // Forward the updated graph to all participants. + ignoreList.Insert(packet->systemAddress,packet->systemAddress, false, __FILE__, __LINE__); + BroadcastGraphUpdate(ignoreList, rakPeerInterface); +} +void ConnectionGraph::OnNewConnectionInternal(Packet *packet) +{ + // Only accept from participants + if (participantList.HasData(packet->systemAddress)==false) + return; + + SystemAddressAndGroupId node1, node2; + unsigned short ping; + RakNet::BitStream inBitstream(packet->data, packet->length, false); + inBitstream.IgnoreBits(8); + inBitstream.Read(node1.systemAddress); + inBitstream.Read(node1.groupId); + inBitstream.Read(node1.guid); + inBitstream.Read(node2.systemAddress); + inBitstream.Read(node2.groupId); + inBitstream.Read(node2.guid); + if (inBitstream.Read(ping)==false) + return; + DataStructures::OrderedList ignoreList; + DeserializeIgnoreList(ignoreList, &inBitstream); + ignoreList.Insert(packet->systemAddress,packet->systemAddress, false, __FILE__, __LINE__); + AddAndRelayConnection(ignoreList, node1, node2, ping, rakPeerInterface); +} +bool ConnectionGraph::OnConnectionLostInternal(Packet *packet, unsigned char packetId) +{ + // Only accept from participants + if (participantList.HasData(packet->systemAddress)==false) + return false; + + SystemAddress node1, node2; + RakNet::BitStream inBitstream(packet->data, packet->length, false); + inBitstream.IgnoreBits(8); + // This is correct - group IDs are not written for removal, only addition. + inBitstream.Read(node1); + if (inBitstream.Read(node2)==false) + return false; + DataStructures::OrderedList ignoreList; + DeserializeIgnoreList(ignoreList, &inBitstream); + ignoreList.Insert(packet->systemAddress, packet->systemAddress, false, __FILE__, __LINE__); + + return RemoveAndRelayConnection(ignoreList, packetId, node1, node2, rakPeerInterface); +} +bool ConnectionGraph::DeserializeIgnoreList(DataStructures::OrderedList &ignoreList, RakNet::BitStream *inBitstream ) +{ + unsigned short count; + SystemAddress temp; + unsigned i; + inBitstream->Read(count); + for (i=0; i < count; i++) + { + if (inBitstream->Read(temp)==false) + { + RakAssert(0); + return false; + } + ignoreList.Insert(temp,temp, false, __FILE__, __LINE__); + } + return true; +} +void ConnectionGraph::SerializeWeightedGraph(RakNet::BitStream *out, const DataStructures::WeightedGraph &g) const +{ + unsigned nodeIndex, connectionIndex; + BitSize_t countOffset, oldOffset; + unsigned short count; + SystemAddressAndGroupId node1, node2; + unsigned short weight; + out->WriteCompressed(g.GetNodeCount()); + for (nodeIndex=0; nodeIndex < g.GetNodeCount(); nodeIndex++) + { + // Write the node + node1=g.GetNodeAtIndex(nodeIndex); +#ifdef _CONNECTION_GRAPH_DEBUG_PRINT + RAKNET_DEBUG_PRINTF("[%i] ", node1.systemAddress.port); +#endif + out->Write(node1.systemAddress); + out->Write(node1.groupId); + out->Write(node1.guid); + + // Write the adjacency list count + count=(unsigned short)g.GetConnectionCount(nodeIndex); + out->AlignWriteToByteBoundary(); + countOffset=out->GetWriteOffset(); + out->Write(count); + count=0; + for (connectionIndex=0; connectionIndex < g.GetConnectionCount(nodeIndex); connectionIndex++) + { + g.GetConnectionAtIndex(nodeIndex, connectionIndex, node2, weight); + // For efficiencies' sake, only serialize the upper half of the connection pairs + if (node2 > node1) + { + count++; + out->Write(node2.systemAddress); + out->Write(node2.groupId); + out->Write(node2.guid); + out->Write(weight); + +#ifdef _CONNECTION_GRAPH_DEBUG_PRINT + RAKNET_DEBUG_PRINTF("(%i) ", node2.systemAddress.port); +#endif + } + } + + // Go back and change how many elements were written + oldOffset=out->GetWriteOffset(); + out->SetWriteOffset(countOffset); + out->Write(count); + out->SetWriteOffset(oldOffset); + } +} +bool ConnectionGraph::DeserializeWeightedGraph(RakNet::BitStream *inBitstream, RakPeerInterface *peer) +{ + unsigned nodeCount, nodeIndex, connectionIndex; + unsigned short connectionCount; + SystemAddressAndGroupId node, connection; + bool anyConnectionsNew=false; + unsigned short weight; + inBitstream->ReadCompressed(nodeCount); + for (nodeIndex=0; nodeIndex < nodeCount; nodeIndex++) + { + inBitstream->Read(node.systemAddress); + inBitstream->Read(node.groupId); + inBitstream->Read(node.guid); + + inBitstream->AlignReadToByteBoundary(); + if (inBitstream->Read(connectionCount)==false) + { + RakAssert(0); + return false; + } + for (connectionIndex=0; connectionIndex < connectionCount; connectionIndex++) + { + inBitstream->Read(connection.systemAddress); + inBitstream->Read(connection.groupId); + inBitstream->Read(connection.guid); + if (inBitstream->Read(weight)==false) + { + RakAssert(0); + return false; + } + if (subscribedGroups.HasData(connection.groupId)==false || + subscribedGroups.HasData(node.groupId)==false) + continue; + RakAssert(node.systemAddress!=UNASSIGNED_SYSTEM_ADDRESS); + RakAssert(connection.systemAddress!=UNASSIGNED_SYSTEM_ADDRESS); + if (IsNewRemoteConnection(node,connection,peer)) + NotifyUserOfRemoteConnection(node,connection,weight,peer); + + if (graph.HasConnection(node,connection)==false) + anyConnectionsNew=true; + + graph.AddConnection(node,connection,weight); + } + } + return anyConnectionsNew; +} +void ConnectionGraph::RemoveParticipant(SystemAddress systemAddress) +{ + unsigned index; + bool objectExists; + index=participantList.GetIndexFromKey(systemAddress, &objectExists); + if (objectExists) + participantList.RemoveAtIndex(index); +} + +void ConnectionGraph::AddParticipant(SystemAddress systemAddress) +{ + participantList.Insert(systemAddress,systemAddress, false, __FILE__, __LINE__); +} + +void ConnectionGraph::AddAndRelayConnection(DataStructures::OrderedList &ignoreList, const SystemAddressAndGroupId &conn1, const SystemAddressAndGroupId &conn2, unsigned short ping, RakPeerInterface *peer) +{ + if (graph.HasConnection(conn1,conn2)) + return; + + if (ping==65535) + ping=0; + RakAssert(conn1.systemAddress!=UNASSIGNED_SYSTEM_ADDRESS); + RakAssert(conn2.systemAddress!=UNASSIGNED_SYSTEM_ADDRESS); + + if (IsNewRemoteConnection(conn1,conn2,peer)) + { + NotifyUserOfRemoteConnection(conn1,conn2,ping,peer); + + // What was this return here for? + // return; + } + + graph.AddConnection(conn1,conn2,ping); + + RakNet::BitStream outBitstream; + outBitstream.Write((MessageID)ID_CONNECTION_GRAPH_NEW_CONNECTION); + outBitstream.Write(conn1.systemAddress); + outBitstream.Write(conn1.groupId); + outBitstream.Write(conn1.guid); + outBitstream.Write(conn2.systemAddress); + outBitstream.Write(conn2.groupId); + outBitstream.Write(conn2.guid); + outBitstream.Write(ping); + ignoreList.Insert(conn2.systemAddress,conn2.systemAddress, false, __FILE__, __LINE__); + ignoreList.Insert(conn1.systemAddress,conn1.systemAddress, false, __FILE__, __LINE__); + SerializeIgnoreListAndBroadcast(&outBitstream, ignoreList, peer); +} +bool ConnectionGraph::RemoveAndRelayConnection(DataStructures::OrderedList &ignoreList, unsigned char packetId, const SystemAddress node1, const SystemAddress node2, RakPeerInterface *peer) +{ + SystemAddressAndGroupId n1, n2; + n1.systemAddress=node1; + n2.systemAddress=node2; + if (graph.HasConnection(n1,n2)==false) + return false; + graph.RemoveConnection(n1,n2); + + // TODO - clear islands + + RakNet::BitStream outBitstream; + outBitstream.Write(packetId); + outBitstream.Write(node1); + outBitstream.Write(node2); + + ignoreList.Insert(node1,node1, false, __FILE__, __LINE__); + ignoreList.Insert(node2,node2, false, __FILE__, __LINE__); + SerializeIgnoreListAndBroadcast(&outBitstream, ignoreList, peer); + + return true; +} + +void ConnectionGraph::BroadcastGraphUpdate(DataStructures::OrderedList &ignoreList, RakPeerInterface *peer) +{ + RakNet::BitStream outBitstream; + outBitstream.Write((MessageID)ID_CONNECTION_GRAPH_UPDATE); + SerializeWeightedGraph(&outBitstream, graph); + SerializeIgnoreListAndBroadcast(&outBitstream, ignoreList, peer); +} +void ConnectionGraph::SerializeIgnoreListAndBroadcast(RakNet::BitStream *outBitstream, DataStructures::OrderedList &ignoreList, RakPeerInterface *peer) +{ + DataStructures::List sendList; + unsigned i; + for (i=0; i < participantList.Size(); i++) + { + if (ignoreList.HasData(participantList[i])==false) + sendList.Insert(participantList[i], __FILE__, __LINE__); + } + if (sendList.Size()==0) + return; + + SystemAddress self = peer->GetExternalID(sendList[0]); + ignoreList.Insert(self,self, false, __FILE__, __LINE__); + outBitstream->Write((unsigned short) (ignoreList.Size()+sendList.Size())); + for (i=0; i < ignoreList.Size(); i++) + outBitstream->Write(ignoreList[i]); + for (i=0; i < sendList.Size(); i++) + outBitstream->Write(sendList[i]); + + for (i=0; i < sendList.Size(); i++) + { + peer->Send(outBitstream, LOW_PRIORITY, RELIABLE_ORDERED, connectionGraphChannel, sendList[i], false); + } +} +bool ConnectionGraph::IsNewRemoteConnection(const SystemAddressAndGroupId &conn1, const SystemAddressAndGroupId &conn2,RakPeerInterface *peer) +{ + if (graph.HasConnection(conn1,conn2)==false && + subscribedGroups.HasData(conn1.groupId) && + subscribedGroups.HasData(conn2.groupId) && + (peer->IsConnected(conn1.systemAddress)==false || peer->IsConnected(conn2.systemAddress)==false)) + { + SystemAddress externalId1, externalId2; + externalId1=peer->GetExternalID(conn1.systemAddress); + externalId2=peer->GetExternalID(conn2.systemAddress); + return (externalId1!=conn1.systemAddress && externalId1!=conn2.systemAddress && + externalId2!=conn1.systemAddress && externalId2!=conn2.systemAddress); + } + return false; +} +void ConnectionGraph::NotifyUserOfRemoteConnection(const SystemAddressAndGroupId &conn1, const SystemAddressAndGroupId &conn2,unsigned short ping, RakPeerInterface *peer) +{ + // Create a packet to tell the user of this event + static const int length=(const int) (sizeof(MessageID) + (SystemAddress::size() + sizeof(ConnectionGraphGroupID) + RakNetGUID::size()) * 2 + sizeof(unsigned short)); + Packet *p = peer->AllocatePacket(length); + RakNet::BitStream b(p->data, length, false); + p->bitSize=p->length*8; + b.SetWriteOffset(0); + b.Write((MessageID)ID_REMOTE_NEW_INCOMING_CONNECTION); + b.Write(conn1.systemAddress); + b.Write(conn1.groupId); + b.Write(conn1.guid); + b.Write(conn2.systemAddress); + b.Write(conn2.groupId); + b.Write(conn2.guid); + b.Write(ping); + if (peer->IsConnected(conn2.systemAddress)==false) + { + p->systemAddress=conn2.systemAddress; + p->guid=conn2.guid; + } + else + { + p->systemAddress=conn1.systemAddress; + p->guid=conn1.guid; + } + peer->PushBackPacket(p, false); +} + +#ifdef _MSC_VER +#pragma warning( pop ) +#endif + +#endif // _RAKNET_SUPPORT_* diff --git a/RakNet/Sources/ConnectionGraph.h b/RakNet/Sources/ConnectionGraph.h new file mode 100644 index 0000000..d771f17 --- /dev/null +++ b/RakNet/Sources/ConnectionGraph.h @@ -0,0 +1,167 @@ +/// \file +/// \brief Connection graph plugin. This maintains a graph of connections for the entire network, so every peer knows about every other peer. +/// +/// This file is part of RakNet Copyright 2003 Jenkins Software LLC +/// +/// Usage of RakNet is subject to the appropriate license agreement. + +#include "NativeFeatureIncludes.h" +#if _RAKNET_SUPPORT_ConnectionGraph==1 + +#ifndef __CONNECTION_GRAPH_H +#define __CONNECTION_GRAPH_H + +class RakPeerInterface; +#include "RakMemoryOverride.h" +#include "RakNetTypes.h" +#include "PluginInterface2.h" +#include "DS_List.h" +#include "DS_WeightedGraph.h" +#include "GetTime.h" +#include "Export.h" + +/// \defgroup CONNECTION_GRAPH_GROUP ConnectionGraph +/// \brief Tells other instances of RakPeer about connections to and from this instance +/// \details Both connection graph plugins have RakNet automatically tell other connected instances of RakNet about new and lost connections
+/// See ID_REMOTE_DISCONNECTION_NOTIFICATION, ID_REMOTE_CONNECTION_LOST, ID_REMOTE_NEW_INCOMING_CONNECTION
+/// ConnectionGraph is deprecated. Use ConnectionGraph2 instead. +/// \ingroup PLUGINS_GROUP + +// If you need more than 255 groups just change this typedef +typedef unsigned char ConnectionGraphGroupID; + +/// \brief A connection graph. Each peer will know about all other peers. +/// \ingroup CONNECTION_GRAPH_GROUP +/// \deprecated Use ConnectionGraph2 +class RAK_DLL_EXPORT ConnectionGraph : public PluginInterface2 +{ +public: + ConnectionGraph(); + virtual ~ConnectionGraph(); + + /// A node in the connection graph + struct RAK_DLL_EXPORT SystemAddressAndGroupId + { + SystemAddressAndGroupId(); + ~SystemAddressAndGroupId(); + SystemAddressAndGroupId(SystemAddress _systemAddress, ConnectionGraphGroupID _groupID, RakNetGUID _guid); + + SystemAddress systemAddress; + ConnectionGraphGroupID groupId; + RakNetGUID guid; + + bool operator==( const SystemAddressAndGroupId& right ) const; + bool operator!=( const SystemAddressAndGroupId& right ) const; + bool operator > ( const SystemAddressAndGroupId& right ) const; + bool operator < ( const SystemAddressAndGroupId& right ) const; + }; + + // -------------------------------------------------------------------------------------------- + // User functions + // -------------------------------------------------------------------------------------------- + /// Plaintext encoding of the password, or 0 for none. If you use a password, use secure connections + void SetPassword(const char *password); + + /// \brief Returns the connection graph. + /// \return The connection graph, stored as map of adjacency lists + DataStructures::WeightedGraph *GetGraph(void); + + /// \brief Automatically add new connections to the connection graph. + /// \details Defaults to true + /// If true, then the system will automatically add all new connections for you, assigning groupId 0 to these systems. + /// If you want more control, you should call SetAutoAddNewConnections(false); + /// When false, it is up to you to call RequestConnectionGraph and AddNewConnection to complete the graph + /// However, this way you can choose which nodes are on the graph for this system and can assign groupIds to those nodes + /// \param[in] autoAdd true to automatically add new connections to the connection graph. False to not do so. + void SetAutoAddNewConnections(bool autoAdd); + + /// \brief Requests the connection graph from another system. + /// \details Only necessary to call if SetAutoAddNewConnections(false) is called. + /// You should call this sometime after getting ID_CONNECTION_REQUEST_ACCEPTED and \a systemAddress is or should be a node on the connection graph + /// \param[in] systemAddress The system to send to + void RequestConnectionGraph(SystemAddress systemAddress); + + /// \brief Adds a new connection to the connection graph from this system to the specified system. Also assigns a group identifier for that system + /// \details Only used and valid when SetAutoAddNewConnections(false) is called. + /// Call this for this system sometime after getting ID_NEW_INCOMING_CONNECTION or ID_CONNECTION_REQUEST_ACCEPTED for systems that contain a connection graph + /// Groups are sets of one or more nodes in the total graph + /// We only add to the graph groups which we subscribe to + /// \param[in] peer The instance of RakPeer to send through + /// \param[in] systemAddress The system that is connected to us. + /// \param[in] guid The system that is connected to us. + /// \param[in] groupId Just a number representing a group. Important: 0 is reserved to mean unassigned group ID and is assigned to all systems added with SetAutoAddNewConnections(true) + void AddNewConnection(RakPeerInterface *peer, SystemAddress systemAddress, RakNetGUID guid, ConnectionGraphGroupID groupId); + + /// \brief Sets our own group ID. + /// \details Only used and valid when SetAutoAddNewConnections(false) is called. + /// Defaults to 0 + /// \param[in] groupId Our group ID + void SetGroupId(ConnectionGraphGroupID groupId); + + /// \brief Allows adding to the connection graph nodes with this groupId. + /// \details By default, you subscribe to group 0, which are all systems automatically added with SetAutoAddNewConnections(true) + /// Calling this does not add nodes which were previously rejected due to an unsubscribed group - it only allows addition of nodes after the fact + /// \param[in] groupId Just a number representing a group. 0 is reserved to mean unassigned group ID, automatically added with SetAutoAddNewConnections(true) + void SubscribeToGroup(ConnectionGraphGroupID groupId); + + /// \brief Disables addition of graph nodes with this groupId. + /// \details Calling this does not add remove nodes with this groupId which are already present in the graph. It only disables addition of nodes after the fact + /// \param[in] groupId Just a number representing a group. 0 is reserved to mean unassigned group ID, automatically added with SetAutoAddNewConnections(true) + void UnsubscribeFromGroup(ConnectionGraphGroupID groupId); + + // -------------------------------------------------------------------------------------------- + // Packet handling functions + // -------------------------------------------------------------------------------------------- + /// \internal + virtual void OnRakPeerShutdown(void); + /// \internal + virtual void Update(void); + /// \internal + virtual PluginReceiveResult OnReceive(Packet *packet); + /// \internal + virtual void OnClosedConnection(SystemAddress systemAddress, RakNetGUID rakNetGUID, PI2_LostConnectionReason lostConnectionReason ); + /// \internal + virtual void OnNewConnection(SystemAddress systemAddress, RakNetGUID rakNetGUID, bool isIncoming); + +protected: + void HandleDroppedConnection(RakPeerInterface *peer, SystemAddress systemAddress, unsigned char packetId); + void DeleteFromPeerList(SystemAddress systemAddress); + + void OnNewIncomingConnection(Packet *packet); + void OnConnectionGraphRequest(Packet *packet); + void OnConnectionGraphReply(Packet *packet); + void OnConnectionGraphUpdate(Packet *packet); + void OnNewConnectionInternal(Packet *packet); + bool OnConnectionLostInternal(Packet *packet, unsigned char packetId); + void OnConnectionAddition(Packet *packet); + void OnConnectionRemoval(Packet *packet); + void SendConnectionGraph(SystemAddress target, unsigned char packetId, RakPeerInterface *peer); + void SerializeWeightedGraph(RakNet::BitStream *out, const DataStructures::WeightedGraph &g) const; + bool DeserializeWeightedGraph(RakNet::BitStream *inBitstream, RakPeerInterface *peer); + void AddAndRelayConnection(DataStructures::OrderedList &ignoreList, const SystemAddressAndGroupId &conn1, const SystemAddressAndGroupId &conn2, unsigned short ping, RakPeerInterface *peer); + bool RemoveAndRelayConnection(DataStructures::OrderedList &ignoreList, unsigned char packetId, const SystemAddress node1, const SystemAddress node2, RakPeerInterface *peer); + void RemoveParticipant(SystemAddress systemAddress); + void AddParticipant(SystemAddress systemAddress); + void BroadcastGraphUpdate(DataStructures::OrderedList &ignoreList, RakPeerInterface *peer); + void NotifyUserOfRemoteConnection(const SystemAddressAndGroupId &conn1, const SystemAddressAndGroupId &conn2,unsigned short ping, RakPeerInterface *peer); + bool IsNewRemoteConnection(const SystemAddressAndGroupId &conn1, const SystemAddressAndGroupId &conn2,RakPeerInterface *peer); + bool DeserializeIgnoreList(DataStructures::OrderedList &ignoreList, RakNet::BitStream *inBitstream ); + void SerializeIgnoreListAndBroadcast(RakNet::BitStream *outBitstream, DataStructures::OrderedList &ignoreList, RakPeerInterface *peer); + + RakNetTime nextWeightUpdate; + char *pw; + DataStructures::OrderedList participantList; + + DataStructures::WeightedGraph graph; + bool autoAddNewConnections; + ConnectionGraphGroupID myGroupId; + + DataStructures::OrderedList subscribedGroups; + + // Used to broadcast new connections after some time so the pings are correct + //RakNetTime forceBroadcastTime; +}; + +#endif + +#endif // _RAKNET_SUPPORT_* diff --git a/RakNet/Sources/ConnectionGraph2.cpp b/RakNet/Sources/ConnectionGraph2.cpp new file mode 100644 index 0000000..1c3d6c6 --- /dev/null +++ b/RakNet/Sources/ConnectionGraph2.cpp @@ -0,0 +1,173 @@ +#include "NativeFeatureIncludes.h" +#if _RAKNET_SUPPORT_ConnectionGraph2==1 + +#include "ConnectionGraph2.h" +#include "RakPeerInterface.h" +#include "MessageIdentifiers.h" +#include "BitStream.h" + +int ConnectionGraph2::RemoteSystemComp( const RakNetGUID &key, RemoteSystem * const &data ) +{ + if (key < data->guid) + return -1; + if (key > data->guid) + return 1; + return 0; +} + +int ConnectionGraph2::SystemAddressAndGuidComp( const SystemAddressAndGuid &key, const SystemAddressAndGuid &data ) +{ + if (key.guiddata.guid) + return 1; + return 0; +} + +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); +} +void ConnectionGraph2::OnClosedConnection(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__,__LINE__); + remoteSystems.RemoveAtIndex(idx); + } +} +void ConnectionGraph2::OnNewConnection(SystemAddress systemAddress, RakNetGUID rakNetGUID, bool isIncoming) +{ + (void) isIncoming; + + // Send all existing systems to new connection + RakNet::BitStream bs; + bs.Write((MessageID)ID_REMOTE_NEW_INCOMING_CONNECTION); + bs.Write((unsigned int)1); + bs.Write(systemAddress); + bs.Write(rakNetGUID); + SendUnified(&bs,HIGH_PRIORITY,RELIABLE_ORDERED,0,systemAddress,true); + + // Send everyone to the new guy + DataStructures::List addresses; + DataStructures::List guids; + rakPeerInterface->GetSystemList(addresses, guids); + bs.Reset(); + bs.Write((MessageID)ID_REMOTE_NEW_INCOMING_CONNECTION); + BitSize_t writeOffset = bs.GetWriteOffset(); + bs.Write((unsigned int) addresses.Size()); + + unsigned int i; + unsigned int count=0; + for (i=0; i < addresses.Size(); i++) + { + if (addresses[i]==systemAddress) + continue; + + bs.Write(addresses[i]); + bs.Write(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); + } + + RemoteSystem* remoteSystem = RakNet::OP_NEW(__FILE__,__LINE__); + remoteSystem->guid=rakNetGUID; + remoteSystems.Insert(rakNetGUID,remoteSystem,true,__FILE__,__LINE__); +} +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); + SystemAddressAndGuid saag; + bs.Read(saag.systemAddress); + bs.Read(saag.guid); + remoteSystems[idx]->remoteConnections.Remove(saag); + } + } + else if (packet->data[0]==ID_REMOTE_NEW_INCOMING_CONNECTION) + { + bool objectExists; + unsigned idx = remoteSystems.GetIndexFromKey(packet->guid, &objectExists); + if (objectExists) + { + unsigned int numAddresses; + RakNet::BitStream bs(packet->data,packet->length,false); + bs.Read(numAddresses); + for (unsigned int idx2=0; idx2 < numAddresses; idx2++) + { + SystemAddressAndGuid saag; + bs.Read(saag.systemAddress); + bs.Read(saag.guid); + remoteSystems[idx]->remoteConnections.Insert(saag,saag,true,__FILE__,__LINE__); + } + } + } + + return RR_CONTINUE_PROCESSING; +} + +#endif // _RAKNET_SUPPORT_* diff --git a/RakNet/Sources/ConnectionGraph2.h b/RakNet/Sources/ConnectionGraph2.h new file mode 100644 index 0000000..5bf9118 --- /dev/null +++ b/RakNet/Sources/ConnectionGraph2.h @@ -0,0 +1,76 @@ +/// \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.h" +#if _RAKNET_SUPPORT_ConnectionGraph2==1 + +#ifndef __CONNECTION_GRAPH_2_H +#define __CONNECTION_GRAPH_2_H + +class RakPeerInterface; +#include "RakMemoryOverride.h" +#include "RakNetTypes.h" +#include "PluginInterface2.h" +#include "DS_List.h" +#include "DS_WeightedGraph.h" +#include "GetTime.h" +#include "Export.h" + +/// \brief A one hop connection graph. +/// \details Sends ID_CONNECTION_GRAPH_DISCONNECTION_NOTIFICATION, ID_CONNECTION_GRAPH_CONNECTION_LOST, ID_CONNECTION_GRAPH_NEW_CONNECTION
+/// 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: + + /// \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); + + /// \internal + struct SystemAddressAndGuid + { + SystemAddress systemAddress; + RakNetGUID guid; + }; + /// \internal + static int SystemAddressAndGuidComp( const SystemAddressAndGuid &key, const SystemAddressAndGuid &data ); + + /// \internal + struct RemoteSystem + { + DataStructures::OrderedList remoteConnections; + RakNetGUID guid; + }; + /// \internal + static int RemoteSystemComp( const RakNetGUID &key, RemoteSystem * const &data ); + +protected: + /// \internal + virtual void OnClosedConnection(SystemAddress systemAddress, RakNetGUID rakNetGUID, PI2_LostConnectionReason lostConnectionReason ); + /// \internal + virtual void OnNewConnection(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 remoteSystems; + +}; + +#endif + +#endif // _RAKNET_SUPPORT_* diff --git a/RakNet/Sources/ConsoleServer.cpp b/RakNet/Sources/ConsoleServer.cpp new file mode 100644 index 0000000..ae8f919 --- /dev/null +++ b/RakNet/Sources/ConsoleServer.cpp @@ -0,0 +1,307 @@ +#include "NativeFeatureIncludes.h" +#if _RAKNET_SUPPORT_ConsoleServer==1 + +#include "ConsoleServer.h" +#include "TransportInterface.h" +#include "CommandParserInterface.h" +#include +#include + +#define COMMAND_DELINATOR ' ' +#define COMMAND_DELINATOR_TOGGLE '"' + +#include "LinuxStrings.h" + +ConsoleServer::ConsoleServer() +{ + transport=0; + password[0]=0; + prompt=0; +} +ConsoleServer::~ConsoleServer() +{ + if (prompt) + rakFree_Ex(prompt, __FILE__, __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__, __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; + SystemAddress newOrLostConnectionId; + Packet *p; + 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; + 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 Show help on a particular parser.\r\n"); + transport->Send(p->systemAddress, "help Show help on a particular command.\r\n"); + transport->Send(p->systemAddress, "quit Disconnects from the server.\r\n"); + transport->Send(p->systemAddress, "[] [] Execute a command\r\n"); + transport->Send(p->systemAddress, "[] [] 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. + RegisteredCommand rc; + for (i=0; i < commandParserList.Size(); i++) + { + if (commandParserList[i]->GetRegisteredCommand(parameterList[1], &rc)) + { + if (rc.parameterCount==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 + { + 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__,__LINE__); + if (_prompt && _prompt[0]) + { + size_t len = strlen(_prompt); + prompt = (char*) rakMalloc_Ex(len+1,__FILE__,__LINE__); + strcpy(prompt,_prompt); + } + else + prompt=0; +} + +#endif // _RAKNET_SUPPORT_* diff --git a/RakNet/Sources/ConsoleServer.h b/RakNet/Sources/ConsoleServer.h new file mode 100644 index 0000000..6bbd6c7 --- /dev/null +++ b/RakNet/Sources/ConsoleServer.h @@ -0,0 +1,68 @@ +/// \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.h" +#if _RAKNET_SUPPORT_ConsoleServer==1 + +#ifndef __CONSOLE_SERVER_H +#define __CONSOLE_SERVER_H + +class TransportInterface; +class CommandParserInterface; + +#include "RakMemoryOverride.h" +#include "DS_List.h" +#include "RakNetTypes.h" +#include "Export.h" + +/// \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: + 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 commandParserList; + char* password[256]; + char *prompt; +}; + +#endif + +#endif // _RAKNET_SUPPORT_* diff --git a/RakNet/Sources/DS_BPlusTree.h b/RakNet/Sources/DS_BPlusTree.h new file mode 100644 index 0000000..f9aade6 --- /dev/null +++ b/RakNet/Sources/DS_BPlusTree.h @@ -0,0 +1,1146 @@ +/// \file DS_BPlusTree.h +/// +/// This file is part of RakNet Copyright 2003 Jenkins Software LLC +/// +/// Usage of RakNet is subject to the appropriate license agreement. + +#ifndef __B_PLUS_TREE_CPP +#define __B_PLUS_TREE_CPP + +#include "DS_MemoryPool.h" +#include "DS_Queue.h" +#include +#include "Export.h" + +// Java +// http://www.seanster.com/BplusTree/BplusTree.html + +// Overview +// http://babbage.clarku.edu/~achou/cs160/B+Trees/B+Trees.htm + +// Deletion +// http://dbpubs.stanford.edu:8090/pub/1995-19 + +#ifdef _MSC_VER +#pragma warning( push ) +#endif + +#include "RakMemoryOverride.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 +{ + /// Used in the BPlusTree. Used for both leaf and index nodes. + /// Don't use a constructor or destructor, due to the memory pool I am using + template + struct RAK_DLL_EXPORT Page + { + // We use the same data structure for both leaf and index nodes. + // It uses a little more memory for index nodes but reduces + // memory fragmentation, allocations, and deallocations. + bool isLeaf; + + // Used for both leaf and index nodes. + // For a leaf it means the number of elements in data + // For an index it means the number of keys and is one less than the number of children pointers. + int size; + + // Used for both leaf and index nodes. + KeyType keys[order]; + + // Used only for leaf nodes. Data is the actual data, while next is the pointer to the next leaf (for B+) + DataType data[order]; + Page *next; + Page *previous; + + // Used only for index nodes. Pointers to the children of this node. + Page *children[order+1]; + }; + + /// A BPlus tree + /// Written with efficiency and speed in mind. + template + class RAK_DLL_EXPORT BPlusTree + { + public: + struct ReturnAction + { + KeyType key1; + KeyType key2; + enum + { + NO_ACTION, + REPLACE_KEY1_WITH_KEY2, + PUSH_KEY_TO_PARENT, + SET_BRANCH_KEY, + } action; // 0=none, 1=replace key1 with key2 + }; + + BPlusTree(); + ~BPlusTree(); + void SetPoolPageSize(int size); // Set the page size for the memory pool. Optionsl + bool Get(const KeyType key, DataType &out) const; + bool Delete(const KeyType key); + bool Delete(const KeyType key, DataType &out); + bool Insert(const KeyType key, const DataType &data); + void Clear(void); + unsigned Size(void) const; + bool IsEmpty(void) const; + Page *GetListHead(void) const; + DataType GetDataHead(void) const; + void PrintLeaves(void); + void ForEachLeaf(void (*func)(Page * leaf, int index)); + void ForEachData(void (*func)(DataType input, int index)); + void PrintGraph(void); + protected: + void ValidateTreeRecursive(Page *cur); + void DeleteFromPageAtIndex(const int index, Page *cur); + static void PrintLeaf(Page * leaf, int index); + void FreePages(void); + bool GetIndexOf(const KeyType key, Page *page, int *out) const; + void ShiftKeysLeft(Page *cur); + bool CanRotateLeft(Page *cur, int childIndex); + bool CanRotateRight(Page *cur, int childIndex); + void RotateRight(Page *cur, int childIndex, ReturnAction *returnAction); + void RotateLeft(Page *cur, int childIndex, ReturnAction *returnAction); + Page* InsertIntoNode(const KeyType key, const DataType &childData, int insertionIndex, Page *nodeData, Page *cur, ReturnAction* returnAction); + Page* InsertBranchDown(const KeyType key, const DataType &data,Page *cur, ReturnAction* returnAction, bool *success); + Page* GetLeafFromKey(const KeyType key) const; + bool FindDeleteRebalance(const KeyType key, Page *cur, bool *underflow, KeyType rightRootKey, ReturnAction *returnAction, DataType &out); + bool FixUnderflow(int branchIndex, Page *cur, KeyType rightRootKey, ReturnAction *returnAction); + void ShiftNodeLeft(Page *cur); + void ShiftNodeRight(Page *cur); + + MemoryPool > pagePool; + Page *root, *leftmostLeaf; + }; + + template + BPlusTree::BPlusTree () + { + RakAssert(order>1); + root=0; + leftmostLeaf=0; + } + template + BPlusTree::~BPlusTree () + { + Clear(); + } + template + void BPlusTree::SetPoolPageSize(int size) + { + pagePool.SetPageSize(size); + } + template + bool BPlusTree::Get(const KeyType key, DataType &out) const + { + if (root==0) + return false; + + Page* leaf = GetLeafFromKey(key); + int childIndex; + + if (GetIndexOf(key, leaf, &childIndex)) + { + out=leaf->data[childIndex]; + return true; + } + return false; + } + template + void BPlusTree::DeleteFromPageAtIndex(const int index, Page *cur) + { + int i; + for (i=index; i < cur->size-1; i++) + cur->keys[i]=cur->keys[i+1]; + if (cur->isLeaf) + { + for (i=index; i < cur->size-1; i++) + cur->data[i]=cur->data[i+1]; + } + else + { + for (i=index; i < cur->size-1; i++) + cur->children[i+1]=cur->children[i+2]; + } + cur->size--; + } + template + bool BPlusTree::Delete(const KeyType key) + { + DataType temp; + return Delete(key, temp); + } + template + bool BPlusTree::Delete(const KeyType key, DataType &out) + { + if (root==0) + return false; + + ReturnAction returnAction; + returnAction.action=ReturnAction::NO_ACTION; + int childIndex; + bool underflow=false; + if (root==leftmostLeaf) + { + if (GetIndexOf(key, root, &childIndex)==false) + return false; + out=root->data[childIndex]; + DeleteFromPageAtIndex(childIndex,root); + if (root->size==0) + { + pagePool.Release(root, __FILE__,__LINE__); + root=0; + leftmostLeaf=0; + } + return true; + } + else if (FindDeleteRebalance(key, root, &underflow,root->keys[0], &returnAction, out)==false) + return false; + +// RakAssert(returnAction.action==ReturnAction::NO_ACTION); + + if (underflow && root->size==0) + { + // Move the root down. + Page *oldRoot=root; + root=root->children[0]; + pagePool.Release(oldRoot, __FILE__,__LINE__); + // memset(oldRoot,0,sizeof(root)); + } + + return true; + } + template + bool BPlusTree::FindDeleteRebalance(const KeyType key, Page *cur, bool *underflow, KeyType rightRootKey, ReturnAction *returnAction, DataType &out) + { + // Get index of child to follow. + int branchIndex, childIndex; + if (GetIndexOf(key, cur, &childIndex)) + branchIndex=childIndex+1; + else + branchIndex=childIndex; + + // If child is not a leaf, call recursively + if (cur->children[branchIndex]->isLeaf==false) + { + if (branchIndexsize) + rightRootKey=cur->keys[branchIndex]; // Shift right to left + else + rightRootKey=cur->keys[branchIndex-1]; // Shift center to left + + if (FindDeleteRebalance(key, cur->children[branchIndex], underflow, rightRootKey, returnAction, out)==false) + return false; + + // Call again in case the root key changed + if (branchIndexsize) + rightRootKey=cur->keys[branchIndex]; // Shift right to left + else + rightRootKey=cur->keys[branchIndex-1]; // Shift center to left + + if (returnAction->action==ReturnAction::SET_BRANCH_KEY && branchIndex!=childIndex) + { + returnAction->action=ReturnAction::NO_ACTION; + cur->keys[childIndex]=returnAction->key1; + + if (branchIndexsize) + rightRootKey=cur->keys[branchIndex]; // Shift right to left + else + rightRootKey=cur->keys[branchIndex-1]; // Shift center to left + } + } + else + { + // If child is a leaf, get the index of the key. If the item is not found, cancel delete. + if (GetIndexOf(key, cur->children[branchIndex], &childIndex)==false) + return false; + + // Delete: + // Remove childIndex from the child at branchIndex + out=cur->children[branchIndex]->data[childIndex]; + DeleteFromPageAtIndex(childIndex, cur->children[branchIndex]); + + if (childIndex==0) + { + if (branchIndex>0) + cur->keys[branchIndex-1]=cur->children[branchIndex]->keys[0]; + + if (branchIndex==0) + { + returnAction->action=ReturnAction::SET_BRANCH_KEY; + returnAction->key1=cur->children[0]->keys[0]; + } + } + + if (cur->children[branchIndex]->size < order/2) + *underflow=true; + else + *underflow=false; + } + + // Fix underflow: + if (*underflow) + { + *underflow=FixUnderflow(branchIndex, cur, rightRootKey, returnAction); + } + + return true; + } + template + bool BPlusTree::FixUnderflow(int branchIndex, Page *cur, KeyType rightRootKey, ReturnAction *returnAction) + { + // Borrow from a neighbor that has excess. + Page *source; + Page *dest; + + if (branchIndex>0 && cur->children[branchIndex-1]->size > order/2) + { + dest=cur->children[branchIndex]; + source=cur->children[branchIndex-1]; + + // Left has excess + ShiftNodeRight(dest); + if (dest->isLeaf) + { + dest->keys[0]=source->keys[source->size-1]; + dest->data[0]=source->data[source->size-1]; + } + else + { + dest->children[0]=source->children[source->size]; + dest->keys[0]=cur->keys[branchIndex-1]; + } + // Update the parent key for the child (middle) + cur->keys[branchIndex-1]=source->keys[source->size-1]; + source->size--; + + // if (branchIndex==0) + // { + // returnAction->action=ReturnAction::SET_BRANCH_KEY; + // returnAction->key1=dest->keys[0]; + // } + + // No underflow + return false; + } + else if (branchIndexsize && cur->children[branchIndex+1]->size > order/2) + { + dest=cur->children[branchIndex]; + source=cur->children[branchIndex+1]; + + // Right has excess + if (dest->isLeaf) + { + dest->keys[dest->size]=source->keys[0]; + dest->data[dest->size]=source->data[0]; + + // The first key in the leaf after shifting is the parent key for the right branch + cur->keys[branchIndex]=source->keys[1]; + +#ifdef _MSC_VER +#pragma warning( disable : 4127 ) // warning C4127: conditional expression is constant +#endif + if (order<=3 && dest->size==0) + { + if (branchIndex==0) + { + returnAction->action=ReturnAction::SET_BRANCH_KEY; + returnAction->key1=dest->keys[0]; + } + else + cur->keys[branchIndex-1]=cur->children[branchIndex]->keys[0]; + } + } + else + { + if (returnAction->action==ReturnAction::NO_ACTION) + { + returnAction->action=ReturnAction::SET_BRANCH_KEY; + returnAction->key1=dest->keys[0]; + } + + dest->keys[dest->size]=rightRootKey; + dest->children[dest->size+1]=source->children[0]; + + // The shifted off key is the leftmost key for a node + cur->keys[branchIndex]=source->keys[0]; + } + + + dest->size++; + ShiftNodeLeft(source); + + //cur->keys[branchIndex]=source->keys[0]; + +// returnAction->action=ReturnAction::SET_BRANCH_KEY; +// returnAction->key1=dest->keys[dest->size-1]; + + // No underflow + return false; + } + else + { + int sourceIndex; + + // If no neighbors have excess, merge two branches. + // + // To merge two leaves, just copy the data and keys over. + // + // To merge two branches, copy the pointers and keys over, using rightRootKey as the key for the extra pointer + if (branchIndexsize) + { + // Merge right child to current child and delete right child. + dest=cur->children[branchIndex]; + source=cur->children[branchIndex+1]; + } + else + { + // Move current child to left and delete current child + dest=cur->children[branchIndex-1]; + source=cur->children[branchIndex]; + } + + // Merge + if (dest->isLeaf) + { + for (sourceIndex=0; sourceIndexsize; sourceIndex++) + { + dest->keys[dest->size]=source->keys[sourceIndex]; + dest->data[dest->size++]=source->data[sourceIndex]; + } + } + else + { + // We want the tree root key of the source, not the current. + dest->keys[dest->size]=rightRootKey; + dest->children[dest->size++ + 1]=source->children[0]; + for (sourceIndex=0; sourceIndexsize; sourceIndex++) + { + dest->keys[dest->size]=source->keys[sourceIndex]; + dest->children[dest->size++ + 1]=source->children[sourceIndex + 1]; + } + } + +#ifdef _MSC_VER +#pragma warning( disable : 4127 ) // warning C4127: conditional expression is constant +#endif + if (order<=3 && branchIndex>0 && cur->children[branchIndex]->isLeaf) // With order==2 it is possible to delete data[0], which is not possible with higher orders. + cur->keys[branchIndex-1]=cur->children[branchIndex]->keys[0]; + + if (branchIndexsize) + { + // Update the parent key, removing the source (right) + DeleteFromPageAtIndex(branchIndex, cur); + } + else + { + if (branchIndex>0) + { + // Update parent key, removing the source (current) + DeleteFromPageAtIndex(branchIndex-1, cur); + } + } + + if (branchIndex==0 && dest->isLeaf) + { + returnAction->action=ReturnAction::SET_BRANCH_KEY; + returnAction->key1=dest->keys[0]; + } + + if (source==leftmostLeaf) + leftmostLeaf=source->next; + + if (source->isLeaf) + { + if (source->previous) + source->previous->next=source->next; + if (source->next) + source->next->previous=source->previous; + } + + // Free the source node + pagePool.Release(source, __FILE__,__LINE__); + // memset(source,0,sizeof(root)); + + // Return underflow or not of parent. + return cur->size < order/2; + } + } + template + void BPlusTree::ShiftNodeRight(Page *cur) + { + int i; + for (i=cur->size; i>0; i--) + cur->keys[i]=cur->keys[i-1]; + if (cur->isLeaf) + { + for (i=cur->size; i>0; i--) + cur->data[i]=cur->data[i-1]; + } + else + { + for (i=cur->size+1; i>0; i--) + cur->children[i]=cur->children[i-1]; + } + + cur->size++; + } + template + void BPlusTree::ShiftNodeLeft(Page *cur) + { + int i; + for (i=0; i < cur->size-1; i++) + cur->keys[i]=cur->keys[i+1]; + if (cur->isLeaf) + { + for (i=0; i < cur->size; i++) + cur->data[i]=cur->data[i+1]; + } + else + { + for (i=0; i < cur->size; i++) + cur->children[i]=cur->children[i+1]; + } + cur->size--; + } + template + Page* BPlusTree::InsertIntoNode(const KeyType key, const DataType &leafData, int insertionIndex, Page *nodeData, Page *cur, ReturnAction* returnAction) + { + int i; + if (cur->size < order) + { + for (i=cur->size; i > insertionIndex; i--) + cur->keys[i]=cur->keys[i-1]; + if (cur->isLeaf) + { + for (i=cur->size; i > insertionIndex; i--) + cur->data[i]=cur->data[i-1]; + } + else + { + for (i=cur->size+1; i > insertionIndex+1; i--) + cur->children[i]=cur->children[i-1]; + } + cur->keys[insertionIndex]=key; + if (cur->isLeaf) + cur->data[insertionIndex]=leafData; + else + cur->children[insertionIndex+1]=nodeData; + + cur->size++; + } + else + { + Page* newPage = pagePool.Allocate( __FILE__, __LINE__ ); + newPage->isLeaf=cur->isLeaf; + if (cur->isLeaf) + { + newPage->next=cur->next; + if (cur->next) + cur->next->previous=newPage; + newPage->previous=cur; + cur->next=newPage; + } + + int destIndex, sourceIndex; + + if (insertionIndex>=(order+1)/2) + { + destIndex=0; + sourceIndex=order/2; + + for (; sourceIndex < insertionIndex; sourceIndex++, destIndex++) + { + newPage->keys[destIndex]=cur->keys[sourceIndex]; + } + newPage->keys[destIndex++]=key; + for (; sourceIndex < order; sourceIndex++, destIndex++) + { + newPage->keys[destIndex]=cur->keys[sourceIndex]; + } + + destIndex=0; + sourceIndex=order/2; + if (cur->isLeaf) + { + for (; sourceIndex < insertionIndex; sourceIndex++, destIndex++) + { + newPage->data[destIndex]=cur->data[sourceIndex]; + } + newPage->data[destIndex++]=leafData; + for (; sourceIndex < order; sourceIndex++, destIndex++) + { + newPage->data[destIndex]=cur->data[sourceIndex]; + } + } + else + { + + for (; sourceIndex < insertionIndex; sourceIndex++, destIndex++) + { + newPage->children[destIndex]=cur->children[sourceIndex+1]; + } + newPage->children[destIndex++]=nodeData; + + // sourceIndex+1 is sort of a hack but it works - because there is one extra child than keys + // skip past the last child for cur + for (; sourceIndex+1 < cur->size+1; sourceIndex++, destIndex++) + { + newPage->children[destIndex]=cur->children[sourceIndex+1]; + } + + // the first key is the middle key. Remove it from the page and push it to the parent + returnAction->action=ReturnAction::PUSH_KEY_TO_PARENT; + returnAction->key1=newPage->keys[0]; + for (int i=0; i < destIndex-1; i++) + newPage->keys[i]=newPage->keys[i+1]; + + } + cur->size=order/2; + } + else + { + destIndex=0; + sourceIndex=(order+1)/2-1; + for (; sourceIndex < order; sourceIndex++, destIndex++) + newPage->keys[destIndex]=cur->keys[sourceIndex]; + destIndex=0; + if (cur->isLeaf) + { + sourceIndex=(order+1)/2-1; + for (; sourceIndex < order; sourceIndex++, destIndex++) + newPage->data[destIndex]=cur->data[sourceIndex]; + } + else + { + sourceIndex=(order+1)/2; + for (; sourceIndex < order+1; sourceIndex++, destIndex++) + newPage->children[destIndex]=cur->children[sourceIndex]; + + // the first key is the middle key. Remove it from the page and push it to the parent + returnAction->action=ReturnAction::PUSH_KEY_TO_PARENT; + returnAction->key1=newPage->keys[0]; + for (int i=0; i < destIndex-1; i++) + newPage->keys[i]=newPage->keys[i+1]; + } + cur->size=(order+1)/2-1; + if (cur->size) + { + bool b = GetIndexOf(key, cur, &insertionIndex); + (void) b; + RakAssert(b==false); + } + else + insertionIndex=0; + InsertIntoNode(key, leafData, insertionIndex, nodeData, cur, returnAction); + } + + newPage->size=destIndex; + + return newPage; + } + + return 0; + } + + template + bool BPlusTree::CanRotateLeft(Page *cur, int childIndex) + { + return childIndex>0 && cur->children[childIndex-1]->size + void BPlusTree::RotateLeft(Page *cur, int childIndex, ReturnAction *returnAction) + { + Page *dest = cur->children[childIndex-1]; + Page *source = cur->children[childIndex]; + returnAction->key1=source->keys[0]; + dest->keys[dest->size]=source->keys[0]; + dest->data[dest->size]=source->data[0]; + dest->size++; + for (int i=0; i < source->size-1; i++) + { + source->keys[i]=source->keys[i+1]; + source->data[i]=source->data[i+1]; + } + source->size--; + cur->keys[childIndex-1]=source->keys[0]; + returnAction->key2=source->keys[0]; + } + + template + bool BPlusTree::CanRotateRight(Page *cur, int childIndex) + { + return childIndex < cur->size && cur->children[childIndex+1]->size + void BPlusTree::RotateRight(Page *cur, int childIndex, ReturnAction *returnAction) + { + Page *dest = cur->children[childIndex+1]; + Page *source = cur->children[childIndex]; + returnAction->key1=dest->keys[0]; + for (int i= dest->size; i > 0; i--) + { + dest->keys[i]=dest->keys[i-1]; + dest->data[i]=dest->data[i-1]; + } + dest->keys[0]=source->keys[source->size-1]; + dest->data[0]=source->data[source->size-1]; + dest->size++; + source->size--; + + cur->keys[childIndex]=dest->keys[0]; + returnAction->key2=dest->keys[0]; + } + template + Page* BPlusTree::GetLeafFromKey(const KeyType key) const + { + Page* cur = root; + int childIndex; + while (cur->isLeaf==false) + { + // When searching, if we match the exact key we go down the pointer after that index + if (GetIndexOf(key, cur, &childIndex)) + childIndex++; + cur = cur->children[childIndex]; + } + return cur; + } + + template + Page* BPlusTree::InsertBranchDown(const KeyType key, const DataType &data,Page *cur, ReturnAction *returnAction, bool *success) + { + int childIndex; + int branchIndex; + if (GetIndexOf(key, cur, &childIndex)) + branchIndex=childIndex+1; + else + branchIndex=childIndex; + Page* newPage; + if (cur->isLeaf==false) + { + if (cur->children[branchIndex]->isLeaf==true && cur->children[branchIndex]->size==order) + { + if (branchIndex==childIndex+1) + { + *success=false; + return 0; // Already exists + } + + if (CanRotateLeft(cur, branchIndex)) + { + returnAction->action=ReturnAction::REPLACE_KEY1_WITH_KEY2; + if (key > cur->children[branchIndex]->keys[0]) + { + RotateLeft(cur, branchIndex, returnAction); + + int insertionIndex; + GetIndexOf(key, cur->children[branchIndex], &insertionIndex); + InsertIntoNode(key, data, insertionIndex, 0, cur->children[branchIndex], 0); + } + else + { + // Move head element to left and replace it with key,data + Page* dest=cur->children[branchIndex-1]; + Page* source=cur->children[branchIndex]; + returnAction->key1=source->keys[0]; + returnAction->key2=key; + dest->keys[dest->size]=source->keys[0]; + dest->data[dest->size]=source->data[0]; + dest->size++; + source->keys[0]=key; + source->data[0]=data; + } + cur->keys[branchIndex-1]=cur->children[branchIndex]->keys[0]; + + return 0; + } + else if (CanRotateRight(cur, branchIndex)) + { + returnAction->action=ReturnAction::REPLACE_KEY1_WITH_KEY2; + + if (key < cur->children[branchIndex]->keys[cur->children[branchIndex]->size-1]) + { + RotateRight(cur, branchIndex, returnAction); + + int insertionIndex; + GetIndexOf(key, cur->children[branchIndex], &insertionIndex); + InsertIntoNode(key, data, insertionIndex, 0, cur->children[branchIndex], 0); + + } + else + { + // Insert to the head of the right leaf instead and change our key + returnAction->key1=cur->children[branchIndex+1]->keys[0]; + InsertIntoNode(key, data, 0, 0, cur->children[branchIndex+1], 0); + returnAction->key2=key; + } + cur->keys[branchIndex]=cur->children[branchIndex+1]->keys[0]; + return 0; + } + } + + newPage=InsertBranchDown(key,data,cur->children[branchIndex], returnAction, success); + if (returnAction->action==ReturnAction::REPLACE_KEY1_WITH_KEY2) + { + if (branchIndex>0 && cur->keys[branchIndex-1]==returnAction->key1) + cur->keys[branchIndex-1]=returnAction->key2; + } + if (newPage) + { + if (newPage->isLeaf==false) + { + RakAssert(returnAction->action==ReturnAction::PUSH_KEY_TO_PARENT); + newPage->size--; + return InsertIntoNode(returnAction->key1, data, branchIndex, newPage, cur, returnAction); + } + else + { + return InsertIntoNode(newPage->keys[0], data, branchIndex, newPage, cur, returnAction); + } + } + } + else + { + if (branchIndex==childIndex+1) + { + *success=false; + return 0; // Already exists + } + else + { + return InsertIntoNode(key, data, branchIndex, 0, cur, returnAction); + } + } + + return 0; + } + template + bool BPlusTree::Insert(const KeyType key, const DataType &data) + { + if (root==0) + { + // Allocate root and make root a leaf + root = pagePool.Allocate( __FILE__, __LINE__ ); + root->isLeaf=true; + leftmostLeaf=root; + root->size=1; + root->keys[0]=key; + root->data[0]=data; + root->next=0; + root->previous=0; + } + else + { + bool success=true; + ReturnAction returnAction; + returnAction.action=ReturnAction::NO_ACTION; + Page* newPage = InsertBranchDown(key, data, root, &returnAction, &success); + if (success==false) + return false; + if (newPage) + { + KeyType newKey; + if (newPage->isLeaf==false) + { + // One key is pushed up through the stack. I store that at keys[0] but it has to be removed for the page to be correct + RakAssert(returnAction.action==ReturnAction::PUSH_KEY_TO_PARENT); + newKey=returnAction.key1; + newPage->size--; + } + else + newKey = newPage->keys[0]; + // propagate the root + Page* newRoot = pagePool.Allocate( __FILE__, __LINE__ ); + newRoot->isLeaf=false; + newRoot->size=1; + newRoot->keys[0]=newKey; + newRoot->children[0]=root; + newRoot->children[1]=newPage; + root=newRoot; + } + } + + return true; + } + template + void BPlusTree::ShiftKeysLeft(Page *cur) + { + int i; + for (i=0; i < cur->size; i++) + cur->keys[i]=cur->keys[i+1]; + } + template + void BPlusTree::Clear(void) + { + if (root) + { + FreePages(); + leftmostLeaf=0; + root=0; + } + pagePool.Clear(__FILE__, __LINE__); + } + template + unsigned BPlusTree::Size(void) const + { + int count=0; + DataStructures::Page *cur = GetListHead(); + while (cur) + { + count+=cur->size; + cur=cur->next; + } + return count; + } + template + bool BPlusTree::IsEmpty(void) const + { + return root==0; + } + template + bool BPlusTree::GetIndexOf(const KeyType key, Page *page, int *out) const + { + RakAssert(page->size>0); + int index, upperBound, lowerBound; + upperBound=page->size-1; + lowerBound=0; + index = page->size/2; + +#ifdef _MSC_VER +#pragma warning( disable : 4127 ) // warning C4127: conditional expression is constant +#endif + while (1) + { + if (key==page->keys[index]) + { + *out=index; + return true; + } + else if (keykeys[index]) + upperBound=index-1; + else + lowerBound=index+1; + + index=lowerBound+(upperBound-lowerBound)/2; + + if (lowerBound>upperBound) + { + *out=lowerBound; + return false; // No match + } + } + } + template + void BPlusTree::FreePages(void) + { + DataStructures::Queue *> queue; + DataStructures::Page *ptr; + int i; + queue.Push(root, __FILE__, __LINE__ ); + while (queue.Size()) + { + ptr=queue.Pop(); + if (ptr->isLeaf==false) + { + for (i=0; i < ptr->size+1; i++) + queue.Push(ptr->children[i], __FILE__, __LINE__ ); + } + pagePool.Release(ptr, __FILE__,__LINE__); + // memset(ptr,0,sizeof(root)); + }; + } + template + Page *BPlusTree::GetListHead(void) const + { + return leftmostLeaf; + } + template + DataType BPlusTree::GetDataHead(void) const + { + return leftmostLeaf->data[0]; + } + template + void BPlusTree::ForEachLeaf(void (*func)(Page * leaf, int index)) + { + int count=0; + DataStructures::Page *cur = GetListHead(); + while (cur) + { + func(cur, count++); + cur=cur->next; + } + } + template + void BPlusTree::ForEachData(void (*func)(DataType input, int index)) + { + int count=0,i; + DataStructures::Page *cur = GetListHead(); + while (cur) + { + for (i=0; i < cur->size; i++) + func(cur->data[i], count++); + cur=cur->next; + } + } + template + void BPlusTree::PrintLeaf(Page * leaf, int index) + { + int i; + RAKNET_DEBUG_PRINTF("%i] SELF=%p\n", index+1, leaf); + for (i=0; i < leaf->size; i++) + RAKNET_DEBUG_PRINTF(" %i. %i\n", i+1, leaf->data[i]); + } + template + void BPlusTree::PrintLeaves(void) + { + ForEachLeaf(PrintLeaf); + } + + template + void BPlusTree::ValidateTreeRecursive(Page *cur) + { + RakAssert(cur==root || cur->size>=order/2); + + if (cur->children[0]->isLeaf) + { + RakAssert(cur->children[0]->keys[0] < cur->keys[0]); + for (int i=0; i < cur->size; i++) + { + RakAssert(cur->children[i+1]->keys[0]==cur->keys[i]); + } + } + else + { + for (int i=0; i < cur->size+1; i++) + ValidateTreeRecursive(cur->children[i]); + } + } + + template + void BPlusTree::PrintGraph(void) + { + DataStructures::Queue *> queue; + queue.Push(root,__FILE__,__LINE__); + queue.Push(0,__FILE__,__LINE__); + DataStructures::Page *ptr; + int i,j; + if (root) + { + RAKNET_DEBUG_PRINTF("%p(", root); + for (i=0; i < root->size; i++) + { + RAKNET_DEBUG_PRINTF("%i ", root->keys[i]); + } + RAKNET_DEBUG_PRINTF(") "); + RAKNET_DEBUG_PRINTF("\n"); + } + while (queue.Size()) + { + ptr=queue.Pop(); + if (ptr==0) + RAKNET_DEBUG_PRINTF("\n"); + else if (ptr->isLeaf==false) + { + for (i=0; i < ptr->size+1; i++) + { + RAKNET_DEBUG_PRINTF("%p(", ptr->children[i]); + //RAKNET_DEBUG_PRINTF("(", ptr->children[i]); + for (j=0; j < ptr->children[i]->size; j++) + RAKNET_DEBUG_PRINTF("%i ", ptr->children[i]->keys[j]); + RAKNET_DEBUG_PRINTF(") "); + queue.Push(ptr->children[i],__FILE__,__LINE__); + } + queue.Push(0,__FILE__,__LINE__); + RAKNET_DEBUG_PRINTF(" -- "); + } + } + RAKNET_DEBUG_PRINTF("\n"); + } +} +#ifdef _MSC_VER +#pragma warning( pop ) +#endif + +#endif + +// Code to test this hellish data structure. +/* +#include "DS_BPlusTree.h" +#include + +// Handle underflow on root. If there is only one item left then I can go downwards. +// Make sure I keep the leftmost pointer valid by traversing it +// When I free a leaf, be sure to adjust the pointers around it. + +#include "Rand.h" + +void main(void) +{ + DataStructures::BPlusTree btree; + DataStructures::List haveList, removedList; + int temp; + int i, j, index; + int testSize; + bool b; + + for (testSize=0; testSize < 514; testSize++) + { + RAKNET_DEBUG_PRINTF("TestSize=%i\n", testSize); + + for (i=0; i < testSize; i++) + haveList.Insert(i); + + for (i=0; i < testSize; i++) + { + index=i+randomMT()%(testSize-i); + temp=haveList[index]; + haveList[index]=haveList[i]; + haveList[i]=temp; + } + + for (i=0; i + * + * OR + * + * AVLBalancedBinarySearchTree + * + * Use the AVL balanced tree if you want the tree to be balanced after every deletion and addition. This avoids the potential + * worst case scenario where ordered input to a binary search tree gives linear search time results. It's not needed + * if input will be evenly distributed, in which case the search time is O (log n). The search time for the AVL + * balanced binary tree is O (log n) irregardless of input. + * + * Has the following member functions + * unsigned int Height() - Returns the height of the tree at the optional specified starting index. Default is the root + * add(element) - adds an element to the BinarySearchTree + * bool del(element) - deletes the node containing element if the element is in the tree as defined by a comparison with the == operator. Returns true on success, false if the element is not found + * bool IsInelement) - returns true if element is in the tree as defined by a comparison with the == operator. Otherwise returns false + * DisplayInorder(array) - Fills an array with an inorder search of the elements in the tree. USER IS REPONSIBLE FOR ALLOCATING THE ARRAY!. + * DisplayPreorder(array) - Fills an array with an preorder search of the elements in the tree. USER IS REPONSIBLE FOR ALLOCATING THE ARRAY!. + * DisplayPostorder(array) - Fills an array with an postorder search of the elements in the tree. USER IS REPONSIBLE FOR ALLOCATING THE ARRAY!. + * DisplayBreadthFirstSearch(array) - Fills an array with a breadth first search of the elements in the tree. USER IS REPONSIBLE FOR ALLOCATING THE ARRAY!. + * clear - Destroys the tree. Same as calling the destructor + * unsigned int Height() - Returns the height of the tree + * unsigned int size() - returns the size of the BinarySearchTree + * GetPointerToNode(element) - returns a pointer to the comparision element in the tree, allowing for direct modification when necessary with complex data types. + * Be warned, it is possible to corrupt the tree if the element used for comparisons is modified. Returns NULL if the item is not found + * + * + * EXAMPLE + * @code + * BinarySearchTree A; + * A.Add(10); + * A.Add(15); + * A.Add(5); + * int* array = RakNet::OP_NEW(A.Size(), __FILE__, __LINE__ ); + * A.DisplayInorder(array); + * array[0]; // returns 5 + * array[1]; // returns 10 + * array[2]; // returns 15 + * @endcode + * compress - reallocates memory to fit the number of elements. Best used when the number of elements decreases + * + * clear - empties the BinarySearchTree and returns storage + * The assignment and copy constructors are defined + * + * \note The template type must have the copy constructor and + * assignment operator defined and must work with >, <, and == All + * elements in the tree MUST be distinct The assignment operator is + * defined between BinarySearchTree and AVLBalancedBinarySearchTree + * as long as they are of the same template type. However, passing a + * BinarySearchTree to an AVLBalancedBinarySearchTree will lose its + * structure unless it happened to be AVL balanced to begin with + * Requires queue_linked_list.cpp for the breadth first search used + * in the copy constructor, overloaded assignment operator, and + * display_breadth_first_search. + * + * + */ + template + class RAK_DLL_EXPORT BinarySearchTree + { + + public: + + struct node + { + BinarySearchTreeType* item; + node* left; + node* right; + }; + + BinarySearchTree(); + virtual ~BinarySearchTree(); + BinarySearchTree( const BinarySearchTree& original_type ); + BinarySearchTree& operator= ( const BinarySearchTree& original_copy ); + unsigned int Size( void ); + void Clear( const char *file, unsigned int line ); + unsigned int Height( node* starting_node = 0 ); + node* Add ( const BinarySearchTreeType& input, const char *file, unsigned int line ); + node* Del( const BinarySearchTreeType& input, const char *file, unsigned int line ); + bool IsIn( const BinarySearchTreeType& input ); + void DisplayInorder( BinarySearchTreeType* return_array ); + void DisplayPreorder( BinarySearchTreeType* return_array ); + void DisplayPostorder( BinarySearchTreeType* return_array ); + void DisplayBreadthFirstSearch( BinarySearchTreeType* return_array ); + BinarySearchTreeType*& GetPointerToNode( const BinarySearchTreeType& element ); + + protected: + + node* root; + + enum Direction_Types + { + NOT_FOUND, LEFT, RIGHT, ROOT + } direction; + unsigned int HeightRecursive( node* current ); + unsigned int BinarySearchTree_size; + node*& Find( const BinarySearchTreeType& element, node** parent ); + node*& FindParent( const BinarySearchTreeType& element ); + void DisplayPostorderRecursive( node* current, BinarySearchTreeType* return_array, unsigned int& index ); + void FixTree( node* current ); + + }; + + /// An AVLBalancedBinarySearchTree is a binary tree that is always balanced + template + class RAK_DLL_EXPORT AVLBalancedBinarySearchTree : public BinarySearchTree + { + + public: + AVLBalancedBinarySearchTree() {} + virtual ~AVLBalancedBinarySearchTree(); + void Add ( const BinarySearchTreeType& input ); + void Del( const BinarySearchTreeType& input ); + BinarySearchTree& operator= ( BinarySearchTree& original_copy ) + { + return BinarySearchTree::operator= ( original_copy ); + } + + private: + void BalanceTree( typename BinarySearchTree::node* current, bool rotateOnce ); + void RotateRight( typename BinarySearchTree::node *C ); + void RotateLeft( typename BinarySearchTree::node* C ); + void DoubleRotateRight( typename BinarySearchTree::node *A ); + void DoubleRotateLeft( typename BinarySearchTree::node* A ); + bool RightHigher( typename BinarySearchTree::node* A ); + bool LeftHigher( typename BinarySearchTree::node* A ); + }; + + template + void AVLBalancedBinarySearchTree::BalanceTree( typename BinarySearchTree::node* current, bool rotateOnce ) + { + int left_height, right_height; + + while ( current ) + { + if ( current->left == 0 ) + left_height = 0; + else + left_height = Height( current->left ); + + if ( current->right == 0 ) + right_height = 0; + else + right_height = Height( current->right ); + + if ( right_height - left_height == 2 ) + { + if ( RightHigher( current->right ) ) + RotateLeft( current->right ); + else + DoubleRotateLeft( current ); + + if ( rotateOnce ) + break; + } + + else + if ( right_height - left_height == -2 ) + { + if ( LeftHigher( current->left ) ) + RotateRight( current->left ); + else + DoubleRotateRight( current ); + + if ( rotateOnce ) + break; + } + + if ( current == this->root ) + break; + + current = FindParent( *( current->item ) ); + + } + } + + template + void AVLBalancedBinarySearchTree::Add ( const BinarySearchTreeType& input ) + { + + typename BinarySearchTree::node * current = BinarySearchTree::Add ( input, __FILE__,__LINE__ ); + BalanceTree( current, true ); + } + + template + void AVLBalancedBinarySearchTree::Del( const BinarySearchTreeType& input ) + { + typename BinarySearchTree::node * current = BinarySearchTree::Del( input, __FILE__,__LINE__ ); + BalanceTree( current, false ); + + } + + template + bool AVLBalancedBinarySearchTree::RightHigher( typename BinarySearchTree::node *A ) + { + if ( A == 0 ) + return false; + + return Height( A->right ) > Height( A->left ); + } + + template + bool AVLBalancedBinarySearchTree::LeftHigher( typename BinarySearchTree::node *A ) + { + if ( A == 0 ) + return false; + + return Height( A->left ) > Height( A->right ); + } + + template + void AVLBalancedBinarySearchTree::RotateRight( typename BinarySearchTree::node *C ) + { + typename BinarySearchTree::node * A, *B, *D; + /* + RIGHT ROTATION + + A = parent(b) + b= parent(c) + c = node to rotate around + + A + | // Either direction + B + / \ + C + / \ + D + + TO + + A + | // Either Direction + C + / \ + B + / \ + D + + + + + */ + + B = FindParent( *( C->item ) ); + A = FindParent( *( B->item ) ); + D = C->right; + + if ( A ) + { + // Direction was set by the last find_parent call + + if ( this->direction == this->LEFT ) + A->left = C; + else + A->right = C; + } + + else + this->root = C; // If B has no parent parent then B must have been the root node + + B->left = D; + + C->right = B; + } + + template + void AVLBalancedBinarySearchTree::DoubleRotateRight( typename BinarySearchTree::node *A ) + { + // The left side of the left child must be higher for the tree to balance with a right rotation. If it isn't, rotate it left before the normal rotation so it is. + RotateLeft( A->left->right ); + RotateRight( A->left ); + } + + template + void AVLBalancedBinarySearchTree::RotateLeft( typename BinarySearchTree::node *C ) + { + typename BinarySearchTree::node * A, *B, *D; + /* + RIGHT ROTATION + + A = parent(b) + b= parent(c) + c = node to rotate around + + A + | // Either direction + B + / \ + C + / \ + D + + TO + + A + | // Either Direction + C + / \ + B + / \ + D + + + + + */ + + B = FindParent( *( C->item ) ); + A = FindParent( *( B->item ) ); + D = C->left; + + if ( A ) + { + // Direction was set by the last find_parent call + + if ( this->direction == this->LEFT ) + A->left = C; + else + A->right = C; + } + + else + this->root = C; // If B has no parent parent then B must have been the root node + + B->right = D; + + C->left = B; + } + + template + void AVLBalancedBinarySearchTree::DoubleRotateLeft( typename BinarySearchTree::node *A ) + { + // The left side of the right child must be higher for the tree to balance with a left rotation. If it isn't, rotate it right before the normal rotation so it is. + RotateRight( A->right->left ); + RotateLeft( A->right ); + } + + template + AVLBalancedBinarySearchTree::~AVLBalancedBinarySearchTree() + { + this->Clear(__FILE__,__LINE__); + } + + template + unsigned int BinarySearchTree::Size( void ) + { + return BinarySearchTree_size; + } + + template + unsigned int BinarySearchTree::Height( typename BinarySearchTree::node* starting_node ) + { + if ( BinarySearchTree_size == 0 || starting_node == 0 ) + return 0; + else + return HeightRecursive( starting_node ); + } + + // Recursively return the height of a binary tree + template + unsigned int BinarySearchTree::HeightRecursive( typename BinarySearchTree::node* current ) + { + unsigned int left_height = 0, right_height = 0; + + if ( ( current->left == 0 ) && ( current->right == 0 ) ) + return 1; // Leaf + + if ( current->left != 0 ) + left_height = 1 + HeightRecursive( current->left ); + + if ( current->right != 0 ) + right_height = 1 + HeightRecursive( current->right ); + + if ( left_height > right_height ) + return left_height; + else + return right_height; + } + + template + BinarySearchTree::BinarySearchTree() + { + BinarySearchTree_size = 0; + root = 0; + } + + template + BinarySearchTree::~BinarySearchTree() + { + this->Clear(__FILE__,__LINE__); + } + + template + BinarySearchTreeType*& BinarySearchTree::GetPointerToNode( const BinarySearchTreeType& element ) + { + static typename BinarySearchTree::node * tempnode; + static BinarySearchTreeType* dummyptr = 0; + tempnode = Find ( element, &tempnode ); + + if ( this->direction == this->NOT_FOUND ) + return dummyptr; + + return tempnode->item; + } + + template + typename BinarySearchTree::node*& BinarySearchTree::Find( const BinarySearchTreeType& element, typename BinarySearchTree::node** parent ) + { + static typename BinarySearchTree::node * current; + + current = this->root; + *parent = 0; + this->direction = this->ROOT; + + if ( BinarySearchTree_size == 0 ) + { + this->direction = this->NOT_FOUND; + return current = 0; + } + + // Check if the item is at the root + if ( element == *( current->item ) ) + { + this->direction = this->ROOT; + return current; + } + +#ifdef _MSC_VER +#pragma warning( disable : 4127 ) // warning C4127: conditional expression is constant +#endif + while ( true ) + { + // Move pointer + + if ( element < *( current->item ) ) + { + *parent = current; + this->direction = this->LEFT; + current = current->left; + } + + else + if ( element > *( current->item ) ) + { + *parent = current; + this->direction = this->RIGHT; + current = current->right; + } + + if ( current == 0 ) + break; + + // Check if new position holds the item + if ( element == *( current->item ) ) + { + return current; + } + } + + + this->direction = this->NOT_FOUND; + return current = 0; + } + + template + typename BinarySearchTree::node*& BinarySearchTree::FindParent( const BinarySearchTreeType& element ) + { + static typename BinarySearchTree::node * parent; + Find ( element, &parent ); + return parent; + } + + // Performs a series of value swaps starting with current to fix the tree if needed + template + void BinarySearchTree::FixTree( typename BinarySearchTree::node* current ) + { + BinarySearchTreeType temp; + + while ( 1 ) + { + if ( ( ( current->left ) != 0 ) && ( *( current->item ) < *( current->left->item ) ) ) + { + // Swap the current value with the one to the left + temp = *( current->left->item ); + *( current->left->item ) = *( current->item ); + *( current->item ) = temp; + current = current->left; + } + + else + if ( ( ( current->right ) != 0 ) && ( *( current->item ) > *( current->right->item ) ) ) + { + // Swap the current value with the one to the right + temp = *( current->right->item ); + *( current->right->item ) = *( current->item ); + *( current->item ) = temp; + current = current->right; + } + + else + break; // current points to the right place so quit + } + } + + template + typename BinarySearchTree::node* BinarySearchTree::Del( const BinarySearchTreeType& input, const char *file, unsigned int line ) + { + typename BinarySearchTree::node * node_to_delete, *current, *parent; + + if ( BinarySearchTree_size == 0 ) + return 0; + + if ( BinarySearchTree_size == 1 ) + { + Clear(file, line); + return 0; + } + + node_to_delete = Find( input, &parent ); + + if ( direction == NOT_FOUND ) + return 0; // Couldn't find the element + + current = node_to_delete; + + // Replace the deleted node with the appropriate value + if ( ( current->right ) == 0 && ( current->left ) == 0 ) // Leaf node, just remove it + { + + if ( parent ) + { + if ( direction == LEFT ) + parent->left = 0; + else + parent->right = 0; + } + + RakNet::OP_DELETE(node_to_delete->item, file, line); + RakNet::OP_DELETE(node_to_delete, file, line); + BinarySearchTree_size--; + return parent; + } + else + if ( ( current->right ) != 0 && ( current->left ) == 0 ) // Node has only one child, delete it and cause the parent to point to that child + { + + if ( parent ) + { + if ( direction == RIGHT ) + parent->right = current->right; + else + parent->left = current->right; + } + + else + root = current->right; // Without a parent this must be the root node + + RakNet::OP_DELETE(node_to_delete->item, file, line); + + RakNet::OP_DELETE(node_to_delete, file, line); + + BinarySearchTree_size--; + + return parent; + } + else + if ( ( current->right ) == 0 && ( current->left ) != 0 ) // Node has only one child, delete it and cause the parent to point to that child + { + + if ( parent ) + { + if ( direction == RIGHT ) + parent->right = current->left; + else + parent->left = current->left; + } + + else + root = current->left; // Without a parent this must be the root node + + RakNet::OP_DELETE(node_to_delete->item, file, line); + + RakNet::OP_DELETE(node_to_delete, file, line); + + BinarySearchTree_size--; + + return parent; + } + else // Go right, then as left as far as you can + { + parent = current; + direction = RIGHT; + current = current->right; // Must have a right branch because the if statements above indicated that it has 2 branches + + while ( current->left ) + { + direction = LEFT; + parent = current; + current = current->left; + } + + // Replace the value held by the node to RakNet::OP_DELETE(with the value pointed to by current, __FILE__, __LINE__); + *( node_to_delete->item ) = *( current->item ); + + // Delete current. + // If it is a leaf node just delete it + if ( current->right == 0 ) + { + if ( direction == RIGHT ) + parent->right = 0; + else + parent->left = 0; + + RakNet::OP_DELETE(current->item, file, line); + + RakNet::OP_DELETE(current, file, line); + + BinarySearchTree_size--; + + return parent; + } + + else + { + // Skip this node and make its parent point to its right branch + + if ( direction == RIGHT ) + parent->right = current->right; + else + parent->left = current->right; + + RakNet::OP_DELETE(current->item, file, line); + + RakNet::OP_DELETE(current, file, line); + + BinarySearchTree_size--; + + return parent; + } + } + } + + template + typename BinarySearchTree::node* BinarySearchTree::Add ( const BinarySearchTreeType& input, const char *file, unsigned int line ) + { + typename BinarySearchTree::node * current; + + // Add the new element to the tree according to the following alogrithm: + // 1. If the current node is empty add the new leaf + // 2. If the element is less than the current node then go down the left branch + // 3. If the element is greater than the current node then go down the right branch + + if ( BinarySearchTree_size == 0 ) + { + BinarySearchTree_size = 1; + root = RakNet::OP_NEW( file, line ); + root->item = RakNet::OP_NEW( file, line ); + *( root->item ) = input; + root->left = 0; + root->right = 0; + + return root; + } + + else + { + // start at the root + current = root; + +#ifdef _MSC_VER +#pragma warning( disable : 4127 ) // warning C4127: conditional expression is constant +#endif + while ( true ) // This loop traverses the tree to find a spot for insertion + { + + if ( input < *( current->item ) ) + { + if ( current->left == 0 ) + { + current->left = RakNet::OP_NEW( file, line ); + current->left->item = RakNet::OP_NEW( file, line ); + current = current->left; + current->left = 0; + current->right = 0; + *( current->item ) = input; + + BinarySearchTree_size++; + return current; + } + + else + { + current = current->left; + } + } + + else + if ( input > *( current->item ) ) + { + if ( current->right == 0 ) + { + current->right = RakNet::OP_NEW( file, line ); + current->right->item = RakNet::OP_NEW( file, line ); + current = current->right; + current->left = 0; + current->right = 0; + *( current->item ) = input; + + BinarySearchTree_size++; + return current; + } + + else + { + current = current->right; + } + } + + else + return 0; // ((input == current->item) == true) which is not allowed since the tree only takes discrete values. Do nothing + } + } + } + + template + bool BinarySearchTree::IsIn( const BinarySearchTreeType& input ) + { + typename BinarySearchTree::node * parent; + find( input, &parent ); + + if ( direction != NOT_FOUND ) + return true; + else + return false; + } + + + template + void BinarySearchTree::DisplayInorder( BinarySearchTreeType* return_array ) + { + typename BinarySearchTree::node * current, *parent; + bool just_printed = false; + + unsigned int index = 0; + + current = root; + + if ( BinarySearchTree_size == 0 ) + return ; // Do nothing for an empty tree + + else + if ( BinarySearchTree_size == 1 ) + { + return_array[ 0 ] = *( root->item ); + return ; + } + + + direction = ROOT; // Reset the direction + + while ( index != BinarySearchTree_size ) + { + // direction is set by the find function and holds the direction of the parent to the last node visited. It is used to prevent revisiting nodes + + if ( ( current->left != 0 ) && ( direction != LEFT ) && ( direction != RIGHT ) ) + { + // Go left if the following 2 conditions are true + // I can go left + // I did not just move up from a right child + // I did not just move up from a left child + + current = current->left; + direction = ROOT; // Reset the direction + } + + else + if ( ( direction != RIGHT ) && ( just_printed == false ) ) + { + // Otherwise, print the current node if the following 3 conditions are true: + // I did not just move up from a right child + // I did not print this ndoe last cycle + + return_array[ index++ ] = *( current->item ); + just_printed = true; + } + + else + if ( ( current->right != 0 ) && ( direction != RIGHT ) ) + { + // Otherwise, go right if the following 2 conditions are true + // I did not just move up from a right child + // I can go right + + current = current->right; + direction = ROOT; // Reset the direction + just_printed = false; + } + + else + { + // Otherwise I've done everything I can. Move up the tree one node + parent = FindParent( *( current->item ) ); + current = parent; + just_printed = false; + } + } + } + + template + void BinarySearchTree::DisplayPreorder( BinarySearchTreeType* return_array ) + { + typename BinarySearchTree::node * current, *parent; + + unsigned int index = 0; + + current = root; + + if ( BinarySearchTree_size == 0 ) + return ; // Do nothing for an empty tree + + else + if ( BinarySearchTree_size == 1 ) + { + return_array[ 0 ] = *( root->item ); + return ; + } + + + direction = ROOT; // Reset the direction + return_array[ index++ ] = *( current->item ); + + while ( index != BinarySearchTree_size ) + { + // direction is set by the find function and holds the direction of the parent to the last node visited. It is used to prevent revisiting nodes + + if ( ( current->left != 0 ) && ( direction != LEFT ) && ( direction != RIGHT ) ) + { + + current = current->left; + direction = ROOT; + + // Everytime you move a node print it + return_array[ index++ ] = *( current->item ); + } + + else + if ( ( current->right != 0 ) && ( direction != RIGHT ) ) + { + current = current->right; + direction = ROOT; + + // Everytime you move a node print it + return_array[ index++ ] = *( current->item ); + } + + else + { + // Otherwise I've done everything I can. Move up the tree one node + parent = FindParent( *( current->item ) ); + current = parent; + } + } + } + + template + inline void BinarySearchTree::DisplayPostorder( BinarySearchTreeType* return_array ) + { + unsigned int index = 0; + + if ( BinarySearchTree_size == 0 ) + return ; // Do nothing for an empty tree + + else + if ( BinarySearchTree_size == 1 ) + { + return_array[ 0 ] = *( root->item ); + return ; + } + + DisplayPostorderRecursive( root, return_array, index ); + } + + + // Recursively do a postorder traversal + template + void BinarySearchTree::DisplayPostorderRecursive( typename BinarySearchTree::node* current, BinarySearchTreeType* return_array, unsigned int& index ) + { + if ( current->left != 0 ) + DisplayPostorderRecursive( current->left, return_array, index ); + + if ( current->right != 0 ) + DisplayPostorderRecursive( current->right, return_array, index ); + + return_array[ index++ ] = *( current->item ); + + } + + + template + void BinarySearchTree::DisplayBreadthFirstSearch( BinarySearchTreeType* return_array ) + { + typename BinarySearchTree::node * current; + unsigned int index = 0; + + // Display the tree using a breadth first search + // Put the children of the current node into the queue + // Pop the queue, put its children into the queue, repeat until queue is empty + + if ( BinarySearchTree_size == 0 ) + return ; // Do nothing for an empty tree + + else + if ( BinarySearchTree_size == 1 ) + { + return_array[ 0 ] = *( root->item ); + return ; + } + + else + { + DataStructures::QueueLinkedList tree_queue; + + // Add the root of the tree I am copying from + tree_queue.Push( root ); + + do + { + current = tree_queue.Pop(); + return_array[ index++ ] = *( current->item ); + + // Add the child or children of the tree I am copying from to the queue + + if ( current->left != 0 ) + tree_queue.Push( current->left ); + + if ( current->right != 0 ) + tree_queue.Push( current->right ); + + } + + while ( tree_queue.Size() > 0 ); + } + } + + + template + BinarySearchTree::BinarySearchTree( const BinarySearchTree& original_copy ) + { + typename BinarySearchTree::node * current; + // Copy the tree using a breadth first search + // Put the children of the current node into the queue + // Pop the queue, put its children into the queue, repeat until queue is empty + + // This is a copy of the constructor. A bug in Visual C++ made it so if I just put the constructor call here the variable assignments were ignored. + BinarySearchTree_size = 0; + root = 0; + + if ( original_copy.BinarySearchTree_size == 0 ) + { + BinarySearchTree_size = 0; + } + + else + { + DataStructures::QueueLinkedList tree_queue; + + // Add the root of the tree I am copying from + tree_queue.Push( original_copy.root ); + + do + { + current = tree_queue.Pop(); + + Add ( *( current->item ), __FILE__, __LINE__ ) + + ; + + // Add the child or children of the tree I am copying from to the queue + if ( current->left != 0 ) + tree_queue.Push( current->left ); + + if ( current->right != 0 ) + tree_queue.Push( current->right ); + + } + + while ( tree_queue.Size() > 0 ); + } + } + + template + BinarySearchTree& BinarySearchTree::operator= ( const BinarySearchTree& original_copy ) + { + typename BinarySearchTree::node * current; + + if ( ( &original_copy ) == this ) + return *this; + + Clear( __FILE__, __LINE__ ); // Remove the current tree + + // This is a copy of the constructor. A bug in Visual C++ made it so if I just put the constructor call here the variable assignments were ignored. + BinarySearchTree_size = 0; + + root = 0; + + + // Copy the tree using a breadth first search + // Put the children of the current node into the queue + // Pop the queue, put its children into the queue, repeat until queue is empty + if ( original_copy.BinarySearchTree_size == 0 ) + { + BinarySearchTree_size = 0; + } + + else + { + DataStructures::QueueLinkedList tree_queue; + + // Add the root of the tree I am copying from + tree_queue.Push( original_copy.root ); + + do + { + current = tree_queue.Pop(); + + Add ( *( current->item ), __FILE__, __LINE__ ) + + ; + + // Add the child or children of the tree I am copying from to the queue + if ( current->left != 0 ) + tree_queue.Push( current->left ); + + if ( current->right != 0 ) + tree_queue.Push( current->right ); + + } + + while ( tree_queue.Size() > 0 ); + } + + return *this; + } + + template + inline void BinarySearchTree::Clear ( const char *file, unsigned int line ) + { + typename BinarySearchTree::node * current, *parent; + + current = root; + + while ( BinarySearchTree_size > 0 ) + { + if ( BinarySearchTree_size == 1 ) + { + RakNet::OP_DELETE(root->item, file, line); + RakNet::OP_DELETE(root, file, line); + root = 0; + BinarySearchTree_size = 0; + } + + else + { + if ( current->left != 0 ) + { + current = current->left; + } + + else + if ( current->right != 0 ) + { + current = current->right; + } + + else // leaf + { + // Not root node so must have a parent + parent = FindParent( *( current->item ) ); + + if ( ( parent->left ) == current ) + parent->left = 0; + else + parent->right = 0; + + RakNet::OP_DELETE(current->item, file, line); + + RakNet::OP_DELETE(current, file, line); + + current = parent; + + BinarySearchTree_size--; + } + } + } + } + +} // End namespace + +#endif + +#ifdef _MSC_VER +#pragma warning( pop ) +#endif diff --git a/RakNet/Sources/DS_BytePool.cpp b/RakNet/Sources/DS_BytePool.cpp new file mode 100644 index 0000000..7ff57c9 --- /dev/null +++ b/RakNet/Sources/DS_BytePool.cpp @@ -0,0 +1,149 @@ +#include "DS_BytePool.h" +#include "RakAssert.h" +#ifndef __APPLE__ +// Use stdlib and not malloc for compatibility +#include +#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__, __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__, __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__, __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 +} diff --git a/RakNet/Sources/DS_BytePool.h b/RakNet/Sources/DS_BytePool.h new file mode 100644 index 0000000..53eb834 --- /dev/null +++ b/RakNet/Sources/DS_BytePool.h @@ -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.h" +#include "DS_MemoryPool.h" +#include "Export.h" +#include "SimpleMutex.h" +#include "RakAssert.h" + +// #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 pool128; + MemoryPool pool512; + MemoryPool pool2048; + MemoryPool pool8192; +#ifdef _THREADSAFE_BYTE_POOL + SimpleMutex mutex128; + SimpleMutex mutex512; + SimpleMutex mutex2048; + SimpleMutex mutex8192; +#endif + }; +} + +#endif diff --git a/RakNet/Sources/DS_ByteQueue.cpp b/RakNet/Sources/DS_ByteQueue.cpp new file mode 100644 index 0000000..91dc061 --- /dev/null +++ b/RakNet/Sources/DS_ByteQueue.cpp @@ -0,0 +1,127 @@ +#include "DS_ByteQueue.h" +#include // Memmove +#include // realloc +#include + + +using namespace DataStructures; + +ByteQueue::ByteQueue() +{ + readOffset=writeOffset=lengthAllocated=0; + data=0; +} +ByteQueue::~ByteQueue() +{ + Clear(__FILE__, __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"); +} diff --git a/RakNet/Sources/DS_ByteQueue.h b/RakNet/Sources/DS_ByteQueue.h new file mode 100644 index 0000000..a288813 --- /dev/null +++ b/RakNet/Sources/DS_ByteQueue.h @@ -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.h" +#include "Export.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 +{ + 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 diff --git a/RakNet/Sources/DS_Heap.h b/RakNet/Sources/DS_Heap.h new file mode 100644 index 0000000..0ce5fe8 --- /dev/null +++ b/RakNet/Sources/DS_Heap.h @@ -0,0 +1,294 @@ +/// \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.h" +#include "DS_List.h" +#include "Export.h" +#include "RakAssert.h" + +#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 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 heap; + bool optimizeNextSeriesPush; + }; + + template + Heap::Heap() + { + optimizeNextSeriesPush=false; + } + + template + Heap::~Heap() + { + //Clear(true, __FILE__,__LINE__); + } + + template + void Heap::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++) + { + 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 + void Heap::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 + data_type Heap::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 + data_type Heap::Peek(const unsigned startingIndex) const + { + return heap[startingIndex].data; + } + + template + weight_type Heap::PeekWeight(const unsigned startingIndex) const + { + return heap[startingIndex].weight; + } + + template + void Heap::Clear(bool doNotDeallocateSmallBlocks, const char *file, unsigned int line) + { + heap.Clear(doNotDeallocateSmallBlocks, file, line); + } + + template + data_type& Heap::operator[] ( const unsigned int position ) const + { + return heap[position].data; + } + template + unsigned Heap::Size(void) const + { + return heap.Size(); + } + + template + unsigned Heap::LeftChild(const unsigned i) const + { + return i*2+1; + } + + template + unsigned Heap::RightChild(const unsigned i) const + { + return i*2+2; + } + + template + unsigned Heap::Parent(const unsigned i) const + { +#ifdef _DEBUG + RakAssert(i!=0); +#endif + return (i-1)/2; + } + + template + void Heap::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 diff --git a/RakNet/Sources/DS_HuffmanEncodingTree.cpp b/RakNet/Sources/DS_HuffmanEncodingTree.cpp new file mode 100644 index 0000000..5bd6124 --- /dev/null +++ b/RakNet/Sources/DS_HuffmanEncodingTree.cpp @@ -0,0 +1,297 @@ +/// \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.h" +#include "DS_Queue.h" +#include "BitStream.h" +#include "RakAssert.h" + +#ifdef _MSC_VER +#pragma warning( push ) +#endif + +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 nodeQueue; + + HuffmanEncodingTreeNode *node; + + nodeQueue.Push( root, __FILE__, __LINE__ ); + + while ( nodeQueue.Size() > 0 ) + { + node = nodeQueue.Pop(); + + if ( node->left ) + nodeQueue.Push( node->left, __FILE__, __LINE__ ); + + if ( node->right ) + nodeQueue.Push( node->right, __FILE__, __LINE__ ); + + RakNet::OP_DELETE(node, __FILE__, __LINE__); + } + + // Delete the encoding table + for ( int i = 0; i < 256; i++ ) + rakFree_Ex(encodingTable[ i ].encoding, __FILE__, __LINE__ ); + + root = 0; +} + + +////#include + +// 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 huffmanEncodingTreeNodeList; + + FreeMemory(); + + for ( counter = 0; counter < 256; counter++ ) + { + node = RakNet::OP_NEW( __FILE__, __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( __FILE__, __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 *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 diff --git a/RakNet/Sources/DS_HuffmanEncodingTree.h b/RakNet/Sources/DS_HuffmanEncodingTree.h new file mode 100644 index 0000000..0b98609 --- /dev/null +++ b/RakNet/Sources/DS_HuffmanEncodingTree.h @@ -0,0 +1,62 @@ +/// \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.h" +#include "DS_HuffmanEncodingTreeNode.h" +#include "BitStream.h" +#include "Export.h" +#include "DS_LinkedList.h" + +/// 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 *huffmanEncodingTreeNodeList ) const; +}; + +#endif diff --git a/RakNet/Sources/DS_HuffmanEncodingTreeFactory.h b/RakNet/Sources/DS_HuffmanEncodingTreeFactory.h new file mode 100644 index 0000000..94b8b68 --- /dev/null +++ b/RakNet/Sources/DS_HuffmanEncodingTreeFactory.h @@ -0,0 +1,52 @@ +/// \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.h" +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 ]; +}; + +#endif diff --git a/RakNet/Sources/DS_HuffmanEncodingTreeNode.h b/RakNet/Sources/DS_HuffmanEncodingTreeNode.h new file mode 100644 index 0000000..f726e4f --- /dev/null +++ b/RakNet/Sources/DS_HuffmanEncodingTreeNode.h @@ -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 diff --git a/RakNet/Sources/DS_LinkedList.h b/RakNet/Sources/DS_LinkedList.h new file mode 100644 index 0000000..da7f4a5 --- /dev/null +++ b/RakNet/Sources/DS_LinkedList.h @@ -0,0 +1,1252 @@ +/// \file DS_LinkedList.h +/// \internal +/// \brief Straightforward linked list data structure. +/// +/// This file is part of RakNet Copyright 2003 Jenkins Software LLC +/// +/// Usage of RakNet is subject to the appropriate license agreement. + + +#ifndef __LINKED_LIST_H +#define __LINKED_LIST_H + +#include "Export.h" +#include "RakMemoryOverride.h" + +#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 +{ + // Prototype to prevent error in CircularLinkedList class when a reference is made to a LinkedList class + template + class RAK_DLL_EXPORT LinkedList; + + /** + * \brief (Circular) Linked List ADT (Doubly Linked Pointer to Node Style) - + * + * \details + * By Kevin Jenkins (http://www.rakkar.org) + * Initilize with the following command + * LinkedList + * OR + * CircularLinkedList + * + * Has the following member functions + * - size: returns number of elements in the linked list + * - insert(item): inserts @em item at the current position in + * the LinkedList. + * - add(item): inserts @em item after the current position in + * the LinkedList. Does not increment the position + * - replace(item): replaces the element at the current position @em item. + * - peek: returns the element at the current position + * - pop: returns the element at the current position and deletes it + * - del: deletes the current element. Does nothing for an empty list. + * - clear: empties the LinkedList and returns storage + * - bool IsInitem): Does a linear search for @em item. Does not set + * the position to it, only returns true on item found, false otherwise + * - bool find(item): Does a linear search for @em item and sets the current + * position to point to it if and only if the item is found. Returns true + * on item found, false otherwise + * - sort: Sorts the elements of the list with a mergesort and sets the + * current pointer to the first element + * - concatenate(list L): This appends L to the current list + * - ++(prefix): moves the pointer one element up in the list and returns the + * appropriate copy of the element in the list + * - --(prefix): moves the pointer one element back in the list and returns + * the appropriate copy of the element in the list + * - beginning - moves the pointer to the start of the list. For circular + * linked lists this is first 'position' created. You should call this + * after the sort function to read the first value. + * - end - moves the pointer to the end of the list. For circular linked + * lists this is one less than the first 'position' created + * The assignment and copy constructor operators are defined + * + * \note + * 1. LinkedList and CircularLinkedList are exactly the same except LinkedList + * won't let you wrap around the root and lets you jump to two positions + * relative to the root/ + * 2. Postfix ++ and -- can be used but simply call the prefix versions. + * + * + * EXAMPLE: + * @code + * LinkedList A; // Creates a Linked List of integers called A + * CircularLinkedList B; // Creates a Circular Linked List of + * // integers called B + * + * A.Insert(20); // Adds 20 to A. A: 20 - current is 20 + * A.Insert(5); // Adds 5 to A. A: 5 20 - current is 5 + * A.Insert(1); // Adds 1 to A. A: 1 5 20 - current is 1 + * + * A.IsIn1); // returns true + * A.IsIn200); // returns false + * A.Find(5); // returns true and sets current to 5 + * A.Peek(); // returns 5 + * A.Find(1); // returns true and sets current to 1 + * + * (++A).Peek(); // Returns 5 + * A.Peek(); // Returns 5 + * + * A.Replace(10); // Replaces 5 with 10. + * A.Peek(); // Returns 10 + * + * A.Beginning(); // Current points to the beginning of the list at 1 + * + * (++A).Peek(); // Returns 5 + * A.Peek(); // Returns 10 + * + * A.Del(); // Deletes 10. Current points to the next element, which is 20 + * A.Peek(); // Returns 20 + * + * A.Beginning(); // Current points to the beginning of the list at 1 + * + * (++A).Peek(); // Returns 5 + * A.Peek(); // Returns 20 + * + * A.Clear(__FILE__, __LINE__); // Deletes all nodes in A + * + * A.Insert(5); // A: 5 - current is 5 + * A.Insert(6); // A: 6 5 - current is 6 + * A.Insert(7); // A: 7 6 5 - current is 7 + * + * A.Clear(__FILE__, __LINE__); + * B.Clear(__FILE__, __LINE__); + * + * B.Add(10); + * B.Add(20); + * B.Add(30); + * B.Add(5); + * B.Add(2); + * B.Add(25); + * // Sorts the numbers in the list and sets the current pointer to the + * // first element + * B.sort(); + * + * // Postfix ++ just calls the prefix version and has no functional + * // difference. + * B.Peek(); // Returns 2 + * B++; + * B.Peek(); // Returns 5 + * B++; + * B.Peek(); // Returns 10 + * B++; + * B.Peek(); // Returns 20 + * B++; + * B.Peek(); // Returns 25 + * B++; + * B.Peek(); // Returns 30 + * @endcode + */ + template + + class CircularLinkedList + { + + public: + + struct node + { + CircularLinkedListType item; + + node* previous; + node* next; + }; + + CircularLinkedList(); + ~CircularLinkedList(); + CircularLinkedList( const CircularLinkedList& original_copy ); + // CircularLinkedList(LinkedList original_copy) {CircularLinkedList(original_copy);} // Converts linked list to circular type + bool operator= ( const CircularLinkedList& original_copy ); + CircularLinkedList& operator++(); // CircularLinkedList A; ++A; + CircularLinkedList& operator++( int ); // Circular_Linked List A; A++; + CircularLinkedList& operator--(); // CircularLinkedList A; --A; + CircularLinkedList& operator--( int ); // Circular_Linked List A; A--; + bool IsIn( const CircularLinkedListType& input ); + bool Find( const CircularLinkedListType& input ); + void Insert( const CircularLinkedListType& input ); + + CircularLinkedListType& Add ( const CircularLinkedListType& input ) + + ; // Adds after the current position + void Replace( const CircularLinkedListType& input ); + + void Del( void ); + + unsigned int Size( void ); + + CircularLinkedListType& Peek( void ); + + CircularLinkedListType Pop( void ); + + void Clear( void ); + + void Sort( void ); + + void Beginning( void ); + + void End( void ); + + void Concatenate( const CircularLinkedList& L ); + + protected: + unsigned int list_size; + + node *root; + + node *position; + + node* FindPointer( const CircularLinkedListType& input ); + + private: + CircularLinkedList Merge( CircularLinkedList L1, CircularLinkedList L2 ); + + CircularLinkedList Mergesort( const CircularLinkedList& L ); + }; + + template + + class LinkedList : public CircularLinkedList + { + + public: + LinkedList() + {} + + LinkedList( const LinkedList& original_copy ); + ~LinkedList(); + bool operator= ( const LinkedList& original_copy ); + LinkedList& operator++(); // LinkedList A; ++A; + LinkedList& operator++( int ); // Linked List A; A++; + LinkedList& operator--(); // LinkedList A; --A; + LinkedList& operator--( int ); // Linked List A; A--; + + private: + LinkedList Merge( LinkedList L1, LinkedList L2 ); + LinkedList Mergesort( const LinkedList& L ); + + }; + + + template + inline void CircularLinkedList::Beginning( void ) + { + if ( this->root ) + this->position = this->root; + } + + template + inline void CircularLinkedList::End( void ) + { + if ( this->root ) + this->position = this->root->previous; + } + + template + bool LinkedList::operator= ( const LinkedList& original_copy ) + { + typename LinkedList::node * original_copy_pointer, *last, *save_position; + + if ( ( &original_copy ) != this ) + { + + this->Clear(); + + + if ( original_copy.list_size == 0 ) + { + this->root = 0; + this->position = 0; + this->list_size = 0; + } + + else + if ( original_copy.list_size == 1 ) + { + this->root = RakNet::OP_NEW( __FILE__, __LINE__ ); + // root->item = RakNet::OP_NEW( __FILE__, __LINE__ ); + this->root->next = this->root; + this->root->previous = this->root; + this->list_size = 1; + this->position = this->root; + // *(root->item)=*((original_copy.root)->item); + this->root->item = original_copy.root->item; + } + + else + { + // Setup the first part of the root node + original_copy_pointer = original_copy.root; + this->root = RakNet::OP_NEW( __FILE__, __LINE__ ); + // root->item = RakNet::OP_NEW( __FILE__, __LINE__ ); + this->position = this->root; + // *(root->item)=*((original_copy.root)->item); + this->root->item = original_copy.root->item; + + if ( original_copy_pointer == original_copy.position ) + save_position = this->position; + + do + { + + + // Save the current element + last = this->position; + + // Point to the next node in the source list + original_copy_pointer = original_copy_pointer->next; + + // Create a new node and point position to it + this->position = RakNet::OP_NEW( __FILE__, __LINE__ ); + // position->item = RakNet::OP_NEW( __FILE__, __LINE__ ); + + // Copy the item to the new node + // *(position->item)=*(original_copy_pointer->item); + this->position->item = original_copy_pointer->item; + + if ( original_copy_pointer == original_copy.position ) + save_position = this->position; + + + // Set the previous pointer for the new node + ( this->position->previous ) = last; + + // Set the next pointer for the old node to the new node + ( last->next ) = this->position; + + } + + while ( ( original_copy_pointer->next ) != ( original_copy.root ) ); + + // Complete the circle. Set the next pointer of the newest node to the root and the previous pointer of the root to the newest node + this->position->next = this->root; + + this->root->previous = this->position; + + this->list_size = original_copy.list_size; + + this->position = save_position; + } + } + + return true; + } + + + template + CircularLinkedList::CircularLinkedList() + { + this->root = 0; + this->position = 0; + this->list_size = 0; + } + + template + CircularLinkedList::~CircularLinkedList() + { + this->Clear(); + } + + template + LinkedList::~LinkedList() + { + this->Clear(); + } + + template + LinkedList::LinkedList( const LinkedList& original_copy ) + { + typename LinkedList::node * original_copy_pointer, *last, *save_position; + + if ( original_copy.list_size == 0 ) + { + this->root = 0; + this->position = 0; + this->list_size = 0; + return ; + } + + else + if ( original_copy.list_size == 1 ) + { + this->root = RakNet::OP_NEW( __FILE__, __LINE__ ); + // root->item = RakNet::OP_NEW( __FILE__, __LINE__ ); + this->root->next = this->root; + this->root->previous = this->root; + this->list_size = 1; + this->position = this->root; + // *(root->item) = *((original_copy.root)->item); + this->root->item = original_copy.root->item; + } + + else + { + // Setup the first part of the root node + original_copy_pointer = original_copy.root; + this->root = RakNet::OP_NEW( __FILE__, __LINE__ ); + // root->item = RakNet::OP_NEW( __FILE__, __LINE__ ); + this->position = this->root; + // *(root->item)=*((original_copy.root)->item); + this->root->item = original_copy.root->item; + + if ( original_copy_pointer == original_copy.position ) + save_position = this->position; + + do + { + // Save the current element + last = this->position; + + // Point to the next node in the source list + original_copy_pointer = original_copy_pointer->next; + + // Create a new node and point position to it + this->position = RakNet::OP_NEW( __FILE__, __LINE__ ); + // position->item = RakNet::OP_NEW( __FILE__, __LINE__ ); + + // Copy the item to the new node + // *(position->item)=*(original_copy_pointer->item); + this->position->item = original_copy_pointer->item; + + if ( original_copy_pointer == original_copy.position ) + save_position = this->position; + + // Set the previous pointer for the new node + ( this->position->previous ) = last; + + // Set the next pointer for the old node to the new node + ( last->next ) = this->position; + + } + + while ( ( original_copy_pointer->next ) != ( original_copy.root ) ); + + // Complete the circle. Set the next pointer of the newest node to the root and the previous pointer of the root to the newest node + this->position->next = this->root; + + this->root->previous = this->position; + + this->list_size = original_copy.list_size; + + this->position = save_position; + } + } + +#ifdef _MSC_VER +#pragma warning( disable : 4701 ) // warning C4701: local variable may be used without having been initialized +#endif + template + CircularLinkedList::CircularLinkedList( const CircularLinkedList& original_copy ) + { + node * original_copy_pointer; + node *last; + node *save_position; + + if ( original_copy.list_size == 0 ) + { + this->root = 0; + this->position = 0; + this->list_size = 0; + return ; + } + + else + if ( original_copy.list_size == 1 ) + { + this->root = RakNet::OP_NEW( __FILE__, __LINE__ ); + // root->item = RakNet::OP_NEW( __FILE__, __LINE__ ); + this->root->next = this->root; + this->root->previous = this->root; + this->list_size = 1; + this->position = this->root; + // *(root->item) = *((original_copy.root)->item); + this->root->item = original_copy.root->item; + } + + else + { + // Setup the first part of the root node + original_copy_pointer = original_copy.root; + this->root = RakNet::OP_NEW( __FILE__, __LINE__ ); + // root->item = RakNet::OP_NEW( __FILE__, __LINE__ ); + this->position = this->root; + // *(root->item)=*((original_copy.root)->item); + this->root->item = original_copy.root->item; + + if ( original_copy_pointer == original_copy.position ) + save_position = this->position; + + do + { + + + // Save the current element + last = this->position; + + // Point to the next node in the source list + original_copy_pointer = original_copy_pointer->next; + + // Create a new node and point position to it + this->position = RakNet::OP_NEW( __FILE__, __LINE__ ); + // position->item = RakNet::OP_NEW( __FILE__, __LINE__ ); + + // Copy the item to the new node + // *(position->item)=*(original_copy_pointer->item); + this->position->item = original_copy_pointer->item; + + if ( original_copy_pointer == original_copy.position ) + save_position = position; + + // Set the previous pointer for the new node + ( this->position->previous ) = last; + + // Set the next pointer for the old node to the new node + ( last->next ) = this->position; + + } + + while ( ( original_copy_pointer->next ) != ( original_copy.root ) ); + + // Complete the circle. Set the next pointer of the newest node to the root and the previous pointer of the root to the newest node + this->position->next = this->root; + + this->root->previous = position; + + this->list_size = original_copy.list_size; + + this->position = save_position; + } + } + +#ifdef _MSC_VER +#pragma warning( disable : 4701 ) // warning C4701: local variable may be used without having been initialized +#endif + template + bool CircularLinkedList::operator= ( const CircularLinkedList& original_copy ) + { + node * original_copy_pointer; + node *last; + node *save_position; + + if ( ( &original_copy ) != this ) + { + + this->Clear(); + + + if ( original_copy.list_size == 0 ) + { + this->root = 0; + this->position = 0; + this->list_size = 0; + } + + else + if ( original_copy.list_size == 1 ) + { + this->root = RakNet::OP_NEW( __FILE__, __LINE__ ); + // root->item = RakNet::OP_NEW( __FILE__, __LINE__ ); + this->root->next = this->root; + this->root->previous = this->root; + this->list_size = 1; + this->position = this->root; + // *(root->item)=*((original_copy.root)->item); + this->root->item = original_copy.root->item; + } + + else + { + // Setup the first part of the root node + original_copy_pointer = original_copy.root; + this->root = RakNet::OP_NEW( __FILE__, __LINE__ ); + // root->item = RakNet::OP_NEW( __FILE__, __LINE__ ); + this->position = this->root; + // *(root->item)=*((original_copy.root)->item); + this->root->item = original_copy.root->item; + + if ( original_copy_pointer == original_copy.position ) + save_position = this->position; + + do + { + // Save the current element + last = this->position; + + // Point to the next node in the source list + original_copy_pointer = original_copy_pointer->next; + + // Create a new node and point position to it + this->position = RakNet::OP_NEW( __FILE__, __LINE__ ); + // position->item = RakNet::OP_NEW( __FILE__, __LINE__ ); + + // Copy the item to the new node + // *(position->item)=*(original_copy_pointer->item); + this->position->item = original_copy_pointer->item; + + if ( original_copy_pointer == original_copy.position ) + save_position = this->position; + + // Set the previous pointer for the new node + ( this->position->previous ) = last; + + // Set the next pointer for the old node to the new node + ( last->next ) = this->position; + + } + + while ( ( original_copy_pointer->next ) != ( original_copy.root ) ); + + // Complete the circle. Set the next pointer of the newest node to the root and the previous pointer of the root to the newest node + this->position->next = this->root; + + this->root->previous = this->position; + + this->list_size = original_copy.list_size; + + this->position = save_position; + } + } + + return true; + } + + template + void CircularLinkedList::Insert( const CircularLinkedListType& input ) + { + node * new_node; + + if ( list_size == 0 ) + { + this->root = RakNet::OP_NEW( __FILE__, __LINE__ ); + // root->item = RakNet::OP_NEW( __FILE__, __LINE__ ); + //*(root->item)=input; + this->root->item = input; + this->root->next = this->root; + this->root->previous = this->root; + this->list_size = 1; + this->position = this->root; + } + + else + if ( list_size == 1 ) + { + this->position = RakNet::OP_NEW( __FILE__, __LINE__ ); + // position->item = RakNet::OP_NEW( __FILE__, __LINE__ ); + this->root->next = this->position; + this->root->previous = this->position; + this->position->previous = this->root; + this->position->next = this->root; + // *(position->item)=input; + this->position->item = input; + this->root = this->position; // Since we're inserting into a 1 element list the old root is now the second item + this->list_size = 2; + } + + else + { + /* + + B + | + A --- C + + position->previous=A + new_node=B + position=C + + Note that the order of the following statements is important */ + + new_node = RakNet::OP_NEW( __FILE__, __LINE__ ); + // new_node->item = RakNet::OP_NEW( __FILE__, __LINE__ ); + + // *(new_node->item)=input; + new_node->item = input; + + // Point next of A to B + ( this->position->previous ) ->next = new_node; + + // Point last of B to A + new_node->previous = this->position->previous; + + // Point last of C to B + this->position->previous = new_node; + + // Point next of B to C + new_node->next = this->position; + + // Since the root pointer is bound to a node rather than an index this moves it back if you insert an element at the root + + if ( this->position == this->root ) + { + this->root = new_node; + this->position = this->root; + } + + // Increase the recorded size of the list by one + this->list_size++; + } + } + + template + CircularLinkedListType& CircularLinkedList::Add ( const CircularLinkedListType& input ) + { + node * new_node; + + if ( this->list_size == 0 ) + { + this->root = RakNet::OP_NEW( __FILE__, __LINE__ ); + // root->item = RakNet::OP_NEW( __FILE__, __LINE__ ); + // *(root->item)=input; + this->root->item = input; + this->root->next = this->root; + this->root->previous = this->root; + this->list_size = 1; + this->position = this->root; + // return *(position->item); + return this->position->item; + } + + else + if ( list_size == 1 ) + { + this->position = RakNet::OP_NEW( __FILE__, __LINE__ ); + // position->item = RakNet::OP_NEW( __FILE__, __LINE__ ); + this->root->next = this->position; + this->root->previous = this->position; + this->position->previous = this->root; + this->position->next = this->root; + // *(position->item)=input; + this->position->item = input; + this->list_size = 2; + this->position = this->root; // Don't move the position from the root + // return *(position->item); + return this->position->item; + } + + else + { + /* + + B + | + A --- C + + new_node=B + position=A + position->next=C + + Note that the order of the following statements is important */ + + new_node = RakNet::OP_NEW( __FILE__, __LINE__ ); + // new_node->item = RakNet::OP_NEW( __FILE__, __LINE__ ); + + // *(new_node->item)=input; + new_node->item = input; + + // Point last of B to A + new_node->previous = this->position; + + // Point next of B to C + new_node->next = ( this->position->next ); + + // Point last of C to B + ( this->position->next ) ->previous = new_node; + + // Point next of A to B + ( this->position->next ) = new_node; + + // Increase the recorded size of the list by one + this->list_size++; + + // return *(new_node->item); + return new_node->item; + } + } + + template + inline void CircularLinkedList::Replace( const CircularLinkedListType& input ) + { + if ( this->list_size > 0 ) + // *(position->item)=input; + this->position->item = input; + } + + template + void CircularLinkedList::Del() + { + node * new_position; + + if ( this->list_size == 0 ) + return ; + + else + if ( this->list_size == 1 ) + { + // RakNet::OP_DELETE(root->item, __FILE__, __LINE__); + RakNet::OP_DELETE(this->root, __FILE__, __LINE__); + this->root = this->position = 0; + this->list_size = 0; + } + + else + { + ( this->position->previous ) ->next = this->position->next; + ( this->position->next ) ->previous = this->position->previous; + new_position = this->position->next; + + if ( this->position == this->root ) + this->root = new_position; + + // RakNet::OP_DELETE(position->item, __FILE__, __LINE__); + RakNet::OP_DELETE(this->position, __FILE__, __LINE__); + + this->position = new_position; + + this->list_size--; + } + } + + template + bool CircularLinkedList::IsIn(const CircularLinkedListType& input ) + { + node * return_value, *old_position; + + old_position = this->position; + + return_value = FindPointer( input ); + this->position = old_position; + + if ( return_value != 0 ) + return true; + else + return false; // Can't find the item don't do anything + } + + template + bool CircularLinkedList::Find( const CircularLinkedListType& input ) + { + node * return_value; + + return_value = FindPointer( input ); + + if ( return_value != 0 ) + { + this->position = return_value; + return true; + } + + else + return false; // Can't find the item don't do anything + } + + template + typename CircularLinkedList::node* CircularLinkedList::FindPointer( const CircularLinkedListType& input ) + { + node * current; + + if ( this->list_size == 0 ) + return 0; + + current = this->root; + + // Search for the item starting from the root node and incrementing the pointer after every check + // If you wind up pointing at the root again you looped around the list so didn't find the item, in which case return 0 + do + { + // if (*(current->item) == input) return current; + + if ( current->item == input ) + return current; + + current = current->next; + } + + while ( current != this->root ); + + return 0; + + } + + template + inline unsigned int CircularLinkedList::Size( void ) + { + return this->list_size; + } + + template + inline CircularLinkedListType& CircularLinkedList::Peek( void ) + { + // return *(position->item); + return this->position->item; + } + + template + CircularLinkedListType CircularLinkedList::Pop( void ) + { + CircularLinkedListType element; + element = Peek(); + Del(); + return CircularLinkedListType( element ); // return temporary + } + + // Prefix + template + CircularLinkedList& CircularLinkedList::operator++() + { + if ( this->list_size != 0 ) + position = position->next; + + return *this; + } + + /* + // Postfix + template + CircularLinkedList& CircularLinkedList::operator++(int) + { + CircularLinkedList before; + before=*this; + operator++(); + return before; + } + */ + + template + CircularLinkedList& CircularLinkedList::operator++( int ) + { + return this->operator++(); + } + + // Prefix + template + CircularLinkedList& CircularLinkedList::operator--() + { + if ( this->list_size != 0 ) + this->position = this->position->previous; + + return *this; + } + + /* + // Postfix + template + CircularLinkedList& CircularLinkedList::operator--(int) + { + CircularLinkedList before; + before=*this; + operator--(); + return before; + } + */ + + template + CircularLinkedList& CircularLinkedList::operator--( int ) + { + return this->operator--(); + } + + template + void CircularLinkedList::Clear( void ) + { + if ( this->list_size == 0 ) + return ; + else + if ( this->list_size == 1 ) // {RakNet::OP_DELETE(root->item); RakNet::OP_DELETE(root, __FILE__, __LINE__);} + { + RakNet::OP_DELETE(this->root, __FILE__, __LINE__); + } + + else + { + node* current; + node* temp; + + current = this->root; + + do + { + temp = current; + current = current->next; + // RakNet::OP_DELETE(temp->item, __FILE__, __LINE__); + RakNet::OP_DELETE(temp, __FILE__, __LINE__); + } + + while ( current != this->root ); + } + + this->list_size = 0; + this->root = 0; + this->position = 0; + } + + template + inline void CircularLinkedList::Concatenate( const CircularLinkedList& L ) + { + unsigned int counter; + node* ptr; + + if ( L.list_size == 0 ) + return ; + + if ( this->list_size == 0 ) + * this = L; + + ptr = L.root; + + this->position = this->root->previous; + + // Cycle through each element in L and add it to the current list + for ( counter = 0; counter < L.list_size; counter++ ) + { + // Add item after the current item pointed to + // add(*(ptr->item)); + + Add ( ptr->item ); + + // Update pointers. Moving ptr keeps the current pointer at the end of the list since the add function does not move the pointer + ptr = ptr->next; + + this->position = this->position->next; + } + } + + template + inline void CircularLinkedList::Sort( void ) + { + if ( this->list_size <= 1 ) + return ; + + // Call equal operator to assign result of mergesort to current object + *this = Mergesort( *this ); + + this->position = this->root; + } + + template + CircularLinkedList CircularLinkedList::Mergesort( const CircularLinkedList& L ) + { + unsigned int counter; + node* location; + CircularLinkedList L1; + CircularLinkedList L2; + + location = L.root; + + // Split the list into two equal size sublists, L1 and L2 + + for ( counter = 0; counter < L.list_size / 2; counter++ ) + { + // L1.add (*(location->item)); + L1.Add ( location->item ); + location = location->next; + } + + for ( ;counter < L.list_size; counter++ ) + { + // L2.Add(*(location->item)); + L2.Add ( location->item ); + location = location->next; + } + + // Recursively sort the sublists + if ( L1.list_size > 1 ) + L1 = Mergesort( L1 ); + + if ( L2.list_size > 1 ) + L2 = Mergesort( L2 ); + + // Merge the two sublists + return Merge( L1, L2 ); + } + + template + CircularLinkedList CircularLinkedList::Merge( CircularLinkedList L1, CircularLinkedList L2 ) + { + CircularLinkedList X; + CircularLinkedListType element; + L1.position = L1.root; + L2.position = L2.root; + + // While neither list is empty + + while ( ( L1.list_size != 0 ) && ( L2.list_size != 0 ) ) + { + // Compare the first items of L1 and L2 + // Remove the smaller of the two items from the list + + if ( ( ( L1.root ) ->item ) < ( ( L2.root ) ->item ) ) + // if ((*((L1.root)->item)) < (*((L2.root)->item))) + { + // element = *((L1.root)->item); + element = ( L1.root ) ->item; + L1.Del(); + } + else + { + // element = *((L2.root)->item); + element = ( L2.root ) ->item; + L2.Del(); + } + + // Add this item to the end of X + X.Add( element ); + + X++; + } + + // Add the remaining list to X + if ( L1.list_size != 0 ) + X.Concatenate( L1 ); + else + X.Concatenate( L2 ); + + return X; + } + + template + LinkedList LinkedList::Mergesort( const LinkedList& L ) + { + unsigned int counter; + typename LinkedList::node* location; + LinkedList L1; + LinkedList L2; + + location = L.root; + + // Split the list into two equal size sublists, L1 and L2 + + for ( counter = 0; counter < L.LinkedList_size / 2; counter++ ) + { + // L1.add (*(location->item)); + L1.Add ( location->item ); + location = location->next; + } + + for ( ;counter < L.LinkedList_size; counter++ ) + { + // L2.Add(*(location->item)); + L2.Add ( location->item ); + location = location->next; + } + + // Recursively sort the sublists + if ( L1.list_size > 1 ) + L1 = Mergesort( L1 ); + + if ( L2.list_size > 1 ) + L2 = Mergesort( L2 ); + + // Merge the two sublists + return Merge( L1, L2 ); + } + + template + LinkedList LinkedList::Merge( LinkedList L1, LinkedList L2 ) + { + LinkedList X; + LinkedListType element; + L1.position = L1.root; + L2.position = L2.root; + + // While neither list is empty + + while ( ( L1.LinkedList_size != 0 ) && ( L2.LinkedList_size != 0 ) ) + { + // Compare the first items of L1 and L2 + // Remove the smaller of the two items from the list + + if ( ( ( L1.root ) ->item ) < ( ( L2.root ) ->item ) ) + // if ((*((L1.root)->item)) < (*((L2.root)->item))) + { + element = ( L1.root ) ->item; + // element = *((L1.root)->item); + L1.Del(); + } + else + { + element = ( L2.root ) ->item; + // element = *((L2.root)->item); + L2.Del(); + } + + // Add this item to the end of X + X.Add( element ); + } + + // Add the remaining list to X + if ( L1.LinkedList_size != 0 ) + X.concatenate( L1 ); + else + X.concatenate( L2 ); + + return X; + } + + + // Prefix + template + LinkedList& LinkedList::operator++() + { + if ( ( this->list_size != 0 ) && ( this->position->next != this->root ) ) + this->position = this->position->next; + + return *this; + } + + /* + // Postfix + template + LinkedList& LinkedList::operator++(int) + { + LinkedList before; + before=*this; + operator++(); + return before; + } + */ + // Postfix + template + LinkedList& LinkedList::operator++( int ) + { + return this->operator++(); + } + + // Prefix + template + LinkedList& LinkedList::operator--() + { + if ( ( this->list_size != 0 ) && ( this->position != this->root ) ) + this->position = this->position->previous; + + return *this; + } + + /* + // Postfix + template + LinkedList& LinkedList::operator--(int) + { + LinkedList before; + before=*this; + operator--(); + return before; + } + */ + + // Postfix + template + LinkedList& LinkedList::operator--( int ) + { + return this->operator--(); + } + +} // End namespace + +#ifdef _MSC_VER +#pragma warning( pop ) +#endif + +#endif diff --git a/RakNet/Sources/DS_List.h b/RakNet/Sources/DS_List.h new file mode 100644 index 0000000..2a5fcfb --- /dev/null +++ b/RakNet/Sources/DS_List.h @@ -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.h" +#include // memmove +#include "Export.h" +#include "RakMemoryOverride.h" + +/// 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 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 + List::List() + { + allocation_size = 0; + listArray = 0; + list_size = 0; + } + + template + List::~List() + { + if (allocation_size>0) + RakNet::OP_DELETE_ARRAY(listArray, __FILE__, __LINE__); + } + + + template + List::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( original_copy.list_size , __FILE__, __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 + List& List::operator= ( const List& original_copy ) + { + if ( ( &original_copy ) != this ) + { + Clear( false, __FILE__, __LINE__ ); + + // Allocate memory for copy + + if ( original_copy.list_size == 0 ) + { + list_size = 0; + allocation_size = 0; + } + + else + { + listArray = RakNet::OP_NEW_ARRAY( original_copy.list_size , __FILE__, __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 + inline list_type& List::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 + inline list_type& List::Get ( const unsigned int position ) const + { + return listArray[ position ]; + } + + template + void List::Push(const list_type &input, const char *file, unsigned int line) + { + Insert(input, file, line); + } + + template + inline list_type& List::Pop(void) + { +#ifdef _DEBUG + RakAssert(list_size>0); +#endif + --list_size; + return listArray[list_size]; + } + + template + void List::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( 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 + void List::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( 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 + inline void List::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( 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 + inline void List::Replace( const list_type &input ) + { + if ( list_size > 0 ) + listArray[ list_size - 1 ] = input; + } + + template + void List::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 + void List::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 + inline void List::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 + unsigned int List::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 + inline unsigned int List::Size( void ) const + { + return list_size; + } + + template + void List::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 + void List::Compress( const char *file, unsigned int line ) + { + list_type * new_array; + + if ( allocation_size == 0 ) + return ; + + new_array = RakNet::OP_NEW_ARRAY( 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 + void List::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 diff --git a/RakNet/Sources/DS_Map.h b/RakNet/Sources/DS_Map.h new file mode 100644 index 0000000..162298d --- /dev/null +++ b/RakNet/Sources/DS_Map.h @@ -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.h" +#include "Export.h" +#include "RakMemoryOverride.h" +#include "RakAssert.h" + +// 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 + int defaultMapKeyComparison(const key_type &a, const key_type &b) + { + if (a > + class RAK_DLL_EXPORT Map + { + public: + static void IMPLEMENT_DEFAULT_COMPARISON(void) {DataStructures::defaultMapKeyComparison(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 + Map::Map() + { + lastSearchIndexValid=false; + } + + template + Map::~Map() + { + Clear(); + } + + template + Map::Map( const Map& original_copy ) + { + mapNodeList=original_copy.mapNodeList; + lastSearchIndex=original_copy.lastSearchIndex; + lastSearchKey=original_copy.lastSearchKey; + lastSearchIndexValid=original_copy.lastSearchIndexValid; + } + + template + Map& Map::operator= ( const Map& original_copy ) + { + mapNodeList=original_copy.mapNodeList; + lastSearchIndex=original_copy.lastSearchIndex; + lastSearchKey=original_copy.lastSearchKey; + lastSearchIndexValid=original_copy.lastSearchIndexValid; + return *this; + } + + template + data_type& Map::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 + unsigned Map::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 + void Map::RemoveAtIndex(const unsigned index) + { + mapNodeList.RemoveAtIndex(index); + lastSearchIndexValid=false; + } + + template + data_type Map::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 + void Map::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__,__LINE__)); + } + } + + template + void Map::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 + void Map::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__,__LINE__)); + } + + template + bool Map::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 + bool Map::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 + void Map::Clear(void) + { + lastSearchIndexValid=false; + mapNodeList.Clear(false, __FILE__, __LINE__); + } + + template + data_type& Map::operator[]( const unsigned int position ) const + { + return mapNodeList[position].mapNodeData; + } + + template + key_type Map::GetKeyAtIndex( const unsigned int position ) const + { + return mapNodeList[position].mapNodeKey; + } + + template + unsigned Map::Size(void) const + { + return mapNodeList.Size(); + } + + template + void Map::SaveLastSearch(const key_type &key, const unsigned index) const + { + (void) key; + (void) index; + + /* + lastSearchIndex=index; + lastSearchKey=key; + lastSearchIndexValid=true; + */ + } + + template + bool Map::HasSavedSearchResult(const key_type &key) const + { + (void) key; + + // Not threadsafe! + return false; + // return lastSearchIndexValid && key_comparison_func(key,lastSearchKey)==0; + } +} + +#endif diff --git a/RakNet/Sources/DS_MemoryPool.h b/RakNet/Sources/DS_MemoryPool.h new file mode 100644 index 0000000..06e8390 --- /dev/null +++ b/RakNet/Sources/DS_MemoryPool.h @@ -0,0 +1,295 @@ +/// \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 +#endif +#include "RakAssert.h" +#include "Export.h" + +#include "RakMemoryOverride.h" + +// 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 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 + MemoryPool::MemoryPool() + { +#ifndef _DISABLE_MEMORY_POOL + //AllocateFirst(); + availablePagesSize=0; + unavailablePagesSize=0; + memoryPoolPageSize=16384; +#endif + } + template + MemoryPool::~MemoryPool() + { +#ifndef _DISABLE_MEMORY_POOL + Clear(__FILE__, __LINE__); +#endif + } + + template + void MemoryPool::SetPageSize(int size) + { + memoryPoolPageSize=size; + } + + template + MemoryBlockType* MemoryPool::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 + void MemoryPool::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 + void MemoryPool::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 + int MemoryPool::BlocksPerPage(void) const + { + return memoryPoolPageSize / sizeof(MemoryWithPage); + } + template + bool MemoryPool::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 diff --git a/RakNet/Sources/DS_Multilist.h b/RakNet/Sources/DS_Multilist.h new file mode 100644 index 0000000..e9c6ba3 --- /dev/null +++ b/RakNet/Sources/DS_Multilist.h @@ -0,0 +1,1644 @@ +/// \file DS_Multilist.h +/// \internal +/// \brief ADT that can represent an unordered list, ordered list, stack, or queue with a common interface +/// +/// This file is part of RakNet Copyright 2003 Jenkins Software LLC +/// +/// Usage of RakNet is subject to the appropriate license agreement. + + +#ifndef __MULTILIST_H +#define __MULTILIST_H + +#include "RakAssert.h" +#include // memmove +#include "Export.h" +#include "RakMemoryOverride.h" +#include "NativeTypes.h" + + +#ifdef _MSC_VER +#pragma warning( push ) +#pragma warning( disable : 4127 ) // warning C4127: conditional expression is constant +#pragma warning( disable : 4512 ) // warning C4512: assignment operator could not be generated +#endif + +/// What algorithm to use to store the data for the Multilist +enum MultilistType +{ + /// Removing from the middle of the list will swap the end of the list rather than shift the elements. Push and Pop operate on the tail. + ML_UNORDERED_LIST, + /// A normal list, with the list order preserved. Push and Pop operate on the tail. + ML_STACK, + /// A queue. Push and Pop operate on the head + ML_QUEUE, + /// A list that is always kept in order. Elements must be unique, and compare against each other consistently using <, ==, and > + ML_ORDERED_LIST, + /// A list whose type can change at runtime + ML_VARIABLE_DURING_RUNTIME +}; + +/// 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 +{ + /// Can be used with Multilist::ForEach + /// Assuming the Multilist holds pointers, will delete those pointers + template + void DeletePtr_RakNet(templateType &ptr, const char *file, unsigned int line ) {RakNet::OP_DELETE(ptr, file, line);} + + /// Can be used with Multilist::ForEach + /// Assuming the Multilist holds pointers, will delete those pointers + template + void DeletePtr(templateType &ptr) {delete ptr;} + + /// The following is invalid. + /// bool operator<( const MyClass *myClass, const int &inputKey ) {return myClass->value < inputKey;} + /// At least one type has to be a reference to a class + /// MLKeyRef is a helper class to turn a native type into a class, so you can compare that native type against a pointer to a different class + /// Used for he Multilist, when _DataType != _KeyType + template < class templateType > + class MLKeyRef + { + public: + MLKeyRef(const templateType& input) : val(input) {} + const templateType &Get(void) const {return val;} + bool operator<( const templateType &right ) {return val < right;} + bool operator>( const templateType &right ) {return val > right;} + bool operator==( const templateType &right ) {return val == right;} + protected: + const templateType &val; + }; + + /// For the Multilist, when _DataType != _KeyType, you must define the comparison operators between the key and the data + /// This is non-trivial due to the need to use MLKeyRef in case the type held is a pointer to a structure or class and the key type is not a class + /// For convenience, this macro will implement the comparison operators under the following conditions + /// 1. _DataType is a pointer to a class or structure + /// 2. The key is a member variable of _DataType + #define DEFINE_MULTILIST_PTR_TO_MEMBER_COMPARISONS( _CLASS_NAME_, _KEY_TYPE_, _MEMBER_VARIABLE_NAME_ ) \ + bool operator<( const DataStructures::MLKeyRef<_KEY_TYPE_> &inputKey, const _CLASS_NAME_ *cls ) {return inputKey.Get() < cls->_MEMBER_VARIABLE_NAME_;} \ + bool operator>( const DataStructures::MLKeyRef<_KEY_TYPE_> &inputKey, const _CLASS_NAME_ *cls ) {return inputKey.Get() > cls->_MEMBER_VARIABLE_NAME_;} \ + bool operator==( const DataStructures::MLKeyRef<_KEY_TYPE_> &inputKey, const _CLASS_NAME_ *cls ) {return inputKey.Get() == cls->_MEMBER_VARIABLE_NAME_;} + + typedef uint32_t DefaultIndexType; + + /// \brief The multilist, representing an abstract data type that generally holds lists. + /// \param[in] _MultilistType What type of list this is, \sa MultilistType + /// \param[in] _DataType What type of data this list holds. + /// \param[in] _KeyType If a function takes a key to sort on, what type of key this is. The comparison operator between _DataType and _KeyType must be defined + /// \param[in] _IndexType What variable type to use for indices + template + class RAK_DLL_EXPORT Multilist + { + public: + Multilist(); + ~Multilist(); + Multilist( const Multilist& source ); + Multilist& operator= ( const Multilist& source ); + _DataType& operator[] ( const _IndexType position ) const; + /// Unordered list, stack is LIFO + /// QUEUE is FIFO + /// Ordered list is inserted in order + void Push(const _DataType &d, const char *file=__FILE__, unsigned int line=__LINE__ ); + void Push(const _DataType &d, const _KeyType &key, const char *file=__FILE__, unsigned int line=__LINE__ ); + + /// \brief Gets or removes and gets an element from the list, according to the same rules as Push(). + /// Ordered list is LIFO for the purposes of Pop and Peek. + _DataType &Pop(const char *file=__FILE__, unsigned int line=__LINE__); + _DataType &Peek(void) const; + + /// \brief Same as Push(), except FIFO and LIFO are reversed. + /// Ordered list still inserts in order. + void PushOpposite(const _DataType &d, const char *file=__FILE__, unsigned int line=__LINE__ ); + void PushOpposite(const _DataType &d, const _KeyType &key, const char *file=__FILE__, unsigned int line=__LINE__ ); + + /// \brief Same as Pop() and Peek(), except FIFO and LIFO are reversed. + _DataType &PopOpposite(const char *file=__FILE__, unsigned int line=__LINE__); + _DataType &PeekOpposite(void) const; + + /// \brief Stack,Queue: Inserts at index indicated, elements are shifted. + /// Ordered list: Inserts, position is ignored + void InsertAtIndex(const _DataType &d, _IndexType index, const char *file=__FILE__, unsigned int line=__LINE__); + + /// \brief Unordered list, removes at index indicated, swaps last element with that element. + /// Otherwise, array is shifted left to overwrite removed element + /// \details Index[0] returns the same as Pop() for a queue. + /// Same as PopOpposite() for the list and ordered list + void RemoveAtIndex(_IndexType position, const char *file=__FILE__, unsigned int line=__LINE__); + + /// \brief Find the index of \a key, and remove at that index. + bool RemoveAtKey(_KeyType key, bool assertIfDoesNotExist, const char *file=__FILE__, unsigned int line=__LINE__); + + /// \brief Finds the index of \a key. Return -1 if the key is not found. + _IndexType GetIndexOf(_KeyType key) const; + + /// \brief Returns where in the list we should insert the item, to preserve list order. + /// Returns -1 if the item is already in the list + _IndexType GetInsertionIndex(_KeyType key) const; + + /// \brief Finds the index of \a key. Return 0 if the key is not found. Useful if _DataType is always non-zero pointers. + _DataType GetPtr(_KeyType key) const; + + /// \brief Iterate over the list, calling the function pointer on each element. + void ForEach(void (*func)(_DataType &item, const char *file, unsigned int line), const char *file, unsigned int line); + void ForEach(void (*func)(_DataType &item)); + + /// \brief Returns if the list is empty. + bool IsEmpty(void) const; + + /// \brief Returns the number of elements used in the list. + _IndexType GetSize(void) const; + + /// \brief Empties the list. The list is not deallocated if it is small, + /// unless \a deallocateSmallBlocks is true + void Clear( bool deallocateSmallBlocks=true, const char *file=__FILE__, unsigned int line=__LINE__ ); + + /// \brief Empties the list, first calling RakNet::OP_Delete on all items. + /// \details The list is not deallocated if it is small, unless \a deallocateSmallBlocks is true + void ClearPointers( bool deallocateSmallBlocks=true, const char *file=__FILE__, unsigned int line=__LINE__ ); + + /// \brief Empty one item from the list, first calling RakNet::OP_Delete on that item. + void ClearPointer( _KeyType key, const char *file=__FILE__, unsigned int line=__LINE__ ); + + /// \brief Reverses the elements in the list, and flips the sort order + /// returned by GetSortOrder() if IsSorted() returns true at the time the function is called + void ReverseList(void); + + /// \brief Reallocates the list to a larger size. + /// If \a size is smaller than the value returned by GetSize(), the call does nothing. + void Reallocate(_IndexType size, const char *file=__FILE__, unsigned int line=__LINE__); + + /// \brief Sorts the list unless it is an ordered list, in which it does nothing as the list is assumed to already be sorted. + /// \details However, if \a force is true, it will also resort the ordered list, useful if the comparison operator between _KeyType and _DataType would now return different results + /// Once the list is sorted, further operations to lookup by key will be log2(n) until the list is modified + void Sort(bool force); + + /// \brief Sets the list to be remembered as sorted. + /// \details Optimization if the source is sorted already + void TagSorted(void); + + /// \brief Defaults to ascending. + /// \details Used by Sort(), and by ML_ORDERED_LIST + void SetSortOrder(bool ascending); + + /// \brief Returns true if ascending. + bool GetSortOrder(void) const; + + /// \brief Returns true if the list is currently believed to be in a sorted state. + /// \details Doesn't actually check for sortedness, just if Sort() + /// was recently called, or MultilistType is ML_ORDERED_LIST + bool IsSorted(void) const; + + /// Returns what type of list this is + MultilistType GetMultilistType(void) const; + + /// \brief Changes what type of list this is. + /// \pre Template must be defined with ML_VARIABLE_DURING_RUNTIME for this to do anything + /// \param[in] mlType Any value of the enum MultilistType, except ML_VARIABLE_DURING_RUNTIME + void SetMultilistType(MultilistType newType); + + /// \brief Returns the intersection of two lists. + /// Intersection is items common to both lists. + static void FindIntersection( + Multilist& source1, + Multilist& source2, + Multilist& intersection, + Multilist& uniqueToSource1, + Multilist& uniqueToSource2); + + protected: + void ReallocateIfNeeded(const char *file, unsigned int line); + void DeallocateIfNeeded(const char *file, unsigned int line); + void ReallocToSize(_IndexType newAllocationSize, const char *file, unsigned int line); + void ReverseListInternal(void); + void InsertInOrderedList(const _DataType &d, const _KeyType &key); + _IndexType GetIndexFromKeyInSortedList(const _KeyType &key, bool *objectExists) const; + void InsertShiftArrayRight(const _DataType &d, _IndexType index); + void DeleteShiftArrayLeft(_IndexType index); + void QSortAscending(_IndexType left, _IndexType right); + void QSortDescending(_IndexType left, _IndexType right); + void CopySource( const Multilist& source ); + + /// An array of user values + _DataType* data; + + /// Number of elements in the list + _IndexType dataSize; + + /// Size of \a array + _IndexType allocationSize; + + /// Array index for the head of the queue + _IndexType queueHead; + + /// Array index for the tail of the queue + _IndexType queueTail; + + /// How many bytes the user chose to preallocate + /// Won't automatically deallocate below this + _IndexType preallocationSize; + + enum + { + ML_UNSORTED, + ML_SORTED_ASCENDING, + ML_SORTED_DESCENDING + } sortState; + + bool ascendingSort; + + // In case we are using the variable type multilist + MultilistType variableMultilistType; + }; + + template + Multilist<_MultilistType, _DataType, _KeyType, _IndexType>::Multilist() + { + data=0; + dataSize=0; + allocationSize=0; + ascendingSort=true; + sortState=ML_UNSORTED; + queueHead=0; + queueTail=0; + preallocationSize=0; + + if (_MultilistType==ML_ORDERED_LIST) + sortState=ML_SORTED_ASCENDING; + else + sortState=ML_UNSORTED; + + if (_MultilistType==ML_VARIABLE_DURING_RUNTIME) + variableMultilistType=ML_UNORDERED_LIST; + } + + template + Multilist<_MultilistType, _DataType, _KeyType, _IndexType>::~Multilist() + { + if (data!=0) + RakNet::OP_DELETE_ARRAY(data, __FILE__, __LINE__); + } + + template + Multilist<_MultilistType, _DataType, _KeyType, _IndexType>::Multilist( const Multilist& source ) + { + CopySource(source); + } + + template + Multilist<_MultilistType, _DataType, _KeyType, _IndexType>& Multilist<_MultilistType, _DataType, _KeyType, _IndexType>::operator= ( const Multilist& source ) + { + Clear(true); + CopySource(source); + return *this; + } + + template + void Multilist<_MultilistType, _DataType, _KeyType, _IndexType>::CopySource( const Multilist& source ) + { + dataSize=source.GetSize(); + ascendingSort=source.ascendingSort; + sortState=source.sortState; + queueHead=0; + queueTail=dataSize; + preallocationSize=source.preallocationSize; + variableMultilistType=source.variableMultilistType; + if (source.data==0) + { + data=0; + allocationSize=0; + } + else + { + allocationSize=dataSize; + data = RakNet::OP_NEW_ARRAY<_DataType>(dataSize,__FILE__,__LINE__); + _IndexType i; + for (i=0; i < dataSize; i++) + data[i]=source[i]; + } + } + + template + _DataType& Multilist<_MultilistType, _DataType, _KeyType, _IndexType>::operator[] ( const _IndexType position ) const + { + RakAssert(position= allocationSize ) + return data[ queueHead + position - allocationSize ]; + else + return data[ queueHead + position ]; + } + + return data[position]; + } + + template + void Multilist<_MultilistType, _DataType, _KeyType, _IndexType>::Push(const _DataType &d, const char *file, unsigned int line ) + { + Push(d,d,file,line); + } + + template + void Multilist<_MultilistType, _DataType, _KeyType, _IndexType>::Push(const _DataType &d, const _KeyType &key, const char *file, unsigned int line ) + { + ReallocateIfNeeded(file,line); + + if (GetMultilistType()==ML_UNORDERED_LIST || GetMultilistType()==ML_STACK) + { + data[dataSize]=d; + dataSize++; + } + else if (GetMultilistType()==ML_QUEUE) + { + data[queueTail++] = d; + + if ( queueTail == allocationSize ) + queueTail = 0; + dataSize++; + } + else + { + RakAssert(GetMultilistType()==ML_ORDERED_LIST); + InsertInOrderedList(d,key); + } + + if (GetMultilistType()==ML_UNORDERED_LIST || GetMultilistType()==ML_STACK || GetMultilistType()==ML_QUEUE) + { + // Break sort if no longer sorted + if (sortState!=ML_UNSORTED && dataSize>1) + { + if (ascendingSort) + { + if ( MLKeyRef<_KeyType>(key) < operator[](dataSize-2) ) + sortState=ML_UNSORTED; + } + else + { + if ( MLKeyRef<_KeyType>(key) > operator[](dataSize-2) ) + sortState=ML_UNSORTED; + } + + sortState=ML_UNSORTED; + } + } + } + + template + _DataType &Multilist<_MultilistType, _DataType, _KeyType, _IndexType>::Pop(const char *file, unsigned int line) + { + RakAssert(IsEmpty()==false); + DeallocateIfNeeded(file,line); + if (GetMultilistType()==ML_UNORDERED_LIST || GetMultilistType()==ML_STACK || GetMultilistType()==ML_ORDERED_LIST) + { + dataSize--; + return data[dataSize]; + } + else + { + RakAssert(GetMultilistType()==ML_QUEUE); + + if ( ++queueHead == allocationSize ) + queueHead = 0; + + if ( queueHead == 0 ) + return data[ allocationSize -1 ]; + + dataSize--; + return data[ queueHead -1 ]; + } + } + + template + _DataType &Multilist<_MultilistType, _DataType, _KeyType, _IndexType>::Peek(void) const + { + RakAssert(IsEmpty()==false); + if (GetMultilistType()==ML_UNORDERED_LIST || GetMultilistType()==ML_STACK || GetMultilistType()==ML_ORDERED_LIST) + { + return data[dataSize-1]; + } + else + { + RakAssert(GetMultilistType()==ML_QUEUE); + return data[ queueHead ]; + } + } + + template + void Multilist<_MultilistType, _DataType, _KeyType, _IndexType>::PushOpposite(const _DataType &d, const char *file, unsigned int line ) + { + PushOpposite(d,d,file,line); + } + + template + void Multilist<_MultilistType, _DataType, _KeyType, _IndexType>::PushOpposite(const _DataType &d, const _KeyType &key, const char *file, unsigned int line ) + { + ReallocateIfNeeded(file,line); + + // Unordered list Push at back + if (GetMultilistType()==ML_UNORDERED_LIST) + { + data[dataSize]=d; + dataSize++; + } + else if (GetMultilistType()==ML_STACK) + { + // Stack push at front of the list, instead of back as normal + InsertAtIndex(d,0,file,line); + } + else if (GetMultilistType()==ML_QUEUE) + { + // Queue push at front of the list, instead of back as normal + InsertAtIndex(d,0,file,line); + } + else + { + RakAssert(GetMultilistType()==ML_ORDERED_LIST); + InsertInOrderedList(d,key); + } + + if (GetMultilistType()==ML_UNORDERED_LIST || GetMultilistType()==ML_STACK || GetMultilistType()==ML_QUEUE) + { + // Break sort if no longer sorted + if (sortState!=ML_UNSORTED && dataSize>1) + { + if (ascendingSort) + { + if ( MLKeyRef<_KeyType>(key) > operator[](1) ) + sortState=ML_UNSORTED; + } + else + { + if ( MLKeyRef<_KeyType>(key) < operator[](1) ) + sortState=ML_UNSORTED; + } + } + } + } + + template + _DataType &Multilist<_MultilistType, _DataType, _KeyType, _IndexType>::PopOpposite(const char *file, unsigned int line) + { + RakAssert(IsEmpty()==false); + if (GetMultilistType()==ML_UNORDERED_LIST || GetMultilistType()==ML_STACK || GetMultilistType()==ML_ORDERED_LIST) + { + // Copy leftmost to end + ReallocateIfNeeded(file,line); + data[dataSize]=data[0]; + DeleteShiftArrayLeft(0); + --dataSize; + // Assuming still leaves at least one element past the end of the list allocated + DeallocateIfNeeded(file,line); + // Return end + return data[dataSize+1]; + } + else + { + RakAssert(GetMultilistType()==ML_QUEUE); + // Deallocate first, since we are returning off the existing list + DeallocateIfNeeded(file,line); + dataSize--; + + if (queueTail==0) + queueTail=allocationSize-1; + else + --queueTail; + + return data[queueTail]; + } + } + + template + _DataType &Multilist<_MultilistType, _DataType, _KeyType, _IndexType>::PeekOpposite(void) const + { + RakAssert(IsEmpty()==false); + if (GetMultilistType()==ML_UNORDERED_LIST || GetMultilistType()==ML_STACK || GetMultilistType()==ML_ORDERED_LIST) + { + return data[0]; + } + else + { + RakAssert(GetMultilistType()==ML_QUEUE); + _IndexType priorIndex; + if (queueTail==0) + priorIndex=allocationSize-1; + else + priorIndex=queueTail-1; + + return data[priorIndex]; + } + } + + template + void Multilist<_MultilistType, _DataType, _KeyType, _IndexType>::InsertAtIndex(const _DataType &d, _IndexType index, const char *file, unsigned int line) + { + ReallocateIfNeeded(file,line); + + if (GetMultilistType()==ML_UNORDERED_LIST || GetMultilistType()==ML_STACK || GetMultilistType()==ML_ORDERED_LIST) + { + if (index>=dataSize) + { + // insert at end + data[dataSize]=d; + + dataSize++; + } + else + { + // insert at index + InsertShiftArrayRight(d,index); + } + } + else + { + data[queueTail++] = d; + + if ( queueTail == allocationSize ) + queueTail = 0; + + ++dataSize; + + if (dataSize==1) + return; + + _IndexType writeIndex, readIndex, trueWriteIndex, trueReadIndex; + writeIndex=dataSize-1; + readIndex=writeIndex-1; + while (readIndex >= index) + { + if ( queueHead + writeIndex >= allocationSize ) + trueWriteIndex = queueHead + writeIndex - allocationSize; + else + trueWriteIndex = queueHead + writeIndex; + + if ( queueHead + readIndex >= allocationSize ) + trueReadIndex = queueHead + readIndex - allocationSize; + else + trueReadIndex = queueHead + readIndex; + + data[trueWriteIndex]=data[trueReadIndex]; + + if (readIndex==0) + break; + writeIndex--; + readIndex--; + } + + if ( queueHead + index >= allocationSize ) + trueWriteIndex = queueHead + index - allocationSize; + else + trueWriteIndex = queueHead + index; + + data[trueWriteIndex]=d; + } + + if (_MultilistType!=ML_ORDERED_LIST) + sortState=ML_UNSORTED; + } + + template + void Multilist<_MultilistType, _DataType, _KeyType, _IndexType>::RemoveAtIndex(_IndexType position, const char *file, unsigned int line) + { + RakAssert(position < dataSize); + RakAssert(IsEmpty()==false); + + if (GetMultilistType()==ML_UNORDERED_LIST) + { + // Copy tail to current + data[position]=data[dataSize-1]; + } + else if (GetMultilistType()==ML_STACK || GetMultilistType()==ML_ORDERED_LIST) + { + DeleteShiftArrayLeft(position); + } + else + { + RakAssert(GetMultilistType()==ML_QUEUE); + + _IndexType index, next; + + if ( queueHead + position >= allocationSize ) + index = queueHead + position - allocationSize; + else + index = queueHead + position; + + next = index + 1; + + if ( next == allocationSize ) + next = 0; + + while ( next != queueTail ) + { + // Overwrite the previous element + data[ index ] = data[ next ]; + index = next; + //next = (next + 1) % allocationSize; + + if ( ++next == allocationSize ) + next = 0; + } + + // Move the queueTail back + if ( queueTail == 0 ) + queueTail = allocationSize - 1; + else + --queueTail; + } + + + dataSize--; + DeallocateIfNeeded(file,line); + } + + template + bool Multilist<_MultilistType, _DataType, _KeyType, _IndexType>::RemoveAtKey(_KeyType key, bool assertIfDoesNotExist, const char *file, unsigned int line) + { + _IndexType index = GetIndexOf(key); + if (index==(_IndexType)-1) + { + RakAssert(assertIfDoesNotExist==false && "RemoveAtKey element not found"); + return false; + } + RemoveAtIndex(index,file,line); + return true; + } + + template + _IndexType Multilist<_MultilistType, _DataType, _KeyType, _IndexType>::GetIndexOf(_KeyType key) const + { + _IndexType i; + if (IsSorted()) + { + bool objectExists; + i=GetIndexFromKeyInSortedList(key, &objectExists); + if (objectExists) + return i; + return (_IndexType)-1; + } + else if (GetMultilistType()==ML_UNORDERED_LIST || GetMultilistType()==ML_STACK) + { + for (i=0; i < dataSize; i++) + { + if (MLKeyRef<_KeyType>(key)==data[i]) + return i; + } + return (_IndexType)-1; + } + else + { + RakAssert( GetMultilistType()==ML_QUEUE ); + + for (i=0; i < dataSize; i++) + { + if (MLKeyRef<_KeyType>(key)==operator[](i)) + return i; + } + return (_IndexType)-1; + } + } + + template + _IndexType Multilist<_MultilistType, _DataType, _KeyType, _IndexType>::GetInsertionIndex(_KeyType key) const + { + _IndexType i; + if (IsSorted()) + { + bool objectExists; + i=GetIndexFromKeyInSortedList(key, &objectExists); + if (objectExists) + return (_IndexType)-1; + return i; + } + else if (GetMultilistType()==ML_UNORDERED_LIST || GetMultilistType()==ML_STACK) + { + for (i=0; i < dataSize; i++) + { + if (MLKeyRef<_KeyType>(key)==data[i]) + return (_IndexType)-1; + } + return dataSize; + } + else + { + RakAssert( GetMultilistType()==ML_QUEUE ); + + for (i=0; i < dataSize; i++) + { + if (MLKeyRef<_KeyType>(key)==operator[](i)) + return (_IndexType)-1; + } + return dataSize; + } + } + + template + _DataType Multilist<_MultilistType, _DataType, _KeyType, _IndexType>::GetPtr(_KeyType key) const + { + _IndexType i = GetIndexOf(key); + if (i==(_IndexType)-1) + return 0; + return data[i]; + } + + template + void Multilist<_MultilistType, _DataType, _KeyType, _IndexType>::ForEach(void (*func)(_DataType &item, const char *file, unsigned int line), const char *file, unsigned int line) + { + _IndexType i; + for (i=0; i < dataSize; i++) + func(operator[](i), file, line); + } + + template + void Multilist<_MultilistType, _DataType, _KeyType, _IndexType>::ForEach(void (*func)(_DataType &item)) + { + _IndexType i; + for (i=0; i < dataSize; i++) + func(operator[](i)); + } + + template + bool Multilist<_MultilistType, _DataType, _KeyType, _IndexType>::IsEmpty(void) const + { + return dataSize==0; + } + + template + _IndexType Multilist<_MultilistType, _DataType, _KeyType, _IndexType>::GetSize(void) const + { + return dataSize; + } + + template + void Multilist<_MultilistType, _DataType, _KeyType, _IndexType>::Clear( bool deallocateSmallBlocks, const char *file, unsigned int line ) + { + dataSize=0; + if (GetMultilistType()==ML_ORDERED_LIST) + if (ascendingSort) + sortState=ML_SORTED_ASCENDING; + else + sortState=ML_SORTED_DESCENDING; + else + sortState=ML_UNSORTED; + queueHead=0; + queueTail=0; + + if (deallocateSmallBlocks && allocationSize < 128 && data) + { + RakNet::OP_DELETE_ARRAY(data,file,line); + data=0; + allocationSize=0; + } + } + + template + void Multilist<_MultilistType, _DataType, _KeyType, _IndexType>::ClearPointers( bool deallocateSmallBlocks, const char *file, unsigned int line ) + { + _IndexType i; + for (i=0; i < dataSize; i++) + RakNet::OP_DELETE(operator[](i), file, line); + Clear(deallocateSmallBlocks, file, line); + } + + template + void Multilist<_MultilistType, _DataType, _KeyType, _IndexType>::ClearPointer( _KeyType key, const char *file, unsigned int line ) + { + _IndexType i; + i = GetIndexOf(key); + if (i!=-1) + { + RakNet::OP_DELETE(operator[](i), file, line); + RemoveAtIndex(i); + } + } + + template + void Multilist<_MultilistType, _DataType, _KeyType, _IndexType>::ReverseList(void) + { + if (IsSorted()) + ascendingSort=!ascendingSort; + + ReverseListInternal(); + } + + template + void Multilist<_MultilistType, _DataType, _KeyType, _IndexType>::Reallocate(_IndexType size, const char *file, unsigned int line) + { + _IndexType newAllocationSize; + if (size < dataSize) + newAllocationSize=dataSize; + else + newAllocationSize=size; + preallocationSize=size; + ReallocToSize(newAllocationSize,file,line); + } + + template + void Multilist<_MultilistType, _DataType, _KeyType, _IndexType>::Sort(bool force) + { + if (IsSorted() && force==false) + return; + + if (dataSize>1) + { + if (ascendingSort) + QSortAscending(0,dataSize-1); + else + QSortDescending(0,dataSize-1); + } + + TagSorted(); + } + + template + void Multilist<_MultilistType, _DataType, _KeyType, _IndexType>::TagSorted(void) + { + if (ascendingSort) + sortState=ML_SORTED_ASCENDING; + else + sortState=ML_SORTED_DESCENDING; + } + + template + void Multilist<_MultilistType, _DataType, _KeyType, _IndexType>::QSortAscending(_IndexType leftEdge, _IndexType rightEdge) + { + _DataType temp; + _IndexType left=leftEdge; + _IndexType right=rightEdge; + _IndexType pivotIndex=left++; + + while (left data[pivotIndex]) + { + --left; + + data[pivotIndex]=data[left]; + data[left]=temp; + } + else + { + data[pivotIndex]=data[left]; + data[left]=temp; + + --left; + } + + if (left!=leftEdge) + QSortAscending(leftEdge, left); + + if (right!=rightEdge) + QSortAscending(right, rightEdge); + } + + template + void Multilist<_MultilistType, _DataType, _KeyType, _IndexType>::QSortDescending(_IndexType leftEdge, _IndexType rightEdge) + { + _DataType temp; + _IndexType left=leftEdge; + _IndexType right=rightEdge; + _IndexType pivotIndex=left++; + + while (left= data[pivotIndex]) + { + ++left; + } + else + { + temp=data[left]; + data[left]=data[right]; + data[right]=temp; + --right; + } + } + + temp=data[pivotIndex]; + + // Move pivot to center + if (data[left] < data[pivotIndex]) + { + --left; + + data[pivotIndex]=data[left]; + data[left]=temp; + } + else + { + data[pivotIndex]=data[left]; + data[left]=temp; + + --left; + } + + if (left!=leftEdge) + QSortDescending(leftEdge, left); + + if (right!=rightEdge) + QSortDescending(right, rightEdge); + } + + template + void Multilist<_MultilistType, _DataType, _KeyType, _IndexType>::SetSortOrder(bool ascending) + { + if (ascendingSort!=ascending && IsSorted()) + { + ascendingSort=ascending; + // List is sorted, and the sort order has changed. So reverse the list + ReverseListInternal(); + } + else + ascendingSort=ascending; + } + + template + bool Multilist<_MultilistType, _DataType, _KeyType, _IndexType>::GetSortOrder(void) const + { + return ascendingSort; + } + + template + bool Multilist<_MultilistType, _DataType, _KeyType, _IndexType>::IsSorted(void) const + { + return GetMultilistType()==ML_ORDERED_LIST || sortState!=ML_UNSORTED; + } + + template + MultilistType Multilist<_MultilistType, _DataType, _KeyType, _IndexType>::GetMultilistType(void) const + { + if (_MultilistType==ML_VARIABLE_DURING_RUNTIME) + return variableMultilistType; + return _MultilistType; + } + + template + void Multilist<_MultilistType, _DataType, _KeyType, _IndexType>::SetMultilistType(MultilistType newType) + { + RakAssert(_MultilistType==ML_VARIABLE_DURING_RUNTIME); + switch (variableMultilistType) + { + case ML_UNORDERED_LIST: + switch (newType) + { + case ML_UNORDERED_LIST: + // No change + break; + case ML_STACK: + // Same data format + break; + case ML_QUEUE: + queueHead=0; + queueTail=dataSize; + break; + case ML_ORDERED_LIST: + Sort(false); + break; + } + break; + case ML_STACK: + switch (newType) + { + case ML_UNORDERED_LIST: + // Same data format + break; + case ML_STACK: + // No change + break; + case ML_QUEUE: + queueHead=0; + queueTail=dataSize; + break; + case ML_ORDERED_LIST: + Sort(false); + break; + } + break; + case ML_QUEUE: + switch (newType) + { + case ML_UNORDERED_LIST: + case ML_STACK: + case ML_ORDERED_LIST: + if (queueTail < queueHead) + { + // Realign data if wrapped + ReallocToSize(dataSize, __FILE__, __LINE__); + } + else + { + // Else can just copy starting at head + _IndexType i; + for (i=0; i < dataSize; i++) + data[i]=operator[](i); + } + if (newType==ML_ORDERED_LIST) + Sort(false); + break; + case ML_QUEUE: + // No change + break; + } + break; + case ML_ORDERED_LIST: + switch (newType) + { + case ML_UNORDERED_LIST: + case ML_STACK: + case ML_QUEUE: + // Same data format + // Tag as sorted + if (ascendingSort) + sortState=ML_SORTED_ASCENDING; + else + sortState=ML_SORTED_DESCENDING; + if (newType==ML_QUEUE) + { + queueHead=0; + queueTail=dataSize; + } + break; + case ML_ORDERED_LIST: + // No change + break; + } + break; + } + + variableMultilistType=newType; + } + + template + void Multilist<_MultilistType, _DataType, _KeyType, _IndexType>::FindIntersection( + Multilist& source1, + Multilist& source2, + Multilist& intersection, + Multilist& uniqueToSource1, + Multilist& uniqueToSource2) + { + _IndexType index1=0, index2=0; + source1.SetSortOrder(true); + source2.SetSortOrder(true); + source1.Sort(false); + source2.Sort(false); + intersection.Clear(true,__FILE__, __LINE__); + uniqueToSource1.Clear(true,__FILE__, __LINE__); + uniqueToSource2.Clear(true,__FILE__, __LINE__); + + while (index1 < source1.GetSize() && index2 < source2.GetSize()) + { + if (source1[index1] + void Multilist<_MultilistType, _DataType, _KeyType, _IndexType>::ReallocateIfNeeded(const char *file, unsigned int line) + { + if (dataSize65536) + newAllocationSize=allocationSize+65536; + else + { + newAllocationSize=allocationSize<<1; // * 2 + // Protect against underflow + if (newAllocationSize < allocationSize) + newAllocationSize=allocationSize+65536; + } + + ReallocToSize(newAllocationSize,file,line); + } + + template + void Multilist<_MultilistType, _DataType, _KeyType, _IndexType>::DeallocateIfNeeded(const char *file, unsigned int line) + { + if (allocationSize<512) + return; + if (dataSize >= allocationSize/3 ) + return; + if (dataSize <= preallocationSize ) + return; + + _IndexType newAllocationSize = dataSize<<1; // * 2 + + ReallocToSize(newAllocationSize,file,line); + } + + template + void Multilist<_MultilistType, _DataType, _KeyType, _IndexType>::ReallocToSize(_IndexType newAllocationSize, const char *file, unsigned int line) + { + _DataType* newData = RakNet::OP_NEW_ARRAY<_DataType>(newAllocationSize,file,line); + _IndexType i; + for (i=0; i < dataSize; i++) + newData[i]=operator[](i); + if (dataSize>0) + { + RakNet::OP_DELETE_ARRAY(data,file,line); + if (GetMultilistType()==ML_QUEUE) + { + queueHead=0; + queueTail=dataSize; + } + } + data=newData; + allocationSize=newAllocationSize; + } + + template + void Multilist<_MultilistType, _DataType, _KeyType, _IndexType>::ReverseListInternal(void) + { + _DataType temp; + _IndexType i; + for (i=0; i < dataSize/2; i++) + { + temp=operator[](i); + operator[](i)=operator[](dataSize-1-i); + operator[](dataSize-1-i)=temp; + } + } + + template + void Multilist<_MultilistType, _DataType, _KeyType, _IndexType>::InsertInOrderedList(const _DataType &d, const _KeyType &key) + { + RakAssert(GetMultilistType()==ML_ORDERED_LIST); + + bool objectExists; + _IndexType index; + index = GetIndexFromKeyInSortedList(key, &objectExists); + + // if (objectExists) + // { + // Ordered list only allows unique insertions + // RakAssert("Duplicate insertion into ordered list" && false); + // return; + // } + + if (index>=dataSize) + { + // insert at end + data[dataSize]=d; + dataSize++; + } + else + { + // insert at index + InsertShiftArrayRight(d,index); + } + } + + template + _IndexType Multilist<_MultilistType, _DataType, _KeyType, _IndexType>::GetIndexFromKeyInSortedList(const _KeyType &key, bool *objectExists) const + { + RakAssert(IsSorted()); + _IndexType index, upperBound, lowerBound; + + if (dataSize==0) + { + *objectExists=false; + return 0; + } + + upperBound=dataSize-1; + lowerBound=0; + index = dataSize/2; + +#ifdef _MSC_VER + #pragma warning( disable : 4127 ) // warning C4127: conditional expression is constant +#endif + while (1) + { + if (MLKeyRef<_KeyType>(key) > operator[](index) ) + { + if (ascendingSort) + lowerBound=index+1; + else + upperBound=index-1; + } + else if (MLKeyRef<_KeyType>(key) < operator[](index) ) + { + if (ascendingSort) + upperBound=index-1; + else + lowerBound=index+1; + } + else + { + // == + *objectExists=true; + return index; + } + + index=lowerBound+(upperBound-lowerBound)/2; + + if (lowerBound>upperBound || upperBound==(_IndexType)-1) + { + *objectExists=false; + return lowerBound; // No match + } + } + } + + template + void Multilist<_MultilistType, _DataType, _KeyType, _IndexType>::InsertShiftArrayRight(const _DataType &d, _IndexType index) + { + RakAssert(_MultilistType!=ML_QUEUE); + + // Move the elements in the list to make room + _IndexType i; + for ( i = dataSize; i != index; i-- ) + data[ i ] = data[ i - 1 ]; + + // Insert the new item at the correct spot + data[ index ] = d; + + ++dataSize; + } + + template + void Multilist<_MultilistType, _DataType, _KeyType, _IndexType>::DeleteShiftArrayLeft( _IndexType index ) + { + RakAssert(index < dataSize); + RakAssert(_MultilistType!=ML_QUEUE); + + _IndexType i; + for ( i = index; i < dataSize-1; i++ ) + data[i]=data[i+1]; + } +}; + +/* +struct KeyAndValue +{ + int key; + short value; +}; + +DEFINE_MULTILIST_PTR_TO_MEMBER_COMPARISONS(KeyAndValue,int,key) + +void MultilistUnitTest(void) +{ + DataStructures::DefaultIndexType oldSize; + DataStructures::Multilist ml1; + ml1.Reallocate(64); + RakAssert(ml1.IsEmpty()); + ml1.Push(53); + RakAssert(ml1.Peek()==53); + RakAssert(ml1.IsEmpty()==false); + RakAssert(ml1.Pop()==53); + RakAssert(ml1.IsEmpty()==true); + for (int i=0; i < 512; i++) + ml1.Push(i); + RakAssert(ml1.GetIndexOf(200)==200); + RakAssert(ml1.PeekOpposite()==0); + RakAssert(ml1.PopOpposite()==0); + RakAssert(ml1.PeekOpposite()==1); + RakAssert(ml1.Peek()==511); + ml1.ReverseList(); + for (int i=0; i < 511; i++) + RakAssert(ml1[i]==511-i); + RakAssert(ml1.PeekOpposite()==511); + RakAssert(ml1.Peek()==1); + oldSize = ml1.GetSize(); + ml1.RemoveAtIndex(0); + RakAssert(ml1.GetSize()==oldSize-1); + RakAssert(ml1.PeekOpposite()==1); + ml1.Clear(__FILE__, __LINE__); + RakAssert(ml1.IsEmpty()==true); + + ml1.Sort(true); + ml1.Clear(__FILE__, __LINE__); + + ml1.Push(100); + ml1.Sort(true); + ml1.Clear(__FILE__, __LINE__); + + ml1.Push(50); + ml1.Push(100); + ml1.Sort(true); + ml1.Clear(__FILE__, __LINE__); + + ml1.Push(100); + ml1.Push(50); + ml1.Sort(true); + ml1.Clear(__FILE__, __LINE__); + + ml1.Push(100); + ml1.Push(50); + ml1.Push(150); + ml1.Push(25); + ml1.Push(175); + ml1.Sort(true); + RakAssert(ml1[0]==25); + RakAssert(ml1[1]==50); + RakAssert(ml1[2]==100); + RakAssert(ml1[3]==150); + RakAssert(ml1[4]==175); + RakAssert(ml1.GetIndexOf(25)==0); + RakAssert(ml1.GetIndexOf(50)==1); + RakAssert(ml1.GetIndexOf(100)==2); + RakAssert(ml1.GetIndexOf(150)==3); + RakAssert(ml1.GetIndexOf(175)==4); + ml1.Clear(__FILE__, __LINE__); + + ml1.Push(1); + ml1.Push(2); + ml1.Push(3); + ml1.Push(4); + ml1.Push(5); + ml1.Sort(true); + RakAssert(ml1[0]==1); + RakAssert(ml1[1]==2); + RakAssert(ml1[2]==3); + RakAssert(ml1[3]==4); + RakAssert(ml1[4]==5); + RakAssert(ml1.GetIndexOf(1)==0); + RakAssert(ml1.GetIndexOf(2)==1); + RakAssert(ml1.GetIndexOf(3)==2); + RakAssert(ml1.GetIndexOf(4)==3); + RakAssert(ml1.GetIndexOf(5)==4); + ml1.Clear(__FILE__, __LINE__); + + ml1.Push(5); + ml1.Push(4); + ml1.Push(3); + ml1.Push(2); + ml1.Push(1); + ml1.Sort(true); + RakAssert(ml1[0]==1); + RakAssert(ml1[1]==2); + RakAssert(ml1[2]==3); + RakAssert(ml1[3]==4); + RakAssert(ml1[4]==5); + RakAssert(ml1.GetIndexOf(1)==0); + RakAssert(ml1.GetIndexOf(2)==1); + RakAssert(ml1.GetIndexOf(3)==2); + RakAssert(ml1.GetIndexOf(4)==3); + RakAssert(ml1.GetIndexOf(5)==4); + ml1.Sort(true); + RakAssert(ml1[0]==1); + RakAssert(ml1[1]==2); + RakAssert(ml1[2]==3); + RakAssert(ml1[3]==4); + RakAssert(ml1[4]==5); + RakAssert(ml1.GetIndexOf(1)==0); + RakAssert(ml1.GetIndexOf(2)==1); + RakAssert(ml1.GetIndexOf(3)==2); + RakAssert(ml1.GetIndexOf(4)==3); + RakAssert(ml1.GetIndexOf(5)==4); + ml1.Clear(__FILE__, __LINE__); + + DataStructures::Multilist ml2; + ml2.Reallocate(64); + RakAssert(ml2.IsEmpty()); + ml2.Push(53); + RakAssert(ml2.Peek()==53); + RakAssert(ml2.IsEmpty()==false); + RakAssert(ml2.Pop()==53); + RakAssert(ml2.IsEmpty()==true); + for (int i=0; i < 512; i++) + ml2.Push(i); + RakAssert(ml2.GetIndexOf(200)==200); + RakAssert(ml2.PeekOpposite()==0); + RakAssert(ml2.PopOpposite()==0); + RakAssert(ml2.PeekOpposite()==1); + RakAssert(ml2.Peek()==511); + ml2.ReverseList(); + for (int i=0; i < 511; i++) + RakAssert(ml2[i]==511-i); + RakAssert(ml2.PeekOpposite()==511); + RakAssert(ml2.Peek()==1); + oldSize = ml2.GetSize(); + ml2.RemoveAtIndex(0); + RakAssert(ml2.GetSize()==oldSize-1); + RakAssert(ml2.Peek()==1); + RakAssert(ml2.PeekOpposite()==510); + ml2.Clear(__FILE__, __LINE__); + RakAssert(ml2.IsEmpty()==true); + + DataStructures::Multilist ml3; + RakAssert(ml3.IsEmpty()); + ml3.Push(53); + RakAssert(ml3.Peek()==53); + RakAssert(ml3.IsEmpty()==false); + RakAssert(ml3.Pop()==53); + RakAssert(ml3.IsEmpty()==true); + for (int i=0; i < 512; i++) + ml3.Push(i); + RakAssert(ml3.GetIndexOf(200)==200); + RakAssert(ml3.PeekOpposite()==511); + RakAssert(ml3.PopOpposite()==511); + RakAssert(ml3.PeekOpposite()==510); + RakAssert(ml3.Peek()==0); + ml3.ReverseList(); + for (int i=0; i < 511; i++) + RakAssert(ml3[i]==511-1-i); + RakAssert(ml3.PeekOpposite()==0); + RakAssert(ml3.Peek()==510); + oldSize = ml3.GetSize(); + ml3.RemoveAtIndex(0); + RakAssert(ml3.GetSize()==oldSize-1); + RakAssert(ml3.Peek()==509); + RakAssert(ml3.PeekOpposite()==0); + ml3.Clear(__FILE__, __LINE__); + RakAssert(ml3.IsEmpty()==true); + + ml3.PushOpposite(100); + ml3.PushOpposite(50); + ml3.PushOpposite(150); + ml3.PushOpposite(25); + ml3.PushOpposite(175); + ml3.Sort(true); + RakAssert(ml3[0]==25); + RakAssert(ml3[1]==50); + RakAssert(ml3[2]==100); + RakAssert(ml3[3]==150); + RakAssert(ml3[4]==175); + RakAssert(ml3.GetIndexOf(25)==0); + RakAssert(ml3.GetIndexOf(50)==1); + RakAssert(ml3.GetIndexOf(100)==2); + RakAssert(ml3.GetIndexOf(150)==3); + RakAssert(ml3.GetIndexOf(175)==4); + ml3.Clear(__FILE__, __LINE__); + + ml3.PushOpposite(1); + ml3.PushOpposite(2); + ml3.PushOpposite(3); + ml3.PushOpposite(4); + ml3.PushOpposite(5); + ml3.Sort(true); + RakAssert(ml3[0]==1); + RakAssert(ml3[1]==2); + RakAssert(ml3[2]==3); + RakAssert(ml3[3]==4); + RakAssert(ml3[4]==5); + RakAssert(ml3.GetIndexOf(1)==0); + RakAssert(ml3.GetIndexOf(2)==1); + RakAssert(ml3.GetIndexOf(3)==2); + RakAssert(ml3.GetIndexOf(4)==3); + RakAssert(ml3.GetIndexOf(5)==4); + ml3.Clear(__FILE__, __LINE__); + + ml3.PushOpposite(5); + ml3.PushOpposite(4); + ml3.PushOpposite(3); + ml3.PushOpposite(2); + ml3.PushOpposite(1); + ml3.Sort(true); + RakAssert(ml3[0]==1); + RakAssert(ml3[1]==2); + RakAssert(ml3[2]==3); + RakAssert(ml3[3]==4); + RakAssert(ml3[4]==5); + RakAssert(ml3.GetIndexOf(1)==0); + RakAssert(ml3.GetIndexOf(2)==1); + RakAssert(ml3.GetIndexOf(3)==2); + RakAssert(ml3.GetIndexOf(4)==3); + RakAssert(ml3.GetIndexOf(5)==4); + ml3.Sort(true); + RakAssert(ml3[0]==1); + RakAssert(ml3[1]==2); + RakAssert(ml3[2]==3); + RakAssert(ml3[3]==4); + RakAssert(ml3[4]==5); + RakAssert(ml3.GetIndexOf(1)==0); + RakAssert(ml3.GetIndexOf(2)==1); + RakAssert(ml3.GetIndexOf(3)==2); + RakAssert(ml3.GetIndexOf(4)==3); + RakAssert(ml3.GetIndexOf(5)==4); + + ml3.SetSortOrder(false); + ml3.Sort(false); + RakAssert(ml3[0]==5); + RakAssert(ml3[1]==4); + RakAssert(ml3[2]==3); + RakAssert(ml3[3]==2); + RakAssert(ml3[4]==1); + RakAssert(ml3.GetIndexOf(1)==4); + RakAssert(ml3.GetIndexOf(2)==3); + RakAssert(ml3.GetIndexOf(3)==2); + RakAssert(ml3.GetIndexOf(4)==1); + RakAssert(ml3.GetIndexOf(5)==0); + + ml3.Clear(__FILE__, __LINE__); + + DataStructures::Multilist ml4; + ml4.Reallocate(64); + RakAssert(ml4.IsEmpty()); + ml4.Push(53); + RakAssert(ml4.Peek()==53); + RakAssert(ml4.IsEmpty()==false); + RakAssert(ml4.Pop()==53); + RakAssert(ml4.IsEmpty()==true); + for (int i=0; i < 512; i++) + ml4.Push(i); + RakAssert(ml4.GetIndexOf(200)==200); + RakAssert(ml4.PeekOpposite()==0); + RakAssert(ml4.PopOpposite()==0); + RakAssert(ml4.PeekOpposite()==1); + RakAssert(ml4.Peek()==511); + ml4.ReverseList(); + for (int i=0; i < 511; i++) + RakAssert(ml4[i]==511-i); + RakAssert(ml4.PeekOpposite()==511); + RakAssert(ml4.Peek()==1); + oldSize = ml4.GetSize(); + ml4.RemoveAtIndex(0); + RakAssert(ml4.GetSize()==oldSize-1); + RakAssert(ml4.Peek()==1); + RakAssert(ml4.PeekOpposite()==510); + ml4.Clear(__FILE__, __LINE__); + RakAssert(ml4.IsEmpty()==true); + + DataStructures::Multilist ml5; + + for (int i=0; i < 16; i++) + { + KeyAndValue *kav = new KeyAndValue; + kav->key=i; + kav->value=i+100; + ml5.Push(kav,kav->key); + } + + RakAssert(ml5.GetIndexOf(0)==0); + RakAssert(ml5.GetIndexOf(5)==5); + RakAssert(ml5.GetIndexOf(15)==15); + RakAssert(ml5.GetIndexOf(16)==-1); + ml5.RemoveAtKey(0,true); + RakAssert(ml5.GetIndexOf(1)==0); + KeyAndValue *iPtr = ml5.GetPtr(5); + RakAssert(iPtr); + RakAssert(iPtr->value=105); + iPtr = ml5.GetPtr(1234); + RakAssert(iPtr==0); + ml5.ForEach(DataStructures::DeletePtr); + + + DataStructures::Multilist ml6; + ml6.Push(2); + ml6.Push(1); + ml6.Push(6); + ml6.Push(3); + RakAssert(ml6.Peek()==3); + ml6.SetMultilistType(ML_STACK); + RakAssert(ml6.Peek()==3); + ml6.SetMultilistType(ML_QUEUE); + RakAssert(ml6.Peek()==2); + ml6.SetMultilistType(ML_ORDERED_LIST); + RakAssert(ml6.Peek()=6); + ml6.SetMultilistType(ML_STACK); + RakAssert(ml6.Peek()==6); + ml6.SetMultilistType(ML_QUEUE); + RakAssert(ml6.Peek()==1); +} + +#ifdef _MSC_VER +#pragma warning( pop ) +#endif +*/ + +#endif diff --git a/RakNet/Sources/DS_OrderedChannelHeap.h b/RakNet/Sources/DS_OrderedChannelHeap.h new file mode 100644 index 0000000..798f567 --- /dev/null +++ b/RakNet/Sources/DS_OrderedChannelHeap.h @@ -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.h" +#include "DS_Map.h" +#include "DS_Queue.h" +#include "Export.h" +#include "RakAssert.h" +#include "Rand.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 RAK_DLL_EXPORT OrderedChannelHeap + { + public: + static void IMPLEMENT_DEFAULT_COMPARISON(void) {DataStructures::defaultMapKeyComparison(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 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 map; + DataStructures::Heap heap; + void GreatestRandResult(void); + }; + + template + OrderedChannelHeap::OrderedChannelHeap() + { + } + + template + OrderedChannelHeap::~OrderedChannelHeap() + { + Clear(); + } + + template + void OrderedChannelHeap::Push(const channel_key_type &channelID, const heap_data_type &data) + { + PushAtHead(MAX_UNSIGNED_LONG, channelID, data); + } + + template + void OrderedChannelHeap::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 + void OrderedChannelHeap::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 + heap_data_type OrderedChannelHeap::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 + heap_data_type OrderedChannelHeap::Peek(const unsigned startingIndex) const + { + HeapChannelAndData heapChannelAndData = heap.Peek(startingIndex); + return heapChannelAndData.data; + } + + template + void OrderedChannelHeap::AddChannel(const channel_key_type &channelID, const double weight) + { + QueueAndWeight *qaw = RakNet::OP_NEW( __FILE__, __LINE__ ); + qaw->weight=weight; + qaw->signalDeletion=false; + map.SetNew(channelID, qaw); + } + + template + void OrderedChannelHeap::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__, __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 + unsigned OrderedChannelHeap::Size(void) const + { + return heap.Size(); + } + + template + heap_data_type& OrderedChannelHeap::operator[]( const unsigned int position ) const + { + return heap[position].data; + } + + + template + unsigned OrderedChannelHeap::ChannelSize(const channel_key_type &channelID) + { + QueueAndWeight *queueAndWeight=map.Get(channelID); + return queueAndWeight->randResultQueue.Size(); + } + + template + void OrderedChannelHeap::Clear(void) + { + unsigned i; + for (i=0; i < map.Size(); i++) + RakNet::OP_DELETE(map[i], __FILE__, __LINE__); + map.Clear(__FILE__, __LINE__); + heap.Clear(__FILE__, __LINE__); + } +} + +#endif diff --git a/RakNet/Sources/DS_OrderedList.h b/RakNet/Sources/DS_OrderedList.h new file mode 100644 index 0000000..9b66bd6 --- /dev/null +++ b/RakNet/Sources/DS_OrderedList.h @@ -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.h" +#include "RakMemoryOverride.h" +#include "Export.h" + +#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 + int defaultOrderedListComparison(const key_type &a, const data_type &b) + { + if (a > + class RAK_DLL_EXPORT OrderedList + { + public: + static void IMPLEMENT_DEFAULT_COMPARISON(void) {DataStructures::defaultOrderedListComparison(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 orderedList; + }; + + template + OrderedList::OrderedList() + { + } + + template + OrderedList::~OrderedList() + { + Clear(false, __FILE__, __LINE__); + } + + template + OrderedList::OrderedList( const OrderedList& original_copy ) + { + orderedList=original_copy.orderedList; + } + + template + OrderedList& OrderedList::operator= ( const OrderedList& original_copy ) + { + orderedList=original_copy.orderedList; + return *this; + } + + template + bool OrderedList::HasData(const key_type &key, int (*cf)(const key_type&, const data_type&)) const + { + bool objectExists; + GetIndexFromKey(key, &objectExists, cf); + return objectExists; + } + + template + data_type OrderedList::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 + bool OrderedList::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 + unsigned OrderedList::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 + unsigned OrderedList::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 + unsigned OrderedList::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 + unsigned OrderedList::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 + void OrderedList::RemoveAtIndex(const unsigned index) + { + orderedList.RemoveAtIndex(index); + } + + template + void OrderedList::InsertAtIndex(const data_type &data, const unsigned index, const char *file, unsigned int line) + { + orderedList.Insert(data, index, file, line); + } + + template + void OrderedList::InsertAtEnd(const data_type &data, const char *file, unsigned int line) + { + orderedList.Insert(data, file, line); + } + + template + void OrderedList::RemoveFromEnd(const unsigned num) + { + orderedList.RemoveFromEnd(num); + } + + template + void OrderedList::Clear(bool doNotDeallocate, const char *file, unsigned int line) + { + orderedList.Clear(doNotDeallocate, file, line); + } + + template + data_type& OrderedList::operator[]( const unsigned int position ) const + { + return orderedList[position]; + } + + template + unsigned OrderedList::Size(void) const + { + return orderedList.Size(); + } +} + +#endif diff --git a/RakNet/Sources/DS_Queue.h b/RakNet/Sources/DS_Queue.h new file mode 100644 index 0000000..1b772ab --- /dev/null +++ b/RakNet/Sources/DS_Queue.h @@ -0,0 +1,433 @@ +/// \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.h" +#include "Export.h" +#include "RakMemoryOverride.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 +{ + /// \brief A queue implemented as an array with a read and write index. + template + 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 + inline unsigned int Queue::Size( void ) const + { + if ( head <= tail ) + return tail -head; + else + return allocation_size -head + tail; + } + + template + inline bool Queue::IsEmpty(void) const + { + return head==tail; + } + + template + inline unsigned int Queue::AllocationSize( void ) const + { + return allocation_size; + } + + template + Queue::Queue() + { + //allocation_size = 16; + //array = RakNet::OP_NEW_ARRAY(allocation_size, __FILE__, __LINE__ ); + allocation_size = 0; + array=0; + head = 0; + tail = 0; + } + + template + Queue::~Queue() + { + if (allocation_size>0) + RakNet::OP_DELETE_ARRAY(array, __FILE__, __LINE__); + } + + template + inline queue_type Queue::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 + inline queue_type Queue::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 + void Queue::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 + inline queue_type Queue::Peek( void ) const + { +#ifdef _DEBUG + RakAssert( head != tail ); +#endif + + return ( queue_type ) array[ head ]; + } + + template + inline queue_type Queue::PeekTail( void ) const + { +#ifdef _DEBUG + RakAssert( head != tail ); + RakAssert( Size() >= 0 ); +#endif + if (tail!=0) + return ( queue_type ) array[ tail-1 ]; + else + return ( queue_type ) array[ allocation_size-1 ]; + } + + template + void Queue::Push( const queue_type& input, const char *file, unsigned int line ) + { + if ( allocation_size == 0 ) + { + array = RakNet::OP_NEW_ARRAY(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(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 + Queue::Queue( Queue& original_copy ) + { + // Allocate memory for copy + + if ( original_copy.Size() == 0 ) + { + allocation_size = 0; + } + + else + { + array = RakNet::OP_NEW_ARRAY( original_copy.Size() + 1 , __FILE__, __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 + bool Queue::operator= ( const Queue& original_copy ) + { + if ( ( &original_copy ) == this ) + return false; + + Clear(__FILE__, __LINE__); + + // Allocate memory for copy + if ( original_copy.Size() == 0 ) + { + allocation_size = 0; + } + + else + { + array = RakNet::OP_NEW_ARRAY( original_copy.Size() + 1 , __FILE__, __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 + inline void Queue::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 + void Queue::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(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 + bool Queue::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 + void Queue::ClearAndForceAllocation( int size, const char *file, unsigned int line ) + { + RakNet::OP_DELETE_ARRAY(array, file, line); + array = RakNet::OP_NEW_ARRAY(size, file, line ); + allocation_size = size; + head = 0; + tail = 0; + } + + template + inline queue_type& Queue::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 + void Queue::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 + diff --git a/RakNet/Sources/DS_QueueLinkedList.h b/RakNet/Sources/DS_QueueLinkedList.h new file mode 100644 index 0000000..988ec41 --- /dev/null +++ b/RakNet/Sources/DS_QueueLinkedList.h @@ -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.h" +#include "Export.h" +#include "RakMemoryOverride.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 +{ + /// \brief A queue implemented using a linked list. Rarely used. + template + 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 data; + }; + + template + QueueLinkedList::QueueLinkedList() + { + } + + template + inline unsigned int QueueLinkedList::Size() + { + return data.Size(); + } + + template + inline QueueType QueueLinkedList::Pop( void ) + { + data.Beginning(); + return ( QueueType ) data.Pop(); + } + + template + inline QueueType& QueueLinkedList::Peek( void ) + { + data.Beginning(); + return ( QueueType ) data.Peek(); + } + + template + inline QueueType& QueueLinkedList::EndPeek( void ) + { + data.End(); + return ( QueueType ) data.Peek(); + } + + template + void QueueLinkedList::Push( const QueueType& input ) + { + data.End(); + data.Add( input ); + } + + template + QueueLinkedList::QueueLinkedList( const QueueLinkedList& original_copy ) + { + data = original_copy.data; + } + + template + bool QueueLinkedList::operator= ( const QueueLinkedList& original_copy ) + { + if ( ( &original_copy ) == this ) + return false; + + data = original_copy.data; + } + + template + void QueueLinkedList::Clear ( void ) + { + data.Clear(__FILE__, __LINE__); + } +} // End namespace + +#endif diff --git a/RakNet/Sources/DS_RangeList.h b/RakNet/Sources/DS_RangeList.h new file mode 100644 index 0000000..c99a65e --- /dev/null +++ b/RakNet/Sources/DS_RangeList.h @@ -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.h" +#include "BitStream.h" +#include "RakMemoryOverride.h" +#include "RakAssert.h" + +namespace DataStructures +{ + template + struct RangeNode + { + RangeNode() {} + ~RangeNode() {} + RangeNode(range_type min, range_type max) {minIndex=min; maxIndex=max;} + range_type minIndex; + range_type maxIndex; + }; + + + template + int RangeNodeComp(const range_type &a, const RangeNode &b) + { + if (a + class RAK_DLL_EXPORT RangeList + { + public: + RangeList(); + ~RangeList(); + void Insert(range_type index); + void Clear(void); + unsigned Size(void) const; + unsigned RangeSum(void) const; + BitSize_t Serialize(RakNet::BitStream *in, BitSize_t maxBits, bool clearSerialized); + bool Deserialize(RakNet::BitStream *out); + + DataStructures::OrderedList , RangeNodeComp > ranges; + }; + + template + BitSize_t RangeList::Serialize(RakNet::BitStream *in, BitSize_t maxBits, bool clearSerialized) + { + RakAssert(ranges.Size() < (unsigned short)-1); + RakNet::BitStream tempBS; + 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(); + 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 + bool RangeList::Deserialize(RakNet::BitStream *out) + { + ranges.Clear(true, __FILE__, __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,max), __FILE__,__LINE__); + } + return true; + } + + template + RangeList::RangeList() + { + RangeNodeComp(0, RangeNode()); + } + + template + RangeList::~RangeList() + { + Clear(); + } + + template + void RangeList::Insert(range_type index) + { + if (ranges.Size()==0) + { + ranges.Insert(index, RangeNode(index, index), true, __FILE__,__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(index, index), true, __FILE__,__LINE__); + } + + return; + } + + if (index < ranges[insertionIndex].minIndex-(range_type)1) + { + // Insert here + ranges.InsertAtIndex(RangeNode(index, index), insertionIndex, __FILE__,__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 + void RangeList::Clear(void) + { + ranges.Clear(true, __FILE__, __LINE__); + } + + template + unsigned RangeList::Size(void) const + { + return ranges.Size(); + } + + template + unsigned RangeList::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 diff --git a/RakNet/Sources/DS_StringKeyedHash.h b/RakNet/Sources/DS_StringKeyedHash.h new file mode 100644 index 0000000..c59943a --- /dev/null +++ b/RakNet/Sources/DS_StringKeyedHash.h @@ -0,0 +1,319 @@ +/// \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.h" +#include // memmove +#include "Export.h" +#include "RakMemoryOverride.h" +#include "RakString.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 +{ + struct StringKeyedHashIndex + { + unsigned int primaryIndex; + unsigned int secondaryIndex; + bool IsInvalid(void) const {return primaryIndex==(unsigned int) -1;} + void SetInvalid(void) {primaryIndex=(unsigned int) -1;} + }; + + /// \brief Using a string as a identifier for a node, store an allocated pointer to that node + template + class RAK_DLL_EXPORT StringKeyedHash + { + public: + /// Default constructor + StringKeyedHash(); + + // Destructor + ~StringKeyedHash(); + + void Push(const char *key, const data_type &input, const char *file, unsigned int line ); + data_type* Peek(const char *key ); + bool Pop(data_type& out, const char *key, const char *file, unsigned int line ); + bool RemoveAtIndex(StringKeyedHashIndex index, const char *file, unsigned int line ); + StringKeyedHashIndex GetIndexOf(const char *key); + data_type& ItemAtIndex(const StringKeyedHashIndex &index); + RakNet::RakString KeyAtIndex(const StringKeyedHashIndex &index); + void GetItemList(DataStructures::List &outputList,const char *file, unsigned int line); + unsigned int GetHashIndex(const char *str); + + /// \brief Clear the list + void Clear( const char *file, unsigned int line ); + + struct Node + { + Node(const char *strIn, const data_type &_data) {string=strIn; data=_data;} + RakNet::RakString 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; + }; + + template + StringKeyedHash::StringKeyedHash() + { + nodeList=0; + } + + template + StringKeyedHash::~StringKeyedHash() + { + Clear(__FILE__,__LINE__); + } + + template + void StringKeyedHash::Push(const char *key, const data_type &input, const char *file, unsigned int line ) + { + unsigned long hashIndex = GetHashIndex(key); + if (nodeList==0) + { + nodeList=RakNet::OP_NEW_ARRAY(HASH_SIZE,file,line); + memset(nodeList,0,sizeof(Node *)*HASH_SIZE); + } + + Node *newNode=RakNet::OP_NEW_2(file,line,key,input); + newNode->next=nodeList[hashIndex]; + nodeList[hashIndex]=newNode; + } + + template + data_type* StringKeyedHash::Peek(const char *key ) + { + unsigned long hashIndex = GetHashIndex(key); + Node *node = nodeList[hashIndex]; + while (node!=0) + { + if (node->string==key) + return &node->data; + node=node->next; + } + return 0; + } + + template + bool StringKeyedHash::Pop(data_type& out, const char *key, const char *file, unsigned int line ) + { + unsigned long hashIndex = GetHashIndex(key); + 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__,__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); + 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); + return true; + } + last=node; + node=node->next; + } + return false; + } + + template + bool StringKeyedHash::RemoveAtIndex(StringKeyedHashIndex 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); + 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); + 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); + return true; + } + + template + StringKeyedHashIndex StringKeyedHash::GetIndexOf(const char *key) + { + if (nodeList==0) + { + StringKeyedHashIndex temp; + temp.SetInvalid(); + return temp; + } + StringKeyedHashIndex idx; + idx.primaryIndex=GetHashIndex(key); + 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 + data_type& StringKeyedHash::ItemAtIndex(const StringKeyedHashIndex &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 + RakNet::RakString StringKeyedHash::KeyAtIndex(const StringKeyedHashIndex &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 + void StringKeyedHash::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; + } + } + + template + void StringKeyedHash::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; + } + nodeList[index]=0; + } + + template + void StringKeyedHash::GetItemList(DataStructures::List &outputList,const char *file, unsigned int line) + { + if (nodeList==0) + return; + outputList.Clear(false,__FILE__,__LINE__); + + Node *node; + unsigned int i; + for (i=0; i < HASH_SIZE; i++) + { + if (nodeList[i]) + { + node=nodeList[i]; + while (node) + { + outputList.Push(node->data,file,line); + node=node->next; + } + } + } + } + + template + unsigned int StringKeyedHash::GetHashIndex(const char *str) + { + return RakNet::RakString::ToInteger(str) % HASH_SIZE; + } +} +#endif diff --git a/RakNet/Sources/DS_Table.cpp b/RakNet/Sources/DS_Table.cpp new file mode 100644 index 0000000..dcc5760 --- /dev/null +++ b/RakNet/Sources/DS_Table.cpp @@ -0,0 +1,1118 @@ +#include "DS_Table.h" +#include "DS_OrderedList.h" +#include +#include "RakAssert.h" +#include "RakAssert.h" +#include "Itoa.h" + +using namespace DataStructures; + +#ifdef _MSC_VER +#pragma warning( push ) +#endif + +void ExtendRows(Table::Row* input, int index) +{ + (void) index; + input->cells.Insert(RakNet::OP_NEW(__FILE__, __LINE__), __FILE__, __LINE__ ); +} +void FreeRow(Table::Row* input, int index) +{ + (void) index; + + unsigned i; + for (i=0; i < input->cells.Size(); i++) + { + RakNet::OP_DELETE(input->cells[i], __FILE__, __LINE__); + } + RakNet::OP_DELETE(input, __FILE__, __LINE__); +} +Table::Cell::Cell() +{ + isEmpty=true; + c=0; + ptr=0; + i=0.0; +} +Table::Cell::~Cell() +{ + Clear(); +} +Table::Cell& Table::Cell::operator = ( const Table::Cell& input ) +{ + isEmpty=input.isEmpty; + i=input.i; + ptr=input.ptr; + if (c) + rakFree_Ex(c, __FILE__, __LINE__); + if (input.c) + { + c = (char*) rakMalloc_Ex( (int) i, __FILE__, __LINE__ ); + memcpy(c, input.c, (int) i); + } + else + c=0; + return *this; +} +Table::Cell::Cell( const Table::Cell & input) +{ + isEmpty=input.isEmpty; + i=input.i; + ptr=input.ptr; + if (input.c) + { + if (c) + rakFree_Ex(c, __FILE__, __LINE__); + c = (char*) rakMalloc_Ex( (int) i, __FILE__, __LINE__ ); + memcpy(c, input.c, (int) i); + } +} +void Table::Cell::Set(double input) +{ + Clear(); + i=input; + c=0; + ptr=0; + isEmpty=false; +} +void Table::Cell::Set(unsigned int input) +{ + Set((int) input); +} +void Table::Cell::Set(int input) +{ + Clear(); + i=(double) input; + c=0; + ptr=0; + isEmpty=false; +} + +void Table::Cell::Set(const char *input) +{ + Clear(); + + if (input) + { + i=(int)strlen(input)+1; + c = (char*) rakMalloc_Ex( (int) i, __FILE__, __LINE__ ); + strcpy(c, input); + } + else + { + c=0; + i=0; + } + ptr=0; + isEmpty=false; +} +void Table::Cell::Set(const char *input, int inputLength) +{ + Clear(); + if (input) + { + c = (char*) rakMalloc_Ex( inputLength, __FILE__, __LINE__ ); + i=inputLength; + memcpy(c, input, inputLength); + } + else + { + c=0; + i=0; + } + ptr=0; + isEmpty=false; +} +void Table::Cell::SetPtr(void* p) +{ + Clear(); + c=0; + ptr=p; + isEmpty=false; +} +void Table::Cell::Get(int *output) +{ + RakAssert(isEmpty==false); + int o = (int) i; + *output=o; +} +void Table::Cell::Get(double *output) +{ + RakAssert(isEmpty==false); + *output=i; +} +void Table::Cell::Get(char *output) +{ + RakAssert(isEmpty==false); + strcpy(output, c); +} +void Table::Cell::Get(char *output, int *outputLength) +{ + RakAssert(isEmpty==false); + memcpy(output, c, (int) i); + if (outputLength) + *outputLength=(int) i; +} +RakNet::RakString Table::Cell::ToString(ColumnType columnType) +{ + if (isEmpty) + return RakNet::RakString(); + + if (columnType==NUMERIC) + { + return RakNet::RakString("%f", i); + } + else if (columnType==STRING) + { + return RakNet::RakString(c); + } + else if (columnType==BINARY) + { + return RakNet::RakString(""); + } + else if (columnType==POINTER) + { + return RakNet::RakString("%p", ptr); + } + + return RakNet::RakString(); +} +Table::Cell::Cell(double numericValue, char *charValue, void *ptr, ColumnType type) +{ + SetByType(numericValue,charValue,ptr,type); +} +void Table::Cell::SetByType(double numericValue, char *charValue, void *ptr, ColumnType type) +{ + isEmpty=true; + if (type==NUMERIC) + { + Set(numericValue); + } + else if (type==STRING) + { + Set(charValue); + } + else if (type==BINARY) + { + Set(charValue, (int) numericValue); + } + else if (type==POINTER) + { + SetPtr(ptr); + } + else + { + ptr=(void*) charValue; + } +} +Table::ColumnType Table::Cell::EstimateColumnType(void) const +{ + if (c) + if (i!=0.0f) + return BINARY; + else + return STRING; + if (ptr) + return POINTER; + return NUMERIC; +} +void Table::Cell::Clear(void) +{ + if (isEmpty==false && c) + { + rakFree_Ex(c, __FILE__, __LINE__); + c=0; + } + isEmpty=true; +} +Table::ColumnDescriptor::ColumnDescriptor() +{ + +} +Table::ColumnDescriptor::~ColumnDescriptor() +{ + +} +Table::ColumnDescriptor::ColumnDescriptor(const char cn[_TABLE_MAX_COLUMN_NAME_LENGTH], ColumnType ct) +{ + columnType=ct; + strcpy(columnName, cn); +} +void Table::Row::UpdateCell(unsigned columnIndex, double value) +{ + cells[columnIndex]->Clear(); + cells[columnIndex]->Set(value); + +// cells[columnIndex]->i=value; +// cells[columnIndex]->c=0; +// cells[columnIndex]->isEmpty=false; +} +void Table::Row::UpdateCell(unsigned columnIndex, const char *str) +{ + cells[columnIndex]->Clear(); + cells[columnIndex]->Set(str); +} +void Table::Row::UpdateCell(unsigned columnIndex, int byteLength, const char *data) +{ + cells[columnIndex]->Clear(); + cells[columnIndex]->Set(data,byteLength); +} +Table::Table() +{ +} +Table::~Table() +{ + Clear(); +} +unsigned Table::AddColumn(const char columnName[_TABLE_MAX_COLUMN_NAME_LENGTH], ColumnType columnType) +{ + if (columnName[0]==0) + return (unsigned) -1; + + // Add this column. + columns.Insert(Table::ColumnDescriptor(columnName, columnType), __FILE__, __LINE__); + + // Extend the rows by one + rows.ForEachData(ExtendRows); + + return columns.Size()-1; +} +void Table::RemoveColumn(unsigned columnIndex) +{ + if (columnIndex >= columns.Size()) + return; + + columns.RemoveAtIndex(columnIndex); + + // Remove this index from each row. + int i; + DataStructures::Page *cur = rows.GetListHead(); + while (cur) + { + for (i=0; i < cur->size; i++) + { + RakNet::OP_DELETE(cur->data[i]->cells[columnIndex], __FILE__, __LINE__); + cur->data[i]->cells.RemoveAtIndex(columnIndex); + } + + cur=cur->next; + } +} +unsigned Table::ColumnIndex(const char *columnName) const +{ + unsigned columnIndex; + for (columnIndex=0; columnIndex= columns.Size()) + return 0; + else + return (char*)columns[index].columnName; +} +Table::ColumnType Table::GetColumnType(unsigned index) const +{ + if (index >= columns.Size()) + return (Table::ColumnType) 0; + else + return columns[index].columnType; +} +unsigned Table::GetColumnCount(void) const +{ + return columns.Size(); +} +unsigned Table::GetRowCount(void) const +{ + return rows.Size(); +} +Table::Row* Table::AddRow(unsigned rowId) +{ + Row *newRow; + newRow = RakNet::OP_NEW( __FILE__, __LINE__ ); + if (rows.Insert(rowId, newRow)==false) + { + RakNet::OP_DELETE(newRow, __FILE__, __LINE__); + return 0; // Already exists + } + unsigned rowIndex; + for (rowIndex=0; rowIndex < columns.Size(); rowIndex++) + newRow->cells.Insert( RakNet::OP_NEW(__FILE__, __LINE__), __FILE__, __LINE__ ); + return newRow; +} +Table::Row* Table::AddRow(unsigned rowId, DataStructures::List &initialCellValues) +{ + Row *newRow = RakNet::OP_NEW( __FILE__, __LINE__ ); + unsigned rowIndex; + for (rowIndex=0; rowIndex < columns.Size(); rowIndex++) + { + if (rowIndex < initialCellValues.Size() && initialCellValues[rowIndex].isEmpty==false) + { + Table::Cell *c; + c = RakNet::OP_NEW(__FILE__, __LINE__); + c->SetByType(initialCellValues[rowIndex].i,initialCellValues[rowIndex].c,initialCellValues[rowIndex].ptr,columns[rowIndex].columnType); + newRow->cells.Insert(c, __FILE__, __LINE__ ); + } + else + newRow->cells.Insert(RakNet::OP_NEW(__FILE__, __LINE__), __FILE__, __LINE__ ); + } + rows.Insert(rowId, newRow); + return newRow; +} +Table::Row* Table::AddRow(unsigned rowId, DataStructures::List &initialCellValues, bool copyCells) +{ + Row *newRow = RakNet::OP_NEW( __FILE__, __LINE__ ); + unsigned rowIndex; + for (rowIndex=0; rowIndex < columns.Size(); rowIndex++) + { + if (rowIndex < initialCellValues.Size() && initialCellValues[rowIndex] && initialCellValues[rowIndex]->isEmpty==false) + { + if (copyCells==false) + newRow->cells.Insert(RakNet::OP_NEW_4( __FILE__, __LINE__, initialCellValues[rowIndex]->i, initialCellValues[rowIndex]->c, initialCellValues[rowIndex]->ptr, columns[rowIndex].columnType), __FILE__, __LINE__); + else + { + Table::Cell *c = RakNet::OP_NEW( __FILE__, __LINE__ ); + newRow->cells.Insert(c, __FILE__, __LINE__); + *c=*(initialCellValues[rowIndex]); + } + } + else + newRow->cells.Insert(RakNet::OP_NEW(__FILE__, __LINE__), __FILE__, __LINE__); + } + rows.Insert(rowId, newRow); + return newRow; +} +Table::Row* Table::AddRowColumns(unsigned rowId, Row *row, DataStructures::List columnIndices) +{ + Row *newRow = RakNet::OP_NEW( __FILE__, __LINE__ ); + unsigned columnIndex; + for (columnIndex=0; columnIndex < columnIndices.Size(); columnIndex++) + { + if (row->cells[columnIndices[columnIndex]]->isEmpty==false) + { + newRow->cells.Insert(RakNet::OP_NEW_4( __FILE__, __LINE__, + row->cells[columnIndices[columnIndex]]->i, + row->cells[columnIndices[columnIndex]]->c, + row->cells[columnIndices[columnIndex]]->ptr, + columns[columnIndex].columnType + ), __FILE__, __LINE__); + } + else + { + newRow->cells.Insert(RakNet::OP_NEW(__FILE__, __LINE__), __FILE__, __LINE__); + } + } + rows.Insert(rowId, newRow); + return newRow; +} +bool Table::RemoveRow(unsigned rowId) +{ + Row *out; + if (rows.Delete(rowId, out)) + { + DeleteRow(out); + return true; + } + return false; +} +void Table::RemoveRows(Table *tableContainingRowIDs) +{ + unsigned i; + DataStructures::Page *cur = tableContainingRowIDs->GetRows().GetListHead(); + while (cur) + { + for (i=0; i < (unsigned)cur->size; i++) + { + rows.Delete(cur->keys[i]); + } + cur=cur->next; + } + return; +} +bool Table::UpdateCell(unsigned rowId, unsigned columnIndex, int value) +{ + RakAssert(columns[columnIndex].columnType==NUMERIC); + + Row *row = GetRowByID(rowId); + if (row) + { + row->UpdateCell(columnIndex, value); + return true; + } + return false; +} +bool Table::UpdateCell(unsigned rowId, unsigned columnIndex, char *str) +{ + RakAssert(columns[columnIndex].columnType==STRING); + + Row *row = GetRowByID(rowId); + if (row) + { + row->UpdateCell(columnIndex, str); + return true; + } + return false; +} +bool Table::UpdateCell(unsigned rowId, unsigned columnIndex, int byteLength, char *data) +{ + RakAssert(columns[columnIndex].columnType==BINARY); + + Row *row = GetRowByID(rowId); + if (row) + { + row->UpdateCell(columnIndex, byteLength, data); + return true; + } + return false; +} +bool Table::UpdateCellByIndex(unsigned rowIndex, unsigned columnIndex, int value) +{ + RakAssert(columns[columnIndex].columnType==NUMERIC); + + Row *row = GetRowByIndex(rowIndex,0); + if (row) + { + row->UpdateCell(columnIndex, value); + return true; + } + return false; +} +bool Table::UpdateCellByIndex(unsigned rowIndex, unsigned columnIndex, char *str) +{ + RakAssert(columns[columnIndex].columnType==STRING); + + Row *row = GetRowByIndex(rowIndex,0); + if (row) + { + row->UpdateCell(columnIndex, str); + return true; + } + return false; +} +bool Table::UpdateCellByIndex(unsigned rowIndex, unsigned columnIndex, int byteLength, char *data) +{ + RakAssert(columns[columnIndex].columnType==BINARY); + + Row *row = GetRowByIndex(rowIndex,0); + if (row) + { + row->UpdateCell(columnIndex, byteLength, data); + return true; + } + return false; +} +void Table::GetCellValueByIndex(unsigned rowIndex, unsigned columnIndex, int *output) +{ + RakAssert(columns[columnIndex].columnType==NUMERIC); + + Row *row = GetRowByIndex(rowIndex,0); + if (row) + { + row->cells[columnIndex]->Get(output); + } +} +void Table::GetCellValueByIndex(unsigned rowIndex, unsigned columnIndex, char *output) +{ + RakAssert(columns[columnIndex].columnType==STRING); + + Row *row = GetRowByIndex(rowIndex,0); + if (row) + { + row->cells[columnIndex]->Get(output); + } +} +void Table::GetCellValueByIndex(unsigned rowIndex, unsigned columnIndex, char *output, int *outputLength) +{ + RakAssert(columns[columnIndex].columnType==BINARY); + + Row *row = GetRowByIndex(rowIndex,0); + if (row) + { + row->cells[columnIndex]->Get(output, outputLength); + } +} +Table::FilterQuery::FilterQuery() +{ + columnName[0]=0; +} +Table::FilterQuery::~FilterQuery() +{ + +} +Table::FilterQuery::FilterQuery(unsigned column, Cell *cell, FilterQueryType op) +{ + columnIndex=column; + cellValue=cell; + operation=op; +} +Table::Row* Table::GetRowByID(unsigned rowId) const +{ + Row *row; + if (rows.Get(rowId, row)) + return row; + return 0; +} + +Table::Row* Table::GetRowByIndex(unsigned rowIndex, unsigned *key) const +{ + DataStructures::Page *cur = rows.GetListHead(); + while (cur) + { + if (rowIndex < (unsigned)cur->size) + { + if (key) + *key=cur->keys[rowIndex]; + return cur->data[rowIndex]; + } + if (rowIndex <= (unsigned)cur->size) + rowIndex-=cur->size; + else + return 0; + cur=cur->next; + } + return 0; +} + +void Table::QueryTable(unsigned *columnIndicesSubset, unsigned numColumnSubset, FilterQuery *inclusionFilters, unsigned numInclusionFilters, unsigned *rowIds, unsigned numRowIDs, Table *result) +{ + unsigned i; + DataStructures::List columnIndicesToReturn; + + // Clear the result table. + result->Clear(); + + if (columnIndicesSubset && numColumnSubset>0) + { + for (i=0; i < numColumnSubset; i++) + { + if (columnIndicesSubset[i]AddColumn(columns[columnIndicesToReturn[i]].columnName,columns[columnIndicesToReturn[i]].columnType); + } + + // Get the column indices of the filter queries. + DataStructures::List inclusionFilterColumnIndices; + if (inclusionFilters && numInclusionFilters>0) + { + for (i=0; i < numInclusionFilters; i++) + { + if (inclusionFilters[i].columnName[0]) + inclusionFilters[i].columnIndex=ColumnIndex(inclusionFilters[i].columnName); + if (inclusionFilters[i].columnIndex *cur = rows.GetListHead(); + while (cur) + { + for (i=0; i < (unsigned)cur->size; i++) + { + QueryRow(inclusionFilterColumnIndices, columnIndicesToReturn, cur->keys[i], cur->data[i], inclusionFilters, result); + } + cur=cur->next; + } + } + else + { + // Specific rows + Row *row; + for (i=0; i < numRowIDs; i++) + { + if (rows.Get(rowIds[i], row)) + { + QueryRow(inclusionFilterColumnIndices, columnIndicesToReturn, rowIds[i], row, inclusionFilters, result); + } + } + } +} + +void Table::QueryRow(DataStructures::List &inclusionFilterColumnIndices, DataStructures::List &columnIndicesToReturn, unsigned key, Table::Row* row, FilterQuery *inclusionFilters, Table *result) +{ + bool pass=false; + unsigned columnIndex; + unsigned j; + + // If no inclusion filters, just add the row + if (inclusionFilterColumnIndices.Size()==0) + { + result->AddRowColumns(key, row, columnIndicesToReturn); + } + else + { + // Go through all inclusion filters. Only add this row if all filters pass. + for (j=0; jcells[columnIndex]->isEmpty==false ) + { + if (columns[inclusionFilterColumnIndices[j]].columnType==STRING && + (row->cells[columnIndex]->c==0 || + inclusionFilters[j].cellValue->c==0) ) + continue; + + switch (inclusionFilters[j].operation) + { + case QF_EQUAL: + switch(columns[inclusionFilterColumnIndices[j]].columnType) + { + case NUMERIC: + pass=row->cells[columnIndex]->i==inclusionFilters[j].cellValue->i; + break; + case STRING: + pass=strcmp(row->cells[columnIndex]->c,inclusionFilters[j].cellValue->c)==0; + break; + case BINARY: + pass=row->cells[columnIndex]->i==inclusionFilters[j].cellValue->i && + memcmp(row->cells[columnIndex]->c,inclusionFilters[j].cellValue->c, (int) row->cells[columnIndex]->i)==0; + break; + case POINTER: + pass=row->cells[columnIndex]->ptr==inclusionFilters[j].cellValue->ptr; + break; + } + break; + case QF_NOT_EQUAL: + switch(columns[inclusionFilterColumnIndices[j]].columnType) + { + case NUMERIC: + pass=row->cells[columnIndex]->i!=inclusionFilters[j].cellValue->i; + break; + case STRING: + pass=strcmp(row->cells[columnIndex]->c,inclusionFilters[j].cellValue->c)!=0; + break; + case BINARY: + pass=row->cells[columnIndex]->i==inclusionFilters[j].cellValue->i && + memcmp(row->cells[columnIndex]->c,inclusionFilters[j].cellValue->c, (int) row->cells[columnIndex]->i)==0; + break; + case POINTER: + pass=row->cells[columnIndex]->ptr!=inclusionFilters[j].cellValue->ptr; + break; + } + break; + case QF_GREATER_THAN: + switch(columns[inclusionFilterColumnIndices[j]].columnType) + { + case NUMERIC: + pass=row->cells[columnIndex]->i>inclusionFilters[j].cellValue->i; + break; + case STRING: + pass=strcmp(row->cells[columnIndex]->c,inclusionFilters[j].cellValue->c)>0; + break; + case BINARY: + break; + case POINTER: + pass=row->cells[columnIndex]->ptr>inclusionFilters[j].cellValue->ptr; + break; + } + break; + case QF_GREATER_THAN_EQ: + switch(columns[inclusionFilterColumnIndices[j]].columnType) + { + case NUMERIC: + pass=row->cells[columnIndex]->i>=inclusionFilters[j].cellValue->i; + break; + case STRING: + pass=strcmp(row->cells[columnIndex]->c,inclusionFilters[j].cellValue->c)>=0; + break; + case BINARY: + break; + case POINTER: + pass=row->cells[columnIndex]->ptr>=inclusionFilters[j].cellValue->ptr; + break; + } + break; + case QF_LESS_THAN: + switch(columns[inclusionFilterColumnIndices[j]].columnType) + { + case NUMERIC: + pass=row->cells[columnIndex]->ii; + break; + case STRING: + pass=strcmp(row->cells[columnIndex]->c,inclusionFilters[j].cellValue->c)<0; + break; + case BINARY: + break; + case POINTER: + pass=row->cells[columnIndex]->ptrptr; + break; + } + break; + case QF_LESS_THAN_EQ: + switch(columns[inclusionFilterColumnIndices[j]].columnType) + { + case NUMERIC: + pass=row->cells[columnIndex]->i<=inclusionFilters[j].cellValue->i; + break; + case STRING: + pass=strcmp(row->cells[columnIndex]->c,inclusionFilters[j].cellValue->c)<=0; + break; + case BINARY: + break; + case POINTER: + pass=row->cells[columnIndex]->ptr<=inclusionFilters[j].cellValue->ptr; + break; + } + break; + case QF_IS_EMPTY: + pass=false; + break; + case QF_NOT_EMPTY: + pass=true; + break; + default: + pass=false; + RakAssert(0); + break; + } + } + else + { + if (inclusionFilters[j].operation==QF_IS_EMPTY) + pass=true; + else + pass=false; // No value for this cell + } + + if (pass==false) + break; + } + + if (pass) + { + result->AddRowColumns(key, row, columnIndicesToReturn); + } + } +} + +static Table::SortQuery *_sortQueries; +static unsigned _numSortQueries; +static DataStructures::List *_columnIndices; +static DataStructures::List *_columns; +int RowSort(Table::Row* const &first, Table::Row* const &second) // first is the one inserting, second is the one already there. +{ + unsigned i, columnIndex; + for (i=0; i<_numSortQueries; i++) + { + columnIndex=(*_columnIndices)[i]; + if (columnIndex==(unsigned)-1) + continue; + + if (first->cells[columnIndex]->isEmpty==true && second->cells[columnIndex]->isEmpty==false) + return 1; // Empty cells always go at the end + + if (first->cells[columnIndex]->isEmpty==false && second->cells[columnIndex]->isEmpty==true) + return -1; // Empty cells always go at the end + + if (_sortQueries[i].operation==Table::QS_INCREASING_ORDER) + { + if ((*_columns)[columnIndex].columnType==Table::NUMERIC) + { + if (first->cells[columnIndex]->i>second->cells[columnIndex]->i) + return 1; + if (first->cells[columnIndex]->icells[columnIndex]->i) + return -1; + } + else + { + // String + if (strcmp(first->cells[columnIndex]->c,second->cells[columnIndex]->c)>0) + return 1; + if (strcmp(first->cells[columnIndex]->c,second->cells[columnIndex]->c)<0) + return -1; + } + } + else + { + if ((*_columns)[columnIndex].columnType==Table::NUMERIC) + { + if (first->cells[columnIndex]->icells[columnIndex]->i) + return 1; + if (first->cells[columnIndex]->i>second->cells[columnIndex]->i) + return -1; + } + else + { + // String + if (strcmp(first->cells[columnIndex]->c,second->cells[columnIndex]->c)<0) + return 1; + if (strcmp(first->cells[columnIndex]->c,second->cells[columnIndex]->c)>0) + return -1; + } + } + } + + return 0; +} +void Table::SortTable(Table::SortQuery *sortQueries, unsigned numSortQueries, Table::Row** out) +{ + unsigned i; + unsigned outLength; + DataStructures::List columnIndices; + _sortQueries=sortQueries; + _numSortQueries=numSortQueries; + _columnIndices=&columnIndices; + _columns=&columns; + bool anyValid=false; + + for (i=0; i < numSortQueries; i++) + { + if (sortQueries[i].columnIndex *cur; + cur = rows.GetListHead(); + if (anyValid==false) + { + outLength=0; + while (cur) + { + for (i=0; i < (unsigned)cur->size; i++) + { + out[(outLength)++]=cur->data[i]; + } + cur=cur->next; + } + return; + } + + // Start adding to ordered list. + DataStructures::OrderedList orderedList; + while (cur) + { + for (i=0; i < (unsigned)cur->size; i++) + { + RakAssert(cur->data[i]); + orderedList.Insert(cur->data[i],cur->data[i], true, __FILE__,__LINE__); + } + cur=cur->next; + } + + outLength=0; + for (i=0; i < orderedList.Size(); i++) + out[(outLength)++]=orderedList[i]; +} +void Table::PrintColumnHeaders(char *out, int outLength, char columnDelineator) const +{ + if (outLength<=0) + return; + if (outLength==1) + { + *out=0; + return; + } + + unsigned i; + out[0]=0; + int len; + for (i=0; i < columns.Size(); i++) + { + if (i!=0) + { + len = (int) strlen(out); + if (len < outLength-1) + sprintf(out+len, "%c", columnDelineator); + else + return; + } + + len = (int) strlen(out); + if (len < outLength-(int) strlen(columns[i].columnName)) + sprintf(out+len, "%s", columns[i].columnName); + else + return; + } +} +void Table::PrintRow(char *out, int outLength, char columnDelineator, bool printDelineatorForBinary, Table::Row* inputRow) const +{ + if (outLength<=0) + return; + if (outLength==1) + { + *out=0; + return; + } + + if (inputRow->cells.Size()!=columns.Size()) + { + strncpy(out, "Cell width does not match column width.\n", outLength); + out[outLength-1]=0; + return; + } + + char buff[512]; + unsigned i; + int len; + out[0]=0; + for (i=0; i < columns.Size(); i++) + { + if (columns[i].columnType==NUMERIC) + { + if (inputRow->cells[i]->isEmpty==false) + { + sprintf(buff, "%f", inputRow->cells[i]->i); + len=(int)strlen(buff); + } + else + len=0; + if (i+1!=columns.Size()) + buff[len++]=columnDelineator; + buff[len]=0; + } + else if (columns[i].columnType==STRING) + { + if (inputRow->cells[i]->isEmpty==false && inputRow->cells[i]->c) + { + strncpy(buff, inputRow->cells[i]->c, 512-2); + buff[512-2]=0; + len=(int)strlen(buff); + } + else + len=0; + if (i+1!=columns.Size()) + buff[len++]=columnDelineator; + buff[len]=0; + } + else if (columns[i].columnType==POINTER) + { + if (inputRow->cells[i]->isEmpty==false && inputRow->cells[i]->ptr) + { + sprintf(buff, "%p", inputRow->cells[i]->ptr); + len=(int)strlen(buff); + } + else + len=0; + if (i+1!=columns.Size()) + buff[len++]=columnDelineator; + buff[len]=0; + } + else + { + if (printDelineatorForBinary) + { + if (i+1!=columns.Size()) + buff[0]=columnDelineator; + buff[1]=0; + } + else + buff[0]=0; + + } + + len=(int)strlen(out); + if (outLength==len+1) + break; + strncpy(out+len, buff, outLength-len); + out[outLength-1]=0; + } +} + +void Table::Clear(void) +{ + rows.ForEachData(FreeRow); + rows.Clear(); + columns.Clear(true, __FILE__, __LINE__); +} +const List& Table::GetColumns(void) const +{ + return columns; +} +const DataStructures::BPlusTree& Table::GetRows(void) const +{ + return rows; +} +DataStructures::Page * Table::GetListHead(void) +{ + return rows.GetListHead(); +} +unsigned Table::GetAvailableRowId(void) const +{ + bool setKey=false; + unsigned key=0; + int i; + DataStructures::Page *cur = rows.GetListHead(); + + while (cur) + { + for (i=0; i < cur->size; i++) + { + if (setKey==false) + { + key=cur->keys[i]+1; + setKey=true; + } + else + { + if (key!=cur->keys[i]) + return key; + key++; + } + } + + cur=cur->next; + } + return key; +} +void Table::DeleteRow(Table::Row *row) +{ + unsigned rowIndex; + for (rowIndex=0; rowIndex < row->cells.Size(); rowIndex++) + { + RakNet::OP_DELETE(row->cells[rowIndex], __FILE__, __LINE__); + } + RakNet::OP_DELETE(row, __FILE__, __LINE__); +} +Table& Table::operator = ( const Table& input ) +{ + Clear(); + + unsigned int i; + for (i=0; i < input.GetColumnCount(); i++) + AddColumn(input.ColumnName(i), input.GetColumnType(i)); + + DataStructures::Page *cur = input.GetRows().GetListHead(); + while (cur) + { + for (i=0; i < (unsigned int) cur->size; i++) + { + AddRow(cur->keys[i], cur->data[i]->cells, false); + } + + cur=cur->next; + } + + return *this; +} + +#ifdef _MSC_VER +#pragma warning( pop ) +#endif diff --git a/RakNet/Sources/DS_Table.h b/RakNet/Sources/DS_Table.h new file mode 100644 index 0000000..95ce1fe --- /dev/null +++ b/RakNet/Sources/DS_Table.h @@ -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.h" +#include "DS_BPlusTree.h" +#include "RakMemoryOverride.h" +#include "Export.h" +#include "RakString.h" + +#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.
+ /// This is a relatively simple and fast implementation of the types of tables commonly used in databases.
+ /// See TableSerializer to serialize data members of the table.
+ /// 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 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 &initialCellValues); + Table::Row* AddRow(unsigned rowId, DataStructures::List &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& GetColumns(void) const; + + /// \brief Direct access to make things easier. + const DataStructures::BPlusTree& GetRows(void) const; + + /// \brief Get the head of a linked list containing all the row data. + DataStructures::Page * 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 columnIndices); + + void DeleteRow(Row *row); + + void QueryRow(DataStructures::List &inclusionFilterColumnIndices, DataStructures::List &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 rows; + + // Columns in the table. + DataStructures::List columns; + }; +} + +#ifdef _MSC_VER +#pragma warning( pop ) +#endif + +#endif diff --git a/RakNet/Sources/DS_ThreadsafeAllocatingQueue.h b/RakNet/Sources/DS_ThreadsafeAllocatingQueue.h new file mode 100644 index 0000000..12245e9 --- /dev/null +++ b/RakNet/Sources/DS_ThreadsafeAllocatingQueue.h @@ -0,0 +1,132 @@ +/// \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.h" +#include "SimpleMutex.h" +#include "DS_MemoryPool.h" + +#if defined(new) +#pragma push_macro("new") +#undef new +#define RMO_NEW_UNDEF_ALLOCATING_QUEUE +#endif + +namespace DataStructures +{ + +template +class RAK_DLL_EXPORT ThreadsafeAllocatingQueue +{ +public: + // Queue operations + void Push(structureType *s); + structureType *PopInaccurate(void); + structureType *Pop(void); + void SetPageSize(int size); + + // 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 memoryPool; + SimpleMutex memoryPoolMutex; + Queue queue; + SimpleMutex queueMutex; +}; + +template +void ThreadsafeAllocatingQueue::Push(structureType *s) +{ + queueMutex.Lock(); + queue.Push(s, __FILE__, __LINE__ ); + queueMutex.Unlock(); +} + +template +structureType *ThreadsafeAllocatingQueue::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 +structureType *ThreadsafeAllocatingQueue::Pop(void) +{ + structureType *s; + queueMutex.Lock(); + if (queue.IsEmpty()) + { + queueMutex.Unlock(); + return 0; + } + s=queue.Pop(); + queueMutex.Unlock(); + return s; +} + +template +structureType *ThreadsafeAllocatingQueue::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 +void ThreadsafeAllocatingQueue::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 +void ThreadsafeAllocatingQueue::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 +void ThreadsafeAllocatingQueue::SetPageSize(int size) +{ + memoryPool.SetPageSize(size); +} + +}; + + +#if defined(RMO_NEW_UNDEF_ALLOCATING_QUEUE) +#pragma pop_macro("new") +#undef RMO_NEW_UNDEF_ALLOCATING_QUEUE +#endif + +#endif diff --git a/RakNet/Sources/DS_Tree.h b/RakNet/Sources/DS_Tree.h new file mode 100644 index 0000000..cbab271 --- /dev/null +++ b/RakNet/Sources/DS_Tree.h @@ -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.h" +#include "DS_List.h" +#include "DS_Queue.h" +#include "RakMemoryOverride.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 RAK_DLL_EXPORT Tree + { + public: + Tree(); + Tree(TreeType &inputData); + ~Tree(); + void LevelOrderTraversal(DataStructures::List &output); + void AddChild(TreeType &newData); + void DeleteDecendants(void); + + TreeType data; + DataStructures::List children; + }; + + template + Tree::Tree() + { + + } + + template + Tree::Tree(TreeType &inputData) + { + data=inputData; + } + + template + Tree::~Tree() + { + DeleteDecendants(); + } + + template + void Tree::LevelOrderTraversal(DataStructures::List &output) + { + unsigned i; + Tree *node; + DataStructures::Queue*> queue; + + for (i=0; i < children.Size(); i++) + queue.Push(children[i]); + + while (queue.Size()) + { + node=queue.Pop(); + output.Insert(node, __FILE__, __LINE__); + for (i=0; i < node->children.Size(); i++) + queue.Push(node->children[i]); + } + } + + template + void Tree::AddChild(TreeType &newData) + { + children.Insert(RakNet::OP_NEW(newData, __FILE__, __LINE__)); + } + + template + void Tree::DeleteDecendants(void) + { + /* + DataStructures::List output; + LevelOrderTraversal(output); + unsigned i; + for (i=0; i < output.Size(); i++) + RakNet::OP_DELETE(output[i], __FILE__, __LINE__); +*/ + + // Already recursive to do this + unsigned int i; + for (i=0; i < children.Size(); i++) + RakNet::OP_DELETE(children[i], __FILE__, __LINE__); + } +} + +#endif diff --git a/RakNet/Sources/DS_WeightedGraph.h b/RakNet/Sources/DS_WeightedGraph.h new file mode 100644 index 0000000..fa3d71b --- /dev/null +++ b/RakNet/Sources/DS_WeightedGraph.h @@ -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.h" +#include "DS_Map.h" +#include "DS_Heap.h" +#include "DS_Queue.h" +#include "DS_Tree.h" +#include "RakAssert.h" +#include "RakMemoryOverride.h" +#ifdef _DEBUG +#include +#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 RAK_DLL_EXPORT WeightedGraph + { + public: + static void IMPLEMENT_DEFAULT_COMPARISON(void) {DataStructures::defaultMapKeyComparison(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 &path, node_type startNode, node_type endNode, weight_type INFINITE_WEIGHT); + bool GetSpanningTree(DataStructures::Tree &outTree, DataStructures::List *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 *> 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 costMatrixIndices; + weight_type *costMatrix; + node_type *leastNodeArray; + // } dijkstra; + + struct NodeAndParent + { + DataStructures::Tree*node; + DataStructures::Tree*parent; + }; + }; + + template + WeightedGraph::WeightedGraph() + { + isValidPath=false; + costMatrix=0; + } + + template + WeightedGraph::~WeightedGraph() + { + Clear(); + } + + template + WeightedGraph::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(costMatrixIndices.Size() * costMatrixIndices.Size(), __FILE__, __LINE__ ); + leastNodeArray = RakNet::OP_NEW_ARRAY(costMatrixIndices.Size(), __FILE__, __LINE__ ); + memcpy(costMatrix, original_copy.costMatrix, costMatrixIndices.Size() * costMatrixIndices.Size() * sizeof(weight_type)); + memcpy(leastNodeArray, original_copy.leastNodeArray, costMatrixIndices.Size() * sizeof(weight_type)); + } + } + + template + WeightedGraph& WeightedGraph::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(costMatrixIndices.Size() * costMatrixIndices.Size(), __FILE__, __LINE__ ); + leastNodeArray = RakNet::OP_NEW_ARRAY(costMatrixIndices.Size(), __FILE__, __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 + void WeightedGraph::AddNode(const node_type &node) + { + adjacencyLists.SetNew(node, RakNet::OP_NEW >( __FILE__, __LINE__) ); + } + + template + void WeightedGraph::RemoveNode(const node_type &node) + { + unsigned i; + DataStructures::Queue removeNodeQueue; + + removeNodeQueue.Push(node, __FILE__, __LINE__ ); + while (removeNodeQueue.Size()) + { + RakNet::OP_DELETE(adjacencyLists.Pop(removeNodeQueue.Pop()), __FILE__, __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__, __LINE__ ); + } + } + + ClearDijkstra(); + } + + template + bool WeightedGraph::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 + void WeightedGraph::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 + void WeightedGraph::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 + void WeightedGraph::Clear(void) + { + unsigned i; + for (i=0; i < adjacencyLists.Size(); i++) + RakNet::OP_DELETE(adjacencyLists[i], __FILE__, __LINE__); + adjacencyLists.Clear(); + + ClearDijkstra(); + } + + template + bool WeightedGraph::GetShortestPath(DataStructures::List &path, node_type startNode, node_type endNode, weight_type INFINITE_WEIGHT) + { + path.Clear(false, __FILE__, __LINE__); + if (startNode==endNode) + { + path.Insert(startNode, __FILE__, __LINE__); + path.Insert(endNode, __FILE__, __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 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__, __LINE__); + path.Insert(endNode, __FILE__, __LINE__); + return true; + } + currentWeight=costMatrix[row*adjacencyLists.Size() + col]; + if (currentWeight==INFINITE_WEIGHT) + { + // No path + return true; + } + vertex=endNode; + outputQueue.PushAtHead(vertex, 0, __FILE__,__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__, __LINE__); + for (col=0; outputQueue.Size(); col++) + path.Insert(outputQueue.Pop(), __FILE__, __LINE__); + return true; + } + --row; + } + + vertex=leastNodeArray[row]; + outputQueue.PushAtHead(vertex, 0, __FILE__,__LINE__); + if (row==0) + break; + col=costMatrixIndices.GetIndexFromKey(vertex, &objectExists); + currentWeight=costMatrix[row*adjacencyLists.Size() + col]; + } + + path.Insert(startNode, __FILE__, __LINE__); + for (col=0; outputQueue.Size(); col++) + path.Insert(outputQueue.Pop(), __FILE__, __LINE__); + return true; + } + + template + node_type WeightedGraph::GetNodeAtIndex(unsigned nodeIndex) const + { + return adjacencyLists.GetKeyAtIndex(nodeIndex); + } + + template + unsigned WeightedGraph::GetNodeCount(void) const + { + return adjacencyLists.Size(); + } + + template + unsigned WeightedGraph::GetConnectionCount(unsigned nodeIndex) const + { + return adjacencyLists[nodeIndex]->Size(); + } + + template + void WeightedGraph::GetConnectionAtIndex(unsigned nodeIndex, unsigned connectionIndex, node_type &outNode, weight_type &outWeight) const + { + outWeight=adjacencyLists[nodeIndex]->operator[](connectionIndex); + outNode=adjacencyLists[nodeIndex]->GetKeyAtIndex(connectionIndex); + } + + template + bool WeightedGraph::GetSpanningTree(DataStructures::Tree &outTree, DataStructures::List *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 path; + DataStructures::WeightedGraph 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 nodesToProcess; + DataStructures::Tree *current; + DataStructures::Map *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 >( __FILE__, __LINE__ ); + nap2.node->data=adjacencyList->GetKeyAtIndex(i); + nap2.parent=current; + nodesToProcess.Push(nap2, __FILE__, __LINE__ ); + current->children.Insert(nap2.node, __FILE__, __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 >( __FILE__, __LINE__ ); + nap2.node->data=key; + nap2.parent=current; + nodesToProcess.Push(nap2, __FILE__, __LINE__ ); + current->children.Insert(nap2.node, __FILE__, __LINE__); + } + } + } + + return true; + } + + template + void WeightedGraph::GenerateDisjktraMatrix(node_type startNode, weight_type INFINITE_WEIGHT) + { + if (adjacencyLists.Size()==0) + return; + + costMatrix = RakNet::OP_NEW_ARRAY(adjacencyLists.Size() * adjacencyLists.Size(), __FILE__, __LINE__ ); + leastNodeArray = RakNet::OP_NEW_ARRAY(adjacencyLists.Size(), __FILE__, __LINE__ ); + + node_type currentNode; + unsigned col, row, row2, openSetIndex; + node_type adjacentKey; + unsigned adjacentIndex; + weight_type edgeWeight, currentNodeWeight, adjacentNodeWeight; + DataStructures::Map *adjacencyList; + DataStructures::Heap minHeap; + DataStructures::Map 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__,__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__,__LINE__); + for (openSetIndex=0; openSetIndex < openSet.Size(); openSetIndex++) + minHeap.Push(openSet[openSetIndex], openSet.GetKeyAtIndex(openSetIndex),__FILE__,__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 + void WeightedGraph::ClearDijkstra(void) + { + if (isValidPath) + { + isValidPath=false; + RakNet::OP_DELETE_ARRAY(costMatrix, __FILE__, __LINE__); + RakNet::OP_DELETE_ARRAY(leastNodeArray, __FILE__, __LINE__); + costMatrixIndices.Clear(false, __FILE__, __LINE__); + } + } + + template + void WeightedGraph::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(""); + 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 diff --git a/RakNet/Sources/DataBlockEncryptor.cpp b/RakNet/Sources/DataBlockEncryptor.cpp new file mode 100644 index 0000000..c3a3832 --- /dev/null +++ b/RakNet/Sources/DataBlockEncryptor.cpp @@ -0,0 +1,197 @@ +/// \file +/// +/// This file is part of RakNet Copyright 2003 Jenkins Software LLC +/// +/// Usage of RakNet is subject to the appropriate license agreement. + + +#include "DataBlockEncryptor.h" +#include "CheckSum.h" +#include "GetTime.h" +#include "Rand.h" +#include "RakAssert.h" +#include +#include "Rijndael.h" +//#include "Types.h" + +DataBlockEncryptor::DataBlockEncryptor() +{ + keySet = false; +} + +DataBlockEncryptor::~DataBlockEncryptor() +{} + +bool DataBlockEncryptor::IsKeySet( void ) const +{ + return keySet; +} + +void DataBlockEncryptor::SetKey( const unsigned char key[ 16 ] ) +{ + keySet = true; + //secretKeyAES128.set_key( key ); + makeKey(&keyEncrypt, DIR_ENCRYPT, 16, (char*)key); + makeKey(&keyDecrypt, DIR_DECRYPT, 16, (char*)key); + cipherInit(&cipherInst, MODE_ECB, 0); // ECB is not secure except that I chain manually farther down. +} + +void DataBlockEncryptor::UnsetKey( void ) +{ + keySet = false; +} + +void DataBlockEncryptor::Encrypt( unsigned char *input, unsigned int inputLength, unsigned char *output, unsigned int *outputLength, RakNetRandom *rnr ) +{ + unsigned index, byteIndex, lastBlock; + unsigned int checkSum; + unsigned char paddingBytes; + unsigned char encodedPad; + unsigned char randomChar; + CheckSum checkSumCalculator; + +#ifdef _DEBUG + + RakAssert( keySet ); +#endif + + RakAssert( input && inputLength ); + + + // randomChar will randomize the data so the same data sent twice will not look the same + randomChar = (unsigned char) rnr->RandomMT(); + + // 16-(((x-1) % 16)+1) + + // # of padding bytes is 16 -(((input_length + extra_data -1) % 16)+1) + paddingBytes = (unsigned char) ( 16 - ( ( ( inputLength + sizeof( randomChar ) + sizeof( checkSum ) + sizeof( encodedPad ) - 1 ) % 16 ) + 1 ) ); + + // Randomize the pad size variable + encodedPad = (unsigned char) rnr->RandomMT(); + encodedPad <<= 4; + encodedPad |= paddingBytes; + + *outputLength = inputLength + sizeof( randomChar ) + sizeof( checkSum ) + sizeof( encodedPad ) + paddingBytes; + + // Write the data first, in case we are overwriting ourselves + + if ( input == output ) + memmove( output + sizeof( checkSum ) + sizeof( randomChar ) + sizeof( encodedPad ) + paddingBytes, input, inputLength ); + else + memcpy( output + sizeof( checkSum ) + sizeof( randomChar ) + sizeof( encodedPad ) + paddingBytes, input, inputLength ); + + // Write the random char + memcpy( output + sizeof( checkSum ), ( char* ) & randomChar, sizeof( randomChar ) ); + + // Write the pad size variable + memcpy( output + sizeof( checkSum ) + sizeof( randomChar ), ( char* ) & encodedPad, sizeof( encodedPad ) ); + + // Write the padding + for ( index = 0; index < paddingBytes; index++ ) + *( output + sizeof( checkSum ) + sizeof( randomChar ) + sizeof( encodedPad ) + index ) = (unsigned char) rnr->RandomMT(); + + // Calculate the checksum on the data + checkSumCalculator.Add( output + sizeof( checkSum ), inputLength + sizeof( randomChar ) + sizeof( encodedPad ) + paddingBytes ); + + checkSum = checkSumCalculator.Get(); + + // Write checksum +#ifdef HOST_ENDIAN_IS_BIG + output[0] = checkSum&0xFF; + output[1] = (checkSum>>8)&0xFF; + output[2] = (checkSum>>16)&0xFF; + output[3] = (checkSum>>24)&0xFF; +#else + memcpy( output, ( char* ) & checkSum, sizeof( checkSum ) ); +#endif + + // AES on the first block +// secretKeyAES128.encrypt16( output ); + blockEncrypt(&cipherInst, &keyEncrypt, output, 16, output); + + lastBlock = 0; + + // Now do AES on every other block from back to front + for ( index = *outputLength - 16; index >= 16; index -= 16 ) + { + for ( byteIndex = 0; byteIndex < 16; byteIndex++ ) + output[ index + byteIndex ] ^= output[ lastBlock + byteIndex ]; + + //secretKeyAES128.encrypt16( output + index ); + blockEncrypt(&cipherInst, &keyEncrypt, output+index, 16, output+index); + + lastBlock = index; + } +} + +bool DataBlockEncryptor::Decrypt( unsigned char *input, unsigned int inputLength, unsigned char *output, unsigned int *outputLength ) +{ + unsigned index, byteIndex; + unsigned int checkSum; + unsigned char paddingBytes; + unsigned char encodedPad; + unsigned char randomChar; + CheckSum checkSumCalculator; +#ifdef _DEBUG + + RakAssert( keySet ); +#endif + + if ( input == 0 || inputLength < 16 || ( inputLength % 16 ) != 0 ) + { + return false; + } + + // Unchain in reverse order + for ( index = 16; index <= inputLength - 16;index += 16 ) + { + // secretKeyAES128.decrypt16( input + index ); + blockDecrypt(&cipherInst, &keyDecrypt, input + index, 16, output + index); + + for ( byteIndex = 0; byteIndex < 16; byteIndex++ ) + { + if ( index + 16 == ( unsigned ) inputLength ) + output[ index + byteIndex ] ^= input[ byteIndex ]; + else + output[ index + byteIndex ] ^= input[ index + 16 + byteIndex ]; + } + + // lastBlock = index; + }; + + // Decrypt the first block + //secretKeyAES128.decrypt16( input ); + blockDecrypt(&cipherInst, &keyDecrypt, input, 16, output); + + // Read checksum +#ifdef HOST_ENDIAN_IS_BIG + checkSum = (unsigned int)output[0] | (unsigned int)(output[1]<<8) | + (unsigned int)(output[2]<<16)|(unsigned int)(output[3]<<24); +#else + memcpy( ( char* ) & checkSum, output, sizeof( checkSum ) ); +#endif + + // Read the pad size variable + memcpy( ( char* ) & encodedPad, output + sizeof( randomChar ) + sizeof( checkSum ), sizeof( encodedPad ) ); + + // Ignore the high 4 bytes + paddingBytes = encodedPad & 0x0F; + + + // Get the data length + *outputLength = inputLength - sizeof( randomChar ) - sizeof( checkSum ) - sizeof( encodedPad ) - paddingBytes; + + // Calculate the checksum on the data. + checkSumCalculator.Add( output + sizeof( checkSum ), *outputLength + sizeof( randomChar ) + sizeof( encodedPad ) + paddingBytes ); + + if ( checkSum != checkSumCalculator.Get() ) + return false; + + // Read the data + //if ( input == output ) + memmove( output, output + sizeof( randomChar ) + sizeof( checkSum ) + sizeof( encodedPad ) + paddingBytes, *outputLength ); + //else + // memcpy( output, input + sizeof( randomChar ) + sizeof( checkSum ) + sizeof( encodedPad ) + paddingBytes, *outputLength ); + + return true; +} diff --git a/RakNet/Sources/DataBlockEncryptor.h b/RakNet/Sources/DataBlockEncryptor.h new file mode 100644 index 0000000..bb690f2 --- /dev/null +++ b/RakNet/Sources/DataBlockEncryptor.h @@ -0,0 +1,66 @@ +/// \file DataBlockEncryptor.h +/// \internal +/// \brief Encrypts and decrypts data blocks. Used as part of secure connections. +/// +/// This file is part of RakNet Copyright 2003 Jenkins Software LLC +/// +/// Usage of RakNet is subject to the appropriate license agreement. + + +#ifndef __DATA_BLOCK_ENCRYPTOR_H +#define __DATA_BLOCK_ENCRYPTOR_H + +#include "Rijndael.h" +#include "RakMemoryOverride.h" +class RakNetRandom; + +/// Encrypts and decrypts data blocks. +class DataBlockEncryptor +{ + +public: + + // Constructor + DataBlockEncryptor(); + + // Destructor + ~DataBlockEncryptor(); + + /// \return true if SetKey has been called previously + bool IsKeySet( void ) const; + + /// \brief Set the encryption key + /// \param[in] key The new encryption key + void SetKey( const unsigned char key[ 16 ] ); + + /// \brief Unset the encryption key + void UnsetKey( void ); + + /// \brief Encryption adds 6 data bytes and then pads the number of bytes to be a multiple of 16. + /// \details Output should be large enough to hold this. + /// Output can be the same memory block as input + /// \param[in] input the input buffer to encrypt + /// \param[in] inputLength the size of the @em input buffer + /// \param[in] output the output buffer to store encrypted data + /// \param[in] outputLength the size of the output buffer + void Encrypt( unsigned char *input, unsigned int inputLength, unsigned char *output, unsigned int *outputLength, RakNetRandom *rnr ); + + /// \brief Decryption removes bytes, as few as 6. + /// \details Output should be large enough to hold this. + /// Output can be the same memory block as input + /// \param[in] input the input buffer to decrypt + /// \param[in] inputLength the size of the @em input buffer + /// \param[in] output the output buffer to store decrypted data + /// \param[in] outputLength the size of the @em output buffer + /// \return False on bad checksum or input, true on success + bool Decrypt( unsigned char *input, unsigned int inputLength, unsigned char *output, unsigned int *outputLength ); + +protected: + + keyInstance keyEncrypt; + keyInstance keyDecrypt; + cipherInstance cipherInst; + bool keySet; +}; + +#endif diff --git a/RakNet/Sources/DataCompressor.cpp b/RakNet/Sources/DataCompressor.cpp new file mode 100644 index 0000000..233688a --- /dev/null +++ b/RakNet/Sources/DataCompressor.cpp @@ -0,0 +1,59 @@ +#include "DataCompressor.h" +#include "DS_HuffmanEncodingTree.h" +#include "RakAssert.h" +#include // Use string.h rather than memory.h for a console + +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__, __LINE__); + tree.GenerateFromFrequencyTable(frequencyTable); + decompressedBytes=tree.DecodeArray(input, bitsUsed, destinationSizeInBytes, *output ); + RakAssert(decompressedBytes==destinationSizeInBytes); + return destinationSizeInBytes; +} diff --git a/RakNet/Sources/DataCompressor.h b/RakNet/Sources/DataCompressor.h new file mode 100644 index 0000000..81ec8c9 --- /dev/null +++ b/RakNet/Sources/DataCompressor.h @@ -0,0 +1,25 @@ +/// \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.h" +#include "DS_HuffmanEncodingTree.h" +#include "Export.h" + +/// \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: + static void Compress( unsigned char *userData, unsigned sizeInBytes, RakNet::BitStream * output ); + static unsigned DecompressAndAllocate( RakNet::BitStream * input, unsigned char **output ); +}; + +#endif diff --git a/RakNet/Sources/DirectoryDeltaTransfer.cpp b/RakNet/Sources/DirectoryDeltaTransfer.cpp new file mode 100644 index 0000000..3f25129 --- /dev/null +++ b/RakNet/Sources/DirectoryDeltaTransfer.cpp @@ -0,0 +1,218 @@ +#include "NativeFeatureIncludes.h" +#if _RAKNET_SUPPORT_DirectoryDeltaTransfer==1 + +#include "DirectoryDeltaTransfer.h" +#include "FileList.h" +#include "StringCompressor.h" +#include "RakPeerInterface.h" +#include "FileListTransfer.h" +#include "FileListTransferCBInterface.h" +#include "BitStream.h" +#include "MessageIdentifiers.h" +#include "FileOperations.h" +#include "IncrementalReadInterface.h" + +#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(void) + { + return onFileCallback->OnDownloadComplete(); + } +}; + +DirectoryDeltaTransfer::DirectoryDeltaTransfer() +{ + applicationDirectory[0]=0; + fileListTransfer=0; + availableUploads = RakNet::OP_NEW( __FILE__, __LINE__ ); + priority=HIGH_PRIORITY; + orderingChannel=0; + incrementalReadInterface=0; + compressOutgoingSends=false; +} +DirectoryDeltaTransfer::~DirectoryDeltaTransfer() +{ + RakNet::OP_DELETE(availableUploads, __FILE__, __LINE__); +} +void DirectoryDeltaTransfer::SetFileListTransferPlugin(FileListTransfer *flt) +{ + fileListTransfer=flt; + if (flt) + availableUploads->SetCallback(flt->GetCallback()); + else + availableUploads->SetCallback(0); +} +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(const char *subdir, const char *outputSubdir, bool prependAppDirToOutputSubdir, SystemAddress host, FileListTransferCBInterface *onFileCallback, PacketPriority _priority, char _orderingChannel, FileListProgress *cb) +{ +// if (rakPeerInterface->IsConnected(host)==false) +// return (unsigned short) -1; + RakAssert(host!=UNASSIGNED_SYSTEM_ADDRESS); + + DDTCallback *transferCallback; + FileList localFiles; + localFiles.SetCallback(cb); + // 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)); + + // Prepare the callback data + transferCallback = RakNet::OP_NEW( __FILE__, __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->EncodeString(subdir, 256, &outBitstream); + stringCompressor->EncodeString(outputSubdir, 256, &outBitstream); + localFiles.Serialize(&outBitstream); + SendUnified(&outBitstream, _priority, RELIABLE_ORDERED, _orderingChannel, host, false); + + return setId; +} +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->DecodeString(subdir, 256, &inBitstream); + stringCompressor->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, compressOutgoingSends, 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::SetCompressOutgoingSends(bool compress) +{ + compressOutgoingSends=compress; +} + +void DirectoryDeltaTransfer::SetDownloadRequestIncrementalReadInterface(IncrementalReadInterface *_incrementalReadInterface, unsigned int _chunkSize) +{ + incrementalReadInterface=_incrementalReadInterface; + chunkSize=_chunkSize; +} + +#ifdef _MSC_VER +#pragma warning( pop ) +#endif + +#endif // _RAKNET_SUPPORT_* diff --git a/RakNet/Sources/DirectoryDeltaTransfer.h b/RakNet/Sources/DirectoryDeltaTransfer.h new file mode 100644 index 0000000..ee6a579 --- /dev/null +++ b/RakNet/Sources/DirectoryDeltaTransfer.h @@ -0,0 +1,136 @@ +/// \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.h" +#if _RAKNET_SUPPORT_DirectoryDeltaTransfer==1 + +#ifndef __DIRECTORY_DELTA_TRANSFER_H +#define __DIRECTORY_DELTA_TRANSFER_H + +#include "RakMemoryOverride.h" +#include "RakNetTypes.h" +#include "Export.h" +#include "PluginInterface2.h" +#include "DS_Map.h" +#include "PacketPriority.h" + +class RakPeerInterface; +class FileList; +struct Packet; +struct InternalPacket; +struct DownloadRequest; +class FileListTransfer; +class FileListTransferCBInterface; +class FileListProgress; +class IncrementalReadInterface; + +/// \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/"
+/// To allow other systems to download files, call AddUploadsFromSubdirectory, where the parameter is a path relative
+/// to the path to your application. This includes subdirectories.
+/// For example:
+/// SetApplicationDirectory("C:/Games/MyRPG/");
+/// AddUploadsFromSubdirectory("Mods/Skins/");
+/// would allow downloads from
+/// "C:/Games/MyRPG/Mods/Skins/*.*" as well as "C:/Games/MyRPG/Mods/Skins/Level1/*.*"
+/// It would NOT allow downloads from C:/Games/MyRPG/Levels, nor would it allow downloads from C:/Windows
+/// While pathToApplication can be anything you want, applicationSubdirectory must match either partially or fully between systems. +/// \ingroup DIRECTORY_DELTA_TRANSFER_GROUP + +class RAK_DLL_EXPORT DirectoryDeltaTransfer : public PluginInterface2 +{ +public: + // 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 + /// \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 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 Set if we should compress outgoing sends or not. + /// \details Defaults to false, because this results in a noticeable freeze on large requests. + /// You can set this to true if you only send small files though + /// \param[in] compress True to compress, false to not. + void SetCompressOutgoingSends(bool compress); + + /// \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; + bool compressOutgoingSends; + IncrementalReadInterface *incrementalReadInterface; + unsigned int chunkSize; +}; + +#endif + +#endif // _RAKNET_SUPPORT_* diff --git a/RakNet/Sources/EmailSender.cpp b/RakNet/Sources/EmailSender.cpp new file mode 100644 index 0000000..c3cb869 --- /dev/null +++ b/RakNet/Sources/EmailSender.cpp @@ -0,0 +1,430 @@ +#include "NativeFeatureIncludes.h" +#if _RAKNET_SUPPORT_EmailSender==1 + +// Useful sites +// http://www.faqs.org\rfcs\rfc2821.html +// http://en.wikipedia.org/wiki/Base64 +// http://www2.rad.com\networks/1995/mime/examples.htm + +#include "EmailSender.h" +#include "TCPInterface.h" +#include "GetTime.h" +#include "Rand.h" +#include "FileList.h" +#include "BitStream.h" +#include + +#if defined(_XBOX) || defined(X360) + +#endif + +#include "RakSleep.h" + +static const char base64Map[]="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; + +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) +{ + 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"; +#ifdef OPEN_SSL_CLIENT_SUPPORT + tcpInterface.StartSSLClient(emailServer); +#endif + RakNetTime timeoutTime = RakNet::GetTime()+3000; + packet=0; + while (RakNet::GetTime() < 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((const int) (strlen(sender)+strlen(password)+2)*3, __FILE__, __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")); + Base64Encoding((const char*)bs.GetData(), bs.GetNumberOfBytesUsed(), outputData, base64Map); + 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()) + { + seedMT((unsigned int) RakNet::GetTime()); + // Random multipart message boundary + for (i=0; i < boundarySize; i++) + boundary[i]=base64Map[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__, __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__, __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__, __LINE__ ); + + outputOffset=Base64Encoding(attachedFiles->fileList[i].data, (int) attachedFiles->fileList[i].dataLengthBytes, newBody, base64Map); + + // Send the base64 mapped file. + tcpInterface.Send(newBody, outputOffset, emailServer,false); + rakFree_Ex(newBody, __FILE__, __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) +{ + Packet *packet; + RakNetTime timeout; + timeout=RakNet::GetTime()+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 defined(OPEN_SSL_CLIENT_SUPPORT) + 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 defined(OPEN_SSL_CLIENT_SUPPORT) + 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::GetTime() > timeout) + return "Timed out"; + RakSleep(100); + } +} + +int EmailSender::Base64Encoding(const char *inputData, int dataLength, char *outputData, const char *base64Map) +{ + int outputOffset, charCount; + int write3Count; + outputOffset=0; + charCount=0; + int j; + + write3Count=dataLength/3; + for (j=0; j < write3Count; j++) + { + // 6 leftmost bits from first byte, shifted to bits 7,8 are 0 + outputData[outputOffset++]=base64Map[inputData[j*3+0] >> 2]; + if ((++charCount % 76)==0) {outputData[outputOffset++]='\r'; outputData[outputOffset++]='\n'; charCount=0;} + + // Remaining 2 bits from first byte, placed in position, and 4 high bits from the second byte, masked to ignore bits 7,8 + outputData[outputOffset++]=base64Map[((inputData[j*3+0] << 4) | (inputData[j*3+1] >> 4)) & 63]; + if ((++charCount % 76)==0) {outputData[outputOffset++]='\r'; outputData[outputOffset++]='\n'; charCount=0;} + + // 4 low bits from the second byte and the two high bits from the third byte, masked to ignore bits 7,8 + outputData[outputOffset++]=base64Map[((inputData[j*3+1] << 2) | (inputData[j*3+2] >> 6)) & 63]; // Third 6 bits + if ((++charCount % 76)==0) {outputData[outputOffset++]='\r'; outputData[outputOffset++]='\n'; charCount=0;} + + // Last 6 bits from the third byte, masked to ignore bits 7,8 + outputData[outputOffset++]=base64Map[inputData[j*3+2] & 63]; + if ((++charCount % 76)==0) {outputData[outputOffset++]='\r'; outputData[outputOffset++]='\n'; charCount=0;} + } + + if (dataLength % 3==1) + { + // One input byte remaining + outputData[outputOffset++]=base64Map[inputData[j*3+0] >> 2]; + if ((++charCount % 76)==0) {outputData[outputOffset++]='\r'; outputData[outputOffset++]='\n'; charCount=0;} + + // Remaining 2 bits from first byte, placed in position, and 4 high bits from the second byte, masked to ignore bits 7,8 + outputData[outputOffset++]=base64Map[((inputData[j*3+0] << 4) | (inputData[j*3+1] >> 4)) & 63]; + if ((++charCount % 76)==0) {outputData[outputOffset++]='\r'; outputData[outputOffset++]='\n'; charCount=0;} + + // Pad with two equals + outputData[outputOffset++]='='; + outputData[outputOffset++]='='; + } + else if (dataLength % 3==2) + { + // Two input bytes remaining + + // 6 leftmost bits from first byte, shifted to bits 7,8 are 0 + outputData[outputOffset++]=base64Map[inputData[j*3+0] >> 2]; + if ((++charCount % 76)==0) {outputData[outputOffset++]='\r'; outputData[outputOffset++]='\n'; charCount=0;} + + // Remaining 2 bits from first byte, placed in position, and 4 high bits from the second byte, masked to ignore bits 7,8 + outputData[outputOffset++]=base64Map[((inputData[j*3+0] << 4) | (inputData[j*3+1] >> 4)) & 63]; + if ((++charCount % 76)==0) {outputData[outputOffset++]='\r'; outputData[outputOffset++]='\n'; charCount=0;} + + // 4 low bits from the second byte, followed by 00 + outputData[outputOffset++]=base64Map[(inputData[j*3+1] << 2) & 63]; // Third 6 bits + if ((++charCount % 76)==0) {outputData[outputOffset++]='\r'; outputData[outputOffset++]='\n'; charCount=0;} + + // Pad with one equal + outputData[outputOffset++]='='; + //outputData[outputOffset++]='='; + } + + // Append \r\n + outputData[outputOffset++]='\r'; + outputData[outputOffset++]='\n'; + outputData[outputOffset]=0; + + return outputOffset; +} + +#endif // _RAKNET_SUPPORT_* diff --git a/RakNet/Sources/EmailSender.h b/RakNet/Sources/EmailSender.h new file mode 100644 index 0000000..14037e0 --- /dev/null +++ b/RakNet/Sources/EmailSender.h @@ -0,0 +1,47 @@ +/// \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.h" +#if _RAKNET_SUPPORT_EmailSender==1 + +#ifndef __EMAIL_SENDER_H +#define __EMAIL_SENDER_H + +class FileList; +class TCPInterface; +#include "RakNetTypes.h" +#include "RakMemoryOverride.h" + +/// \brief Rudimentary class to send email from code. +class EmailSender +{ +public: + /// \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); + + // \brief Returns how many bytes were written. + int Base64Encoding(const char *inputData, int dataLength, char *outputData, const char *base64Map); +protected: + const char *GetResponse(TCPInterface *tcpInterface, const SystemAddress &emailServer, bool doPrintf); +}; + +#endif + + +#endif // _RAKNET_SUPPORT_* diff --git a/RakNet/Sources/EncodeClassName.cpp b/RakNet/Sources/EncodeClassName.cpp new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/RakNet/Sources/EncodeClassName.cpp @@ -0,0 +1 @@ + diff --git a/RakNet/Sources/EpochTimeToString.cpp b/RakNet/Sources/EpochTimeToString.cpp new file mode 100644 index 0000000..c0174aa --- /dev/null +++ b/RakNet/Sources/EpochTimeToString.cpp @@ -0,0 +1,35 @@ +#include "FormatString.h" +#include "EpochTimeToString.h" +#include +#include +#include +// localtime +#include +#include "LinuxStrings.h" + +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]; +} diff --git a/RakNet/Sources/EpochTimeToString.h b/RakNet/Sources/EpochTimeToString.h new file mode 100644 index 0000000..b6b299a --- /dev/null +++ b/RakNet/Sources/EpochTimeToString.h @@ -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.h" + +RAK_DLL_EXPORT char * EpochTimeToString(long long time); + +#endif + diff --git a/RakNet/Sources/Export.h b/RakNet/Sources/Export.h new file mode 100644 index 0000000..7a8fff5 --- /dev/null +++ b/RakNet/Sources/Export.h @@ -0,0 +1,5 @@ +#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 diff --git a/RakNet/Sources/ExtendedOverlappedPool.cpp b/RakNet/Sources/ExtendedOverlappedPool.cpp new file mode 100644 index 0000000..661f6fa --- /dev/null +++ b/RakNet/Sources/ExtendedOverlappedPool.cpp @@ -0,0 +1,58 @@ +/// \file +/// +/// This file is part of RakNet Copyright 2003 Jenkins Software LLC +/// +/// Usage of RakNet is subject to the appropriate license agreement. + + +// No longer used as I no longer support IO Completion ports +/* +#ifdef __USE_IO_COMPLETION_PORTS +#include "ExtendedOverlappedPool.h" + +ExtendedOverlappedPool ExtendedOverlappedPool::I; + +ExtendedOverlappedPool::ExtendedOverlappedPool() +{} + +ExtendedOverlappedPool::~ExtendedOverlappedPool() +{ + // The caller better have returned all the packets! + ExtendedOverlappedStruct * p; + poolMutex.Lock(); + + while ( pool.Size() ) + { + p = pool.Pop(); + RakNet::OP_DELETE(p, __FILE__, __LINE__); + } + + poolMutex.Unlock(); +} + +ExtendedOverlappedStruct* ExtendedOverlappedPool::GetPointer( void ) +{ + ExtendedOverlappedStruct * p = 0; + poolMutex.Lock(); + + if ( pool.Size() ) + p = pool.Pop(); + + poolMutex.Unlock(); + + if ( p ) + return p; + + return new ExtendedOverlappedStruct; +} + +void ExtendedOverlappedPool::ReleasePointer( ExtendedOverlappedStruct *p ) +{ + poolMutex.Lock(); + pool.Push( p ); + poolMutex.Unlock(); +} + +#endif + +*/ diff --git a/RakNet/Sources/ExtendedOverlappedPool.h b/RakNet/Sources/ExtendedOverlappedPool.h new file mode 100644 index 0000000..0900f83 --- /dev/null +++ b/RakNet/Sources/ExtendedOverlappedPool.h @@ -0,0 +1,41 @@ +/// \file +/// \brief \b [deprecated] This was used for IO completion ports. +/// +/// This file is part of RakNet Copyright 2003 Jenkins Software LLC +/// +/// Usage of RakNet is subject to the appropriate license agreement. + + +// No longer used as I no longer support IO Completion ports +/* +#ifdef __USE_IO_COMPLETION_PORTS +#ifndef __EXTENDED_OVERLAPPED_POOL +#define __EXTENDED_OVERLAPPED_POOL +#include "SimpleMutex.h" +#include "ClientContextStruct.h" +#include "DS_Queue.h" + +/// deprecated - for IO completion ports +class ExtendedOverlappedPool +{ + +public: + ExtendedOverlappedPool(); + ~ExtendedOverlappedPool(); + ExtendedOverlappedStruct* GetPointer( void ); + void ReleasePointer( ExtendedOverlappedStruct *p ); + static inline ExtendedOverlappedPool* Instance() + { + return & I; + } + +private: + DataStructures::Queue pool; + SimpleMutex poolMutex; + static ExtendedOverlappedPool I; +}; + +#endif +#endif + +*/ diff --git a/RakNet/Sources/FileList.cpp b/RakNet/Sources/FileList.cpp new file mode 100644 index 0000000..e43e65d --- /dev/null +++ b/RakNet/Sources/FileList.cpp @@ -0,0 +1,717 @@ +#include "FileList.h" +#include // RAKNET_DEBUG_PRINTF +#include "RakAssert.h" +#if defined(_WIN32) || defined(__CYGWIN__) + #include +#elif !defined ( __APPLE__ ) && !defined ( __APPLE_CC__ ) && !defined ( __PPC__ ) && !defined ( __FreeBSD__ ) + #include +#endif +#include "DS_Queue.h" +#ifdef _WIN32 +// For mkdir +#include +#else +#include +#endif +//#include "SHA1.h" +#include "StringCompressor.h" +#include "BitStream.h" +#include "FileOperations.h" +#include "SuperFastHash.h" +#include "RakAssert.h" +#include "LinuxStrings.h" + +#define MAX_FILENAME_LENGTH 512 +static const unsigned HASH_LENGTH=sizeof(unsigned int); + +// alloca +#if defined(_XBOX) || defined(X360) +#elif defined(_WIN32) +#include +#else +#if !defined ( __FreeBSD__ ) +#include +#endif +#include +#include +#include +#include "_FindFirst.h" +#include //defines intptr_t +#endif + +#include "RakAlloca.h" + +//int RAK_DLL_EXPORT FileListNodeComp( char * const &key, const FileListNode &data ) +//{ +// return strcmp(key, data.filename); +//} + + +#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);} + +FileList::FileList() +{ + callback=0; +} +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; + } + + +#if !defined(_XBOX) && !defined(_X360) + bool usedAlloca=false; + if (length < MAX_ALLOCA_STACK_ALLOCATION) + { + data = ( char* ) alloca( length ); + usedAlloca=true; + } + else +#endif + { + data = (char*) rakMalloc_Ex( length, __FILE__, __LINE__ ); + } + + fread(data, 1, length, fp); + AddFile(filename, filepath, data, length, length, context); + fclose(fp); + +#if !defined(_XBOX) && !defined(_X360) + if (usedAlloca==false) +#endif + rakFree_Ex(data, __FILE__, __LINE__ ); + +} +void FileList::AddFile(const char *filename, const char *fullPathToFile, const char *data, const unsigned dataLength, const unsigned fileLength, FileListNodeContext context, bool isAReference) +{ + 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 dirList; + char root[260]; + char fullPath[520]; + _finddata_t fileInfo; + intptr_t dir; + FILE *fp; + char *dirSoFar, *fileData; + dirSoFar=(char*) rakMalloc_Ex( 520, __FILE__, __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); + } + if (callback) + callback->OnAddFilesFromDirectoryStarted(this, dirSoFar); + // RAKNET_DEBUG_PRINTF("Adding files from directory %s\n",dirSoFar); + dirList.Push(dirSoFar, __FILE__, __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__, __LINE__ ); + unsigned i; + for (i=0; i < dirList.Size(); i++) + rakFree_Ex(dirList[i], __FILE__, __LINE__ ); + return; + } + +// RAKNET_DEBUG_PRINTF("Adding %s. %i remaining.\n", fullPath, dirList.Size()); + if (callback) + callback->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; + + if (callback) + callback->OnFile(this, dirSoFar, fileInfo.name, fileInfo.size); + + if (writeData && writeHash) + { + fileData= (char*) rakMalloc_Ex( fileInfo.size+HASH_LENGTH, __FILE__, __LINE__ ); + fp = fopen(fullPath, "rb"); + 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__, __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__, __LINE__ ); + } + else if ((fileInfo.attrib & _A_SUBDIR) && (fileInfo.attrib & (_A_HIDDEN | _A_SYSTEM))==0 && recursive) + { + char *newDir=(char*) rakMalloc_Ex( 520, __FILE__, __LINE__ ); + strcpy(newDir, dirSoFar); + strcat(newDir, fileInfo.name); + strcat(newDir, "/"); + dirList.Push(newDir, __FILE__, __LINE__ ); + } + + } while (_findnext(dir, &fileInfo ) != -1); + + _findclose(dir); + rakFree_Ex(dirSoFar, __FILE__, __LINE__ ); + } +} +void FileList::Clear(void) +{ + unsigned i; + for (i=0; iWriteCompressed(fileList.Size()); + unsigned i; + for (i=0; i < fileList.Size(); i++) + { + outBitStream->WriteCompressed(fileList[i].context.op); + outBitStream->WriteCompressed(fileList[i].context.fileId); + stringCompressor->EncodeString(fileList[i].filename.C_String(), MAX_FILENAME_LENGTH, outBitStream); + outBitStream->Write((bool)(fileList[i].dataLengthBytes>0==true)); + if (fileList[i].dataLengthBytes>0) + { + 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->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__, __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__, __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 && + (localPathLendirSubsetLen && 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__, __LINE__ ); +// fread(fileData, fileLength, 1, fp); + +// sha1.Reset(); +// sha1.Update( ( unsigned char* ) fileData, fileLength ); +// sha1.Final(); + +// rakFree_Ex(fileData, __FILE__, __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__, __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__, __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__, __LINE__ ); + else + fileList[i].data=(char*) rakMalloc_Ex( fileList[i].fileLengthBytes, __FILE__, __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__, __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()); + +#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 + int result = unlink(fullPath); + if (result!=0) + { + RAKNET_DEBUG_PRINTF("FileList::DeleteFiles: unlink (%s) failed.\n", fullPath); + } + } +} + +void FileList::SetCallback(FileListProgress *cb) +{ + callback=cb; +} + +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 diff --git a/RakNet/Sources/FileList.h b/RakNet/Sources/FileList.h new file mode 100644 index 0000000..359d20f --- /dev/null +++ b/RakNet/Sources/FileList.h @@ -0,0 +1,208 @@ +/// \file FileList.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 +#define __FILE_LIST + +#include "Export.h" +#include "DS_List.h" +#include "RakMemoryOverride.h" +#include "RakNetTypes.h" +#include "FileListNodeContext.h" +#include "RakString.h" + +#ifdef _MSC_VER +#pragma warning( push ) +#endif + +namespace RakNet +{ + class BitStream; +} + +/// 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; +}; + +//int RAK_DLL_EXPORT FileListNodeComp( char * const &key, const FileListNode &data ); + +class RakPeerInterface; +class FileList; + +/// Callback interface set with FileList::SetCallback() in case you want progress notifications when FileList::AddFilesFromDirectory() is called +class RAK_DLL_EXPORT FileListProgress +{ +public: + 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) + { + (void) fileName; + (void) fileLengthBytes; + (void) offset; + (void) bytesBeingSent; + (void) done; + (void) targetSystem; + } +}; + +/// Implementation of FileListProgress to use RAKNET_DEBUG_PRINTF +class RAK_DLL_EXPORT FLP_Printf : public FileListProgress +{ +public: + 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); +}; + +class RAK_DLL_EXPORT FileList +{ +public: + 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 SHA1_LENGTH 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 + void AddFile(const char *filename, const char *fullPathToFile, const char *data, const unsigned dataLength, const unsigned fileLength, FileListNodeContext context, bool isAReference=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 Set a callback to get progress reports about what this class does. + /// \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 SetCallback(FileListProgress *cb); + + // Here so you can read it, but don't modify it + DataStructures::List fileList; + + static bool FixEndingSlash(char *str); +protected: + FileListProgress *callback; +}; + +#ifdef _MSC_VER +#pragma warning( pop ) +#endif + +#endif diff --git a/RakNet/Sources/FileListNodeContext.h b/RakNet/Sources/FileListNodeContext.h new file mode 100644 index 0000000..f489c41 --- /dev/null +++ b/RakNet/Sources/FileListNodeContext.h @@ -0,0 +1,36 @@ +/// \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.h" + +struct FileListNodeContext +{ + FileListNodeContext() {} + FileListNodeContext(unsigned char o, unsigned int f) : op(o), fileId(f) {} + ~FileListNodeContext() {} + + unsigned char op; + unsigned int fileId; +}; + +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); + assert(success); + return in; +} + +#endif diff --git a/RakNet/Sources/FileListTransfer.cpp b/RakNet/Sources/FileListTransfer.cpp new file mode 100644 index 0000000..675e9d1 --- /dev/null +++ b/RakNet/Sources/FileListTransfer.cpp @@ -0,0 +1,919 @@ +#include "NativeFeatureIncludes.h" +#if _RAKNET_SUPPORT_FileListTransfer==1 + +#include "FileListTransfer.h" +#include "DS_HuffmanEncodingTree.h" +#include "FileListTransferCBInterface.h" +#include "StringCompressor.h" +#include "FileList.h" +#include "DS_Queue.h" +#include "MessageIdentifiers.h" +#include "RakNetTypes.h" +#include "RakPeerInterface.h" +#include "RakNetStatistics.h" +#include "IncrementalReadInterface.h" +#include "RakAssert.h" +#include "RakAlloca.h" + +#ifdef _MSC_VER +#pragma warning( push ) +#endif + +struct FLR_MemoryBlock +{ + char *flrMemoryBlock; +}; + +struct FileListReceiver +{ + FileListReceiver(); + ~FileListReceiver(); + FileListTransferCBInterface *downloadHandler; + SystemAddress allowedSender; + unsigned short setID; + unsigned setCount; + unsigned setTotalCompressedTransmissionLength; + unsigned setTotalFinalLength; + unsigned setTotalDownloadedLength; + bool gotSetHeader; + bool deleteDownloadHandler; + bool isCompressed; + int filesReceived; + DataStructures::Map pushedFiles; + + // Notifications + unsigned int partLength; + +}; + +FileListReceiver::FileListReceiver() {filesReceived=0; setTotalDownloadedLength=0; partLength=1; DataStructures::Map::IMPLEMENT_DEFAULT_COMPARISON();} +FileListReceiver::~FileListReceiver() { + unsigned int i=0; + for (i=0; i < pushedFiles.Size(); i++) + rakFree_Ex(pushedFiles[i].flrMemoryBlock, __FILE__, __LINE__ ); +} + +FileListTransfer::FileListTransfer() +{ + setId=0; + callback=0; + DataStructures::Map::IMPLEMENT_DEFAULT_COMPARISON(); +} +FileListTransfer::~FileListTransfer() +{ + Clear(); +} +unsigned short FileListTransfer::SetupReceive(FileListTransferCBInterface *handler, bool deleteHandler, SystemAddress allowedSender) +{ + if (rakPeerInterface && rakPeerInterface->IsConnected(allowedSender)==false) + return (unsigned short)-1; + FileListReceiver *receiver; + + if (fileListReceivers.Has(setId)) + { + receiver=fileListReceivers.Get(setId); + receiver->downloadHandler->OnDereference(); + if (receiver->deleteDownloadHandler) + RakNet::OP_DELETE(receiver->downloadHandler, __FILE__, __LINE__); + RakNet::OP_DELETE(receiver, __FILE__, __LINE__); + fileListReceivers.Delete(setId); + } + + unsigned short oldId; + receiver = RakNet::OP_NEW( __FILE__, __LINE__ ); + RakAssert(handler); + receiver->downloadHandler=handler; + receiver->allowedSender=allowedSender; + receiver->gotSetHeader=false; + receiver->deleteDownloadHandler=deleteHandler; + fileListReceivers.Set(setId, receiver); + oldId=setId; + if (++setId==(unsigned short)-1) + setId=0; + return oldId; +} + +void FileListTransfer::Send(FileList *fileList, RakPeerInterface *rakPeer, SystemAddress recipient, unsigned short setID, PacketPriority priority, char orderingChannel, bool compressData, IncrementalReadInterface *_incrementalReadInterface, unsigned int _chunkSize) +{ + (void) compressData; + + if (callback) + fileList->SetCallback(callback); + + unsigned int i, totalLength; + RakNet::BitStream outBitstream; + bool sendReference; + const char *dataBlocks[2]; + int lengths[2]; + totalLength=0; + for (i=0; i < fileList->fileList.Size(); i++) + { + const FileListNode &fileListNode = fileList->fileList[i]; + totalLength+=fileListNode.fileLengthBytes; + } + + // Write the chunk header, which contains the frequency table, the total number of files, and the total number of bytes + bool anythingToWrite; + outBitstream.Write((MessageID)ID_FILE_LIST_TRANSFER_HEADER); + outBitstream.Write(setID); + anythingToWrite=fileList->fileList.Size()>0; + outBitstream.Write(anythingToWrite); + if (anythingToWrite) + { + outBitstream.WriteCompressed(fileList->fileList.Size()); + outBitstream.WriteCompressed(totalLength); + + if (rakPeer) + rakPeer->Send(&outBitstream, priority, RELIABLE_ORDERED, orderingChannel, recipient, false); + else + SendUnified(&outBitstream, priority, RELIABLE_ORDERED, orderingChannel, recipient, false); + + DataStructures::Queue filesToPush; + + for (i=0; i < fileList->fileList.Size(); i++) + { + sendReference = fileList->fileList[i].isAReference && _incrementalReadInterface!=0; + if (sendReference) + { + FileToPush *fileToPush = RakNet::OP_NEW(__FILE__,__LINE__); + fileToPush->fileListNode.context=fileList->fileList[i].context; + fileToPush->setIndex=i; + fileToPush->fileListNode.filename=fileList->fileList[i].filename; + fileToPush->fileListNode.fullPathToFile=fileList->fileList[i].fullPathToFile; + fileToPush->fileListNode.fileLengthBytes=fileList->fileList[i].fileLengthBytes; + fileToPush->fileListNode.dataLengthBytes=fileList->fileList[i].dataLengthBytes; + // fileToPush->systemAddress=recipient; + fileToPush->setID=setID; + fileToPush->packetPriority=priority; + fileToPush->orderingChannel=orderingChannel; + fileToPush->currentOffset=0; + fileToPush->incrementalReadInterface=_incrementalReadInterface; + fileToPush->chunkSize=_chunkSize; + filesToPush.Push(fileToPush,__FILE__,__LINE__); + } + else + { + outBitstream.Reset(); + outBitstream.Write((MessageID)ID_FILE_LIST_TRANSFER_FILE); + outBitstream << fileList->fileList[i].context; + // outBitstream.Write(fileList->fileList[i].context); + outBitstream.Write(setID); + stringCompressor->EncodeString(fileList->fileList[i].filename, 512, &outBitstream); + + outBitstream.WriteCompressed(i); + outBitstream.WriteCompressed(fileList->fileList[i].dataLengthBytes); // Original length in bytes + + outBitstream.AlignWriteToByteBoundary(); + + dataBlocks[0]=(char*) outBitstream.GetData(); + lengths[0]=outBitstream.GetNumberOfBytesUsed(); + dataBlocks[1]=fileList->fileList[i].data; + lengths[1]=fileList->fileList[i].dataLengthBytes; + SendListUnified(dataBlocks,lengths,2,priority, RELIABLE_ORDERED, orderingChannel, recipient, false); + } + } + + if (filesToPush.IsEmpty()==false) + { + FileToPushRecipient *ftpr=0; + filesToPushAllSameAddressMutex.Lock(); + for (unsigned int i=0; i < filesToPushAllSameAddress.Size(); i++) + { + if (filesToPushAllSameAddress[i]->systemAddress==recipient) + { + ftpr=filesToPushAllSameAddress[i]; + break; + } + } + if (ftpr==0) + { + ftpr = RakNet::OP_NEW(__FILE__,__LINE__); + ftpr->systemAddress=recipient; + filesToPushAllSameAddress.Push(ftpr, __FILE__,__LINE__); + } + while (filesToPush.IsEmpty()==false) + { + ftpr->filesToPush.Push(filesToPush.Pop(), __FILE__,__LINE__); + } + filesToPushAllSameAddressMutex.Unlock(); + SendIRIToAddress(recipient); + return; + } + } + else + { + if (rakPeer) + rakPeer->Send(&outBitstream, priority, RELIABLE_ORDERED, orderingChannel, recipient, false); + else + SendUnified(&outBitstream, priority, RELIABLE_ORDERED, orderingChannel, recipient, false); + } +} + +bool FileListTransfer::DecodeSetHeader(Packet *packet) +{ + bool anythingToWrite=false; + unsigned short setID; + RakNet::BitStream inBitStream(packet->data, packet->length, false); + inBitStream.IgnoreBits(8); + inBitStream.Read(setID); + FileListReceiver *fileListReceiver; + if (fileListReceivers.Has(setID)==false) + { +#ifdef _DEBUG + RakAssert(0); +#endif + return false; + } + fileListReceiver=fileListReceivers.Get(setID); + if (fileListReceiver->allowedSender!=packet->systemAddress) + { +#ifdef _DEBUG + RakAssert(0); +#endif + return false; + } + +#ifdef _DEBUG + RakAssert(fileListReceiver->gotSetHeader==false); +#endif + + inBitStream.Read(anythingToWrite); + + if (anythingToWrite) + { + inBitStream.ReadCompressed(fileListReceiver->setCount); + if (inBitStream.ReadCompressed(fileListReceiver->setTotalFinalLength)) + { + fileListReceiver->setTotalCompressedTransmissionLength=fileListReceiver->setTotalFinalLength; + fileListReceiver->gotSetHeader=true; + return true; + } + + } + else + { + if (fileListReceiver->downloadHandler->OnDownloadComplete()==false) + { + fileListReceiver->downloadHandler->OnDereference(); + fileListReceivers.Delete(setID); + if (fileListReceiver->deleteDownloadHandler) + RakNet::OP_DELETE(fileListReceiver->downloadHandler, __FILE__, __LINE__); + RakNet::OP_DELETE(fileListReceiver, __FILE__, __LINE__); + } + + return true; + } + + return false; +} + +bool FileListTransfer::DecodeFile(Packet *packet, bool isTheFileAndIsNotDownloadProgress) +{ + FileListTransferCBInterface::OnFileStruct onFileStruct; + RakNet::BitStream inBitStream(packet->data, packet->length, false); + inBitStream.IgnoreBits(8); + + unsigned int partCount=0; + unsigned int partTotal=0; + unsigned int partLength=0; + onFileStruct.fileData=0; + if (isTheFileAndIsNotDownloadProgress==false) + { + // Disable endian swapping on reading this, as it's generated locally in ReliabilityLayer.cpp + inBitStream.ReadBits( (unsigned char* ) &partCount, BYTES_TO_BITS(sizeof(partCount)), true ); + inBitStream.ReadBits( (unsigned char* ) &partTotal, BYTES_TO_BITS(sizeof(partTotal)), true ); + inBitStream.ReadBits( (unsigned char* ) &partLength, BYTES_TO_BITS(sizeof(partLength)), true ); + inBitStream.IgnoreBits(8); + // The header is appended to every chunk, which we continue to read after this statement flrMemoryBlock + } + inBitStream >> onFileStruct.context; + // inBitStream.Read(onFileStruct.context); + inBitStream.Read(onFileStruct.setID); + FileListReceiver *fileListReceiver; + if (fileListReceivers.Has(onFileStruct.setID)==false) + { + return false; + } + fileListReceiver=fileListReceivers.Get(onFileStruct.setID); + if (fileListReceiver->allowedSender!=packet->systemAddress) + { +#ifdef _DEBUG + RakAssert(0); +#endif + return false; + } + +#ifdef _DEBUG + RakAssert(fileListReceiver->gotSetHeader==true); +#endif + + if (stringCompressor->DecodeString(onFileStruct.fileName, 512, &inBitStream)==false) + { +#ifdef _DEBUG + RakAssert(0); +#endif + return false; + } + + inBitStream.ReadCompressed(onFileStruct.fileIndex); + inBitStream.ReadCompressed(onFileStruct.byteLengthOfThisFile); + onFileStruct.bytesDownloadedForThisFile=onFileStruct.byteLengthOfThisFile; + + if (isTheFileAndIsNotDownloadProgress) + { + // Support SendLists + inBitStream.AlignReadToByteBoundary(); + + onFileStruct.fileData = (char*) rakMalloc_Ex( (size_t) onFileStruct.byteLengthOfThisFile, __FILE__, __LINE__ ); + + inBitStream.Read((char*)onFileStruct.fileData, onFileStruct.byteLengthOfThisFile); + + fileListReceiver->setTotalDownloadedLength+=onFileStruct.byteLengthOfThisFile; + } + + + onFileStruct.numberOfFilesInThisSet=fileListReceiver->setCount; +// onFileStruct.setTotalCompressedTransmissionLength=fileListReceiver->setTotalCompressedTransmissionLength; + onFileStruct.byteLengthOfThisSet=fileListReceiver->setTotalFinalLength; + + // User callback for this file. + if (isTheFileAndIsNotDownloadProgress) + { + onFileStruct.bytesDownloadedForThisSet=fileListReceiver->setTotalDownloadedLength; + + FileListTransferCBInterface::FileProgressStruct fps; + fps.onFileStruct=&onFileStruct; + fps.partCount=0; + fps.partTotal=1; + fps.dataChunkLength=onFileStruct.byteLengthOfThisFile; + fps.firstDataChunk=onFileStruct.fileData; + fps.iriDataChunk=onFileStruct.fileData; + fps.allocateIrIDataChunkAutomatically=true; + fps.iriWriteOffset=0; + fileListReceiver->downloadHandler->OnFileProgress(&fps); + + // Got a complete file + // Either we are using IncrementalReadInterface and it was a small file or + // We are not using IncrementalReadInterface + if (fileListReceiver->downloadHandler->OnFile(&onFileStruct)) + rakFree_Ex(onFileStruct.fileData, __FILE__, __LINE__ ); + + fileListReceiver->filesReceived++; + + // If this set is done, free the memory for it. + if ((int) fileListReceiver->setCount==fileListReceiver->filesReceived) + { + if (fileListReceiver->downloadHandler->OnDownloadComplete()==false) + { + fileListReceiver->downloadHandler->OnDereference(); + if (fileListReceiver->deleteDownloadHandler) + RakNet::OP_DELETE(fileListReceiver->downloadHandler, __FILE__, __LINE__); + fileListReceivers.Delete(onFileStruct.setID); + RakNet::OP_DELETE(fileListReceiver, __FILE__, __LINE__); + } + } + + } + else + { + inBitStream.AlignReadToByteBoundary(); + + char *firstDataChunk; + unsigned int unreadBits = inBitStream.GetNumberOfUnreadBits(); + unsigned int unreadBytes = BITS_TO_BYTES(unreadBits); + firstDataChunk=(char*) inBitStream.GetData()+BITS_TO_BYTES(inBitStream.GetReadOffset()); + + onFileStruct.bytesDownloadedForThisSet=fileListReceiver->setTotalDownloadedLength+unreadBytes; + onFileStruct.bytesDownloadedForThisFile=onFileStruct.byteLengthOfThisFile; + + FileListTransferCBInterface::FileProgressStruct fps; + fps.onFileStruct=&onFileStruct; + fps.partCount=partCount; + fps.partTotal=partTotal; + fps.dataChunkLength=unreadBytes; + fps.firstDataChunk=firstDataChunk; + fps.iriDataChunk=0; + fps.allocateIrIDataChunkAutomatically=true; + fps.iriWriteOffset=0; + + // Remote system is sending a complete file, but the file is large enough that we get ID_PROGRESS_NOTIFICATION from the transport layer + fileListReceiver->downloadHandler->OnFileProgress(&fps); + + } + + return true; +} +PluginReceiveResult FileListTransfer::OnReceive(Packet *packet) +{ + switch (packet->data[0]) + { + case ID_FILE_LIST_TRANSFER_HEADER: + DecodeSetHeader(packet); + return RR_STOP_PROCESSING_AND_DEALLOCATE; + case ID_FILE_LIST_TRANSFER_FILE: + DecodeFile(packet, true); + return RR_STOP_PROCESSING_AND_DEALLOCATE; + case ID_FILE_LIST_REFERENCE_PUSH: + OnReferencePush(packet, true); + return RR_STOP_PROCESSING_AND_DEALLOCATE; + case ID_FILE_LIST_REFERENCE_PUSH_ACK: + OnReferencePushAck(packet); + return RR_STOP_PROCESSING_AND_DEALLOCATE; + case ID_DOWNLOAD_PROGRESS: + if (packet->length>sizeof(MessageID)+sizeof(unsigned int)*3) + { + if (packet->data[sizeof(MessageID)+sizeof(unsigned int)*3]==ID_FILE_LIST_TRANSFER_FILE) + { + DecodeFile(packet, false); + return RR_STOP_PROCESSING_AND_DEALLOCATE; + } + if (packet->data[sizeof(MessageID)+sizeof(unsigned int)*3]==ID_FILE_LIST_REFERENCE_PUSH) + { + OnReferencePush(packet, false); + return RR_STOP_PROCESSING_AND_DEALLOCATE; + } + } + break; + } + + return RR_CONTINUE_PROCESSING; +} +void FileListTransfer::OnRakPeerShutdown(void) +{ + Clear(); +} +void FileListTransfer::Clear(void) +{ + unsigned i; + for (i=0; i < fileListReceivers.Size(); i++) + { + fileListReceivers[i]->downloadHandler->OnDereference(); + if (fileListReceivers[i]->deleteDownloadHandler) + RakNet::OP_DELETE(fileListReceivers[i]->downloadHandler, __FILE__, __LINE__); + RakNet::OP_DELETE(fileListReceivers[i], __FILE__, __LINE__); + } + fileListReceivers.Clear(); + + filesToPushAllSameAddressMutex.Lock(); + for (unsigned int i=0; i < filesToPushAllSameAddress.Size(); i++) + { + FileToPushRecipient *ftpr = filesToPushAllSameAddress[i]; + for (unsigned int j=0; j < ftpr->filesToPush.Size(); j++) + RakNet::OP_DELETE(ftpr->filesToPush[j],__FILE__,__LINE__); + RakNet::OP_DELETE(ftpr,__FILE__,__LINE__); + } + filesToPushAllSameAddress.Clear(false,__FILE__,__LINE__); + filesToPushAllSameAddressMutex.Unlock(); + + //filesToPush.Clear(false, __FILE__, __LINE__); +} +void FileListTransfer::OnClosedConnection(SystemAddress systemAddress, RakNetGUID rakNetGUID, PI2_LostConnectionReason lostConnectionReason ) +{ + (void) lostConnectionReason; + (void) rakNetGUID; + + RemoveReceiver(systemAddress); +} +void FileListTransfer::CancelReceive(unsigned short setId) +{ + if (fileListReceivers.Has(setId)==false) + { +#ifdef _DEBUG + RakAssert(0); +#endif + return; + } + FileListReceiver *fileListReceiver=fileListReceivers.Get(setId); + fileListReceiver->downloadHandler->OnDereference(); + if (fileListReceiver->deleteDownloadHandler) + RakNet::OP_DELETE(fileListReceiver->downloadHandler, __FILE__, __LINE__); + RakNet::OP_DELETE(fileListReceiver, __FILE__, __LINE__); + fileListReceivers.Delete(setId); +} +void FileListTransfer::RemoveReceiver(SystemAddress systemAddress) +{ + unsigned i; + i=0; + while (i < fileListReceivers.Size()) + { + if (fileListReceivers[i]->allowedSender==systemAddress) + { + fileListReceivers[i]->downloadHandler->OnDereference(); + if (fileListReceivers[i]->deleteDownloadHandler) + RakNet::OP_DELETE(fileListReceivers[i]->downloadHandler, __FILE__, __LINE__); + RakNet::OP_DELETE(fileListReceivers[i], __FILE__, __LINE__); + fileListReceivers.RemoveAtIndex(i); + } + else + i++; + } + +// i=0; +// while (i < filesToPush.Size()) +// { +// if (filesToPush[i].systemAddress==systemAddress) +// { +// filesToPush.RemoveAtIndex(i); +// } +// else +// i++; +// } + + + filesToPushAllSameAddressMutex.Lock(); + for (unsigned int i=0; i < filesToPushAllSameAddress.Size(); i++) + { + if (filesToPushAllSameAddress[i]->systemAddress==systemAddress) + { + FileToPushRecipient *ftpr = filesToPushAllSameAddress[i]; + for (unsigned int j=0; j < ftpr->filesToPush.Size(); j++) + { + RakNet::OP_DELETE(ftpr->filesToPush[j], __FILE__,__LINE__); + } + RakNet::OP_DELETE(ftpr, __FILE__,__LINE__); + filesToPushAllSameAddress.RemoveAtIndexFast(i); + break; + } + } + filesToPushAllSameAddressMutex.Unlock(); + +} +bool FileListTransfer::IsHandlerActive(unsigned short setId) +{ + return fileListReceivers.Has(setId); +} +void FileListTransfer::SetCallback(FileListProgress *cb) +{ + callback=cb; +} +FileListProgress *FileListTransfer::GetCallback(void) const +{ + return callback; +} +void FileListTransfer::Update(void) +{ + unsigned i; + i=0; + while (i < fileListReceivers.Size()) + { + if (fileListReceivers[i]->downloadHandler->Update()==false) + { + fileListReceivers[i]->downloadHandler->OnDereference(); + if (fileListReceivers[i]->deleteDownloadHandler) + RakNet::OP_DELETE(fileListReceivers[i]->downloadHandler, __FILE__, __LINE__); + RakNet::OP_DELETE(fileListReceivers[i], __FILE__, __LINE__); + fileListReceivers.RemoveAtIndex(i); + } + else + i++; + } +} +void FileListTransfer::OnReferencePush(Packet *packet, bool isTheFileAndIsNotDownloadProgress) +{ + RakNet::BitStream refPushAck; + if (isTheFileAndIsNotDownloadProgress) + { + // This is not a progress notification, it is actually the entire packet + refPushAck.Write((MessageID)ID_FILE_LIST_REFERENCE_PUSH_ACK); + SendUnified(&refPushAck,HIGH_PRIORITY, RELIABLE, 0, packet->systemAddress, false); + } + else + { + // 12/23/09 Why do I care about ID_DOWNLOAD_PROGRESS for reference pushes? + return; + } + + FileListTransferCBInterface::OnFileStruct onFileStruct; + RakNet::BitStream inBitStream(packet->data, packet->length, false); + inBitStream.IgnoreBits(8); + + unsigned int partCount=0; + unsigned int partTotal=1; + unsigned int partLength=0; + onFileStruct.fileData=0; + if (isTheFileAndIsNotDownloadProgress==false) + { + // UNREACHABLE CODE + // Disable endian swapping on reading this, as it's generated locally in ReliabilityLayer.cpp + inBitStream.ReadBits( (unsigned char* ) &partCount, BYTES_TO_BITS(sizeof(partCount)), true ); + inBitStream.ReadBits( (unsigned char* ) &partTotal, BYTES_TO_BITS(sizeof(partTotal)), true ); + inBitStream.ReadBits( (unsigned char* ) &partLength, BYTES_TO_BITS(sizeof(partLength)), true ); + inBitStream.IgnoreBits(8); + // The header is appended to every chunk, which we continue to read after this statement flrMemoryBlock + } + + inBitStream >> onFileStruct.context; + // inBitStream.Read(onFileStruct.context); + inBitStream.Read(onFileStruct.setID); + FileListReceiver *fileListReceiver; + if (fileListReceivers.Has(onFileStruct.setID)==false) + { + return; + } + fileListReceiver=fileListReceivers.Get(onFileStruct.setID); + if (fileListReceiver->allowedSender!=packet->systemAddress) + { +#ifdef _DEBUG + RakAssert(0); +#endif + return; + } + +#ifdef _DEBUG + RakAssert(fileListReceiver->gotSetHeader==true); +#endif + + if (stringCompressor->DecodeString(onFileStruct.fileName, 512, &inBitStream)==false) + { +#ifdef _DEBUG + RakAssert(0); +#endif + return; + } + + inBitStream.ReadCompressed(onFileStruct.fileIndex); + inBitStream.ReadCompressed(onFileStruct.byteLengthOfThisFile); + unsigned int offset; + unsigned int chunkLength; + inBitStream.ReadCompressed(offset); + inBitStream.ReadCompressed(chunkLength); + + bool lastChunk; + inBitStream.Read(lastChunk); + bool finished = lastChunk && isTheFileAndIsNotDownloadProgress; + + if (isTheFileAndIsNotDownloadProgress==false) + fileListReceiver->partLength=partLength; + + FLR_MemoryBlock mb; + if (fileListReceiver->pushedFiles.Has(onFileStruct.fileIndex)==false) + { + mb.flrMemoryBlock=(char*) rakMalloc_Ex(onFileStruct.byteLengthOfThisFile, __FILE__, __LINE__); + fileListReceiver->pushedFiles.SetNew(onFileStruct.fileIndex, mb); + } + else + { + mb=fileListReceiver->pushedFiles.Get(onFileStruct.fileIndex); + } + + unsigned int unreadBits = inBitStream.GetNumberOfUnreadBits(); + unsigned int unreadBytes = BITS_TO_BYTES(unreadBits); + unsigned int amountToRead; + if (isTheFileAndIsNotDownloadProgress) + amountToRead=chunkLength; + else + amountToRead=unreadBytes; + + inBitStream.AlignReadToByteBoundary(); + + FileListTransferCBInterface::FileProgressStruct fps; + + if (isTheFileAndIsNotDownloadProgress) + { + if (mb.flrMemoryBlock) + { + // Either the very first block, or a subsequent block and allocateIrIDataChunkAutomatically was true for the first block + memcpy(mb.flrMemoryBlock+offset, inBitStream.GetData()+BITS_TO_BYTES(inBitStream.GetReadOffset()), amountToRead); + fps.iriDataChunk=mb.flrMemoryBlock+offset; + } + else + { + // In here mb.flrMemoryBlock is null + // This means the first block explicitly deallocated the memory, and no blocks will be permanently held by RakNet + fps.iriDataChunk=(char*) inBitStream.GetData()+BITS_TO_BYTES(inBitStream.GetReadOffset()); + } + + onFileStruct.bytesDownloadedForThisFile=offset+amountToRead; + } + else + { + fileListReceiver->setTotalDownloadedLength+=partLength; + onFileStruct.bytesDownloadedForThisFile=partCount*partLength; + fps.iriDataChunk=(char*) inBitStream.GetData()+BITS_TO_BYTES(inBitStream.GetReadOffset()); + } + onFileStruct.bytesDownloadedForThisSet=fileListReceiver->setTotalDownloadedLength; + + onFileStruct.numberOfFilesInThisSet=fileListReceiver->setCount; +// onFileStruct.setTotalCompressedTransmissionLength=fileListReceiver->setTotalCompressedTransmissionLength; + onFileStruct.byteLengthOfThisSet=fileListReceiver->setTotalFinalLength; + // Note: mb.flrMemoryBlock may be null here + onFileStruct.fileData=mb.flrMemoryBlock; + + unsigned int totalNotifications; + unsigned int currentNotificationIndex; + if (chunkLength==0 || chunkLength==onFileStruct.byteLengthOfThisFile) + totalNotifications=1; + else + totalNotifications = onFileStruct.byteLengthOfThisFile / chunkLength + 1; + + if (chunkLength==0) + currentNotificationIndex = 0; + else + currentNotificationIndex = offset / chunkLength; + + fps.onFileStruct=&onFileStruct; + fps.partCount=currentNotificationIndex; + fps.partTotal=totalNotifications; + fps.dataChunkLength=amountToRead; + fps.firstDataChunk=mb.flrMemoryBlock; + fps.allocateIrIDataChunkAutomatically=true; + fps.onFileStruct->fileData=mb.flrMemoryBlock; + fps.iriWriteOffset=offset; + + if (finished) + { + char *oldFileData=fps.onFileStruct->fileData; + if (fps.partCount==0) + fps.firstDataChunk=fps.iriDataChunk; + if (fps.partTotal==1) + fps.onFileStruct->fileData=fps.iriDataChunk; + fileListReceiver->downloadHandler->OnFileProgress(&fps); + + // Incremental read interface sent us a file chunk + // This is the last file chunk we were waiting for to consider the file done + if (fileListReceiver->downloadHandler->OnFile(&onFileStruct)) + rakFree_Ex(oldFileData, __FILE__, __LINE__ ); + fileListReceiver->pushedFiles.Delete(onFileStruct.fileIndex); + + fileListReceiver->filesReceived++; + + // If this set is done, free the memory for it. + if ((int) fileListReceiver->setCount==fileListReceiver->filesReceived) + { + if (fileListReceiver->downloadHandler->OnDownloadComplete()==false) + { + fileListReceiver->downloadHandler->OnDereference(); + fileListReceivers.Delete(onFileStruct.setID); + if (fileListReceiver->deleteDownloadHandler) + RakNet::OP_DELETE(fileListReceiver->downloadHandler, __FILE__, __LINE__); + RakNet::OP_DELETE(fileListReceiver, __FILE__, __LINE__); + } + } + } + else + { + if (isTheFileAndIsNotDownloadProgress) + { + // 12/23/09 Don't use OnReferencePush anymore, just use OnFileProgress + fileListReceiver->downloadHandler->OnFileProgress(&fps); + + if (fps.allocateIrIDataChunkAutomatically==false) + { + rakFree_Ex(fileListReceiver->pushedFiles.Get(onFileStruct.fileIndex).flrMemoryBlock, __FILE__, __LINE__ ); + fileListReceiver->pushedFiles.Get(onFileStruct.fileIndex).flrMemoryBlock=0; + } + } + else + { + // This is a download progress notification for a file chunk using incremental read interface + // We don't have all the data for this chunk yet + + // UNREACHABLE CODE + totalNotifications = onFileStruct.byteLengthOfThisFile / fileListReceiver->partLength + 1; + if (isTheFileAndIsNotDownloadProgress==false) + currentNotificationIndex = (offset+partCount*fileListReceiver->partLength) / fileListReceiver->partLength ; + else + currentNotificationIndex = (offset+chunkLength) / fileListReceiver->partLength ; + unreadBytes = onFileStruct.byteLengthOfThisFile - ((currentNotificationIndex+1) * fileListReceiver->partLength); + + if (rakPeerInterface) + { + // Thus chunk is incomplete + fps.iriDataChunk=0; + + fileListReceiver->downloadHandler->OnFileProgress(&fps); + } + } + } + + return; +} +void FileListTransfer::SendIRIToAddress(SystemAddress systemAddress) +{ + // Was previously using GetStatistics to get outgoing buffer size, but TCP with UnifiedSend doesn't have this + unsigned int bytesRead; + const char *dataBlocks[2]; + int lengths[2]; + unsigned int smallFileTotalSize=0; + RakNet::BitStream outBitstream; + + filesToPushAllSameAddressMutex.Lock(); + for (unsigned int ftpIndex=0; ftpIndex < filesToPushAllSameAddress.Size(); ftpIndex++) + { + FileToPushRecipient *ftpr = filesToPushAllSameAddress[ftpIndex]; + if (ftpr->systemAddress==systemAddress) + { + FileToPush *ftp = ftpr->filesToPush.Peek(); + + // Read and send chunk. If done, delete at this index + void *buff = rakMalloc_Ex(ftp->chunkSize, __FILE__, __LINE__); + if (buff==0) + { + filesToPushAllSameAddressMutex.Unlock(); + notifyOutOfMemory(__FILE__, __LINE__); + return; + } + + // Read the next file chunk + bytesRead=ftp->incrementalReadInterface->GetFilePart(ftp->fileListNode.fullPathToFile, ftp->currentOffset, ftp->chunkSize, buff, ftp->fileListNode.context); + + bool done = ftp->fileListNode.dataLengthBytes == ftp->currentOffset+bytesRead; + while (done && ftp->currentOffset==0 && ftpr->filesToPush.Size()>=2 && smallFileTotalSizechunkSize) + { + // Send all small files at once, rather than wait for ID_FILE_LIST_REFERENCE_PUSH. But at least one ID_FILE_LIST_REFERENCE_PUSH must be sent + outBitstream.Reset(); + outBitstream.Write((MessageID)ID_FILE_LIST_TRANSFER_FILE); + // outBitstream.Write(ftp->fileListNode.context); + outBitstream << ftp->fileListNode.context; + outBitstream.Write(ftp->setID); + stringCompressor->EncodeString(ftp->fileListNode.filename, 512, &outBitstream); + outBitstream.WriteCompressed(ftp->setIndex); + outBitstream.WriteCompressed(ftp->fileListNode.dataLengthBytes); // Original length in bytes + outBitstream.AlignWriteToByteBoundary(); + dataBlocks[0]=(char*) outBitstream.GetData(); + lengths[0]=outBitstream.GetNumberOfBytesUsed(); + dataBlocks[1]=(const char*) buff; + lengths[1]=bytesRead; + + SendListUnified(dataBlocks,lengths,2,ftp->packetPriority, RELIABLE_ORDERED, ftp->orderingChannel, systemAddress, false); + + // LWS : fixed freed pointer reference +// unsigned int chunkSize = ftp->chunkSize; + RakNet::OP_DELETE(ftp,__FILE__,__LINE__); + ftpr->filesToPush.Pop(); + smallFileTotalSize+=bytesRead; + //done = bytesRead!=ftp->chunkSize; + ftp = ftpr->filesToPush.Peek(); + + bytesRead=ftp->incrementalReadInterface->GetFilePart(ftp->fileListNode.fullPathToFile, ftp->currentOffset, ftp->chunkSize, buff, ftp->fileListNode.context); + done = ftp->fileListNode.dataLengthBytes == ftp->currentOffset+bytesRead; + } + + + outBitstream.Reset(); + outBitstream.Write((MessageID)ID_FILE_LIST_REFERENCE_PUSH); + // outBitstream.Write(ftp->fileListNode.context); + outBitstream << ftp->fileListNode.context; + outBitstream.Write(ftp->setID); + stringCompressor->EncodeString(ftp->fileListNode.filename, 512, &outBitstream); + outBitstream.WriteCompressed(ftp->setIndex); + outBitstream.WriteCompressed(ftp->fileListNode.dataLengthBytes); // Original length in bytes + outBitstream.WriteCompressed(ftp->currentOffset); + ftp->currentOffset+=bytesRead; + outBitstream.WriteCompressed(bytesRead); + outBitstream.Write(done); + + if (callback) + { + callback->OnFilePush(ftp->fileListNode.filename, ftp->fileListNode.fileLengthBytes, ftp->currentOffset-bytesRead, bytesRead, done, systemAddress); + } + + dataBlocks[0]=(char*) outBitstream.GetData(); + lengths[0]=outBitstream.GetNumberOfBytesUsed(); + dataBlocks[1]=(char*) buff; + lengths[1]=bytesRead; + //rakPeerInterface->SendList(dataBlocks,lengths,2,ftp->packetPriority, RELIABLE_ORDERED, ftp->orderingChannel, ftp->systemAddress, false); + SendListUnified(dataBlocks,lengths,2,ftp->packetPriority, RELIABLE_ORDERED, ftp->orderingChannel, systemAddress, false); + if (done) + { + // Done + RakNet::OP_DELETE(ftp,__FILE__,__LINE__); + ftpr->filesToPush.Pop(); + if (ftpr->filesToPush.Size()==0) + { + RakNet::OP_DELETE(ftpr,__FILE__,__LINE__); + filesToPushAllSameAddress.RemoveAtIndexFast(ftpIndex); + } + } + rakFree_Ex(buff, __FILE__, __LINE__ ); + break; + } + } + filesToPushAllSameAddressMutex.Unlock(); +} +void FileListTransfer::OnReferencePushAck(Packet *packet) +{ + SendIRIToAddress(packet->systemAddress); +} +unsigned int FileListTransfer::GetPendingFilesToAddress(SystemAddress recipient) +{ + filesToPushAllSameAddressMutex.Lock(); + for (unsigned int i=0; i < filesToPushAllSameAddress.Size(); i++) + { + if (filesToPushAllSameAddress[i]->systemAddress==recipient) + { + unsigned int size = filesToPushAllSameAddress[i]->filesToPush.Size(); + filesToPushAllSameAddressMutex.Unlock(); + return size; + } + + } + filesToPushAllSameAddressMutex.Unlock(); + return 0; +} + +#ifdef _MSC_VER +#pragma warning( pop ) +#endif + +#endif // _RAKNET_SUPPORT_* diff --git a/RakNet/Sources/FileListTransfer.h b/RakNet/Sources/FileListTransfer.h new file mode 100644 index 0000000..da34427 --- /dev/null +++ b/RakNet/Sources/FileListTransfer.h @@ -0,0 +1,136 @@ +/// \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.h" +#if _RAKNET_SUPPORT_FileListTransfer==1 + +#ifndef __FILE_LIST_TRANFER_H +#define __FILE_LIST_TRANFER_H + +#include "RakNetTypes.h" +#include "Export.h" +#include "PluginInterface2.h" +#include "DS_Map.h" +#include "RakNetTypes.h" +#include "PacketPriority.h" +#include "RakMemoryOverride.h" +#include "FileList.h" +#include "DS_Queue.h" +#include "SimpleMutex.h" + +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: + FileListTransfer(); + virtual ~FileListTransfer(); + + /// \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] compressData deprecated, unsupported + /// \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 Send(FileList *fileList, RakPeerInterface *rakPeer, SystemAddress recipient, unsigned short setID, PacketPriority priority, char orderingChannel, bool compressData, 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 Set 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 SetCallback(FileListProgress *cb); + + /// \returns what was sent to SetCallback + /// \return What was sent to SetCallback + FileListProgress *GetCallback(void) const; + + /// \internal For plugin handling + virtual PluginReceiveResult OnReceive(Packet *packet); + /// \internal For plugin handling + virtual void OnRakPeerShutdown(void); + /// \internal For plugin handling + virtual void OnClosedConnection(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 fileListReceivers; + unsigned short setId; + FileListProgress *callback; + + struct FileToPush + { + FileListNode fileListNode; + PacketPriority packetPriority; + char orderingChannel; + unsigned int currentOffset; + unsigned short setID; + unsigned int setIndex; + IncrementalReadInterface *incrementalReadInterface; + unsigned int chunkSize; + }; + struct FileToPushRecipient + { + SystemAddress systemAddress; + DataStructures::Queue filesToPush; + }; + DataStructures::List< FileToPushRecipient* > filesToPushAllSameAddress; + // TODO - overagressive, only one read can happen at a time. See SendIRIToAddress + SimpleMutex filesToPushAllSameAddressMutex; +}; + +#endif + +#endif // _RAKNET_SUPPORT_* diff --git a/RakNet/Sources/FileListTransferCBInterface.h b/RakNet/Sources/FileListTransferCBInterface.h new file mode 100644 index 0000000..5f74d03 --- /dev/null +++ b/RakNet/Sources/FileListTransferCBInterface.h @@ -0,0 +1,120 @@ +/// \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.h" +#include "FileListNodeContext.h" + +#ifdef _MSC_VER +#pragma warning( push ) +#endif + +/// \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; + }; + + // 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[in] allocateIrIDataChunkAutomatically If true, then RakNet will hold iriDataChunk for you and return it in OnFile. Defaults to true + bool allocateIrIDataChunkAutomatically; + }; + + 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(void) {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) {} +}; + +#ifdef _MSC_VER +#pragma warning( pop ) +#endif + +#endif + diff --git a/RakNet/Sources/FileOperations.cpp b/RakNet/Sources/FileOperations.cpp new file mode 100644 index 0000000..6d361d9 --- /dev/null +++ b/RakNet/Sources/FileOperations.cpp @@ -0,0 +1,171 @@ +#include "RakMemoryOverride.h" +#include "_FindFirst.h" // For linux +#include "FileOperations.h" +#include +#include +#ifdef _WIN32 +// For mkdir +#include +#include +#else +#include +#include +#include "_FindFirst.h" +#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__, __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; + + #ifdef _WIN32 + #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. + res = mkdir( pathCopy ); + #else + + res = mkdir( pathCopy, 0744 ); + #endif + if (res<0 && errno!=EEXIST && errno!=EACCES) + { + rakFree_Ex(pathCopy, __FILE__, __LINE__ ); + return false; + } + + pathCopy[ index ] = '/'; + } + + index++; + } + } + + if (data) + { + fp = fopen( path, "wb" ); + + if ( fp == 0 ) + { + rakFree_Ex(pathCopy, __FILE__, __LINE__ ); + return false; + } + + fwrite( data, 1, dataLength, fp ); + + fclose( fp ); + } + else + { +#ifdef _WIN32 +#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. + res = mkdir( pathCopy ); +#else + res = mkdir( pathCopy, 0744 ); +#endif + + if (res<0 && errno!=EEXIST) + { + rakFree_Ex(pathCopy, __FILE__, __LINE__ ); + return false; + } + } + + rakFree_Ex(pathCopy, __FILE__, __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 + + diff --git a/RakNet/Sources/FileOperations.h b/RakNet/Sources/FileOperations.h new file mode 100644 index 0000000..159fb49 --- /dev/null +++ b/RakNet/Sources/FileOperations.h @@ -0,0 +1,19 @@ +/// \file FileOperations.h +/// +/// This file is part of RakNet Copyright 2003 Jenkins Software LLC +/// +/// Usage of RakNet is subject to the appropriate license agreement. + +#ifndef __FILE_OPERATIONS_H +#define __FILE_OPERATIONS_H + +#include "Export.h" + +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 diff --git a/RakNet/Sources/FormatString.cpp b/RakNet/Sources/FormatString.cpp new file mode 100644 index 0000000..e4df09e --- /dev/null +++ b/RakNet/Sources/FormatString.cpp @@ -0,0 +1,30 @@ +#include "FormatString.h" +#include +#include +#include +#include "LinuxStrings.h" + +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; +} diff --git a/RakNet/Sources/FormatString.h b/RakNet/Sources/FormatString.h new file mode 100644 index 0000000..1ebdab9 --- /dev/null +++ b/RakNet/Sources/FormatString.h @@ -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.h" + +extern "C" { +char * FormatString(const char *format, ...); +} +// Threadsafe +extern "C" { +char * FormatStringTS(char *output, const char *format, ...); +} + + +#endif + diff --git a/RakNet/Sources/FullyConnectedMesh.cpp b/RakNet/Sources/FullyConnectedMesh.cpp new file mode 100644 index 0000000..995b652 --- /dev/null +++ b/RakNet/Sources/FullyConnectedMesh.cpp @@ -0,0 +1,91 @@ +/// \file +/// +/// This file is part of RakNet Copyright 2003 Jenkins Software LLC +/// +/// Usage of RakNet is subject to the appropriate license agreement. + +#include "NativeFeatureIncludes.h" +#if _RAKNET_SUPPORT_FullyConnectedMesh==1 + +#include "FullyConnectedMesh.h" +#include "RakPeerInterface.h" +#include "MessageIdentifiers.h" +#include "BitStream.h" +#include "ConnectionGraph.h" +#include +#include "RakAssert.h" + +#ifdef _MSC_VER +#pragma warning( push ) +#endif + +FullyConnectedMesh::FullyConnectedMesh() +{ + pw=0; +} + +FullyConnectedMesh::~FullyConnectedMesh() +{ + if (pw) + rakFree_Ex(pw, __FILE__, __LINE__ ); +} + +void FullyConnectedMesh::Startup(const char *password, int _passwordLength) +{ + if (pw) + rakFree_Ex(pw, __FILE__, __LINE__ ); + if (password) + { + pw = (char*) rakMalloc_Ex( _passwordLength, __FILE__, __LINE__ ); + memcpy(pw, password, _passwordLength); + passwordLength=_passwordLength; + } + else + pw=0; + +} + +PluginReceiveResult FullyConnectedMesh::OnReceive(Packet *packet) +{ + RakAssert(packet); + + switch (packet->data[0]) + { + case ID_REMOTE_NEW_INCOMING_CONNECTION: // This comes from the connection graph plugin + { + RakNet::BitStream b(packet->data, packet->length, false); + b.IgnoreBits(8); + ConnectionGraphGroupID group1, group2; + SystemAddress node1, node2; + RakNetGUID guid1, guid2; + b.Read(node1); + b.Read(group1); + b.Read(guid1); + if (rakPeerInterface->IsConnected(node1,true)==false) + { + char str1[64]; + node1.ToString(false, str1); + rakPeerInterface->Connect(str1, node1.port, pw, pw ? passwordLength : 0); + } + b.Read(node2); + b.Read(group2); + b.Read(guid2); + if (rakPeerInterface->IsConnected(node2,true)==false) + { + char str1[64]; + node2.ToString(false, str1); + rakPeerInterface->Connect(str1, node2.port, pw, pw ? passwordLength : 0); + } + + break; + } + } + + return RR_CONTINUE_PROCESSING; +} + +#ifdef _MSC_VER +#pragma warning( pop ) +#endif + +#endif // _RAKNET_SUPPORT_* diff --git a/RakNet/Sources/FullyConnectedMesh.h b/RakNet/Sources/FullyConnectedMesh.h new file mode 100644 index 0000000..c89913a --- /dev/null +++ b/RakNet/Sources/FullyConnectedMesh.h @@ -0,0 +1,54 @@ +/// \file FullyConnectedMesh.h +/// \brief Fully connected mesh plugin. 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.h" +#if _RAKNET_SUPPORT_FullyConnectedMesh==1 + +#ifndef __FULLY_CONNECTED_MESH_H +#define __FULLY_CONNECTED_MESH_H + +class RakPeerInterface; +#include "PluginInterface2.h" +#include "RakMemoryOverride.h" + +/// \defgroup FULLY_CONNECTED_MESH_GROUP FullyConnectedMesh +/// \brief Deprecated. Connect RakNet to other systems automatically. +/// \details +/// \ingroup PLUGINS_GROUP + +/// Fully connected mesh plugin. This will connect RakPeer to all connecting peers, and all peers the connecting peer knows about. +/// \deprecated Use FullyConnectedMesh2 +/// \pre You must also install the ConnectionGraph plugin. If you want a password, set it there. +/// \ingroup FULLY_CONNECTED_MESH_GROUP +class RAK_DLL_EXPORT FullyConnectedMesh : public PluginInterface2 +{ +public: + FullyConnectedMesh(); + virtual ~FullyConnectedMesh(); + + // -------------------------------------------------------------------------------------------- + // User functions + // -------------------------------------------------------------------------------------------- + /// Set the password to use to connect to the other systems + void Startup(const char *password, int _passwordLength); + + // -------------------------------------------------------------------------------------------- + // Packet handling functions + // -------------------------------------------------------------------------------------------- + virtual PluginReceiveResult OnReceive(Packet *packet); + + +protected: + char *pw; + int passwordLength; + + SystemAddress facilitator; +}; + +#endif + +#endif // _RAKNET_SUPPORT_* diff --git a/RakNet/Sources/FullyConnectedMesh2.cpp b/RakNet/Sources/FullyConnectedMesh2.cpp new file mode 100644 index 0000000..302cd1b --- /dev/null +++ b/RakNet/Sources/FullyConnectedMesh2.cpp @@ -0,0 +1,459 @@ +/// \file +/// +/// This file is part of RakNet Copyright 2003 Jenkins Software LLC +/// +/// Usage of RakNet is subject to the appropriate license agreement. + +#include "NativeFeatureIncludes.h" +#if _RAKNET_SUPPORT_FullyConnectedMesh2==1 + +#include "FullyConnectedMesh2.h" +#include "RakPeerInterface.h" +#include "MessageIdentifiers.h" +#include "BitStream.h" +#include "RakAssert.h" +#include "GetTime.h" +#include "Rand.h" + +// #define DEBUG_FCM2 + +FullyConnectedMesh2::FullyConnectedMesh2() +{ + startupTime=0; + totalConnectionCount=0; + ourFCMGuid=0; + autoParticipateConnections=true; + connectOnNewRemoteConnections=true; +} +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); +} +bool FullyConnectedMesh2::IsConnectedHost(void) const +{ + return GetConnectedHost()==rakPeerInterface->GetGuidFromSystemAddress(UNASSIGNED_SYSTEM_ADDRESS); +} +void FullyConnectedMesh2::SetAutoparticipateConnections(bool b) +{ + autoParticipateConnections=b; +} +void FullyConnectedMesh2::ResetHostCalculation(void) +{ + startupTime=RakNet::GetTimeUS(); + totalConnectionCount=0; + ourFCMGuid=0; + for (unsigned int i=0; i < participantList.Size(); i++) + SendFCMGuidRequest(participantList[i].rakNetGuid); +} +bool FullyConnectedMesh2::AddParticipantInternal( RakNetGUID rakNetGuid, FCM2Guid theirFCMGuid ) +{ + for (unsigned int i=0; i < participantList.Size(); i++) + { + if (participantList[i].rakNetGuid==rakNetGuid) + { + if (theirFCMGuid!=0) + participantList[i].fcm2Guid=theirFCMGuid; + return false; + } + } + + FCM2Participant participant; + participant.rakNetGuid=rakNetGuid; + participant.fcm2Guid=theirFCMGuid; + participantList.Push(participant,__FILE__,__LINE__); + + SendFCMGuidRequest(rakNetGuid); + + return true; +} +void FullyConnectedMesh2::AddParticipant( RakNetGUID rakNetGuid ) +{ + if (rakPeerInterface->IsConnected(rakPeerInterface->GetSystemAddressFromGuid(rakNetGuid),false,false)==false) + { +#ifdef DEBUG_FCM2 + printf("AddParticipant to %s failed (not connected)\n", rakNetGuid.ToString()); +#endif + return; + } + + AddParticipantInternal(rakNetGuid,0); +} +PluginReceiveResult FullyConnectedMesh2::OnReceive(Packet *packet) +{ + switch (packet->data[0]) + { + case ID_REMOTE_NEW_INCOMING_CONNECTION: + { + if (connectOnNewRemoteConnections) + { + 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.port,connectionPassword.C_String(),(int) connectionPassword.GetLength()); + } + } + } + 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; + } + 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(SystemAddress systemAddress, RakNetGUID rakNetGUID, PI2_LostConnectionReason lostConnectionReason ) +{ + (void) lostConnectionReason; + (void) systemAddress; + (void) rakNetGUID; + + unsigned int idx; + for (idx=0; idx < participantList.Size(); idx++) + { + if (participantList[idx].rakNetGuid==rakNetGUID) + { + participantList[idx]=participantList[participantList.Size()-1]; +#ifdef DEBUG_FCM2 + printf("Popping participant %s\n", participantList[participantList.Size()-1].rakNetGuid.ToString()); +#endif + + participantList.Pop(); + if (rakNetGUID==hostRakNetGuid && ourFCMGuid!=0) + { + if (participantList.Size()==0) + { + hostRakNetGuid=UNASSIGNED_RAKNET_GUID; + hostFCM2Guid=ourFCMGuid; + } + else + { + CalculateHost(&hostRakNetGuid, &hostFCM2Guid); + } + PushNewHost(hostRakNetGuid); + } + return; + } + } + +} +RakNetTimeUS FullyConnectedMesh2::GetElapsedRuntime(void) +{ + RakNetTimeUS curTime=RakNet::GetTimeUS(); + if (curTime>startupTime) + return curTime-startupTime; + else + return 0; +} +void FullyConnectedMesh2::OnNewConnection(SystemAddress systemAddress, RakNetGUID rakNetGUID, bool isIncoming) +{ + (void) isIncoming; + (void) rakNetGUID; + (void) systemAddress; + + if (autoParticipateConnections) + AddParticipant(rakNetGUID); +} +void FullyConnectedMesh2::Clear(void) +{ + participantList.Clear(false, __FILE__,__LINE__); + + totalConnectionCount=0; + ourFCMGuid=0; + lastPushedHost=UNASSIGNED_RAKNET_GUID; +} +void FullyConnectedMesh2::PushNewHost(const RakNetGUID &guid) +{ + Packet *p = rakPeerInterface->AllocatePacket(sizeof(MessageID)); + p->data[0]=ID_FCM2_NEW_HOST; + p->systemAddress=rakPeerInterface->GetSystemAddressFromGuid(guid); + p->systemAddress.systemIndex=(SystemIndex)-1; + p->guid=guid; + 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(participantList.Size()>0); + + // Return the lowest value of all FCM2Guid + FCM2Guid lowestFCMGuid=ourFCMGuid; +// SystemAddress associatedSystemAddress=UNASSIGNED_SYSTEM_ADDRESS; + RakNetGUID associatedRakNetGuid=rakPeerInterface->GetGuidFromSystemAddress(UNASSIGNED_SYSTEM_ADDRESS); + + DataStructures::DefaultIndexType idx; + for (idx=0; idx < participantList.Size(); idx++) + { + if (participantList[idx].fcm2Guid!=0 && participantList[idx].fcm2Guiddata,packet->length,false); + bsIn.IgnoreBytes(sizeof(MessageID)); + bool hasRemoteFCMGuid; + bsIn.Read(hasRemoteFCMGuid); + RakNetTimeUS 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 + + RakNetTimeUS 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(); + DataStructures::DefaultIndexType idx; + for (idx=0; idx < participantList.Size(); idx++) + SendOurFCMGuid(rakPeerInterface->GetSystemAddressFromGuid(participantList[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) + { + DataStructures::DefaultIndexType idx; + for (idx=0; idx < participantList.Size(); idx++) + SendOurFCMGuid(rakPeerInterface->GetSystemAddressFromGuid(participantList[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); + AddParticipantInternal(packet->guid,theirFCMGuid); + + CalculateAndPushHost(); +} +void FullyConnectedMesh2::GetParticipantCount(DataStructures::DefaultIndexType *participantListSize) const +{ + *participantListSize=participantList.Size(); +} + +unsigned int FullyConnectedMesh2::GetParticipantCount(void) const +{ + return participantList.Size(); +} +void FullyConnectedMesh2::CalculateAndPushHost(void) +{ + RakNetGUID newHostGuid; + FCM2Guid newFcmGuid; + if (ParticipantListComplete()) + { + CalculateHost(&newHostGuid, &newFcmGuid); + if (newHostGuid!=lastPushedHost) + { + hostRakNetGuid=newHostGuid; + hostFCM2Guid=newFcmGuid; + PushNewHost(hostRakNetGuid); + } + } +} +bool FullyConnectedMesh2::ParticipantListComplete(void) +{ + for (unsigned int i=0; i < participantList.Size(); i++) + { + if (participantList[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; +} + +#endif // _RAKNET_SUPPORT_* diff --git a/RakNet/Sources/FullyConnectedMesh2.h b/RakNet/Sources/FullyConnectedMesh2.h new file mode 100644 index 0000000..e6aa43d --- /dev/null +++ b/RakNet/Sources/FullyConnectedMesh2.h @@ -0,0 +1,217 @@ +/// \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.h" +#if _RAKNET_SUPPORT_FullyConnectedMesh2==1 + +#ifndef __FULLY_CONNECTED_MESH_2_H +#define __FULLY_CONNECTED_MESH_2_H + +class RakPeerInterface; +#include "PluginInterface2.h" +#include "RakMemoryOverride.h" +#include "DS_Multilist.h" +#include "NativeTypes.h" +#include "DS_List.h" +#include "RakString.h" + +typedef int64_t FCM2Guid; + +/// \brief Fully connected mesh plugin, revision 2 +/// \details This will connect RakPeer to all connecting peers, and all peers the connecting peer knows about.
+/// 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 +/// \ingroup FULLY_CONNECTED_MESH_GROUP +class RAK_DLL_EXPORT FullyConnectedMesh2 : public PluginInterface2 +{ +public: + FullyConnectedMesh2(); + virtual ~FullyConnectedMesh2(); + + /// When the message ID_REMOTE_NEW_INCOMING_CONNECTION arrives, we try to connect to that system + /// \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 compelte at the exact same time + /// \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; + + /// \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. + /// \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 + 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 + /// \param[in] participant The new participant + void AddParticipant(RakNetGUID rakNetGuid); + + unsigned int GetParticipantCount(void) const; + void GetParticipantCount(DataStructures::DefaultIndexType *participantListSize) const; + /// \internal + RakNetTimeUS 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(SystemAddress systemAddress, RakNetGUID rakNetGUID, PI2_LostConnectionReason lostConnectionReason ); + /// \internal + virtual void OnNewConnection(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; + }; + +protected: + void Clear(void); + void PushNewHost(const RakNetGUID &guid); + 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 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 + RakNetTimeUS 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 participantList; + + RakNetGUID lastPushedHost; + + // Optimization: Store last calculated host in these variables. + RakNetGUID hostRakNetGuid; + FCM2Guid hostFCM2Guid; + + RakNet::RakString connectionPassword; + bool connectOnNewRemoteConnections; +}; + +/* +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_* diff --git a/RakNet/Sources/FunctionThread.cpp b/RakNet/Sources/FunctionThread.cpp new file mode 100644 index 0000000..dc7c2e4 --- /dev/null +++ b/RakNet/Sources/FunctionThread.cpp @@ -0,0 +1,147 @@ +#include "FunctionThread.h" +#include "RakSleep.h" + +using namespace RakNet; + +#ifdef _MSC_VER +#pragma warning( push ) +#endif + +FunctionThread::FunctorAndContext WorkerThreadFunc(FunctionThread::FunctorAndContext input, bool *returnOutput, void* perThreadData) +{ + (void) perThreadData; + + FunctionThread::FunctorAndContext output; + input.functor->Process(input.context); + output.functor=input.functor; + output.context=input.context; + *returnOutput=true; + return output; +} +FunctionThread::FunctionThread() +{ + pr=0; +} +FunctionThread::~FunctionThread() +{ + StopThreads(false); +} +void FunctionThread::StartThreads(int numThreads) +{ + threadPool.StartThreads(numThreads, 0, 0, 0); +} +void FunctionThread::StopThreads(bool blockOnCurrentProcessing) +{ + // This ensures all waiting data is ultimately passed to a callback, so there are no leaks + CancelInput(); + while (blockOnCurrentProcessing && threadPool.IsWorking()) + { + CallResultHandlers(); + RakSleep(30); + } + threadPool.StopThreads(); +} +void FunctionThread::Push(Functor *functor, void *context) +{ + FunctorAndContext input; + input.functor=functor; + input.context=context; + threadPool.AddInput(WorkerThreadFunc, input); +} +void FunctionThread::CallResultHandlers(void) +{ + FunctorAndContext functorAndResult; + while (threadPool.HasOutputFast() && threadPool.HasOutput()) + { + functorAndResult = threadPool.GetOutput(); + functorAndResult.functor->HandleResult(false, functorAndResult.context); + if (pr) pr(functorAndResult); + } +} +void FunctionThread::CancelFunctorsWithContext(bool (*cancelThisFunctor)(FunctionThread::FunctorAndContext func, void *userData), void *userData) +{ + FunctorAndContext functorAndResult; + unsigned i; + threadPool.LockInput(); + for (i=0; i < threadPool.InputSize(); i++) + { + functorAndResult = threadPool.GetInputAtIndex(i); + if (cancelThisFunctor(functorAndResult, userData)) + { + functorAndResult.functor->HandleResult(true, functorAndResult.context); + if (pr) pr(functorAndResult); + } + } + threadPool.ClearInput(); + threadPool.UnlockInput(); +} +void FunctionThread::SetPostResultFunction(void (*postResult)(FunctionThread::FunctorAndContext func)) +{ + pr=postResult; +} +void FunctionThread::CancelInput(void) +{ + // We do it this way so that the callbacks get called so user-allocated data can be freed. + FunctorAndContext functorAndResult; + unsigned i; + threadPool.LockInput(); + for (i=0; i < threadPool.InputSize(); i++) + { + functorAndResult = threadPool.GetInputAtIndex(i); + functorAndResult.functor->HandleResult(true, functorAndResult.context); + if (pr) pr(functorAndResult); + } + threadPool.ClearInput(); + threadPool.UnlockInput(); +} + +FunctionThreadDependentClass::FunctionThreadDependentClass() +{ + functionThreadWasAllocated=false; + functionThread=0; +} +FunctionThreadDependentClass::~FunctionThreadDependentClass() +{ + if (functionThreadWasAllocated) + RakNet::OP_DELETE(functionThread, __FILE__, __LINE__); +} + +void FunctionThreadDependentClass::AssignFunctionThread(FunctionThread *ft) +{ + if (functionThread && functionThreadWasAllocated) + { + functionThread->StopThreads(true); + RakNet::OP_DELETE(functionThread, __FILE__, __LINE__); + } + + functionThread=ft; + functionThreadWasAllocated=false; +} + +void FunctionThreadDependentClass::StartFunctionThread(void) +{ + if (functionThread==0) + { + functionThread = RakNet::OP_NEW( __FILE__, __LINE__ ); + functionThreadWasAllocated=true; + } + + functionThread->StartThreads(1); +} +FunctionThread *FunctionThreadDependentClass::GetFunctionThread(void) const +{ + return functionThread; +} +bool FunctionThreadDependentClass::GetFunctionThreadWasAllocated(void) const +{ + return functionThreadWasAllocated; +} +void FunctionThreadDependentClass::PushFunctor(Functor *functor, void *context) +{ + StartFunctionThread(); + functionThread->Push(functor, context); +} + +#ifdef _MSC_VER +#pragma warning( pop ) +#endif diff --git a/RakNet/Sources/FunctionThread.h b/RakNet/Sources/FunctionThread.h new file mode 100644 index 0000000..df28f3f --- /dev/null +++ b/RakNet/Sources/FunctionThread.h @@ -0,0 +1,122 @@ +/// \file FunctionThread.h +/// \brief A set of classes to make it easier to perform asynchronous function processing. +/// +/// This file is part of RakNet Copyright 2003 Jenkins Software LLC +/// +/// Usage of RakNet is subject to the appropriate license agreement. + + +#ifndef __FUNCTION_THREAD_H +#define __FUNCTION_THREAD_H + +#include "SingleProducerConsumer.h" +#include "ThreadPool.h" +#include "RakMemoryOverride.h" +#include "Export.h" + +namespace RakNet +{ + +// Forward declarations +class Functor; + +/// FunctionThread takes a stream of classes that implement a processing function, processes them in a thread, and calls a callback with the result. +/// It's a useful way to call blocking functions that you do not want to block, such as file writes and database operations. +class RAK_DLL_EXPORT FunctionThread +{ +public: + FunctionThread(); + ~FunctionThread(); + + struct FunctorAndContext + { + Functor *functor; + void *context; + }; + + /// \brief Starts the thread up. + void StartThreads(int numThreads); + + /// \brief Stop processing. + /// \details Will also call FunctorResultHandler callbacks with /a wasCancelled set to true. + /// \param[in] blockOnCurrentProcessing Wait for the current processing to finish? + void StopThreads(bool blockOnCurrentProcessing); + + /// \brief Add a functor to the incoming stream of functors. + /// \note functor MUST be a valid pointer until Functor::HandleResult() is called, at which point the pointer is returned to you. + /// \note For practical purposes this means the instance of functor you pass to this function has to be allocated using new and delete. + /// \note You should deallocate the pointer inside Functor::HandleResult() + /// \param[in] functor A pointer to an implemented Functor class + /// \param[in] If there is some context to this functor you want to look up to cancel it, you can set it here. Returned back to you in Functor::HandleResult + void Push(Functor *functor, void *context=0); + + /// \brief Call FunctorResultHandler callbacks. + /// \details Normally you would call this once per update cycle, although you do not have to. + void CallResultHandlers(void); + + /// \brief If you want to cancel input and output functors associated with some context, you can pass a function to do that here + /// \param[in] cancelThisFunctor Function should return true to cancel the functor, false to let it process + /// \param[in] userData Pointer to whatever you want. Passed to the cancelThisFunctor call + void CancelFunctorsWithContext(bool (*cancelThisFunctor)(FunctorAndContext func, void *userData), void *userData); + + /// \brief If you want to automatically do some kind of processing on every functor after Functor::HandleResult is called, set it here. + /// \details Useful to cleanup FunctionThread::Push::context + /// \param[in] postResult pointer to a C function to do post-processing + void SetPostResultFunction(void (*postResult)(FunctorAndContext func)); + + +protected: + void CancelInput(void); + ThreadPool threadPool; + + void (*pr)(FunctorAndContext func); +}; + +/// A functor is a single unit of processing to send to the Function thread. +/// Derive from it, add your data, and implement the processing function. +class Functor +{ +public: + Functor() {} + virtual ~Functor() {} + + /// \brief Do whatever processing you want. + /// \param[in] context pointer passed to FunctionThread::Push::context + virtual void Process(void *context)=0; + /// \brief Called from FunctionThread::CallResultHandlers with wasCancelled false OR + /// Called from FunctionThread::StopThread or FunctionThread::~FunctionThread with wasCancelled true + /// \param[in] wasCancelledTrue if CallResultHandlers was called, false if StopThreads or CancelInputWithContext was called before Functor::Process() + /// \param[in] context pointer passed to FunctionThread::Push::context + virtual void HandleResult(bool wasCancelled, void *context)=0; +}; + +class RAK_DLL_EXPORT FunctionThreadDependentClass +{ +public: + FunctionThreadDependentClass(); + virtual ~FunctionThreadDependentClass(); + + /// \brief Assigns a function thread to process asynchronous calls. If you do not assign one then one will be created automatically. + /// \param[in] ft An instance of a running function thread class. This class can be shared and used for other functors as well. + virtual void AssignFunctionThread(FunctionThread *ft); + + /// \return Returns the function thread held in the class + FunctionThread *GetFunctionThread(void) const; + + /// \returns Whether or not this class allocated the function thread by itself + bool GetFunctionThreadWasAllocated(void) const; + + /// \brief Allocates and starts the thread if needed, and pushes the functor. + /// \param[in] functor Functor to push + /// \param[in] context Sent to FunctionThread::Push::context + virtual void PushFunctor(Functor *functor, void *context=0); +protected: + /// Allocates and starts the function thread, if necessary + void StartFunctionThread(); + FunctionThread *functionThread; + bool functionThreadWasAllocated; +}; + +} + +#endif diff --git a/RakNet/Sources/Gen_RPC8.cpp b/RakNet/Sources/Gen_RPC8.cpp new file mode 100644 index 0000000..6ff93ba --- /dev/null +++ b/RakNet/Sources/Gen_RPC8.cpp @@ -0,0 +1,758 @@ +#include "Gen_RPC8.h" + +unsigned int GenRPC::BuildStack(char *stack) +{ + char *stackPtr = (char*) stack; + SerializeHeader(stackPtr, 0); + return (unsigned int)(stackPtr-stack); +} + +/// \internal +void GenRPC::Push( char*& p, char*const i, bool doEndianSwap) { + (void) doEndianSwap; + size_t len = strlen( i ) + 1; + memcpy( (void*)p, i, len ); + p += len; +} + +/// \internal +void GenRPC::Push( char*& p, const char*const i, bool doEndianSwap ) { + (void) doEndianSwap; + size_t len = strlen( i ) + 1; + memcpy( (void*)p, i, len ); + p += len; +} +/// \internal +unsigned GenRPC::D_type( const char*const ) { return STR_PARAM; } +/// \internal +unsigned GenRPC::D_type( char*const ) { return STR_PARAM; } + +/// \internal +unsigned GenRPC::D_type( float ) { return REAL_PARAM; } +/// \internal +unsigned GenRPC::D_type( double ) { return REAL_PARAM; } +/// \internal +unsigned GenRPC::D_type( long double ) { return REAL_PARAM; } + +/// \internal +size_t GenRPC::D_size( char*const str ) { return strlen( str ) + 1; } +/// \internal +size_t GenRPC::D_size( char const *const str ){ return strlen( str ) + 1; } + +void GenRPC::SerializeHeader(char *&out, unsigned int numParams) +{ + *out = (char) numParams; + out++; + //out[*writeOffset]=(char) numParams; + //*writeOffset+=sizeof(unsigned char); +} + + +// +// @params +// call: [IN/OUT] workspace to build parameters that we will pass to function +// in: [IN/OUT] is the serialized buffer - used as a temporary working for swapping +// parameters. +// inLength: [IN] is the length of the above +// lastParam: [IN] final parameter, added onto the list +// thisPtr: [IN] if not zero - the value of this (added to start of list). +// +// @returns: +// true: parameter list created successfully. +// false: if deserialization fails for some reason. +// +bool GenRPC::DeserializeParametersAndBuildCall( + CallParams &call, + char *in, unsigned int inLength, + void *lastParam, void *thisPtr + ) { + +#if AUTO_RPC_ABI + + NaturalWord *intCallParam = call.intParams; + + char *refParam = call.refParams; + +#if AUTO_RPC_ALLOC_SEPARATE_FLOATS + HardwareReal *realCallParam = call.realParams; +#endif + +#if AUTO_RPC_CREATE_FLOAT_MAP + call.realMap = 0; + call.numRealParams = 0; +#endif + +#if AUTO_RPC_ABI == AUTO_RPC_ABI_SYSV_AMD64 + // large structure parameters have to be bumped up here - which corresponds with the start + // of the parameters that *are* passed on the stack. + NaturalWord *memParam = &call.intParams[ AUTO_RPC_INT_REG_PARAMS ]; +#endif + + // this is first arg - assume space ;-) +#pragma warning(disable:4311) // pointer truncation + if ( thisPtr ) + *(intCallParam++) = reinterpret_cast( thisPtr ); + + unsigned int serializedArgs = *(unsigned char*)in; + + unsigned char* header = (unsigned char*)in + 1; + + unsigned char* data = &header[ serializedArgs * ( sizeof( unsigned int ) + 1 ) ]; + + // check we have the entire header in buffer + if ( data > (unsigned char*) &in[ inLength ] ) + return 0; + + for ( unsigned int i = 0; i < serializedArgs; i++ ) + { + + // read header entry + int plen = *(unsigned int*)header; + header += sizeof( unsigned int ); + unsigned char const flags = *( header++ ); + + // Some bits are not used by the current implementation. So if we find them we bail because + // we clearly are not equipped to handle the data - and is there no safe way to "fail". + if ( flags & RESERVED_BITS ) + return 0; + +#ifndef __BITSTREAM_NATIVE_END + if (flags & DO_ENDIAN_SWAP) + { + RakNet::BitStream::ReverseBytesInPlace( (unsigned char*)&plen , sizeof( plen ) ); + } +#endif + + if ( !plen || data + plen > (unsigned char*)&in[ inLength ] ) + return 0; + + // handle null-terminated strings. + if ( ( flags & PARAM_TYPE_MASK ) == STR_PARAM ) + { + + if ( intCallParam + 1 >= AUTO_RPC_ARRAY_END( call.intParams ) ) + return 0; + + // Check this has some sort of null termination. NB this assumes string is genuine Ascii + // or UTF+8; using unicode (ie. UTF+16, UCS) could break this. + if ( data[ plen - 1 ] != 0 ) + return 0; + + // The string doesn't need to be aligned, so we leave it in place; saving a copy, and + // preventing clogging up of our buffers with data. + +#pragma warning(disable:4311) // pointer truncation + *( intCallParam++ ) = reinterpret_cast( data ); + + data += plen; + + continue; + } + +#ifndef __BITSTREAM_NATIVE_END + if (flags & DO_ENDIAN_SWAP) + { + RakNet::BitStream::ReverseBytesInPlace( (unsigned char*)data , plen ); + } +#endif + + // convert pointer to ref. + if ( ( flags & PARAM_TYPE_MASK ) == REF_PARAM +#if AUTO_RPC_PARAMETER_REFERENCE_THRESHOLD + || plen > AUTO_RPC_PARAMETER_REFERENCE_THRESHOLD +#endif + ) + { + char *nextRefParam = refParam + AUTO_RPC__ALIGN_P2( plen, AUTO_RPC_REF_ALIGN ); + + if ( nextRefParam >= AUTO_RPC_ARRAY_END( call.refParams ) || intCallParam + 1 >= AUTO_RPC_ARRAY_END( call.intParams ) ) + return 0; + + memcpy( refParam, data, plen ); + +#pragma warning(disable:4311) // pointer truncation + *( intCallParam++ ) = reinterpret_cast( refParam ); + + refParam = nextRefParam; + + data += plen; + + continue; + } + + // Guarantee we have space on the output stack to accommodate the parameter. + NaturalWord *nextParam = (NaturalWord*)( (char*)intCallParam + AUTO_RPC_ALIGN_P2( plen, NaturalWord ) ); + if ( nextParam >= AUTO_RPC_ARRAY_END( call.intParams ) ) + return 0; + +#if AUTO_RPC_ALLOC_SEPARATE_FLOATS + if ( ( flags & PARAM_TYPE_MASK ) == REAL_PARAM + // long doubles, of various sizes (10, 16), all get passed on the stack + && (size_t) plen <= sizeof(double) + // once we've allocated all our floats, they get treated as ordinary int params + && realCallParam < AUTO_RPC_ARRAY_END( call.realParams ) + ) + { + + if ( plen != sizeof( float ) && plen != sizeof( double ) ) { + printf("illegal float size %d\n", plen ); + // We can't handle it - it's not a real real :lol: + return 0; + } + +#ifdef __BIG_ENDIAN__ + memcpy( (char*)( realCallParam + 1 ) - plen, data, plen ); +#else + memcpy( (char*)realCallParam, data, plen ); +#endif + +#if !AUTO_RPC_INT_SHADOW_OF_FLOATS + // we didn't use the int slot, so don't allow an advance. + nextParam = intCallParam; +#endif + + // next time, we use the next Real slot + realCallParam++; + } +#if !AUTO_RPC_INT_SHADOW_OF_FLOATS + else +#endif +#endif // AUTO_RPC_ALLOC_SEPARATE_FLOATS + { + // the processor can atomically zero-extend small types, so even with the test, + // it should be faster than memcpy+memset. + if ( plen == 1 ) + *intCallParam = *(uint8_t*)data; // should resolve to movzx and a move + else if ( plen == 2 ) + *intCallParam = *(uint16_t*)data; // if network order replace use htons(), and skip EndianSwap() + else if ( plen == 4 ) + *intCallParam = *(uint32_t*)data; // if network order replace use htonl(), and skip EndianSwap() +#if AUTO_RPC_AUTORPC_WORD == 64 + else if ( plen == 8 ) + *intCallParam = *(uint64_t*)data; +#endif + +#if AUTO_RPC_ABI == AUTO_RPC_ABI_SYSV_AMD64 + // + // SYSV ABI: aggregates greater 16 bytes must go on the stack; + // in practice, that means they can't come below AUTO_RPC_INT_REG_PARAMS when we call a function. + // + else if ( plen > 16 || ( plen > 8 && intCallParam == &call.intParams[ AUTO_RPC_INT_REG_PARAMS - 1] ) || ( flags & REAL_PARAM ) ) + { + if ( intCallParam < memParam ) + { + NaturalWord*const nextMemParam = (NaturalWord*)( (char*)memParam + AUTO_RPC_ALIGN_P2( plen, NaturalWord ) ); + + if ( nextMemParam >= AUTO_RPC_ARRAY_END( call.intParams ) ) + return 0; + + memcpy( memParam, data, plen ); + + // prevent advancing the ptr slot, since we didn't use it. + nextParam = intCallParam; + + // but advance the memparam + memParam = nextMemParam; + } + else + { + memcpy( (void*)intCallParam, data, plen ); + } + } +#endif // AUTO_RPC_ABI_SYSV_AMD64 + else + { + // We don't need to worry about alignment, because any type that's not a whole multiple + // of the natual word size will be an aggregate and that should be at the base of memory - + // this is true for some PowerPC systems (see [e]) but not all. But hey, you + // probably should be passing such structs by reference. + // + // Zeroing is also unecessary as code shouldn't be reading beyodn the bounds of the structure. + // + memcpy( (void*)intCallParam, data, plen ); + } + + } + +#if AUTO_RPC_ABI == AUTO_RPC_ABI_SYSV_AMD64 + // skip over any stored "class MEMORY" (see [b]) parameters. + if ( nextParam == &call.intParams[AUTO_RPC_INT_REG_PARAMS] ) + intCallParam = memParam; + else +#endif + // advance to next output param + intCallParam = nextParam; + +#if !AUTO_RPC_ALLOC_SEPARATE_FLOATS && AUTO_RPC_CREATE_FLOAT_MAP + if ( ( flags & PARAM_TYPE_MASK ) == REAL_PARAM && i < AUTO_RPC_FLOAT_REG_PARAMS && ( plen == sizeof( double ) || plen == sizeof( float ) ) ) + { + call.numRealParams++; + call.realMap |= ( 1 << i ); + } +#endif + + // advance to next input arg. + data += plen; + } + + // space for lastParam? + if ( &intCallParam[1] >= AUTO_RPC_ARRAY_END( call.intParams ) ) + return 0; + +#pragma warning(disable:4311) // pointer truncation + *( intCallParam++ ) = reinterpret_cast( lastParam ); + +#if AUTO_RPC_ABI == AUTO_RPC_ABI_SYSV_AMD64 + // figure out how many args we have notched up. + if ( memParam > &call.intParams[AUTO_RPC_INT_REG_PARAMS] && memParam > intCallParam ) + intCallParam = memParam; +#endif + + // convert from ptrdif_t to unsigned int; should be small enough, even if its 64-bit pointers. + call.numIntParams = ( unsigned int )( intCallParam - call.intParams ); + +#if AUTO_RPC_FLOAT_REG_PARAMS && AUTO_RPC_ALLOC_SEPARATE_FLOATS + call.numRealParams = ( unsigned int )( realCallParam - call.realParams ); +#endif + + return 1; +#else // AUTO_RPC_ABI + return 0; +#endif + +} + + +// +// @params +// callParams: [IN] parameter list +// functionPtr: [IN] function to call. +// +// @returns: +// true: function was called. +// false: too many parameters, probably. +// +bool GenRPC::CallWithStack( CallParams& call, void *functionPtr ) { +#if AUTO_RPC_ABI + // Are we x86-32? +#if !defined( AUTO_RPC_NO_ASM ) && ( defined(__i386__) || defined( _M_IX86 ) || defined( __INTEL__ ) ) +#if !defined(__GNUC__) + // Intel dialect assembly + NaturalWord const paramc = call.numIntParams; +#pragma warning(disable:4311) // pointer truncation + NaturalWord const paramv = reinterpret_cast( call.intParams ); + _asm + { + + // Load numbytes. + mov ecx, paramc + + // allocate space on the stack for all these params + lea edi,dword ptr[ecx * 4] + sub esp,edi + + // setup source of copy + mov esi, paramv + + // Setup the destination of the copy: the return stack. + mov edi,esp + + // copy data + rep movs dword ptr es:[edi],dword ptr [esi] + + // call the function + call functionPtr + + // Restore the stack to its state, prior to our invocation. + // + // Detail: edi is one of the registers that must be preserved + // across function calls. (The compiler should be saving it for us.) + // + // We left edi pointing to the end of the block copied; i.e. the state + // of the stack prior to copying our params. So by loading it + // into the esp we can restore the return stack to the state prior + // to the copy. + // + mov esp,edi + }; +#else + // GCC has its own form of asm block - so we'll always have to write two versions. + // Therefore, as we're writing it twice, we use the ATT dialect, because only later + // gcc support Intel dialect. This one also aligns the stack to a multiple of 16 bytes; which + // windows doesn't seem to care about. + // Be aware, we can't use offset of to get the address, as gcc insists on sticking. + // NaturalWord const paramv = reinterpret_cast( call.intParams ); + asm (\ + "lea 4(%%ecx),%%esi\n\ + mov (%%ecx),%%ecx\n\ + lea (,%%ecx,4),%%edi\n\ + sub %%edi,%%esp\n\ + mov $12,%%edx\n\ + and %%esp,%%edx\n\ + sub %%edx,%%esp\n\ + mov %%esp,%%edi\n\ + rep movsl %%ds:(%%esi),%%es:(%%edi)\n\ + add %%edx,%%edi\n\ + call *%1\n\ + mov %%edi,%%esp"\ + : /* no outputs */\ + : "c" ( &call ), "m" (functionPtr)\ + : "%edi" , "%esi", "%edx", "%eax"\ + ); +#endif // GNUC vs non GNUC + return 1; +#elif !defined( AUTO_RPC_NO_ASM ) && ( defined( _M_X64 ) || defined( __x86_64__ ) || defined( _M_AMD64 ) ) +#if AUTO_RPC_ABI == AUTO_RPC_ABI_WIN_AMD64 + NaturalWord const paramv = reinterpret_cast( call.intParams ); + _asm { + // rcx := number of qwords to copy + mov rcx, paramc + + // r9 := 0 + sub r9,r9 + + // rsi => our parameter list. + mov rsi, paramv + + // r9 := -(number of qwords to copy) + sub r9,rcx + + // Preparation to align the stack to 16 byte boundary + mov rdx,8 + + // rdi => projected bottom of stack + lea rdi,dword ptr[rsp + r9 * 8] + + // figure out if stack needs aligning + and rdx,rdi + + // save stack into rbx + mov rbx,rsp + + // align stack + sub rdi,rdx + mov rsp,rdi + + // rdx => our parameter list + mov rdx,rsi + + // + // copy data - we copy all parameters, because we have to + // create a shadow area; and this way its easiest. + // + rep movs qword ptr es:[edi],qword ptr [esi] + + // load registers + // rcx|xmm0, rdx|xmm1,r8|xmm2,r9|xmm3 + mov rcx,qword ptr [rdx] + mov r8,qword ptr 16[rdx] + movq xmm0,rcx + mov r9,qword ptr 24[rdx] + movq xmm2,r8 + mov rdx,qword ptr 8[rdx] + movq xmm3,r9 + movq xmm1,rdx + + // call the function + call functionPtr + + // Restore the stack to its state, prior to our invocation - + // saved in rbx. + mov rsp,rbx + } +#elif AUTO_RPC_ABI == AUTO_RPC_ABI_SYSV_AMD64 + // + // GCC won't generate a stack frame on higher optimization levels, so we don't touch it. + // on -O3 it inlines the code, breaking it because of the jump reference. + // + // I figure a 64-bit compiler will be recent enough to do Intel syntax. May need to change + // my mind on that. NB. Structure members are hard coded into this. + // + asm (\ + ".intel_syntax noprefix\n\ + push rbx\n\ + mov rax,rsi\n\ + push r15\n\ + mov ecx,dword ptr[rdi+8+8*8]\n\ + lea rsi,[rdi+8+8*8+8]\n\ + mov r15,rsp\n\ + lea rbx,[rdi+8]\n\ + sub r8,r8\n\ + sub rcx,6\n\ + lea r9,[rsi + 6 * 8]\n\ + jbe .L1\n\ + sub r8,rcx\n\ + mov rdx,8\n\ + lea rdi,qword ptr[rsp + r8 * 8]\n\ + and rdx,rdi\n\ + mov rsi,r9\n\ + sub rdi,rdx\n\ + mov rsp,rdi\n\ + rep movsq \n\ + .L1:\n\ + movq xmm0,[rbx]\n\ + movq xmm1,[rbx+8]\n\ + movq xmm2,[rbx+16]\n\ + movq xmm3,[rbx+24]\n\ + movq xmm4,[rbx+32]\n\ + movq xmm5,[rbx+40]\n\ + movq xmm6,[rbx+48]\n\ + movq xmm7,[rbx+56]\n\ + mov rdi,[r9-48]\n\ + mov rsi,[r9-40]\n\ + mov rdx,[r9-32]\n\ + mov rcx,[r9-24]\n\ + mov r8,[r9-16]\n\ + mov r9,[r9-8]\n\ + call rax\n\ + mov rsp,r15\n\ + pop r15\n\ + pop rbx\n\ + .att_syntax prefix"\ + : /* no outputs */\ + : "D" ( &call ), "S" (functionPtr)\ + : "%rdx", "%rcx" , "%r8", "%r9", "%rax",\ + "%xmm0", "%xmm1", "%xmm2", "%xmm3", "%xmm4", "%xmm5", "%xmm6", "%xmm7" ); + // : "D", ( &call ), "c" ( &call.numIntParams ), "S" ( paramv ), "b" ( floatv ), "a" (functionPtr) +#else +#error unsupport ABI +#endif + return 1; +#else + // AUTO_RPC_NO_ASM or no x86-32/x86-64 + // + // 4. Passing the parameters. + // + // The compiler knows how to call functions, so having sorted out the argument list, + // we just pass it to a function of the correct form - and let the compiler align stacks, + // load registers, place parameters where they should be. + // + // This is particularly important as GCC has control over the stack frame, and it can + // improperly frame it - for instance utilising red zones to save args, rather than pushing them. + // On PowerPC it must create the parameter passing area, too. + // + // The most brute force way, is to code a function call for every possible number of parameters + // + // switch( paramc ) { + // case 1: ( (void(AUTO_RPC_CALLSPEC*)(NaturalWord)) myfunc)( callParam[0] ); break; + // case 2: ( (void(AUTO_RPC_CALLSPEC*)(NaturalWord,NaturalWord)) myfunc)( callParam[0], callParam[1] ); break; + // ... + // case 64: ( (void(AUTO_RPC_CALLSPEC*)(NaturalWord,NaturalWord)) myfunc)( callParam[0], callParam[1], ... , callParam[63] ); break; + // } + // + // This is the only way to code WIN32 stdcall, for example, as the args must match exactly; + // and so the only way to call from C if you need to call WINAPI routines. + // + // 2) Fortunately most calling conventions allowing excessive args. So this means we could + // write something like below: + // + // ( (void(*)(...)) myfunc )( args[0],...,args[63] ); + // + // And although this should work, its a huge performance penalty copying between memory + // locations for so many args. + // + // So we compromise - and do a stepped sequence. Noticing that the WIN64 ABI alwys requires + // space for three args anyway. + // + // And on SysV x64 systems, the first 6 args are passed in reg; so again, these are only + // copies into register, not memory copies. And finally that if we load word[n], word[n+1] + // is loaded into the cache - thus the overhead for loading is not as big as it might be. + // + // For most realistic cases, a dozen args would be excessive. Obviously, if you have + // a tested assembler equivalent, its probably better to use that. + // + // +#if AUTO_RPC_FLOAT_REG_PARAMS + if ( call.numRealParams == 0 ) +#endif + { + if ( call.numIntParams <= 3 ) + { + ( (void(AUTO_RPC_CALLSPEC*)(AUTO_RPC_NW_3)) functionPtr )( AUTO_RPC_INT_ARGS_3( call ) ); + return 1; + } + if ( call.numIntParams <= 6 ) + { + ( (void(AUTO_RPC_CALLSPEC*)(AUTO_RPC_NW_6)) functionPtr )( AUTO_RPC_INT_ARGS_6( call ) ); + return 1; + } + if ( call.numIntParams <= 9 ) + { + ((void(AUTO_RPC_CALLSPEC*)(AUTO_RPC_NW_9))functionPtr)( AUTO_RPC_INT_ARGS_9( call ) ); + return 1; + } + if ( call.numIntParams <= 12 ) + { + ((void(AUTO_RPC_CALLSPEC*)(AUTO_RPC_NW_12))functionPtr)( AUTO_RPC_INT_ARGS_12( call ) ); + return 1; + } + if ( call.numIntParams <= 32 ) + { + ((void(AUTO_RPC_CALLSPEC*)(AUTO_RPC_NW_32))functionPtr)( AUTO_RPC_INT_ARGS_32( call ) ); + return 1; + } + if ( call.numIntParams <= 64 ) + { + ((void(AUTO_RPC_CALLSPEC*)(AUTO_RPC_NW_64))functionPtr)( AUTO_RPC_INT_ARGS_64( call ) ); + return 1; + } + } +#if AUTO_RPC_FLOAT_REG_PARAMS && !AUTO_RPC_ALLOC_SEPARATE_FLOATS + else + { + if ( call.numIntParams > 64 ) return 0; + + switch( call.realMap ) + { + // case 0: - no floats, never happens here. + + case 1: ( (void(AUTO_RPC_CALLSPEC*)(HardwareReal,NaturalWord,NaturalWord,NaturalWord,AUTO_RPC_NW_4_64))functionPtr)( + call.realParams[0], call.intParams[1], call.intParams[2], call.intParams[3], + AUTO_RPC_INT_ARGS_4_64( call ) + ); + break; + + case 2: + ((void(AUTO_RPC_CALLSPEC*)(NaturalWord,HardwareReal,NaturalWord,NaturalWord,AUTO_RPC_NW_4_64))functionPtr)( + call.intParams[0], call.realParams[1], call.intParams[2], call.intParams[3], + AUTO_RPC_INT_ARGS_4_64( call ) + ); + break; + + case 3: + ((void(AUTO_RPC_CALLSPEC*)(HardwareReal,HardwareReal,NaturalWord,NaturalWord,AUTO_RPC_NW_4_64))functionPtr)( + call.realParams[0], call.realParams[1], call.intParams[2], call.intParams[3], + AUTO_RPC_INT_ARGS_4_64( call ) + ); + break; + + case 4: ( (void(AUTO_RPC_CALLSPEC*)(NaturalWord,NaturalWord,HardwareReal,NaturalWord,AUTO_RPC_NW_4_64))functionPtr)( + call.intParams[0], call.intParams[1], call.realParams[2], call.intParams[3], + AUTO_RPC_INT_ARGS_4_64( call ) + ); + break; + + case 5: + ((void(AUTO_RPC_CALLSPEC*)(HardwareReal,NaturalWord,HardwareReal,NaturalWord,AUTO_RPC_NW_4_64))functionPtr)( + call.realParams[0], call.intParams[1], call.realParams[2], call.intParams[3], + AUTO_RPC_INT_ARGS_4_64( call ) + ); + break; + + case 6: + ((void(AUTO_RPC_CALLSPEC*)(NaturalWord,HardwareReal,HardwareReal,NaturalWord,AUTO_RPC_NW_4_64))functionPtr)( + call.intParams[0], call.realParams[1], call.realParams[2], call.intParams[3], + AUTO_RPC_INT_ARGS_4_64( call ) + ); + break; + + case 7: + ((void(AUTO_RPC_CALLSPEC*)(HardwareReal,HardwareReal,HardwareReal,NaturalWord,AUTO_RPC_NW_4_64))functionPtr)( + call.realParams[0], call.realParams[1], call.realParams[2], call.intParams[3], + AUTO_RPC_INT_ARGS_4_64( call ) + ); + break; + + case 8: ( (void(AUTO_RPC_CALLSPEC*)(NaturalWord,NaturalWord,NaturalWord,HardwareReal,AUTO_RPC_NW_4_64))functionPtr)( + call.intParams[0], call.intParams[1], call.intParams[2], call.realParams[3], + AUTO_RPC_INT_ARGS_4_64( call ) + ); + break; + + case 9: + ((void(AUTO_RPC_CALLSPEC*)(HardwareReal,NaturalWord,NaturalWord,HardwareReal,AUTO_RPC_NW_4_64))functionPtr)( + call.realParams[0], call.intParams[1], call.intParams[2], call.realParams[3], + AUTO_RPC_INT_ARGS_4_64( call ) + ); + break; + case 10: + ((void(AUTO_RPC_CALLSPEC*)(NaturalWord,HardwareReal,NaturalWord,HardwareReal,AUTO_RPC_NW_4_64))functionPtr)( + call.intParams[0], call.realParams[1], call.intParams[2], call.realParams[3], + AUTO_RPC_INT_ARGS_4_64( call ) + ); + break; + + + case 11: + ((void(AUTO_RPC_CALLSPEC*)(HardwareReal,HardwareReal,NaturalWord,HardwareReal,AUTO_RPC_NW_4_64))functionPtr)( + call.realParams[0], call.realParams[1], call.intParams[2], call.realParams[3], + AUTO_RPC_INT_ARGS_4_64( call ) + ); + break; + + case 12: ( (void(AUTO_RPC_CALLSPEC*)(NaturalWord,NaturalWord,HardwareReal,HardwareReal,AUTO_RPC_NW_4_64))functionPtr)( + call.intParams[0], call.intParams[1], call.realParams[2], call.realParams[3], + AUTO_RPC_INT_ARGS_4_64( call ) + ); + break; + + case 13: + ((void(AUTO_RPC_CALLSPEC*)(HardwareReal,NaturalWord,HardwareReal,HardwareReal,AUTO_RPC_NW_4_64))functionPtr)( + call.realParams[0], call.intParams[1], call.realParams[2], call.realParams[3], + AUTO_RPC_INT_ARGS_4_64( call ) + ); + break; + + case 14: + ((void(AUTO_RPC_CALLSPEC*)(NaturalWord,HardwareReal,HardwareReal,HardwareReal,AUTO_RPC_NW_4_64))functionPtr)( + call.intParams[0], call.realParams[1], call.realParams[2], call.realParams[3], + AUTO_RPC_INT_ARGS_4_64( call ) + ); + break; + + case 15: + ((void(AUTO_RPC_CALLSPEC*)(HardwareReal,HardwareReal,HardwareReal,HardwareReal,AUTO_RPC_NW_4_64))functionPtr)( + call.realParams[0], call.realParams[1], call.realParams[2], call.realParams[3], + AUTO_RPC_INT_ARGS_4_64( call ) + ); + break; + + default: return 0; + } + } +#elif AUTO_RPC_FLOAT_REG_PARAMS + else + { + // we pass FLOAT args last for powerpc compatibility. And although it means we pass them twice, + // they should end up in the correct floating point register, with the rest of the integers in the + // correct place... + // + // NB if you want to write inline asm for powerpc, you'll have to be put it in a separate + // "naked" function to that uou can setup the parameter passing area and ensure its big enough. + // (GCC will delete functions that are unused - it will delete the body of functions that + // aren't called.) + // + if ( call.numIntParams <= 3 ) + { + ((void(AUTO_RPC_CALLSPEC*)(AUTO_RPC_NW_3,AUTO_RPC_FLOAT_REG_TYPE))functionPtr)( AUTO_RPC_INT_ARGS_3( call ), AUTO_RPC_FLOAT_REG_ARGS( call ) ); + return 1; + } + if ( call.numIntParams <= 6 ) + { + ((void(AUTO_RPC_CALLSPEC*)(AUTO_RPC_NW_6,AUTO_RPC_FLOAT_REG_TYPE))functionPtr)( AUTO_RPC_INT_ARGS_6( call ),AUTO_RPC_FLOAT_REG_ARGS( call ) ); + return 1; + } + if ( call.numIntParams <= 9 ) + { + ((void(AUTO_RPC_CALLSPEC*)(AUTO_RPC_NW_9,AUTO_RPC_FLOAT_REG_TYPE))functionPtr)( AUTO_RPC_INT_ARGS_9( call ),AUTO_RPC_FLOAT_REG_ARGS( call ) ); + return 1; + } + if ( call.numIntParams <= 12 ) + { + ( (void(AUTO_RPC_CALLSPEC*)(AUTO_RPC_NW_12,AUTO_RPC_FLOAT_REG_TYPE)) functionPtr )( AUTO_RPC_INT_ARGS_12( call ), AUTO_RPC_FLOAT_REG_ARGS( call ) ); + return 1; + } + if ( call.numIntParams <= 64 ) + { + ( (void(AUTO_RPC_CALLSPEC*)(AUTO_RPC_NW_64,AUTO_RPC_FLOAT_REG_TYPE)) functionPtr )( AUTO_RPC_INT_ARGS_64( call ), AUTO_RPC_FLOAT_REG_ARGS( call ) ); + return 1; + } + } +#endif // AUTO_RPC_FLOAT_REG_PARAMS + return 0; +#endif // AUTO_RPC_NO_ASM +#else // AUTO_RPC_ABI + return 0; +#endif +} +// --8<---8<----8<----8<---END diff --git a/RakNet/Sources/Gen_RPC8.h b/RakNet/Sources/Gen_RPC8.h new file mode 100644 index 0000000..5371c41 --- /dev/null +++ b/RakNet/Sources/Gen_RPC8.h @@ -0,0 +1,761 @@ +/// \file Gen_RPC8.h +/// +/// This file is part of RakNet Copyright 2003 Jenkins Software LLC +/// +/// Usage of RakNet is subject to the appropriate license agreement. + +#ifndef __GEN_RPC8_H +#define __GEN_RPC8_H + +#include +#include +#include // memcpy +#include +#if defined(_XBOX) || defined(X360) + +#elif defined (_WIN32) +#include "WindowsIncludes.h" +#endif +#include +//#define ASSEMBLY_BLOCK asm +//#include "Types.h" +#include "BitStream.h" +// #define AUTO_RPC_NO_ASM + +#ifdef _WIN64 +#define AUTO_RPC_NO_ASM +#endif + +namespace GenRPC +{ + +//#define __BITSTREAM_NATIVE_END 1 + + +// -8<----8<----8<----BEGIN + +// +// 0. References +// a. Calling conventions for different C++ compilers and operating systems [http://www.agner.org/optimize] +// b. System V Application Binary Interface AMD64 Architecture Processor Supplement +// Used by 64-bit MAC and 64-bit Linux. +// c. 32-bit PowerPC MAC calling conventions [http://developer.apple.com/documentation/DeveloperTools/Conceptual/LowLevelABI/Articles/32bitPowerPC.html#//apple_ref/doc/uid/TP40002438-SW20] +// d. 32-bit IA MAC calling conventions [http://developer.apple.com/documentation/DeveloperTools/Conceptual/LowLevelABI/Articles/IA32.html#//apple_ref/doc/uid/TP40002492-SW4] +// e. Calling conventions on 64-bit windows [http://msdn2.microsoft.com/en-us/library/zthk2dkh(VS.80).aspx] +// f. 64-bit PowerPC MAC calling conventions [http://developer.apple.com/documentation/DeveloperTools/Conceptual/LowLevelABI/Articles/64bitPowerPC.html#//apple_ref/doc/uid/TP40002471] +// +// 1. General Introduction. +// +// Quite a lot of code we write hinges on hidden assumptions. For instance, most code tacitly +// assumes a char is 8 bits, even though it needn't be. And how much code relies on +// two's-a-compliment numbers, by, for example, assuming the only representation of zero +// is all bits clear? Yet this too isn't mandated by the C standard - which allows for non +// two's a compliment numbers. +// And the switch to 64-bit will see us discovering how +// much of our code assumes sizeof(int) == sizeof(long) == sizeof(void*) +// +// These tradeoffs and compromises are made because we know the architectures +// with CHAR_BITS != 8 are embedded systems of FPGAs and we don't extend our definition of +// portability to those systems. Or Windows code can +// assume its running on a little-endian machine, without loss of generality. In fact, it +// often impossible to test our code in situatiosn where assumptions are not true. +// +// The same is true of a lightweight RPC - to be possible at all, it will have make some +// assumptions about the architecture on (like CHAR_BITS == 8) which limit portability, but +// which are not unreasonable for the cases where its expected to run (modern personal computers) +// and hopefully can be extended easily to meet new cases. +// +// 2. Parameter passing - Introduction +// +// The C standard doesn't mandate how parameters are passed from one function to another. +// That's down to the particular archictecture, normally laid out in the Application Binary +// Interface (ABI). +// +// On some architecture (e.g. 32bit X86) the parameters are all passed on the stack; +// on some architectures (e.g. SPARC) there is no stack and they are all passed in register. +// Sometimes the function must be passed the exact number of args it expects (e.g. WIN32 +// "stdcall"); somtimes it can take an arbitrary number of args (IA-32/64 linux). +// +// But whatever the case, the compiler knows about all the details - it sorts them out every +// time we write a call. So to make it portable we must ensure we pass the compiler *sufficient* +// information to be able to encode the call, in all the cases we're interested in. To do this +// we need some knowledge of the ABI, without getting our hands dirty writing assembler. +// Not only because we can't all be experts at every particularl architecture with its necessary +// prefetches, conditional moves and innate parralelism, but also because that allows the compiler to +// code for the exact processors - rather than using a lowest-common denominator. +// +// +// 3. Preparing parameters and assumptions +// +// We assume that the processor has a 32 bit or 64 bit "natural word size" - and that the +// registers, stack entries (if a stack exists) and pointers all have this natural word +// size. We further assume (1) parameters smaller than this have to be padded out to meet this +// size, and that (2) this can be done by zero-extended them, regardless of whether they are signed +// or unsigned quanitites. +// +// The SysV ABI for 64bit X86 [b] and that ABI for 64-bit windows [e] require that floating point +// parameters are passed in floating points registers - so to work with these types we need to know +// they are floats and alert the compiler. A similar arrangement is true for both 32-bit and 64-bit +// Power PC systems. +// +// This can extend to structures ("aggregate types") containing floating point numbers - where +// individual members can still be passed in register. (Although +// on 64-bit windows, aggregates of size > 8 bytes are passed on the stack, so, +// except for the pathological case of struct S { double F }; there are no problems with +// structures containing floats.) +// +// --- + +// +// AUTO_RPC_MAX_PARAMS: Absolute number of stack words we can handle +// ABI: used to select features specific to ABI. +// AUTO_RPC_INT_REG_PARAMS: Number of parameters passed in integer registers. (Only used by SYSV ABI) +// AUTO_RPC_FLOAT_REG_PARAMS: Number of parameters passed in floating point registers. +// AUTO_RPC_INT_SHADOW_OF_FLOATS: Create a copy of the floats in the integer/stack space. +// AUTO_RPC_ALLOC_SEPARATE_FLOATS: Push floats to a separate contiguous floating point "stack". +// Ortherwise we rely on shadow. +// +// PARAMETER_REF_THRESHOLD: parameters bigger than this are replaced by a reference ( WIN64 ) +// +// +#define AUTO_RPC_MAX_PARAMS 64 + +#define AUTO_RPC_ABI_NONE 0 // not supported - should fail. + +#define AUTO_RPC_ABI_IA_32 1 // all parameters are passed on the stack. +// preserves: ebx,esi,edi,ebp + +#define AUTO_RPC_ABI_WIN_AMD64 2 // first four parameters in either +// rcx|xmm0, rdx|xmm1,r8|xmm2,r9|xmm3 +// preserves: rbx,rsi,rdi,rbp,r12-r15; xmm6-xmm15 +// comments: aggregates > 8 passed by ref; reg params shadowed on stack + +#define AUTO_RPC_ABI_SYSV_AMD64 3 // first six ints in: rdi,rsi,rdx,rcx,r8,r9 +// first eight reals: in xmm0...xmm7 +// preserves: rbx,rbp,r12-r15 +// comments: aggregates > 16 bumped to stack + +#define AUTO_RPC_ABI_PPC 4 // first 6 args (even if float) in int reg; first 13 floats in reg. +// parameter passing area with shadow area. + + +// Configure the parameters for the system. +#if defined(__i386__) || defined( _M_IX86 ) || defined( __INTEL__ ) +// 32 bit system. +#define AUTO_RPC_AUTORPC_WORD 32 + +typedef unsigned int NaturalWord; +typedef double HardwareReal; + +#define AUTO_RPC_INT_REG_PARAMS 0 +#define AUTO_RPC_FLOAT_REG_PARAMS 0 + +#define AUTO_RPC_ABI AUTO_RPC_ABI_IA_32 +#define AUTO_RPC_PARAMETER_REFERENCE_THRESHOLD 0 +#define AUTO_RPC_INT_SHADOW_OF_FLOATS 1 +#define AUTO_RPC_ALLOC_SEPARATE_FLOATS 0 +#define AUTO_RPC_CREATE_FLOAT_MAP 0 + +#elif defined( _M_X64 ) || defined( __x86_64__ ) || defined( _M_AMD64 ) +#define AUTO_RPC_AUTORPC_WORD 64 + +#if defined( _WIN64 ) +#define AUTO_RPC_FLOAT_REG_PARAMS 4 +#define AUTO_RPC_INT_REG_PARAMS 4 +#define AUTO_RPC_ABI AUTO_RPC_ABI_WIN_AMD64 +#define AUTO_RPC_PARAMETER_REFERENCE_THRESHOLD 8 +#define AUTO_RPC_INT_SHADOW_OF_FLOATS 1 +#define AUTO_RPC_ALLOC_SEPARATE_FLOATS 0 +#define AUTO_RPC_CREATE_FLOAT_MAP 1 +#else +#define AUTO_RPC_ABI AUTO_RPC_ABI_SYSV_AMD64 +#define AUTO_RPC_INT_REG_PARAMS 6 +#define AUTO_RPC_FLOAT_REG_PARAMS 8 +#define AUTO_RPC_PARAMETER_REFERENCE_THRESHOLD 0 +#define AUTO_RPC_INT_SHADOW_OF_FLOATS 0 +#define AUTO_RPC_ALLOC_SEPARATE_FLOATS 1 +#define AUTO_RPC_CREATE_FLOAT_MAP 0 +#endif + +// NB OS's differ over. +typedef unsigned long long NaturalWord; +typedef double HardwareReal; // could be changed to __float128 on AMD64/nonwin + +#elif defined(_PS3) || defined(__PS3__) || defined(SN_TARGET_PS3) + +#elif defined(_M_PPC) || defined( __POWERPC__ ) + +#include + +/// PPC Mac doesn't support sizeof( long ) in an #if statement +#if defined (LONG_BIT) + #if LONG_BIT == 64 + #define AUTORPC_WORD 64 + typedef double HardwareReal; + typedef unsigned long long NaturalWord; + #else + #define AUTORPC_WORD 32 + typedef double HardwareReal; + typedef unsigned int NaturalWord; + #endif +#else + #if defined(_XBOX) || defined(X360) + + #else + #if sizeof( long ) == 8 + #define AUTORPC_WORD 64 + typedef double HardwareReal; + typedef unsigned long long NaturalWord; + #else + #define AUTORPC_WORD 32 + typedef double HardwareReal; + typedef unsigned int NaturalWord; + #endif + #endif +#endif + +#define AUTO_RPC_INT_REG_PARAMS 8 +#define AUTO_RPC_FLOAT_REG_PARAMS 13 +#define AUTO_RPC_INT_SHADOW_OF_FLOATS 1 +#define AUTO_RPC_ALLOC_SEPARATE_FLOATS 1 +#define AUTO_RPC_CREATE_FLOAT_MAP 0 +#define AUTO_RPC_PARAMETER_REFERENCE_THRESHOLD 0 +#define AUTO_RPC_ABI AUTO_RPC_ABI_PPC + + +#else +#ifdef __GNUC__ +// gcc won't implemented message - so use #warning +#warning Unknown Architecture +#else +#pragma message( Unknown architecture ) +#endif + +// defining AUTO_RPC_ABI_NONE, creates stub code that fails +#define AUTO_RPC_ABI AUTO_RPC_ABI_NONE +#endif + +// +// Calling convention - we need to be explict on WIN32, so we do that here. Everybody else +// has only one fixed, calling convention. +// +#ifdef _WIN32 +#define AUTO_RPC_CALLSPEC WINAPIV +#else +#define AUTO_RPC_CALLSPEC +#endif + +// +// useful macros; could be rewritten inline/inline templates +// +#define AUTO_RPC__ALIGN_P2( len, bytes ) ( ( len + bytes - 1 ) & ~( bytes - 1 ) ) + +// Return len rounded-up to an integral number of sizeof(type) - provided sizeof(type) is a power of 2 +#define AUTO_RPC_ALIGN_P2( len, type ) AUTO_RPC__ALIGN_P2( len, sizeof( type ) ) + +// Return ptr to end of 'array[xxx]' +#define AUTO_RPC_ARRAY_END( array ) &array[ sizeof( array ) / sizeof( array[0] ) ] + +// strip floating point params if there are no float regs. +#if !AUTO_RPC_FLOAT_REG_PARAMS + +#if AUTO_RPC_CREATE_FLOAT_MAP +#undef AUTO_RPC_CREATE_FLOAT_MAP +#define AUTO_RPC_CREATE_FLOAT_MAP 0 +#endif + +#if AUTO_RPC_ALLOC_SEPARATE_FLOATS +#undef AUTO_RPC_ALLOC_SEPARATE_FLOATS +#define AUTO_RPC_ALLOC_SEPARATE_FLOATS 0 +#endif + +#endif // FLOAT_REG_PARAM + +#define AUTO_RPC_REF_ALIGN 16 // some structures must be memory aligned to 16 bytes, even, on 32-bit systems. +// stack simialrly must be aligned +#define AUTO_RPC_STACK_PADDING ( sizeof( NaturalWord ) / AUTO_RPC_REF_ALIGN ) + + +// defining these externally makes the code a hell of a lot more readable. +#ifdef USE_VARADIC_CALL + +#define AUTO_RPC_NW_3 ... +#define AUTO_RPC_NW_6 ... +#define AUTO_RPC_NW_9 ... +#define AUTO_RPC_NW_12 ... +#define AUTO_RPC_NW_64 ... + +#else +#define AUTO_RPC_NW_3 NaturalWord,NaturalWord,NaturalWord + +#define AUTO_RPC_NW_6 NaturalWord,NaturalWord,NaturalWord,\ + NaturalWord,NaturalWord,NaturalWord + +#define AUTO_RPC_NW_9 NaturalWord,NaturalWord,NaturalWord,\ + NaturalWord,NaturalWord,NaturalWord,\ + NaturalWord,NaturalWord,NaturalWord + +#define AUTO_RPC_NW_5 NaturalWord,NaturalWord,\ + NaturalWord,NaturalWord,NaturalWord + +#define AUTO_RPC_NW_4_9 AUTO_RPC_NW_5 + +#define AUTO_RPC_NW_12 NaturalWord,NaturalWord,NaturalWord,\ + NaturalWord,NaturalWord,NaturalWord,\ + NaturalWord,NaturalWord,NaturalWord,\ + NaturalWord,NaturalWord,NaturalWord + +#define AUTO_RPC_NW_32 NaturalWord,NaturalWord,NaturalWord,NaturalWord,\ + NaturalWord,NaturalWord,NaturalWord,NaturalWord,\ + NaturalWord,NaturalWord,NaturalWord,NaturalWord,\ + NaturalWord,NaturalWord,NaturalWord,NaturalWord,\ + NaturalWord,NaturalWord,NaturalWord,NaturalWord,\ + NaturalWord,NaturalWord,NaturalWord,NaturalWord,\ + NaturalWord,NaturalWord,NaturalWord,NaturalWord,\ + NaturalWord,NaturalWord,NaturalWord,NaturalWord + +#define AUTO_RPC_NW_64 NaturalWord,NaturalWord,NaturalWord,NaturalWord,\ + NaturalWord,NaturalWord,NaturalWord,NaturalWord,\ + NaturalWord,NaturalWord,NaturalWord,NaturalWord,\ + NaturalWord,NaturalWord,NaturalWord,NaturalWord,\ + NaturalWord,NaturalWord,NaturalWord,NaturalWord,\ + NaturalWord,NaturalWord,NaturalWord,NaturalWord,\ + NaturalWord,NaturalWord,NaturalWord,NaturalWord,\ + NaturalWord,NaturalWord,NaturalWord,NaturalWord,\ + NaturalWord,NaturalWord,NaturalWord,NaturalWord,\ + NaturalWord,NaturalWord,NaturalWord,NaturalWord,\ + NaturalWord,NaturalWord,NaturalWord,NaturalWord,\ + NaturalWord,NaturalWord,NaturalWord,NaturalWord,\ + NaturalWord,NaturalWord,NaturalWord,NaturalWord,\ + NaturalWord,NaturalWord,NaturalWord,NaturalWord,\ + NaturalWord,NaturalWord,NaturalWord,NaturalWord,\ + NaturalWord,NaturalWord,NaturalWord,NaturalWord + +#define AUTO_RPC_NW_60 NaturalWord,NaturalWord,NaturalWord,NaturalWord,\ + NaturalWord,NaturalWord,NaturalWord,NaturalWord,\ + NaturalWord,NaturalWord,NaturalWord,NaturalWord,\ + NaturalWord,NaturalWord,NaturalWord,NaturalWord,\ + NaturalWord,NaturalWord,NaturalWord,NaturalWord,\ + NaturalWord,NaturalWord,NaturalWord,NaturalWord,\ + NaturalWord,NaturalWord,NaturalWord,NaturalWord,\ + NaturalWord,NaturalWord,NaturalWord,NaturalWord,\ + NaturalWord,NaturalWord,NaturalWord,NaturalWord,\ + NaturalWord,NaturalWord,NaturalWord,NaturalWord,\ + NaturalWord,NaturalWord,NaturalWord,NaturalWord,\ + NaturalWord,NaturalWord,NaturalWord,NaturalWord,\ + NaturalWord,NaturalWord,NaturalWord,NaturalWord,\ + NaturalWord,NaturalWord,NaturalWord,NaturalWord,\ + NaturalWord,NaturalWord,NaturalWord,NaturalWord + +#define AUTO_RPC_NW_4_64 AUTO_RPC_NW_60 + +#endif // USE VARADIC + +#define AUTO_RPC_INT_ARGS_3( call ) call.intParams[0],call.intParams[1],call.intParams[2] + +#define AUTO_RPC_INT_ARGS_6( call ) call.intParams[0],call.intParams[1],call.intParams[2],call.intParams[3],\ + call.intParams[4],call.intParams[5] + +#define AUTO_RPC_INT_ARGS_9( call ) call.intParams[0],call.intParams[1],call.intParams[2],call.intParams[3],\ + call.intParams[4],call.intParams[5],call.intParams[6],call.intParams[7],\ + call.intParams[8] + +#define AUTO_RPC_INT_ARGS_4_9( call ) call.intParams[4],call.intParams[5],call.intParams[6],call.intParams[7],\ + call.intParams[8] + +#define AUTO_RPC_INT_ARGS_12( call ) call.intParams[0],call.intParams[1],call.intParams[2],call.intParams[3],\ + call.intParams[4],call.intParams[5],call.intParams[6],call.intParams[7],\ + call.intParams[8],call.intParams[9],call.intParams[10],call.intParams[11] + +#define AUTO_RPC_INT_ARGS_32( call ) call.intParams[0],call.intParams[1],call.intParams[2],call.intParams[3],\ + call.intParams[4],call.intParams[5],call.intParams[6],call.intParams[7],\ + call.intParams[8],call.intParams[9],call.intParams[10],call.intParams[11],\ + call.intParams[12],call.intParams[13],call.intParams[14],call.intParams[15],\ + call.intParams[16],call.intParams[17],call.intParams[18],call.intParams[19],\ + call.intParams[20],call.intParams[21],call.intParams[22],call.intParams[23],\ + call.intParams[24],call.intParams[25],call.intParams[26],call.intParams[27],\ + call.intParams[28],call.intParams[29],call.intParams[30],call.intParams[31] + +#define AUTO_RPC_INT_ARGS_4_64( call ) call.intParams[4],call.intParams[5],call.intParams[6],call.intParams[7],\ + call.intParams[8],call.intParams[9],call.intParams[10],call.intParams[11],\ + call.intParams[12],call.intParams[13],call.intParams[14],call.intParams[15],\ + call.intParams[16],call.intParams[17],call.intParams[18],call.intParams[19],\ + call.intParams[20],call.intParams[21],call.intParams[22],call.intParams[23],\ + call.intParams[24],call.intParams[25],call.intParams[26],call.intParams[27],\ + call.intParams[28],call.intParams[29],call.intParams[30],call.intParams[31],\ + call.intParams[32],call.intParams[33],call.intParams[34],call.intParams[35],\ + call.intParams[36],call.intParams[37],call.intParams[38],call.intParams[39],\ + call.intParams[40],call.intParams[41],call.intParams[42],call.intParams[43],\ + call.intParams[44],call.intParams[45],call.intParams[46],call.intParams[47],\ + call.intParams[48],call.intParams[49],call.intParams[50],call.intParams[51],\ + call.intParams[52],call.intParams[53],call.intParams[54],call.intParams[55],\ + call.intParams[56],call.intParams[57],call.intParams[58],call.intParams[59],\ + call.intParams[60],call.intParams[61],call.intParams[62],call.intParams[63] + +#define AUTO_RPC_INT_ARGS_64( call ) call.intParams[0],call.intParams[1],call.intParams[2],call.intParams[3],\ + call.intParams[4],call.intParams[5],call.intParams[6],call.intParams[7],\ + call.intParams[8],call.intParams[9],call.intParams[10],call.intParams[11],\ + call.intParams[12],call.intParams[13],call.intParams[14],call.intParams[15],\ + call.intParams[16],call.intParams[17],call.intParams[18],call.intParams[19],\ + call.intParams[20],call.intParams[21],call.intParams[22],call.intParams[23],\ + call.intParams[24],call.intParams[25],call.intParams[26],call.intParams[27],\ + call.intParams[28],call.intParams[29],call.intParams[30],call.intParams[31],\ + call.intParams[32],call.intParams[33],call.intParams[34],call.intParams[35],\ + call.intParams[36],call.intParams[37],call.intParams[38],call.intParams[39],\ + call.intParams[40],call.intParams[41],call.intParams[42],call.intParams[43],\ + call.intParams[44],call.intParams[45],call.intParams[46],call.intParams[47],\ + call.intParams[48],call.intParams[49],call.intParams[50],call.intParams[51],\ + call.intParams[52],call.intParams[53],call.intParams[54],call.intParams[55],\ + call.intParams[56],call.intParams[57],call.intParams[58],call.intParams[59],\ + call.intParams[60],call.intParams[61],call.intParams[62],call.intParams[63] + +#if AUTO_RPC_ALLOC_SEPARATE_FLOATS +#if AUTO_RPC_FLOAT_REG_PARAMS == 8 + +#define AUTO_RPC_FLOAT_REG_TYPE HardwareReal,HardwareReal,HardwareReal,HardwareReal,\ + HardwareReal,HardwareReal,HardwareReal,HardwareReal +#define AUTO_RPC_FLOAT_REG_ARGS( a ) a.realParams[0],a.realParams[1],a.realParams[2],a.realParams[3],\ + a.realParams[4],a.realParams[5],a.realParams[6],a.realParams[7] + +#elif AUTO_RPC_FLOAT_REG_PARAMS == 4 + +#define AUTO_RPC_FLOAT_REG_TYPE HardwareReal,HardwareReal,HardwareReal,HardwareReal +#define AUTO_RPC_FLOAT_REG_ARGS( a ) a.realParams[0],a.realParams[1],a.realParams[2],a.realParams[3] + +#elif AUTO_RPC_FLOAT_REG_PARAMS == 13 + +#define AUTO_RPC_FLOAT_REG_TYPE HardwareReal,HardwareReal,HardwareReal,HardwareReal,\ + HardwareReal,HardwareReal,HardwareReal,HardwareReal,\ + HardwareReal,HardwareReal,HardwareReal,HardwareReal,HardwareReal +#define AUTO_RPC_FLOAT_REG_ARGS( a ) a.realParams[0],a.realParams[1],a.realParams[2],a.realParams[3],\ + a.realParams[4],a.realParams[5],a.realParams[6],a.realParams[7],\ + a.realParams[8],a.realParams[9],a.realParams[10],a.realParams[11],a.realParams[12] + +#elif AUTO_RPC_FLOAT_REG_PARAMS +#error Need FLOAT_REG_TYPE and AUTO_RPC_FLOAT_REG_ARGS setup +#endif + + +#endif // AUTO_RPC_ALLOC_SEPARATE_FLOATS + +/// \internal +/// Writes number of parameters to push on the stack +void SerializeHeader(char *&out, unsigned int numParams); + +/// Builds up a function call and all parameters onto a stack +/// \param[out] Destination stack, which must be big enough to hold all parameters +unsigned int BuildStack(char *stack); + +/// Builds up a function call and all parameters onto a stack +/// \param[out] Destination stack, which must be big enough to hold all parameters +template +unsigned int BuildStack(char *stack, P1 p1, + bool es1=true) +{ + char *stackPtr = (char*) stack; + SerializeHeader(stackPtr, 1); + PushHeader(stackPtr, p1, es1); + Push( stackPtr, p1, es1 ); + return (unsigned int)(stackPtr-stack); +} + +/// Builds up a function call and all parameters onto a stack +/// \param[out] Destination stack, which must be big enough to hold all parameters +template +unsigned int BuildStack(char *stack, P1 p1, P2 p2, + bool es1=true, bool es2=true) +{ + char *stackPtr = (char*) stack; + SerializeHeader(stackPtr, 2); + PushHeader(stackPtr, p1, es1); + PushHeader(stackPtr, p2, es2); + Push( stackPtr, p1, es1 ); + Push( stackPtr, p2, es2 ); + return (unsigned int)(stackPtr-stack); +} + +/// Builds up a function call and all parameters onto a stack +/// \param[out] Destination stack, which must be big enough to hold all parameters +template +unsigned int BuildStack(char *stack, P1 p1, P2 p2, P3 p3, + bool es1=true, bool es2=true, bool es3=true ) +{ + char *stackPtr = (char*) stack; + SerializeHeader(stackPtr, 3); + PushHeader(stackPtr, p1, es1); + PushHeader(stackPtr, p2, es2); + PushHeader(stackPtr, p3, es3); + Push( stackPtr, p1, es1 ); + Push( stackPtr, p2, es2 ); + Push( stackPtr, p3, es3 ); + return (unsigned int)(stackPtr-stack); +} + +/// Builds up a function call and all parameters onto a stack +/// \param[out] Destination stack, which must be big enough to hold all parameters +template +unsigned int BuildStack(char *stack, P1 p1, P2 p2, P3 p3, P4 p4, + bool es1=true, bool es2=true, bool es3=true, bool es4=true ) +{ + char *stackPtr = (char*) stack; + SerializeHeader(stackPtr, 4); + PushHeader(stackPtr, p1, es1); + PushHeader(stackPtr, p2, es2); + PushHeader(stackPtr, p3, es3); + PushHeader(stackPtr, p4, es4); + Push( stackPtr, p1, es1 ); + Push( stackPtr, p2, es2 ); + Push( stackPtr, p3, es3 ); + Push( stackPtr, p4, es4 ); + return (unsigned int)(stackPtr-stack); +} + +/// Builds up a function call and all parameters onto a stack +/// \param[out] Destination stack, which must be big enough to hold all parameters +template +unsigned int BuildStack(char *stack, P1 p1, P2 p2, P3 p3, P4 p4, P5 p5, + bool es1=true, bool es2=true, bool es3=true, bool es4=true, bool es5=true ) +{ + char *stackPtr = (char*) stack; + SerializeHeader(stackPtr, 5); + PushHeader(stackPtr, p1, es1); + PushHeader(stackPtr, p2, es2); + PushHeader(stackPtr, p3, es3); + PushHeader(stackPtr, p4, es4); + PushHeader(stackPtr, p5, es5); + Push( stackPtr, p1, es1 ); + Push( stackPtr, p2, es2 ); + Push( stackPtr, p3, es3 ); + Push( stackPtr, p4, es4 ); + Push( stackPtr, p5, es5 ); + return (unsigned int)(stackPtr-stack); +} + +/// Builds up a function call and all parameters onto a stack +/// \param[out] Destination stack, which must be big enough to hold all parameters +template +unsigned int BuildStack(char *stack, P1 p1, P2 p2, P3 p3, P4 p4, P5 p5, P6 p6, + bool es1=true, bool es2=true, bool es3=true, bool es4=true, bool es5=true, bool es6=true ) +{ + char *stackPtr = (char*) stack; + SerializeHeader(stackPtr, 6); + PushHeader(stackPtr, p1, es1); + PushHeader(stackPtr, p2, es2); + PushHeader(stackPtr, p3, es3); + PushHeader(stackPtr, p4, es4); + PushHeader(stackPtr, p5, es5); + PushHeader(stackPtr, p6, es6); + Push( stackPtr, p1, es1 ); + Push( stackPtr, p2, es2 ); + Push( stackPtr, p3, es3 ); + Push( stackPtr, p4, es4 ); + Push( stackPtr, p5, es5 ); + Push( stackPtr, p6, es6 ); + return (unsigned int)(stackPtr-stack); +} + +/// Builds up a function call and all parameters onto a stack +/// \param[out] Destination stack, which must be big enough to hold all parameters +template +unsigned int BuildStack(char *stack, P1 p1, P2 p2, P3 p3, P4 p4, P5 p5, P6 p6, P7 p7, + bool es1=true, bool es2=true, bool es3=true, bool es4=true, bool es5=true, bool es6=true, bool es7=true ) +{ + char *stackPtr = (char*) stack; + SerializeHeader(stackPtr, 7); + PushHeader(stackPtr, p1, es1); + PushHeader(stackPtr, p2, es2); + PushHeader(stackPtr, p3, es3); + PushHeader(stackPtr, p4, es4); + PushHeader(stackPtr, p5, es5); + PushHeader(stackPtr, p6, es6); + PushHeader(stackPtr, p7, es7); + Push( stackPtr, p1, es1 ); + Push( stackPtr, p2, es2 ); + Push( stackPtr, p3, es3 ); + Push( stackPtr, p4, es4 ); + Push( stackPtr, p5, es5 ); + Push( stackPtr, p6, es6 ); + Push( stackPtr, p7, es7 ); + return (unsigned int)(stackPtr-stack); +} + +/// Builds up a function call and all parameters onto a stack +/// \param[out] Destination stack, which must be big enough to hold all parameters +template +unsigned int BuildStack(char *stack, P1 p1, P2 p2, P3 p3, P4 p4, P5 p5, P6 p6, P7 p7, P8 p8, + bool es1=true, bool es2=true, bool es3=true, bool es4=true, bool es5=true, bool es6=true, bool es7=true, bool es8=true ) +{ + char *stackPtr = (char*) stack; + SerializeHeader(stackPtr, 8); + PushHeader(stackPtr, p1, es1); + PushHeader(stackPtr, p2, es2); + PushHeader(stackPtr, p3, es3); + PushHeader(stackPtr, p4, es4); + PushHeader(stackPtr, p5, es5); + PushHeader(stackPtr, p6, es6); + PushHeader(stackPtr, p7, es7); + PushHeader(stackPtr, p8, es8); + Push( stackPtr, p1, es1 ); + Push( stackPtr, p2, es2 ); + Push( stackPtr, p3, es3 ); + Push( stackPtr, p4, es4 ); + Push( stackPtr, p5, es5 ); + Push( stackPtr, p6, es6 ); + Push( stackPtr, p7, es7 ); + Push( stackPtr, p8, es8 ); + return (unsigned int)(stackPtr-stack); +} + +/// \internal +template +void Push( char*& p, item const i, bool doEndianSwap ) { + memcpy( (void*)p, (void*)&i, sizeof( i ) ); + if (doEndianSwap && RakNet::BitStream::DoEndianSwap()) + RakNet::BitStream::ReverseBytesInPlace((unsigned char*) p,sizeof( i )); + p += sizeof( i ); +} + +/// \internal +template +void Push( char*& p, item*const i, bool doEndianSwap) { + memcpy( (void*)p, (void*)i, sizeof( *i ) ); + if (doEndianSwap && RakNet::BitStream::DoEndianSwap()) + RakNet::BitStream::ReverseBytesInPlace((unsigned char*) p,sizeof( i )); + p += sizeof( *i ); +} + +/// \internal +template +void Push( char*& p, item const*const i, bool doEndianSwap) { + memcpy( (void*)p, (void*)i, sizeof( *i ) ); + if (doEndianSwap && RakNet::BitStream::DoEndianSwap()) + RakNet::BitStream::ReverseBytesInPlace((unsigned char*) p,sizeof( i )); + p += sizeof( *i ); +} + +/// \internal +void Push( char*& p, char*const i, bool doEndianSwap); + +/// \internal +void Push( char*& p, const char*const i, bool doEndianSwap ); + +// THIS STRUCTURE LAYOUT IS HARDCODED INTO THE ASSEMBLY. Unfortunately, that appears to be the +// only way to do it. +struct CallParams { +#if AUTO_RPC_ABI +#if AUTO_RPC_FLOAT_REG_PARAMS + // on most platforms, just a bool telling us whether we need any floats. + unsigned numRealParams; + +#if AUTO_RPC_CREATE_FLOAT_MAP + // + // bitmask: bit(n) set indicate parameter n is a float, not an int. + // + unsigned realMap; +#endif + + // N.B. these may not have type HardwareReal - they're not promoted or converted. +#if AUTO_RPC_ALLOC_SEPARATE_FLOATS + HardwareReal realParams[ AUTO_RPC_FLOAT_REG_PARAMS ]; +#endif + +#endif // AUTO_RPC_FLOAT_REG_PARAMS + + unsigned numIntParams; +#if !AUTO_RPC_ALLOC_SEPARATE_FLOATS && AUTO_RPC_FLOAT_REG_PARAMS && AUTO_RPC_CREATE_FLOAT_MAP + union { + HardwareReal realParams[ AUTO_RPC_FLOAT_REG_PARAMS ]; +#endif + NaturalWord intParams[ ( AUTO_RPC_MAX_PARAMS > AUTO_RPC_INT_REG_PARAMS ? AUTO_RPC_MAX_PARAMS : AUTO_RPC_INT_REG_PARAMS ) + AUTO_RPC_STACK_PADDING ]; + +#if !AUTO_RPC_ALLOC_SEPARATE_FLOATS && AUTO_RPC_FLOAT_REG_PARAMS && AUTO_RPC_CREATE_FLOAT_MAP + }; +#endif + + char refParams[ AUTO_RPC_MAX_PARAMS * AUTO_RPC_REF_ALIGN ]; +#endif // AUTO_RPC_ABI +}; + +/// Given a stack, the length of the stack, a possible last parameter, and a possible this pointer, build a call to a C or C++ function +bool DeserializeParametersAndBuildCall( + CallParams &call, + char *in, unsigned int inLength, + void *lastParam, void *thisPtr); + +// Given the output of DeserializeParametersAndBuildCall, actually call a function +bool CallWithStack( CallParams& call, void *functionPtr ); + +/// \internal +/// functions to return the size of the item. +template +size_t D_size( item const ) { return sizeof( item ); } + +/// \internal +/// functions to return the size of the item. +template +size_t D_size( item const*const ) { return sizeof( item ); } + +/// \internal +/// functions to return the size of the item. +template +size_t D_size( item*const ) { return sizeof( item ); } + +/// \internal +size_t D_size( char*const str ); +/// \internal +size_t D_size( char const*const str ); + +/// \internal +enum { + // to maintain binary compatibility with a historical decision, bit 1 is not used + // in defining the "well known param" types + PARAM_TYPE_MASK = 0x5, + INT_PARAM = 0, // pass by value an integer or structure composed of integers. + REAL_PARAM = 1, // pass by value a SINGLE floating point parameter. + REF_PARAM = 4, // pass a pointer or reference to data which must be aligned. + STR_PARAM = 5, // pass a pointer to this data, which need not be unaligned; + // but MUST be null terminated. + // OBJECT_PARAM = 8, // TODO: pass by value an object, object id as first uint32_t of serialized data? + // OBJECT_REF_PARAM = 9, // TODO: pass by reference an object, object id as first uint32_t of serialized data? + // SC == "Shift count" (Bit index); which is always useful. + ENDIAN_SWAP_SC = 1, DO_ENDIAN_SWAP = 1 << ENDIAN_SWAP_SC, + + RESERVED_BITS = 0xf8, +}; + +/// \internal +template +unsigned D_type( item const ) { return INT_PARAM; } + +/// \internal +template +unsigned D_type( item const*const ) { return REF_PARAM; } + +/// \internal +template +unsigned D_type( item*const ) { return REF_PARAM; } + +/// \internal +unsigned D_type( const char*const ); +/// \internal +unsigned D_type( char*const ); + +/// \internal +unsigned D_type( float ); +/// \internal +unsigned D_type( double ); +/// \internal +unsigned D_type( long double ); + +/// \internal +template +void PushHeader( char*& p, item const i, bool endianSwap ) { + unsigned int s = (unsigned int) D_size( i ); + unsigned char f = D_type( i ) | ( ((int) endianSwap) << ENDIAN_SWAP_SC ); + Push( p, s, endianSwap ); + Push( p, f, false ); +} + +} + +#endif diff --git a/RakNet/Sources/GetTime.cpp b/RakNet/Sources/GetTime.cpp new file mode 100644 index 0000000..fb924c1 --- /dev/null +++ b/RakNet/Sources/GetTime.cpp @@ -0,0 +1,154 @@ +/// \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) && !defined(_XBOX) && !defined(X360) +#include "WindowsIncludes.h" +// To call timeGetTime +// on Code::Blocks, this needs to be libwinmm.a instead +#pragma comment(lib, "Winmm.lib") +#endif + +#include "GetTime.h" +#if defined(_XBOX) || defined(X360) + +#endif +#if defined(_WIN32) +DWORD mProcMask; +DWORD mSysMask; +HANDLE mThread; +static LARGE_INTEGER yo; +#elif defined(_PS3) || defined(__PS3__) || defined(SN_TARGET_PS3) + +#else +#include +#include +RakNetTimeUS initialTime; +#endif + +static bool initialized=false; +int queryCount=0; + +RakNetTime RakNet::GetTime( void ) +{ + return (RakNetTime)(GetTimeNS()/1000); +} +RakNetTimeUS RakNet::GetTimeNS( void ) +{ +#if defined(_PS3) || defined(__PS3__) || defined(SN_TARGET_PS3) + +#elif defined(_WIN32) + // Win32 + if ( initialized == false) + { + initialized = true; + +#if !defined(_WIN32_WCE) && !defined(_XBOX) && !defined(X360) + // Save the current process + HANDLE mProc = GetCurrentProcess(); + + // Get the current Affinity +#if _MSC_VER >= 1400 && defined (_M_X64) + GetProcessAffinityMask(mProc, (PDWORD_PTR)&mProcMask, (PDWORD_PTR)&mSysMask); +#else + GetProcessAffinityMask(mProc, &mProcMask, &mSysMask); +#endif + + mThread = GetCurrentThread(); + +#endif // !defined(_WIN32_WCE) + + QueryPerformanceFrequency( &yo ); + } + // 01/12/08 - According to the docs "The frequency cannot change while the system is running." so this shouldn't be necessary + /* + if (++queryCount==200) + { + // Set affinity to the first core + SetThreadAffinityMask(mThread, 1); + + QueryPerformanceFrequency( &yo ); + + // Reset affinity + SetThreadAffinityMask(mThread, mProcMask); + + queryCount=0; + } + */ + +#elif (defined(__GNUC__) || defined(__GCCXML__)) + timeval tp; + if ( initialized == false) + { + gettimeofday( &tp, 0 ); + initialized=true; + // I do this because otherwise RakNetTime in milliseconds won't work as it will underflow when dividing by 1000 to do the conversion + initialTime = ( tp.tv_sec ) * (RakNetTimeUS) 1000000 + ( tp.tv_usec ); + } +#endif + +#if defined(_PS3) || defined(__PS3__) || defined(SN_TARGET_PS3) + +#elif defined(_WIN32) + + RakNetTimeUS curTime; + static RakNetTimeUS lastQueryVal=(RakNetTimeUS)0; +// static unsigned long lastTickCountVal = GetTickCount(); + + LARGE_INTEGER PerfVal; + +#if !defined(_WIN32_WCE) && !defined(_XBOX) && !defined(X360) + // Set affinity to the first core + // 8/9/09 This freaking destroys performance, 90% of the time in this function is due to SetThreadAffinityMask(). + //SetThreadAffinityMask(mThread, 1); +#endif // !defined(_WIN32_WCE) + + // Docs: On a multiprocessor computer, it should not matter which processor is called. + // However, you can get different results on different processors due to bugs in the basic input/output system (BIOS) or the hardware abstraction layer (HAL). To specify processor affinity for a thread, use the SetThreadAffinityMask function. + // Query the timer + QueryPerformanceCounter( &PerfVal ); + +#if !defined(_WIN32_WCE) && !defined(_XBOX) && !defined(X360) + // Reset affinity + // 8/9/09 This freaking destroys performance, 90% of the time in this function is due to SetThreadAffinityMask(). +// SetThreadAffinityMask(mThread, mProcMask); +#endif // !defined(_WIN32_WCE) + + __int64 quotient, remainder; + quotient=((PerfVal.QuadPart) / yo.QuadPart); + remainder=((PerfVal.QuadPart) % yo.QuadPart); + curTime = (RakNetTimeUS) quotient*(RakNetTimeUS)1000000 + (remainder*(RakNetTimeUS)1000000 / yo.QuadPart); + + // 08/26/08 - With the below workaround, the time seems to jump forward regardless. + // Just make sure the time doesn't go backwards + if (curTime < lastQueryVal) + return lastQueryVal; + +#if !defined(_XBOX) && !defined(X360) + // To call timeGetTime + // on Code::Blocks, at the top of the file you need to import libwinmm.a instead of Winmm.lib + DWORD tgt = timeGetTime(); + RakNetTimeMS timeInMS = curTime/1000; + if (timeInMS>tgt+1000) + { + // To workaround http://support.microsoft.com/kb/274323 where the timer can sometimes jump forward by hours or days + curTime=(RakNetTimeUS) tgt * (RakNetTimeUS) 1000; + } +#endif + lastQueryVal=curTime; + + return curTime; + +#elif (defined(__GNUC__) || defined(__GCCXML__)) + // GCC + RakNetTimeUS curTime; + gettimeofday( &tp, 0 ); + + curTime = ( tp.tv_sec ) * (RakNetTimeUS) 1000000 + ( tp.tv_usec ); + // Subtract from initialTime so the millisecond conversion does not underflow + return curTime - initialTime; +#endif +} diff --git a/RakNet/Sources/GetTime.h b/RakNet/Sources/GetTime.h new file mode 100644 index 0000000..2d3af55 --- /dev/null +++ b/RakNet/Sources/GetTime.h @@ -0,0 +1,32 @@ +/// \file GetTime.h +/// \brief Returns the value from QueryPerformanceCounter. This is the function RakNet uses to represent time. +/// +/// 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.h" +#include "RakNetTime.h" // For RakNetTime + +/// The namespace RakNet is not consistently used. It's only purpose is to avoid compiler errors for classes whose names are very common. +/// For the most part I've tried to avoid this simply by using names very likely to be unique for my classes. +namespace RakNet +{ + /// \brief Returns the value from QueryPerformanceCounter. This is the function RakNet uses to represent time. + /// Should be renamed GetTimeMS + /// \note This time won't match the time returned by GetTimeCount(). See http://www.jenkinssoftware.com/forum/index.php?topic=2798.0 + RakNetTime RAK_DLL_EXPORT GetTime( void ); + + /// Should be renamed GetTimeUS + RakNetTimeUS RAK_DLL_EXPORT GetTimeNS( void ); + + // Renames, for RakNet 4 + inline RakNetTime RAK_DLL_EXPORT GetTimeMS( void ) {return GetTime();} + inline RakNetTimeUS RAK_DLL_EXPORT GetTimeUS( void ) {return GetTimeNS();} +} + +#endif diff --git a/RakNet/Sources/Getche.cpp b/RakNet/Sources/Getche.cpp new file mode 100644 index 0000000..be057ec --- /dev/null +++ b/RakNet/Sources/Getche.cpp @@ -0,0 +1,25 @@ +#if defined(_WIN32) && !defined(_XBOX) && !defined(X360) +#include /* getche() */ +#elif defined(_PS3) || defined(__PS3__) || defined(SN_TARGET_PS3) + +#else + +#include "Getche.h" + +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 diff --git a/RakNet/Sources/Getche.h b/RakNet/Sources/Getche.h new file mode 100644 index 0000000..f1a4b8a --- /dev/null +++ b/RakNet/Sources/Getche.h @@ -0,0 +1,9 @@ +#if defined(_WIN32) && !defined(_XBOX) && !defined(X360) +#include /* getche() */ +#elif defined(_PS3) || defined(__PS3__) || defined(SN_TARGET_PS3) +#else +#include +#include +#include +char getche(); +#endif diff --git a/RakNet/Sources/GridSectorizer.cpp b/RakNet/Sources/GridSectorizer.cpp new file mode 100644 index 0000000..dbbe69e --- /dev/null +++ b/RakNet/Sources/GridSectorizer.cpp @@ -0,0 +1,191 @@ +#include "RakAssert.h" +#include "GridSectorizer.h" +//#include +#include + +GridSectorizer::GridSectorizer() +{ + grid=0; +} +GridSectorizer::~GridSectorizer() +{ + if (grid) + RakNet::OP_DELETE_ARRAY(grid, __FILE__, __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__, __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>(gridCellWidthCount*gridCellHeightCount, __FILE__, __LINE__ ); + DataStructures::OrderedList::IMPLEMENT_DEFAULT_COMPARISON(); +#else + grid = RakNet::OP_NEW_ARRAY >(gridCellWidthCount*gridCellHeightCount, __FILE__, __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__, __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& intersectionList, const float minX, const float minY, const float maxX, const float maxY) +{ +#ifdef _USE_ORDERED_LIST + DataStructures::OrderedList* cell; +#else + DataStructures::List* 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__,__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__, __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& 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* grid; +#else + DataStructures::List* grid; +#endif +}; + +#endif diff --git a/RakNet/Sources/HTTPConnection.cpp b/RakNet/Sources/HTTPConnection.cpp new file mode 100644 index 0000000..630fe18 --- /dev/null +++ b/RakNet/Sources/HTTPConnection.cpp @@ -0,0 +1,273 @@ +/// \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.h" +#if _RAKNET_SUPPORT_HTTPConnection==1 + +#include "TCPInterface.h" +#include "HTTPConnection.h" +#include "RakSleep.h" +#include "RakString.h" +#include "RakAssert.h" +#include +#include +#include +#include + +using namespace RakNet; + +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) +{ + OutgoingPost op; + op.contentType=_contentType; + op.data=data; + op.remotePath=remote_path; + outgoingPosts.Push(op, __FILE__, __LINE__ ); + //printf("Adding outgoing post\n"); +} + +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() +{ + if (incomingData.IsEmpty()==false) + { +// printf("\n\n----------------------- PUSHING -------------\n\n"); +// printf(incomingData.C_String()); +// printf("\n------------------------------------\n\n"); + //printf("Pushing result\n"); + results.Push(incomingData, __FILE__, __LINE__ ); + } + incomingData.Clear(); + tcp->CloseConnection(server); + connectionState=CS_NONE; +// printf("Disconnecting\n"); +} +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 (outgoingPosts.IsEmpty()) + return; + + //printf("Connecting\n"); + server = tcp->Connect(host, port, false); + connectionState = CS_CONNECTING; + } + break; + case CS_CONNECTING: + { + } + break; + case CS_CONNECTED: + { + //printf("Connected\n"); + if (outgoingPosts.IsEmpty()) + { + //printf("Closed connection (nothing to do)\n"); + CloseConnection(); + return; + } + +#if defined(OPEN_SSL_CLIENT_SUPPORT) + tcp->StartSSLClient(server); +#endif + + //printf("Sending request\n"); + currentProcessingRequest = outgoingPosts.Pop(); + RakString request("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", + currentProcessingRequest.remotePath.C_String(), + host.C_String(), + port, + currentProcessingRequest.contentType.C_String(), + (unsigned) currentProcessingRequest.data.GetLength(), + currentProcessingRequest.data.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 && currentProcessingRequest.data.IsEmpty()==false) +// outgoingPosts.PushAtHead(currentProcessingRequest); +} +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 RakString(); + } + + // size_t len = strlen(start_of_body); + //printf("Returning result with length %i\n", len); + return RakNet::RakString::NonVariadic(start_of_body); +} +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__, __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(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 = (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(); + } + } + //} + } + } +} + +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_* diff --git a/RakNet/Sources/HTTPConnection.h b/RakNet/Sources/HTTPConnection.h new file mode 100644 index 0000000..b90d24f --- /dev/null +++ b/RakNet/Sources/HTTPConnection.h @@ -0,0 +1,162 @@ +/// \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.h" +#if _RAKNET_SUPPORT_HTTPConnection==1 + +#ifndef __HTTP_CONNECTION +#define __HTTP_CONNECTION + +#include "Export.h" +#include "RakString.h" +#include "RakMemoryOverride.h" +#include "RakNetTypes.h" +#include "DS_Queue.h" + +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: + /// 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 "mywebpage/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"); + + /// 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 OutgoingPost + { + RakNet::RakString remotePath; + RakNet::RakString data; + RakNet::RakString contentType; + }; + + DataStructures::Queue outgoingPosts; + OutgoingPost currentProcessingRequest; + +private: + SystemAddress server; + TCPInterface *tcp; + RakNet::RakString host; + unsigned short port; + DataStructures::Queue badResponses; + + enum ConnectionState + { + CS_NONE, + CS_CONNECTING, + CS_CONNECTED, + CS_PROCESSING, + } connectionState; + + RakNet::RakString incomingData; + DataStructures::Queue 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); + */ + +}; + +#endif + +#endif // _RAKNET_SUPPORT_* diff --git a/RakNet/Sources/IncrementalReadInterface.cpp b/RakNet/Sources/IncrementalReadInterface.cpp new file mode 100644 index 0000000..198cd6e --- /dev/null +++ b/RakNet/Sources/IncrementalReadInterface.cpp @@ -0,0 +1,13 @@ +#include "IncrementalReadInterface.h" +#include + +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; +} diff --git a/RakNet/Sources/IncrementalReadInterface.h b/RakNet/Sources/IncrementalReadInterface.h new file mode 100644 index 0000000..4b8c1bb --- /dev/null +++ b/RakNet/Sources/IncrementalReadInterface.h @@ -0,0 +1,23 @@ +#ifndef __INCREMENTAL_READ_INTERFACE_H +#define __INCREMENTAL_READ_INTERFACE_H + +#include "FileListNodeContext.h" +#include "Export.h" + +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); +}; + +#endif diff --git a/RakNet/Sources/InlineFunctor.cpp b/RakNet/Sources/InlineFunctor.cpp new file mode 100644 index 0000000..34467bc --- /dev/null +++ b/RakNet/Sources/InlineFunctor.cpp @@ -0,0 +1,51 @@ +#include "InlineFunctor.h" + +void InlineFunctor::HandleResult(bool wasCancelled, void *context) +{ + (void) wasCancelled; + (void) context; + ifp->Pop(callDepth); +} + +InlineFunctorProcessor::InlineFunctorProcessor() +{ + +} +InlineFunctorProcessor::~InlineFunctorProcessor() +{ + StopThreads(false); +} + +void InlineFunctorProcessor::StartThreads(int numThreads) +{ + functionThread.StartThreads(numThreads); +} +void InlineFunctorProcessor::StopThreads(bool blockOnCurrentProcessing) +{ + functionThread.StopThreads(blockOnCurrentProcessing); +} +void InlineFunctorProcessor::YieldOnFunctor(InlineFunctor *inlineFunctor) +{ + inlineFunctor->callDepth=GetCallDepth(); + inlineFunctor->ifp=this; + functionThread.Push(inlineFunctor); + completedThreads.Push(false, __FILE__, __LINE__); +} +bool InlineFunctorProcessor::UpdateIFP(void) +{ + functionThread.CallResultHandlers(); + if (completedThreads.Size() && completedThreads[completedThreads.Size()-1]==true) + { + completedThreads.Pop(); + return true; + } + return false; +} +void InlineFunctorProcessor::Pop(int threadCallDepth) +{ + completedThreads[threadCallDepth]=true; +} +unsigned InlineFunctorProcessor::GetCallDepth(void) const +{ + return completedThreads.Size(); +} diff --git a/RakNet/Sources/InlineFunctor.h b/RakNet/Sources/InlineFunctor.h new file mode 100644 index 0000000..f3c17c7 --- /dev/null +++ b/RakNet/Sources/InlineFunctor.h @@ -0,0 +1,61 @@ +#include "FunctionThread.h" +#include "DS_List.h" + +class InlineFunctorProcessor; + +/// A base class to derive functors from for the InlineFunctorProcessor system +class InlineFunctor : public RakNet::Functor +{ +protected: + /// \internal - Calls InlineFunctorProcessor to signal that the functor is done + virtual void HandleResult(bool wasCancelled, void *context); + + /// Tracks the call depth when this functor was pushed. It allows the system to return from functions in the correct order + int callDepth; + + /// Pointer to the calling instance of InlineFunctorProcessor + InlineFunctorProcessor *ifp; + friend class InlineFunctorProcessor; +}; + +/// A base class that will allow you to call YieldOnFunctor() from within a function, and continue with that function when the asynchronous processing has completed +class InlineFunctorProcessor +{ +public: + InlineFunctorProcessor(); + ~InlineFunctorProcessor(); + + /// Start the threads. Should call this first + /// \param[in] numThreads How many worker threads to start + /// \note If only one thread is started, then the calls to YieldOnFunctor will process in that order + void StartThreads(int numThreads); + + /// Stop the threads + /// \param[in] blockOnCurrentProcessing Wait for the current processing to finish? + void StopThreads(bool blockOnCurrentProcessing); + + /// Yield processing in the current function, continuing with the function implemented by CallYieldFunction + /// When the functor completes, this function will return and the caller will continue processing + /// \param[in] inlineFunctor A class that implements Functor::Process() to perform processing that can work asynchronously, such as loading a file or doing a database call + void YieldOnFunctor(InlineFunctor *inlineFunctor); + + /// \internal + /// If the functor is done, continue processing the caller + /// \return True if the topmost functor has completed, false otherwise + bool UpdateIFP(void); + + /// \internal + /// Notify the caller that the functor is done + void Pop(int threadCallDepth); + +protected: + + /// Returns the number of functors that were passed to the system + unsigned GetCallDepth(void) const; + + /// Used to create a thread that processes functors + RakNet::FunctionThread functionThread; + + /// Tracks which threads have been completed + DataStructures::List completedThreads; +}; diff --git a/RakNet/Sources/InternalPacket.h b/RakNet/Sources/InternalPacket.h new file mode 100644 index 0000000..e15e1d4 --- /dev/null +++ b/RakNet/Sources/InternalPacket.h @@ -0,0 +1,114 @@ +/// \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.h" +#include "RakNetTypes.h" +#include "RakMemoryOverride.h" +#include "RakNetDefines.h" +#include "CCRakNetUDT.h" +#include "NativeTypes.h" + +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 RakNetTimeUS 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 channels + OrderingIndexType orderingIndex; + ///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 + RakNetTimeUS creationTime; + ///The resendNext time to take action on this packet + RakNetTimeUS 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 + } 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; +}; + +#endif + diff --git a/RakNet/Sources/Itoa.cpp b/RakNet/Sources/Itoa.cpp new file mode 100644 index 0000000..2facb95 --- /dev/null +++ b/RakNet/Sources/Itoa.cpp @@ -0,0 +1,46 @@ + +// 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; +} + diff --git a/RakNet/Sources/Itoa.h b/RakNet/Sources/Itoa.h new file mode 100644 index 0000000..be1b2da --- /dev/null +++ b/RakNet/Sources/Itoa.h @@ -0,0 +1,8 @@ +#ifndef __RAK_ITOA_H +#define __RAK_ITOA_H + +#include "Export.h" + +RAK_DLL_EXPORT char* Itoa( int value, char* result, int base ); + +#endif diff --git a/RakNet/Sources/Kbhit.h b/RakNet/Sources/Kbhit.h new file mode 100644 index 0000000..66fccf2 --- /dev/null +++ b/RakNet/Sources/Kbhit.h @@ -0,0 +1,84 @@ +/***************************************************************************** +kbhit() and getch() for Linux/UNIX +Chris Giese 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 /* kbhit(), getch() */ +#elif defined(_XBOX) || defined(X360) +#elif !defined(_PS3) && !defined(__PS3__) && !defined(SN_TARGET_PS3) +#include /* struct timeval, select() */ +/* ICANON, ECHO, TCSANOW, struct termios */ +#include /* tcgetattr(), tcsetattr() */ +#include /* atexit(), exit() */ +#include /* read() */ +#include /* printf() */ +#include /* 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 + + diff --git a/RakNet/Sources/LightweightDatabaseClient.cpp b/RakNet/Sources/LightweightDatabaseClient.cpp new file mode 100644 index 0000000..023cba0 --- /dev/null +++ b/RakNet/Sources/LightweightDatabaseClient.cpp @@ -0,0 +1,131 @@ +#include "NativeFeatureIncludes.h" +#if _RAKNET_SUPPORT_LightweightDatabaseClient==1 + +#include "LightweightDatabaseClient.h" +#include "StringCompressor.h" +#include "MessageIdentifiers.h" +#include "RakPeerInterface.h" +#include "TableSerializer.h" +#include "BitStream.h" +#include "RakAssert.h" + +#ifdef _MSC_VER +#pragma warning( push ) +#endif + +LightweightDatabaseClient::LightweightDatabaseClient() +{ +} +LightweightDatabaseClient::~LightweightDatabaseClient() +{ + +} +void LightweightDatabaseClient::QueryTable(const char *version, const char *tableName, const char *queryPassword, const char **columnNamesSubset, unsigned char numColumnSubset, DatabaseFilter *filter, unsigned char numFilters, unsigned *rowIds, unsigned char numRowIDs, SystemAddress systemAddress, bool broadcast) +{ + if (tableName==0 || tableName[0]==0) + return; + if (rakPeerInterface==0) + return; + + RakNet::BitStream out; + out.Write((MessageID)ID_DATABASE_QUERY_REQUEST); + + out.Write(version, 3); + + stringCompressor->EncodeString(tableName, _SIMPLE_DATABASE_TABLE_NAME_LENGTH, &out); + if (queryPassword && queryPassword[0]) + { + out.Write(true); + // This is sent in plain text. I can do this securely but it's not worth the trouble. + // Use secure connections if you want security. + stringCompressor->EncodeString(queryPassword, _SIMPLE_DATABASE_PASSWORD_LENGTH, &out); + } + else + out.Write(false); + + out.Write(numColumnSubset); + unsigned i; + for (i=0; i < numColumnSubset; i++) + { + stringCompressor->EncodeString(columnNamesSubset[i],256,&out); + } + + out.Write(numFilters); + for (i=0; i < numFilters; i++) + { + RakAssert((int)filter[i].operation<=(int)DataStructures::Table::QF_NOT_EMPTY); + filter[i].Serialize(&out); + } + + out.Write(numRowIDs); + for (i=0; i < numRowIDs; i++) + out.Write(rowIds[i]); + + SendUnified(&out, HIGH_PRIORITY, RELIABLE_ORDERED,0,systemAddress, broadcast); +} +void LightweightDatabaseClient::RemoveRow(const char *tableName, const char *removePassword, unsigned rowId, SystemAddress systemAddress, bool broadcast) +{ + if (tableName==0 || tableName[0]==0) + return; + if (rakPeerInterface==0) + return; + + RakNet::BitStream out; + out.Write((MessageID)ID_DATABASE_REMOVE_ROW); + stringCompressor->EncodeString(tableName, _SIMPLE_DATABASE_TABLE_NAME_LENGTH, &out); + if (removePassword && removePassword[0]) + { + out.Write(true); + // This is sent in plain text. I can do this securely but it's not worth the trouble. + // Use secure connections if you want security. + stringCompressor->EncodeString(removePassword, _SIMPLE_DATABASE_PASSWORD_LENGTH, &out); + } + else + out.Write(false); + + out.Write(rowId); + + SendUnified(&out, HIGH_PRIORITY, RELIABLE_ORDERED,0,systemAddress, broadcast); +} +void LightweightDatabaseClient::UpdateRow(const char *version, const char *tableName, const char *updatePassword, RowUpdateMode updateMode, bool hasRowId, unsigned rowId, DatabaseCellUpdate *cellUpdates, unsigned char numCellUpdates, SystemAddress systemAddress, bool broadcast) +{ + if (tableName==0 || tableName[0]==0) + return; + if (rakPeerInterface==0) + return; + if (cellUpdates==0 || numCellUpdates==0) + return; + + RakNet::BitStream out; + out.Write((MessageID)ID_DATABASE_UPDATE_ROW); + + out.Write(version, 3); + + stringCompressor->EncodeString(tableName, _SIMPLE_DATABASE_TABLE_NAME_LENGTH, &out); + if (updatePassword && updatePassword[0]) + { + out.Write(true); + // This is sent in plain text. I can do this securely but it's not worth the trouble. + // Use secure connections if you want security. + stringCompressor->EncodeString(updatePassword, _SIMPLE_DATABASE_PASSWORD_LENGTH, &out); + } + else + out.Write(false); + + out.Write((unsigned char) updateMode); + out.Write(hasRowId); + if (hasRowId) + out.Write(rowId); + out.Write(numCellUpdates); + unsigned i; + for (i=0; i < numCellUpdates; i++) + cellUpdates[i].Serialize(&out); + + SendUnified(&out, HIGH_PRIORITY, RELIABLE_ORDERED,0,systemAddress, broadcast); +} + +#ifdef _MSC_VER +#pragma warning( pop ) +#endif + +#endif // _RAKNET_SUPPORT_* diff --git a/RakNet/Sources/LightweightDatabaseClient.h b/RakNet/Sources/LightweightDatabaseClient.h new file mode 100644 index 0000000..d28c388 --- /dev/null +++ b/RakNet/Sources/LightweightDatabaseClient.h @@ -0,0 +1,81 @@ +/// \file +/// \brief Contains the client interface to the simple database included with RakNet, useful for a server browser or a lobby server. +/// +/// This file is part of RakNet Copyright 2003 Jenkins Software LLC +/// +/// Usage of RakNet is subject to the appropriate license agreement. + +#include "NativeFeatureIncludes.h" +#if _RAKNET_SUPPORT_LightweightDatabaseClient==1 + +#ifndef __LIGHTWEIGHT_DATABASE_CLIENT_H +#define __LIGHTWEIGHT_DATABASE_CLIENT_H + +#include "Export.h" +#include "PluginInterface2.h" +#include "LightweightDatabaseCommon.h" + +class RakPeerInterface; +struct Packet; + +/// \defgroup SIMPLE_DATABASE_GROUP MySQLNetworkedDatabase +/// \brief Networked data stored in tabular format +/// \details +/// \ingroup PLUGINS_GROUP + +/// \deprecated Use RakNet::SQLite3ClientPlugin found at DependentExtensions/SQLite3Plugin/SQLite3ClientPlugin.h +/// \brief The client interface to the simple database included with RakNet, useful for a server browser or a lobby server. +/// \ingroup SIMPLE_DATABASE_GROUP +class RAK_DLL_EXPORT LightweightDatabaseClient : public PluginInterface2 +{ +public: + + // Constructor + LightweightDatabaseClient(); + + // Destructor + virtual ~LightweightDatabaseClient(); + + /// Sends a query to a remote table. + /// The remote system should return ID_DATABASE_QUERY_REPLY, ID_DATABASE_UNKNOWN_TABLE, or ID_DATABASE_INCORRECT_PASSWORD + /// \note If the remote table uses a password and you send the wrong one you will be silently disconnected and banned for one second. + /// \param[in] tableName String name of the remote table. Case sensitive. + /// \param[in] queryPassword Password to query the remote table, if any. + /// \param[in] columnSubset An array of string names of the columns to query. The resulting table will return the columns in this same order. Pass 0 for all columns. + /// \param[in] numColumnSubset The number of elements in the columnSubset array + /// \param[in] filter Filters to apply to the query. For each row the filters are applied. All filters must be passed for the row to be returned. Pass 0 for no filters. + /// \param[in] numFilters The number of elements in the filter array + /// \param[in] rowIds Which particular rows to return. Pass 0 for all rows. + /// \param[in] numRowIDs The number of elements in the rowIds array + /// \param[in] systemAddress Which system to send to. + /// \param[in] broadcast Broadcast or not. Same as the parameter in RakPeer::Send + void QueryTable(const char *version, const char *tableName, const char *queryPassword, const char **columnNamesSubset, unsigned char numColumnSubset, DatabaseFilter *filter, unsigned char numFilters, unsigned *rowIds, unsigned char numRowIDs, SystemAddress systemAddress, bool broadcast); + + /// Sets one or more values in a new or existing row, assuming the server allows row creation and updates. + /// No response is returned by the server. + /// \param[in] tableName String name of the remote table. Case sensitive. + /// \param[in] updatePassword Password to update the remote table, if any. + /// \param[in] updateMode See RowUpdateMode in LightweightDatabaseCommon.h . This determines if to update an existing or new row. + /// \param[in] hasRowId True if a valid value was passed for \a rowId, false otherwise. If false, will lookup the row by system address. Required if adding a new row and the remote system does not automatically create rowIDs. + /// \param[in] rowId The rowID of the new or existing row. + /// \param[in] cellUpdates An array of DatabaseCellUpdate structures containing the values to write to the remote row. + /// \param[in] numCellUpdates The number of elements in the cellUpdates array + /// \param[in] systemAddress Which system to send to. + /// \param[in] broadcast Broadcast or not. Same as the parameter in RakPeer::Send + void UpdateRow(const char *version, const char *tableName, const char *updatePassword, RowUpdateMode updateMode, bool hasRowId, unsigned rowId, DatabaseCellUpdate *cellUpdates, unsigned char numCellUpdates, SystemAddress systemAddress, bool broadcast); + + /// Removes a remote row, assuming the server allows row removal. + /// No response is returned by the server. + /// \param[in] tableName String name of the remote table. Case sensitive. + /// \param[in] removePassword Password to remove rows from the remote table, if any. + /// \param[in] rowId The rowID of the existing row. + /// \param[in] systemAddress Which system to send to. + /// \param[in] broadcast Broadcast or not. Same as the parameter in RakPeer::Send + void RemoveRow(const char *tableName, const char *removePassword, unsigned rowId, SystemAddress systemAddress, bool broadcast); + +protected: +}; + +#endif + +#endif // _RAKNET_SUPPORT_* diff --git a/RakNet/Sources/LightweightDatabaseCommon.cpp b/RakNet/Sources/LightweightDatabaseCommon.cpp new file mode 100644 index 0000000..89047f2 --- /dev/null +++ b/RakNet/Sources/LightweightDatabaseCommon.cpp @@ -0,0 +1,45 @@ +#include "TableSerializer.h" +#include "LightweightDatabaseCommon.h" +#include "BitStream.h" +#include "StringCompressor.h" + +void DatabaseFilter::Serialize(RakNet::BitStream *out) +{ + stringCompressor->EncodeString(columnName, _TABLE_MAX_COLUMN_NAME_LENGTH, out); + out->Write((unsigned char)columnType); + out->Write((unsigned char)operation); + if (operation!=DataStructures::Table::QF_IS_EMPTY && operation!=DataStructures::Table::QF_NOT_EMPTY) + { + RakAssert(cellValue.isEmpty==false); + TableSerializer::SerializeCell(out, &cellValue, columnType); + } +} +bool DatabaseFilter::Deserialize(RakNet::BitStream *in) +{ + unsigned char temp; + stringCompressor->DecodeString(columnName, _TABLE_MAX_COLUMN_NAME_LENGTH, in); + in->Read(temp); + columnType=(DataStructures::Table::ColumnType)temp; + if (in->Read(temp)==false) + return false; + operation=(DataStructures::Table::FilterQueryType)temp; + if (operation!=DataStructures::Table::QF_IS_EMPTY && operation!=DataStructures::Table::QF_NOT_EMPTY) + { + return TableSerializer::DeserializeCell(in, &cellValue, columnType); + } + return true; +} +void DatabaseCellUpdate::Serialize(RakNet::BitStream *out) +{ + stringCompressor->EncodeString(columnName, _TABLE_MAX_COLUMN_NAME_LENGTH, out); + out->Write((unsigned char)columnType); + TableSerializer::SerializeCell(out, &cellValue, columnType); +} +bool DatabaseCellUpdate::Deserialize(RakNet::BitStream *in) +{ + unsigned char temp; + stringCompressor->DecodeString(columnName, _TABLE_MAX_COLUMN_NAME_LENGTH, in); + in->Read(temp); + columnType=(DataStructures::Table::ColumnType)temp; + return TableSerializer::DeserializeCell(in, &cellValue, columnType); +} diff --git a/RakNet/Sources/LightweightDatabaseCommon.h b/RakNet/Sources/LightweightDatabaseCommon.h new file mode 100644 index 0000000..e7203cf --- /dev/null +++ b/RakNet/Sources/LightweightDatabaseCommon.h @@ -0,0 +1,54 @@ +#ifndef __SIMPLE_DATABASE_COMMON_H +#define __SIMPLE_DATABASE_COMMON_H + +#include "DS_Table.h" + +namespace RakNet +{ + class BitStream; +}; + +#define _SIMPLE_DATABASE_PASSWORD_LENGTH 32 +#define _SIMPLE_DATABASE_TABLE_NAME_LENGTH 32 + +#define SYSTEM_ID_COLUMN_NAME "__SystemAddress" +#define SYSTEM_GUID_COLUMN_NAME "__SystemGuid" +#define LAST_PING_RESPONSE_COLUMN_NAME "__lastPingResponseTime" +#define NEXT_PING_SEND_COLUMN_NAME "__nextPingSendTime" + +struct DatabaseFilter +{ + void Serialize(RakNet::BitStream *out); + bool Deserialize(RakNet::BitStream *in); + + DataStructures::Table::Cell cellValue; + DataStructures::Table::FilterQueryType operation; + DataStructures::Table::ColumnType columnType; + char columnName[_TABLE_MAX_COLUMN_NAME_LENGTH]; +}; + +/// The value to write to a cell in a remote database. +struct DatabaseCellUpdate +{ + void Serialize(RakNet::BitStream *out); + bool Deserialize(RakNet::BitStream *in); + + DataStructures::Table::Cell cellValue; + DataStructures::Table::ColumnType columnType; + char columnName[_TABLE_MAX_COLUMN_NAME_LENGTH]; +}; + +enum RowUpdateMode +{ + // Only update an existing row. rowId is required. + RUM_UPDATE_EXISTING_ROW, + + // Update an existing row if present - otherwise add a new row. rowId is required. + RUM_UPDATE_OR_ADD_ROW, + + // Add a new row. If rowId is passed then the row will only be added if this id doesn't already exist. + // If rowId is not passed and the table requires a rowId the creation fails. + RUM_ADD_NEW_ROW, +}; + +#endif diff --git a/RakNet/Sources/LightweightDatabaseServer.cpp b/RakNet/Sources/LightweightDatabaseServer.cpp new file mode 100644 index 0000000..cfcb09d --- /dev/null +++ b/RakNet/Sources/LightweightDatabaseServer.cpp @@ -0,0 +1,1088 @@ +#include "NativeFeatureIncludes.h" +#if _RAKNET_SUPPORT_LightweightDatabaseServer==1 + +#include "LightweightDatabaseServer.h" +#include "MessageIdentifiers.h" +#include "BitStream.h" +#include "StringCompressor.h" +#include "RakPeerInterface.h" +#include "TableSerializer.h" +#include "RakAssert.h" +#include "GetTime.h" +#include "Rand.h" +#include "MasterServer.h" +#include "Log.h" +#include "DS_Table.h" +#include +#include "RakNetStatistics.h" +#include +static const int SEND_PING_INTERVAL=15000; +static const int DROP_SERVER_INTERVAL=75000; + +#ifdef _MSC_VER +#pragma warning( push ) +#endif + +int LightweightDatabaseServer::DatabaseTableComp( const char* const &key1, const char* const &key2 ) +{ + return strcmp(key1, key2); +} + +LightweightDatabaseServer::LightweightDatabaseServer() +{ + // Set current master server _client_ version + version[0]=2; version[1]=0; version[2]=0; + + statDelay = 0; + statTimer = time(0) + statDelay; + lastMinQueryCount = 0; + lastMinUpdateCount = 0; +} +LightweightDatabaseServer::~LightweightDatabaseServer() +{ + Clear(); +} +DataStructures::Table *LightweightDatabaseServer::GetTable(const char *tableName) +{ + if (tableName==0) + { + if (database.Size()>0) + return &(database[0]->table); + return 0; + } + if (database.Has(tableName)) + return &(database.Get(tableName)->table); + return 0; +} +DataStructures::Page *LightweightDatabaseServer::GetTableRows(const char *tableName) +{ + if (database.Has(tableName)) + database.Get(tableName)->table.GetRows().GetListHead(); + return 0; +} +DataStructures::Table* LightweightDatabaseServer::AddTable(const char *tableName, + bool allowRemoteQuery, + bool allowRemoteUpdate, + bool allowRemoteRemove, + const char *queryPassword, + const char *updatePassword, + const char *removePassword, + bool oneRowPerSystemAddress, + bool onlyUpdateOwnRows, + bool removeRowOnPingFailure, + bool removeRowOnDisconnect, + bool autogenerateRowIDs) +{ + if (tableName==0 || tableName[0]==0) + return 0; + if (database.Has(tableName)) + return 0; + DatabaseTable *databaseTable = RakNet::OP_NEW( __FILE__, __LINE__ ); + + strncpy(databaseTable->tableName, tableName, _SIMPLE_DATABASE_TABLE_NAME_LENGTH-1); + databaseTable->tableName[_SIMPLE_DATABASE_TABLE_NAME_LENGTH-1]=0; + + if (allowRemoteUpdate) + { + strncpy(databaseTable->updatePassword, updatePassword, _SIMPLE_DATABASE_PASSWORD_LENGTH-1); + databaseTable->updatePassword[_SIMPLE_DATABASE_PASSWORD_LENGTH-1]=0; + } + else + databaseTable->updatePassword[0]=0; + + if (allowRemoteQuery) + { + strncpy(databaseTable->queryPassword, queryPassword, _SIMPLE_DATABASE_PASSWORD_LENGTH-1); + databaseTable->queryPassword[_SIMPLE_DATABASE_PASSWORD_LENGTH-1]=0; + } + else + databaseTable->queryPassword[0]=0; + + if (allowRemoteRemove) + { + strncpy(databaseTable->removePassword, removePassword, _SIMPLE_DATABASE_PASSWORD_LENGTH-1); + databaseTable->removePassword[_SIMPLE_DATABASE_PASSWORD_LENGTH-1]=0; + } + else + databaseTable->removePassword[0]=0; + + if (allowRemoteUpdate) + { + databaseTable->allowRemoteUpdate=true; + databaseTable->oneRowPerSystemAddress=oneRowPerSystemAddress; + databaseTable->onlyUpdateOwnRows=onlyUpdateOwnRows; + databaseTable->removeRowOnPingFailure=removeRowOnPingFailure; + databaseTable->removeRowOnDisconnect=removeRowOnDisconnect; + } + else + { + // All these parameters are related to IP tracking, which is not done if remote updates are not allowed + databaseTable->allowRemoteUpdate=true; + databaseTable->oneRowPerSystemAddress=false; + databaseTable->onlyUpdateOwnRows=false; + databaseTable->removeRowOnPingFailure=false; + databaseTable->removeRowOnDisconnect=false; + } + + databaseTable->nextRowId=0; + databaseTable->nextRowPingCheck=0; + + databaseTable->autogenerateRowIDs=autogenerateRowIDs; + databaseTable->allowRemoteRemove=allowRemoteRemove; + databaseTable->allowRemoteQuery=allowRemoteQuery; + + database.SetNew(databaseTable->tableName, databaseTable); + + if ( oneRowPerSystemAddress || onlyUpdateOwnRows || removeRowOnPingFailure || removeRowOnDisconnect) + { + databaseTable->SystemAddressColumnIndex=databaseTable->table.AddColumn(SYSTEM_ID_COLUMN_NAME, DataStructures::Table::BINARY); + databaseTable->SystemGuidColumnIndex=databaseTable->table.AddColumn(SYSTEM_GUID_COLUMN_NAME, DataStructures::Table::BINARY); + } + else + { + databaseTable->SystemAddressColumnIndex=(unsigned) -1; + databaseTable->SystemGuidColumnIndex=(unsigned) -1; + } + if (databaseTable->removeRowOnPingFailure) + { + databaseTable->lastPingResponseColumnIndex=databaseTable->table.AddColumn(LAST_PING_RESPONSE_COLUMN_NAME, DataStructures::Table::NUMERIC); + databaseTable->nextPingSendColumnIndex=databaseTable->table.AddColumn(NEXT_PING_SEND_COLUMN_NAME, DataStructures::Table::NUMERIC); + } + else + { + databaseTable->lastPingResponseColumnIndex=(unsigned) -1; + databaseTable->nextPingSendColumnIndex=(unsigned) -1; + } + + return &(databaseTable->table); +} +bool LightweightDatabaseServer::RemoveTable(const char *tableName) +{ + LightweightDatabaseServer::DatabaseTable *databaseTable; + databaseTable = database.Get(tableName); + if (databaseTable==0) + return false; + // Be sure to call Delete on database before I do the actual pointer deletion since the key won't be valid after that time. + database.Delete(tableName); + databaseTable->table.Clear(); + RakNet::OP_DELETE(databaseTable, __FILE__, __LINE__); + return true; +} +void LightweightDatabaseServer::Clear(void) +{ + unsigned i; + + for (i=0; i < database.Size(); i++) + { + database[i]->table.Clear(); + RakNet::OP_DELETE(database[i], __FILE__, __LINE__); + } + + database.Clear(); +} +unsigned LightweightDatabaseServer::GetAndIncrementRowID(const char *tableName) + { + LightweightDatabaseServer::DatabaseTable *databaseTable; + databaseTable = database.Get(tableName); + RakAssert(databaseTable); + RakAssert(databaseTable->autogenerateRowIDs==true); + return ++(databaseTable->nextRowId) - 1; + } + +void LightweightDatabaseServer::Update(void) +{ + RakNetTime rakTime=0; + DatabaseTable *databaseTable; + DataStructures::Page *cur; + unsigned i,j; + DataStructures::Table::Row* row; + DataStructures::List removeList; + SystemAddress systemAddress; + + // periodic ping if removing system that do not respond to pings. + for (i=0; i < database.Size(); i++) + { + databaseTable=database[i]; + + if (databaseTable->removeRowOnPingFailure) + { + // Reading the time is slow - only do it once if necessary. + if (rakTime==0) + rakTime = RakNet::GetTime(); + + if (databaseTable->nextRowPingCheck < rakTime) + { + databaseTable->nextRowPingCheck=rakTime+1000+(randomMT()%1000); + const DataStructures::BPlusTree &rows = databaseTable->table.GetRows(); + cur = rows.GetListHead(); + DataStructures::List removeAddressList; + while (cur) + { + // Mark dropped entities + for (j=0; j < (unsigned)cur->size; j++) + { + row = cur->data[j]; + row->cells[databaseTable->SystemAddressColumnIndex]->Get((char*)&systemAddress, 0); + if (rakPeerInterface->IsConnected(systemAddress)==false) + { + if (rakTime > rakTime - (unsigned int) row->cells[databaseTable->lastPingResponseColumnIndex]->i && + rakTime - (unsigned int) row->cells[databaseTable->lastPingResponseColumnIndex]->i > (unsigned int) DROP_SERVER_INTERVAL) + { + removeList.Insert(cur->keys[j], __FILE__, __LINE__); + removeAddressList.Insert(systemAddress, __FILE__, __LINE__); + } + else + { + if (row->cells[databaseTable->nextPingSendColumnIndex]->i < (int) rakTime) + { + char str1[64]; + systemAddress.ToString(false, str1); + rakPeerInterface->Ping(str1, systemAddress.port, false); + row->cells[databaseTable->nextPingSendColumnIndex]->i=(double)(rakTime+SEND_PING_INTERVAL+(randomMT()%1000)); + } + } + } + } + cur=cur->next; + } + + // Remove dropped entities + for (j=0; j < removeList.Size(); j++) + { + addressMap.erase(removeAddressList[j]); + databaseTable->table.RemoveRow(removeList[j]); + } + removeList.Clear(true, __FILE__,__LINE__); + + } + } + } + + // Total queries and updates at COUNT_DELAY interval + if (statTimer < time(0) && statDelay > 0) + { + std::ostringstream tableString; + int emptyTables = 0; + for (i=0; i < database.Size(); i++) + { + databaseTable=database[i]; + tableString << databaseTable->tableName << " "; + if (databaseTable->table.GetRowCount() == 0) + emptyTables++; + } + if (database.Size() > 0) + { + tableString << "\n"; + Log::stats_log(tableString.str().c_str()); + } + + if (rakPeerInterface->NumberOfConnections() == 0 && addressMap.size() != 0) + { + Log::warn_log("Cleaning up address map, dumping %d addresses\n", addressMap.size()); + addressMap.clear(); + } + + Log::stats_log("Tables: %d, Empty tables: %d, Conn. count: %d, Queries: %d, Updates: %d, Address map: %d\n", database.Size(), emptyTables, rakPeerInterface->NumberOfConnections(), lastMinQueryCount, lastMinUpdateCount, addressMap.size()); + // Reset last minute counters if minute is up + lastMinQueryCount = 0; + lastMinUpdateCount = 0; + statTimer = time(0) + statDelay; + } +} +PluginReceiveResult LightweightDatabaseServer::OnReceive(Packet *packet) + { + switch (packet->data[0]) + { + case ID_DATABASE_QUERY_REQUEST: + Log::info_log("Query request from %s\n", packet->systemAddress.ToString()); + lastMinQueryCount++; + OnQueryRequest(packet); + return RR_STOP_PROCESSING_AND_DEALLOCATE; + case ID_DATABASE_UPDATE_ROW: + Log::info_log("Update row request from %s\n", packet->systemAddress.ToString()); + lastMinUpdateCount++; + OnUpdateRow(packet); + return RR_STOP_PROCESSING_AND_DEALLOCATE; + case ID_DATABASE_REMOVE_ROW: + OnRemoveRow(packet); + return RR_STOP_PROCESSING_AND_DEALLOCATE; + case ID_PONG: + OnPong(packet); + return RR_CONTINUE_PROCESSING; + } + return RR_CONTINUE_PROCESSING; + } +void LightweightDatabaseServer::OnClosedConnection(SystemAddress systemAddress, RakNetGUID rakNetGUID, PI2_LostConnectionReason lostConnectionReason ) +{ + (void) rakNetGUID; + (void) lostConnectionReason; + Log::info_log("Connection with %s closed\n", systemAddress.ToString()); + + RemoveRowsFromIP(systemAddress); +} +void LightweightDatabaseServer::OnQueryRequest(Packet *packet) +{ + RakNet::BitStream inBitstream(packet->data, packet->length, false); + + if (!CheckVersion(packet, inBitstream)) + { + // Incompatible version, stop processing + return; + } + LightweightDatabaseServer::DatabaseTable *databaseTable = DeserializeClientHeader(&inBitstream, packet, 0, true); + RakNet::BitStream outBitstream; + if (databaseTable==0) + { + // Return empty list to client so he knows for sure no servers are running + outBitstream.Write((MessageID)ID_DATABASE_QUERY_REPLY); + rakPeerInterface->Send(&outBitstream, HIGH_PRIORITY, RELIABLE_ORDERED, 0, packet->systemAddress, false); + return; + } + else if (databaseTable->table.GetRowCount() == 0) + { + outBitstream.Write((MessageID)ID_DATABASE_QUERY_REPLY); + rakPeerInterface->Send(&outBitstream, HIGH_PRIORITY, RELIABLE_ORDERED, 0, packet->systemAddress, false); + return; + } + if (databaseTable->allowRemoteQuery==false) + return; + unsigned char numColumnSubset; + unsigned i; + if (inBitstream.Read(numColumnSubset)==false) + return; + unsigned char columnName[256]; + unsigned columnIndicesSubset[256]; + unsigned columnIndicesCount; + for (i=0,columnIndicesCount=0; i < numColumnSubset; i++) + { + stringCompressor->DecodeString((char*)columnName, 256, &inBitstream); + unsigned colIndex = databaseTable->table.ColumnIndex((char*)columnName); + if (colIndex!=(unsigned)-1) + columnIndicesSubset[columnIndicesCount++]=colIndex; + } + unsigned char numNetworkedFilters; + if (inBitstream.Read(numNetworkedFilters)==false) + return; + DatabaseFilter networkedFilters[256]; + for (i=0; i < numNetworkedFilters; i++) + { + if (networkedFilters[i].Deserialize(&inBitstream)==false) + return; + } + + unsigned rowIds[256]; + unsigned char numRowIDs; + if (inBitstream.Read(numRowIDs)==false) + return; + for (i=0; i < numRowIDs; i++) + inBitstream.Read(rowIds[i]); + + // Convert the safer and more robust networked database filter to the more efficient form the table actually uses. + DataStructures::Table::FilterQuery tableFilters[256]; + unsigned numTableFilters=0; + for (i=0; i < numNetworkedFilters; i++) + { + tableFilters[numTableFilters].columnIndex=databaseTable->table.ColumnIndex(networkedFilters[i].columnName); + if (tableFilters[numTableFilters].columnIndex==(unsigned)-1) + continue; + if (networkedFilters[i].columnType!=databaseTable->table.GetColumns()[tableFilters[numTableFilters].columnIndex].columnType) + continue; + tableFilters[numTableFilters].operation=networkedFilters[i].operation; + // It's important that I store a pointer to the class here or the destructor of the class will deallocate the cell twice + tableFilters[numTableFilters++].cellValue=&(networkedFilters[i].cellValue); + } + + DataStructures::Table queryResult; + databaseTable->table.QueryTable(columnIndicesSubset, columnIndicesCount, tableFilters, numTableFilters, rowIds, numRowIDs, &queryResult); + + // Go through query results and check if the IP address needs to be replaced because of common NAT router. + DataStructures::Page *cur = queryResult.GetRows().GetListHead(); + unsigned rowIndex; + while (cur) + { + for (rowIndex=0; rowIndex < (unsigned)cur->size; rowIndex++) + { + // If this host has the same IP as the client requesting the host list give him the internal IPs since + // connecting directly to the external IP will probably not work. Do this regardless of if NAT was enabled or not. + if (strcmp(cur->data[rowIndex]->cells[IPINDEX]->c,packet->systemAddress.ToString(false))==0) + { + SystemAddress tempID; + tempID.SetBinaryAddress(cur->data[rowIndex]->cells[IPINDEX]->c); + tempID.port = cur->data[rowIndex]->cells[PORTINDEX]->i; + + int ipCount = ((InternalID)addressMap[tempID]).ips.size(); + if (addressMap.count(tempID) > 0) + { + char (*ips)[16]; + ips = new char[ipCount][16]; + int counter = 0; + Log::debug_log("Injecting %d internal IPs: ", ipCount); + std::ostringstream internalIPString; + internalIPString << "Injecting %d internal IPs: " << ipCount << " "; + IPList::iterator i; + IPList list = ((InternalID)addressMap[tempID]).ips; + for (i= list.begin(); i != list.end(); i++) + { + strcpy(ips[counter], ((std::string)*i).c_str()); + internalIPString << ips[counter] << " "; + counter++; + } + internalIPString << "\n"; + Log::debug_log(internalIPString.str().c_str()); + + cur->data[rowIndex]->UpdateCell(NATINDEX, 0.0); // Turn off NAT because we don't need to NAT connection routine internally + cur->data[rowIndex]->UpdateCell(IPINDEX, ipCount*16, (char*)ips); + cur->data[rowIndex]->UpdateCell(PORTINDEX, ((InternalID)addressMap[tempID]).port); + delete[] ips; + } + else + { + Log::error_log("Server (with NAT enabled) and client IPs, %s, match but server internal IP was not found during lookup\n", packet->systemAddress.ToString(false)); + } + } + } + cur=cur->next; + } + outBitstream.Write((MessageID)ID_DATABASE_QUERY_REPLY); + TableSerializer::SerializeTable(&queryResult, &outBitstream); + SendUnified(&outBitstream, HIGH_PRIORITY, RELIABLE_ORDERED, 0, packet->systemAddress, false); +} +void LightweightDatabaseServer::OnUpdateRow(Packet *packet) +{ + RakNet::BitStream inBitstream(packet->data, packet->length, false); + + if (!CheckVersion(packet, inBitstream)) + { + // Incompatible version, stop processing + return; + } + + LightweightDatabaseServer::DatabaseTable *databaseTable = DeserializeClientHeader(&inBitstream, packet, 1); + if (databaseTable==0) + { + Log::error_log("Table not found and failed to dynamically create table\n"); + return; + } + if (databaseTable->allowRemoteUpdate==false) + { + Log::error_log("Remote updates not allowed by table design\n"); + return; + } + unsigned char updateMode; + bool hasRowId=false; + unsigned rowId; + unsigned i; + DataStructures::Table::Row *row; + inBitstream.Read(updateMode); + inBitstream.Read(hasRowId); + if (hasRowId) + inBitstream.Read(rowId); + else + rowId=0; // MODIFIED: We would like row IDs starting from 0 + //rowId=(unsigned) -1; // Not used here but remove the debugging check + unsigned char numCellUpdates; + if (inBitstream.Read(numCellUpdates)==false) + return; + // Read the updates for the row + DatabaseCellUpdate cellUpdates[256]; + for (i=0; i < numCellUpdates; i++) + { + if (cellUpdates[i].Deserialize(&inBitstream)==false) + { + Log::error_log("LightweightDatabaseServer::OnUpdateRow cellUpdates deserialize failed i=%i numCellUpdates=%i\n",i,numCellUpdates); + return; + } + } + + if ((RowUpdateMode)updateMode==RUM_UPDATE_EXISTING_ROW) + { + if (hasRowId==false) + { + unsigned rowKey; + row = GetRowFromIP(databaseTable, packet->systemAddress, &rowKey); + if (row==0) + { + Log::error_log("Attempting to update existing row without supplying row ID"); + return; + } + } + else + { + + row = databaseTable->table.GetRowByID(rowId); + if (row==0 || (databaseTable->onlyUpdateOwnRows && RowHasIP(row, packet->systemAddress, databaseTable->SystemAddressColumnIndex)==false)) + { + if (row==0) + Log::error_log("LightweightDatabaseServer::OnUpdateRow row = databaseTable->table.GetRowByID(rowId); row==0\n"); + else + Log::error_log("Attempting to update someone elses row while in RUM_UPDATE_EXISTING_ROW\n"); + + return; // You can't update some other system's row + } + } + } + else if ((RowUpdateMode)updateMode==RUM_UPDATE_OR_ADD_ROW) + { + if (hasRowId) + { + Log::info_log("Updating row %u in %s for %s\n", rowId, databaseTable->tableName, packet->systemAddress.ToString()); + row = databaseTable->table.GetRowByID(rowId); + } + else + { + unsigned rowKey; + row = GetRowFromIP(databaseTable, packet->systemAddress, &rowKey); + } + + if (row==0) + { + if (hasRowId) Log::warn_log("Failed to find given row ID %u from %s\n",rowId, packet->systemAddress.ToString()); + row=AddRow(databaseTable, packet->systemAddress, packet->guid, hasRowId, rowId); + // Send the chosen row ID to client + RakNet::BitStream stream; + stream.Write((unsigned char)ID_DATABASE_ROWID); + stream.Write(rowId); + SendUnified(&stream, HIGH_PRIORITY, RELIABLE_ORDERED, 0, packet->systemAddress, false); + Log::info_log("Sent row ID %u in %s to host at %s\n", rowId, databaseTable->tableName, packet->systemAddress.ToString()); + if (row==0) + { + Log::error_log("Row error while performing RUM_UPDATE_OR_ADD_ROW, row update failed\n"); + return; + } + } + else + { + // Existing row + if (databaseTable->onlyUpdateOwnRows && RowHasIP(row, packet->systemAddress, databaseTable->SystemAddressColumnIndex)==false) + { + SystemAddress sysAddr; + memcpy(&sysAddr, row->cells[databaseTable->SystemAddressColumnIndex]->c, SystemAddress::size()); + + char str1[64], str2[64]; + packet->systemAddress.ToString(true, str1); + sysAddr.ToString(true, str2); + Log::warn_log("Attempting to update someone elses row, while in RUM_UPDATE_OR_ADD_ROW. caller=%s owner=%s. Creating new row.\n", + str1, str2); + + Log::warn_log("Attempting to update someone elses row, while in RUM_UPDATE_OR_ADD_ROW. Creating new row.\n"); + row=AddRow(databaseTable, packet->systemAddress, packet->guid, false, rowId); + RakNet::BitStream stream; + stream.Write((unsigned char)ID_DATABASE_ROWID); + stream.Write(rowId); + SendUnified(&stream, HIGH_PRIORITY, RELIABLE_ORDERED, 0, packet->systemAddress, false); + Log::warn_log("Sent row ID %u to host at %s\n",rowId, packet->systemAddress.ToString()); + + //return; // You can't update some other system's row + } + } + } + else + { + RakAssert((RowUpdateMode)updateMode==RUM_ADD_NEW_ROW); + + row=AddRow(databaseTable, packet->systemAddress, packet->guid, hasRowId, rowId); + Log::info_log("Created new row %u in %s for %s\n", rowId, databaseTable->tableName, packet->systemAddress.ToString()); + + // Send the chosen row ID to client + RakNet::BitStream stream; + stream.Write((unsigned char)ID_DATABASE_ROWID); + stream.Write(rowId); + rakPeerInterface->Send(&stream, HIGH_PRIORITY, RELIABLE_ORDERED, 0, packet->systemAddress, false); + Log::info_log("Sent row ID %u to client %s\n",rowId, packet->systemAddress.ToString()); + + if (row==0) + { + Log::error_log("ERROR: LightweightDatabaseServer::OnUpdateRow updateMode==RUM_ADD_NEW_ROW; row==0\n"); + return; + } + } + + unsigned columnIndex; + bool doNAT = false; + InternalID InternalID; + for (i=0; i < numCellUpdates; i++) + { + columnIndex=databaseTable->table.ColumnIndex(cellUpdates[i].columnName); + RakAssert(columnIndex!=(unsigned)-1); // Unknown column name + if (columnIndex!=(unsigned)-1 && + (databaseTable->onlyUpdateOwnRows==false || + (columnIndex!=databaseTable->lastPingResponseColumnIndex && + columnIndex!=databaseTable->nextPingSendColumnIndex && + columnIndex!=databaseTable->SystemAddressColumnIndex && + columnIndex!=databaseTable->SystemGuidColumnIndex))) + { + if (cellUpdates[i].cellValue.isEmpty) + row->cells[columnIndex]->Clear(); + else if (cellUpdates[i].columnType==databaseTable->table.GetColumnType(columnIndex)) + { + if (cellUpdates[i].columnType==DataStructures::Table::NUMERIC) + { + if (columnIndex == NATINDEX) + doNAT = (bool) cellUpdates[i].cellValue.i; + if (columnIndex == PORTINDEX) + { + row->UpdateCell(columnIndex, packet->systemAddress.port); + InternalID.port = cellUpdates[i].cellValue.i; + Log::debug_log("Updating, injecting %d at column %d\n", packet->systemAddress.port, columnIndex); + } + else + { + row->UpdateCell(columnIndex, cellUpdates[i].cellValue.i); + Log::debug_log("Updating, inserting %d at column %d\n", cellUpdates[i].cellValue.i, columnIndex); + } + } + else if (cellUpdates[i].columnType==DataStructures::Table::BINARY) + { + if (columnIndex == IPINDEX) + { + // Process external IP + row->UpdateCell(columnIndex, 16, (char*)packet->systemAddress.ToString(false)); + + // Process internal IP(s) + char (*ips)[16]; + ips = new char[((int)cellUpdates[i].cellValue.i)/16][16]; + memcpy(ips, cellUpdates[i].cellValue.c, cellUpdates[i].cellValue.i); + InternalID.ips.clear(); + for (int j=0; j<(cellUpdates[i].cellValue.i)/16; j++) + { + if (ips[j][0] == 0) + break; + InternalID.ips.push_back(ips[j]); + } + + Log::debug_log("Updating, injecting %s at column %d\n",packet->systemAddress.ToString(false), columnIndex); + delete[] ips; + } + else + { + row->UpdateCell(columnIndex, cellUpdates[i].cellValue.i, cellUpdates[i].cellValue.c); + } + } + else + { + RakAssert(cellUpdates[i].columnType==DataStructures::Table::STRING); + row->UpdateCell(columnIndex, cellUpdates[i].cellValue.c); + Log::debug_log("Updating, inserting %s at column %d\n",cellUpdates[i].cellValue.c, columnIndex); + } + } + } + } + // Add GUID + row->UpdateCell(GUIDINDEX, packet->guid.ToString()); + Log::debug_log("Updating, inserting %s at column %d\n",packet->guid.ToString(), GUIDINDEX); + + std::ostringstream internalIPString; + internalIPString << "Internal port is " << InternalID.port << " IPs set as "; + for (IPList::iterator iterator = InternalID.ips.begin(); iterator != InternalID.ips.end(); iterator++) + { + internalIPString << (std::string)*iterator << " "; + } + internalIPString << "\n"; + Log::debug_log(internalIPString.str().c_str()); + addressMap[packet->systemAddress] = InternalID; +} +void LightweightDatabaseServer::OnRemoveRow(Packet *packet) +{ + RakNet::BitStream inBitstream(packet->data, packet->length, false); + LightweightDatabaseServer::DatabaseTable *databaseTable = DeserializeClientHeader(&inBitstream, packet, 0, true); + if (databaseTable==0) + { + Log::info_log("Failed to remove row for %s because table lookup failed\n",packet->systemAddress.ToString()); + return; + } + if (databaseTable->allowRemoteRemove==false) + { + Log::info_log("Remote row removal on %s forbidden, from %s\n", databaseTable->tableName, packet->systemAddress.ToString()); + return; + } + unsigned rowId; + inBitstream.Read(rowId); + databaseTable->table.RemoveRow(rowId); + addressMap.erase(packet->systemAddress); + Log::info_log("Removing row %u by request\n",rowId); + Log::debug_log("Current address map count is %d\n", addressMap.size()); +} +void LightweightDatabaseServer::OnPong(Packet *packet) +{ + unsigned databaseIndex; + DatabaseTable *databaseTable; + unsigned curIndex; + SystemAddress systemAddress; + RakNetTime time=0; + for (databaseIndex=0; databaseIndex < database.Size(); databaseIndex++) + { + databaseTable=database[databaseIndex]; + if (databaseTable->removeRowOnPingFailure) + { + if ((unsigned int) databaseTable->SystemAddressColumnIndex==(unsigned int)-1) + continue; + if (time==0) + time=RakNet::GetTime(); + + const DataStructures::BPlusTree &rows = databaseTable->table.GetRows(); + DataStructures::Page *cur = rows.GetListHead(); + + while (cur) + { + for (curIndex=0; curIndex < (unsigned) cur->size; curIndex++) + { + cur->data[curIndex]->cells[databaseTable->SystemAddressColumnIndex]->Get((char*)&systemAddress,0); + if (systemAddress==packet->systemAddress) + { + cur->data[curIndex]->cells[databaseTable->lastPingResponseColumnIndex]->i=(int)time; + } + } + cur=cur->next; + } + } + } +} + +// Read version number from bitstream +bool LightweightDatabaseServer::CheckVersion(Packet *packet, RakNet::BitStream &inBitstream) +{ + inBitstream.IgnoreBits(8); + char peerVersion[3]; + inBitstream.Read(peerVersion, sizeof(peerVersion)); + + char version009[3] = {0,0,9}; + if (memcmp(peerVersion, version, sizeof(peerVersion))==0) + { + Log::debug_log("Version %d.%d.%d connected\n", peerVersion[0], peerVersion[1], peerVersion[2]); + } + // This is just demo code but for each master server version which is redirected, an if block like this needs to be created. + else if (memcmp(peerVersion, version009, sizeof(peerVersion))==0) + { + RakNet::BitStream outBitstream; + SystemAddress oldMasterServer, oldFacilitator; + oldMasterServer.port = 34567; + oldMasterServer.SetBinaryAddress("127.0.0.1"); + oldFacilitator.port = 50001; + oldFacilitator.SetBinaryAddress("127.0.0.1"); + if (rakPeerInterface->GetInternalID().port == oldMasterServer.port) + { + Log::warn_log("Wrong version number but accepting connection\n"); + return true; + } + else + { + char oldFac[32]; + strcpy(oldFac, oldFacilitator.ToString()); + Log::info_log("Old version connected, %d.%d.%d, redirecting master server to %s, facilitator to %s\n", peerVersion[0], peerVersion[1], peerVersion[2], oldMasterServer.ToString(), oldFac); + + outBitstream.Write((MessageID)ID_MASTERSERVER_REDIRECT); + outBitstream.Write(oldMasterServer); + outBitstream.Write(oldFacilitator); + rakPeerInterface->Send(&outBitstream, HIGH_PRIORITY, RELIABLE_ORDERED, 0, packet->systemAddress, false); + return false; + } + } + else + { + Log::error_log("Incorrect master server client version, %d.%d.%d\n", peerVersion[0], peerVersion[1], peerVersion[2]); + + RakNet::BitStream outBitstream; + outBitstream.Write((MessageID)ID_MASTERSERVER_MSG); + std::string message = "Your master server client version is too old. Please upgrade."; + outBitstream.Write((unsigned int)message.length()); + outBitstream.Write(message.c_str(), (unsigned int)message.length()); + rakPeerInterface->Send(&outBitstream, HIGH_PRIORITY, RELIABLE_ORDERED, 0, packet->systemAddress, false); + return false; + } + + // Correct version + return true; + +} + +// Added utility function for dynamic table creation +LightweightDatabaseServer::DatabaseTable* LightweightDatabaseServer::CreateDefaultTable(std::string tableName) +{ + if (tableName.empty()) + { + Log::error_log("Error: Empty table name given during table creation. Aborting.\n"); + return 0; + } + + bool allowRemoteUpdate = true; + bool allowRemoteQuery = true; + bool allowRemoteRemove = true; + char queryPassword[_SIMPLE_DATABASE_PASSWORD_LENGTH]; + char updatePassword[_SIMPLE_DATABASE_PASSWORD_LENGTH]; + char removePassword[_SIMPLE_DATABASE_PASSWORD_LENGTH]; + bool oneRowPerSystemId = true; + bool onlyUpdateOwnRows = true; + bool removeRowOnPingFailure = true; + bool removeRowOnDisconnect = true; + bool autogenerateRowIDs = true; + + queryPassword[0] = 0; + updatePassword[0] = 0; + removePassword[0] = 0; + + DataStructures::Table *table; + table = AddTable(const_cast(tableName.c_str()), allowRemoteQuery, allowRemoteUpdate, allowRemoteRemove, queryPassword, updatePassword, removePassword, oneRowPerSystemId, onlyUpdateOwnRows, removeRowOnPingFailure, removeRowOnDisconnect, autogenerateRowIDs); + if (table) + { + Log::debug_log("Table %s created.\n", tableName.c_str()); + table->AddColumn("NAT", table->NUMERIC); + table->AddColumn("Game name", table->STRING); + table->AddColumn("Connected players", table->NUMERIC); + table->AddColumn("Player limit", table->NUMERIC); + table->AddColumn("Password protected", table->NUMERIC); + table->AddColumn("IP address", table->BINARY); + table->AddColumn("Port", table->NUMERIC); + table->AddColumn("Comment", table->STRING); + table->AddColumn("GUID", table->STRING); + return database.Get(const_cast(tableName.c_str())); + } + else + { + Log::error_log("Table %s creation failed. Possibly already exists.\n", tableName.c_str()); + return 0; + } +} + +LightweightDatabaseServer::DatabaseTable * LightweightDatabaseServer::DeserializeClientHeader(RakNet::BitStream *inBitstream, Packet *packet, int mode, bool query) + { + RakNet::BitStream outBitstream; + bool hasPassword=false; + char password[_SIMPLE_DATABASE_PASSWORD_LENGTH]; + //inBitstream->IgnoreBits(8); // inBitstream should be at the correct position when passed + char tableName[_SIMPLE_DATABASE_TABLE_NAME_LENGTH]; + stringCompressor->DecodeString(tableName, _SIMPLE_DATABASE_TABLE_NAME_LENGTH, inBitstream); + if ((tableName[0]) == 0) + { + if (query) + Log::error_log("No table name defined, during query/removal.\n"); + else + Log::error_log("No table name defined.\n"); + Log::error_log("Malformed packet from %s, packet size %d bits.\n", packet->systemAddress.ToString(), packet->bitSize); + return 0; + } + DatabaseTable *databaseTable; + // Check if table already exists or if it needs to be dynamically created + if (database.Has(tableName)) + { + Log::info_log("Fetching existing table %s\n", tableName); + databaseTable = database.Get(tableName); + } + else + { + // If this is a query (row removal or host list) then we should + // stop now because we don't want to create a new table etc. + if (query) + { + Log::info_log("Table %s not found during query or row removal from %s\n", tableName, packet->systemAddress.ToString()); + return 0; + } + + // Note that when table lookups fail index 0 is returned and this is the first table created. + databaseTable = CreateDefaultTable(tableName); + if (databaseTable == 0) + { + Log::error_log("No table selected. Cannot proceed.\n"); + outBitstream.Write((MessageID)ID_DATABASE_UNKNOWN_TABLE); + rakPeerInterface->Send(&outBitstream, HIGH_PRIORITY, RELIABLE_ORDERED, 0, packet->systemAddress, false); + return 0; + } + } + + if (strcmp(databaseTable->tableName,tableName)!=0) + { + Log::warn_log("Incorrect table returned during table lookup, requested %s, got %s\n", tableName, databaseTable->tableName); + if (query) + return 0; + databaseTable = CreateDefaultTable(tableName); + } + + if (databaseTable == 0) + { + Log::error_log("Critical error: No database table selected just before being accessed. Something went wrong.\n"); + return 0; + } + + const char *dbPass; + if (mode==0) + dbPass=databaseTable->queryPassword; + else if (mode==1) + dbPass=databaseTable->updatePassword; + else + dbPass=databaseTable->removePassword; + + inBitstream->Read(hasPassword); + if (hasPassword) + { + if (stringCompressor->DecodeString(password, _SIMPLE_DATABASE_PASSWORD_LENGTH, inBitstream)==false) + Log::error_log("Error: Table password info not found\n"); + return 0; + if (databaseTable->queryPassword[0] && strcmp(password, dbPass)!=0) + { + outBitstream.Write((MessageID)ID_DATABASE_INCORRECT_PASSWORD); + rakPeerInterface->Send(&outBitstream, HIGH_PRIORITY, RELIABLE_ORDERED, 0, packet->systemAddress, false); + // Short ban to prevent brute force password attempts + char str1[64]; + packet->systemAddress.ToString(false, str1); + rakPeerInterface->AddToBanList(str1, 1000); + // Don't send a disconnection notification so it closes the connection right away. + rakPeerInterface->CloseConnection(packet->systemAddress, false, 0); + Log::error_log("Incorrect table password given, banning client\n"); + return 0; + } + } + else if (dbPass[0]) + { + outBitstream.Write((MessageID)ID_DATABASE_INCORRECT_PASSWORD); + rakPeerInterface->Send(&outBitstream, HIGH_PRIORITY, RELIABLE_ORDERED, 0, packet->systemAddress, false); + Log::error_log("Error: Empty table password\n"); + return 0; + } + + return databaseTable; + } + +DataStructures::Table::Row * LightweightDatabaseServer::GetRowFromIP(DatabaseTable *databaseTable, SystemAddress systemAddress, unsigned *rowKey) +{ + const DataStructures::BPlusTree &rows = databaseTable->table.GetRows(); + DataStructures::Page *cur = rows.GetListHead(); + DataStructures::Table::Row* row; + unsigned i; + if ((unsigned int) databaseTable->SystemAddressColumnIndex==(unsigned int)-1) + return 0; + while (cur) + { + for (i=0; i < (unsigned)cur->size; i++) + { + row = cur->data[i]; + if (RowHasIP(row, systemAddress, databaseTable->SystemAddressColumnIndex )) + { + if (rowKey) + *rowKey=cur->keys[i]; + return row; + } + } + cur=cur->next; + } + return 0; +} +bool LightweightDatabaseServer::RowHasIP(DataStructures::Table::Row *row, SystemAddress systemAddress, unsigned SystemAddressColumnIndex) +{ + if ((unsigned int) SystemAddressColumnIndex==(unsigned int)-1) + return false; + + SystemAddress sysAddr; + memcpy(&sysAddr, row->cells[SystemAddressColumnIndex]->c, SystemAddress::size()); + return sysAddr==systemAddress; + + // Doesn't work in release for some reason + //RakAssert(row->cells[SystemAddressColumnIndex]->isEmpty==false); + //if (memcmp(row->cells[SystemAddressColumnIndex]->c, &systemAddress, SystemAddress::size())==0) + // return true; + // return false; +} +DataStructures::Table::Row * LightweightDatabaseServer::AddRow(LightweightDatabaseServer::DatabaseTable *databaseTable, SystemAddress systemAddress, RakNetGUID guid, bool hasRowId, unsigned rowId) + { + // If this is an old row ID (master server crashed) update the ID counter to prevent collisions + if (rowId >= databaseTable->nextRowId) databaseTable->nextRowId = rowId+1; + + DataStructures::Table::Row *row = GetRowFromIP(databaseTable, systemAddress, 0); + if (databaseTable->oneRowPerSystemAddress && row) + { + Log::error_log("This system already has a row\n"); + //return 0; // This system already has a row. + return row; + } + + if (databaseTable->autogenerateRowIDs==false) + { + // For a new row: + // rowID required but not specified OR + // rowId specified but already in the table + // Then exit + if (hasRowId==false || databaseTable->table.GetRowByID(rowId)) + { + Log::error_log("Error: Row ID error while creating row\n"); + return 0; + } + } + else + rowId=databaseTable->nextRowId++; + + // Add new row + row = databaseTable->table.AddRow(rowId); + + // Set IP and last update time + if ( databaseTable->oneRowPerSystemAddress || databaseTable->onlyUpdateOwnRows || databaseTable->removeRowOnPingFailure || databaseTable->removeRowOnDisconnect) + { + row->cells[databaseTable->SystemAddressColumnIndex]->Set((char*)&systemAddress, (int) SystemAddress::size()); + row->cells[databaseTable->SystemGuidColumnIndex]->Set((char*)&guid, sizeof(guid)); + } + if (databaseTable->removeRowOnPingFailure) + { + RakNetTime time = RakNet::GetTime(); + row->cells[databaseTable->lastPingResponseColumnIndex]->Set((int) time); + row->cells[databaseTable->nextPingSendColumnIndex]->Set((int) time+SEND_PING_INTERVAL); + } + + return row; + } +void LightweightDatabaseServer::RemoveRowsFromIP(SystemAddress systemAddress) +{ + // Remove rows for tables that do so on a system disconnect + Log::info_log("Removing rows for IP %s\n",systemAddress.ToString()); + DatabaseTable *databaseTable; + DataStructures::List removeList; + DataStructures::Table::Row* row; + DataStructures::Page *cur; + unsigned i,j; + for (i=0; i < database.Size(); i++) + { + databaseTable=database[i]; + if ((unsigned int) databaseTable->SystemAddressColumnIndex!=(unsigned int)-1) + { + const DataStructures::BPlusTree &rows = databaseTable->table.GetRows(); + cur = rows.GetListHead(); + while (cur) + { + // Mark dropped entities + for (j=0; j < (unsigned)cur->size; j++) + { + if (RowHasIP(cur->data[j], systemAddress, databaseTable->SystemAddressColumnIndex)) + { + if (databaseTable->removeRowOnDisconnect) + { + Log::debug_log("Found row ID %d\n", cur->keys[j]); + removeList.Insert(cur->keys[j], __FILE__, __LINE__); + } + else if (databaseTable->removeRowOnPingFailure) + { + row = cur->data[j]; + row->cells[databaseTable->nextPingSendColumnIndex]->i=(double)(RakNet::GetTime()+SEND_PING_INTERVAL+(randomMT()%1000)); + } + } + } + cur=cur->next; + } + } + + for (j=0; j < removeList.Size(); j++) + { + addressMap.erase(systemAddress); + databaseTable->table.RemoveRow(removeList[j]); + Log::info_log("Force removing row %u\n",removeList[j]); + Log::debug_log("Current address map count %d\n", addressMap.size()); + } + removeList.Clear(true, __FILE__,__LINE__); + + if (databaseTable->table.GetRowCount() == 0) + { + Log::info_log("Deleting unused table for %s\n", databaseTable->tableName); + RemoveTable(databaseTable->tableName); + } + } +} +#ifdef _MSC_VER +#pragma warning( pop ) +#endif + +#endif // _RAKNET_SUPPORT_* diff --git a/RakNet/Sources/LightweightDatabaseServer.h b/RakNet/Sources/LightweightDatabaseServer.h new file mode 100644 index 0000000..be847f1 --- /dev/null +++ b/RakNet/Sources/LightweightDatabaseServer.h @@ -0,0 +1,161 @@ +/// \file +/// \brief A simple flat database included with RakNet, useful for a server browser or a lobby server. +/// +/// This file is part of RakNet Copyright 2003 Jenkins Software LLC +/// +/// Usage of RakNet is subject to the appropriate license agreement. + +#include "NativeFeatureIncludes.h" +#if _RAKNET_SUPPORT_LightweightDatabaseServer==1 + +#ifndef __LIGHTWEIGHT_DATABASE_SERVER_H +#define __LIGHTWEIGHT_DATABASE_SERVER_H + +#include "Export.h" +#include "PluginInterface2.h" +#include "LightweightDatabaseCommon.h" +#include "DS_Map.h" +#include +#include +#include + + +class RakPeerInterface; +struct Packet; + +/// \deprecated Use RakNet::SQLite3ServerPlugin found at DependentExtensions/SQLite3Plugin/SQLite3ServerPlugin.h +/// \brief A simple flat database included with RakNet, useful for a server browser or a lobby server. +/// \details A flat database interface. Adds the ability to track IPs of row updaters and passwords for table read and write operations, +/// Best used for data in which queries which do not need to be updated in real-time +/// \ingroup SIMPLE_DATABASE_GROUP +class RAK_DLL_EXPORT LightweightDatabaseServer : public PluginInterface2 + { + public: + typedef std::map IPMap; + typedef std::list IPList; + struct InternalID { int port; IPList ips; }; + typedef std::map AddressMap; + // Constructor + LightweightDatabaseServer(); + + // Destructor + virtual ~LightweightDatabaseServer(); + + /// Returns a table by name. + /// It is valid to read and write to the returned pointer. + /// \param[in] tableName The name of the table that was passed to AddTable + /// \return The requested table, or 0 if \a tableName cannot be found. + DataStructures::Table *GetTable(const char *tableName); + + /// Adds a new table + /// It is valid to read and write to the returned pointer. + /// \param[in] tableName Name of the new table to create. Remote systems will refer to this table by this name. + /// \param[in] allowRemoteQuery true to allow remote systems to query the table. false to not allow this. + /// \param[in] allowRemoteUpdate true to allow remote systems to update rows in the table. false to not allow this. + /// \param[in] allowRemoteRemove true to allow remote systems to remove rows from the table. false to not allow this. + /// \param[in] queryPassword The password required to query the table. Pass an empty string for no password. + /// \param[in] updatePassword The password required to update rows in the table. Pass an empty string for no password. + /// \param[in] removePassword The password required to remove rows from the table. Pass an empty string for no password. + /// \param[in] oneRowPerSystemAddress Only used if allowRemoteUpdate==true. This limits remote systems to one row. + /// \param[in] onlyUpdateOwnRows Only used if allowRemoteUpdate==true. This limits remote systems to only updating rows they created. + /// \param[in] removeRowOnPingFailure Only used if allowRemoteUpdate==true and removeRowOnDisconnect==false. If true, this will periodically ping disconnected systems and remove rows created by that system if that system does not respond to pings. + /// \param[in] removeRowOnDisconnect Only used if allowRemoteUpdate==true. This removes rows created by a system when that system disconnects. + /// \param[in] autogenerateRowIDs true to automatically generate row IDs. Rows are stored in order by row ID. If false, the clients must specify a unique row ID when adding rows. If they specify a row that already exists the addition is ignored. + /// \return The newly created table, or 0 on failure. + DataStructures::Table* AddTable(const char *tableName, + bool allowRemoteQuery, + bool allowRemoteUpdate, + bool allowRemoteRemove, + const char *queryPassword, + const char *updatePassword, + const char *removePassword, + bool oneRowPerSystemAddress, + bool onlyUpdateOwnRows, + bool removeRowOnPingFailure, + bool removeRowOnDisconnect, + bool autogenerateRowIDs); + + /// Removes a table by name. + /// \param[in] tableName The name of the table that was passed to AddTable + /// \return true on success, false on failure. + bool RemoveTable(const char *tableName); + + /// Clears all memory. + void Clear(void); + + // Gets the next valid auto-generated rowId for a table and increments it. + unsigned GetAndIncrementRowID(const char *tableName); + + /// Returns a linked list of ordered lists containing the rows of a table, by name. + /// The returned structure is internal to the BPlus tree. See DS_BPlusTree + /// This is a convenience accessor, as you can also get this from the table returned from GetTable() + /// \param[in] tableName The name of the table that was passed to AddTable + /// \return The requested rows, or 0 if \a tableName cannot be found. + DataStructures::Page *GetTableRows(const char *tableName); + + /// \internal For plugin handling + virtual void Update(void); + /// \internal For plugin handling + virtual PluginReceiveResult OnReceive(Packet *packet); + /// \internal For plugin handling + virtual void OnClosedConnection(SystemAddress systemAddress, RakNetGUID rakNetGUID, PI2_LostConnectionReason lostConnectionReason ); + + void SetStatDelay(int value) { statDelay = value; } + int GetStatDelay() { return statDelay; } + + struct DatabaseTable + { + bool allowRemoteUpdate; + bool allowRemoteQuery; + bool allowRemoteRemove; + + char updatePassword[_SIMPLE_DATABASE_PASSWORD_LENGTH]; + char queryPassword[_SIMPLE_DATABASE_PASSWORD_LENGTH]; + char removePassword[_SIMPLE_DATABASE_PASSWORD_LENGTH]; + bool oneRowPerSystemAddress; + bool onlyUpdateOwnRows; + bool removeRowOnPingFailure; + bool removeRowOnDisconnect; + bool autogenerateRowIDs; + char tableName[_SIMPLE_DATABASE_TABLE_NAME_LENGTH]; + + // Used if autogenerateRowIDs==true + unsigned nextRowId; + + unsigned SystemAddressColumnIndex; + unsigned SystemGuidColumnIndex; + unsigned lastPingResponseColumnIndex; + unsigned nextPingSendColumnIndex; + RakNetTime nextRowPingCheck; + DataStructures::Table table; + }; + + static int DatabaseTableComp( const char* const &key1, const char* const &key2 ); + LightweightDatabaseServer::DatabaseTable* CreateDefaultTable(std::string tableName); + + protected: + DataStructures::Map database; + void OnQueryRequest(Packet *packet); + void OnUpdateRow(Packet *packet); + void OnRemoveRow(Packet *packet); + void OnPong(Packet *packet); + // mode 0 = query, mode 1 = update, mode 2 = remove + DatabaseTable * DeserializeClientHeader(RakNet::BitStream *inBitstream, Packet *packet, int mode, bool query = false); + DataStructures::Table::Row * GetRowFromIP(DatabaseTable *databaseTable, SystemAddress systemAddress, unsigned *rowId); + bool RowHasIP(DataStructures::Table::Row *row, SystemAddress systemAddress, unsigned SystemAddressColumnIndex); + DataStructures::Table::Row * AddRow(LightweightDatabaseServer::DatabaseTable *databaseTable, SystemAddress systemAddress, RakNetGUID guid, bool hasRowId, unsigned rowId); + void RemoveRowsFromIP(SystemAddress systemAddress); + bool CheckVersion(Packet *packet, RakNet::BitStream &inBitStream); + IPMap ipMap; + AddressMap addressMap; + char version[3]; + int lastMinQueryCount; + int lastMinUpdateCount; + time_t countTimer; + time_t statTimer; + int statDelay; // Delay between displaying statistics + }; + +#endif + +#endif // _RAKNET_SUPPORT_* diff --git a/RakNet/Sources/LinuxStrings.cpp b/RakNet/Sources/LinuxStrings.cpp new file mode 100644 index 0000000..c72832b --- /dev/null +++ b/RakNet/Sources/LinuxStrings.cpp @@ -0,0 +1,26 @@ +#if (defined(__GNUC__) || defined(__GCCXML__)) && !defined(__WIN32) +#include +#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 diff --git a/RakNet/Sources/LinuxStrings.h b/RakNet/Sources/LinuxStrings.h new file mode 100644 index 0000000..69d2f50 --- /dev/null +++ b/RakNet/Sources/LinuxStrings.h @@ -0,0 +1,21 @@ +#ifndef _GCC_WIN_STRINGS +#define _GCC_WIN_STRINGS + +#if defined(_PS3) || defined(__PS3__) || defined(SN_TARGET_PS3) + +#else + #if (defined(__GNUC__) || defined(__GCCXML__)) && !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 + +#endif // _GCC_WIN_STRINGS diff --git a/RakNet/Sources/LogCommandParser.cpp b/RakNet/Sources/LogCommandParser.cpp new file mode 100644 index 0000000..bc7424f --- /dev/null +++ b/RakNet/Sources/LogCommandParser.cpp @@ -0,0 +1,275 @@ +#include "NativeFeatureIncludes.h" +#if _RAKNET_SUPPORT_LogCommandParser==1 + +#include "LogCommandParser.h" +#include "TransportInterface.h" +#if !defined(_PS3) && !defined(__PS3__) && !defined(SN_TARGET_PS3) +#include +#endif +#include +#include +#include + +#include "LinuxStrings.h" + +#ifdef _MSC_VER +#pragma warning( push ) +#endif + +LogCommandParser::LogCommandParser() +{ + RegisterCommand(CommandParserInterface::VARIABLE_NUMBER_OF_PARAMETERS,"Subscribe","[] - Subscribes to a named channel, or all channels"); + RegisterCommand(CommandParserInterface::VARIABLE_NUMBER_OF_PARAMETERS,"Unsubscribe","[] - 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, 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, 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; + 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(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(SystemAddress systemAddress, TransportInterface *transport) +{ + (void) systemAddress; + (void) transport; +} +void LogCommandParser::OnConnectionLost(SystemAddress systemAddress, TransportInterface *transport) +{ + (void) transport; + Unsubscribe(systemAddress, 0); +} +unsigned LogCommandParser::Unsubscribe(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< 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; +}; + +#endif + +#endif // _RAKNET_SUPPORT_* diff --git a/RakNet/Sources/MTUSize.h b/RakNet/Sources/MTUSize.h new file mode 100644 index 0000000..40eb2b2 --- /dev/null +++ b/RakNet/Sources/MTUSize.h @@ -0,0 +1,38 @@ +/// \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 DEFAULT_MTU_SIZE + +/// The MTU size to use if RakPeer::SetMTUSize() is not called. +/// \remarks I think many people forget to call RakPeer::SetMTUSize() so I'm setting this to 1500 by default for efficiency. +/// \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. +#if defined(_XBOX) || defined(X360) + +#else +#define DEFAULT_MTU_SIZE 1492 +#endif + +/// The largest value for an UDP datagram +/// \sa RakPeer::SetMTUSize() +#if defined(_XBOX) || defined(X360) + +#else +#define MAXIMUM_MTU_SIZE 1492 +#endif + +#endif diff --git a/RakNet/Sources/MessageFilter.cpp b/RakNet/Sources/MessageFilter.cpp new file mode 100644 index 0000000..8d3a5e3 --- /dev/null +++ b/RakNet/Sources/MessageFilter.cpp @@ -0,0 +1,371 @@ +#include "NativeFeatureIncludes.h" +#if _RAKNET_SUPPORT_MessageFilter==1 + +#include "MessageFilter.h" +#include "RakAssert.h" +#include "GetTime.h" +#include "MessageIdentifiers.h" +#include "RakPeerInterface.h" +#include "RakAssert.h" + +#ifdef _MSC_VER +#pragma warning( push ) +#endif + +int MessageFilterStrComp( char *const &key,char *const &data ) +{ + return strcmp(key,data); +} + +int FilteredSystemComp( const SystemAddress &key, const FilteredSystem &data ) +{ + if (key < data.systemAddress) + return -1; + else if (key==data.systemAddress) + return 0; + else + return 1; +} + +int FilterSetComp( const int &key, FilterSet * const &data ) +{ + if (key < data->filterSetID) + return -1; + else if (key==data->filterSetID) + return 0; + else + return 1; +} +MessageFilter::MessageFilter() +{ + +} +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::SetAllowRPC(bool allow, const char *functionName, int filterSetID) +{ + (void) allow; + FilterSet *filterSet = GetFilterSetByID(filterSetID); + bool objectExists; + unsigned index = filterSet->allowedRPCs.GetIndexFromKey((char *const) functionName, &objectExists); + if (objectExists==false) + { + char *str = (char*) rakMalloc_Ex( strlen(functionName)+1, __FILE__, __LINE__ ); + strcpy(str, functionName); + filterSet->allowedRPCs.InsertAtIndex(str, index, __FILE__,__LINE__); + } +} +void MessageFilter::SetActionOnDisallowedMessage(bool kickOnDisallowed, bool banOnDisallowed, RakNetTime 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, SystemAddress 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, SystemAddress systemAddress, int filterSetID, void *userData)) +{ + FilterSet *filterSet = GetFilterSetByID(filterSetID); + filterSet->timeoutCallback=invalidMessageCallback; + filterSet->timeoutUserData=userData; +} +void MessageFilter::SetFilterMaxTime(int allowedTimeMS, bool banOnExceed, RakNetTime banTimeMS, int filterSetID) +{ + FilterSet *filterSet = GetFilterSetByID(filterSetID); + filterSet->maxMemberTimeMS=allowedTimeMS; + filterSet->banOnFilterTimeExceed=banOnExceed; + filterSet->timeExceedBanTimeMS=banTimeMS; +} +int MessageFilter::GetSystemFilterSet(SystemAddress systemAddress) +{ + bool objectExists; + unsigned index = systemList.GetIndexFromKey(systemAddress, &objectExists); + if (objectExists==false) + return -1; + else + return systemList[index].filter->filterSetID; +} +void MessageFilter::SetSystemFilterSet(SystemAddress systemAddress, int filterSetID) +{ + // Allocate this filter set if it doesn't exist. + RakAssert(systemAddress!=UNASSIGNED_SYSTEM_ADDRESS); + bool objectExists; + unsigned index = systemList.GetIndexFromKey(systemAddress, &objectExists); + if (objectExists==false) + { + if (filterSetID<0) + return; + + FilteredSystem filteredSystem; + filteredSystem.filter = GetFilterSetByID(filterSetID); + filteredSystem.systemAddress=systemAddress; + filteredSystem.timeEnteredThisSet=RakNet::GetTime(); + systemList.Insert(systemAddress, filteredSystem, true, __FILE__,__LINE__); + } + else + { + if (filterSetID>=0) + { + FilterSet *filterSet = GetFilterSetByID(filterSetID); + systemList[index].timeEnteredThisSet=RakNet::GetTime(); + systemList[index].filter=filterSet; + } + else + { + systemList.RemoveAtIndex(index); + } + } +} +unsigned MessageFilter::GetSystemCount(int filterSetID) const +{ + if (filterSetID==-1) + return systemList.Size(); + else + { + unsigned i; + unsigned count=0; + for (i=0; i < systemList.Size(); i++) + if (systemList[i].filter->filterSetID==filterSetID) + ++count; + return count; + } +} +SystemAddress MessageFilter::GetSystemByIndex(int filterSetID, unsigned index) +{ + if (filterSetID==-1) + return systemList[index].systemAddress; + else + { + unsigned i; + unsigned count=0; + for (i=0; i < systemList.Size(); i++) + { + if (systemList[i].filter->filterSetID==filterSetID) + { + if (index==count) + return systemList[i].systemAddress; + count++; + } + } + } + return UNASSIGNED_SYSTEM_ADDRESS; +} +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); + + // 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(false, __FILE__,__LINE__); + for (i=0; i < filterList.Size(); i++) + DeallocateFilterSet(filterList[i]); + filterList.Clear(false, __FILE__,__LINE__); +} +void MessageFilter::DeallocateFilterSet(FilterSet* filterSet) +{ + unsigned i; + for (i=0; i < filterSet->allowedRPCs.Size(); i++) + rakFree_Ex(filterSet->allowedRPCs[i], __FILE__, __LINE__ ); + RakNet::OP_DELETE(filterSet, __FILE__, __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( __FILE__, __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__,__LINE__); + return newFilterSet; + } +} +void MessageFilter::OnInvalidMessage(FilterSet *filterSet, SystemAddress systemAddress, unsigned char messageID) +{ + if (filterSet->invalidMessageCallback) + filterSet->invalidMessageCallback(rakPeerInterface, systemAddress, filterSet->filterSetID, filterSet->disallowedCallbackUserData, messageID); + if (filterSet->banOnDisallowedMessage) + { + char str1[64]; + systemAddress.ToString(false, str1); + rakPeerInterface->AddToBanList(str1, filterSet->disallowedMessageBanTimeMS); + } + if (filterSet->kickOnDisallowedMessage) + rakPeerInterface->CloseConnection(systemAddress, true, 0); +} +void MessageFilter::Update(void) +{ + // Update all timers for all systems. If those systems' filter sets are expired, take the appropriate action. + RakNetTime time = RakNet::GetTime(); + unsigned index; + index=0; + while (index < systemList.Size()) + { + if (systemList[index].filter && + systemList[index].filter->maxMemberTimeMS>0 && + time-systemList[index].timeEnteredThisSet >= systemList[index].filter->maxMemberTimeMS) + { + if (systemList[index].filter->timeoutCallback) + systemList[index].filter->timeoutCallback(rakPeerInterface, systemList[index].systemAddress, systemList[index].filter->filterSetID, systemList[index].filter->timeoutUserData); + + if (systemList[index].filter->banOnFilterTimeExceed) + { + char str1[64]; + systemList[index].systemAddress.ToString(false, str1); + rakPeerInterface->AddToBanList(str1, systemList[index].filter->timeExceedBanTimeMS); + } + rakPeerInterface->CloseConnection(systemList[index].systemAddress, true, 0); + systemList.RemoveAtIndex(index); + } + else + ++index; + } +} +void MessageFilter::OnNewConnection(SystemAddress systemAddress, RakNetGUID rakNetGUID, bool isIncoming) +{ + (void) systemAddress; + (void) rakNetGUID; + (void) isIncoming; + + // New system, automatically assign to filter set if appropriate + if (autoAddNewConnectionsToFilter>=0 && systemList.HasData(systemAddress)==false) + SetSystemFilterSet(systemAddress, autoAddNewConnectionsToFilter); +} +void MessageFilter::OnClosedConnection(SystemAddress systemAddress, RakNetGUID rakNetGUID, PI2_LostConnectionReason lostConnectionReason ) +{ + (void) rakNetGUID; + (void) lostConnectionReason; + + // Lost system, remove from the list + systemList.RemoveIfExists(systemAddress); +} + PluginReceiveResult MessageFilter::OnReceive(Packet *packet) +{ + bool objectExists; + unsigned 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_RSA_PUBLIC_KEY_MISMATCH: + case ID_CONNECTION_BANNED: + case ID_INVALID_PASSWORD: + case ID_MODIFIED_PACKET: + case ID_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->lengthdata[sizeof(MessageID) + sizeof(RakNetTime)]; + } + 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->systemAddress, &objectExists); + if (objectExists==false) + break; + if (messageId==ID_RPC) + { + const char *uniqueIdentifier = rakPeerInterface->GetRPCString((const char*) packet->data, packet->bitSize, packet->systemAddress); + if (systemList[index].filter->allowedRPCs.HasData((char *const)uniqueIdentifier)==false) + { + OnInvalidMessage(systemList[index].filter, packet->systemAddress, packet->data[0]); + return RR_STOP_PROCESSING_AND_DEALLOCATE; + } + } + else + { + if (systemList[index].filter->allowedIDs[messageId]==false) + { + OnInvalidMessage(systemList[index].filter, packet->systemAddress, packet->data[0]); + return RR_STOP_PROCESSING_AND_DEALLOCATE; + } + } + + break; + } + + return RR_CONTINUE_PROCESSING; +} + +#ifdef _MSC_VER +#pragma warning( pop ) +#endif + +#endif // _RAKNET_SUPPORT_* diff --git a/RakNet/Sources/MessageFilter.h b/RakNet/Sources/MessageFilter.h new file mode 100644 index 0000000..f0af92e --- /dev/null +++ b/RakNet/Sources/MessageFilter.h @@ -0,0 +1,188 @@ +/// \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.h" +#if _RAKNET_SUPPORT_MessageFilter==1 + +#ifndef __MESSAGE_FILTER_PLUGIN_H +#define __MESSAGE_FILTER_PLUGIN_H + +class RakPeerInterface; +#include "RakNetTypes.h" +#include "PluginInterface2.h" +#include "DS_OrderedList.h" +#include "Export.h" + +/// MessageIdentifier (ID_*) values shoudln't go higher than this. Change it if you do. +#define MESSAGE_FILTER_MAX_MESSAGE_ID 256 + +/// \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; + RakNetTime disallowedMessageBanTimeMS; + RakNetTime timeExceedBanTimeMS; + RakNetTime maxMemberTimeMS; + void (*invalidMessageCallback)(RakPeerInterface *peer, SystemAddress systemAddress, int filterSetID, void *userData, unsigned char messageID); + void *disallowedCallbackUserData; + void (*timeoutCallback)(RakPeerInterface *peer, SystemAddress systemAddress, int filterSetID, void *userData); + void *timeoutUserData; + int filterSetID; + bool allowedIDs[MESSAGE_FILTER_MAX_MESSAGE_ID]; + DataStructures::OrderedList allowedRPCs; +}; + +/// \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 +{ + SystemAddress systemAddress; + FilterSet *filter; + RakNetTime timeEnteredThisSet; +}; + +/// \internal Has to be public so some of the shittier compilers can use it. +int RAK_DLL_EXPORT FilteredSystemComp( const SystemAddress &key, const FilteredSystem &data ); + +/// \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.
+/// You implicitly define FilterSets, and add allowed message IDs and RPC calls to these FilterSets.
+/// You then add systems to these filters, such that those systems are limited to sending what the filters allows.
+/// You can automatically assign systems to a filter.
+/// You can automatically kick and possibly ban users that stay in a filter too long, or send the wrong message.
+/// Each system is a member of either zero or one filters.
+/// 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: + 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 an RPC function, by name + /// \param[in] allow True to allow an RPC call with this function name, false to disallow. All RPCs are disabled by default. + /// \param[in] functionName the function name of the RPC call. Must match the function name exactly, including case. + /// \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 SetAllowRPC(bool allow, const char *functionName, 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, RakNetTime 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, SystemAddress systemAddress, 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, SystemAddress systemAddress, 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, RakNetTime banTimeMS, int filterSetID); + + /// Get the filterSetID a system is using. Returns -1 for none. + /// \param[in] systemAddress The system we are referring to + int GetSystemFilterSet(SystemAddress systemAddress); + + /// 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] systemAddress 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(SystemAddress systemAddress, 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 a system subscribed to a particular filter set,by index. + /// index should be between 0 and the GetSystemCount(filterSetID)-1; + /// \param[in] filterSetID The filter set to limit to. Use -1 for none (just indexes all the filtered systems in that case). + /// \param[in] index A number between 0 and GetSystemCount(filterSetID)-1; + SystemAddress GetSystemByIndex(int filterSetID, unsigned index); + + /// 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(SystemAddress systemAddress, RakNetGUID rakNetGUID, bool isIncoming); + virtual void OnClosedConnection(SystemAddress systemAddress, RakNetGUID rakNetGUID, PI2_LostConnectionReason lostConnectionReason ); + +protected: + + void Clear(void); + void DeallocateFilterSet(FilterSet *filterSet); + FilterSet* GetFilterSetByID(int filterSetID); + void OnInvalidMessage(FilterSet *filterSet, SystemAddress systemAddress, unsigned char messageID); + + DataStructures::OrderedList filterList; + DataStructures::OrderedList systemList; + + int autoAddNewConnectionsToFilter; +}; + +#endif + +#endif // _RAKNET_SUPPORT_* diff --git a/RakNet/Sources/MessageIdentifiers.h b/RakNet/Sources/MessageIdentifiers.h new file mode 100644 index 0000000..83817d2 --- /dev/null +++ b/RakNet/Sources/MessageIdentifiers.h @@ -0,0 +1,363 @@ +/// \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.h" +#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_ROUTER_2_REROUTE, +}; + +/// 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) + /// 0 is reserved for UDT's connect message + ID_INTERNAL_PING, + /// Ping from an unconnected system. Reply but do not update timestamps. (internal use only) + ID_PING, + /// Ping from an unconnected system. Only reply if we have open connections. Do not update timestamps. (internal use only) + ID_PING_OPEN_CONNECTIONS, + /// Pong from a connected system. Update timestamps (internal use only) + ID_CONNECTED_PONG, + /// Asking for a new connection (internal use only) + ID_CONNECTION_REQUEST, + /// Connecting to a secured server/peer (internal use only) + ID_SECURED_CONNECTION_RESPONSE, + /// Connecting to a secured server/peer (internal use only) + ID_SECURED_CONNECTION_CONFIRMATION, + /// Packet that tells us the packet contains an integer ID to name mapping for the remote system (internal use only) + ID_RPC_MAPPING, + /// A reliable packet to detect lost connections (internal use only) + ID_DETECT_LOST_CONNECTIONS, + /// Offline message so we know when to reset and start a new connection (internal use only) + ID_OPEN_CONNECTION_REQUEST, + /// Offline message response so we know when to reset and start a new connection (internal use only) + ID_OPEN_CONNECTION_REPLY, //10 + /// Remote procedure call (internal use only) + ID_RPC, + /// Remote procedure call reply, for RPCs that return data (internal use only) + ID_RPC_REPLY, + /// 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, + + + // + // 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, //20 + /// RakPeer - We preset an RSA public key which does not match what the system we connected to is using. + ID_RSA_PUBLIC_KEY_MISMATCH, + /// 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 - A packet has been tampered with in transit. The sender is contained in Packet::systemAddress. + ID_MODIFIED_PACKET, + /// RakPeer - The four bytes following this byte represent an unsigned int 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_PONG, second sizeof(RakNetTime) bytes is the ping, following bytes is system specific enumeration data. + ID_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, + /// ConnectionGraph 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, //30 + /// ConnectionGraph 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, + /// ConnectionGraph plugin - In a client/server environment, a client other than ourselves has connected. Packet::systemAddress is modified to reflect the systemAddress of the client that is not connected directly to us. The packet encoding is SystemAddress 1, ConnectionGraphGroupID 1, SystemAddress 2, ConnectionGraphGroupID 2 + /// ConnectionGraph2 plugin: Bytes 1-4 = count. for (count items) contains {SystemAddress, RakNetGUID} + ID_REMOTE_NEW_INCOMING_CONNECTION, + // 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, + + /// 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 - Destroy an object + ID_REPLICA_MANAGER_DESTRUCTION, //40 + /// 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, + + /// ConnectionGraph plugin - Request the connection graph from another system + ID_CONNECTION_GRAPH_REQUEST, + /// ConnectionGraph plugin - Reply to a connection graph download request + ID_CONNECTION_GRAPH_REPLY, + /// ConnectionGraph plugin - Update edges / nodes for a system with a connection graph + ID_CONNECTION_GRAPH_UPDATE, + /// ConnectionGraph plugin - Add a new connection to a connection graph + ID_CONNECTION_GRAPH_NEW_CONNECTION, + /// ConnectionGraph plugin - Remove a connection from a connection graph - connection was abruptly lost. Two systems addresses encoded in the data packet. + ID_CONNECTION_GRAPH_CONNECTION_LOST, + /// ConnectionGraph plugin - Remove a connection from a connection graph - connection was gracefully lost. Two systems addresses encoded in the data packet. + ID_CONNECTION_GRAPH_DISCONNECTION_NOTIFICATION, //50 + + /// Router plugin - route a message through another system + ID_ROUTE_AND_MULTICAST, + + /// 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, //60 + /// 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_CONNECT_AT_TIME, + /// NATPunchthrough plugin: internal + ID_NAT_GET_MOST_RECENT_PORT, + /// NATPunchthrough plugin: internal + ID_NAT_CLIENT_READY, + + /// 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 the plugin messages. Possibly the plugin is not installed. Bytes starting at offset 1 contains the RakNetGUID destination field of NatPunchthroughClient::OpenNAT(). + ID_NAT_TARGET_UNRESPONSIVE, //70 + /// 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 suceeded. 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, + + /// LightweightDatabase plugin - Query + ID_DATABASE_QUERY_REQUEST, + /// LightweightDatabase plugin - Update + ID_DATABASE_UPDATE_ROW, + /// LightweightDatabase plugin - Remove + ID_DATABASE_REMOVE_ROW, + /// LightweightDatabase plugin - A serialized table. Bytes 1+ contain the table. Pass to TableSerializer::DeserializeTable + ID_DATABASE_QUERY_REPLY, + /// LightweightDatabase plugin - Specified table not found + ID_DATABASE_UNKNOWN_TABLE, + /// LightweightDatabase plugin - Incorrect password + ID_DATABASE_INCORRECT_PASSWORD, //80 + + /// 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, + + /// Auto RPC procedure call + ID_AUTO_RPC_CALL, + + /// Auto RPC functionName to index mapping + ID_AUTO_RPC_REMOTE_INDEX, + + /// Auto RPC functionName to index mapping, lookup failed. Will try to auto recover + ID_AUTO_RPC_UNKNOWN_REMOTE_INDEX, + + /// Auto RPC error code + /// See AutoRPC.h for codes, stored in packet->data[1] + ID_RPC_REMOTE_ERROR, + + /// FileListTransfer transferring large files in chunks that are read only when needed, to save memory + ID_FILE_LIST_REFERENCE_PUSH, //90 + + /// 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 RakNetGuid + 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, //100 + + /// 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, + + /// Serialize construction for an object that already exists on the remote system + ID_REPLICA_MANAGER_3_SERIALIZE_CONSTRUCTION_EXISTING, + ID_REPLICA_MANAGER_3_LOCAL_CONSTRUCTION_REJECTED, + ID_REPLICA_MANAGER_3_LOCAL_CONSTRUCTION_ACCEPTED, + + /// Sent to NatTypeDetectionServer + ID_NAT_TYPE_DETECTION_REQUEST, + + /// Sent to NatTypeDetectionClient. Byte 1 contains the type of NAT detected. + ID_NAT_TYPE_DETECTION_RESULT, + + /// Events happening with SQLiteClientLoggerPlugin + ID_SQLLITE_LOGGER, + + /// Used by the router2 plugin + ID_ROUTER_2_INTERNAL, //110 + /// No path is available or can be established to the remote system + 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. + 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. + ID_TEAM_BALANCER_TEAM_ASSIGNED, + /// Gamebryo Lightspeed + ID_LIGHTSPEED_INTEGRATION, + + /// Plugin based replacement for old RPC system, no boost required, but only works with C functions + ID_RPC_4_PLUGIN, + + /// 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, //120 + + /// 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_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, + + // So I can add more without changing user enumerations + 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 diff --git a/RakNet/Sources/NatPunchthroughClient.cpp b/RakNet/Sources/NatPunchthroughClient.cpp new file mode 100644 index 0000000..b6d7349 --- /dev/null +++ b/RakNet/Sources/NatPunchthroughClient.cpp @@ -0,0 +1,755 @@ +#include "NativeFeatureIncludes.h" +#if _RAKNET_SUPPORT_NatPunchthroughClient==1 + +#include "NatPunchthroughClient.h" +#include "BitStream.h" +#include "MessageIdentifiers.h" +#include "RakPeerInterface.h" +#include "GetTime.h" +#include "PacketLogger.h" + +void NatPunchthroughDebugInterface_Printf::OnClientMessage(const char *msg) +{ + printf("%s\n", msg); +} +void NatPunchthroughDebugInterface_PacketLogger::OnClientMessage(const char *msg) +{ + if (pl) + { + pl->WriteMiscellaneous("Nat", msg); + } +} + +NatPunchthroughClient::NatPunchthroughClient() +{ + natPunchthroughDebugInterface=0; + mostRecentNewExternalPort=0; + sp.nextActionTime=0; +} +NatPunchthroughClient::~NatPunchthroughClient() +{ + rakPeerInterface=0; + Clear(); +} +bool NatPunchthroughClient::OpenNAT(RakNetGUID destination, SystemAddress facilitator) +{ + if (rakPeerInterface->IsConnected(facilitator)==false) + return false; + // Already connected + SystemAddress sa = rakPeerInterface->GetSystemAddressFromGuid(destination); + if (sa!=UNASSIGNED_SYSTEM_ADDRESS && rakPeerInterface->IsConnected(sa,true,true) ) + return false; + + SendPunchthrough(destination, facilitator); + return true; +} +void NatPunchthroughClient::SetDebugInterface(NatPunchthroughDebugInterface *i) +{ + natPunchthroughDebugInterface=i; +} +unsigned short NatPunchthroughClient::GetUPNPExternalPort(void) const +{ + return mostRecentNewExternalPort; +} +unsigned short NatPunchthroughClient::GetUPNPInternalPort(void) const +{ + return rakPeerInterface->GetInternalID(UNASSIGNED_SYSTEM_ADDRESS).port; +} +RakNet::RakString NatPunchthroughClient::GetUPNPInternalAddress(void) const +{ + char dest[64]; + rakPeerInterface->GetInternalID(UNASSIGNED_SYSTEM_ADDRESS).ToString(false, dest); + RakNet::RakString rs = dest; + return rs; +} +void NatPunchthroughClient::Update(void) +{ + RakNetTimeMS time = RakNet::GetTimeMS(); + if (sp.nextActionTime && sp.nextActionTime < time) + { + RakNetTimeMS delta = time - sp.nextActionTime; + if (sp.testMode==SendPing::TESTING_INTERNAL_IPS) + { + SendOutOfBand(sp.internalIds[sp.attemptCount],ID_NAT_ESTABLISH_UNIDIRECTIONAL); + + if (++sp.retryCount>=pc.UDP_SENDS_PER_PORT_INTERNAL) + { + ++sp.attemptCount; + sp.retryCount=0; + } + + if (sp.attemptCount>=pc.MAXIMUM_NUMBER_OF_INTERNAL_IDS_TO_CHECK) + { + sp.testMode=SendPing::WAITING_FOR_INTERNAL_IPS_RESPONSE; + if (pc.INTERNAL_IP_WAIT_AFTER_ATTEMPTS>0) + { + sp.nextActionTime=time+pc.INTERNAL_IP_WAIT_AFTER_ATTEMPTS-delta; + } + else + { + sp.testMode=SendPing::TESTING_EXTERNAL_IPS_FROM_FACILITATOR_PORT; + sp.attemptCount=0; + } + } + else + { + sp.nextActionTime=time+pc.TIME_BETWEEN_PUNCH_ATTEMPTS_INTERNAL-delta; + } + } + else if (sp.testMode==SendPing::WAITING_FOR_INTERNAL_IPS_RESPONSE) + { + sp.testMode=SendPing::TESTING_EXTERNAL_IPS_FROM_FACILITATOR_PORT; + sp.attemptCount=0; + } + + if (sp.testMode==SendPing::TESTING_EXTERNAL_IPS_FROM_FACILITATOR_PORT) + { + SystemAddress sa; + sa=sp.targetAddress; + int port = sa.port+sp.attemptCount; + sa.port=(unsigned short) port; + SendOutOfBand(sa,ID_NAT_ESTABLISH_UNIDIRECTIONAL); + + if (++sp.retryCount>=pc.UDP_SENDS_PER_PORT_EXTERNAL) + { + ++sp.attemptCount; + sp.retryCount=0; + sp.nextActionTime=time+pc.EXTERNAL_IP_WAIT_BETWEEN_PORTS-delta; + } + else + { + sp.nextActionTime=time+pc.TIME_BETWEEN_PUNCH_ATTEMPTS_EXTERNAL-delta; + } + + if (sp.attemptCount>=pc.MAX_PREDICTIVE_PORT_RANGE) + { + // From 1024 disabled, never helps as I've seen, but slows down the process by half + //sp.testMode=SendPing::TESTING_EXTERNAL_IPS_FROM_1024; + //sp.attemptCount=0; + sp.testMode=SendPing::WAITING_AFTER_ALL_ATTEMPTS; + sp.nextActionTime=time+pc.EXTERNAL_IP_WAIT_AFTER_ALL_ATTEMPTS-delta; + } + } + else if (sp.testMode==SendPing::TESTING_EXTERNAL_IPS_FROM_1024) + { + SystemAddress sa; + sa=sp.targetAddress; + int port = 1024+sp.attemptCount; + sa.port=(unsigned short) port; + SendOutOfBand(sa,ID_NAT_ESTABLISH_UNIDIRECTIONAL); + + if (++sp.retryCount>=pc.UDP_SENDS_PER_PORT_EXTERNAL) + { + ++sp.attemptCount; + sp.retryCount=0; + sp.nextActionTime=time+pc.EXTERNAL_IP_WAIT_BETWEEN_PORTS-delta; + } + else + { + sp.nextActionTime=time+pc.TIME_BETWEEN_PUNCH_ATTEMPTS_EXTERNAL-delta; + } + + if (sp.attemptCount>=pc.MAX_PREDICTIVE_PORT_RANGE) + { + if (natPunchthroughDebugInterface) + { + char ipAddressString[32]; + sp.targetAddress.ToString(true, ipAddressString); + char guidString[128]; + sp.targetGuid.ToString(guidString); + natPunchthroughDebugInterface->OnClientMessage(RakNet::RakString("Likely bidirectional punchthrough failure to guid %s, system address %s.", guidString, ipAddressString)); + } + + sp.testMode=SendPing::WAITING_AFTER_ALL_ATTEMPTS; + sp.nextActionTime=time+pc.EXTERNAL_IP_WAIT_AFTER_ALL_ATTEMPTS-delta; + } + } + else if (sp.testMode==SendPing::WAITING_AFTER_ALL_ATTEMPTS) + { + // Failed. Tell the user + OnPunchthroughFailure(); + } + + if (sp.testMode==SendPing::PUNCHING_FIXED_PORT) + { +// RakAssert(rakPeerInterface->IsConnected(sp.targetAddress,true,true)==false); + SendOutOfBand(sp.targetAddress,ID_NAT_ESTABLISH_BIDIRECTIONAL); + if (++sp.retryCount>=sp.punchingFixedPortAttempts) + { + if (natPunchthroughDebugInterface) + { + char ipAddressString[32]; + sp.targetAddress.ToString(true, ipAddressString); + char guidString[128]; + sp.targetGuid.ToString(guidString); + natPunchthroughDebugInterface->OnClientMessage(RakNet::RakString("Likely unidirectional punchthrough failure to guid %s, system address %s.", guidString, ipAddressString)); + } + + sp.testMode=SendPing::WAITING_AFTER_ALL_ATTEMPTS; + sp.nextActionTime=time+pc.EXTERNAL_IP_WAIT_AFTER_ALL_ATTEMPTS-delta; + } + else + { + if ((sp.retryCount%pc.UDP_SENDS_PER_PORT_EXTERNAL)==0) + sp.nextActionTime=time+pc.EXTERNAL_IP_WAIT_BETWEEN_PORTS-delta; + else + sp.nextActionTime=time+pc.TIME_BETWEEN_PUNCH_ATTEMPTS_EXTERNAL-delta; + } + } + } +} +void NatPunchthroughClient::PushFailure(void) +{ + Packet *p = rakPeerInterface->AllocatePacket(sizeof(MessageID)+sizeof(unsigned char)); + p->data[0]=ID_NAT_PUNCHTHROUGH_FAILED; + p->systemAddress=sp.targetAddress; + p->systemAddress.systemIndex=(SystemIndex)-1; + p->guid=sp.targetGuid; + if (sp.weAreSender) + p->data[1]=1; + else + p->data[1]=0; + rakPeerInterface->PushBackPacket(p, true); +} +void NatPunchthroughClient::OnPunchthroughFailure(void) +{ + if (pc.retryOnFailure==false) + { + if (natPunchthroughDebugInterface) + { + char ipAddressString[32]; + sp.targetAddress.ToString(true, ipAddressString); + char guidString[128]; + sp.targetGuid.ToString(guidString); + natPunchthroughDebugInterface->OnClientMessage(RakNet::RakString("Failed punchthrough once. Returning failure to guid %s, system address %s to user.", guidString, ipAddressString)); + } + + PushFailure(); + OnReadyForNextPunchthrough(); + return; + } + + unsigned int i; + for (i=0; i < failedAttemptList.Size(); i++) + { + if (failedAttemptList[i].guid==sp.targetGuid) + { + if (natPunchthroughDebugInterface) + { + char ipAddressString[32]; + sp.targetAddress.ToString(true, ipAddressString); + char guidString[128]; + sp.targetGuid.ToString(guidString); + natPunchthroughDebugInterface->OnClientMessage(RakNet::RakString("Failed punchthrough twice. Returning failure to guid %s, system address %s to user.", guidString, ipAddressString)); + } + + // Failed a second time, so return failed to user + PushFailure(); + + OnReadyForNextPunchthrough(); + + failedAttemptList.RemoveAtIndexFast(i); + return; + } + } + + if (rakPeerInterface->IsConnected(sp.facilitator)==false) + { + if (natPunchthroughDebugInterface) + { + char ipAddressString[32]; + sp.targetAddress.ToString(true, ipAddressString); + char guidString[128]; + sp.targetGuid.ToString(guidString); + natPunchthroughDebugInterface->OnClientMessage(RakNet::RakString("Not connected to facilitator, so cannot retry punchthrough after first failure. Returning failure onj guid %s, system address %s to user.", guidString, ipAddressString)); + } + + // Failed, and can't try again because no facilitator + PushFailure(); + return; + } + + if (natPunchthroughDebugInterface) + { + char ipAddressString[32]; + sp.targetAddress.ToString(true, ipAddressString); + char guidString[128]; + sp.targetGuid.ToString(guidString); + natPunchthroughDebugInterface->OnClientMessage(RakNet::RakString("First punchthrough failure on guid %s, system address %s. Reattempting.", guidString, ipAddressString)); + } + + // Failed the first time. Add to the failure queue and try again + AddrAndGuid aag; + aag.addr=sp.targetAddress; + aag.guid=sp.targetGuid; + failedAttemptList.Push(aag, __FILE__, __LINE__); + + // Tell the server we are ready + OnReadyForNextPunchthrough(); + + // If we are the sender, try again, immediately if possible, else added to the queue on the faciltiator + if (sp.weAreSender) + SendPunchthrough(sp.targetGuid, sp.facilitator); +} +PluginReceiveResult NatPunchthroughClient::OnReceive(Packet *packet) +{ + switch (packet->data[0]) + { + case ID_NAT_GET_MOST_RECENT_PORT: + { + OnGetMostRecentPort(packet); + return RR_STOP_PROCESSING_AND_DEALLOCATE; + } + case ID_OUT_OF_BAND_INTERNAL: + if (packet->length>=2 && + (packet->data[1]==ID_NAT_ESTABLISH_UNIDIRECTIONAL || packet->data[1]==ID_NAT_ESTABLISH_BIDIRECTIONAL) && + sp.nextActionTime!=0) + { + RakNet::BitStream bs(packet->data,packet->length,false); + bs.IgnoreBytes(2); + uint16_t sessionId; + bs.Read(sessionId); +// RakAssert(sessionId<100); + if (sessionId!=sp.sessionId) + break; + + char ipAddressString[32]; + packet->systemAddress.ToString(true,ipAddressString); + if (packet->data[1]==ID_NAT_ESTABLISH_UNIDIRECTIONAL && sp.targetGuid==packet->guid) + { + if (natPunchthroughDebugInterface) + { + char guidString[128]; + sp.targetGuid.ToString(guidString); + natPunchthroughDebugInterface->OnClientMessage(RakNet::RakString("Received ID_NAT_ESTABLISH_UNIDIRECTIONAL from guid %s, system address %s.", guidString, ipAddressString)); + } + if (sp.testMode!=SendPing::PUNCHING_FIXED_PORT) + { + sp.testMode=SendPing::PUNCHING_FIXED_PORT; + sp.retryCount+=sp.attemptCount*pc.UDP_SENDS_PER_PORT_EXTERNAL; + sp.targetAddress=packet->systemAddress; +// RakAssert(rakPeerInterface->IsConnected(sp.targetAddress,true,true)==false); + // Keeps trying until the other side gives up too, in case it is unidirectional + sp.punchingFixedPortAttempts=pc.UDP_SENDS_PER_PORT_EXTERNAL*pc.MAX_PREDICTIVE_PORT_RANGE; + } + + SendOutOfBand(sp.targetAddress,ID_NAT_ESTABLISH_BIDIRECTIONAL); + } + else if (packet->data[1]==ID_NAT_ESTABLISH_BIDIRECTIONAL && + sp.targetGuid==packet->guid) + { + // They send back our port + bs.Read(mostRecentNewExternalPort); + + SendOutOfBand(packet->systemAddress,ID_NAT_ESTABLISH_BIDIRECTIONAL); + + // Tell the user about the success + sp.targetAddress=packet->systemAddress; + PushSuccess(); + OnReadyForNextPunchthrough(); +// RakAssert(rakPeerInterface->IsConnected(sp.targetAddress,true,true)==false); + bool removedFromFailureQueue=RemoveFromFailureQueue(); + + if (natPunchthroughDebugInterface) + { + char guidString[128]; + sp.targetGuid.ToString(guidString); + if (removedFromFailureQueue) + natPunchthroughDebugInterface->OnClientMessage(RakNet::RakString("Punchthrough to guid %s, system address %s succeeded on 2nd attempt.", guidString, ipAddressString)); + else + natPunchthroughDebugInterface->OnClientMessage(RakNet::RakString("Punchthrough to guid %s, system address %s succeeded on 1st attempt.", guidString, ipAddressString)); + } + } + + // mostRecentNewExternalPort=packet->systemAddress.port; + } + return RR_STOP_PROCESSING_AND_DEALLOCATE; + case ID_NAT_ALREADY_IN_PROGRESS: + { + RakNet::BitStream incomingBs(packet->data, packet->length, false); + incomingBs.IgnoreBytes(sizeof(MessageID)); + RakNetGUID targetGuid; + incomingBs.Read(targetGuid); + if (natPunchthroughDebugInterface) + { + char guidString[128]; + targetGuid.ToString(guidString); + natPunchthroughDebugInterface->OnClientMessage(RakNet::RakString("Punchthrough retry to guid %s failed due to ID_NAT_ALREADY_IN_PROGRESS. Returning failure.", guidString)); + } + + } + break; + case ID_NAT_TARGET_NOT_CONNECTED: + case ID_NAT_CONNECTION_TO_TARGET_LOST: + case ID_NAT_TARGET_UNRESPONSIVE: + { + char *reason; + if (packet->data[0]==ID_NAT_TARGET_NOT_CONNECTED) + reason="ID_NAT_TARGET_NOT_CONNECTED"; + else if (packet->data[0]==ID_NAT_CONNECTION_TO_TARGET_LOST) + reason="ID_NAT_CONNECTION_TO_TARGET_LOST"; + else + reason="ID_NAT_TARGET_UNRESPONSIVE"; + + RakNet::BitStream incomingBs(packet->data, packet->length, false); + incomingBs.IgnoreBytes(sizeof(MessageID)); + + RakNetGUID targetGuid; + incomingBs.Read(targetGuid); + if (packet->data[0]==ID_NAT_CONNECTION_TO_TARGET_LOST || + packet->data[0]==ID_NAT_TARGET_UNRESPONSIVE) + { + uint16_t sessionId; + incomingBs.Read(sessionId); + if (sessionId!=sp.sessionId) + break; + } + + unsigned int i; + for (i=0; i < failedAttemptList.Size(); i++) + { + if (failedAttemptList[i].guid==targetGuid) + { + if (natPunchthroughDebugInterface) + { + char guidString[128]; + targetGuid.ToString(guidString); + natPunchthroughDebugInterface->OnClientMessage(RakNet::RakString("Punchthrough retry to guid %s failed due to %s.", guidString, reason)); + + } + + // If the retry target is not connected, or loses connection, or is not responsive, then previous failures cannot be retried. + + // Don't need to return failed, the other messages indicate failure anyway + /* + Packet *p = rakPeerInterface->AllocatePacket(sizeof(MessageID)); + p->data[0]=ID_NAT_PUNCHTHROUGH_FAILED; + p->systemAddress=failedAttemptList[i].addr; + p->systemAddress.systemIndex=(SystemIndex)-1; + p->guid=failedAttemptList[i].guid; + rakPeerInterface->PushBackPacket(p, false); + */ + + failedAttemptList.RemoveAtIndexFast(i); + break; + } + } + + if (natPunchthroughDebugInterface) + { + char guidString[128]; + targetGuid.ToString(guidString); + natPunchthroughDebugInterface->OnClientMessage(RakNet::RakString("Punchthrough attempt to guid %s failed due to %s.", guidString, reason)); + } + + // Stop trying punchthrough + sp.nextActionTime=0; + + /* + RakNet::BitStream bs(packet->data, packet->length, false); + bs.IgnoreBytes(sizeof(MessageID)); + RakNetGUID failedSystem; + bs.Read(failedSystem); + bool deletedFirst=false; + unsigned int i=0; + while (i < pendingOpenNAT.Size()) + { + if (pendingOpenNAT[i].destination==failedSystem) + { + if (i==0) + deletedFirst=true; + pendingOpenNAT.RemoveAtIndex(i); + } + else + i++; + } + // Failed while in progress. Go to next in attempt queue + if (deletedFirst && pendingOpenNAT.Size()) + { + SendPunchthrough(pendingOpenNAT[0].destination, pendingOpenNAT[0].facilitator); + sp.nextActionTime=0; + } + */ + } + break; + case ID_TIMESTAMP: + if (packet->data[sizeof(MessageID)+sizeof(RakNetTime)]==ID_NAT_CONNECT_AT_TIME) + { + OnConnectAtTime(packet); + return RR_STOP_PROCESSING_AND_DEALLOCATE; + } + break; + } + return RR_CONTINUE_PROCESSING; +} +/* +void NatPunchthroughClient::ProcessNextPunchthroughQueue(void) +{ + // Go to the next attempt + if (pendingOpenNAT.Size()) + pendingOpenNAT.RemoveAtIndex(0); + + // Do next punchthrough attempt + if (pendingOpenNAT.Size()) + SendPunchthrough(pendingOpenNAT[0].destination, pendingOpenNAT[0].facilitator); + + sp.nextActionTime=0; +} +*/ +void NatPunchthroughClient::OnConnectAtTime(Packet *packet) +{ +// RakAssert(sp.nextActionTime==0); + + RakNet::BitStream bs(packet->data, packet->length, false); + bs.IgnoreBytes(sizeof(MessageID)); + bs.Read(sp.nextActionTime); + bs.IgnoreBytes(sizeof(MessageID)); + bs.Read(sp.sessionId); + bs.Read(sp.targetAddress); + //RakAssert(rakPeerInterface->IsConnected(sp.targetAddress,true,true)==false); + int j; + for (j=0; j < MAXIMUM_NUMBER_OF_INTERNAL_IDS; j++) + bs.Read(sp.internalIds[j]); + sp.attemptCount=0; + sp.retryCount=0; + if (pc.MAXIMUM_NUMBER_OF_INTERNAL_IDS_TO_CHECK>0) + { + sp.testMode=SendPing::TESTING_INTERNAL_IPS; + } + else + { + sp.testMode=SendPing::TESTING_EXTERNAL_IPS_FROM_FACILITATOR_PORT; + } + bs.Read(sp.targetGuid); + bs.Read(sp.weAreSender); + + //RakAssert(rakPeerInterface->IsConnected(sp.targetAddress,true,true)==false); +} +void NatPunchthroughClient::SendTTL(SystemAddress sa) +{ + if (sa==UNASSIGNED_SYSTEM_ADDRESS) + return; + if (sa.port==0) + return; + + char ipAddressString[32]; + sa.ToString(false, ipAddressString); + rakPeerInterface->SendTTL(ipAddressString,sa.port, 3); +} +void NatPunchthroughClient::SendOutOfBand(SystemAddress sa, MessageID oobId) +{ + if (sa==UNASSIGNED_SYSTEM_ADDRESS) + return; + if (sa.port==0) + return; + + //RakAssert(rakPeerInterface->IsConnected(sp.targetAddress,true,true)==false); + + RakNet::BitStream oob; + oob.Write(oobId); + oob.Write(sp.sessionId); +// RakAssert(sp.sessionId<100); + if (oobId==ID_NAT_ESTABLISH_BIDIRECTIONAL) + oob.Write(sa.port); + char ipAddressString[32]; + sa.ToString(false, ipAddressString); + rakPeerInterface->SendOutOfBand((const char*) ipAddressString,sa.port,(MessageID) ID_OUT_OF_BAND_INTERNAL,(const char*) oob.GetData(),oob.GetNumberOfBytesUsed()); + + if (natPunchthroughDebugInterface) + { + sa.ToString(true,ipAddressString); + char guidString[128]; + sp.targetGuid.ToString(guidString); + + if (oobId==ID_NAT_ESTABLISH_UNIDIRECTIONAL) + natPunchthroughDebugInterface->OnClientMessage(RakNet::RakString("Sent OOB ID_NAT_ESTABLISH_UNIDIRECTIONAL to guid %s, system address %s.", guidString, ipAddressString)); + else + natPunchthroughDebugInterface->OnClientMessage(RakNet::RakString("Sent OOB ID_NAT_ESTABLISH_BIDIRECTIONAL to guid %s, system address %s.", guidString, ipAddressString)); + } +} +void NatPunchthroughClient::OnNewConnection(SystemAddress systemAddress, RakNetGUID rakNetGUID, bool isIncoming) +{ + (void) rakNetGUID; + (void) isIncoming; + + // Try to track new port mappings on the router. Not reliable, but better than nothing. + SystemAddress ourExternalId = rakPeerInterface->GetExternalID(systemAddress); + if (ourExternalId!=UNASSIGNED_SYSTEM_ADDRESS) + mostRecentNewExternalPort=ourExternalId.port; +} + +void NatPunchthroughClient::OnClosedConnection(SystemAddress systemAddress, RakNetGUID rakNetGUID, PI2_LostConnectionReason lostConnectionReason ) +{ + (void) systemAddress; + (void) rakNetGUID; + (void) lostConnectionReason; + + if (sp.facilitator==systemAddress) + { + // If we lose the connection to the facilitator, all previous failures not currently in progress are returned as such + unsigned int i=0; + while (i < failedAttemptList.Size()) + { + if (sp.nextActionTime!=0 && sp.targetGuid==failedAttemptList[i].guid) + { + i++; + continue; + } + + PushFailure(); + + failedAttemptList.RemoveAtIndexFast(i); + } + } + + /* + (void) lostConnectionReason; + + bool deletedFirst=false; + unsigned int i=0; + while (i < pendingOpenNAT.Size()) + { + if (pendingOpenNAT[i].facilitator==systemAddress) + { + if (natPunchthroughDebugInterface) + { + if (lostConnectionReason==LCR_CLOSED_BY_USER) + natPunchthroughDebugInterface->OnClientMessage("Nat server connection lost. Reason=LCR_CLOSED_BY_USER\n"); + else if (lostConnectionReason==LCR_DISCONNECTION_NOTIFICATION) + natPunchthroughDebugInterface->OnClientMessage("Nat server connection lost. Reason=LCR_CLOSED_BY_USER\n"); + else if (lostConnectionReason==LCR_CONNECTION_LOST) + natPunchthroughDebugInterface->OnClientMessage("Nat server connection lost. Reason=LCR_CONNECTION_LOST\n"); + } + + // Request failed because connection to server lost before remote system ping attempt occurred + Packet *p = rakPeerInterface->AllocatePacket(sizeof(MessageID)); + p->data[0]=ID_NAT_CONNECTION_TO_TARGET_LOST; + p->systemAddress=systemAddress; + p->systemAddress.systemIndex=(SystemIndex)-1; + p->guid=rakNetGUID; + rakPeerInterface->PushBackPacket(p, false); + if (i==0) + deletedFirst; + + pendingOpenNAT.RemoveAtIndex(i); + } + else + i++; + } + + // Lost connection to facilitator while an attempt was in progress. Give up on that attempt, and try the next in the queue. + if (deletedFirst && pendingOpenNAT.Size()) + { + SendPunchthrough(pendingOpenNAT[0].destination, pendingOpenNAT[0].facilitator); + } + */ +} +void NatPunchthroughClient::OnGetMostRecentPort(Packet *packet) +{ + RakNet::BitStream incomingBs(packet->data,packet->length,false); + incomingBs.IgnoreBytes(sizeof(MessageID)); + uint16_t sessionId; + incomingBs.Read(sessionId); + + RakNet::BitStream outgoingBs; + outgoingBs.Write((MessageID)ID_NAT_GET_MOST_RECENT_PORT); + outgoingBs.Write(sessionId); + outgoingBs.Write(mostRecentNewExternalPort); + rakPeerInterface->Send(&outgoingBs,HIGH_PRIORITY,RELIABLE_ORDERED,0,packet->systemAddress,false); + sp.facilitator=packet->systemAddress; +} +/* +unsigned int NatPunchthroughClient::GetPendingOpenNATIndex(RakNetGUID destination, SystemAddress facilitator) +{ + unsigned int i; + for (i=0; i < pendingOpenNAT.Size(); i++) + { + if (pendingOpenNAT[i].destination==destination && pendingOpenNAT[i].facilitator==facilitator) + return i; + } + return (unsigned int) -1; +} +*/ +void NatPunchthroughClient::SendPunchthrough(RakNetGUID destination, SystemAddress facilitator) +{ + RakNet::BitStream outgoingBs; + outgoingBs.Write((MessageID)ID_NAT_PUNCHTHROUGH_REQUEST); + outgoingBs.Write(destination); + rakPeerInterface->Send(&outgoingBs,HIGH_PRIORITY,RELIABLE_ORDERED,0,facilitator,false); + +// RakAssert(rakPeerInterface->GetSystemAddressFromGuid(destination)==UNASSIGNED_SYSTEM_ADDRESS); + + if (natPunchthroughDebugInterface) + { + char guidString[128]; + destination.ToString(guidString); + natPunchthroughDebugInterface->OnClientMessage(RakNet::RakString("Starting ID_NAT_PUNCHTHROUGH_REQUEST to guid %s.", guidString)); + } +} +void NatPunchthroughClient::OnAttach(void) +{ + Clear(); +} +void NatPunchthroughClient::OnDetach(void) +{ + Clear(); +} +void NatPunchthroughClient::OnRakPeerShutdown(void) +{ + Clear(); +} +void NatPunchthroughClient::Clear(void) +{ + OnReadyForNextPunchthrough(); + + failedAttemptList.Clear(false, __FILE__,__LINE__); +} +PunchthroughConfiguration* NatPunchthroughClient::GetPunchthroughConfiguration(void) +{ + return &pc; +} +void NatPunchthroughClient::OnReadyForNextPunchthrough(void) +{ + if (rakPeerInterface==0) + return; + + sp.nextActionTime=0; + + RakNet::BitStream outgoingBs; + outgoingBs.Write((MessageID)ID_NAT_CLIENT_READY); + rakPeerInterface->Send(&outgoingBs,HIGH_PRIORITY,RELIABLE_ORDERED,0,sp.facilitator,false); +} + +void NatPunchthroughClient::PushSuccess(void) +{ +// RakAssert(rakPeerInterface->IsConnected(sp.targetAddress,true,true)==false); + + Packet *p = rakPeerInterface->AllocatePacket(sizeof(MessageID)+sizeof(unsigned char)); + p->data[0]=ID_NAT_PUNCHTHROUGH_SUCCEEDED; + p->systemAddress=sp.targetAddress; + p->systemAddress.systemIndex=(SystemIndex)-1; + p->guid=sp.targetGuid; + if (sp.weAreSender) + p->data[1]=1; + else + p->data[1]=0; + rakPeerInterface->PushBackPacket(p, true); +} + +bool NatPunchthroughClient::RemoveFromFailureQueue(void) +{ + unsigned int i; + for (i=0; i < failedAttemptList.Size(); i++) + { + if (failedAttemptList[i].guid==sp.targetGuid) + { + // Remove from failure queue + failedAttemptList.RemoveAtIndexFast(i); + return true; + } + } + return false; +} + +#endif // _RAKNET_SUPPORT_* + diff --git a/RakNet/Sources/NatPunchthroughClient.h b/RakNet/Sources/NatPunchthroughClient.h new file mode 100644 index 0000000..ad10979 --- /dev/null +++ b/RakNet/Sources/NatPunchthroughClient.h @@ -0,0 +1,234 @@ + +/// \file +/// \brief Contains the NAT-punchthrough plugin for the client. +/// +/// This file is part of RakNet Copyright 2003 Jenkins Software LLC +/// +/// Usage of RakNet is subject to the appropriate license agreement. + +#include "NativeFeatureIncludes.h" +#if _RAKNET_SUPPORT_NatPunchthroughClient==1 + +#ifndef __NAT_PUNCHTHROUGH_CLIENT_H +#define __NAT_PUNCHTHROUGH_CLIENT_H + +#include "RakNetTypes.h" +#include "Export.h" +#include "PluginInterface2.h" +#include "PacketPriority.h" +#include "SocketIncludes.h" +#include "DS_List.h" +#include "RakString.h" + +// Trendnet TEW-632BRP sometimes starts at port 1024 and increments sequentially. +// Zonnet zsr1134we. Replies go out on the net, but are always absorbed by the remote router?? +// Dlink ebr2310 to Trendnet ok +// Trendnet TEW-652BRP to Trendnet 632BRP OK +// Trendnet TEW-632BRP to Trendnet 632BRP OK +// Buffalo WHR-HP-G54 OK +// Netgear WGR614 ok + +class RakPeerInterface; +struct Packet; +class PacketLogger; + +/// \ingroup NAT_PUNCHTHROUGH_GROUP +struct RAK_DLL_EXPORT PunchthroughConfiguration +{ + /// internal: (15 ms * 2 tries + 30 wait) * 5 ports * 8 players = 2.4 seconds + /// external: (50 ms * 8 sends + 100 wait) * 2 port * 8 players = 8 seconds + /// Total: 8 seconds + PunchthroughConfiguration() { + TIME_BETWEEN_PUNCH_ATTEMPTS_INTERNAL=15; + TIME_BETWEEN_PUNCH_ATTEMPTS_EXTERNAL=50; + UDP_SENDS_PER_PORT_INTERNAL=2; + UDP_SENDS_PER_PORT_EXTERNAL=8; + INTERNAL_IP_WAIT_AFTER_ATTEMPTS=30; + MAXIMUM_NUMBER_OF_INTERNAL_IDS_TO_CHECK=5; /// set to 0 to not do lan connects + MAX_PREDICTIVE_PORT_RANGE=2; + EXTERNAL_IP_WAIT_BETWEEN_PORTS=100; + EXTERNAL_IP_WAIT_AFTER_ALL_ATTEMPTS=EXTERNAL_IP_WAIT_BETWEEN_PORTS; + retryOnFailure=false; + } + + /// How much time between each UDP send + RakNetTimeMS TIME_BETWEEN_PUNCH_ATTEMPTS_INTERNAL; + RakNetTimeMS TIME_BETWEEN_PUNCH_ATTEMPTS_EXTERNAL; + + /// How many tries for one port before giving up and going to the next port + int UDP_SENDS_PER_PORT_INTERNAL; + int UDP_SENDS_PER_PORT_EXTERNAL; + + /// After giving up on one internal port, how long to wait before trying the next port + int INTERNAL_IP_WAIT_AFTER_ATTEMPTS; + + /// How many external ports to try past the last known starting port + int MAX_PREDICTIVE_PORT_RANGE; + + /// After giving up on one external port, how long to wait before trying the next port + int EXTERNAL_IP_WAIT_BETWEEN_PORTS; + + /// After trying all external ports, how long to wait before returning ID_NAT_PUNCHTHROUGH_FAILED + int EXTERNAL_IP_WAIT_AFTER_ALL_ATTEMPTS; + + /// Maximum number of internal IP address to try to connect to. + /// Cannot be greater than MAXIMUM_NUMBER_OF_INTERNAL_IDS + /// Should be high enough to try all internal IP addresses on the majority of computers + int MAXIMUM_NUMBER_OF_INTERNAL_IDS_TO_CHECK; + + /// If the first punchthrough attempt fails, try again + /// This sometimes works because the remote router was looking for an incoming message on a higher numbered port before responding to a lower numbered port from the other system + bool retryOnFailure; +}; + +/// \ingroup NAT_PUNCHTHROUGH_GROUP +struct NatPunchthroughDebugInterface +{ + NatPunchthroughDebugInterface() {} + virtual ~NatPunchthroughDebugInterface() {} + virtual void OnClientMessage(const char *msg)=0; +}; + +/// \ingroup NAT_PUNCHTHROUGH_GROUP +struct NatPunchthroughDebugInterface_Printf : public NatPunchthroughDebugInterface +{ + virtual void OnClientMessage(const char *msg); +}; + +/// \ingroup NAT_PUNCHTHROUGH_GROUP +struct NatPunchthroughDebugInterface_PacketLogger : public NatPunchthroughDebugInterface +{ + // Set to non-zero to write to the packetlogger! + PacketLogger *pl; + + NatPunchthroughDebugInterface_PacketLogger() {pl=0;} + ~NatPunchthroughDebugInterface_PacketLogger() {} + virtual void OnClientMessage(const char *msg); +}; + +/// \brief Client code for NATPunchthrough +/// \details Maintain connection to NatPunchthroughServer to process incoming connection attempts through NatPunchthroughClient
+/// Client will send datagrams to port to estimate next port
+/// Will simultaneously connect with another client once ports are estimated. +/// \sa NatTypeDetectionClient +/// See also http://www.jenkinssoftware.com/raknet/manual/natpunchthrough.html +/// \ingroup NAT_PUNCHTHROUGH_GROUP +class RAK_DLL_EXPORT NatPunchthroughClient : public PluginInterface2 +{ +public: + NatPunchthroughClient(); + ~NatPunchthroughClient(); + + /// Punchthrough a NAT. Doesn't connect, just tries to setup the routing table + bool OpenNAT(RakNetGUID destination, SystemAddress facilitator); + + /// Modify the system configuration if desired + /// Don't modify the variables in the structure while punchthrough is in progress + PunchthroughConfiguration* GetPunchthroughConfiguration(void); + + /// Sets a callback to be called with debug messages + /// \param[in] i Pointer to an interface. The pointer is stored, so don't delete it while in progress. Pass 0 to clear. + void SetDebugInterface(NatPunchthroughDebugInterface *i); + + /// Returns the port on the router that incoming messages will be sent to + /// UPNP needs to know this (See UPNP project) + /// \pre Must have connected to the facilitator first + /// \return Port that incoming messages will be sent to, from other clients. This probably won't be the same port RakNet was started on. + unsigned short GetUPNPExternalPort(void) const; + + /// Returns our internal port that RakNet was started on + /// Equivalent to calling rakPeer->GetInternalID(UNASSIGNED_SYSTEM_ADDRESS).port + /// \return Port that incoming messages will arrive on, on our actual system. + unsigned short GetUPNPInternalPort(void) const; + + /// Returns our locally bound system address + /// Equivalent to calling rakPeer->GetInternalID(UNASSIGNED_SYSTEM_ADDRESS).ToString(false); + /// \return Internal address that UPNP should forward messages to + RakNet::RakString GetUPNPInternalAddress(void) const; + + /// \internal For plugin handling + virtual void Update(void); + + /// \internal For plugin handling + virtual PluginReceiveResult OnReceive(Packet *packet); + + /// \internal For plugin handling + virtual void OnNewConnection(SystemAddress systemAddress, RakNetGUID rakNetGUID, bool isIncoming); + + /// \internal For plugin handling + virtual void OnClosedConnection(SystemAddress systemAddress, RakNetGUID rakNetGUID, PI2_LostConnectionReason lostConnectionReason ); + + virtual void OnAttach(void); + virtual void OnDetach(void); + virtual void OnRakPeerShutdown(void); + void Clear(void); + +protected: + unsigned short mostRecentNewExternalPort; + void OnGetMostRecentPort(Packet *packet); + void OnConnectAtTime(Packet *packet); + unsigned int GetPendingOpenNATIndex(RakNetGUID destination, SystemAddress facilitator); + void SendPunchthrough(RakNetGUID destination, SystemAddress facilitator); + void SendTTL(SystemAddress sa); + void SendOutOfBand(SystemAddress sa, MessageID oobId); + void OnPunchthroughFailure(void); + void OnReadyForNextPunchthrough(void); + void PushFailure(void); + bool RemoveFromFailureQueue(void); + void PushSuccess(void); + //void ProcessNextPunchthroughQueue(void); + + /* + struct PendingOpenNAT + { + RakNetGUID destination; + SystemAddress facilitator; + }; + DataStructures::List pendingOpenNAT; + */ + + struct SendPing + { + RakNetTime nextActionTime; + SystemAddress targetAddress; + SystemAddress facilitator; + SystemAddress internalIds[MAXIMUM_NUMBER_OF_INTERNAL_IDS]; + RakNetGUID targetGuid; + bool weAreSender; + int attemptCount; + int retryCount; + int punchingFixedPortAttempts; // only used for TestMode::PUNCHING_FIXED_PORT + uint16_t sessionId; + // Give priority to internal IP addresses because if we are on a LAN, we don't want to try to connect through the internet + enum TestMode + { + TESTING_INTERNAL_IPS, + WAITING_FOR_INTERNAL_IPS_RESPONSE, + TESTING_EXTERNAL_IPS_FROM_FACILITATOR_PORT, + TESTING_EXTERNAL_IPS_FROM_1024, + WAITING_AFTER_ALL_ATTEMPTS, + + // The trendnet remaps the remote port to 1024. + // If you continue punching on a different port for the same IP it bans you and the communication becomes unidirectioal + PUNCHING_FIXED_PORT, + + // try port 1024-1028 + } testMode; + } sp; + + PunchthroughConfiguration pc; + NatPunchthroughDebugInterface *natPunchthroughDebugInterface; + + // The first time we fail a NAT attempt, we add it to failedAttemptList and try again, since sometimes trying again later fixes the problem + // The second time we fail, we return ID_NAT_PUNCHTHROUGH_FAILED + struct AddrAndGuid + { + SystemAddress addr; + RakNetGUID guid; + }; + DataStructures::List failedAttemptList; +}; + +#endif + +#endif // _RAKNET_SUPPORT_* diff --git a/RakNet/Sources/NatPunchthroughServer.cpp b/RakNet/Sources/NatPunchthroughServer.cpp new file mode 100644 index 0000000..67f306d --- /dev/null +++ b/RakNet/Sources/NatPunchthroughServer.cpp @@ -0,0 +1,534 @@ +#include "NativeFeatureIncludes.h" +#if _RAKNET_SUPPORT_NatPunchthroughServer==1 + +#include "NatPunchthroughServer.h" +#include "SocketLayer.h" +#include "BitStream.h" +#include "MessageIdentifiers.h" +#include "RakPeerInterface.h" +#include "MTUSize.h" +#include "GetTime.h" +#include "PacketLogger.h" + +void NatPunchthroughServerDebugInterface_Printf::OnServerMessage(const char *msg) +{ + printf("%s\n", msg); +} +void NatPunchthroughServerDebugInterface_PacketLogger::OnServerMessage(const char *msg) +{ + if (pl) + { + pl->WriteMiscellaneous("Nat", msg); + } +} + + +void NatPunchthroughServer::User::DeleteConnectionAttempt(NatPunchthroughServer::ConnectionAttempt *ca) +{ + unsigned int index = connectionAttempts.GetIndexOf(ca); + if ((unsigned int)index!=(unsigned int)-1) + { + RakNet::OP_DELETE(ca,__FILE__,__LINE__); + connectionAttempts.RemoveAtIndex(index); + } +} +void NatPunchthroughServer::User::DerefConnectionAttempt(NatPunchthroughServer::ConnectionAttempt *ca) +{ + unsigned int index = connectionAttempts.GetIndexOf(ca); + if ((unsigned int)index!=(unsigned int)-1) + { + connectionAttempts.RemoveAtIndex(index); + } +} +bool NatPunchthroughServer::User::HasConnectionAttemptToUser(User *user) +{ + unsigned int index; + for (index=0; index < connectionAttempts.Size(); index++) + { + if (connectionAttempts[index]->recipient->guid==user->guid || + connectionAttempts[index]->sender->guid==user->guid) + return true; + } + return false; +} +void NatPunchthroughServer::User::LogConnectionAttempts(RakNet::RakString &rs) +{ + rs.Clear(); + unsigned int index; + char guidStr[128], ipStr[128]; + guid.ToString(guidStr); + systemAddress.ToString(true,ipStr); + rs=RakNet::RakString("User systemAddress=%s guid=%s\n", ipStr, guidStr); + rs+=RakNet::RakString("%i attempts in list:\n", connectionAttempts.Size()); + for (index=0; index < connectionAttempts.Size(); index++) + { + rs+=RakNet::RakString("%i. SessionID=%i ", index+1, connectionAttempts[index]->sessionId); + if (connectionAttempts[index]->sender==this) + rs+="(We are sender) "; + else + rs+="(We are recipient) "; + if (isReady) + rs+="(READY TO START) "; + else + rs+="(NOT READY TO START) "; + if (connectionAttempts[index]->attemptPhase==NatPunchthroughServer::ConnectionAttempt::NAT_ATTEMPT_PHASE_NOT_STARTED) + rs+="(NOT_STARTED). "; + else + rs+="(GETTING_RECENT_PORTS). "; + if (connectionAttempts[index]->sender==this) + { + connectionAttempts[index]->recipient->guid.ToString(guidStr); + connectionAttempts[index]->recipient->systemAddress.ToString(true,ipStr); + } + else + { + connectionAttempts[index]->sender->guid.ToString(guidStr); + connectionAttempts[index]->sender->systemAddress.ToString(true,ipStr); + } + + rs+=RakNet::RakString("Target systemAddress=%s, guid=%s.\n", ipStr, guidStr); + } +} + +int NatPunchthroughServer::NatPunchthroughUserComp( const RakNetGUID &key, User * const &data ) +{ + if (key < data->guid) + return -1; + if (key > data->guid) + return 1; + return 0; +} + +NatPunchthroughServer::NatPunchthroughServer() +{ + lastUpdate=0; + sessionId=0; + natPunchthroughServerDebugInterface=0; +} +NatPunchthroughServer::~NatPunchthroughServer() +{ + User *user, *otherUser; + ConnectionAttempt *connectionAttempt; + unsigned int j; + while(users.Size()) + { + user = users[0]; + for (j=0; j < user->connectionAttempts.Size(); j++) + { + connectionAttempt=user->connectionAttempts[j]; + if (connectionAttempt->sender==user) + otherUser=connectionAttempt->recipient; + else + otherUser=connectionAttempt->sender; + otherUser->DeleteConnectionAttempt(connectionAttempt); + } + RakNet::OP_DELETE(user,__FILE__,__LINE__); + users[0]=users[users.Size()-1]; + users.RemoveAtIndex(users.Size()-1); + } +} +void NatPunchthroughServer::SetDebugInterface(NatPunchthroughServerDebugInterface *i) +{ + natPunchthroughServerDebugInterface=i; +} +void NatPunchthroughServer::Update(void) +{ + ConnectionAttempt *connectionAttempt; + User *user, *recipient; + unsigned int i,j; + RakNetTime time = RakNet::GetTime(); + if (time > lastUpdate+250) + { + lastUpdate=time; + + for (i=0; i < users.Size(); i++) + { + user=users[i]; + for (j=0; j < user->connectionAttempts.Size(); j++) + { + connectionAttempt=user->connectionAttempts[j]; + if (connectionAttempt->sender==user) + { + if (connectionAttempt->attemptPhase!=ConnectionAttempt::NAT_ATTEMPT_PHASE_NOT_STARTED && + time > connectionAttempt->startTime && + time > 10000 + connectionAttempt->startTime ) // Formerly 5000, but sometimes false positives + { + RakNet::BitStream outgoingBs; + + // that other system might not be running the plugin + outgoingBs.Write((MessageID)ID_NAT_TARGET_UNRESPONSIVE); + outgoingBs.Write(connectionAttempt->recipient->guid); + outgoingBs.Write(connectionAttempt->sessionId); + rakPeerInterface->Send(&outgoingBs,HIGH_PRIORITY,RELIABLE_ORDERED,0,connectionAttempt->sender->systemAddress,false); + + // 05/28/09 Previously only told sender about ID_NAT_CONNECTION_TO_TARGET_LOST + // However, recipient may be expecting it due to external code + // In that case, recipient would never get any response if the sender dropped + outgoingBs.Reset(); + outgoingBs.Write((MessageID)ID_NAT_TARGET_UNRESPONSIVE); + outgoingBs.Write(connectionAttempt->sender->guid); + outgoingBs.Write(connectionAttempt->sessionId); + rakPeerInterface->Send(&outgoingBs,HIGH_PRIORITY,RELIABLE_ORDERED,0,connectionAttempt->recipient->systemAddress,false); + + connectionAttempt->sender->isReady=true; + connectionAttempt->recipient->isReady=true; + recipient=connectionAttempt->recipient; + + + if (natPunchthroughServerDebugInterface) + { + char str[1024]; + char addr1[128], addr2[128]; + // 8/01/09 Fixed bug where this was after DeleteConnectionAttempt() + connectionAttempt->sender->systemAddress.ToString(true,addr1); + connectionAttempt->recipient->systemAddress.ToString(true,addr2); + sprintf(str, "Sending ID_NAT_TARGET_UNRESPONSIVE to sender %s and recipient %s.", addr1, addr2); + natPunchthroughServerDebugInterface->OnServerMessage(str); + RakNet::RakString log; + connectionAttempt->sender->LogConnectionAttempts(log); + connectionAttempt->recipient->LogConnectionAttempts(log); + } + + + connectionAttempt->sender->DerefConnectionAttempt(connectionAttempt); + connectionAttempt->recipient->DeleteConnectionAttempt(connectionAttempt); + + StartPunchthroughForUser(user); + StartPunchthroughForUser(recipient); + + break; + } + } + } + } + } +} +PluginReceiveResult NatPunchthroughServer::OnReceive(Packet *packet) +{ + switch (packet->data[0]) + { + case ID_NAT_PUNCHTHROUGH_REQUEST: + OnNATPunchthroughRequest(packet); + return RR_STOP_PROCESSING_AND_DEALLOCATE; + case ID_NAT_GET_MOST_RECENT_PORT: + OnGetMostRecentPort(packet); + return RR_STOP_PROCESSING_AND_DEALLOCATE; + case ID_NAT_CLIENT_READY: + OnClientReady(packet); + return RR_STOP_PROCESSING_AND_DEALLOCATE; + } + return RR_CONTINUE_PROCESSING; +} +void NatPunchthroughServer::OnClosedConnection(SystemAddress systemAddress, RakNetGUID rakNetGUID, PI2_LostConnectionReason lostConnectionReason ) +{ + (void) lostConnectionReason; + (void) systemAddress; + + unsigned int i=0; + bool objectExists; + i = users.GetIndexFromKey(rakNetGUID, &objectExists); + if (objectExists) + { + RakNet::BitStream outgoingBs; + DataStructures::List freedUpInProgressUsers; + User *user = users[i]; + User *otherUser; + unsigned int connectionAttemptIndex; + ConnectionAttempt *connectionAttempt; + for (connectionAttemptIndex=0; connectionAttemptIndex < user->connectionAttempts.Size(); connectionAttemptIndex++) + { + connectionAttempt=user->connectionAttempts[connectionAttemptIndex]; + outgoingBs.Reset(); + if (connectionAttempt->recipient==user) + { + otherUser=connectionAttempt->sender; + } + else + { + otherUser=connectionAttempt->recipient; + } + + // 05/28/09 Previously only told sender about ID_NAT_CONNECTION_TO_TARGET_LOST + // However, recipient may be expecting it due to external code + // In that case, recipient would never get any response if the sender dropped + outgoingBs.Write((MessageID)ID_NAT_CONNECTION_TO_TARGET_LOST); + outgoingBs.Write(rakNetGUID); + outgoingBs.Write(connectionAttempt->sessionId); + rakPeerInterface->Send(&outgoingBs,HIGH_PRIORITY,RELIABLE_ORDERED,0,otherUser->systemAddress,false); + + // 4/22/09 - Bug: was checking inProgress, legacy variable not used elsewhere + if (connectionAttempt->attemptPhase==ConnectionAttempt::NAT_ATTEMPT_PHASE_GETTING_RECENT_PORTS) + { + otherUser->isReady=true; + freedUpInProgressUsers.Insert(otherUser, __FILE__, __LINE__ ); + } + + otherUser->DeleteConnectionAttempt(connectionAttempt); + } + + RakNet::OP_DELETE(users[i], __FILE__, __LINE__); + users.RemoveAtIndex(i); + + for (i=0; i < freedUpInProgressUsers.Size(); i++) + { + StartPunchthroughForUser(freedUpInProgressUsers[i]); + } + } +} + +void NatPunchthroughServer::OnNewConnection(SystemAddress systemAddress, RakNetGUID rakNetGUID, bool isIncoming) +{ + (void) systemAddress; + (void) isIncoming; + + User *user = RakNet::OP_NEW(__FILE__,__LINE__); + user->guid=rakNetGUID; + user->mostRecentPort=0; + user->systemAddress=systemAddress; + user->isReady=true; + users.Insert(rakNetGUID, user, true, __FILE__,__LINE__); +} +void NatPunchthroughServer::OnNATPunchthroughRequest(Packet *packet) +{ + RakNet::BitStream outgoingBs; + RakNet::BitStream incomingBs(packet->data, packet->length, false); + incomingBs.IgnoreBytes(sizeof(MessageID)); + RakNetGUID recipientGuid, senderGuid; + incomingBs.Read(recipientGuid); + senderGuid=packet->guid; + unsigned int i; + bool objectExists; + i = users.GetIndexFromKey(senderGuid, &objectExists); + RakAssert(objectExists); + + ConnectionAttempt *ca = RakNet::OP_NEW(__FILE__,__LINE__); + ca->sender=users[i]; + ca->sessionId=sessionId++; + i = users.GetIndexFromKey(recipientGuid, &objectExists); + if (objectExists==false) + { + outgoingBs.Write((MessageID)ID_NAT_TARGET_NOT_CONNECTED); + outgoingBs.Write(recipientGuid); + rakPeerInterface->Send(&outgoingBs,HIGH_PRIORITY,RELIABLE_ORDERED,0,packet->systemAddress,false); + RakNet::OP_DELETE(ca,__FILE__,__LINE__); + return; + } + ca->recipient=users[i]; + if (ca->recipient->HasConnectionAttemptToUser(ca->sender)) + { + outgoingBs.Write((MessageID)ID_NAT_ALREADY_IN_PROGRESS); + outgoingBs.Write(recipientGuid); + rakPeerInterface->Send(&outgoingBs,HIGH_PRIORITY,RELIABLE_ORDERED,0,packet->systemAddress,false); + RakNet::OP_DELETE(ca,__FILE__,__LINE__); + return; + } + + ca->sender->connectionAttempts.Insert(ca, __FILE__, __LINE__ ); + ca->recipient->connectionAttempts.Insert(ca, __FILE__, __LINE__ ); + + StartPunchthroughForUser(ca->sender); +} +void NatPunchthroughServer::OnClientReady(Packet *packet) +{ + unsigned int i; + bool objectExists; + i = users.GetIndexFromKey(packet->guid, &objectExists); + if (objectExists) + { + users[i]->isReady=true; + StartPunchthroughForUser(users[i]); + } +} + +void NatPunchthroughServer::OnGetMostRecentPort(Packet *packet) +{ + RakNet::BitStream bsIn(packet->data, packet->length, false); + bsIn.IgnoreBytes(sizeof(MessageID)); + uint16_t sessionId; + unsigned short mostRecentPort; + bsIn.Read(sessionId); + bsIn.Read(mostRecentPort); + + unsigned int i,j; + User *user; + ConnectionAttempt *connectionAttempt; + bool objectExists; + i = users.GetIndexFromKey(packet->guid, &objectExists); + + if (natPunchthroughServerDebugInterface) + { + RakNet::RakString log; + char addr1[128], addr2[128]; + packet->systemAddress.ToString(true,addr1); + packet->guid.ToString(addr2); + log=RakNet::RakString("Got ID_NAT_GET_MOST_RECENT_PORT from systemAddress %s guid %s. port=%i. sessionId=%i. userFound=%i.", addr1, addr2, mostRecentPort, sessionId, objectExists); + natPunchthroughServerDebugInterface->OnServerMessage(log.C_String()); + } + + if (objectExists) + { + user=users[i]; + user->mostRecentPort=mostRecentPort; + RakNetTime time = RakNet::GetTime(); + + for (j=0; j < user->connectionAttempts.Size(); j++) + { + connectionAttempt=user->connectionAttempts[j]; + if (connectionAttempt->attemptPhase==ConnectionAttempt::NAT_ATTEMPT_PHASE_GETTING_RECENT_PORTS && + connectionAttempt->sender->mostRecentPort!=0 && + connectionAttempt->recipient->mostRecentPort!=0 && + // 04/29/08 add sessionId to prevent processing for other systems + connectionAttempt->sessionId==sessionId) + { + SystemAddress senderSystemAddress = connectionAttempt->sender->systemAddress; + SystemAddress recipientSystemAddress = connectionAttempt->recipient->systemAddress; + SystemAddress recipientTargetAddress = recipientSystemAddress; + SystemAddress senderTargetAddress = senderSystemAddress; + recipientTargetAddress.port=connectionAttempt->recipient->mostRecentPort; + senderTargetAddress.port=connectionAttempt->sender->mostRecentPort; + + // Pick a time far enough in the future that both systems will have gotten the message + int targetPing = rakPeerInterface->GetAveragePing(recipientTargetAddress); + int senderPing = rakPeerInterface->GetAveragePing(senderSystemAddress); + RakNetTime simultaneousAttemptTime; + if (targetPing==-1 || senderPing==-1) + simultaneousAttemptTime = time + 1500; + else + { + int largerPing = targetPing > senderPing ? targetPing : senderPing; + if (largerPing * 4 < 100) + simultaneousAttemptTime = time + 100; + else + simultaneousAttemptTime = time + (largerPing * 4); + } + + + if (natPunchthroughServerDebugInterface) + { + RakNet::RakString log; + char addr1[128], addr2[128]; + recipientSystemAddress.ToString(true,addr1); + connectionAttempt->recipient->guid.ToString(addr2); + log=RakNet::RakString("Sending ID_NAT_CONNECT_AT_TIME to recipient systemAddress %s guid %s", addr1, addr2); + natPunchthroughServerDebugInterface->OnServerMessage(log.C_String()); + } + + // Send to recipient timestamped message to connect at time + RakNet::BitStream bsOut; + bsOut.Write((MessageID)ID_TIMESTAMP); + bsOut.Write(simultaneousAttemptTime); + bsOut.Write((MessageID)ID_NAT_CONNECT_AT_TIME); + bsOut.Write(connectionAttempt->sessionId); + bsOut.Write(senderTargetAddress); // Public IP, using most recent port + for (j=0; j < MAXIMUM_NUMBER_OF_INTERNAL_IDS; j++) // Internal IP + bsOut.Write(rakPeerInterface->GetInternalID(senderSystemAddress,j)); + bsOut.Write(connectionAttempt->sender->guid); + bsOut.Write(false); + rakPeerInterface->Send(&bsOut,HIGH_PRIORITY,RELIABLE_ORDERED,0,recipientSystemAddress,false); + + + if (natPunchthroughServerDebugInterface) + { + RakNet::RakString log; + char addr1[128], addr2[128]; + senderSystemAddress.ToString(true,addr1); + connectionAttempt->sender->guid.ToString(addr2); + log=RakNet::RakString("Sending ID_NAT_CONNECT_AT_TIME to sender systemAddress %s guid %s", addr1, addr2); + natPunchthroughServerDebugInterface->OnServerMessage(log.C_String()); + } + + + // Same for sender + bsOut.Reset(); + bsOut.Write((MessageID)ID_TIMESTAMP); + bsOut.Write(simultaneousAttemptTime); + bsOut.Write((MessageID)ID_NAT_CONNECT_AT_TIME); + bsOut.Write(connectionAttempt->sessionId); + bsOut.Write(recipientTargetAddress); // Public IP, using most recent port + for (j=0; j < MAXIMUM_NUMBER_OF_INTERNAL_IDS; j++) // Internal IP + bsOut.Write(rakPeerInterface->GetInternalID(recipientSystemAddress,j)); + bsOut.Write(connectionAttempt->recipient->guid); + bsOut.Write(true); + rakPeerInterface->Send(&bsOut,HIGH_PRIORITY,RELIABLE_ORDERED,0,senderSystemAddress,false); + + connectionAttempt->recipient->DerefConnectionAttempt(connectionAttempt); + connectionAttempt->sender->DeleteConnectionAttempt(connectionAttempt); + + // 04/29/08 missing return + return; + } + } + } + else + { + + if (natPunchthroughServerDebugInterface) + { + RakNet::RakString log; + char addr1[128], addr2[128]; + packet->systemAddress.ToString(true,addr1); + packet->guid.ToString(addr2); + log=RakNet::RakString("Ignoring ID_NAT_GET_MOST_RECENT_PORT from systemAddress %s guid %s", addr1, addr2); + natPunchthroughServerDebugInterface->OnServerMessage(log.C_String()); + } + + } +} +void NatPunchthroughServer::StartPunchthroughForUser(User *user) +{ + if (user->isReady==false) + return; + + ConnectionAttempt *connectionAttempt; + User *sender,*recipient,*otherUser; + unsigned int i; + for (i=0; i < user->connectionAttempts.Size(); i++) + { + connectionAttempt=user->connectionAttempts[i]; + if (connectionAttempt->sender==user) + { + otherUser=connectionAttempt->recipient; + sender=user; + recipient=otherUser; + } + else + { + otherUser=connectionAttempt->sender; + recipient=user; + sender=otherUser; + } + + if (otherUser->isReady) + { + if (natPunchthroughServerDebugInterface) + { + char str[1024]; + char addr1[128], addr2[128]; + sender->systemAddress.ToString(true,addr1); + recipient->systemAddress.ToString(true,addr2); + sprintf(str, "Sending NAT_ATTEMPT_PHASE_GETTING_RECENT_PORTS to sender %s and recipient %s.", addr1, addr2); + natPunchthroughServerDebugInterface->OnServerMessage(str); + } + + sender->isReady=false; + recipient->isReady=false; + connectionAttempt->attemptPhase=ConnectionAttempt::NAT_ATTEMPT_PHASE_GETTING_RECENT_PORTS; + connectionAttempt->startTime=RakNet::GetTime(); + + sender->mostRecentPort=0; + recipient->mostRecentPort=0; + + RakNet::BitStream outgoingBs; + outgoingBs.Write((MessageID)ID_NAT_GET_MOST_RECENT_PORT); + // 4/29/09 Write sessionID so we don't use returned port for a system we don't want + outgoingBs.Write(connectionAttempt->sessionId); + rakPeerInterface->Send(&outgoingBs,HIGH_PRIORITY,RELIABLE_ORDERED,0,sender->systemAddress,false); + rakPeerInterface->Send(&outgoingBs,HIGH_PRIORITY,RELIABLE_ORDERED,0,recipient->systemAddress,false); + + // 4/22/09 - BUG: missing break statement here + break; + } + } +} + +#endif // _RAKNET_SUPPORT_* diff --git a/RakNet/Sources/NatPunchthroughServer.h b/RakNet/Sources/NatPunchthroughServer.h new file mode 100644 index 0000000..fa9e2e0 --- /dev/null +++ b/RakNet/Sources/NatPunchthroughServer.h @@ -0,0 +1,132 @@ +/// \file +/// \brief Contains the NAT-punchthrough plugin for the server. +/// +/// This file is part of RakNet Copyright 2003 Jenkins Software LLC +/// +/// Usage of RakNet is subject to the appropriate license agreement. + +#include "NativeFeatureIncludes.h" +#if _RAKNET_SUPPORT_NatPunchthroughServer==1 + +#ifndef __NAT_PUNCHTHROUGH_SERVER_H +#define __NAT_PUNCHTHROUGH_SERVER_H + +#include "RakNetTypes.h" +#include "Export.h" +#include "PluginInterface2.h" +#include "PacketPriority.h" +#include "SocketIncludes.h" +#include "DS_OrderedList.h" +#include "RakString.h" + +class RakPeerInterface; +struct Packet; +class PacketLogger; + +/// \defgroup NAT_PUNCHTHROUGH_GROUP NatPunchthrough +/// \brief Connect systems despite both systems being behind a router +/// \details +/// \ingroup PLUGINS_GROUP + +/// \ingroup NAT_PUNCHTHROUGH_GROUP +struct NatPunchthroughServerDebugInterface +{ + NatPunchthroughServerDebugInterface() {} + virtual ~NatPunchthroughServerDebugInterface() {} + virtual void OnServerMessage(const char *msg)=0; +}; + +/// \ingroup NAT_PUNCHTHROUGH_GROUP +struct NatPunchthroughServerDebugInterface_Printf : public NatPunchthroughServerDebugInterface +{ + virtual void OnServerMessage(const char *msg); +}; + +/// \ingroup NAT_PUNCHTHROUGH_GROUP +struct NatPunchthroughServerDebugInterface_PacketLogger : public NatPunchthroughServerDebugInterface +{ + // Set to non-zero to write to the packetlogger! + PacketLogger *pl; + + NatPunchthroughServerDebugInterface_PacketLogger() {pl=0;} + ~NatPunchthroughServerDebugInterface_PacketLogger() {} + virtual void OnServerMessage(const char *msg); +}; + +/// \brief Server code for NATPunchthrough +/// \details Maintain connection to NatPunchthroughServer to process incoming connection attempts through NatPunchthroughClient
+/// Server maintains two sockets clients can connect to so as to estimate the next port choice
+/// Server tells other client about port estimate, current public port to the server, and a time to start connection attempts +/// \sa NatTypeDetectionClient +/// See also http://www.jenkinssoftware.com/raknet/manual/natpunchthrough.html +/// \ingroup NAT_PUNCHTHROUGH_GROUP +class RAK_DLL_EXPORT NatPunchthroughServer : public PluginInterface2 +{ +public: + // Constructor + NatPunchthroughServer(); + + // Destructor + virtual ~NatPunchthroughServer(); + + /// Sets a callback to be called with debug messages + /// \param[in] i Pointer to an interface. The pointer is stored, so don't delete it while in progress. Pass 0 to clear. + void SetDebugInterface(NatPunchthroughServerDebugInterface *i); + + /// \internal For plugin handling + virtual void Update(void); + + /// \internal For plugin handling + virtual PluginReceiveResult OnReceive(Packet *packet); + + /// \internal For plugin handling + virtual void OnClosedConnection(SystemAddress systemAddress, RakNetGUID rakNetGUID, PI2_LostConnectionReason lostConnectionReason ); + virtual void OnNewConnection(SystemAddress systemAddress, RakNetGUID rakNetGUID, bool isIncoming); + + // Each connected user has a ready state. Ready means ready for nat punchthrough. + struct User; + struct ConnectionAttempt + { + ConnectionAttempt() {sender=0; recipient=0; startTime=0; attemptPhase=NAT_ATTEMPT_PHASE_NOT_STARTED;} + User *sender, *recipient; + uint16_t sessionId; + RakNetTime startTime; + enum + { + NAT_ATTEMPT_PHASE_NOT_STARTED, + NAT_ATTEMPT_PHASE_GETTING_RECENT_PORTS, + } attemptPhase; + }; + struct User + { + RakNetGUID guid; + SystemAddress systemAddress; + unsigned short mostRecentPort; + bool isReady; + + DataStructures::List connectionAttempts; + bool HasConnectionAttemptToUser(User *user); + void DerefConnectionAttempt(ConnectionAttempt *ca); + void DeleteConnectionAttempt(ConnectionAttempt *ca); + void LogConnectionAttempts(RakNet::RakString &rs); + }; + RakNetTime lastUpdate; + static int NatPunchthroughUserComp( const RakNetGUID &key, User * const &data ); +protected: + void OnNATPunchthroughRequest(Packet *packet); + DataStructures::OrderedList users; + + void OnGetMostRecentPort(Packet *packet); + void OnClientReady(Packet *packet); + + void SendTimestamps(void); + void StartPendingPunchthrough(void); + void StartPunchthroughForUser(User*user); + uint16_t sessionId; + NatPunchthroughServerDebugInterface *natPunchthroughServerDebugInterface; + +}; + +#endif + +#endif // _RAKNET_SUPPORT_* diff --git a/RakNet/Sources/NatTypeDetectionClient.cpp b/RakNet/Sources/NatTypeDetectionClient.cpp new file mode 100644 index 0000000..0b10154 --- /dev/null +++ b/RakNet/Sources/NatTypeDetectionClient.cpp @@ -0,0 +1,161 @@ +#include "NativeFeatureIncludes.h" +#if _RAKNET_SUPPORT_NatTypeDetectionClient==1 + +#include "NatTypeDetectionClient.h" +#include "RakNetSocket.h" +#include "RakNetSmartPtr.h" +#include "BitStream.h" +#include "SocketIncludes.h" +#include "RakString.h" +#include "RakPeerInterface.h" +#include "MessageIdentifiers.h" +#include "SocketLayer.h" + +using namespace RakNet; + +NatTypeDetectionClient::NatTypeDetectionClient() +{ + c2=INVALID_SOCKET; +} +NatTypeDetectionClient::~NatTypeDetectionClient() +{ + if (c2!=INVALID_SOCKET) + { + closesocket(c2); + } +} +void NatTypeDetectionClient::DetectNATType(SystemAddress _serverAddress) +{ + if (IsInProgress()) + return; + + if (c2==INVALID_SOCKET) + { + DataStructures::List > sockets; + rakPeerInterface->GetSockets(sockets); + SystemAddress sockAddr = SocketLayer::GetSystemAddress(sockets[0]->s); + char str[64]; + sockAddr.ToString(false,str); + printf("Binding to local socket %s\n", str); + c2=CreateNonblockingBoundSocket(str); + c2Port=SocketLayer::Instance()->GetLocalPort(c2); + } + + + serverAddress=_serverAddress; + + RakNet::BitStream bs; + bs.Write((unsigned char)ID_NAT_TYPE_DETECTION_REQUEST); + bs.Write(true); // IsRequest + bs.Write(c2Port); + printf("Sending NAT type detection request, local port %d\n", c2Port); + rakPeerInterface->Send(&bs,MEDIUM_PRIORITY,RELIABLE,0,serverAddress,false); +} +void NatTypeDetectionClient::OnCompletion(NATTypeDetectionResult result) +{ + Packet *p = rakPeerInterface->AllocatePacket(sizeof(MessageID)+sizeof(unsigned char)*2); + printf("Returning nat detection result to the user\n"); + p->data[0]=ID_NAT_TYPE_DETECTION_RESULT; + p->systemAddress=serverAddress; + p->systemAddress.systemIndex=(SystemIndex)-1; + p->guid=rakPeerInterface->GetGuidFromSystemAddress(serverAddress); + p->data[1]=(unsigned char) result; + rakPeerInterface->PushBackPacket(p, true); + + // Symmetric and port restricted are determined by server, so no need to notify server we are done + if (result!=NAT_TYPE_PORT_RESTRICTED && result!=NAT_TYPE_SYMMETRIC) + { + // Otherwise tell the server we got this message, so it stops sending tests to us + RakNet::BitStream bs; + bs.Write((unsigned char)ID_NAT_TYPE_DETECTION_REQUEST); + bs.Write(false); // Done + rakPeerInterface->Send(&bs,HIGH_PRIORITY,RELIABLE,0,serverAddress,false); + } + + Shutdown(); +} +bool NatTypeDetectionClient::IsInProgress(void) const +{ + return serverAddress!=UNASSIGNED_SYSTEM_ADDRESS; +} +void NatTypeDetectionClient::Update(void) +{ + if (IsInProgress()) + { + char data[ MAXIMUM_MTU_SIZE ]; + int len; + SystemAddress sender; + len=NatTypeRecvFrom(data, c2, sender); + if (len==1 && data[0]==NAT_TYPE_NONE) + { + OnCompletion(NAT_TYPE_NONE); + RakAssert(IsInProgress()==false); + } + } +} +PluginReceiveResult NatTypeDetectionClient::OnReceive(Packet *packet) +{ + if (IsInProgress()) + { + switch (packet->data[0]) + { + case ID_OUT_OF_BAND_INTERNAL: + { + if (packet->length>=3 && packet->data[1]==ID_NAT_TYPE_DETECT) + { + OnCompletion((NATTypeDetectionResult)packet->data[2]); + return RR_STOP_PROCESSING_AND_DEALLOCATE; + } + } + break; + case ID_NAT_TYPE_DETECTION_RESULT: + OnCompletion((NATTypeDetectionResult)packet->data[1]); + return RR_STOP_PROCESSING_AND_DEALLOCATE; + case ID_NAT_TYPE_DETECTION_REQUEST: + OnTestPortRestricted(packet); + return RR_STOP_PROCESSING_AND_DEALLOCATE; + } + } + + return RR_CONTINUE_PROCESSING; +} +void NatTypeDetectionClient::OnClosedConnection(SystemAddress systemAddress, RakNetGUID rakNetGUID, PI2_LostConnectionReason lostConnectionReason ) +{ + (void) lostConnectionReason; + (void) rakNetGUID; + + if (IsInProgress() && systemAddress==serverAddress) + Shutdown(); +} +void NatTypeDetectionClient::OnTestPortRestricted(Packet *packet) +{ + RakNet::BitStream bsIn(packet->data,packet->length,false); + bsIn.IgnoreBytes(sizeof(MessageID)); + RakNet::RakString s3p4StrAddress; + bsIn.Read(s3p4StrAddress); + unsigned short s3p4Port; + bsIn.Read(s3p4Port); + SystemAddress s3p4Addr(s3p4StrAddress.C_String(), s3p4Port); + + DataStructures::List > sockets; + rakPeerInterface->GetSockets(sockets); + + // Send off the RakNet socket to the specified address, message is unformatted + // Server does this twice, so don't have to unduly worry about packetloss + RakNet::BitStream bsOut; + bsOut.Write((MessageID) NAT_TYPE_PORT_RESTRICTED); + bsOut.Write(rakPeerInterface->GetGuidFromSystemAddress(UNASSIGNED_SYSTEM_ADDRESS)); + SocketLayer::Instance()->SendTo_PC( sockets[0]->s, (const char*) bsOut.GetData(), bsOut.GetNumberOfBytesUsed(), s3p4Addr.binaryAddress, s3p4Addr.port); +} +void NatTypeDetectionClient::Shutdown(void) +{ + serverAddress=UNASSIGNED_SYSTEM_ADDRESS; + if (c2!=INVALID_SOCKET) + { + closesocket(c2); + c2=INVALID_SOCKET; + } + +} + +#endif // _RAKNET_SUPPORT_* diff --git a/RakNet/Sources/NatTypeDetectionClient.h b/RakNet/Sources/NatTypeDetectionClient.h new file mode 100644 index 0000000..68be838 --- /dev/null +++ b/RakNet/Sources/NatTypeDetectionClient.h @@ -0,0 +1,76 @@ +/// \file +/// \brief Contains the NAT-type detection code for the client +/// +/// This file is part of RakNet Copyright 2003 Jenkins Software LLC +/// +/// Usage of RakNet is subject to the appropriate license agreement. + +#include "NativeFeatureIncludes.h" +#if _RAKNET_SUPPORT_NatTypeDetectionClient==1 + +#ifndef __NAT_TYPE_DETECTION_CLIENT_H +#define __NAT_TYPE_DETECTION_CLIENT_H + +#include "RakNetTypes.h" +#include "Export.h" +#include "PluginInterface2.h" +#include "PacketPriority.h" +#include "SocketIncludes.h" +#include "DS_OrderedList.h" +#include "RakString.h" +#include "NatTypeDetectionCommon.h" + +class RakPeerInterface; +struct Packet; + +namespace RakNet +{ + /// \brief Client code for NatTypeDetection + /// \details See NatTypeDetectionServer.h for algorithm + /// To use, just connect to the server, and call DetectNAT + /// You will get back ID_NAT_TYPE_DETECTION_RESULT with one of the enumerated values of NATTypeDetectionResult found in NATTypeDetectionCommon.h + /// See also http://www.jenkinssoftware.com/raknet/manual/natpunchthrough.html + /// \sa NatPunchthroughClient + /// \sa NatTypeDetectionServer + /// \ingroup NAT_TYPE_DETECTION_GROUP + class RAK_DLL_EXPORT NatTypeDetectionClient : public PluginInterface2 + { + public: + // Constructor + NatTypeDetectionClient(); + + // Destructor + virtual ~NatTypeDetectionClient(); + + /// Send the message to the server to detect the nat type + /// Server must be running NatTypeDetectionServer + /// We must already be connected to the server + /// \param[in] serverAddress address of the server + void DetectNATType(SystemAddress _serverAddress); + + /// \internal For plugin handling + virtual void Update(void); + + /// \internal For plugin handling + virtual PluginReceiveResult OnReceive(Packet *packet); + + virtual void OnClosedConnection(SystemAddress systemAddress, RakNetGUID rakNetGUID, PI2_LostConnectionReason lostConnectionReason ); + + protected: + SOCKET c2; + unsigned short c2Port; + void Shutdown(void); + void OnCompletion(NATTypeDetectionResult result); + bool IsInProgress(void) const; + + void OnTestPortRestricted(Packet *packet); + SystemAddress serverAddress; + }; + + +} + + +#endif + +#endif // _RAKNET_SUPPORT_* diff --git a/RakNet/Sources/NatTypeDetectionCommon.cpp b/RakNet/Sources/NatTypeDetectionCommon.cpp new file mode 100644 index 0000000..e80a3a2 --- /dev/null +++ b/RakNet/Sources/NatTypeDetectionCommon.cpp @@ -0,0 +1,93 @@ +#include "NatTypeDetectionCommon.h" +#include "SocketLayer.h" +#include "SocketIncludes.h" + +using namespace RakNet; + +bool RakNet::CanConnect(NATTypeDetectionResult type1, NATTypeDetectionResult type2) +{ + /// If one system is NAT_TYPE_SYMMETRIC, the other must be NAT_TYPE_ADDRESS_RESTRICTED or less + /// If one system is NAT_TYPE_PORT_RESTRICTED, the other must be NAT_TYPE_PORT_RESTRICTED or less + bool connectionGraph[5][5] = + { + // None, Full Cone, Address Restricted, Port Restricted, Symmetric + true, true, true, true, true, // None + true, true, true, true, true, // Full Cone + true, true, true, true, true, // Address restricted + true, true, true, true, false, // Port restricted + true, true, true, false, false, // Symmetric + }; + + return connectionGraph[(int) type1][(int) type2]; +} + +const char *RakNet::NATTypeDetectionResultToString(NATTypeDetectionResult type) +{ + switch (type) + { + case NAT_TYPE_NONE: + return "None"; + case NAT_TYPE_FULL_CONE: + return "Full cone"; + case NAT_TYPE_ADDRESS_RESTRICTED: + return "Address restricted"; + case NAT_TYPE_PORT_RESTRICTED: + return "Port restricted"; + case NAT_TYPE_SYMMETRIC: + return "Symmetric"; + } + return "Error, unknown enum in NATTypeDetectionResult"; +} + +// None and relaxed can connect to anything +// Moderate can connect to moderate or less +// Strict can connect to relaxed or less +const char *RakNet::NATTypeDetectionResultToStringFriendly(NATTypeDetectionResult type) +{ + switch (type) + { + case NAT_TYPE_NONE: + return "Open"; + case NAT_TYPE_FULL_CONE: + return "Relaxed"; + case NAT_TYPE_ADDRESS_RESTRICTED: + return "Relaxed"; + case NAT_TYPE_PORT_RESTRICTED: + return "Moderate"; + case NAT_TYPE_SYMMETRIC: + return "Strict"; + } + return "Error, unknown enum in NATTypeDetectionResult"; +} + + +SOCKET RakNet::CreateNonblockingBoundSocket(const char *bindAddr) +{ + SOCKET s = SocketLayer::Instance()->CreateBoundSocket( 0, false, bindAddr, true ); + #ifdef _WIN32 + unsigned long nonblocking = 1; + ioctlsocket( s, FIONBIO, &nonblocking ); + #elif defined(_PS3) || defined(__PS3__) || defined(SN_TARGET_PS3) + + #else + fcntl( s, F_SETFL, O_NONBLOCK ); + #endif + return s; +} + +int RakNet::NatTypeRecvFrom(char *data, SOCKET socket, SystemAddress &sender) +{ + sockaddr_in sa; + socklen_t len2; + const int flag=0; + len2 = sizeof( sa ); + sa.sin_family = AF_INET; + sa.sin_port=0; + int len = recvfrom( socket, data, MAXIMUM_MTU_SIZE, flag, ( sockaddr* ) & sa, ( socklen_t* ) & len2 ); + if (len>0) + { + sender.binaryAddress = sa.sin_addr.s_addr; + sender.port = ntohs( sa.sin_port ); + } + return len; +} diff --git a/RakNet/Sources/NatTypeDetectionCommon.h b/RakNet/Sources/NatTypeDetectionCommon.h new file mode 100644 index 0000000..17621ca --- /dev/null +++ b/RakNet/Sources/NatTypeDetectionCommon.h @@ -0,0 +1,56 @@ +/// \defgroup NAT_TYPE_DETECTION_GROUP NatTypeDetection +/// \brief Use a remote server with multiple IP addresses to determine what type of NAT your router is using +/// \details +/// \ingroup PLUGINS_GROUP + +#ifndef __NAT_TYPE_DETECTION_COMMON_H +#define __NAT_TYPE_DETECTION_COMMON_H + +#include "SocketIncludes.h" +#include "RakNetTypes.h" + +namespace RakNet +{ + /// All possible types of NATs (except NAT_TYPE_COUNT, which is an internal value) + enum NATTypeDetectionResult + { + /// Works with anyone + NAT_TYPE_NONE, + /// Accepts any datagrams to a port that has been previously used. Will accept the first datagram from the remote peer. + NAT_TYPE_FULL_CONE, + /// Accepts datagrams to a port as long as the datagram source IP address is a system we have already sent to. Will accept the first datagram if both systems send simultaneously. Otherwise, will accept the first datagram after we have sent one datagram. + NAT_TYPE_ADDRESS_RESTRICTED, + /// Same as address-restricted cone NAT, but we had to send to both the correct remote IP address and correct remote port. The same source address and port to a different destination uses the same mapping. + NAT_TYPE_PORT_RESTRICTED, + /// A different port is chosen for every remote destination. The same source address and port to a different destination uses a different mapping. Since the port will be different, the first external punchthrough attempt will fail. For this to work it requires port-prediction (MAX_PREDICTIVE_PORT_RANGE>1) and that the router chooses ports sequentially. + NAT_TYPE_SYMMETRIC, + /// Hasn't been determined. NATTypeDetectionClient does not use this, but other plugins might + NAT_TYPE_UKNOWN, + /// In progress. NATTypeDetectionClient does not use this, but other plugins might + NAT_TYPE_DETECTION_IN_PROGRESS, + /// Didn't bother figuring it out, as we support UPNP, so it is equivalent to NAT_TYPE_NONE. NATTypeDetectionClient does not use this, but other plugins might + NAT_TYPE_SUPPORTS_UPNP, + /// \internal Must be last + NAT_TYPE_COUNT + }; + + /// \return Can one system with NATTypeDetectionResult \a type1 connect to \a type2 + bool CanConnect(NATTypeDetectionResult type1, NATTypeDetectionResult type2); + + /// Return a technical string representin the enumeration + const char *NATTypeDetectionResultToString(NATTypeDetectionResult type); + + /// Return a friendly string representing the enumeration + /// None and relaxed can connect to anything + /// Moderate can connect to moderate or less + /// Strict can connect to relaxed or less + const char *NATTypeDetectionResultToStringFriendly(NATTypeDetectionResult type); + + /// \internal + SOCKET CreateNonblockingBoundSocket(const char *bindAddr); + + /// \internal + int NatTypeRecvFrom(char *data, SOCKET socket, SystemAddress &sender); +} + +#endif diff --git a/RakNet/Sources/NatTypeDetectionServer.cpp b/RakNet/Sources/NatTypeDetectionServer.cpp new file mode 100644 index 0000000..985caf6 --- /dev/null +++ b/RakNet/Sources/NatTypeDetectionServer.cpp @@ -0,0 +1,263 @@ +#include "NativeFeatureIncludes.h" +#if _RAKNET_SUPPORT_NatTypeDetectionServer==1 + +#include "NatTypeDetectionServer.h" +#include "SocketLayer.h" +#include "RakNetSocket.h" +#include "RakNetSmartPtr.h" +#include "SocketIncludes.h" +#include "RakPeerInterface.h" +#include "MessageIdentifiers.h" +#include "GetTime.h" +#include "BitStream.h" + +using namespace RakNet; + +NatTypeDetectionServer::NatTypeDetectionServer() +{ + s1p2=s2p3=s3p4=s4p5=INVALID_SOCKET; +} +NatTypeDetectionServer::~NatTypeDetectionServer() +{ + Shutdown(); +} +void NatTypeDetectionServer::Startup( + const char *nonRakNetIP2, + const char *nonRakNetIP3, + const char *nonRakNetIP4) +{ + DataStructures::List > sockets; + rakPeerInterface->GetSockets(sockets); + char str[64]; + sockets[0]->boundAddress.ToString(false,str); + s1p2=CreateNonblockingBoundSocket(str); + s1p2Port=SocketLayer::Instance()->GetLocalPort(s1p2); + s2p3=CreateNonblockingBoundSocket(nonRakNetIP2); + s2p3Port=SocketLayer::Instance()->GetLocalPort(s2p3); + s3p4=CreateNonblockingBoundSocket(nonRakNetIP3); + s3p4Port=SocketLayer::Instance()->GetLocalPort(s3p4); + s4p5=CreateNonblockingBoundSocket(nonRakNetIP4); + s4p5Port=SocketLayer::Instance()->GetLocalPort(s4p5); + strcpy(s3p4Address, nonRakNetIP3); +} +void NatTypeDetectionServer::Shutdown() +{ + if (s1p2!=INVALID_SOCKET) + { + closesocket(s1p2); + s1p2=INVALID_SOCKET; + } + if (s2p3!=INVALID_SOCKET) + { + closesocket(s2p3); + s2p3=INVALID_SOCKET; + } + if (s3p4!=INVALID_SOCKET) + { + closesocket(s3p4); + s3p4=INVALID_SOCKET; + } + if (s4p5!=INVALID_SOCKET) + { + closesocket(s4p5); + s4p5=INVALID_SOCKET; + } +} +void NatTypeDetectionServer::Update(void) +{ + int i=0; + RakNetTimeMS time = RakNet::GetTime(); + RakNet::BitStream bs; + SystemAddress boundAddress; + + // Only socket that receives messages is s3p4, to see if the external address is different than that of the connection to rakPeerInterface + char data[ MAXIMUM_MTU_SIZE ]; + int len; + SystemAddress senderAddr; + len=NatTypeRecvFrom(data, s3p4, senderAddr); + // Client is asking us if this is port restricted. Only client requests of this type come in on s3p4 + while (len>0 && data[0]==NAT_TYPE_PORT_RESTRICTED) + { + RakNet::BitStream bsIn((unsigned char*) data,len,false); + RakNetGUID senderGuid; + bsIn.IgnoreBytes(sizeof(MessageID)); + bool readSuccess = bsIn.Read(senderGuid); + RakAssert(readSuccess); + if (readSuccess) + { + unsigned int i = GetDetectionAttemptIndex(senderGuid); + if (i!=(unsigned int)-1) + { + bs.Reset(); + bs.Write((unsigned char) ID_NAT_TYPE_DETECTION_RESULT); + // If different, then symmetric + if (senderAddr!=natDetectionAttempts[i].systemAddress) + { + printf("Determined client is symmetric\n"); + bs.Write((unsigned char) NAT_TYPE_SYMMETRIC); + } + else + { + // else port restricted + printf("Determined client is port restricted\n"); + bs.Write((unsigned char) NAT_TYPE_PORT_RESTRICTED); + } + + rakPeerInterface->Send(&bs,HIGH_PRIORITY,RELIABLE,0,natDetectionAttempts[i].systemAddress,false); + + // Done + natDetectionAttempts.RemoveAtIndexFast(i); + } + else + { + RakAssert("i==0 in Update when looking up GUID in NatTypeDetectionServer.cpp. Either a bug or a late resend" && 0); + } + } + else + { + RakAssert("Didn't read GUID in Update in NatTypeDetectionServer.cpp. Message format error" && 0); + } + + len=NatTypeRecvFrom(data, s3p4, senderAddr); + } + + + while (i < (int) natDetectionAttempts.Size()) + { + if (time > natDetectionAttempts[i].nextStateTime) + { + natDetectionAttempts[i].detectionState=(NATDetectionState)((int)natDetectionAttempts[i].detectionState+1); + natDetectionAttempts[i].nextStateTime=time+natDetectionAttempts[i].timeBetweenAttempts; + unsigned char c; + bs.Reset(); + switch (natDetectionAttempts[i].detectionState) + { + case STATE_TESTING_NONE_1: + case STATE_TESTING_NONE_2: + c = NAT_TYPE_NONE; + printf("Testing NAT_TYPE_NONE\n"); + // S4P5 sends to C2. If arrived, no NAT. Done. (Else S4P5 potentially banned, do not use again). + SocketLayer::Instance()->SendTo_PC( s4p5, (const char*) &c, 1, natDetectionAttempts[i].systemAddress.binaryAddress, natDetectionAttempts[i].c2Port ); + break; + case STATE_TESTING_FULL_CONE_1: + case STATE_TESTING_FULL_CONE_2: + printf("Testing NAT_TYPE_FULL_CONE\n"); + rakPeerInterface->WriteOutOfBandHeader(&bs, ID_OUT_OF_BAND_INTERNAL); + bs.Write((unsigned char) ID_NAT_TYPE_DETECT); + bs.Write((unsigned char) NAT_TYPE_FULL_CONE); + // S2P3 sends to C1 (Different address, different port, to previously used port on client). If received, Full-cone nat. Done. (Else S2P3 potentially banned, do not use again). + SocketLayer::Instance()->SendTo_PC( s2p3, (const char*) bs.GetData(), bs.GetNumberOfBytesUsed(), natDetectionAttempts[i].systemAddress.binaryAddress, natDetectionAttempts[i].systemAddress.port ); + break; + case STATE_TESTING_ADDRESS_RESTRICTED_1: + case STATE_TESTING_ADDRESS_RESTRICTED_2: + printf("Testing NAT_TYPE_ADDRESS_RESTRICTED\n"); + rakPeerInterface->WriteOutOfBandHeader(&bs, ID_OUT_OF_BAND_INTERNAL); + bs.Write((unsigned char) ID_NAT_TYPE_DETECT); + bs.Write((unsigned char) NAT_TYPE_ADDRESS_RESTRICTED); + // S1P2 sends to C1 (Same address, different port, to previously used port on client). If received, address-restricted cone nat. Done. + SocketLayer::Instance()->SendTo_PC( s1p2, (const char*) bs.GetData(), bs.GetNumberOfBytesUsed(), natDetectionAttempts[i].systemAddress.binaryAddress, natDetectionAttempts[i].systemAddress.port ); + break; + case STATE_TESTING_PORT_RESTRICTED_1: + case STATE_TESTING_PORT_RESTRICTED_2: + // C1 sends to S3P4. If address of C1 as seen by S3P4 is the same as the address of C1 as seen by S1P1, then port-restricted cone nat. Done + printf("Testing NAT_TYPE_PORT_RESTRICTED\n"); + bs.Write((unsigned char) ID_NAT_TYPE_DETECTION_REQUEST); + bs.Write(RakString::NonVariadic(s3p4Address)); + bs.Write(s3p4Port); + rakPeerInterface->Send(&bs,HIGH_PRIORITY,RELIABLE,0,natDetectionAttempts[i].systemAddress,false); + break; + default: + printf("Warning, exceeded final check STATE_TESTING_PORT_RESTRICTED_2.\nExpected that client would have sent NAT_TYPE_PORT_RESTRICTED on s3p4.\nDefaulting to Symmetric\n"); + bs.Write((unsigned char) ID_NAT_TYPE_DETECTION_RESULT); + bs.Write((unsigned char) NAT_TYPE_SYMMETRIC); + rakPeerInterface->Send(&bs,HIGH_PRIORITY,RELIABLE,0,natDetectionAttempts[i].systemAddress,false); + natDetectionAttempts.RemoveAtIndexFast(i); + i--; + break; + } + + } + i++; + } +} +PluginReceiveResult NatTypeDetectionServer::OnReceive(Packet *packet) +{ + switch (packet->data[0]) + { + case ID_NAT_TYPE_DETECTION_REQUEST: + if (OnDetectionRequest(packet)) + return RR_STOP_PROCESSING_AND_DEALLOCATE; + else + return RR_STOP_PROCESSING; + } + return RR_CONTINUE_PROCESSING; +} +void NatTypeDetectionServer::OnClosedConnection(SystemAddress systemAddress, RakNetGUID rakNetGUID, PI2_LostConnectionReason lostConnectionReason ) +{ + (void) lostConnectionReason; + (void) rakNetGUID; + + unsigned int i = GetDetectionAttemptIndex(systemAddress); + if (i==(unsigned int)-1) + return; + natDetectionAttempts.RemoveAtIndexFast(i); +} +bool NatTypeDetectionServer::OnDetectionRequest(Packet *packet) +{ + #if defined(_DEBUG) + printf("Got detection request, last ping was %d\n", rakPeerInterface->GetLastPing(packet->systemAddress)); + #endif + if (rakPeerInterface->GetLastPing(packet->systemAddress) == 65535) + { + rakPeerInterface->PushBackPacket(packet, false); + return false; + } + unsigned int i = GetDetectionAttemptIndex(packet->systemAddress); + + RakNet::BitStream bsIn(packet->data, packet->length, false); + bsIn.IgnoreBytes(1); + bool isRequest; + bsIn.Read(isRequest); + if (isRequest) + { + if (i!=(unsigned int)-1) + return true; // Already in progress + + NATDetectionAttempt nda; + nda.detectionState=STATE_NONE; + nda.systemAddress=packet->systemAddress; + nda.guid=packet->guid; + bsIn.Read(nda.c2Port); + nda.nextStateTime=0; + nda.timeBetweenAttempts=rakPeerInterface->GetLastPing(nda.systemAddress)*3+50; + natDetectionAttempts.Push(nda, __FILE__, __LINE__); + } + else + { + if (i==(unsigned int)-1) + return true; // Unknown + // They are done + natDetectionAttempts.RemoveAtIndexFast(i); + } + return true; +} +unsigned int NatTypeDetectionServer::GetDetectionAttemptIndex(SystemAddress sa) +{ + for (unsigned int i=0; i < natDetectionAttempts.Size(); i++) + { + if (natDetectionAttempts[i].systemAddress==sa) + return i; + } + return (unsigned int) -1; +} +unsigned int NatTypeDetectionServer::GetDetectionAttemptIndex(RakNetGUID guid) +{ + for (unsigned int i=0; i < natDetectionAttempts.Size(); i++) + { + if (natDetectionAttempts[i].guid==guid) + return i; + } + return (unsigned int) -1; +} + +#endif // _RAKNET_SUPPORT_* diff --git a/RakNet/Sources/NatTypeDetectionServer.h b/RakNet/Sources/NatTypeDetectionServer.h new file mode 100644 index 0000000..b304169 --- /dev/null +++ b/RakNet/Sources/NatTypeDetectionServer.h @@ -0,0 +1,115 @@ +/// \file +/// \brief Contains the NAT-type detection code for the server +/// +/// This file is part of RakNet Copyright 2003 Jenkins Software LLC +/// +/// Usage of RakNet is subject to the appropriate license agreement. + +#include "NativeFeatureIncludes.h" +#if _RAKNET_SUPPORT_NatTypeDetectionServer==1 + +#ifndef __NAT_TYPE_DETECTION_SERVER_H +#define __NAT_TYPE_DETECTION_SERVER_H + +#include "RakNetTypes.h" +#include "Export.h" +#include "PluginInterface2.h" +#include "PacketPriority.h" +#include "SocketIncludes.h" +#include "DS_OrderedList.h" +#include "RakString.h" +#include "NatTypeDetectionCommon.h" + +class RakPeerInterface; +struct Packet; + +namespace RakNet +{ + +/// \brief Server code for NatTypeDetection +/// \details +/// Sends to a remote system on certain ports and addresses to determine what type of router, if any, that client is behind +/// Requires that the server have 4 external IP addresses +///
    +///
  1. Server has 1 instance of RakNet. Server has four external ip addresses S1 to S4. Five ports are used in total P1 to P5. RakNet is bound to S1P1. Sockets are bound to S1P2, S2P3, S3P4, S4P5 +///
  2. Client with one port using RakNet (C1). Another port not using anything (C2). +///
  3. C1 connects to S1P1 for normal communication. +///
  4. S4P5 sends to C2. If arrived, no NAT. Done. (If didn't arrive, S4P5 potentially banned, do not use again). +///
  5. S2P3 sends to C1 (Different address, different port, to previously used port on client). If received, Full-cone nat. Done. (If didn't arrive, S2P3 potentially banned, do not use again). +///
  6. S1P2 sends to C1 (Same address, different port, to previously used port on client). If received, address-restricted cone nat. Done. +///
  7. Server via RakNet connection tells C1 to send to to S3P4. If address of C1 as seen by S3P4 is the same as the address of C1 as seen by S1P1 (RakNet connection), then port-restricted cone nat. Done +///
  8. Else symmetric nat. Done. +///
+/// See also http://www.jenkinssoftware.com/raknet/manual/natpunchthrough.html +/// \sa NatPunchthroughServer +/// \sa NatTypeDetectionClient +/// \ingroup NAT_TYPE_DETECTION_GROUP +class RAK_DLL_EXPORT NatTypeDetectionServer : public PluginInterface2 +{ +public: + // Constructor + NatTypeDetectionServer(); + + // Destructor + virtual ~NatTypeDetectionServer(); + + /// Start the system, binding to 3 external IPs not already in useS + /// \param[in] nonRakNetIP2 First unused external IP + /// \param[in] nonRakNetIP3 Second unused external IP + /// \param[in] nonRakNetIP4 Third unused external IP + void Startup( + const char *nonRakNetIP2, + const char *nonRakNetIP3, + const char *nonRakNetIP4); + + // Releases the sockets created in Startup(); + void Shutdown(void); + + /// \internal For plugin handling + virtual void Update(void); + + /// \internal For plugin handling + virtual PluginReceiveResult OnReceive(Packet *packet); + virtual void OnClosedConnection(SystemAddress systemAddress, RakNetGUID rakNetGUID, PI2_LostConnectionReason lostConnectionReason ); + + enum NATDetectionState + { + STATE_NONE, + STATE_TESTING_NONE_1, + STATE_TESTING_NONE_2, + STATE_TESTING_FULL_CONE_1, + STATE_TESTING_FULL_CONE_2, + STATE_TESTING_ADDRESS_RESTRICTED_1, + STATE_TESTING_ADDRESS_RESTRICTED_2, + STATE_TESTING_PORT_RESTRICTED_1, + STATE_TESTING_PORT_RESTRICTED_2, + STATE_DONE, + }; + + struct NATDetectionAttempt + { + SystemAddress systemAddress; + NATDetectionState detectionState; + RakNetTimeMS nextStateTime; + RakNetTimeMS timeBetweenAttempts; + unsigned short c2Port; + RakNetGUID guid; + }; + +protected: + bool OnDetectionRequest(Packet *packet); + DataStructures::List natDetectionAttempts; + unsigned int GetDetectionAttemptIndex(SystemAddress sa); + unsigned int GetDetectionAttemptIndex(RakNetGUID guid); + + // s1p1 is rakpeer itself + SOCKET s1p2,s2p3,s3p4,s4p5; + unsigned short s1p2Port, s2p3Port, s3p4Port, s4p5Port; + char s3p4Address[64]; +}; +} + + +#endif + +#endif // _RAKNET_SUPPORT_* diff --git a/RakNet/Sources/NativeFeatureIncludes.h b/RakNet/Sources/NativeFeatureIncludes.h new file mode 100644 index 0000000..3f110d4 --- /dev/null +++ b/RakNet/Sources/NativeFeatureIncludes.h @@ -0,0 +1,129 @@ +// If you want to change these defines, put them in NativeFeatureIncludesOverrides so your changes are not lost when updating RakNet +// The user should not edit this file +#include "NativeFeatureIncludesOverrides.h" + +#ifndef __NATIVE_FEATURE_INCLDUES_H +#define __NATIVE_FEATURE_INCLDUES_H + +#ifndef _RAKNET_SUPPORT_AutoRPC +#define _RAKNET_SUPPORT_AutoRPC 0 +#endif +#ifndef _RAKNET_SUPPORT_ConnectionGraph +#define _RAKNET_SUPPORT_ConnectionGraph 0 +#endif +#ifndef _RAKNET_SUPPORT_ConnectionGraph2 +#define _RAKNET_SUPPORT_ConnectionGraph2 0 +#endif +#ifndef _RAKNET_SUPPORT_DirectoryDeltaTransfer +#define _RAKNET_SUPPORT_DirectoryDeltaTransfer 0 +#endif +#ifndef _RAKNET_SUPPORT_FileListTransfer +#define _RAKNET_SUPPORT_FileListTransfer 0 +#endif +#ifndef _RAKNET_SUPPORT_FullyConnectedMesh +#define _RAKNET_SUPPORT_FullyConnectedMesh 0 +#endif +#ifndef _RAKNET_SUPPORT_FullyConnectedMesh2 +#define _RAKNET_SUPPORT_FullyConnectedMesh2 0 +#endif +#ifndef _RAKNET_SUPPORT_LightweightDatabaseClient +#define _RAKNET_SUPPORT_LightweightDatabaseClient 1 +#endif +#ifndef _RAKNET_SUPPORT_LightweightDatabaseServer +#if UNITY_MASTERSERVER +#define _RAKNET_SUPPORT_LightweightDatabaseServer 1 +#else +#define _RAKNET_SUPPORT_LightweightDatabaseServer 0 +#endif +#endif +#ifndef _RAKNET_SUPPORT_MessageFilter +#define _RAKNET_SUPPORT_MessageFilter 0 +#endif +#ifndef _RAKNET_SUPPORT_NatPunchthroughClient +#define _RAKNET_SUPPORT_NatPunchthroughClient 1 +#endif +#ifndef _RAKNET_SUPPORT_NatPunchthroughServer +#if UNITY_FACILITATOR +#define _RAKNET_SUPPORT_NatPunchthroughServer 1 +#else +#define _RAKNET_SUPPORT_NatPunchthroughServer 0 +#endif +#endif +#ifndef _RAKNET_SUPPORT_NatTypeDetectionClient +#define _RAKNET_SUPPORT_NatTypeDetectionClient 1 +#endif +#ifndef _RAKNET_SUPPORT_NatTypeDetectionServer +#if UNITY_CONNECTIONTESTER +#define _RAKNET_SUPPORT_NatTypeDetectionServer 1 +#endif +#else +#define _RAKNET_SUPPORT_NatTypeDetectionServer 0 +#endif +#ifndef _RAKNET_SUPPORT_PacketLogger +#define _RAKNET_SUPPORT_PacketLogger 1 +#endif +#ifndef _RAKNET_SUPPORT_ReadyEvent +#define _RAKNET_SUPPORT_ReadyEvent 0 +#endif +#ifndef _RAKNET_SUPPORT_ReplicaManager +#define _RAKNET_SUPPORT_ReplicaManager 0 +#endif +#ifndef _RAKNET_SUPPORT_ReplicaManager2 +#define _RAKNET_SUPPORT_ReplicaManager2 0 +#endif +#ifndef _RAKNET_SUPPORT_ReplicaManager3 +#define _RAKNET_SUPPORT_ReplicaManager3 0 +#endif +#ifndef _RAKNET_SUPPORT_Router +#define _RAKNET_SUPPORT_Router 0 +#endif +#ifndef _RAKNET_SUPPORT_Router2 +#define _RAKNET_SUPPORT_Router2 0 +#endif +#ifndef _RAKNET_SUPPORT_RPC4Plugin +#define _RAKNET_SUPPORT_RPC4Plugin 0 +#endif +#ifndef _RAKNET_SUPPORT_TeamBalancer +#define _RAKNET_SUPPORT_TeamBalancer 0 +#endif +#ifndef _RAKNET_SUPPORT_UDPProxyClient +#define _RAKNET_SUPPORT_UDPProxyClient 0 +#endif +#ifndef _RAKNET_SUPPORT_UDPProxyCoordinator +#define _RAKNET_SUPPORT_UDPProxyCoordinator 0 +#endif +#ifndef _RAKNET_SUPPORT_UDPProxyServer +#define _RAKNET_SUPPORT_UDPProxyServer 0 +#endif +#ifndef _RAKNET_SUPPORT_ConsoleServer +#define _RAKNET_SUPPORT_ConsoleServer 0 +#endif +#ifndef _RAKNET_SUPPORT_RakNetTransport +#define _RAKNET_SUPPORT_RakNetTransport 0 +#endif +#ifndef _RAKNET_SUPPORT_TelnetTransport +#define _RAKNET_SUPPORT_TelnetTransport 0 +#endif +#ifndef _RAKNET_SUPPORT_TCPInterface +#define _RAKNET_SUPPORT_TCPInterface 0 +#endif +#ifndef _RAKNET_SUPPORT_LogCommandParser +#define _RAKNET_SUPPORT_LogCommandParser 0 +#endif +#ifndef _RAKNET_SUPPORT_RakNetCommandParser +#define _RAKNET_SUPPORT_RakNetCommandParser 0 +#endif + +#if _RAKNET_SUPPORT_TCPInterface==0 +#ifndef _RAKNET_SUPPORT_EmailSender +#define _RAKNET_SUPPORT_EmailSender 0 +#endif +#ifndef _RAKNET_SUPPORT_HTTPConnection +#define _RAKNET_SUPPORT_HTTPConnection 0 +#endif +#ifndef _RAKNET_SUPPORT_PacketizedTCP +#define _RAKNET_SUPPORT_PacketizedTCP 0 +#endif +#endif + +#endif // __NATIVE_FEATURE_INCLDUES_H diff --git a/RakNet/Sources/NativeFeatureIncludesOverrides.h b/RakNet/Sources/NativeFeatureIncludesOverrides.h new file mode 100644 index 0000000..bbc7773 --- /dev/null +++ b/RakNet/Sources/NativeFeatureIncludesOverrides.h @@ -0,0 +1,43 @@ +// USER EDITABLE FILE + +#ifndef __NATIVE_FEATURE_INCLDUES_OVERRIDES_H +#define __NATIVE_FEATURE_INCLDUES_OVERRIDES_H + +// Uncomment below defines to exclude plugins that you do not want to build into the static library, or DLL +// These are not all the plugins, only those that are in the core library +// Other plugins are located in DependentExtensions + +// #define _RAKNET_SUPPORT_AutoRPC 0 +// #define _RAKNET_SUPPORT_ConnectionGraph 0 +// #define _RAKNET_SUPPORT_ConnectionGraph2 0 +// #define _RAKNET_SUPPORT_DirectoryDeltaTransfer 0 +// #define _RAKNET_SUPPORT_FileListTransfer 0 +// #define _RAKNET_SUPPORT_FullyConnectedMesh 0 +// #define _RAKNET_SUPPORT_FullyConnectedMesh2 0 +// #define _RAKNET_SUPPORT_LightweightDatabaseClient 0 +// #define _RAKNET_SUPPORT_LightweightDatabaseServer 0 +// #define _RAKNET_SUPPORT_MessageFilter 0 +// #define _RAKNET_SUPPORT_NatPunchthroughClient 0 +// #define _RAKNET_SUPPORT_NatPunchthroughServer 0 +// #define _RAKNET_SUPPORT_NatTypeDetectionClient 0 +// #define _RAKNET_SUPPORT_NatTypeDetectionServer 0 +// #define _RAKNET_SUPPORT_PacketLogger 0 +// #define _RAKNET_SUPPORT_ReadyEvent 0 +// #define _RAKNET_SUPPORT_ReplicaManager 0 +// #define _RAKNET_SUPPORT_ReplicaManager2 0 +// #define _RAKNET_SUPPORT_ReplicaManager3 0 +// #define _RAKNET_SUPPORT_Router 0 +// #define _RAKNET_SUPPORT_Router2 0 +// #define _RAKNET_SUPPORT_RPC4Plugin 0 +// #define _RAKNET_SUPPORT_TeamBalancer 0 +// #define _RAKNET_SUPPORT_UDPProxyClient 0 +// #define _RAKNET_SUPPORT_UDPProxyCoordinator 0 +// #define _RAKNET_SUPPORT_UDPProxyServer 0 +// #define _RAKNET_SUPPORT_ConsoleServer 0 +// #define _RAKNET_SUPPORT_RakNetTransport 0 +// #define _RAKNET_SUPPORT_TelnetTransport 0 +// #define _RAKNET_SUPPORT_TCPInterface 0 +// #define _RAKNET_SUPPORT_LogCommandParser 0 +// #define _RAKNET_SUPPORT_RakNetCommandParser 0 + +#endif \ No newline at end of file diff --git a/RakNet/Sources/NativeTypes.h b/RakNet/Sources/NativeTypes.h new file mode 100644 index 0000000..ddd1c92 --- /dev/null +++ b/RakNet/Sources/NativeTypes.h @@ -0,0 +1,25 @@ +#ifndef __NATIVE_TYPES_H +#define __NATIVE_TYPES_H + +#if (defined(__GNUC__) || defined(__GCCXML__) || defined(__SNC__)) +#include +#endif + +#if !defined(_STDINT_H) && !defined(_SN_STDINT_H) && !defined(_SYS_STDINT_H_) && !defined(_STDINT) && !defined(_MACHTYPES_H_) && !defined(_STDINT_H_) + typedef unsigned char uint8_t; + typedef unsigned short uint16_t; + typedef unsigned __int32 uint32_t; + typedef signed char int8_t; + typedef signed short int16_t; + typedef __int32 int32_t; + #if defined(_MSC_VER) && _MSC_VER < 1300 + typedef unsigned __int64 uint64_t; + typedef signed __int64 int64_t; + #else + typedef unsigned long long int uint64_t; + typedef signed long long int64_t; + #endif +#endif + + +#endif diff --git a/RakNet/Sources/NetworkIDManager.cpp b/RakNet/Sources/NetworkIDManager.cpp new file mode 100644 index 0000000..79726f6 --- /dev/null +++ b/RakNet/Sources/NetworkIDManager.cpp @@ -0,0 +1,195 @@ +/// \file +/// +/// This file is part of RakNet Copyright 2003 Jenkins Software LLC +/// +/// Usage of RakNet is subject to the appropriate license agreement. + + +#include "NetworkIDManager.h" +#include "NetworkIDObject.h" +#include "RakAssert.h" +#include // For memset + +bool NetworkIDNode::operator==( const NetworkIDNode& right ) const +{ + if ( networkID == right.networkID ) + return !0; + + return 0; +} + +bool NetworkIDNode::operator > ( const NetworkIDNode& right ) const +{ + if ( networkID > right.networkID ) + return !0; + + return 0; +} + +bool NetworkIDNode::operator < ( const NetworkIDNode& right ) const +{ + if ( networkID < right.networkID ) + return !0; + + return 0; +} + +NetworkIDNode::NetworkIDNode() +{ + object = 0; +} + +NetworkIDNode::NetworkIDNode( NetworkID _networkID, NetworkIDObject *_object ) +{ + networkID = _networkID; + object = _object; +} + + +//------------------------------------------------------------------------------------- +NetworkIDObject* NetworkIDManager::GET_BASE_OBJECT_FROM_ID( NetworkID x ) +{ + if ( x == UNASSIGNED_NETWORK_ID ) + return 0; + +#if defined(NETWORK_ID_USE_PTR_TABLE) || defined (NETWORK_ID_USE_HASH) + // You can't use this technique in peer to peer mode. Undefine NETWORK_ID_USE_PTR_TABLE in NetworkIDManager.h + RakAssert(NetworkID::IsPeerToPeerMode()==false); + return IDArray[x.localSystemAddress]; +#else + + NetworkIDNode *n = IDTree.GetPointerToNode( NetworkIDNode( ( x ), 0 ) ); + + if ( n ) + { + return n->object; + } + + return 0; + +#endif + +} +//------------------------------------------------------------------------------------- +void* NetworkIDManager::GET_OBJECT_FROM_ID( NetworkID x ) +{ +#if defined(NETWORK_ID_USE_PTR_TABLE) || defined (NETWORK_ID_USE_HASH) + if (x.localSystemAddress==65535) + return 0; + + // You can't use this technique in peer to peer mode. Undefine NETWORK_ID_USE_PTR_TABLE in NetworkIDManager.h + RakAssert(NetworkID::IsPeerToPeerMode()==false); + if (IDArray[x.localSystemAddress]) + { + if (IDArray[x.localSystemAddress]->GetParent()) + { + return IDArray[x.localSystemAddress]->GetParent(); + } + else + { +#ifdef _DEBUG + // If this assert hit then this object requires a call to SetParent and it never got one. + RakAssert(IDArray[x.localSystemAddress]->RequiresSetParent()==false); +#endif + return IDArray[x.localSystemAddress]; + } + } +#else + NetworkIDObject *object = (NetworkIDObject *) GET_BASE_OBJECT_FROM_ID( x ); + if (object) + { + if (object->GetParent()) + { + return object->GetParent(); + } + else + { +#ifdef _DEBUG + // If this assert hit then this object requires a call to SetParent and it never got one. + RakAssert(object->RequiresSetParent()==false); +#endif + return object; + } + } +#endif + + return 0; +} +//------------------------------------------------------------------------------------- +NetworkIDManager::NetworkIDManager(void) +{ + calledSetIsNetworkIDAuthority=false; + sharedNetworkID=0; + externalSystemAddress=UNASSIGNED_SYSTEM_ADDRESS; + guid=UNASSIGNED_RAKNET_GUID; + +#if defined(NETWORK_ID_USE_PTR_TABLE) || defined (NETWORK_ID_USE_HASH) + // Last element is reserved for UNASSIGNED_NETWORK_ID + IDArray = (NetworkIDObject**) rakMalloc_Ex(sizeof(NetworkIDObject*) * 65534, __FILE__, __LINE__); + memset(IDArray,0,sizeof(NetworkIDObject*)*65534); + // You can't use this technique in peer to peer mode. Undefine NETWORK_ID_USE_PTR_TABLE in NetworkIDManager.h + RakAssert(NetworkID::IsPeerToPeerMode()==false); +#endif +} +//------------------------------------------------------------------------------------- +NetworkIDManager::~NetworkIDManager(void) +{ +#if defined(NETWORK_ID_USE_PTR_TABLE) || defined (NETWORK_ID_USE_HASH) + rakFree_Ex(IDArray, __FILE__, __LINE__ ); +#endif +} +//------------------------------------------------------------------------------------- + +void NetworkIDManager::SetIsNetworkIDAuthority(bool isAuthority) +{ + isNetworkIDAuthority=isAuthority; + calledSetIsNetworkIDAuthority=true; +} + +//------------------------------------------------------------------------------------- + +bool NetworkIDManager::IsNetworkIDAuthority(void) const +{ + RakAssert(calledSetIsNetworkIDAuthority); + return isNetworkIDAuthority; +} + +//------------------------------------------------------------------------------------- + +unsigned short NetworkIDManager::GetSharedNetworkID( void ) +{ + RakAssert(calledSetIsNetworkIDAuthority); + return sharedNetworkID; +} + +//------------------------------------------------------------------------------------- + +void NetworkIDManager::SetSharedNetworkID( unsigned short i ) +{ + RakAssert(calledSetIsNetworkIDAuthority); + sharedNetworkID = i; +} + +//------------------------------------------------------------------------------------- +void NetworkIDManager::SetExternalSystemAddress(SystemAddress systemAddress) +{ + RakAssert(calledSetIsNetworkIDAuthority); + RakAssert(systemAddress!=UNASSIGNED_SYSTEM_ADDRESS); + externalSystemAddress=systemAddress; +} +//------------------------------------------------------------------------------------- +SystemAddress NetworkIDManager::GetExternalSystemAddress(void) +{ + RakAssert(calledSetIsNetworkIDAuthority); + return externalSystemAddress; +} +//------------------------------------------------------------------------------------- +void NetworkIDManager::SetGuid(RakNetGUID g) +{ + guid=g; +} +RakNetGUID NetworkIDManager::GetGuid(void) +{ + RakAssert(guid!=UNASSIGNED_RAKNET_GUID); + return guid; +} diff --git a/RakNet/Sources/NetworkIDManager.h b/RakNet/Sources/NetworkIDManager.h new file mode 100644 index 0000000..b055208 --- /dev/null +++ b/RakNet/Sources/NetworkIDManager.h @@ -0,0 +1,119 @@ +/// \file +/// +/// This file is part of RakNet Copyright 2003 Jenkins Software LLC +/// +/// Usage of RakNet is subject to the appropriate license agreement. + + +#ifndef __NETWORK_ID_MANAGER_H +#define __NETWORK_ID_MANAGER_H + +#include "DS_BinarySearchTree.h" +#include "RakNetTypes.h" +#include "Export.h" +#include "RakMemoryOverride.h" +#include "NetworkIDObject.h" + +/// \internal +/// \brief A node in the AVL tree that holds the mapping between NetworkID and pointers. +struct RAK_DLL_EXPORT NetworkIDNode +{ + NetworkID networkID; + NetworkIDObject *object; + NetworkIDNode(); + NetworkIDNode( NetworkID _networkID, NetworkIDObject *_object ); + bool operator==( const NetworkIDNode& right ) const; + bool operator > ( const NetworkIDNode& right ) const; + bool operator < ( const NetworkIDNode& right ) const; +}; + +/// This class is simply used to generate a unique number for a group of instances of NetworkIDObject +/// An instance of this class is required to use the ObjectID to pointer lookup system +/// You should have one instance of this class per game instance. +/// Call SetIsNetworkIDAuthority before using any functions of this class, or of NetworkIDObject +class RAK_DLL_EXPORT NetworkIDManager +{ +public: + NetworkIDManager(void); + virtual ~NetworkIDManager(void); + + /// For every group of systems, one system needs to be responsible for creating unique IDs for all objects created on all systems. + /// This way, systems can send that id in packets to refer to objects (you can't send pointers because the memory allocations may be different). + /// In a client/server environment, the system that creates unique IDs would be the server. + /// If you are using peer to peer or other situations where you don't have a single system to assign ids, + /// set this to true, and be sure NETWORK_ID_SUPPORTS_PEER_TO_PEER is defined in RakNetDefines.h + void SetIsNetworkIDAuthority(bool isAuthority); + + /// \return Returns what was passed to SetIsNetworkIDAuthority() + bool IsNetworkIDAuthority(void) const; + + /// \deprecated Use NetworkIDManager::SetGuid() and NetworkIDManager::GetGuid() instead + /// Necessary for peer to peer, as NetworkIDs are then composed of your external player Id (doesn't matter which, as long as unique) + /// plus the usual object ID number. + /// Get this from RakPeer::GetExternalSystemAddress) one time, the first time you make a connection. + /// \pre You must first call SetNetworkIDManager before using this function + /// \param[in] systemAddress Your external systemAddress + void SetExternalSystemAddress(SystemAddress systemAddress); + SystemAddress GetExternalSystemAddress(void); + + /// Necessary for peer to peer, as NetworkIDs are then composed of your external player Id (doesn't matter which, as long as unique) + /// plus the usual object ID number. + /// Get this from RakPeer::GetGuidFromSystemAddress(UNASSIGNED_SYSTEM_ADDRESS) one time, the first time you make a connection. + /// \pre You must first call SetNetworkIDManager before using this function + void SetGuid(RakNetGUID g); + RakNetGUID GetGuid(void); + + /// These function is only meant to be used when saving games as you + /// should save the HIGHEST value staticItemID has achieved upon save + /// and reload it upon load. Save AFTER you've created all the items + /// derived from this class you are going to create. + /// \return the HIGHEST Object Id currently used + unsigned short GetSharedNetworkID( void ); + + /// These function is only meant to be used when loading games. Load + /// BEFORE you create any new objects that are not SetIDed based on + /// the save data. + /// \param[in] i the highest number of NetworkIDObject reached. + void SetSharedNetworkID( unsigned short i ); + + /// If you use a parent, returns this instance rather than the parent object. + /// \pre You must first call SetNetworkIDManager before using this function + NetworkIDObject* GET_BASE_OBJECT_FROM_ID( NetworkID x ); + + /// Returns the parent object, or this instance if you don't use a parent. + /// \deprecated, use the template form. This form requires that NetworkIDObject is the basemost derived class + /// \pre You must first call SetNetworkIDManager before using this function + void* GET_OBJECT_FROM_ID( NetworkID x ); + + /// Returns the parent object, or this instance if you don't use a parent. + /// Supports NetworkIDObject anywhere in the inheritance hierarchy + /// \pre You must first call SetNetworkIDManager before using this function + template + returnType GET_OBJECT_FROM_ID(NetworkID x) { + NetworkIDObject *nio = GET_BASE_OBJECT_FROM_ID(x); + if (nio==0) + return 0; + if (nio->GetParent()) + return (returnType) nio->GetParent(); + return (returnType) nio; + } + +protected: + RakNetGUID guid; + // deprecated - use guid instead. This has the problem that it can be different between the LAN and the internet, or duplicated on different LANs + SystemAddress externalSystemAddress; + unsigned short sharedNetworkID; + bool isNetworkIDAuthority; + bool calledSetIsNetworkIDAuthority; + friend class NetworkIDObject; + + +#if ! defined(NETWORK_ID_USE_PTR_TABLE) || defined(NETWORK_ID_USE_HASH) + /// This AVL tree holds the pointer to NetworkID mappings + DataStructures::AVLBalancedBinarySearchTree IDTree; +#else + NetworkIDObject **IDArray; +#endif +}; + +#endif diff --git a/RakNet/Sources/NetworkIDObject.cpp b/RakNet/Sources/NetworkIDObject.cpp new file mode 100644 index 0000000..173ee9e --- /dev/null +++ b/RakNet/Sources/NetworkIDObject.cpp @@ -0,0 +1,234 @@ +/// \file +/// +/// This file is part of RakNet Copyright 2003 Jenkins Software LLC +/// +/// Usage of RakNet is subject to the appropriate license agreement. + + +#include "NetworkIDObject.h" +#include "NetworkIDManager.h" +#include "RakAssert.h" + +#include "RakAlloca.h" + +uint32_t NetworkIDObject::nextAllocationNumber=0; + +////////////////////////////////////////////////////////////////////// +// Construction/Destruction +////////////////////////////////////////////////////////////////////// + +NetworkIDObject::NetworkIDObject() +{ + callGenerationCode=true; + networkID=UNASSIGNED_NETWORK_ID; + parent=0; + networkIDManager=0; + allocationNumber=nextAllocationNumber++; +} + +//------------------------------------------------------------------------------------- + + +NetworkIDObject::~NetworkIDObject() +{ + if (networkID!=UNASSIGNED_NETWORK_ID) + { +#if defined(NETWORK_ID_USE_PTR_TABLE) || defined (NETWORK_ID_USE_HASH) + void *obj = networkIDManager->IDArray[networkID.localSystemAddress]; + if (obj==this) + networkIDManager->IDArray[networkID.localSystemAddress]=0; +#else + NetworkIDNode * object = networkIDManager->IDTree.GetPointerToNode( NetworkIDNode( ( networkID ), 0 ) ); + if ( object && object->object == this ) + networkIDManager->IDTree.Del( NetworkIDNode( object->networkID, 0 ) ); +#endif + } +} + +////////////////////////////////////////////////////////////////////// +// Public Methods +////////////////////////////////////////////////////////////////////// + +void NetworkIDObject::SetNetworkIDManager( NetworkIDManager *manager) +{ + networkIDManager = manager; +} + +//------------------------------------------------------------------------------------- + +NetworkIDManager * NetworkIDObject::GetNetworkIDManager( void ) +{ + return networkIDManager; +} + +//------------------------------------------------------------------------------------- + +NetworkID NetworkIDObject::GetNetworkID( void ) +{ + RakAssert(networkIDManager); + if (callGenerationCode && networkIDManager->IsNetworkIDAuthority()) + { + GenerateID(); + RakAssert(networkID!=UNASSIGNED_NETWORK_ID); + callGenerationCode=false; + } + + return networkID; +}; +//------------------------------------------------------------------------------------- + +bool NetworkIDObject::RequiresSetParent(void) const +{ + return false; +} + +//------------------------------------------------------------------------------------- + +void NetworkIDObject::SetNetworkID( NetworkID id ) +{ + callGenerationCode=false; + + if ( id == UNASSIGNED_NETWORK_ID ) + { + // puts("Warning: NetworkIDObject passed UNASSIGNED_NETWORK_ID. SetID ignored"); + return ; + } + + if ( networkID == id ) + { + // RAKNET_DEBUG_PRINTF("NetworkIDObject passed %i which already exists in the tree. SetID ignored", id); + return ; + } + + RakAssert(networkIDManager); + +#if defined(NETWORK_ID_USE_PTR_TABLE) || defined (NETWORK_ID_USE_HASH) + networkID = id; + networkIDManager->IDArray[id.localSystemAddress]=this; +#else + + NetworkIDNode* collision = networkIDManager->IDTree.GetPointerToNode( NetworkIDNode( ( id ), 0 ) ); + + if ( collision ) // Tree should have only unique values. The new value is already in use. + { + //RAKNET_DEBUG_PRINTF("Warning: NetworkIDObject::SetID passed %i, which has an existing node in the tree. Old node removed, which will cause the item pointed to to be inaccessible to the network", id); + networkIDManager->IDTree.Del( NetworkIDNode( collision->networkID, collision->object ) ); + } + + if ( networkID == UNASSIGNED_NETWORK_ID ) // Object has not had an ID assigned so does not already exist in the tree + { + networkID = id; + networkIDManager->IDTree.Add( NetworkIDNode( networkID, this ) ); + } + else // Object already exists in the tree and has an assigned ID + { + networkIDManager->IDTree.Del( NetworkIDNode( networkID, this ) ); // Delete the node with whatever ID the existing object is using + networkID = id; + networkIDManager->IDTree.Add( NetworkIDNode( networkID, this ) ); + } +#endif +} + +//------------------------------------------------------------------------------------- +void NetworkIDObject::SetParent( void *_parent ) +{ + parent=_parent; + +#ifdef _DEBUG + if (networkIDManager) + { + // Avoid duplicate parents in the tree + unsigned i; +#if defined(NETWORK_ID_USE_PTR_TABLE) || defined (NETWORK_ID_USE_HASH) + for (i=0; i < 65534; i++) + { + NetworkIDObject *nio = networkIDManager->IDArray[i]; + RakAssert(nio==0 || nio->GetParent()!=_parent); + } +#else + unsigned size = networkIDManager->IDTree.Size(); + NetworkIDNode *nodeArray; + + bool usedAlloca=false; + #if !defined(_XBOX) && !defined(X360) + if (sizeof(NetworkIDNode) * size < MAX_ALLOCA_STACK_ALLOCATION) + { + nodeArray = (NetworkIDNode*) alloca(sizeof(NetworkIDNode) * size); + usedAlloca=true; + } + else + #endif + nodeArray = RakNet::OP_NEW_ARRAY(size, __FILE__, __LINE__ ); + + networkIDManager->IDTree.DisplayBreadthFirstSearch( nodeArray ); + for (i=0; i < size; i++) + { + // If this assert hits then this _parent is already in the tree. Classes instance should never contain more than one NetworkIDObject + RakAssert(nodeArray->object->GetParent()!=parent); + } + + if (usedAlloca==false) + RakNet::OP_DELETE_ARRAY(nodeArray, __FILE__, __LINE__); +#endif + } +#endif +} +//------------------------------------------------------------------------------------- +void* NetworkIDObject::GetParent( void ) const +{ + return parent; +} +//------------------------------------------------------------------------------------- +uint32_t NetworkIDObject::GetAllocationNumber(void) const +{ + return allocationNumber; +} +//------------------------------------------------------------------------------------- +void NetworkIDObject::GenerateID(void) +{ + RakAssert(networkIDManager->IsNetworkIDAuthority()); + +#if defined(NETWORK_ID_USE_PTR_TABLE) || defined (NETWORK_ID_USE_HASH) + int count = 65535; + (void) count; + do + { + RakAssert(count-->0); + networkID.localSystemAddress=networkIDManager->sharedNetworkID++; + } while(networkIDManager->IDArray[networkID.localSystemAddress]!=0); + networkIDManager->IDArray[networkID.localSystemAddress]=this; +#else + // If you want more than 65535 network objects, change the type of networkID + RakAssert(networkIDManager->IDTree.Size() < 65535); + + NetworkIDNode* collision; + do + { + networkID.localSystemAddress=networkIDManager->sharedNetworkID++; +#ifdef NETWORK_ID_SUPPORTS_PEER_TO_PEER + if (NetworkID::IsPeerToPeerMode()) + { + if (networkIDManager->GetGuid()==UNASSIGNED_RAKNET_GUID) + { + // If this assert hits you forgot to call SetGUID and SetExternalSystemAddress + RakAssert(networkIDManager->externalSystemAddress!=UNASSIGNED_SYSTEM_ADDRESS); + networkID.systemAddress=networkIDManager->externalSystemAddress; + } + else + { + networkID.guid=networkIDManager->GetGuid(); + networkID.systemAddress=networkIDManager->externalSystemAddress; + } + } +#endif + collision = networkIDManager->IDTree.GetPointerToNode( NetworkIDNode( ( networkID ), 0 ) ); + } + while ( collision ); + + networkIDManager->IDTree.Add( NetworkIDNode( networkID, this ) ); +#endif +} + +////////////////////////////////////////////////////////////////////// +// EOF +////////////////////////////////////////////////////////////////////// diff --git a/RakNet/Sources/NetworkIDObject.h b/RakNet/Sources/NetworkIDObject.h new file mode 100644 index 0000000..a79f8d4 --- /dev/null +++ b/RakNet/Sources/NetworkIDObject.h @@ -0,0 +1,88 @@ +/// \file +/// \brief A class you can derive from to make it easier to represent every networked object with an integer. This way you can refer to objects over the network. +/// +/// This file is part of RakNet Copyright 2003 Jenkins Software LLC +/// +/// Usage of RakNet is subject to the appropriate license agreement. + + +#if !defined(__NETWORK_ID_GENERATOR) +#define __NETWORK_ID_GENERATOR + +#include "RakNetTypes.h" +#include "RakMemoryOverride.h" +#include "Export.h" + +class NetworkIDManager; + +/// \brief Unique shared ids for each object instance +/// \details A class you can derive from to make it easier to represent every networked object with an integer. This way you can refer to objects over the network. +/// One system should return true for IsNetworkIDAuthority() and the rest should return false. When an object needs to be created, have the the one system create the object. +/// Then have that system send a message to all other systems, and include the value returned from GetNetworkID() in that packet. All other systems should then create the same +/// class of object, and call SetNetworkID() on that class with the NetworkID in the packet. +/// \see the manual for more information on this. +class RAK_DLL_EXPORT NetworkIDObject +{ +public: + // Constructor. NetworkIDs, if IsNetworkIDAuthority() is true, are created here. + NetworkIDObject(); + + // Destructor. Used NetworkIDs, if any, are freed here. + virtual ~NetworkIDObject(); + + /// Sets the manager class from which to request unique network IDs + /// Unlike previous versions, the NetworkIDObject relies on a manager class to provide IDs, rather than using statics, + /// So you can have more than one set of IDs on the same system. + virtual void SetNetworkIDManager( NetworkIDManager *manager); + + /// Returns what was passed to SetNetworkIDManager + virtual NetworkIDManager * GetNetworkIDManager( void ); + + /// Returns the NetworkID that you can use to refer to this object over the network. + /// \pre You must first call SetNetworkIDManager before using this function + /// \retval UNASSIGNED_NETWORK_ID UNASSIGNED_NETWORK_ID is returned IsNetworkIDAuthority() is false and SetNetworkID() was not previously called. This is also returned if you call this function in the constructor. + /// \retval 0-65534 Any other value is a valid NetworkID. NetworkIDs start at 0 and go to 65534, wrapping at that point. + virtual NetworkID GetNetworkID( void ); + + /// Sets the NetworkID for this instance. Usually this is called by the clients and determined from the servers. However, if you save multiplayer games you would likely use + /// This on load as well. + virtual void SetNetworkID( NetworkID id ); + + /// Your class does not have to derive from NetworkIDObject, although that is the easiest way to implement this. + /// If you want this to be a member object of another class, rather than inherit, then call SetParent() with a pointer to the parent class instance. + /// GET_OBJECT_FROM_ID will then return the parent rather than this instance. + virtual void SetParent( void *_parent ); + + /// Return what was passed to SetParent + /// \return The value passed to SetParent, or 0 if it was never called. + virtual void* GetParent( void ) const; + + /// Overload this function and return true if you require that SetParent is called before this object is used. + /// This is a safety check you should do this if you want this to be + /// a member object of another class rather than derive from this class. + virtual bool RequiresSetParent(void) const; + + /// Used so I can compare pointers in the ReplicaManager + uint32_t GetAllocationNumber(void) const; + +protected: + /// The network ID of this object + NetworkID networkID; + + /// The parent set by SetParent() + void *parent; + + /// Used so I can compare pointers in the ReplicaManager + static uint32_t nextAllocationNumber; + uint32_t allocationNumber; + + /// Internal function to generate an ID when needed. This is deferred until needed and is not called from the constructor. + void GenerateID(void); + + /// This is crap but is necessary because virtual functions don't work in the constructor + bool callGenerationCode; + + NetworkIDManager *networkIDManager; +}; + +#endif diff --git a/RakNet/Sources/PacketConsoleLogger.cpp b/RakNet/Sources/PacketConsoleLogger.cpp new file mode 100644 index 0000000..b5a1ae8 --- /dev/null +++ b/RakNet/Sources/PacketConsoleLogger.cpp @@ -0,0 +1,25 @@ +#include "NativeFeatureIncludes.h" +#if _RAKNET_SUPPORT_PacketLogger==1 + +#include "PacketConsoleLogger.h" +#include "LogCommandParser.h" +#include + +PacketConsoleLogger::PacketConsoleLogger() +{ + logCommandParser=0; +} + +void PacketConsoleLogger::SetLogCommandParser(LogCommandParser *lcp) +{ + logCommandParser=lcp; + if (logCommandParser) + logCommandParser->AddChannel("PacketConsoleLogger"); +} +void PacketConsoleLogger::WriteLog(const char *str) +{ + if (logCommandParser) + logCommandParser->WriteLog("PacketConsoleLogger", str); +} + +#endif // _RAKNET_SUPPORT_* diff --git a/RakNet/Sources/PacketConsoleLogger.h b/RakNet/Sources/PacketConsoleLogger.h new file mode 100644 index 0000000..f685426 --- /dev/null +++ b/RakNet/Sources/PacketConsoleLogger.h @@ -0,0 +1,32 @@ +/// \file +/// \brief This will write all incoming and outgoing network messages to the log command parser, which can be accessed through Telnet +/// +/// This file is part of RakNet Copyright 2003 Jenkins Software LLC +/// +/// Usage of RakNet is subject to the appropriate license agreement. + +#include "NativeFeatureIncludes.h" +#if _RAKNET_SUPPORT_PacketLogger==1 + +#ifndef __PACKET_CONSOLE_LOGGER_H_ +#define __PACKET_CONSOLE_LOGGER_H_ + +#include "PacketLogger.h" +class LogCommandParser; + +/// \ingroup PACKETLOGGER_GROUP +/// \brief Packetlogger that logs to a remote command console +class RAK_DLL_EXPORT PacketConsoleLogger : public PacketLogger +{ +public: + PacketConsoleLogger(); + // Writes to the command parser used for logging, which is accessed through a secondary communication layer (such as Telnet or RakNet) - See ConsoleServer.h + virtual void SetLogCommandParser(LogCommandParser *lcp); + virtual void WriteLog(const char *str); +protected: + LogCommandParser *logCommandParser; +}; + +#endif + +#endif // _RAKNET_SUPPORT_* diff --git a/RakNet/Sources/PacketFileLogger.cpp b/RakNet/Sources/PacketFileLogger.cpp new file mode 100644 index 0000000..86f0be0 --- /dev/null +++ b/RakNet/Sources/PacketFileLogger.cpp @@ -0,0 +1,44 @@ +#include "NativeFeatureIncludes.h" +#if _RAKNET_SUPPORT_PacketLogger==1 + +#include "PacketFileLogger.h" +#include "GetTime.h" + +PacketFileLogger::PacketFileLogger() +{ + packetLogFile=0; +} +PacketFileLogger::~PacketFileLogger() +{ + if (packetLogFile) + { + fflush(packetLogFile); + fclose(packetLogFile); + } +} +void PacketFileLogger::StartLog(const char *filenamePrefix) +{ + // Open file for writing + char filename[256]; + if (filenamePrefix) + sprintf(filename, "%s_%i.csv", filenamePrefix, (int) RakNet::GetTime()); + else + sprintf(filename, "PacketLog_%i.csv", (int) RakNet::GetTime()); + packetLogFile = fopen(filename, "wt"); + LogHeader(); + if (packetLogFile) + { + fflush(packetLogFile); + } +} + +void PacketFileLogger::WriteLog(const char *str) +{ + if (packetLogFile) + { + fprintf(packetLogFile, "%s\n", str); + fflush(packetLogFile); + } +} + +#endif // _RAKNET_SUPPORT_* diff --git a/RakNet/Sources/PacketFileLogger.h b/RakNet/Sources/PacketFileLogger.h new file mode 100644 index 0000000..e572130 --- /dev/null +++ b/RakNet/Sources/PacketFileLogger.h @@ -0,0 +1,32 @@ +/// \file +/// \brief This will write all incoming and outgoing network messages to a file +/// +/// This file is part of RakNet Copyright 2003 Jenkins Software LLC +/// +/// Usage of RakNet is subject to the appropriate license agreement. + +#include "NativeFeatureIncludes.h" +#if _RAKNET_SUPPORT_PacketLogger==1 + +#ifndef __PACKET_FILE_LOGGER_H_ +#define __PACKET_FILE_LOGGER_H_ + +#include "PacketLogger.h" +#include + +/// \ingroup PACKETLOGGER_GROUP +/// \brief Packetlogger that outputs to a file +class RAK_DLL_EXPORT PacketFileLogger : public PacketLogger +{ +public: + PacketFileLogger(); + virtual ~PacketFileLogger(); + void StartLog(const char *filenamePrefix); + virtual void WriteLog(const char *str); +protected: + FILE *packetLogFile; +}; + +#endif + +#endif // _RAKNET_SUPPORT_* diff --git a/RakNet/Sources/PacketLogger.cpp b/RakNet/Sources/PacketLogger.cpp new file mode 100644 index 0000000..88c2b79 --- /dev/null +++ b/RakNet/Sources/PacketLogger.cpp @@ -0,0 +1,441 @@ +#include "NativeFeatureIncludes.h" +#if _RAKNET_SUPPORT_PacketLogger==1 + +#include "PacketLogger.h" +#include "BitStream.h" +#include "DS_List.h" +#include "InternalPacket.h" +#include "RakPeerInterface.h" +#include "MessageIdentifiers.h" +#include "StringCompressor.h" +#include "RPCMap.h" +#include "GetTime.h" +#include +#include +#include +#include "Itoa.h" +#include +#include "SocketIncludes.h" +#include "gettimeofday.h" + +#ifdef _MSC_VER +#pragma warning( push ) +#endif + +PacketLogger::PacketLogger() +{ + printId=true; + printAcks=true; + prefix[0]=0; + suffix[0]=0; + logDirectMessages=true; +} +PacketLogger::~PacketLogger() +{ +} +void PacketLogger::FormatLine( +char* into, const char* dir, const char* type, unsigned int packet, unsigned int frame, unsigned char id +, const BitSize_t bitLen, unsigned long long time, const SystemAddress& local, const SystemAddress& remote, +unsigned int splitPacketId, unsigned int splitPacketIndex, unsigned int splitPacketCount, unsigned int orderingIndex) +{ + char numericID[16]; + const char* idToPrint = NULL; + if(printId) + { + if (splitPacketCount>0) + idToPrint="(SPLIT PACKET)"; + else + idToPrint = IDTOString(id); + } + // If printId is false, idToPrint will be NULL, as it will + // in the case of an unrecognized id. Testing printId for false + // would just be redundant. + if(idToPrint == NULL) + { + sprintf(numericID, "%5u", id); + idToPrint = numericID; + } + + FormatLine(into, dir, type, packet, frame, idToPrint, bitLen, time, local, remote,splitPacketId,splitPacketIndex,splitPacketCount, orderingIndex); +} + +void PacketLogger::FormatLine( +char* into, const char* dir, const char* type, unsigned int packet, unsigned int frame, const char* idToPrint +, const BitSize_t bitLen, unsigned long long time, const SystemAddress& local, const SystemAddress& remote, +unsigned int splitPacketId, unsigned int splitPacketIndex, unsigned int splitPacketCount, unsigned int orderingIndex) +{ + char str1[64], str2[62]; + local.ToString(true, str1); + remote.ToString(true, str2); + char localtime[128]; + GetLocalTime(localtime); + + sprintf(into, "%s,%s%s,%s,%5u,%5u,%s,%u,%" PRINTF_64_BIT_MODIFIER "u,%s,%s,%i,%i,%i,%i,%s," + , localtime + , prefix + , dir + , type + , packet + , frame + , idToPrint + , bitLen + , time + , str1 + , str2 + , splitPacketId + , splitPacketIndex + , splitPacketCount + , orderingIndex + , suffix + ); +} +void PacketLogger::OnDirectSocketSend(const char *data, const BitSize_t bitsUsed, SystemAddress remoteSystemAddress) +{ + if (logDirectMessages==false) + return; + + char str[256]; + FormatLine(str, "Snd", "Raw", 0, 0, data[0], bitsUsed, RakNet::GetTime(), rakPeerInterface->GetExternalID(remoteSystemAddress), remoteSystemAddress, (unsigned int)-1,(unsigned int)-1,(unsigned int)-1,(unsigned int)-1); + AddToLog(str); +} + +void PacketLogger::LogHeader(void) +{ + // Last 5 are splitpacket id, split packet index, split packet count, ordering index, suffix + AddToLog("Clock,S|R,Typ,Pckt#,Frm #,PktID,BitLn,Time ,Local IP:Port ,RemoteIP:Port,SPID,SPIN,SPCO,OI,Suffix,Miscellaneous\n"); +} +void PacketLogger::OnDirectSocketReceive(const char *data, const BitSize_t bitsUsed, SystemAddress remoteSystemAddress) +{ + if (logDirectMessages==false) + return; + + char str[256]; + FormatLine(str, "Rcv", "Raw", 0, 0, data[0], bitsUsed, RakNet::GetTime(), rakPeerInterface->GetExternalID(remoteSystemAddress), remoteSystemAddress,(unsigned int)-1,(unsigned int)-1,(unsigned int)-1,(unsigned int)-1); + AddToLog(str); +} +void PacketLogger::OnReliabilityLayerPacketError(const char *errorMessage, const BitSize_t bitsUsed, SystemAddress remoteSystemAddress) +{ + char str[1024]; + FormatLine(str, "RcvErr", errorMessage, 0, 0, "", bitsUsed, RakNet::GetTime(), rakPeerInterface->GetExternalID(remoteSystemAddress), remoteSystemAddress,(unsigned int)-1,(unsigned int)-1,(unsigned int)-1,(unsigned int)-1); + AddToLog(str); +} +void PacketLogger::OnAck(unsigned int messageNumber, SystemAddress remoteSystemAddress, RakNetTime time) +{ + char str[256]; + char str1[64], str2[62]; + SystemAddress localSystemAddress = rakPeerInterface->GetExternalID(remoteSystemAddress); + localSystemAddress.ToString(true, str1); + remoteSystemAddress.ToString(true, str2); + char localtime[128]; + GetLocalTime(localtime); + + sprintf(str, "%s,Rcv,Ack,%i,,,,%" PRINTF_64_BIT_MODIFIER "u,%s,%s,,,,,," + , localtime + , messageNumber + , (unsigned long long) time + , str1 + , str2 + ); + AddToLog(str); +} +void PacketLogger::OnPushBackPacket(const char *data, const BitSize_t bitsUsed, SystemAddress remoteSystemAddress) +{ + char str[256]; + char str1[64], str2[62]; + SystemAddress localSystemAddress = rakPeerInterface->GetExternalID(remoteSystemAddress); + localSystemAddress.ToString(true, str1); + remoteSystemAddress.ToString(true, str2); + RakNetTime time = RakNet::GetTime(); + char localtime[128]; + GetLocalTime(localtime); + + sprintf(str, "%s,Lcl,PBP,,,%s,%i,%" PRINTF_64_BIT_MODIFIER "u,%s,%s,,,,,," + , localtime + , BaseIDTOString(data[0]) + , bitsUsed + , (unsigned long long) time + , str1 + , str2 + ); + AddToLog(str); +} +void PacketLogger::OnInternalPacket(InternalPacket *internalPacket, unsigned frameNumber, SystemAddress remoteSystemAddress, RakNetTime time, int isSend) +{ + char str[256]; + const char *sendTypes[] = + { + "Rcv", + "Snd", + "Err1", + "Err2", + "Err3", + "Err4", + "Err5", + "Err6", + }; + const char *sendType = sendTypes[isSend]; + SystemAddress localSystemAddress = rakPeerInterface->GetExternalID(remoteSystemAddress); + + if (internalPacket->data[0]==ID_TIMESTAMP && internalPacket->data[sizeof(unsigned char)+sizeof(RakNetTime)]!=ID_RPC) + { + FormatLine(str, sendType, "Tms", internalPacket->reliableMessageNumber, frameNumber, internalPacket->data[1+sizeof(int)], internalPacket->dataBitLength, (unsigned long long)time, localSystemAddress, remoteSystemAddress, internalPacket->splitPacketId, internalPacket->splitPacketIndex, internalPacket->splitPacketCount, internalPacket->orderingIndex); + } + else if (internalPacket->data[0]==ID_RPC || (internalPacket->dataBitLength>(sizeof(unsigned char)+sizeof(RakNetTime))*8 && internalPacket->data[0]==ID_TIMESTAMP && internalPacket->data[sizeof(unsigned char)+sizeof(RakNetTime)]==ID_RPC)) + { + const char *uniqueIdentifier = rakPeerInterface->GetRPCString((const char*) internalPacket->data, internalPacket->dataBitLength, isSend==1 ? remoteSystemAddress : UNASSIGNED_SYSTEM_ADDRESS); + + if (internalPacket->data[0]==ID_TIMESTAMP) + FormatLine(str, sendType, "RpT", internalPacket->reliableMessageNumber, frameNumber, uniqueIdentifier, internalPacket->dataBitLength, (unsigned long long)time, localSystemAddress, remoteSystemAddress, internalPacket->splitPacketId, internalPacket->splitPacketIndex, internalPacket->splitPacketCount, internalPacket->orderingIndex); + else + FormatLine(str, sendType, "Rpc", internalPacket->reliableMessageNumber, frameNumber, uniqueIdentifier, internalPacket->dataBitLength, (unsigned long long)time, localSystemAddress, remoteSystemAddress, internalPacket->splitPacketId, internalPacket->splitPacketIndex, internalPacket->splitPacketCount, internalPacket->orderingIndex); + } + else + { + FormatLine(str, sendType, "Nrm", internalPacket->reliableMessageNumber, frameNumber, internalPacket->data[0], internalPacket->dataBitLength, (unsigned long long)time, localSystemAddress, remoteSystemAddress, internalPacket->splitPacketId, internalPacket->splitPacketIndex, internalPacket->splitPacketCount, internalPacket->orderingIndex); + } + + AddToLog(str); +} +void PacketLogger::AddToLog(const char *str) +{ + WriteLog(str); +} +void PacketLogger::WriteLog(const char *str) +{ + RAKNET_DEBUG_PRINTF("%s\n", str); +} +void PacketLogger::WriteMiscellaneous(const char *type, const char *msg) +{ + char str[1024]; + char str1[64]; + SystemAddress localSystemAddress = rakPeerInterface->GetInternalID(); + localSystemAddress.ToString(true, str1); + RakNetTime time = RakNet::GetTime(); + char localtime[128]; + GetLocalTime(localtime); + + sprintf(str, "%s,Lcl,%s,,,,,%" PRINTF_64_BIT_MODIFIER "u,%s,,,,,,,%s" + , localtime + , type + , (unsigned long long) time + , str1 + , msg + ); + + AddToLog(msg); +} +void PacketLogger::SetPrintID(bool print) +{ + printId=print; +} +void PacketLogger::SetPrintAcks(bool print) +{ + printAcks=print; +} +const char* PacketLogger::BaseIDTOString(unsigned char Id) +{ + if (Id >= ID_USER_PACKET_ENUM) + return 0; + + const char *IDTable[((int)ID_USER_PACKET_ENUM)+1]= + { + "ID_INTERNAL_PING", + "ID_PING", + "ID_PING_OPEN_CONNECTIONS", + "ID_CONNECTED_PONG", + "ID_CONNECTION_REQUEST", + "ID_SECURED_CONNECTION_RESPONSE", + "ID_SECURED_CONNECTION_CONFIRMATION", + "ID_RPC_MAPPING", + "ID_DETECT_LOST_CONNECTIONS", + "ID_OPEN_CONNECTION_REQUEST", + "ID_OPEN_CONNECTION_REPLY", + "ID_RPC", + "ID_RPC_REPLY", + "ID_OUT_OF_BAND_INTERNAL", + "ID_CONNECTION_REQUEST_ACCEPTED", + "ID_CONNECTION_ATTEMPT_FAILED", + "ID_ALREADY_CONNECTED", + "ID_NEW_INCOMING_CONNECTION", + "ID_NO_FREE_INCOMING_CONNECTIONS", + "ID_DISCONNECTION_NOTIFICATION", + "ID_CONNECTION_LOST", + "ID_RSA_PUBLIC_KEY_MISMATCH", + "ID_CONNECTION_BANNED", + "ID_INVALID_PASSWORD", + "ID_INCOMPATIBLE_PROTOCOL_VERSION", + "ID_IP_RECENTLY_CONNECTED", + "ID_MODIFIED_PACKET", + "ID_TIMESTAMP", + "ID_PONG", + "ID_ADVERTISE_SYSTEM", + "ID_REMOTE_DISCONNECTION_NOTIFICATION", + "ID_REMOTE_CONNECTION_LOST", + "ID_REMOTE_NEW_INCOMING_CONNECTION", + "ID_DOWNLOAD_PROGRESS", + "ID_FILE_LIST_TRANSFER_HEADER", + "ID_FILE_LIST_TRANSFER_FILE", + "ID_FILE_LIST_REFERENCE_PUSH_ACK", + "ID_DDT_DOWNLOAD_REQUEST", + "ID_TRANSPORT_STRING", + "ID_REPLICA_MANAGER_CONSTRUCTION", + "ID_REPLICA_MANAGER_DESTRUCTION", + "ID_REPLICA_MANAGER_SCOPE_CHANGE", + "ID_REPLICA_MANAGER_SERIALIZE", + "ID_REPLICA_MANAGER_DOWNLOAD_STARTED", + "ID_REPLICA_MANAGER_DOWNLOAD_COMPLETE", + "ID_CONNECTION_GRAPH_REQUEST", + "ID_CONNECTION_GRAPH_REPLY", + "ID_CONNECTION_GRAPH_UPDATE", + "ID_CONNECTION_GRAPH_NEW_CONNECTION", + "ID_CONNECTION_GRAPH_CONNECTION_LOST", + "ID_CONNECTION_GRAPH_DISCONNECTION_NOTIFICATION", + "ID_ROUTE_AND_MULTICAST", + "ID_RAKVOICE_OPEN_CHANNEL_REQUEST", + "ID_RAKVOICE_OPEN_CHANNEL_REPLY", + "ID_RAKVOICE_CLOSE_CHANNEL", + "ID_RAKVOICE_DATA", + "ID_AUTOPATCHER_GET_CHANGELIST_SINCE_DATE", + "ID_AUTOPATCHER_CREATION_LIST", + "ID_AUTOPATCHER_DELETION_LIST", + "ID_AUTOPATCHER_GET_PATCH", + "ID_AUTOPATCHER_PATCH_LIST", + "ID_AUTOPATCHER_REPOSITORY_FATAL_ERROR", + "ID_AUTOPATCHER_FINISHED_INTERNAL", + "ID_AUTOPATCHER_FINISHED", + "ID_AUTOPATCHER_RESTART_APPLICATION", + "ID_NAT_PUNCHTHROUGH_REQUEST", + "ID_NAT_CONNECT_AT_TIME", + "ID_NAT_GET_MOST_RECENT_PORT", + "ID_NAT_CLIENT_READY", + "ID_NAT_TARGET_NOT_CONNECTED", + "ID_NAT_TARGET_UNRESPONSIVE", + "ID_NAT_CONNECTION_TO_TARGET_LOST", + "ID_NAT_ALREADY_IN_PROGRESS", + "ID_NAT_PUNCHTHROUGH_FAILED", + "ID_NAT_PUNCHTHROUGH_SUCCEEDED", + "ID_DATABASE_QUERY_REQUEST", + "ID_DATABASE_UPDATE_ROW", + "ID_DATABASE_REMOVE_ROW", + "ID_DATABASE_QUERY_REPLY", + "ID_DATABASE_UNKNOWN_TABLE", + "ID_DATABASE_INCORRECT_PASSWORD", + "ID_READY_EVENT_SET", + "ID_READY_EVENT_UNSET", + "ID_READY_EVENT_ALL_SET", + "ID_READY_EVENT_QUERY", + "ID_LOBBY_GENERAL", + "ID_AUTO_RPC_CALL", + "ID_AUTO_RPC_REMOTE_INDEX", + "ID_AUTO_RPC_UNKNOWN_REMOTE_INDEX", + "ID_RPC_REMOTE_ERROR", + "ID_FILE_LIST_REFERENCE_PUSH", + "ID_READY_EVENT_FORCE_ALL_SET", + "ID_ROOMS_EXECUTE_FUNC", + "ID_ROOMS_LOGON_STATUS", + "ID_ROOMS_HANDLE_CHANGE", + "ID_LOBBY2_SEND_MESSAGE", + "ID_LOBBY2_SERVER_ERROR", + "ID_FCM2_NEW_HOST", + "ID_FCM2_REQUEST_FCMGUID", + "ID_FCM2_RESPOND_CONNECTION_COUNT", + "ID_FCM2_INFORM_FCMGUID", + "ID_UDP_PROXY_GENERAL", + "ID_SQLite3_EXEC", + "ID_SQLite3_UNKNOWN_DB", + "ID_REPLICA_MANAGER_3_SERIALIZE_CONSTRUCTION_EXISTING", + "ID_REPLICA_MANAGER_3_LOCAL_CONSTRUCTION_REJECTED", + "ID_REPLICA_MANAGER_3_LOCAL_CONSTRUCTION_ACCEPTED", + "ID_NAT_TYPE_DETECTION_REQUEST", + "ID_NAT_TYPE_DETECTION_RESULT", + "ID_SQLLITE_LOGGER", + "ID_ROUTER_2_INTERNAL", + "ID_ROUTER_2_FORWARDING_NO_PATH", + "ID_ROUTER_2_REROUTED", + "ID_TEAM_BALANCER_INTERNAL", + "ID_TEAM_BALANCER_REQUESTED_TEAM_CHANGE_PENDING", + "ID_TEAM_BALANCER_TEAMS_LOCKED", + "ID_TEAM_BALANCER_TEAM_ASSIGNED", + "ID_LIGHTSPEED_INTEGRATION", + "ID_RPC_4_PLUGIN", + "ID_SND_RECEIPT_ACKED", + "ID_SND_RECEIPT_LOSS", + "ID_RESERVED_5", + "ID_RESERVED_6", + "ID_RESERVED_7", + "ID_RESERVED_8", + "ID_RESERVED_9", + "ID_USER_PACKET_ENUM" + }; + + return (char*)IDTable[Id]; +} +const char* PacketLogger::UserIDTOString(unsigned char Id) +{ + // Users should override this + static char str[256]; + Itoa(Id, str, 10); + return (const char*) str; +} +const char* PacketLogger::IDTOString(unsigned char Id) +{ + const char *out; + out=BaseIDTOString(Id); + if (out) + return out; + return UserIDTOString(Id); +} +void PacketLogger::SetPrefix(const char *_prefix) +{ + strncpy(prefix, _prefix, 255); + prefix[255]=0; +} +void PacketLogger::SetSuffix(const char *_suffix) +{ + strncpy(suffix, _suffix, 255); + suffix[255]=0; +} +void PacketLogger::GetLocalTime(char buffer[128]) +{ +#if defined(_WIN32) && !defined(__GNUC__) && !defined(__GCCXML__) + time_t rawtime; + struct timeval tv; + // If you get an arror about an incomplete type, just delete this file + struct timezone tz; + gettimeofday(&tv, &tz); + // time ( &rawtime ); + rawtime=tv.tv_sec; + + struct tm * timeinfo; + timeinfo = localtime ( &rawtime ); + strftime (buffer,128,"%x %X",timeinfo); + char buff[32]; + sprintf(buff, ".%i", tv.tv_usec); + strcat(buffer,buff); + + // Commented version puts the time first + /* + struct tm * timeinfo; + timeinfo = localtime ( &rawtime ); + strftime (buffer,128,"%X",timeinfo); + char buff[32]; + sprintf(buff, ".%i ", tv.tv_usec); + strcat(buffer,buff); + char buff2[32]; + strftime (buff2,32,"%x",timeinfo); + strcat(buffer,buff2); + */ +#else + buffer[0]=0; +#endif +} +void PacketLogger::SetLogDirectMessages(bool send) +{ + logDirectMessages=send; +} + +#ifdef _MSC_VER +#pragma warning( pop ) +#endif + +#endif // _RAKNET_SUPPORT_* diff --git a/RakNet/Sources/PacketLogger.h b/RakNet/Sources/PacketLogger.h new file mode 100644 index 0000000..caffe3d --- /dev/null +++ b/RakNet/Sources/PacketLogger.h @@ -0,0 +1,90 @@ +/// \file +/// \brief This will write all incoming and outgoing network messages to the local console screen. See derived functions for other outputs +/// +/// This file is part of RakNet Copyright 2003 Jenkins Software LLC +/// +/// Usage of RakNet is subject to the appropriate license agreement. + +#include "NativeFeatureIncludes.h" +#if _RAKNET_SUPPORT_PacketLogger==1 + +#ifndef __PACKET_LOGGER_H +#define __PACKET_LOGGER_H + +class RakPeerInterface; +#include "RakNetTypes.h" +#include "PluginInterface2.h" +#include "Export.h" + +/// \defgroup PACKETLOGGER_GROUP PacketLogger +/// \brief Print out incoming messages to a target destination +/// \details +/// \ingroup PLUGINS_GROUP + +/// \brief Writes incoming and outgoing messages to the screen. +/// This will write all incoming and outgoing messages to the console window, or to a file if you override it and give it this functionality. +/// \ingroup PACKETLOGGER_GROUP +class RAK_DLL_EXPORT PacketLogger : public PluginInterface2 +{ +public: + PacketLogger(); + virtual ~PacketLogger(); + + // Translate the supplied parameters into an output line - overloaded version that takes a MessageIdentifier + // and translates it into a string (numeric or textual representation based on printId); this calls the + // second version which takes a const char* argument for the messageIdentifier + virtual void FormatLine(char* into, const char* dir, const char* type, unsigned int packet, unsigned int frame + , unsigned char messageIdentifier, const BitSize_t bitLen, unsigned long long time, const SystemAddress& local, const SystemAddress& remote, + unsigned int splitPacketId, unsigned int splitPacketIndex, unsigned int splitPacketCount, unsigned int orderingIndex); + virtual void FormatLine(char* into, const char* dir, const char* type, unsigned int packet, unsigned int frame + , const char* idToPrint, const BitSize_t bitLen, unsigned long long time, const SystemAddress& local, const SystemAddress& remote, + unsigned int splitPacketId, unsigned int splitPacketIndex, unsigned int splitPacketCount, unsigned int orderingIndex); + + /// Events on low level sends and receives. These functions may be called from different threads at the same time. + virtual void OnDirectSocketSend(const char *data, const BitSize_t bitsUsed, SystemAddress remoteSystemAddress); + virtual void OnDirectSocketReceive(const char *data, const BitSize_t bitsUsed, SystemAddress remoteSystemAddress); + virtual void OnReliabilityLayerPacketError(const char *errorMessage, const BitSize_t bitsUsed, SystemAddress remoteSystemAddress); + virtual void OnInternalPacket(InternalPacket *internalPacket, unsigned frameNumber, SystemAddress remoteSystemAddress, RakNetTime time, int isSend); + virtual void OnAck(unsigned int messageNumber, SystemAddress remoteSystemAddress, RakNetTime time); + virtual void OnPushBackPacket(const char *data, const BitSize_t bitsUsed, SystemAddress remoteSystemAddress); + + /// Logs out a header for all the data + virtual void LogHeader(void); + + /// Override this to log strings to wherever. Log should be threadsafe + virtual void WriteLog(const char *str); + + // Write informational messages + virtual void WriteMiscellaneous(const char *type, const char *msg); + + + // Set to true to print ID_* instead of numbers + virtual void SetPrintID(bool print); + // Print or hide acks (clears up the screen not to print them but is worse for debugging) + virtual void SetPrintAcks(bool print); + + /// Prepend this string to output logs. + virtual void SetPrefix(const char *_prefix); + + /// Append this string to output logs. (newline is useful here) + virtual void SetSuffix(const char *_suffix); + static const char* BaseIDTOString(unsigned char Id); + + /// Log the direct sends and receives or not. Default true + void SetLogDirectMessages(bool send); +protected: + const char* IDTOString(unsigned char Id); + virtual void AddToLog(const char *str); + // Users should override this + virtual const char* UserIDTOString(unsigned char Id); + void GetLocalTime(char buffer[128]); + bool logDirectMessages; + + bool printId, printAcks; + char prefix[256]; + char suffix[256]; +}; + +#endif + +#endif // _RAKNET_SUPPORT_* diff --git a/RakNet/Sources/PacketOutputWindowLogger.cpp b/RakNet/Sources/PacketOutputWindowLogger.cpp new file mode 100644 index 0000000..d1f94d1 --- /dev/null +++ b/RakNet/Sources/PacketOutputWindowLogger.cpp @@ -0,0 +1,25 @@ +#include "NativeFeatureIncludes.h" +#if _RAKNET_SUPPORT_PacketLogger==1 + +#include "PacketOutputWindowLogger.h" +#include "RakString.h" +#if defined(_WIN32) && !defined(__X360__) && !defined(_XBOX) +#include "WindowsIncludes.h" +#endif + +PacketOutputWindowLogger::PacketOutputWindowLogger() +{ +} +PacketOutputWindowLogger::~PacketOutputWindowLogger() +{ +} +void PacketOutputWindowLogger::WriteLog(const char *str) +{ +#if defined(_WIN32) && !defined(__X360__) && !defined(_XBOX) + RakNet::RakString str2 = str; + str2+="\n"; + OutputDebugStr(str2.C_String()); +#endif +} + +#endif // _RAKNET_SUPPORT_* diff --git a/RakNet/Sources/PacketOutputWindowLogger.h b/RakNet/Sources/PacketOutputWindowLogger.h new file mode 100644 index 0000000..e5f3e72 --- /dev/null +++ b/RakNet/Sources/PacketOutputWindowLogger.h @@ -0,0 +1,29 @@ +/// \file +/// \brief This will write all incoming and outgoing network messages to a file +/// +/// This file is part of RakNet Copyright 2003 Jenkins Software LLC +/// +/// Usage of RakNet is subject to the appropriate license agreement. + +#include "NativeFeatureIncludes.h" +#if _RAKNET_SUPPORT_PacketLogger==1 + +#ifndef __PACKET_OUTPUT_WINDOW_LOGGER_H_ +#define __PACKET_OUTPUT_WINDOW_LOGGER_H_ + +#include "PacketLogger.h" + +/// \ingroup PACKETLOGGER_GROUP +/// \brief Packetlogger that outputs to the output window in the debugger. Windows only. +class RAK_DLL_EXPORT PacketOutputWindowLogger : public PacketLogger +{ +public: + PacketOutputWindowLogger(); + virtual ~PacketOutputWindowLogger(); + virtual void WriteLog(const char *str); +protected: +}; + +#endif + +#endif // _RAKNET_SUPPORT_* diff --git a/RakNet/Sources/PacketPool.h b/RakNet/Sources/PacketPool.h new file mode 100644 index 0000000..b534cac --- /dev/null +++ b/RakNet/Sources/PacketPool.h @@ -0,0 +1 @@ +// REMOVEME \ No newline at end of file diff --git a/RakNet/Sources/PacketPriority.h b/RakNet/Sources/PacketPriority.h new file mode 100644 index 0000000..13bbe7f --- /dev/null +++ b/RakNet/Sources/PacketPriority.h @@ -0,0 +1,76 @@ +/// \file +/// \brief This file contains enumerations for packet priority and reliability enumerations. +/// +/// This file is part of RakNet Copyright 2003 Jenkins Software LLC +/// +/// Usage of RakNet is subject to the appropriate license agreement. + + +#ifndef __PACKET_PRIORITY_H +#define __PACKET_PRIORITY_H + +/// These enumerations are used to describe when packets are delivered. +enum PacketPriority +{ + /// The highest possible priority. These message trigger sends immediately, and are generally not buffered or aggregated into a single datagram. + IMMEDIATE_PRIORITY, + + /// For every 2 IMMEDIATE_PRIORITY messages, 1 HIGH_PRIORITY will be sent. + /// Messages at this priority and lower are buffered to be sent in groups at 10 millisecond intervals to reduce UDP overhead and better measure congestion control. + HIGH_PRIORITY, + + /// For every 2 HIGH_PRIORITY messages, 1 MEDIUM_PRIORITY will be sent. + /// Messages at this priority and lower are buffered to be sent in groups at 10 millisecond intervals to reduce UDP overhead and better measure congestion control. + MEDIUM_PRIORITY, + + /// For every 2 MEDIUM_PRIORITY messages, 1 LOW_PRIORITY will be sent. + /// Messages at this priority and lower are buffered to be sent in groups at 10 millisecond intervals to reduce UDP overhead and better measure congestion control. + LOW_PRIORITY, + + /// \internal + NUMBER_OF_PRIORITIES +}; + +/// These enumerations are used to describe how packets are delivered. +/// \note Note to self: I write this with 3 bits in the stream. If I add more remember to change that +/// \note In ReliabilityLayer::WriteToBitStreamFromInternalPacket I assume there are 5 major types +/// \note Do not reorder, I check on >= UNRELIABLE_WITH_ACK_RECEIPT +enum PacketReliability +{ + /// Same as regular UDP, except that it will also discard duplicate datagrams. RakNet adds (6 to 17) + 21 bits of overhead, 16 of which is used to detect duplicate packets and 6 to 17 of which is used for message length. + UNRELIABLE, + + /// Regular UDP with a sequence counter. Out of order messages will be discarded. This adds an additional 13 bits on top what is used for UNRELIABLE. + UNRELIABLE_SEQUENCED, + + /// The message is sent reliably, but not necessarily in any order. Same overhead as UNRELIABLE. + RELIABLE, + + /// This message is reliable and will arrive in the order you sent it. Messages will be delayed while waiting for out of order messages. Same overhead as UNRELIABLE_SEQUENCED. + RELIABLE_ORDERED, + + /// This message is reliable and will arrive in the sequence you sent it. Out or order messages will be dropped. Same overhead as UNRELIABLE_SEQUENCED. + RELIABLE_SEQUENCED, + + /// Same as UNRELIABLE, however the user will get either ID_SND_RECEIPT_ACKED or ID_SND_RECEIPT_LOSS based on the result of sending this message when calling RakPeerInterface::Receive(). Bytes 1-4 will contain the number returned from the Send() function. On disconnect or shutdown, all messages not previously acked should be considered lost. + UNRELIABLE_WITH_ACK_RECEIPT, + + /// Same as UNRELIABLE_SEQUENCED, however the user will get either ID_SND_RECEIPT_ACKED or ID_SND_RECEIPT_LOSS based on the result of sending this message when calling RakPeerInterface::Receive(). Bytes 1-4 will contain the number returned from the Send() function. On disconnect or shutdown, all messages not previously acked should be considered lost. + /// 05/04/10 You can't have sequenced and ack receipts, because you don't know if the other system discarded the message, meaning you don't know if the message was processed + // UNRELIABLE_SEQUENCED_WITH_ACK_RECEIPT, + + /// Same as UNRELIABLE_SEQUENCED. The user will also get ID_SND_RECEIPT_ACKED after the message is delivered when calling RakPeerInterface::Receive(). Bytes 1-4 will contain the number returned from the Send() function. On disconnect or shutdown, all messages not previously acked should be considered lost. + RELIABLE_WITH_ACK_RECEIPT, + + /// Same as RELIABLE_ORDERED_ACK_RECEIPT. The user will also get ID_SND_RECEIPT_ACKED after the message is delivered when calling RakPeerInterface::Receive(). Bytes 1-4 will contain the number returned from the Send() function. On disconnect or shutdown, all messages not previously acked should be considered lost. + RELIABLE_ORDERED_WITH_ACK_RECEIPT, + + /// Same as RELIABLE_SEQUENCED. The user will also get ID_SND_RECEIPT_ACKED after the message is delivered when calling RakPeerInterface::Receive(). Bytes 1-4 will contain the number returned from the Send() function. On disconnect or shutdown, all messages not previously acked should be considered lost. + /// 05/04/10 You can't have sequenced and ack receipts, because you don't know if the other system discarded the message, meaning you don't know if the message was processed + // RELIABLE_SEQUENCED_WITH_ACK_RECEIPT, + + /// \internal + NUMBER_OF_RELIABILITIES +}; + +#endif diff --git a/RakNet/Sources/PacketizedTCP.cpp b/RakNet/Sources/PacketizedTCP.cpp new file mode 100644 index 0000000..6221a04 --- /dev/null +++ b/RakNet/Sources/PacketizedTCP.cpp @@ -0,0 +1,397 @@ +#include "NativeFeatureIncludes.h" +#if _RAKNET_SUPPORT_PacketizedTCP==1 + +#include "PacketizedTCP.h" +#include "NativeTypes.h" +#include "BitStream.h" +#include "MessageIdentifiers.h" +#include "RakAlloca.h" + +typedef uint32_t PTCPHeader; + +PacketizedTCP::PacketizedTCP() +{ + +} +PacketizedTCP::~PacketizedTCP() +{ + ClearAllConnections(); +} + +bool PacketizedTCP::Start(unsigned short port, unsigned short maxIncomingConnections, int threadPriority) +{ + bool success = TCPInterface::Start(port, maxIncomingConnections,0,threadPriority); + if (success) + { + unsigned int i; + for (i=0; i < messageHandlerList.Size(); i++) + messageHandlerList[i]->OnRakPeerStartup(); + } + return success; +} + +void PacketizedTCP::Stop(void) +{ + unsigned int i; + for (i=0; i < messageHandlerList.Size(); i++) + messageHandlerList[i]->OnRakPeerShutdown(); + for (i=0; i < waitingPackets.Size(); i++) + DeallocatePacket(waitingPackets[i]); + TCPInterface::Stop(); + ClearAllConnections(); +} + +void PacketizedTCP::Send( const char *data, unsigned length, SystemAddress systemAddress, bool broadcast ) +{ + PTCPHeader dataLength; + dataLength=length; +#ifndef __BITSTREAM_NATIVE_END + if (RakNet::BitStream::DoEndianSwap()) + RakNet::BitStream::ReverseBytes((unsigned char*) &length,(unsigned char*) &dataLength,sizeof(dataLength)); +#else + dataLength=length; +#endif + + unsigned int lengthsArray[2]; + const char *dataArray[2]; + dataArray[0]=(char*) &dataLength; + dataArray[1]=data; + lengthsArray[0]=sizeof(dataLength); + lengthsArray[1]=length; + TCPInterface::SendList(dataArray,lengthsArray,2,systemAddress,broadcast); +} +bool PacketizedTCP::SendList( const char **data, const int *lengths, const int numParameters, SystemAddress systemAddress, bool broadcast ) +{ + if (isStarted==false) + return false; + if (data==0) + return false; + if (systemAddress==UNASSIGNED_SYSTEM_ADDRESS && broadcast==false) + return false; + PTCPHeader totalLengthOfUserData=0; + int i; + for (i=0; i < numParameters; i++) + { + if (lengths[i]>0) + totalLengthOfUserData+=lengths[i]; + } + if (totalLengthOfUserData==0) + return false; + + PTCPHeader dataLength; +#ifndef __BITSTREAM_NATIVE_END + if (RakNet::BitStream::DoEndianSwap()) + RakNet::BitStream::ReverseBytes((unsigned char*) &totalLengthOfUserData,(unsigned char*) &dataLength,sizeof(dataLength)); +#else + dataLength=totalLengthOfUserData; +#endif + + + unsigned int lengthsArray[512]; + const char *dataArray[512]; + dataArray[0]=(char*) &dataLength; + lengthsArray[0]=sizeof(dataLength); + for (int i=0; i < 512 && i < numParameters; i++) + { + dataArray[i+1]=data[i]; + lengthsArray[i+1]=lengths[i]; + } + return TCPInterface::SendList(dataArray,lengthsArray,numParameters+1,systemAddress,broadcast); +} +void PacketizedTCP::PushNotificationsToQueues(void) +{ + SystemAddress sa; + sa = TCPInterface::HasNewIncomingConnection(); + if (sa!=UNASSIGNED_SYSTEM_ADDRESS) + { + _newIncomingConnections.Push(sa, __FILE__, __LINE__ ); + AddToConnectionList(sa); + unsigned int i; + for (i=0; i < messageHandlerList.Size(); i++) + messageHandlerList[i]->OnNewConnection(sa, UNASSIGNED_RAKNET_GUID, true); + } + + sa = TCPInterface::HasFailedConnectionAttempt(); + if (sa!=UNASSIGNED_SYSTEM_ADDRESS) + { + _failedConnectionAttempts.Push(sa, __FILE__, __LINE__ ); + unsigned int i; + for (i=0; i < messageHandlerList.Size(); i++) + messageHandlerList[i]->OnFailedConnectionAttempt(sa, FCAR_CONNECTION_ATTEMPT_FAILED); + } + + sa = TCPInterface::HasLostConnection(); + if (sa!=UNASSIGNED_SYSTEM_ADDRESS) + { + _lostConnections.Push(sa, __FILE__, __LINE__ ); + RemoveFromConnectionList(sa); + unsigned int i; + for (i=0; i < messageHandlerList.Size(); i++) + messageHandlerList[i]->OnClosedConnection(sa, UNASSIGNED_RAKNET_GUID, LCR_DISCONNECTION_NOTIFICATION); + } + + sa = TCPInterface::HasCompletedConnectionAttempt(); + if (sa!=UNASSIGNED_SYSTEM_ADDRESS) + { + _completedConnectionAttempts.Push(sa, __FILE__, __LINE__ ); + AddToConnectionList(sa); + unsigned int i; + for (i=0; i < messageHandlerList.Size(); i++) + messageHandlerList[i]->OnNewConnection(sa, UNASSIGNED_RAKNET_GUID, true); + } +} +Packet* PacketizedTCP::Receive( void ) +{ + PushNotificationsToQueues(); + + unsigned int i; + for (i=0; i < messageHandlerList.Size(); i++) + messageHandlerList[i]->Update(); + + Packet *outgoingPacket=ReturnOutgoingPacket(); + if (outgoingPacket) + return outgoingPacket; + + Packet *incomingPacket; + incomingPacket = TCPInterface::Receive(); + unsigned int index; + + while (incomingPacket) + { + if (connections.Has(incomingPacket->systemAddress)) + index = connections.GetIndexAtKey(incomingPacket->systemAddress); + else + index=(unsigned int) -1; + if ((unsigned int)index==(unsigned int)-1) + { + DeallocatePacket(incomingPacket); + incomingPacket = TCPInterface::Receive(); + continue; + } + + + if (incomingPacket->deleteData==true) + { + // Came from network + SystemAddress systemAddressFromPacket; + if (index < connections.Size()) + { + DataStructures::ByteQueue *bq = connections[index]; + // Buffer data + bq->WriteBytes((const char*) incomingPacket->data,incomingPacket->length, __FILE__,__LINE__); + systemAddressFromPacket=incomingPacket->systemAddress; + PTCPHeader dataLength; + + // Peek the header to see if a full message is waiting + bq->ReadBytes((char*) &dataLength,sizeof(PTCPHeader),true); + if (RakNet::BitStream::DoEndianSwap()) + RakNet::BitStream::ReverseBytesInPlace((unsigned char*) &dataLength,sizeof(dataLength)); + // Header indicates packet length. If enough data is available, read out and return one packet + if (bq->GetBytesWritten()>=dataLength+sizeof(PTCPHeader)) + { + do + { + bq->IncrementReadOffset(sizeof(PTCPHeader)); + outgoingPacket = RakNet::OP_NEW(__FILE__, __LINE__); + outgoingPacket->length=dataLength; + outgoingPacket->bitSize=BYTES_TO_BITS(dataLength); + outgoingPacket->guid=UNASSIGNED_RAKNET_GUID; + outgoingPacket->systemAddress=systemAddressFromPacket; + outgoingPacket->deleteData=false; // Did not come from the network + outgoingPacket->data=(unsigned char*) rakMalloc_Ex(dataLength, __FILE__, __LINE__); + if (outgoingPacket->data==0) + { + notifyOutOfMemory(__FILE__, __LINE__); + RakNet::OP_DELETE(outgoingPacket,__FILE__,__LINE__); + return 0; + } + bq->ReadBytes((char*) outgoingPacket->data,dataLength,false); + + waitingPackets.Push(outgoingPacket, __FILE__, __LINE__ ); + + // Peek the header to see if a full message is waiting + if (bq->ReadBytes((char*) &dataLength,sizeof(PTCPHeader),true)) + { + if (RakNet::BitStream::DoEndianSwap()) + RakNet::BitStream::ReverseBytesInPlace((unsigned char*) &dataLength,sizeof(dataLength)); + } + else + break; + } while (bq->GetBytesWritten()>=dataLength+sizeof(PTCPHeader)); + } + else + { + + unsigned int oldWritten = bq->GetBytesWritten()-incomingPacket->length; + unsigned int newWritten = bq->GetBytesWritten(); + + // Return ID_DOWNLOAD_PROGRESS + if (newWritten/65536!=oldWritten/65536) + { + outgoingPacket = RakNet::OP_NEW(__FILE__, __LINE__); + outgoingPacket->length=sizeof(MessageID) + + sizeof(unsigned int)*2 + + sizeof(unsigned int) + + 65536; + outgoingPacket->bitSize=BYTES_TO_BITS(incomingPacket->length); + outgoingPacket->guid=UNASSIGNED_RAKNET_GUID; + outgoingPacket->systemAddress=incomingPacket->systemAddress; + outgoingPacket->deleteData=false; + outgoingPacket->data=(unsigned char*) rakMalloc_Ex(outgoingPacket->length, __FILE__, __LINE__); + if (outgoingPacket->data==0) + { + notifyOutOfMemory(__FILE__, __LINE__); + RakNet::OP_DELETE(outgoingPacket,__FILE__,__LINE__); + return 0; + } + + outgoingPacket->data[0]=(MessageID)ID_DOWNLOAD_PROGRESS; + unsigned int totalParts=dataLength/65536; + unsigned int partIndex=newWritten/65536; + unsigned int oneChunkSize=65536; + memcpy(outgoingPacket->data+sizeof(MessageID), &partIndex, sizeof(unsigned int)); + memcpy(outgoingPacket->data+sizeof(MessageID)+sizeof(unsigned int)*1, &totalParts, sizeof(unsigned int)); + memcpy(outgoingPacket->data+sizeof(MessageID)+sizeof(unsigned int)*2, &oneChunkSize, sizeof(unsigned int)); + bq->IncrementReadOffset(sizeof(PTCPHeader)); + bq->ReadBytes((char*) outgoingPacket->data+sizeof(MessageID)+sizeof(unsigned int)*3,oneChunkSize,true); + bq->DecrementReadOffset(sizeof(PTCPHeader)); + + waitingPackets.Push(outgoingPacket, __FILE__, __LINE__ ); + } + } + + } + + DeallocatePacket(incomingPacket); + incomingPacket=0; + } + else + waitingPackets.Push(incomingPacket, __FILE__, __LINE__ ); + + incomingPacket = TCPInterface::Receive(); + } + + return ReturnOutgoingPacket(); +} +Packet *PacketizedTCP::ReturnOutgoingPacket(void) +{ + Packet *outgoingPacket=0; + unsigned int i; + while (outgoingPacket==0 && waitingPackets.IsEmpty()==false) + { + outgoingPacket=waitingPackets.Pop(); + PluginReceiveResult pluginResult; + for (i=0; i < messageHandlerList.Size(); i++) + { + pluginResult=messageHandlerList[i]->OnReceive(outgoingPacket); + if (pluginResult==RR_STOP_PROCESSING_AND_DEALLOCATE) + { + DeallocatePacket( outgoingPacket ); + outgoingPacket=0; // Will do the loop again and get another packet + break; // break out of the enclosing for + } + else if (pluginResult==RR_STOP_PROCESSING) + { + outgoingPacket=0; + break; + } + } + } + + return outgoingPacket; +} + +void PacketizedTCP::AttachPlugin( PluginInterface2 *plugin ) +{ + if (messageHandlerList.GetIndexOf(plugin)==MAX_UNSIGNED_LONG) + { + messageHandlerList.Insert(plugin, __FILE__, __LINE__); + plugin->SetPacketizedTCP(this); + plugin->OnAttach(); + } +} +void PacketizedTCP::DetachPlugin( PluginInterface2 *plugin ) +{ + if (plugin==0) + return; + + unsigned int index; + index = messageHandlerList.GetIndexOf(plugin); + if (index!=MAX_UNSIGNED_LONG) + { + messageHandlerList[index]->OnDetach(); + // Unordered list so delete from end for speed + messageHandlerList[index]=messageHandlerList[messageHandlerList.Size()-1]; + messageHandlerList.RemoveFromEnd(); + plugin->SetPacketizedTCP(0); + } +} +void PacketizedTCP::CloseConnection( SystemAddress systemAddress ) +{ + RemoveFromConnectionList(systemAddress); + unsigned int i; + for (i=0; i < messageHandlerList.Size(); i++) + messageHandlerList[i]->OnClosedConnection(systemAddress, UNASSIGNED_RAKNET_GUID, LCR_CLOSED_BY_USER); + TCPInterface::CloseConnection(systemAddress); +} +void PacketizedTCP::RemoveFromConnectionList(SystemAddress sa) +{ + if (sa==UNASSIGNED_SYSTEM_ADDRESS) + return; + if (connections.Has(sa)) + { + unsigned int index = connections.GetIndexAtKey(sa); + if (index!=(unsigned int)-1) + { + RakNet::OP_DELETE(connections[index],__FILE__,__LINE__); + connections.RemoveAtIndex(index); + } + } +} +void PacketizedTCP::AddToConnectionList(SystemAddress sa) +{ + if (sa==UNASSIGNED_SYSTEM_ADDRESS) + return; + connections.SetNew(sa, RakNet::OP_NEW(__FILE__, __LINE__)); +} +void PacketizedTCP::ClearAllConnections(void) +{ + unsigned int i; + for (i=0; i < connections.Size(); i++) + RakNet::OP_DELETE(connections[i],__FILE__,__LINE__); + connections.Clear(); +} +SystemAddress PacketizedTCP::HasCompletedConnectionAttempt(void) +{ + PushNotificationsToQueues(); + + if (_completedConnectionAttempts.IsEmpty()==false) + return _completedConnectionAttempts.Pop(); + return UNASSIGNED_SYSTEM_ADDRESS; +} +SystemAddress PacketizedTCP::HasFailedConnectionAttempt(void) +{ + PushNotificationsToQueues(); + + if (_failedConnectionAttempts.IsEmpty()==false) + return _failedConnectionAttempts.Pop(); + return UNASSIGNED_SYSTEM_ADDRESS; +} +SystemAddress PacketizedTCP::HasNewIncomingConnection(void) +{ + PushNotificationsToQueues(); + + if (_newIncomingConnections.IsEmpty()==false) + return _newIncomingConnections.Pop(); + return UNASSIGNED_SYSTEM_ADDRESS; +} +SystemAddress PacketizedTCP::HasLostConnection(void) +{ + PushNotificationsToQueues(); + + if (_lostConnections.IsEmpty()==false) + return _lostConnections.Pop(); + return UNASSIGNED_SYSTEM_ADDRESS; +} + +#endif // _RAKNET_SUPPORT_* diff --git a/RakNet/Sources/PacketizedTCP.h b/RakNet/Sources/PacketizedTCP.h new file mode 100644 index 0000000..ed1e5f7 --- /dev/null +++ b/RakNet/Sources/PacketizedTCP.h @@ -0,0 +1,81 @@ +/// \file +/// \brief A simple TCP based server allowing sends and receives. Can be connected by any TCP client, including telnet. +/// +/// This file is part of RakNet Copyright 2003 Jenkins Software LLC +/// +/// Usage of RakNet is subject to the appropriate license agreement. + +#include "NativeFeatureIncludes.h" +#if _RAKNET_SUPPORT_PacketizedTCP==1 + +#ifndef __PACKETIZED_TCP +#define __PACKETIZED_TCP + +#include "TCPInterface.h" +#include "DS_ByteQueue.h" +#include "PluginInterface2.h" +#include "DS_Map.h" + +class RAK_DLL_EXPORT PacketizedTCP : public TCPInterface +{ +public: + PacketizedTCP(); + virtual ~PacketizedTCP(); + + /// Starts the TCP server on the indicated port + /// \param[in] threadPriority Passed to thread creation routine. Use THREAD_PRIORITY_NORMAL for Windows. WARNING!!! On Linux, 0 means highest priority! You MUST set this to something valid based on the values used by your other threads + bool Start(unsigned short port, unsigned short maxIncomingConnections, int threadPriority=-99999); + + /// Stops the TCP server + void Stop(void); + + /// Sends a byte stream + void Send( const char *data, unsigned length, SystemAddress systemAddress, bool broadcast ); + + // Sends a concatenated list of byte streams + bool SendList( const char **data, const int *lengths, const int numParameters, SystemAddress systemAddress, bool broadcast ); + + /// Returns data received + Packet* Receive( void ); + + /// Disconnects a player/address + void CloseConnection( SystemAddress systemAddress ); + + /// Has a previous call to connect succeeded? + /// \return UNASSIGNED_SYSTEM_ADDRESS = no. Anything else means yes. + SystemAddress HasCompletedConnectionAttempt(void); + + /// Has a previous call to connect failed? + /// \return UNASSIGNED_SYSTEM_ADDRESS = no. Anything else means yes. + SystemAddress HasFailedConnectionAttempt(void); + + /// Queued events of new incoming connections + SystemAddress HasNewIncomingConnection(void); + + /// Queued events of lost connections + SystemAddress HasLostConnection(void); + + // Only currently tested with FileListTransfer! + void AttachPlugin( PluginInterface2 *plugin ); + void DetachPlugin( PluginInterface2 *plugin ); + +protected: + void ClearAllConnections(void); + void RemoveFromConnectionList(SystemAddress sa); + void AddToConnectionList(SystemAddress sa); + void PushNotificationsToQueues(void); + Packet *ReturnOutgoingPacket(void); + + // Plugins + DataStructures::List messageHandlerList; + // A single TCP recieve may generate multiple split packets. They are stored in the waitingPackets list until Receive is called + DataStructures::Queue waitingPackets; + DataStructures::Map connections; + + // Mirrors single producer / consumer, but processes them in Receive() before returning to user + DataStructures::Queue _newIncomingConnections, _lostConnections, _failedConnectionAttempts, _completedConnectionAttempts; +}; + +#endif + +#endif // _RAKNET_SUPPORT_* diff --git a/RakNet/Sources/Platform.h b/RakNet/Sources/Platform.h new file mode 100644 index 0000000..a0ae351 --- /dev/null +++ b/RakNet/Sources/Platform.h @@ -0,0 +1,267 @@ +#ifndef PLATFORM_HPP +#define PLATFORM_HPP + + +//// Platform tweaks //// + +#include "RakNetTypes.h" + +#ifdef __APPLE__ +#include "TargetConditionals.h" +#endif + +#ifdef _WIN32_WCE //makslane +# define _byteswap_ushort(s) ((s >> 8) | (s << 8)) +# define _byteswap_ulong(s) (byteswap_ushort(s&0xffff)<<16) | (byteswap_ushort(s>>16)) +# define byteswap_ushort _byteswap_ushort +# define byteswap_ulong _byteswap_ulong +#endif + +#if !defined(LITTLE_ENDIAN) && !defined(__BIG_ENDIAN__) +# if defined(__sparc) || defined(__sparc__) || defined(__powerpc__) || \ + defined(__ppc__) || defined(__hppa) || defined(_MIPSEB) || defined(_POWER) || \ + defined(_M_PPC) || defined(_M_MPPC) || defined(_M_MRX000) || \ + defined(__POWERPC) || defined(m68k) || defined(powerpc) || \ + defined(sel) || defined(pyr) || defined(mc68000) || defined(is68k) || \ + defined(tahoe) || defined(ibm032) || defined(ibm370) || defined(MIPSEB) || \ + defined(__convex__) || defined(DGUX) || defined(hppa) || defined(apollo) || \ + defined(_CRAY) || defined(__hp9000) || defined(__hp9000s300) || defined(_AIX) || \ + defined(__AIX) || defined(__pyr__) || defined(hp9000s700) || defined(_IBMR2) +# define __BIG_ENDIAN__ +# elif defined(__i386__) || defined(i386) || defined(intel) || defined(_M_IX86) || \ + defined(__alpha__) || defined(__alpha) || defined(__ia64) || defined(__ia64__) || \ + defined(_M_ALPHA) || defined(ns32000) || defined(__ns32000__) || defined(sequent) || \ + defined(MIPSEL) || defined(_MIPSEL) || defined(sun386) || defined(__sun386__) || \ + defined(x86_64) || defined(__x86_64) || (defined(TARGET_OS_IPHONE) && defined(TARGET_CPU_ARM)) || \ + defined(__arm__) +# define LITTLE_ENDIAN +# else +# error "Add your platform to the list" +# endif +#endif + + +//// Compiler tweaks //// + +// Structure packing syntax +#if defined(__GNUC__) +#ifndef PACKED +# define PACKED __attribute__((__packed__)) +#endif +# define ASSEMBLY_ATT_SYNTAX +# define ASSEMBLY_BLOCK asm +#elif defined(_WIN32) +# define PACKED +# define PRAGMA_PACK /* pragma push/pop */ +#ifndef DEBUG +# define DEBUG _DEBUG +#endif +# if !defined(_M_X64) +# define ASSEMBLY_INTEL_SYNTAX +# define ASSEMBLY_BLOCK __asm +# endif +#elif defined(__BORLANDC__) // Borland +# define ASSEMBLY_INTEL_SYNTAX +# define ASSEMBLY_BLOCK _asm +#else +# error "Fix code for your compiler's packing syntax" +#endif + +// Stringize +#define STRINGIZE(X) DO_STRINGIZE(X) +#define DO_STRINGIZE(X) #X + + +//// Elementary types //// + +struct u128 { uint32_t u[4]; }; + +union catFloat32 { + float f; + uint32_t i; + + catFloat32(float n) { f = n; } + catFloat32(uint32_t n) { i = n; } +}; + + +//// Coordinate system //// + +// Order of vertices in any quad +enum QuadCoords +{ + QUAD_UL, // upper left (0,0) + QUAD_LL, // lower left (0,1) + QUAD_LR, // lower right (1,1) + QUAD_UR, // upper right (1,0) +}; + + +//// Rotation macros //// + +#ifndef ROL8 +#define ROL8(n, r) ( ((uint8_t)(n) << (r)) | ((uint8_t)(n) >> ( 8 - (r))) ) /* only works for uint8_t */ +#endif +#ifndef ROR8 +#define ROR8(n, r) ( ((uint8_t)(n) >> (r)) | ((uint8_t)(n) << ( 8 - (r))) ) /* only works for uint8_t */ +#endif +#ifndef ROL16 +#define ROL16(n, r) ( ((uint16_t)(n) << (r)) | ((uint16_t)(n) >> (16 - (r))) ) /* only works for uint16_t */ +#endif +#ifndef ROR16 +#define ROR16(n, r) ( ((uint16_t)(n) >> (r)) | ((uint16_t)(n) << (16 - (r))) ) /* only works for uint16_t */ +#endif +#ifndef ROL32 +#define ROL32(n, r) ( ((uint32_t)(n) << (r)) | ((uint32_t)(n) >> (32 - (r))) ) /* only works for uint32_t */ +#endif +#ifndef ROR32 +#define ROR32(n, r) ( ((uint32_t)(n) >> (r)) | ((uint32_t)(n) << (32 - (r))) ) /* only works for uint32_t */ +#endif +#ifndef ROL64 +#define ROL64(n, r) ( ((uint64_t)(n) << (r)) | ((uint64_t)(n) >> (64 - (r))) ) /* only works for uint64_t */ +#endif +#ifndef ROR64 +#define ROR64(n, r) ( ((uint64_t)(n) >> (r)) | ((uint64_t)(n) << (64 - (r))) ) /* only works for uint64_t */ +#endif + +//// Byte-order swapping //// + +#ifndef BOSWAP16 +#define BOSWAP16(n) ROL16(n, 8) +#endif +#ifndef BOSWAP32 +#define BOSWAP32(n) ( (ROL32(n, 8) & 0x00ff00ff) | (ROL32(n, 24) & 0xff00ff00) ) +#endif +#ifndef BOSWAP64 +#define BOSWAP64(n) ( ((uint64_t)BOSWAP32((uint32_t)n) << 32) | BOSWAP32((uint32_t)(n >> 32)) ) +#endif + +//// Miscellaneous bitwise macros //// + +#define BITCLRHI8(reg, count) ((uint8_t)((uint8_t)(reg) << (count)) >> (count)) /* sets to zero a number of high bits in a byte */ +#define BITCLRLO8(reg, count) ((uint8_t)((uint8_t)(reg) >> (count)) << (count)) /* sets to zero a number of low bits in a byte */ +#define BITCLRHI16(reg, count) ((uint16_t)((uint16_t)(reg) << (count)) >> (count)) /* sets to zero a number of high bits in a 16-bit word */ +#define BITCLRLO16(reg, count) ((uint16_t)((uint16_t)(reg) >> (count)) << (count)) /* sets to zero a number of low bits in a 16-bit word */ +#define BITCLRHI32(reg, count) ((uint32_t)((uint32_t)(reg) << (count)) >> (count)) /* sets to zero a number of high bits in a 32-bit word */ +#define BITCLRLO32(reg, count) ((uint32_t)((uint32_t)(reg) >> (count)) << (count)) /* sets to zero a number of low bits in a 32-bit word */ + + +//// Integer macros //// + +#define AT_LEAST_2_BITS(n) ( (n) & ((n) - 1) ) +#define LEAST_SIGNIFICANT_BIT(n) ( (n) & -(n) ) /* 0 -> 0 */ +#define IS_POWER_OF_2(n) ( n && !AT_LEAST_2_BITS(n) ) + +// Bump 'n' to the next unit of 'width' +// 0=CEIL_UNIT(0, 16), 1=CEIL_UNIT(1, 16), 1=CEIL_UNIT(16, 16), 2=CEIL_UNIT(17, 16) +#define CEIL_UNIT(n, width) ( ( (n) + (width) - 1 ) / (width) ) +// 0=CEIL(0, 16), 16=CEIL(1, 16), 16=CEIL(16, 16), 32=CEIL(17, 16) +#define CEIL(n, width) ( CEIL_UNIT(n, width) * (width) ) + + +//// String and buffer macros //// + +// Same as strncpy() in all ways except that the result is guaranteed to +// be a nul-terminated C string +#ifndef STRNCPY +# define STRNCPY(dest, src, size) { strncpy(dest, src, size); (dest)[(size)-1] = '\0'; } +#endif + +// Because memory clearing is a frequent operation +#define MEMCLR(dest, size) memset(dest, 0, size) + +// Works for arrays, also +#define OBJCLR(object) memset(&(object), 0, sizeof(object)) + + +//// Intrinsics //// + +#if defined(_MSC_VER) + +# include + +# pragma intrinsic(_lrotl) +# pragma intrinsic(_lrotr) +# pragma intrinsic(_byteswap_ushort) +# pragma intrinsic(_byteswap_ulong) + +# undef ROL32 +# undef ROR32 +# undef BOSWAP16 +# undef BOSWAP32 + +# define ROL32(n, r) _lrotl(n, r) +# define ROR32(n, r) _lrotr(n, r) +# define BOSWAP16(n) _byteswap_ushort(n) +# define BOSWAP32(n) _byteswap_ulong(n) + +#endif + + +// getLE() converts from little-endian word to native byte-order word +// getBE() converts from big-endian word to native byte-order word + +#if defined(LITTLE_ENDIAN) + +# define swapLE(n) +# define getLE(n) (n) +# define getLE16(n) (n) +# define getLE32(n) (n) +# define getLE64(n) (n) + + inline void swapBE(uint16_t &n) { n = BOSWAP16(n); } + inline void swapBE(uint32_t &n) { n = BOSWAP32(n); } + inline void swapBE(uint64_t &n) { n = BOSWAP64(n); } + inline uint16_t getBE(uint16_t n) { return BOSWAP16(n); } + inline uint32_t getBE(uint32_t n) { return BOSWAP32(n); } + inline uint64_t getBE(uint64_t n) { return BOSWAP64(n); } + inline uint16_t getBE16(uint16_t n) { return BOSWAP16(n); } + inline uint32_t getBE32(uint32_t n) { return BOSWAP32(n); } + inline uint64_t getBE64(uint64_t n) { return BOSWAP64(n); } + inline void swapBE(int16_t &n) { n = BOSWAP16((uint16_t)n); } + inline void swapBE(int32_t &n) { n = BOSWAP32((uint32_t)n); } + inline void swapBE(int64_t &n) { n = BOSWAP64((uint64_t)n); } + inline int16_t getBE(int16_t n) { return BOSWAP16((uint16_t)n); } + inline int32_t getBE(int32_t n) { return BOSWAP32((uint32_t)n); } + inline int64_t getBE(int64_t n) { return BOSWAP64((uint64_t)n); } + + inline float getBE(float n) { + catFloat32 c = n; + c.i = BOSWAP32(c.i); + return c.f; + } + +#else + +# define swapBE(n) +# define getBE(n) (n) +# define getBE16(n) (n) +# define getBE32(n) (n) +# define getBE64(n) (n) + + inline void swapLE(uint16_t &n) { n = BOSWAP16(n); } + inline void swapLE(uint32_t &n) { n = BOSWAP32(n); } + inline void swapLE(uint64_t &n) { n = BOSWAP64(n); } + inline uint16_t getLE(uint16_t n) { return BOSWAP16(n); } + inline uint32_t getLE(uint32_t n) { return BOSWAP32(n); } + inline uint64_t getLE(uint64_t n) { return BOSWAP64(n); } + inline uint16_t getLE16(uint16_t n) { return BOSWAP16(n); } + inline uint32_t getLE32(uint32_t n) { return BOSWAP32(n); } + inline uint64_t getLE32(uint64_t n) { return BOSWAP64(n); } + inline void swapLE(int16_t &n) { n = BOSWAP16((uint16_t)n); } + inline void swapLE(int32_t &n) { n = BOSWAP32((uint32_t)n); } + inline void swapLE(int64_t &n) { n = BOSWAP64((uint64_t)n); } + inline int16_t getLE(int16_t n) { return BOSWAP16((uint16_t)n); } + inline int32_t getLE(int32_t n) { return BOSWAP32((uint32_t)n); } + inline int64_t getLE(int64_t n) { return BOSWAP64((uint64_t)n); } + + inline float getLE(float n) { + catFloat32 c = n; + c.i = BOSWAP32(c.i); + return c.f; + } + +#endif + + +#endif // include guard diff --git a/RakNet/Sources/PluginInterface.cpp b/RakNet/Sources/PluginInterface.cpp new file mode 100644 index 0000000..06e1c43 --- /dev/null +++ b/RakNet/Sources/PluginInterface.cpp @@ -0,0 +1,78 @@ +/// \file +/// +/// This file is part of RakNet Copyright 2003 Jenkins Software LLC +/// +/// Usage of RakNet is subject to the appropriate license agreement. + + +/* +#include "PluginInterface.h" + +#ifdef _MSC_VER +#pragma warning( push ) +#endif + +PluginInterface::PluginInterface() +{ + +} +PluginInterface::~PluginInterface() +{ + +} +void PluginInterface::OnAttach(RakPeerInterface *peer) +{ + (void) peer; +} +void PluginInterface::OnDetach(RakPeerInterface *peer) +{ + (void) peer; +} +void PluginInterface::OnStartup(RakPeerInterface *peer) +{ + (void) peer; +} +void PluginInterface::Update(RakPeerInterface *peer) +{ + (void) peer; +} +PluginReceiveResult PluginInterface::OnReceive(RakPeerInterface *peer, Packet *packet) +{ + (void) peer; + (void) packet; + return RR_CONTINUE_PROCESSING; +} +void PluginInterface::OnShutdown(RakPeerInterface *peer) +{ + (void) peer; +} +void PluginInterface::OnCloseConnection(RakPeerInterface *peer, SystemAddress systemAddress) +{ + (void) peer; + (void) systemAddress; +} +void PluginInterface::OnDirectSocketSend(const char *data, const BitSize_t bitsUsed, SystemAddress remoteSystemAddress) +{ + (void) data; + (void) bitsUsed; + (void) remoteSystemAddress; +} +void PluginInterface::OnDirectSocketReceive(const char *data, const BitSize_t bitsUsed, SystemAddress remoteSystemAddress) +{ + (void) data; + (void) bitsUsed; + (void) remoteSystemAddress; +} +void PluginInterface::OnInternalPacket(InternalPacket *internalPacket, unsigned frameNumber, SystemAddress remoteSystemAddress, RakNetTime time, int isSend) +{ + (void) internalPacket; + (void) frameNumber; + (void) remoteSystemAddress; + (void) time; + (void) isSend; +} + +#ifdef _MSC_VER +#pragma warning( pop ) +#endif +*/ diff --git a/RakNet/Sources/PluginInterface.h b/RakNet/Sources/PluginInterface.h new file mode 100644 index 0000000..63107af --- /dev/null +++ b/RakNet/Sources/PluginInterface.h @@ -0,0 +1,104 @@ +/// \file +/// \brief \b RakNet's plugin functionality system. You can derive from this to create your own plugins. +/// +/// This file is part of RakNet Copyright 2003 Jenkins Software LLC +/// +/// Usage of RakNet is subject to the appropriate license agreement. + + +/* +#ifndef __PLUGIN_INTERFACE_H +#define __PLUGIN_INTERFACE_H + +class RakPeerInterface; +struct Packet; +struct InternalPacket; + +enum PluginReceiveResult +{ + // The plugin used this message and it shouldn't be given to the user. + RR_STOP_PROCESSING_AND_DEALLOCATE=0, + + // This message will be processed by other plugins, and at last by the user. + RR_CONTINUE_PROCESSING, + + // The plugin is going to hold on to this message. Do not deallocate it but do not pass it to other plugins either. + RR_STOP_PROCESSING, +}; + +#include "RakNetTypes.h" +#include "Export.h" +#include "RakMemoryOverride.h" + +/// \defgroup PLUGINS_GROUP PluginInterface + +/// \brief PluginInterface provides a mechanism to add functionality in a modular way. +/// MessageHandlers should derive from PluginInterface and be attached to RakPeer using the function AttachPlugin +/// On a user call to Receive, OnReceive is called for every PluginInterface, which can then take action based on the message +/// passed to it. This is used to transparently add game-independent functional modules, similar to browser plugins +/// +/// \sa ReplicaManager +/// \sa FullyConnectedMesh +/// \sa PacketLogger +/// \ingroup PLUGINS_GROUP +class RAK_DLL_EXPORT PluginInterface +{ +public: + PluginInterface(); + virtual ~PluginInterface(); + + /// Called when the interface is attached + /// \param[in] peer the instance of RakPeer that is calling Receive + virtual void OnAttach(RakPeerInterface *peer); + + /// Called when the interface is detached + /// \param[in] peer the instance of RakPeer that is calling Receive + virtual void OnDetach(RakPeerInterface *peer); + + /// Called when RakPeer is initialized + /// \param[in] peer the instance of RakPeer that is calling Receive + virtual void OnStartup(RakPeerInterface *peer); + + /// Update is called every time a packet is checked for . + /// \param[in] peer - the instance of RakPeer that is calling Receive + virtual void Update(RakPeerInterface *peer); + + /// OnReceive is called for every packet. + /// \param[in] peer the instance of RakPeer that is calling Receive + /// \param[in] packet the packet that is being returned to the user + /// \return True to allow the game and other plugins to get this message, false to absorb it + virtual PluginReceiveResult OnReceive(RakPeerInterface *peer, Packet *packet); + + /// Called when RakPeer is shutdown + /// \param[in] peer the instance of RakPeer that is calling Receive + virtual void OnShutdown(RakPeerInterface *peer); + + /// Called when a connection is dropped because the user called RakPeer::CloseConnection() for a particular system + /// \param[in] peer the instance of RakPeer that is calling Receive + /// \param[in] systemAddress The system whose connection was closed + virtual void OnCloseConnection(RakPeerInterface *peer, SystemAddress systemAddress); + + /// Called on a send to the socket, per datagram, that does not go through the reliability layer + /// \param[in] data The data being sent + /// \param[in] bitsUsed How many bits long \a data is + /// \param[in] remoteSystemAddress Which system this message is being sent to + virtual void OnDirectSocketSend(const char *data, const BitSize_t bitsUsed, SystemAddress remoteSystemAddress); + + /// Called on a receive from the socket, per datagram, that does not go through the reliability layer + /// \param[in] data The data being sent + /// \param[in] bitsUsed How many bits long \a data is + /// \param[in] remoteSystemAddress Which system this message is being sent to + virtual void OnDirectSocketReceive(const char *data, const BitSize_t bitsUsed, SystemAddress remoteSystemAddress); + + /// Called on a send or recieve within the reliability layer + /// \param[in] internalPacket The user message, along with all send data. + /// \param[in] frameNumber The number of frames sent or received so far for this player depending on \a isSend . Indicates the frame of this user message. + /// \param[in] remoteSystemAddress The player we sent or got this packet from + /// \param[in] time The current time as returned by RakNet::GetTime() + /// \param[in] isSend Is this callback representing a send event or receive event? + virtual void OnInternalPacket(InternalPacket *internalPacket, unsigned frameNumber, SystemAddress remoteSystemAddress, RakNetTime time, int isSend); +}; + +#endif + +*/ \ No newline at end of file diff --git a/RakNet/Sources/PluginInterface2.cpp b/RakNet/Sources/PluginInterface2.cpp new file mode 100644 index 0000000..61bdbe8 --- /dev/null +++ b/RakNet/Sources/PluginInterface2.cpp @@ -0,0 +1,87 @@ +/// \file +/// +/// This file is part of RakNet Copyright 2003 Jenkins Software LLC +/// +/// Usage of RakNet is subject to the appropriate license agreement. + + +#include "PluginInterface2.h" +#include "PacketizedTCP.h" +#include "RakPeerInterface.h" +#include "BitStream.h" + +PluginInterface2::PluginInterface2() +{ + rakPeerInterface=0; +#if _RAKNET_SUPPORT_PacketizedTCP==1 + packetizedTCP=0; +#endif +} +PluginInterface2::~PluginInterface2() +{ + +} +void PluginInterface2::SendUnified( const RakNet::BitStream * bitStream, PacketPriority priority, PacketReliability reliability, char orderingChannel, const AddressOrGUID systemIdentifier, bool broadcast ) +{ + if (rakPeerInterface) + rakPeerInterface->Send(bitStream,priority,reliability,orderingChannel,systemIdentifier,broadcast); +#if _RAKNET_SUPPORT_PacketizedTCP==1 + else + packetizedTCP->Send((const char*) bitStream->GetData(), bitStream->GetNumberOfBytesUsed(), systemIdentifier.systemAddress, broadcast); +#endif +} +Packet *PluginInterface2::AllocatePacketUnified(unsigned dataSize) +{ + if (rakPeerInterface) + return rakPeerInterface->AllocatePacket(dataSize); +#if _RAKNET_SUPPORT_PacketizedTCP==1 + else + return packetizedTCP->AllocatePacket(dataSize); +#else + return 0; +#endif + +} +void PluginInterface2::PushBackPacketUnified(Packet *packet, bool pushAtHead) +{ + if (rakPeerInterface) + rakPeerInterface->PushBackPacket(packet,pushAtHead); +#if _RAKNET_SUPPORT_PacketizedTCP==1 + else + packetizedTCP->PushBackPacket(packet,pushAtHead); +#endif +} +void PluginInterface2::DeallocPacketUnified(Packet *packet) +{ + if (rakPeerInterface) + rakPeerInterface->DeallocatePacket(packet); +#if _RAKNET_SUPPORT_PacketizedTCP==1 + else + packetizedTCP->DeallocatePacket(packet); +#endif +} +bool PluginInterface2::SendListUnified( const char **data, const int *lengths, const int numParameters, PacketPriority priority, PacketReliability reliability, char orderingChannel, const AddressOrGUID systemIdentifier, bool broadcast ) +{ + if (rakPeerInterface) + { + return rakPeerInterface->SendList(data,lengths,numParameters,priority,reliability,orderingChannel,systemIdentifier,broadcast)!=0; + } +#if _RAKNET_SUPPORT_PacketizedTCP==1 + else + { + return packetizedTCP->SendList(data,lengths,numParameters,systemIdentifier.systemAddress,broadcast ); + } +#else + return false; +#endif +} +void PluginInterface2::SetRakPeerInterface( RakPeerInterface *ptr ) +{ + rakPeerInterface=ptr; +} +#if _RAKNET_SUPPORT_PacketizedTCP==1 +void PluginInterface2::SetPacketizedTCP( PacketizedTCP *ptr ) +{ + packetizedTCP=ptr; +} +#endif diff --git a/RakNet/Sources/PluginInterface2.h b/RakNet/Sources/PluginInterface2.h new file mode 100644 index 0000000..b04e5d7 --- /dev/null +++ b/RakNet/Sources/PluginInterface2.h @@ -0,0 +1,185 @@ +/// \file +/// \brief \b RakNet's plugin functionality system, version 2. You can derive from this to create your own plugins. +/// +/// This file is part of RakNet Copyright 2003 Jenkins Software LLC +/// +/// Usage of RakNet is subject to the appropriate license agreement. + + +#ifndef __PLUGIN_INTERFACE_2_H +#define __PLUGIN_INTERFACE_2_H + +#include "NativeFeatureIncludes.h" + +class RakPeerInterface; +class PacketizedTCP; +struct Packet; +struct InternalPacket; + +/// \defgroup PLUGIN_INTERFACE_GROUP PluginInterface2 + +/// \defgroup PLUGINS_GROUP Plugins +/// \ingroup PLUGIN_INTERFACE_GROUP + +/// For each message that arrives on an instance of RakPeer, the plugins get an opportunity to process them first. This enumeration represents what to do with the message +/// \ingroup PLUGIN_INTERFACE_GROUP +enum PluginReceiveResult +{ + /// The plugin used this message and it shouldn't be given to the user. + RR_STOP_PROCESSING_AND_DEALLOCATE=0, + + /// This message will be processed by other plugins, and at last by the user. + RR_CONTINUE_PROCESSING, + + /// The plugin is going to hold on to this message. Do not deallocate it but do not pass it to other plugins either. + RR_STOP_PROCESSING, +}; + +#include "RakNetTypes.h" +#include "Export.h" +#include "PacketPriority.h" + +/// Reasons why a connection was lost +/// \ingroup PLUGIN_INTERFACE_GROUP +enum PI2_LostConnectionReason +{ + /// Called RakPeer::CloseConnection() + LCR_CLOSED_BY_USER, + + /// Got ID_DISCONNECTION_NOTIFICATION + LCR_DISCONNECTION_NOTIFICATION, + + /// GOT ID_CONNECTION_LOST + LCR_CONNECTION_LOST +}; + +/// Returns why a connection attempt failed +/// \ingroup PLUGIN_INTERFACE_GROUP +enum PI2_FailedConnectionAttemptReason +{ + FCAR_CONNECTION_ATTEMPT_FAILED, + FCAR_ALREADY_CONNECTED, + FCAR_NO_FREE_INCOMING_CONNECTIONS, + FCAR_RSA_PUBLIC_KEY_MISMATCH, + FCAR_CONNECTION_BANNED, + FCAR_INVALID_PASSWORD, + FCAR_INCOMPATIBLE_PROTOCOL, + FCAR_IP_RECENTLY_CONNECTED +}; + +/// RakNet's plugin system. Each plugin processes the following events: +/// -Connection attempts +/// -The result of connection attempts +/// -Each incoming message +/// -Updates over time, when RakPeer::Receive() is called +/// +/// \ingroup PLUGIN_INTERFACE_GROUP +class RAK_DLL_EXPORT PluginInterface2 +{ +public: + PluginInterface2(); + virtual ~PluginInterface2(); + + /// Called when the interface is attached + /// \param[in] peer the instance of RakPeer that is calling Receive + virtual void OnAttach(void) {} + + /// Called when the interface is detached + /// \param[in] peer the instance of RakPeer that is calling Receive + virtual void OnDetach(void) {} + + /// Update is called every time a packet is checked for . + virtual void Update(void) {} + + /// OnReceive is called for every packet. + /// \param[in] packet the packet that is being returned to the user + /// \return True to allow the game and other plugins to get this message, false to absorb it + virtual PluginReceiveResult OnReceive(Packet *packet) {(void) packet; return RR_CONTINUE_PROCESSING;} + + /// Called when RakPeer is initialized + virtual void OnRakPeerStartup(void) {} + + /// Called when RakPeer is shutdown + virtual void OnRakPeerShutdown(void) {} + + /// Called when a connection is dropped because the user called RakPeer::CloseConnection() for a particular system + /// \param[in] systemAddress The system whose connection was closed + /// \param[in] rakNetGuid The guid of the specified system + /// \param[in] lostConnectionReason How the connection was closed: manually, connection lost, or notification of disconnection + virtual void OnClosedConnection(SystemAddress systemAddress, RakNetGUID rakNetGUID, PI2_LostConnectionReason lostConnectionReason ){(void) systemAddress; (void) rakNetGUID; (void) lostConnectionReason;} + + /// Called when we got a new connection + /// \param[in] systemAddress Address of the new connection + /// \param[in] rakNetGuid The guid of the specified system + /// \param[in] isIncoming If true, this is ID_NEW_INCOMING_CONNECTION, or the equivalent + virtual void OnNewConnection(SystemAddress systemAddress, RakNetGUID rakNetGUID, bool isIncoming) {(void) systemAddress; (void) rakNetGUID; (void) isIncoming;} + + /// Called when a connection attempt fails + /// \param[in] systemAddress Address of the connection + /// \param[in] failedConnectionReason Why the connection failed + virtual void OnFailedConnectionAttempt(SystemAddress systemAddress, PI2_FailedConnectionAttemptReason failedConnectionAttemptReason) {(void) systemAddress; (void) failedConnectionAttemptReason;} + + /// Called on a send to the socket, per datagram, that does not go through the reliability layer + /// \param[in] data The data being sent + /// \param[in] bitsUsed How many bits long \a data is + /// \param[in] remoteSystemAddress Which system this message is being sent to + virtual void OnDirectSocketSend(const char *data, const BitSize_t bitsUsed, SystemAddress remoteSystemAddress) {(void) data; (void) bitsUsed; (void) remoteSystemAddress;} + + /// Called on a receive from the socket, per datagram, that does not go through the reliability layer + /// \param[in] data The data being sent + /// \param[in] bitsUsed How many bits long \a data is + /// \param[in] remoteSystemAddress Which system this message is being sent to + virtual void OnDirectSocketReceive(const char *data, const BitSize_t bitsUsed, SystemAddress remoteSystemAddress) {(void) data; (void) bitsUsed; (void) remoteSystemAddress;} + + /// Called when the reliability layer rejects a send or receive + /// \param[in] bitsUsed How many bits long \a data is + /// \param[in] remoteSystemAddress Which system this message is being sent to + virtual void OnReliabilityLayerPacketError(const char *errorMessage, const BitSize_t bitsUsed, SystemAddress remoteSystemAddress) {(void) errorMessage; (void) bitsUsed; (void) remoteSystemAddress;} + + /// Called on a send or receive of a message within the reliability layer + /// \param[in] internalPacket The user message, along with all send data. + /// \param[in] frameNumber The number of frames sent or received so far for this player depending on \a isSend . Indicates the frame of this user message. + /// \param[in] remoteSystemAddress The player we sent or got this packet from + /// \param[in] time The current time as returned by RakNet::GetTime() + /// \param[in] isSend Is this callback representing a send event or receive event? + virtual void OnInternalPacket(InternalPacket *internalPacket, unsigned frameNumber, SystemAddress remoteSystemAddress, RakNetTime time, int isSend) {(void) internalPacket; (void) frameNumber; (void) remoteSystemAddress; (void) time; (void) isSend;} + + /// Called when we get an ack for a message we reliabily sent + /// \param[in] messageNumber The numerical identifier for which message this is + /// \param[in] remoteSystemAddress The player we sent or got this packet from + /// \param[in] time The current time as returned by RakNet::GetTime() + virtual void OnAck(unsigned int messageNumber, SystemAddress remoteSystemAddress, RakNetTime time) {(void) messageNumber; (void) remoteSystemAddress; (void) time;} + + /// System called RakPeerInterface::PushBackPacket + /// \param[in] data The data being sent + /// \param[in] bitsUsed How many bits long \a data is + /// \param[in] remoteSystemAddress The player we sent or got this packet from + virtual void OnPushBackPacket(const char *data, const BitSize_t bitsUsed, SystemAddress remoteSystemAddress) {(void) data; (void) bitsUsed; (void) remoteSystemAddress;} + + RakPeerInterface *GetRakPeerInterface(void) const {return rakPeerInterface;} + + /// \internal + void SetRakPeerInterface( RakPeerInterface *ptr ); + +#if _RAKNET_SUPPORT_PacketizedTCP==1 + /// \internal + void SetPacketizedTCP( PacketizedTCP *ptr ); +#endif +protected: + // Send through either rakPeerInterface or packetizedTCP, whichever is available + void SendUnified( const RakNet::BitStream * bitStream, PacketPriority priority, PacketReliability reliability, char orderingChannel, const AddressOrGUID systemIdentifier, bool broadcast ); + bool SendListUnified( const char **data, const int *lengths, const int numParameters, PacketPriority priority, PacketReliability reliability, char orderingChannel, const AddressOrGUID systemIdentifier, bool broadcast ); + + Packet *AllocatePacketUnified(unsigned dataSize); + void PushBackPacketUnified(Packet *packet, bool pushAtHead); + void DeallocPacketUnified(Packet *packet); + + // Filled automatically in when attached + RakPeerInterface *rakPeerInterface; +#if _RAKNET_SUPPORT_PacketizedTCP==1 + PacketizedTCP *packetizedTCP; +#endif +}; + +#endif + diff --git a/RakNet/Sources/RPC4Plugin.cpp b/RakNet/Sources/RPC4Plugin.cpp new file mode 100644 index 0000000..7d2ca38 --- /dev/null +++ b/RakNet/Sources/RPC4Plugin.cpp @@ -0,0 +1,125 @@ +#include "NativeFeatureIncludes.h" +#if _RAKNET_SUPPORT_RPC4Plugin==1 + +#include "RPC4Plugin.h" +#include "MessageIdentifiers.h" +#include "RakPeerInterface.h" +#include "PacketizedTCP.h" + +using namespace RakNet; + +RPC4Plugin::RPC4Plugin() +{ + +} +RPC4Plugin::~RPC4Plugin() +{ + +} +bool RPC4Plugin::RegisterFunction(const char* uniqueID, void ( *functionPointer ) ( RakNet::BitStream *userData, Packet *packet )) +{ + DataStructures::StringKeyedHashIndex skhi = registeredFunctions.GetIndexOf(uniqueID); + if (skhi.IsInvalid()==false) + return false; + + registeredFunctions.Push(uniqueID,functionPointer,__FILE__,__LINE__); + return true; +} +bool RPC4Plugin::UnregisterFunction(const char* uniqueID) +{ + void ( *f ) ( RakNet::BitStream *, Packet * ); + return registeredFunctions.Pop(f,uniqueID,__FILE__,__LINE__); +} +void RPC4Plugin::CallLoopback( const char* uniqueID, RakNet::BitStream * bitStream ) +{ + Packet *p; + + DataStructures::StringKeyedHashIndex skhi = registeredFunctions.GetIndexOf(uniqueID); + + if (skhi.IsInvalid()==true) + { + if (rakPeerInterface) + p=rakPeerInterface->AllocatePacket(sizeof(MessageID)+sizeof(unsigned char)+strlen(uniqueID)+1); + else + p=packetizedTCP->AllocatePacket(sizeof(MessageID)+sizeof(unsigned char)+strlen(uniqueID)+1); + + if (rakPeerInterface) + p->guid=rakPeerInterface->GetGuidFromSystemAddress(UNASSIGNED_SYSTEM_ADDRESS); + else + p->guid=UNASSIGNED_RAKNET_GUID; + + p->systemAddress=UNASSIGNED_SYSTEM_ADDRESS; + p->systemAddress.systemIndex=(SystemIndex)-1; + p->data[0]=ID_RPC_REMOTE_ERROR; + p->data[1]=RPC_ERROR_FUNCTION_NOT_REGISTERED; + strcpy((char*) p->data+2, uniqueID); + + PushBackPacketUnified(p,false); + + return; + } + + RakNet::BitStream out; + out.Write((MessageID) ID_RPC_4_PLUGIN); + out.WriteCompressed(uniqueID); + if (bitStream) + { + bitStream->ResetReadPointer(); + out.Write(bitStream); + } + if (rakPeerInterface) + p=rakPeerInterface->AllocatePacket(out.GetNumberOfBytesUsed()); + else + p=packetizedTCP->AllocatePacket(out.GetNumberOfBytesUsed()); + + if (rakPeerInterface) + p->guid=rakPeerInterface->GetGuidFromSystemAddress(UNASSIGNED_SYSTEM_ADDRESS); + else + p->guid=UNASSIGNED_RAKNET_GUID; + p->systemAddress=UNASSIGNED_SYSTEM_ADDRESS; + p->systemAddress.systemIndex=(SystemIndex)-1; + memcpy(p->data,out.GetData(),out.GetNumberOfBytesUsed()); + PushBackPacketUnified(p,false); + return; +} +void RPC4Plugin::Call( const char* uniqueID, RakNet::BitStream * bitStream, PacketPriority priority, PacketReliability reliability, char orderingChannel, const AddressOrGUID systemIdentifier, bool broadcast ) +{ + RakNet::BitStream out; + out.Write((MessageID) ID_RPC_4_PLUGIN); + out.WriteCompressed(uniqueID); + if (bitStream) + { + bitStream->ResetReadPointer(); + out.Write(bitStream); + } + SendUnified(&out,priority,reliability,orderingChannel,systemIdentifier,broadcast); +} +PluginReceiveResult RPC4Plugin::OnReceive(Packet *packet) +{ + if (packet->data[0]==ID_RPC_4_PLUGIN) + { + RakNet::BitStream bsIn(packet->data,packet->length,false); + bsIn.IgnoreBytes(1); + RakNet::RakString functionName; + bsIn.ReadCompressed(functionName); + DataStructures::StringKeyedHashIndex skhi = registeredFunctions.GetIndexOf(functionName.C_String()); + if (skhi.IsInvalid()) + { + RakNet::BitStream bsOut; + bsOut.Write((unsigned char) ID_RPC_REMOTE_ERROR); + bsOut.Write((unsigned char) RPC_ERROR_FUNCTION_NOT_REGISTERED); + bsOut.Write(functionName.C_String(),functionName.GetLength()+1); + SendUnified(&bsOut,HIGH_PRIORITY,RELIABLE_ORDERED,0,packet->systemAddress,false); + return RR_STOP_PROCESSING_AND_DEALLOCATE; + } + + void ( *fp ) ( RakNet::BitStream *, Packet * ); + fp = registeredFunctions.ItemAtIndex(skhi); + fp(&bsIn,packet); + return RR_STOP_PROCESSING_AND_DEALLOCATE; + } + + return RR_CONTINUE_PROCESSING; +} + +#endif // _RAKNET_SUPPORT_* diff --git a/RakNet/Sources/RPC4Plugin.h b/RakNet/Sources/RPC4Plugin.h new file mode 100644 index 0000000..2a41c9a --- /dev/null +++ b/RakNet/Sources/RPC4Plugin.h @@ -0,0 +1,106 @@ +/// \file +/// \brief Remote procedure call, supporting C functions only. No external dependencies required. +/// +/// This file is part of RakNet Copyright 2003 Jenkins Software LLC +/// +/// Usage of RakNet is subject to the appropriate license agreement. + +#include "NativeFeatureIncludes.h" +#if _RAKNET_SUPPORT_RPC4Plugin==1 + + +#ifndef __RPC_4_PLUGIN_H +#define __RPC_4_PLUGIN_H + +class RakPeerInterface; +class NetworkIDManager; +#include "PluginInterface2.h" +#include "PacketPriority.h" +#include "RakNetTypes.h" +#include "BitStream.h" +#include "RakString.h" +#include "NetworkIDObject.h" +#include "DS_StringKeyedHash.h" + +#ifdef _MSC_VER +#pragma warning( push ) +#endif + +/// \defgroup RPC_PLUGIN_GROUP RPC +/// \brief Remote procedure calls, without external dependencies. +/// \details This should not be used at the same time as RPC3. This is a less functional version of RPC3, and is here for users that do not want the Boost dependency of RPC3. +/// \ingroup PLUGINS_GROUP + +namespace RakNet +{ + /// \brief Error codes returned by a remote system as to why an RPC function call cannot execute + /// \details Error code follows packet ID ID_RPC_REMOTE_ERROR, that is packet->data[1]
+ /// Name of the function will be appended starting at packet->data[2] + /// \ingroup RPC_PLUGIN_GROUP + enum RPCErrorCodes + { + /// Named function was not registered with RegisterFunction(). Check your spelling. + RPC_ERROR_FUNCTION_NOT_REGISTERED, + }; + + /// \brief The RPC4Plugin plugin is just an association between a C function pointer and a string. + /// \details It is for users that want to use RPC, but do not want to use boost. + /// You do not have the automatic serialization or other features of RPC3, and C++ member calls are not supported. + /// \ingroup RPC_PLUGIN_GROUP + class RPC4Plugin : public PluginInterface2 + { + public: + // Constructor + RPC4Plugin(); + + // Destructor + virtual ~RPC4Plugin(); + + /// \brief Register a function pointer to be callable from a remote system + /// \details The hash of the function name will be stored as an association with the function pointer + /// When a call is made to call this function from the \a Call() or CallLoopback() function, the function pointer will be invoked with the passed bitStream to Call() and the actual Packet that RakNet got. + /// \param[in] uniqueID Identifier to be associated with \a functionPointer. If this identifier is already in use, the call will return false. + /// \param[in] functionPointer C function pointer to be called + /// \return True if the hash of uniqueID is not in use, false otherwise. + bool RegisterFunction(const char* uniqueID, void ( *functionPointer ) ( RakNet::BitStream *userData, Packet *packet )); + + /// \brief Unregister a function pointer previously registered with RegisterFunction() + /// \param[in] Identifier originally passed to RegisterFunction() + /// \return True if the hash of uniqueID was in use, and hence removed. false otherwise. + bool UnregisterFunction(const char* uniqueID); + + /// Send to the attached instance of RakPeer. See RakPeerInterface::SendLoopback() + /// \param[in] Identifier originally passed to RegisterFunction() on the local system + /// \param[in] bitStream bitStream encoded data to send to the function callback + void CallLoopback( const char* uniqueID, RakNet::BitStream * bitStream ); + + /// Send to the specified remote instance of RakPeer. + /// \param[in] Identifier originally passed to RegisterFunction() on the remote system(s) + /// \param[in] bitStream bitStream encoded data to send to the function callback + /// \param[in] priority See RakPeerInterface::Send() + /// \param[in] reliability See RakPeerInterface::Send() + /// \param[in] orderingChannel See RakPeerInterface::Send() + /// \param[in] systemIdentifier See RakPeerInterface::Send() + /// \param[in] broadcast See RakPeerInterface::Send() + void Call( const char* uniqueID, RakNet::BitStream * bitStream, PacketPriority priority, PacketReliability reliability, char orderingChannel, const AddressOrGUID systemIdentifier, bool broadcast ); + + protected: + + // -------------------------------------------------------------------------------------------- + // Packet handling functions + // -------------------------------------------------------------------------------------------- + virtual PluginReceiveResult OnReceive(Packet *packet); + + DataStructures::StringKeyedHash registeredFunctions; + + }; + +} // End namespace + +#endif + +#ifdef _MSC_VER +#pragma warning( pop ) +#endif + +#endif // _RAKNET_SUPPORT_* diff --git a/RakNet/Sources/RPCMap.cpp b/RakNet/Sources/RPCMap.cpp new file mode 100644 index 0000000..d7f34e7 --- /dev/null +++ b/RakNet/Sources/RPCMap.cpp @@ -0,0 +1,158 @@ +/// \file +/// +/// This file is part of RakNet Copyright 2003 Jenkins Software LLC +/// +/// Usage of RakNet is subject to the appropriate license agreement. + + +#include "RPCMap.h" +#include + +RPCMap::RPCMap() +{ +} +RPCMap::~RPCMap() +{ + Clear(); +} +void RPCMap::Clear(void) +{ + unsigned i; + RPCNode *node; + for (i=0; i < rpcSet.Size(); i++) + { + node=rpcSet[i]; + if (node) + { + rakFree_Ex(node->uniqueIdentifier, __FILE__, __LINE__ ); + RakNet::OP_DELETE(node, __FILE__, __LINE__); + } + } + rpcSet.Clear(false, __FILE__, __LINE__); +} +RPCNode *RPCMap::GetNodeFromIndex(RPCIndex index) +{ + if ((unsigned)index < rpcSet.Size()) + return rpcSet[(unsigned)index]; + return 0; +} +RPCNode *RPCMap::GetNodeFromFunctionName(const char *uniqueIdentifier) +{ + unsigned index; + index=(unsigned)GetIndexFromFunctionName(uniqueIdentifier); + if ((RPCIndex)index!=UNDEFINED_RPC_INDEX) + return rpcSet[index]; + return 0; +} +RPCIndex RPCMap::GetIndexFromFunctionName(const char *uniqueIdentifier) +{ + unsigned index; + for (index=0; index < rpcSet.Size(); index++) + if (rpcSet[index] && strcmp(rpcSet[index]->uniqueIdentifier, uniqueIdentifier)==0) + return (RPCIndex) index; + return UNDEFINED_RPC_INDEX; +} + +// Called from the user thread for the local system +void RPCMap::AddIdentifierWithFunction(const char *uniqueIdentifier, void *functionPointer, bool isPointerToMember) +{ +#ifdef _DEBUG + RakAssert((int) rpcSet.Size()+1 < MAX_RPC_MAP_SIZE); // If this hits change the typedef of RPCIndex to use an unsigned short + RakAssert(uniqueIdentifier && uniqueIdentifier[0]); + RakAssert(functionPointer); +#endif + + unsigned index, existingNodeIndex; + RPCNode *node; + + existingNodeIndex=GetIndexFromFunctionName(uniqueIdentifier); + if ((RPCIndex)existingNodeIndex!=UNDEFINED_RPC_INDEX) // Insert at any free spot. + { + // Trying to insert an identifier at any free slot and that identifier already exists + // The user should not insert nodes that already exist in the list +#ifdef _DEBUG +// assert(0); +#endif + return; + } + + node = RakNet::OP_NEW( __FILE__, __LINE__ ); + node->uniqueIdentifier = (char*) rakMalloc_Ex( strlen(uniqueIdentifier)+1, __FILE__, __LINE__ ); + strcpy(node->uniqueIdentifier, uniqueIdentifier); + node->functionPointer=functionPointer; + node->isPointerToMember=isPointerToMember; + + // Insert into an empty spot if possible + for (index=0; index < rpcSet.Size(); index++) + { + if (rpcSet[index]==0) + { + rpcSet.Replace(node, 0, index, __FILE__,__LINE__); + return; + } + } + + rpcSet.Insert(node, __FILE__, __LINE__); // No empty spots available so just add to the end of the list + +} +void RPCMap::AddIdentifierAtIndex(const char *uniqueIdentifier, RPCIndex insertionIndex) +{ +#ifdef _DEBUG + RakAssert(uniqueIdentifier && uniqueIdentifier[0]); +#endif + + unsigned existingNodeIndex; + RPCNode *node, *oldNode; + + existingNodeIndex=GetIndexFromFunctionName(uniqueIdentifier); + + if (existingNodeIndex==insertionIndex) + return; // Already there + + if ((RPCIndex)existingNodeIndex!=UNDEFINED_RPC_INDEX) + { + // Delete the existing one + oldNode=rpcSet[existingNodeIndex]; + rpcSet[existingNodeIndex]=0; + rakFree_Ex(oldNode->uniqueIdentifier, __FILE__, __LINE__ ); + RakNet::OP_DELETE(oldNode, __FILE__, __LINE__); + } + + node = RakNet::OP_NEW( __FILE__, __LINE__ ); + node->uniqueIdentifier = (char*) rakMalloc_Ex( strlen(uniqueIdentifier)+1, __FILE__, __LINE__ ); + strcpy(node->uniqueIdentifier, uniqueIdentifier); + node->functionPointer=0; + + // Insert at a user specified spot + if (insertionIndex < rpcSet.Size()) + { + // Overwrite what is there already + oldNode=rpcSet[insertionIndex]; + if (oldNode) + { + RakNet::OP_DELETE_ARRAY(oldNode->uniqueIdentifier, __FILE__, __LINE__); + RakNet::OP_DELETE(oldNode, __FILE__, __LINE__); + } + rpcSet[insertionIndex]=node; + } + else + { + // Insert after the end of the list and use 0 as a filler for the empty spots + rpcSet.Replace(node, 0, insertionIndex, __FILE__,__LINE__); + } +} + +void RPCMap::RemoveNode(const char *uniqueIdentifier) +{ + unsigned index; + index=GetIndexFromFunctionName(uniqueIdentifier); + #ifdef _DEBUG + RakAssert((int) index!=UNDEFINED_RPC_INDEX); // If this hits then the user was removing an RPC call that wasn't currently registered + #endif + RPCNode *node; + node = rpcSet[index]; + rakFree_Ex(node->uniqueIdentifier, __FILE__, __LINE__ ); + RakNet::OP_DELETE(node, __FILE__, __LINE__); + rpcSet[index]=0; +} + diff --git a/RakNet/Sources/RPCMap.h b/RakNet/Sources/RPCMap.h new file mode 100644 index 0000000..d09268e --- /dev/null +++ b/RakNet/Sources/RPCMap.h @@ -0,0 +1,40 @@ +/// \file +/// \brief \b [Internal] A container class for a list of RPCNodes +/// +/// \ingroup RAKNET_RPC +/// +/// This file is part of RakNet Copyright 2003 Jenkins Software LLC +/// +/// Usage of RakNet is subject to the appropriate license agreement. + + +#ifndef __RPC_MAP +#define __RPC_MAP + +#include "RakMemoryOverride.h" +#include "RPCNode.h" +#include "DS_List.h" +#include "RakNetTypes.h" +#include "Export.h" + +/// \ingroup RAKNET_RPC +/// \internal +/// \brief A container class for a list of RPCNodes +struct RAK_DLL_EXPORT RPCMap +{ +public: + RPCMap(); + ~RPCMap(); + void Clear(void); + RPCNode *GetNodeFromIndex(RPCIndex index); + RPCNode *GetNodeFromFunctionName(const char *uniqueIdentifier); + RPCIndex GetIndexFromFunctionName(const char *uniqueIdentifier); + void AddIdentifierWithFunction(const char *uniqueIdentifier, void *functionPointer, bool isPointerToMember); + void AddIdentifierAtIndex(const char *uniqueIdentifier, RPCIndex insertionIndex); + void RemoveNode(const char *uniqueIdentifier); +protected: + DataStructures::List rpcSet; +}; + +#endif + diff --git a/RakNet/Sources/RPCNode.h b/RakNet/Sources/RPCNode.h new file mode 100644 index 0000000..ab4c72b --- /dev/null +++ b/RakNet/Sources/RPCNode.h @@ -0,0 +1,53 @@ +/// \file +/// \brief \b [Internal] Holds information related to a RPC +/// +/// \ingroup RAKNET_RPC +/// +/// This file is part of RakNet Copyright 2003 Jenkins Software LLC +/// +/// Usage of RakNet is subject to the appropriate license agreement. + + +#ifndef __RPC_NODE +#define __RPC_NODE + +#include "RakNetTypes.h" +#include "Export.h" + +class RakPeerInterface; + + +/// \defgroup RAKNET_RPC Remote Procedure Call Subsystem +/// \Deprecated Use RPC3 +/// \brief A system to call C or object member procudures on other systems, and even to return return values. + +/// \ingroup RAKNET_RPC +/// \internal +/// +/// \brief Map registered procedure inside of a peer. +/// +struct RAK_DLL_EXPORT RPCNode +{ + + /// String identifier of the RPC + char *uniqueIdentifier; + + /// Force casting of member functions to void * + union + { + void ( *staticFunctionPointer ) ( RPCParameters *rpcParms ); + #if (defined(__GNUC__) || defined(__GCCXML__)) + void (*memberFunctionPointer)(void* _this, RPCParameters *rpcParms); + #else + void (__cdecl *memberFunctionPointer)(void* _this, RPCParameters *rpcParms); + #endif + + void *functionPointer; + }; + + /// Is this a member function pointer? True if so. If false it's a regular C function. + bool isPointerToMember; +}; + +#endif + diff --git a/RakNet/Sources/RSACrypt.cpp b/RakNet/Sources/RSACrypt.cpp new file mode 100644 index 0000000..014be7d --- /dev/null +++ b/RakNet/Sources/RSACrypt.cpp @@ -0,0 +1,226 @@ +#if !defined(_XBOX) && !defined(X360) + +#include "RSACrypt.h" +//#include "Random.hpp" +#include "Rand.h" +#include "BigInt.h" +#include "RakAlloca.h" +#include "RakMemoryOverride.h" +using namespace big; + + +RSACrypt::RSACrypt() +{ + p = 0; + q = 0; + qInv = 0; + dP = 0; + dQ = 0; + modulus = 0; + + p_inv = 0; + q_inv = 0; + mod_inv = 0; + mod_limbs = 0; + factor_limbs = 0; + e = 0; +} + +RSACrypt::~RSACrypt() +{ + cleanup(); +} + +void RSACrypt::cleanup() +{ + if (p) RakNet::OP_DELETE_ARRAY(p, __FILE__, __LINE__); + p = 0; + if (q)RakNet::OP_DELETE_ARRAY(q, __FILE__, __LINE__); + q = 0; + if (qInv) RakNet::OP_DELETE_ARRAY(qInv, __FILE__, __LINE__); + qInv = 0; + if (dQ) RakNet::OP_DELETE_ARRAY(dQ, __FILE__, __LINE__); + dQ = 0; + if (dP) RakNet::OP_DELETE_ARRAY(dP, __FILE__, __LINE__); + dP = 0; + if (modulus) RakNet::OP_DELETE_ARRAY(modulus, __FILE__, __LINE__); + modulus = 0; + + p_inv = 0; + q_inv = 0; + mod_inv = 0; + mod_limbs = 0; + factor_limbs = 0; + e = 0; +} + +bool RSACrypt::setPrivateKey(const uint32_t *pi, const uint32_t *qi, int halfFactorLimbs) +{ + cleanup(); + + factor_limbs = halfFactorLimbs; + mod_limbs = factor_limbs * 2; + + p = RakNet::OP_NEW_ARRAY(factor_limbs, __FILE__, __LINE__ ); + q = RakNet::OP_NEW_ARRAY(factor_limbs, __FILE__, __LINE__ ); + dP = RakNet::OP_NEW_ARRAY(factor_limbs, __FILE__, __LINE__ ); + dQ = RakNet::OP_NEW_ARRAY(factor_limbs, __FILE__, __LINE__ ); + qInv = RakNet::OP_NEW_ARRAY(factor_limbs, __FILE__, __LINE__ ); + modulus = RakNet::OP_NEW_ARRAY(mod_limbs, __FILE__, __LINE__ ); + if (!p || !q || !qInv || !dP || !dQ || !modulus) return false; + + // Insure that p > q + if (Greater(factor_limbs, pi, qi)) + { + Set(p, factor_limbs, pi); + Set(q, factor_limbs, qi); + } + else + { + Set(q, factor_limbs, pi); + Set(p, factor_limbs, qi); + } + + // p1 = p-1 + uint32_t *p1 = (uint32_t *)alloca(factor_limbs*4); + Set(p1, factor_limbs, p); + Subtract32(p1, factor_limbs, 1); + + // q1 = q-1 + uint32_t *q1 = (uint32_t *)alloca(factor_limbs*4); + Set(q1, factor_limbs, q); + Subtract32(q1, factor_limbs, 1); + + // e = first number relatively prime to phi, starting at 65537 + e = 65537-2; + + uint32_t r; + do { + e += 2; + GCD(&e, 1, p1, factor_limbs, &r); + if (r != 1) continue; + GCD(&e, 1, q1, factor_limbs, &r); + } while (r != 1 && e >= 65537); + + if (r != 1) return false; + + // modulus = p * q + Multiply(factor_limbs, modulus, p, q); + + // dP = (1/e) mod (p-1) + if (!InvMod(&e, 1, p1, factor_limbs, dP)) + return false; + + // dQ = (1/e) mod (q-1) + if (!InvMod(&e, 1, q1, factor_limbs, dQ)) + return false; + + // qInv = (1/q) mod p + if (!InvMod(q, factor_limbs, p, factor_limbs, qInv)) + return false; + + // Prepare for Montgomery multiplication + p_inv = MonReducePrecomp(p[0]); + q_inv = MonReducePrecomp(q[0]); + mod_inv = MonReducePrecomp(modulus[0]); + + return true; +} + +bool RSACrypt::setPublicKey(const uint32_t *modulusi, int mod_limbsi, uint32_t ei) +{ + cleanup(); + + e = ei; + + mod_limbs = mod_limbsi; + + modulus = RakNet::OP_NEW_ARRAY(mod_limbs, __FILE__, __LINE__ ); + if (!modulus) return false; + + Set(modulus, mod_limbs, modulusi); + + mod_inv = MonReducePrecomp(modulus[0]); + + return true; +} + +bool RSACrypt::generatePrivateKey(uint32_t limbs) +{ + uint32_t *pf = (uint32_t *)alloca(limbs*4); + GenerateStrongPseudoPrime(pf, limbs/2); + + uint32_t *qf = (uint32_t *)alloca(limbs*4); + GenerateStrongPseudoPrime(qf, limbs/2); + + return setPrivateKey(pf, qf, limbs/2); +} + +uint32_t RSACrypt::getFactorLimbs() +{ + return factor_limbs; +} + +void RSACrypt::getPrivateP(uint32_t *po) +{ + Set(po, factor_limbs, p); +} + +void RSACrypt::getPrivateQ(uint32_t *qo) +{ + Set(qo, factor_limbs, q); +} + +uint32_t RSACrypt::getModLimbs() +{ + return mod_limbs; +} + +void RSACrypt::getPublicModulus(uint32_t *moduluso) +{ + Set(moduluso, mod_limbs, modulus); +} + +uint32_t RSACrypt::getPublicExponent() +{ + return e; +} + +bool RSACrypt::encrypt(uint32_t *ct, const uint32_t *pt) +{ + if (!e) return false; + + ExpMod(pt, mod_limbs, &e, 1, modulus, mod_limbs, mod_inv, ct); + + return true; +} + +bool RSACrypt::decrypt(uint32_t *pt, const uint32_t *ct) +{ + if (!e) return false; + + // CRT method + + // s_p = c ^ dP mod p + uint32_t *s_p = (uint32_t*)alloca(factor_limbs*4); + ExpMod(ct, mod_limbs, dP, factor_limbs, p, factor_limbs, p_inv, s_p); + + // s_q = c ^ dQ mod q + uint32_t *s_q = (uint32_t*)alloca(factor_limbs*4); + ExpMod(ct, mod_limbs, dQ, factor_limbs, q, factor_limbs, q_inv, s_q); + + // Garner's CRT recombination + + // s_p = qInv(s_p - s_q) mod p + if (Subtract(s_p, factor_limbs, s_q, factor_limbs)) + Add(s_p, factor_limbs, p, factor_limbs); + MulMod(factor_limbs, qInv, s_p, p, s_p); + + // pt = s_q + s_p*q + Multiply(factor_limbs, pt, s_p, q); + Add(pt, mod_limbs, s_q, factor_limbs); + + return true; +} + +#endif diff --git a/RakNet/Sources/RSACrypt.h b/RakNet/Sources/RSACrypt.h new file mode 100644 index 0000000..73fd140 --- /dev/null +++ b/RakNet/Sources/RSACrypt.h @@ -0,0 +1,45 @@ +#if !defined(_XBOX) && !defined(X360) + +#ifndef RSA_CRYPT_HPP +#define RSA_CRYPT_HPP + +#include "Platform.h" +#include "Export.h" + +class RAK_DLL_EXPORT RSACrypt +{ + uint32_t *p, p_inv, *q, q_inv, *qInv, *dP, *dQ, factor_limbs; + uint32_t e, *modulus, mod_inv, mod_limbs; + + void cleanup(); + +public: + RSACrypt(); + ~RSACrypt(); + +public: + bool setPrivateKey(const uint32_t *p, const uint32_t *q, int halfFactorLimbs); + bool setPublicKey(const uint32_t *modulus, int mod_limbs, uint32_t e); + +public: + // Bitsize is limbs * 32 + // Private key size is limbs/2 words + bool generatePrivateKey(uint32_t limbs); // limbs must be a multiple of 2 + +public: + uint32_t getFactorLimbs(); + void getPrivateP(uint32_t *p); // p buffer has factor_limbs + void getPrivateQ(uint32_t *q); // q buffer has factor_limbs + + uint32_t getModLimbs(); + void getPublicModulus(uint32_t *modulus); // modulus buffer has mod_limbs + uint32_t getPublicExponent(); + +public: + bool encrypt(uint32_t *ct, const uint32_t *pt); // pt limbs = mod_limbs + bool decrypt(uint32_t *pt, const uint32_t *ct); // ct limbs = mod_limbs +}; + +#endif // RSA_CRYPT_HPP + +#endif diff --git a/RakNet/Sources/RakAlloca.h b/RakNet/Sources/RakAlloca.h new file mode 100644 index 0000000..6e5ec37 --- /dev/null +++ b/RakNet/Sources/RakAlloca.h @@ -0,0 +1,16 @@ +#if defined(__FreeBSD__) +#include +#elif defined(_PS3) || defined(__PS3__) || defined(SN_TARGET_PS3) + +#elif defined(_XBOX) || defined(X360) + +#elif defined ( __APPLE__ ) || defined ( __APPLE_CC__ ) +#include +#include +#elif defined(_WIN32) +#include +#else +#include +// Alloca needed on Ubuntu apparently +#include +#endif diff --git a/RakNet/Sources/RakAssert.h b/RakNet/Sources/RakAssert.h new file mode 100644 index 0000000..f0cc96f --- /dev/null +++ b/RakNet/Sources/RakAssert.h @@ -0,0 +1,2 @@ +#include +#include "RakNetDefines.h" diff --git a/RakNet/Sources/RakMemoryOverride.cpp b/RakNet/Sources/RakMemoryOverride.cpp new file mode 100644 index 0000000..85629c9 --- /dev/null +++ b/RakNet/Sources/RakMemoryOverride.cpp @@ -0,0 +1,294 @@ +#include "RakMemoryOverride.h" +#include "RakAssert.h" +#include + +#ifdef _RAKNET_SUPPORT_DL_MALLOC +#include "rdlmalloc.h" +#endif + +#if defined(_PS3) || defined(__PS3__) || defined(SN_TARGET_PS3) + +#endif + +using namespace RakNet; + +#if _USE_RAK_MEMORY_OVERRIDE==1 + #if defined(malloc) + #pragma push_macro("malloc") + #undef malloc + #define RMO_MALLOC_UNDEF + #endif + + #if defined(realloc) + #pragma push_macro("realloc") + #undef realloc + #define RMO_REALLOC_UNDEF + #endif + + #if defined(free) + #pragma push_macro("free") + #undef free + #define RMO_FREE_UNDEF + #endif +#endif + +void DefaultOutOfMemoryHandler(const char *file, const long line) +{ + (void) file; + (void) line; + RakAssert(0); +} + +void * (*rakMalloc) (size_t size) = RakNet::_RakMalloc; +void* (*rakRealloc) (void *p, size_t size) = RakNet::_RakRealloc; +void (*rakFree) (void *p) = RakNet::_RakFree; +void* (*rakMalloc_Ex) (size_t size, const char *file, unsigned int line) = RakNet::_RakMalloc_Ex; +void* (*rakRealloc_Ex) (void *p, size_t size, const char *file, unsigned int line) = RakNet::_RakRealloc_Ex; +void (*rakFree_Ex) (void *p, const char *file, unsigned int line) = RakNet::_RakFree_Ex; +void (*notifyOutOfMemory) (const char *file, const long line)=DefaultOutOfMemoryHandler; +void * (*dlMallocMMap) (size_t size) = RakNet::_DLMallocMMap; +void * (*dlMallocDirectMMap) (size_t size) = RakNet::_DLMallocDirectMMap; +int (*dlMallocMUnmap) (void* ptr, size_t size) = RakNet::_DLMallocMUnmap; + +void SetMalloc( void* (*userFunction)(size_t size) ) +{ + rakMalloc=userFunction; +} +void SetRealloc( void* (*userFunction)(void *p, size_t size) ) +{ + rakRealloc=userFunction; +} +void SetFree( void (*userFunction)(void *p) ) +{ + rakFree=userFunction; +} +void SetMalloc_Ex( void* (*userFunction)(size_t size, const char *file, unsigned int line) ) +{ + rakMalloc_Ex=userFunction; +} +void SetRealloc_Ex( void* (*userFunction)(void *p, size_t size, const char *file, unsigned int line) ) +{ + rakRealloc_Ex=userFunction; +} +void SetFree_Ex( void (*userFunction)(void *p, const char *file, unsigned int line) ) +{ + rakFree_Ex=userFunction; +} +void SetNotifyOutOfMemory( void (*userFunction)(const char *file, const long line) ) +{ + notifyOutOfMemory=userFunction; +} +void SetDLMallocMMap( void* (*userFunction)(size_t size) ) +{ + dlMallocMMap=userFunction; +} +void SetDLMallocDirectMMap( void* (*userFunction)(size_t size) ) +{ + dlMallocDirectMMap=userFunction; +} +void SetDLMallocMUnmap( int (*userFunction)(void* ptr, size_t size) ) +{ + dlMallocMUnmap=userFunction; +} +void * (*GetMalloc()) (size_t size) +{ + return rakMalloc; +} +void * (*GetRealloc()) (void *p, size_t size) +{ + return rakRealloc; +} +void (*GetFree()) (void *p) +{ + return rakFree; +} +void * (*GetMalloc_Ex()) (size_t size, const char *file, unsigned int line) +{ + return rakMalloc_Ex; +} +void * (*GetRealloc_Ex()) (void *p, size_t size, const char *file, unsigned int line) +{ + return rakRealloc_Ex; +} +void (*GetFree_Ex()) (void *p, const char *file, unsigned int line) +{ + return rakFree_Ex; +} +void *(*GetDLMallocMMap())(size_t size) +{ + return dlMallocMMap; +} +void *(*GetDLMallocDirectMMap())(size_t size) +{ + return dlMallocDirectMMap; +} +int (*GetDLMallocMUnmap())(void* ptr, size_t size) +{ + return dlMallocMUnmap; +} +void* RakNet::_RakMalloc (size_t size) +{ + return malloc(size); +} + +void* RakNet::_RakRealloc (void *p, size_t size) +{ + return realloc(p,size); +} + +void RakNet::_RakFree (void *p) +{ + free(p); +} + +void* RakNet::_RakMalloc_Ex (size_t size, const char *file, unsigned int line) +{ + (void) file; + (void) line; + + return malloc(size); +} + +void* RakNet::_RakRealloc_Ex (void *p, size_t size, const char *file, unsigned int line) +{ + (void) file; + (void) line; + + return realloc(p,size); +} + +void RakNet::_RakFree_Ex (void *p, const char *file, unsigned int line) +{ + (void) file; + (void) line; + + free(p); +} +#ifdef _RAKNET_SUPPORT_DL_MALLOC +void * RakNet::_DLMallocMMap (size_t size) +{ + return RAK_MMAP_DEFAULT(size); +} +void * RakNet::_DLMallocDirectMMap (size_t size) +{ + return RAK_DIRECT_MMAP_DEFAULT(size); +} +int RakNet::_DLMallocMUnmap (void *p, size_t size) +{ + return RAK_MUNMAP_DEFAULT(p,size); +} + +static mspace rakNetFixedHeapMSpace=0; + +void* _DLMalloc(size_t size) +{ + return rak_mspace_malloc(rakNetFixedHeapMSpace,size); +} + +void* _DLRealloc(void *p, size_t size) +{ + return rak_mspace_realloc(rakNetFixedHeapMSpace,p,size); +} + +void _DLFree(void *p) +{ + if (p) + rak_mspace_free(rakNetFixedHeapMSpace,p); +} +void* _DLMalloc_Ex (size_t size, const char *file, unsigned int line) +{ + (void) file; + (void) line; + + return rak_mspace_malloc(rakNetFixedHeapMSpace,size); +} + +void* _DLRealloc_Ex (void *p, size_t size, const char *file, unsigned int line) +{ + (void) file; + (void) line; + + return rak_mspace_realloc(rakNetFixedHeapMSpace,p,size); +} + +void _DLFree_Ex (void *p, const char *file, unsigned int line) +{ + (void) file; + (void) line; + + if (p) + rak_mspace_free(rakNetFixedHeapMSpace,p); +} + +void UseRaknetFixedHeap(size_t initialCapacity, + void * (*yourMMapFunction) (size_t size), + void * (*yourDirectMMapFunction) (size_t size), + int (*yourMUnmapFunction) (void *p, size_t size)) +{ + SetDLMallocMMap(yourMMapFunction); + SetDLMallocDirectMMap(yourDirectMMapFunction); + SetDLMallocMUnmap(yourMUnmapFunction); + SetMalloc(_DLMalloc); + SetRealloc(_DLRealloc); + SetFree(_DLFree); + SetMalloc_Ex(_DLMalloc_Ex); + SetRealloc_Ex(_DLRealloc_Ex); + SetFree_Ex(_DLFree_Ex); + + rakNetFixedHeapMSpace=rak_create_mspace(initialCapacity, 0); +} +void FreeRakNetFixedHeap(void) +{ + if (rakNetFixedHeapMSpace) + { + rak_destroy_mspace(rakNetFixedHeapMSpace); + rakNetFixedHeapMSpace=0; + } + + SetMalloc(_RakMalloc); + SetRealloc(_RakRealloc); + SetFree(_RakFree); + SetMalloc_Ex(_RakMalloc_Ex); + SetRealloc_Ex(_RakRealloc_Ex); + SetFree_Ex(_RakFree_Ex); +} +#else +void * RakNet::_DLMallocMMap (size_t size) {(void) size; return 0;} +void * RakNet::_DLMallocDirectMMap (size_t size) {(void) size; return 0;} +int RakNet::_DLMallocMUnmap (void *p, size_t size) {(void) size; (void) p; return 0;} +void* _DLMalloc(size_t size) {(void) size; return 0;} +void* _DLRealloc(void *p, size_t size) {(void) p; (void) size; return 0;} +void _DLFree(void *p) {(void) p;} +void* _DLMalloc_Ex (size_t size, const char *file, unsigned int line) {(void) size; (void) file; (void) line; return 0;} +void* _DLRealloc_Ex (void *p, size_t size, const char *file, unsigned int line) {(void) p; (void) size; (void) file; (void) line; return 0;} +void _DLFree_Ex (void *p, const char *file, unsigned int line) {(void) p; (void) file; (void) line;} + +void UseRaknetFixedHeap(size_t initialCapacity, + void * (*yourMMapFunction) (size_t size), + void * (*yourDirectMMapFunction) (size_t size), + int (*yourMUnmapFunction) (void *p, size_t size)) +{ + (void) initialCapacity; + (void) yourMMapFunction; + (void) yourDirectMMapFunction; + (void) yourMUnmapFunction; +} +void FreeRakNetFixedHeap(void) {} +#endif + +#if _USE_RAK_MEMORY_OVERRIDE==1 + #if defined(RMO_MALLOC_UNDEF) + #pragma pop_macro("malloc") + #undef RMO_MALLOC_UNDEF + #endif + + #if defined(RMO_REALLOC_UNDEF) + #pragma pop_macro("realloc") + #undef RMO_REALLOC_UNDEF + #endif + + #if defined(RMO_FREE_UNDEF) + #pragma pop_macro("free") + #undef RMO_FREE_UNDEF + #endif +#endif diff --git a/RakNet/Sources/RakMemoryOverride.h b/RakNet/Sources/RakMemoryOverride.h new file mode 100644 index 0000000..fb122f1 --- /dev/null +++ b/RakNet/Sources/RakMemoryOverride.h @@ -0,0 +1,234 @@ +/// \file +/// \brief If _USE_RAK_MEMORY_OVERRIDE is defined, memory allocations go through rakMalloc, rakRealloc, and rakFree +/// +/// This file is part of RakNet Copyright 2003 Jenkins Software LLC +/// +/// Usage of RakNet is subject to the appropriate license agreement. + + +#ifndef __RAK_MEMORY_H +#define __RAK_MEMORY_H + +#include "Export.h" +#include "RakNetDefines.h" +#include + +#if defined(_PS3) || defined(__PS3__) || defined(SN_TARGET_PS3) + +#endif + +#include "RakAlloca.h" + +#if _USE_RAK_MEMORY_OVERRIDE==1 + #if defined(new) + #pragma push_macro("new") + #undef new + #define RMO_NEW_UNDEF + #endif +#endif + + +// These pointers are statically and globally defined in RakMemoryOverride.cpp +// Change them to point to your own allocators if you want. +// Use the functions for a DLL, or just reassign the variable if using source +extern RAK_DLL_EXPORT void * (*rakMalloc) (size_t size); +extern RAK_DLL_EXPORT void * (*rakRealloc) (void *p, size_t size); +extern RAK_DLL_EXPORT void (*rakFree) (void *p); +extern RAK_DLL_EXPORT void * (*rakMalloc_Ex) (size_t size, const char *file, unsigned int line); +extern RAK_DLL_EXPORT void * (*rakRealloc_Ex) (void *p, size_t size, const char *file, unsigned int line); +extern RAK_DLL_EXPORT void (*rakFree_Ex) (void *p, const char *file, unsigned int line); +extern RAK_DLL_EXPORT void (*notifyOutOfMemory) (const char *file, const long line); +extern RAK_DLL_EXPORT void * (*dlMallocMMap) (size_t size); +extern RAK_DLL_EXPORT void * (*dlMallocDirectMMap) (size_t size); +extern RAK_DLL_EXPORT int (*dlMallocMUnmap) (void* ptr, size_t size); + +// Change to a user defined allocation function +void RAK_DLL_EXPORT SetMalloc( void* (*userFunction)(size_t size) ); +void RAK_DLL_EXPORT SetRealloc( void* (*userFunction)(void *p, size_t size) ); +void RAK_DLL_EXPORT SetFree( void (*userFunction)(void *p) ); +void RAK_DLL_EXPORT SetMalloc_Ex( void* (*userFunction)(size_t size, const char *file, unsigned int line) ); +void RAK_DLL_EXPORT SetRealloc_Ex( void* (*userFunction)(void *p, size_t size, const char *file, unsigned int line) ); +void RAK_DLL_EXPORT SetFree_Ex( void (*userFunction)(void *p, const char *file, unsigned int line) ); +// Change to a user defined out of memory function +void RAK_DLL_EXPORT SetNotifyOutOfMemory( void (*userFunction)(const char *file, const long line) ); +void RAK_DLL_EXPORT SetDLMallocMMap( void* (*userFunction)(size_t size) ); +void RAK_DLL_EXPORT SetDLMallocDirectMMap( void* (*userFunction)(size_t size) ); +void RAK_DLL_EXPORT SetDLMallocMUnmap( int (*userFunction)(void* ptr, size_t size) ); + +extern RAK_DLL_EXPORT void * (*GetMalloc()) (size_t size); +extern RAK_DLL_EXPORT void * (*GetRealloc()) (void *p, size_t size); +extern RAK_DLL_EXPORT void (*GetFree()) (void *p); +extern RAK_DLL_EXPORT void * (*GetMalloc_Ex()) (size_t size, const char *file, unsigned int line); +extern RAK_DLL_EXPORT void * (*GetRealloc_Ex()) (void *p, size_t size, const char *file, unsigned int line); +extern RAK_DLL_EXPORT void (*GetFree_Ex()) (void *p, const char *file, unsigned int line); +extern RAK_DLL_EXPORT void *(*GetDLMallocMMap())(size_t size); +extern RAK_DLL_EXPORT void *(*GetDLMallocDirectMMap())(size_t size); +extern RAK_DLL_EXPORT int (*GetDLMallocMUnmap())(void* ptr, size_t size); + +namespace RakNet +{ + + template + RAK_DLL_EXPORT Type* OP_NEW(const char *file, unsigned int line) + { +#if _USE_RAK_MEMORY_OVERRIDE==1 + char *buffer = (char *) (GetMalloc_Ex())(sizeof(Type), file, line); + Type *t = new (buffer) Type; + return t; +#else + (void) file; + (void) line; + return new Type; +#endif + } + + template + RAK_DLL_EXPORT Type* OP_NEW_1(const char *file, unsigned int line, const P1 &p1) + { +#if _USE_RAK_MEMORY_OVERRIDE==1 + char *buffer = (char *) (GetMalloc_Ex())(sizeof(Type), file, line); + Type *t = new (buffer) Type(p1); + return t; +#else + (void) file; + (void) line; + return new Type(p1); +#endif + } + + template + RAK_DLL_EXPORT Type* OP_NEW_2(const char *file, unsigned int line, const P1 &p1, const P2 &p2) + { +#if _USE_RAK_MEMORY_OVERRIDE==1 + char *buffer = (char *) (GetMalloc_Ex())(sizeof(Type), file, line); + Type *t = new (buffer) Type(p1, p2); + return t; +#else + (void) file; + (void) line; + return new Type(p1, p2); +#endif + } + + template + RAK_DLL_EXPORT Type* OP_NEW_3(const char *file, unsigned int line, const P1 &p1, const P2 &p2, const P3 &p3) + { +#if _USE_RAK_MEMORY_OVERRIDE==1 + char *buffer = (char *) (GetMalloc_Ex())(sizeof(Type), file, line); + Type *t = new (buffer) Type(p1, p2, p3); + return t; +#else + (void) file; + (void) line; + return new Type(p1, p2, p3); +#endif + } + + template + RAK_DLL_EXPORT Type* OP_NEW_4(const char *file, unsigned int line, const P1 &p1, const P2 &p2, const P3 &p3, const P4 &p4) + { +#if _USE_RAK_MEMORY_OVERRIDE==1 + char *buffer = (char *) (GetMalloc_Ex())(sizeof(Type), file, line); + Type *t = new (buffer) Type(p1, p2, p3, p4); + return t; +#else + (void) file; + (void) line; + return new Type(p1, p2, p3, p4); +#endif + } + + + template + RAK_DLL_EXPORT Type* OP_NEW_ARRAY(const int count, const char *file, unsigned int line) + { + if (count==0) + return 0; + +#if _USE_RAK_MEMORY_OVERRIDE==1 +// Type *t; + char *buffer = (char *) (GetMalloc_Ex())(sizeof(int)+sizeof(Type)*count, file, line); + ((int*)buffer)[0]=count; + for (int i=0; i + RAK_DLL_EXPORT void OP_DELETE(Type *buff, const char *file, unsigned int line) + { +#if _USE_RAK_MEMORY_OVERRIDE==1 + if (buff==0) return; + buff->~Type(); + (GetFree_Ex())((char*)buff, file, line ); +#else + (void) file; + (void) line; + delete buff; +#endif + + } + + template + RAK_DLL_EXPORT void OP_DELETE_ARRAY(Type *buff, const char *file, unsigned int line) + { +#if _USE_RAK_MEMORY_OVERRIDE==1 + if (buff==0) + return; + + int count = ((int*)((char*)buff-sizeof(int)))[0]; + Type *t; + for (int i=0; i~Type(); + } + (GetFree_Ex())((char*)buff-sizeof(int), file, line ); +#else + (void) file; + (void) line; + delete [] buff; +#endif + + } + + void RAK_DLL_EXPORT * _RakMalloc (size_t size); + void RAK_DLL_EXPORT * _RakRealloc (void *p, size_t size); + void RAK_DLL_EXPORT _RakFree (void *p); + void RAK_DLL_EXPORT * _RakMalloc_Ex (size_t size, const char *file, unsigned int line); + void RAK_DLL_EXPORT * _RakRealloc_Ex (void *p, size_t size, const char *file, unsigned int line); + void RAK_DLL_EXPORT _RakFree_Ex (void *p, const char *file, unsigned int line); + void RAK_DLL_EXPORT * _DLMallocMMap (size_t size); + void RAK_DLL_EXPORT * _DLMallocDirectMMap (size_t size); + int RAK_DLL_EXPORT _DLMallocMUnmap (void *p, size_t size); + +} + +// Call to make RakNet allocate a large block of memory, and do all subsequent allocations in that memory block +// Initial and reallocations will be done through whatever function is pointed to by yourMMapFunction, and yourDirectMMapFunction (default is malloc) +// Allocations will be freed through whatever function is pointed to by yourMUnmapFunction (default free) +void UseRaknetFixedHeap(size_t initialCapacity, + void * (*yourMMapFunction) (size_t size) = RakNet::_DLMallocMMap, + void * (*yourDirectMMapFunction) (size_t size) = RakNet::_DLMallocDirectMMap, + int (*yourMUnmapFunction) (void *p, size_t size) = RakNet::_DLMallocMUnmap); + +// Free memory allocated from UseRaknetFixedHeap +void FreeRakNetFixedHeap(void); + +#if _USE_RAK_MEMORY_OVERRIDE==1 + #if defined(RMO_NEW_UNDEF) + #pragma pop_macro("new") + #undef RMO_NEW_UNDEF + #endif +#endif + +#endif diff --git a/RakNet/Sources/RakNetCommandParser.cpp b/RakNet/Sources/RakNetCommandParser.cpp new file mode 100644 index 0000000..58af991 --- /dev/null +++ b/RakNet/Sources/RakNetCommandParser.cpp @@ -0,0 +1,309 @@ +#include "NativeFeatureIncludes.h" +#if _RAKNET_SUPPORT_RakNetCommandParser==1 + +#include "RakNetCommandParser.h" +#include "TransportInterface.h" +#include "RakPeerInterface.h" +#include "BitStream.h" +#include "RakAssert.h" +#include +#include +#include + +#ifdef _MSC_VER +#pragma warning( push ) +#endif + +RakNetCommandParser::RakNetCommandParser() +{ + RegisterCommand(4, "Startup","( unsigned short maxConnections, int _threadSleepTimer, unsigned short localPort, const char *forceHostAddress );"); + RegisterCommand(0,"InitializeSecurity","();"); + RegisterCommand(0,"DisableSecurity","( void );"); + RegisterCommand(1,"AddToSecurityExceptionList","( const char *ip );"); + RegisterCommand(1,"RemoveFromSecurityExceptionList","( const char *ip );"); + RegisterCommand(1,"IsInSecurityExceptionList","( const char *ip );"); + RegisterCommand(1,"SetMaximumIncomingConnections","( unsigned short numberAllowed );"); + RegisterCommand(0,"GetMaximumIncomingConnections","( void ) const;"); + RegisterCommand(4,"Connect","( const char* host, unsigned short remotePort, const char *passwordData, int passwordDataLength );"); + RegisterCommand(2,"Disconnect","( unsigned int blockDuration, unsigned char orderingChannel=0 );"); + RegisterCommand(0,"IsActive","( void ) const;"); + RegisterCommand(0,"GetConnectionList","() const;"); + RegisterCommand(4,"CloseConnection","( const SystemAddress target, bool sendDisconnectionNotification, unsigned char orderingChannel=0 );"); + RegisterCommand(2,"IsConnected","( );"); + RegisterCommand(2,"GetIndexFromSystemAddress","( const SystemAddress systemAddress );"); + RegisterCommand(1,"GetSystemAddressFromIndex","( int index );"); + RegisterCommand(2,"AddToBanList","( const char *IP, RakNetTime milliseconds=0 );"); + RegisterCommand(1,"RemoveFromBanList","( const char *IP );"); + RegisterCommand(0,"ClearBanList","( void );"); + RegisterCommand(1,"IsBanned","( const char *IP );"); + RegisterCommand(2,"Ping1","( const SystemAddress target );"); + RegisterCommand(3,"Ping2","( const char* host, unsigned short remotePort, bool onlyReplyOnAcceptingConnections );"); + RegisterCommand(2,"GetAveragePing","( const SystemAddress systemAddress );"); + RegisterCommand(2,"GetLastPing","( const SystemAddress systemAddress ) const;"); + RegisterCommand(2,"GetLowestPing","( const SystemAddress systemAddress ) const;"); + RegisterCommand(1,"SetOccasionalPing","( bool doPing );"); + RegisterCommand(2,"SetOfflinePingResponse","( const char *data, const unsigned int length );"); + RegisterCommand(0,"GetInternalID","( void ) const;"); + RegisterCommand(2,"GetExternalID","( const SystemAddress target ) const;"); + RegisterCommand(3,"SetTimeoutTime","( RakNetTime timeMS, const SystemAddress target );"); + RegisterCommand(1,"SetMTUSize","( int size );"); + RegisterCommand(0,"GetMTUSize","( void ) const;"); + RegisterCommand(0,"GetNumberOfAddresses","( void );"); + RegisterCommand(1,"GetLocalIP","( unsigned int index );"); + RegisterCommand(1,"AllowConnectionResponseIPMigration","( bool allow );"); + RegisterCommand(4,"AdvertiseSystem","( const char *host, unsigned short remotePort, const char *data, int dataLength );"); + RegisterCommand(2,"SetIncomingPassword","( const char* passwordData, int passwordDataLength );"); + RegisterCommand(0,"GetIncomingPassword","( void );"); + RegisterCommand(3,"ApplyNetworkSimulator","( float packetloss, unsigned short minExtraPing, unsigned short extraPingVariance);"); + RegisterCommand(0,"IsNetworkSimulatorActive","( void );"); +} +RakNetCommandParser::~RakNetCommandParser() +{ +} +void RakNetCommandParser::SetRakPeerInterface(RakPeerInterface *rakPeer) +{ + peer=rakPeer; +} +bool RakNetCommandParser::OnCommand(const char *command, unsigned numParameters, char **parameterList, TransportInterface *transport, SystemAddress systemAddress, const char *originalString) +{ + (void) originalString; + (void) numParameters; + + if (peer==0) + return false; + + if (strcmp(command, "Startup")==0) + { + SocketDescriptor socketDescriptor((unsigned short)atoi(parameterList[1]), parameterList[3]); + ReturnResult(peer->Startup((unsigned short)atoi(parameterList[0]), atoi(parameterList[2]), &socketDescriptor, 1), command, transport, systemAddress); + } + else if (strcmp(command, "InitializeSecurity")==0) + { + peer->InitializeSecurity(0,0,0,0); + ReturnResult(command, transport, systemAddress); + } + else if (strcmp(command, "DisableSecurity")==0) + { + peer->DisableSecurity(); + ReturnResult(command, transport, systemAddress); + } + else if (strcmp(command, "AddToSecurityExceptionList")==0) + { + peer->AddToSecurityExceptionList(parameterList[1]); + ReturnResult(command, transport, systemAddress); + } + else if (strcmp(command, "RemoveFromSecurityExceptionList")==0) + { + peer->RemoveFromSecurityExceptionList(parameterList[1]); + ReturnResult(command, transport, systemAddress); + } + else if (strcmp(command, "IsInSecurityExceptionList")==0) + { + ReturnResult(peer->IsInSecurityExceptionList(parameterList[1]),command, transport, systemAddress); + } + else if (strcmp(command, "SetMaximumIncomingConnections")==0) + { + peer->SetMaximumIncomingConnections((unsigned short)atoi(parameterList[0])); + ReturnResult(command, transport, systemAddress); + } + else if (strcmp(command, "GetMaximumIncomingConnections")==0) + { + ReturnResult(peer->GetMaximumIncomingConnections(), command, transport, systemAddress); + } + else if (strcmp(command, "Connect")==0) + { + ReturnResult(peer->Connect(parameterList[0], (unsigned short)atoi(parameterList[1]),parameterList[2],atoi(parameterList[3])), command, transport, systemAddress); + } + else if (strcmp(command, "Disconnect")==0) + { + peer->Shutdown(atoi(parameterList[0]), (unsigned char)atoi(parameterList[1])); + ReturnResult(command, transport, systemAddress); + } + else if (strcmp(command, "IsActive")==0) + { + ReturnResult(peer->IsActive(), command, transport, systemAddress); + } + else if (strcmp(command, "GetConnectionList")==0) + { + SystemAddress remoteSystems[32]; + unsigned short count=32; + unsigned i; + if (peer->GetConnectionList(remoteSystems, &count)) + { + if (count==0) + { + transport->Send(systemAddress, "GetConnectionList() returned no systems connected.\r\n"); + } + else + { + transport->Send(systemAddress, "GetConnectionList() returned:\r\n"); + for (i=0; i < count; i++) + { + char str1[64]; + remoteSystems[i].ToString(false, str1); + transport->Send(systemAddress, "%i %s %i:%i\r\n", i, str1, remoteSystems[i].binaryAddress, remoteSystems[i].port); + } + } + } + else + transport->Send(systemAddress, "GetConnectionList() returned false.\r\n"); + } + else if (strcmp(command, "CloseConnection")==0) + { + peer->CloseConnection(IntegersToSystemAddress(atoi(parameterList[0]), atoi(parameterList[1])),atoi(parameterList[2])!=0,(unsigned char)atoi(parameterList[3])); + ReturnResult(command, transport, systemAddress); + } + else if (strcmp(command, "IsConnected")==0) + { + ReturnResult(peer->IsConnected(IntegersToSystemAddress(atoi(parameterList[0]), atoi(parameterList[1]))), command, transport, systemAddress); + } + else if (strcmp(command, "GetIndexFromSystemAddress")==0) + { + ReturnResult(peer->GetIndexFromSystemAddress(IntegersToSystemAddress(atoi(parameterList[0]), atoi(parameterList[1]))), command, transport, systemAddress); + } + else if (strcmp(command, "GetSystemAddressFromIndex")==0) + { + ReturnResult(peer->GetSystemAddressFromIndex(atoi(parameterList[0])), command, transport, systemAddress); + } + else if (strcmp(command, "AddToBanList")==0) + { + peer->AddToBanList(parameterList[0], atoi(parameterList[1])); + ReturnResult(command, transport, systemAddress); + } + else if (strcmp(command, "RemoveFromBanList")==0) + { + peer->RemoveFromBanList(parameterList[0]); + ReturnResult(command, transport, systemAddress); + } + else if (strcmp(command, "ClearBanList")==0) + { + peer->ClearBanList(); + ReturnResult(command, transport, systemAddress); + } + else if (strcmp(command, "IsBanned")==0) + { + ReturnResult(peer->IsBanned(parameterList[0]), command, transport, systemAddress); + } + else if (strcmp(command, "Ping1")==0) + { + peer->Ping(IntegersToSystemAddress(atoi(parameterList[0]), atoi(parameterList[1]))); + ReturnResult(command, transport, systemAddress); + } + else if (strcmp(command, "Ping2")==0) + { + peer->Ping(parameterList[0], (unsigned short) atoi(parameterList[1]), atoi(parameterList[2])!=0); + ReturnResult(command, transport, systemAddress); + } + else if (strcmp(command, "GetAveragePing")==0) + { + ReturnResult(peer->GetAveragePing(IntegersToSystemAddress(atoi(parameterList[0]), atoi(parameterList[1]))), command, transport, systemAddress); + } + else if (strcmp(command, "GetLastPing")==0) + { + ReturnResult(peer->GetLastPing(IntegersToSystemAddress(atoi(parameterList[0]), atoi(parameterList[1]))), command, transport, systemAddress); + } + else if (strcmp(command, "GetLowestPing")==0) + { + ReturnResult(peer->GetLowestPing(IntegersToSystemAddress(atoi(parameterList[0]), atoi(parameterList[1]))), command, transport, systemAddress); + } + else if (strcmp(command, "SetOccasionalPing")==0) + { + peer->SetOccasionalPing(atoi(parameterList[0])!=0); + ReturnResult(command, transport, systemAddress); + } + else if (strcmp(command, "SetOfflinePingResponse")==0) + { + peer->SetOfflinePingResponse(parameterList[0], atoi(parameterList[1])); + ReturnResult(command, transport, systemAddress); + } + else if (strcmp(command, "GetInternalID")==0) + { + ReturnResult(peer->GetInternalID(), command, transport, systemAddress); + } + else if (strcmp(command, "GetExternalID")==0) + { + ReturnResult(peer->GetExternalID(IntegersToSystemAddress(atoi(parameterList[0]), atoi(parameterList[1]))), command, transport, systemAddress); + } + else if (strcmp(command, "SetTimeoutTime")==0) + { + peer->SetTimeoutTime(atoi(parameterList[0]), IntegersToSystemAddress(atoi(parameterList[0]), atoi(parameterList[1]))); + ReturnResult(command, transport, systemAddress); + } + /* + else if (strcmp(command, "SetMTUSize")==0) + { + ReturnResult(peer->SetMTUSize(atoi(parameterList[0]), UNASSIGNED_SYSTEM_ADDRESS), command, transport, systemAddress); + } + */ + else if (strcmp(command, "GetMTUSize")==0) + { + ReturnResult(peer->GetMTUSize(UNASSIGNED_SYSTEM_ADDRESS), command, transport, systemAddress); + } + else if (strcmp(command, "GetNumberOfAddresses")==0) + { + ReturnResult((int)peer->GetNumberOfAddresses(), command, transport, systemAddress); + } + else if (strcmp(command, "GetLocalIP")==0) + { + ReturnResult((char*) peer->GetLocalIP(atoi(parameterList[0])), command, transport, systemAddress); + } + else if (strcmp(command, "AllowConnectionResponseIPMigration")==0) + { + peer->AllowConnectionResponseIPMigration(atoi(parameterList[0])!=0); + ReturnResult(command, transport, systemAddress); + } + else if (strcmp(command, "AdvertiseSystem")==0) + { + peer->AdvertiseSystem(parameterList[0], (unsigned short) atoi(parameterList[1]),parameterList[2],atoi(parameterList[3])); + ReturnResult(command, transport, systemAddress); + } + else if (strcmp(command, "ApplyNetworkSimulator")==0) + { + peer->ApplyNetworkSimulator((float) atof(parameterList[0]), (unsigned short) atoi(parameterList[1]),(unsigned short) atoi(parameterList[2])); + ReturnResult(command, transport, systemAddress); + } + else if (strcmp(command, "IsNetworkSimulatorActive")==0) + { + ReturnResult(peer->IsNetworkSimulatorActive(), command, transport, systemAddress); + } + else if (strcmp(command, "SetIncomingPassword")==0) + { + peer->SetIncomingPassword(parameterList[0], atoi(parameterList[1])); + ReturnResult(command, transport, systemAddress); + } + else if (strcmp(command, "GetIncomingPassword")==0) + { + char password[256]; + int passwordLength; + peer->GetIncomingPassword(password, &passwordLength); + if (passwordLength) + ReturnResult((char*)password, command, transport, systemAddress); + else + ReturnResult(0, command, transport, systemAddress); + } + + return true; +} +const char *RakNetCommandParser::GetName(void) const +{ + return "RakNet"; +} +void RakNetCommandParser::SendHelp(TransportInterface *transport, SystemAddress systemAddress) +{ + if (peer) + { + transport->Send(systemAddress, "The RakNet parser provides mirror functions to RakPeer\r\n"); + transport->Send(systemAddress, "SystemAddresss take two parameters: send .\r\n"); + transport->Send(systemAddress, "For bool, send 1 or 0.\r\n"); + } + else + { + transport->Send(systemAddress, "Parser not active. Call SetRakPeerInterface.\r\n"); + } +} + +#ifdef _MSC_VER +#pragma warning( pop ) +#endif + +#endif // _RAKNET_SUPPORT_* diff --git a/RakNet/Sources/RakNetCommandParser.h b/RakNet/Sources/RakNetCommandParser.h new file mode 100644 index 0000000..d4ce34c --- /dev/null +++ b/RakNet/Sources/RakNetCommandParser.h @@ -0,0 +1,55 @@ +/// \file +/// \brief Contains RakNetCommandParser , used to send commands to an instance of RakPeer +/// +/// This file is part of RakNet Copyright 2003 Jenkins Software LLC +/// +/// Usage of RakNet is subject to the appropriate license agreement. + +#include "NativeFeatureIncludes.h" +#if _RAKNET_SUPPORT_RakNetCommandParser==1 + +#ifndef __RAKNET_COMMAND_PARSER +#define __RAKNET_COMMAND_PARSER + +#include "CommandParserInterface.h" +#include "Export.h" +class RakPeerInterface; + +/// \brief This allows a console client to call most of the functions in RakPeer +class RAK_DLL_EXPORT RakNetCommandParser : public CommandParserInterface +{ +public: + RakNetCommandParser(); + ~RakNetCommandParser(); + + /// 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, 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, SystemAddress systemAddress); + + /// Records the instance of RakPeer to perform the desired commands on + /// \param[in] rakPeer The RakPeer instance, or a derived class (e.g. RakPeer or RakPeer) + void SetRakPeerInterface(RakPeerInterface *rakPeer); +protected: + + /// Which instance of RakPeer we are working on. Set from SetRakPeerInterface() + RakPeerInterface *peer; +}; + +#endif + +#endif // _RAKNET_SUPPORT_* diff --git a/RakNet/Sources/RakNetDefines.h b/RakNet/Sources/RakNetDefines.h new file mode 100644 index 0000000..c329697 --- /dev/null +++ b/RakNet/Sources/RakNetDefines.h @@ -0,0 +1,108 @@ +#ifndef __RAKNET_DEFINES_H +#define __RAKNET_DEFINES_H + +// If you want to change these defines, put them in RakNetDefinesOverrides so your changes are not lost when updating RakNet +// The user should not edit this file +#include "RakNetDefinesOverrides.h" + +/// Define __GET_TIME_64BIT to have RakNetTime use a 64, rather than 32 bit value. A 32 bit value will overflow after about 5 weeks. +/// However, this doubles the bandwidth use for sending times, so don't do it unless you have a reason to. +/// Comment out if you are using the iPod Touch TG. See http://www.jenkinssoftware.com/forum/index.php?topic=2717.0 +#ifndef __GET_TIME_64BIT +#define __GET_TIME_64BIT 1 +#endif + +/// Makes RakNet threadsafe +/// Do not undefine! +#define _RAKNET_THREADSAFE + +/// Define __BITSTREAM_NATIVE_END to NOT support endian swapping in the BitStream class. This is faster and is what you should use +/// unless you actually plan to have different endianness systems connect to each other +/// Enabled by default. +// #define __BITSTREAM_NATIVE_END + +/// Maximum (stack) size to use with _alloca before using new and delete instead. +#ifndef MAX_ALLOCA_STACK_ALLOCATION +#define MAX_ALLOCA_STACK_ALLOCATION 1048576 +#endif + +// Use WaitForSingleObject instead of sleep. +// Defining it plays nicer with other systems, and uses less CPU, but gives worse RakNet performance +// Undefining it uses more CPU time, but is more responsive and faster. +#ifndef _WIN32_WCE +#define USE_WAIT_FOR_MULTIPLE_EVENTS +#endif + +/// Uncomment to use RakMemoryOverride for custom memory tracking +/// See RakMemoryOverride.h. +#ifndef _USE_RAK_MEMORY_OVERRIDE +#define _USE_RAK_MEMORY_OVERRIDE 1 +#endif + +/// If defined, OpenSSL is enabled for the class TCPInterface +/// This is necessary to use the SendEmail class with Google POP servers +/// Note that OpenSSL carries its own license restrictions that you should be aware of. If you don't agree, don't enable this define +/// This also requires that you enable header search paths to DependentExtensions\openssl-0.9.8g +// #define OPEN_SSL_CLIENT_SUPPORT + +/// Threshold at which to do a malloc / free rather than pushing data onto a fixed stack for the bitstream class +/// Arbitrary size, just picking something likely to be larger than most packets +#ifndef BITSTREAM_STACK_ALLOCATION_SIZE +#define BITSTREAM_STACK_ALLOCATION_SIZE 256 +#endif + +// Redefine if you want to disable or change the target for debug RAKNET_DEBUG_PRINTF +#ifndef RAKNET_DEBUG_PRINTF +#define RAKNET_DEBUG_PRINTF printf +#endif + +// 16 * 4 * 8 = 512 bit. Used for InitializeSecurity() +#ifndef RAKNET_RSA_FACTOR_LIMBS +#define RAKNET_RSA_FACTOR_LIMBS 16 +#endif + +// Enable to support peer to peer with NetworkIDs. Disable to save memory if doing client/server only +#ifndef NETWORK_ID_SUPPORTS_PEER_TO_PEER +#define NETWORK_ID_SUPPORTS_PEER_TO_PEER 1 +#endif + +// O(1) instead of O(log2n) but takes more memory if less than 1/3 of the mappings are used. +// Only supported if NETWORK_ID_SUPPORTS_PEER_TO_PEER is commented out +// #define NETWORK_ID_USE_PTR_TABLE + +// Maximum number of local IP addresses supported +#ifndef MAXIMUM_NUMBER_OF_INTERNAL_IDS +#define MAXIMUM_NUMBER_OF_INTERNAL_IDS 10 +#endif + +#ifndef RakAssert +#if defined(_XBOX) || defined(X360) + +#else +#if defined(_DEBUG) +#define RakAssert(x) assert(x); +#else +#define RakAssert(x) +#endif +#endif +#endif + +/// This controls the amount of memory used per connection. If more than this many datagrams are sent without an ack, then the ack has no effect +#ifndef DATAGRAM_MESSAGE_ID_ARRAY_LENGTH +#define DATAGRAM_MESSAGE_ID_ARRAY_LENGTH 512 +#endif + +/// This is the maximum number of reliable user messages the system will track to unresponsive systems +/// A good value is the maximum number of reliable messages you will send over 10 seconds +#ifndef RESEND_BUFFER_ARRAY_LENGTH +#define RESEND_BUFFER_ARRAY_LENGTH 512 +#define RESEND_BUFFER_ARRAY_MASK 0x1FF +#endif + +/// Uncomment if you want to link in the DLMalloc library to use with RakMemoryOverride +// #define _LINK_DL_MALLOC + +/// DO NOT USE, doesn't work +// #define USE_THREADED_SEND + +#endif // __RAKNET_DEFINES_H diff --git a/RakNet/Sources/RakNetDefinesOverrides.h b/RakNet/Sources/RakNetDefinesOverrides.h new file mode 100644 index 0000000..913357a --- /dev/null +++ b/RakNet/Sources/RakNetDefinesOverrides.h @@ -0,0 +1,2 @@ +// USER EDITABLE FILE + diff --git a/RakNet/Sources/RakNetSmartPtr.h b/RakNet/Sources/RakNetSmartPtr.h new file mode 100644 index 0000000..a1d4e4b --- /dev/null +++ b/RakNet/Sources/RakNetSmartPtr.h @@ -0,0 +1,168 @@ +#ifndef __RAKNET_SMART_PTR_H +#define __RAKNET_SMART_PTR_H + +// From http://www.codeproject.com/KB/cpp/SmartPointers.aspx +// with bugs fixed + +#include "RakMemoryOverride.h" +#include "Export.h" + +//static int allocCount=0; +//static int deallocCount=0; + +class RAK_DLL_EXPORT ReferenceCounter +{ +private: + int refCount; + +public: + ReferenceCounter() {refCount=0;} + ~ReferenceCounter() {} + void AddRef() {refCount++;} + int Release() {return --refCount;} + int GetRefCount(void) const {return refCount;} +}; + +template < typename T > class RAK_DLL_EXPORT RakNetSmartPtr +{ +private: + T* ptr; // pointer + ReferenceCounter* reference; // Reference refCount + +public: + RakNetSmartPtr() : ptr(0), reference(0) + { + // Do not allocate by default, wasteful if we just have a list of preallocated and unassigend smart pointers + } + + RakNetSmartPtr(T* pValue) : ptr(pValue) + { + reference = RakNet::OP_NEW(__FILE__,__LINE__); + reference->AddRef(); + +// allocCount+=2; +// printf("allocCount=%i deallocCount=%i Line=%i\n",allocCount, deallocCount, __LINE__); + } + + RakNetSmartPtr(const RakNetSmartPtr& sp) : ptr(sp.ptr), reference(sp.reference) + { + if (reference) + reference->AddRef(); + } + + ~RakNetSmartPtr() + { + if(reference && reference->Release() == 0) + { + RakNet::OP_DELETE(ptr, __FILE__, __LINE__); + RakNet::OP_DELETE(reference, __FILE__, __LINE__); + +// deallocCount+=2; +// printf("allocCount=%i deallocCount=%i Line=%i\n",allocCount, deallocCount, __LINE__); + } + } + + bool IsNull(void) const + { + return ptr==0; + } + + void SetNull(void) + { + if(reference && reference->Release() == 0) + { + RakNet::OP_DELETE(ptr, __FILE__, __LINE__); + RakNet::OP_DELETE(reference, __FILE__, __LINE__); + +// deallocCount+=2; +// printf("allocCount=%i deallocCount=%i Line=%i\n",allocCount, deallocCount, __LINE__); + } + ptr=0; + reference=0; + } + + bool IsUnique(void) const + { + return reference->GetRefCount()==1; + } + + // Allow you to change the values of the internal contents of the pointer, without changing what is pointed to by other instances of the smart pointer + void Clone(bool copyContents) + { + if (IsUnique()==false) + { + reference->Release(); + + reference = RakNet::OP_NEW(__FILE__,__LINE__); + reference->AddRef(); + T* oldPtr=ptr; + ptr=RakNet::OP_NEW(__FILE__,__LINE__); + if (copyContents) + *ptr=*oldPtr; + } + } + + int GetRefCount(void) const + { + return reference->GetRefCount(); + } + + T& operator* () + { + return *ptr; + } + + const T& operator* () const + { + return *ptr; + } + + T* operator-> () + { + return ptr; + } + + const T* operator-> () const + { + return ptr; + } + + bool operator == (const RakNetSmartPtr& sp) + { + return ptr == sp.ptr; + } + bool operator<( const RakNetSmartPtr &right ) {return ptr < right.ptr;} + bool operator>( const RakNetSmartPtr &right ) {return ptr > right.ptr;} + + bool operator != (const RakNetSmartPtr& sp) + { + return ptr != sp.ptr; + } + + RakNetSmartPtr& operator = (const RakNetSmartPtr& sp) + { + // Assignment operator + + if (this != &sp) // Avoid self assignment + { + if(reference && reference->Release() == 0) + { + RakNet::OP_DELETE(ptr, __FILE__, __LINE__); + RakNet::OP_DELETE(reference, __FILE__, __LINE__); + +// deallocCount+=2; +// printf("allocCount=%i deallocCount=%i Line=%i\n",allocCount, deallocCount, __LINE__); + } + + ptr = sp.ptr; + reference = sp.reference; + if (reference) + reference->AddRef(); + } + return *this; + } + + +}; + +#endif diff --git a/RakNet/Sources/RakNetSocket.cpp b/RakNet/Sources/RakNetSocket.cpp new file mode 100644 index 0000000..e249f5c --- /dev/null +++ b/RakNet/Sources/RakNetSocket.cpp @@ -0,0 +1,22 @@ +#include "RakNetSocket.h" +#include "SocketIncludes.h" + +RakNetSocket::RakNetSocket() { + s = (unsigned int)-1; +#if defined (_WIN32) && defined(USE_WAIT_FOR_MULTIPLE_EVENTS) + recvEvent=INVALID_HANDLE_VALUE; +#endif +} +RakNetSocket::~RakNetSocket() +{ + if ((SOCKET)s != (SOCKET)-1) + closesocket(s); + +#if defined (_WIN32) && defined(USE_WAIT_FOR_MULTIPLE_EVENTS) + if (recvEvent!=INVALID_HANDLE_VALUE) + { + CloseHandle( recvEvent ); + recvEvent = INVALID_HANDLE_VALUE; + } +#endif +} diff --git a/RakNet/Sources/RakNetSocket.h b/RakNet/Sources/RakNetSocket.h new file mode 100644 index 0000000..e0feed9 --- /dev/null +++ b/RakNet/Sources/RakNetSocket.h @@ -0,0 +1,27 @@ +#ifndef __RAKNET_SOCKET_H +#define __RAKNET_SOCKET_H + +#include "RakNetTypes.h" +#include "RakNetDefines.h" +#include "Export.h" +// #include "SocketIncludes.h" + +struct RAK_DLL_EXPORT RakNetSocket +{ + RakNetSocket(); + ~RakNetSocket(); + // SocketIncludes.h includes Windows.h, which messes up a lot of compiles + // SOCKET s; + unsigned int s; + unsigned int userConnectionSocketIndex; + SystemAddress boundAddress; +#if defined (_WIN32) && defined(USE_WAIT_FOR_MULTIPLE_EVENTS) + void* recvEvent; +#endif + // Only need to set for the PS3, when using signaling. + // Connect with the port returned by signaling. Set this to whatever port RakNet was actually started on + unsigned short remotePortRakNetWasStartedOn_PS3; + +}; + +#endif diff --git a/RakNet/Sources/RakNetStatistics.cpp b/RakNet/Sources/RakNetStatistics.cpp new file mode 100644 index 0000000..54c4dae --- /dev/null +++ b/RakNet/Sources/RakNetStatistics.cpp @@ -0,0 +1,149 @@ +/// \file +/// +/// This file is part of RakNet Copyright 2003 Jenkins Software LLC +/// +/// Usage of RakNet is subject to the appropriate license agreement. + + +#include "RakNetStatistics.h" +#include // sprintf +#include "BitStream.h" // BITS_TO_BYTES +#include "GetTime.h" +#include "RakString.h" + +using namespace RakNet; + +// Verbosity level currently supports 0 (low), 1 (medium), 2 (high) +// Buffer must be hold enough to hold the output string. See the source to get an idea of how many bytes will be output +void RAK_DLL_EXPORT StatisticsToString( RakNetStatistics *s, char *buffer, int verbosityLevel ) +{ + if ( s == 0 ) + { + sprintf( buffer, "stats is a NULL pointer in statsToString\n" ); + return ; + } + + if (verbosityLevel==0) + { + sprintf(buffer, + "Bytes per second sent %" PRINTF_64_BIT_MODIFIER "u\n" + "Bytes per second received %" PRINTF_64_BIT_MODIFIER "u\n" + "Current packetloss %.0f%%\n", + s->valueOverLastSecond[ACTUAL_BYTES_SENT], + s->valueOverLastSecond[ACTUAL_BYTES_RECEIVED], + s->packetlossLastSecond + ); + } + else if (verbosityLevel==1) + { + sprintf(buffer, + "Actual bytes per second sent %" PRINTF_64_BIT_MODIFIER "u\n" + "Actual bytes per second received %" PRINTF_64_BIT_MODIFIER "u\n" + "Message bytes per second queued %" PRINTF_64_BIT_MODIFIER "u\n" + "Total actual bytes sent %" PRINTF_64_BIT_MODIFIER "u\n" + "Total actual bytes received %" PRINTF_64_BIT_MODIFIER "u\n" + "Total message bytes queued %" PRINTF_64_BIT_MODIFIER "u\n" + "Current packetloss %.0f%%\n" + "Average packetloss %.0f%%\n" + "Elapsed connection time in seconds %" PRINTF_64_BIT_MODIFIER "u\n", + s->valueOverLastSecond[ACTUAL_BYTES_SENT], + s->valueOverLastSecond[ACTUAL_BYTES_RECEIVED], + s->valueOverLastSecond[USER_MESSAGE_BYTES_PUSHED], + s->runningTotal[ACTUAL_BYTES_SENT], + s->runningTotal[ACTUAL_BYTES_RECEIVED], + s->runningTotal[USER_MESSAGE_BYTES_PUSHED], + s->packetlossLastSecond, + s->packetlossTotal, + (uint64_t)((RakNet::GetTimeUS()-s->connectionStartTime)/1000000) + ); + + if (s->BPSLimitByCongestionControl!=0) + { + char buff2[128]; + sprintf(buff2, + "Send capacity %" PRINTF_64_BIT_MODIFIER "u (%.0f%%)\n", + s->BPSLimitByCongestionControl, + 100.0f * s->valueOverLastSecond[ACTUAL_BYTES_SENT] / s->BPSLimitByCongestionControl + ); + strcat(buffer,buff2); + } + if (s->BPSLimitByOutgoingBandwidthLimit!=0) + { + char buff2[128]; + sprintf(buff2, + "Send limit %" PRINTF_64_BIT_MODIFIER "u (%.0f%%)\n", + s->BPSLimitByOutgoingBandwidthLimit, + 100.0f * s->valueOverLastSecond[ACTUAL_BYTES_SENT] / s->BPSLimitByOutgoingBandwidthLimit + ); + strcat(buffer,buff2); + } + } + else + { + sprintf(buffer, + "Actual bytes per second sent %" PRINTF_64_BIT_MODIFIER "u\n" + "Actual bytes per second received %" PRINTF_64_BIT_MODIFIER "u\n" + "Message bytes per second sent %" PRINTF_64_BIT_MODIFIER "u\n" + "Message bytes per second resent %" PRINTF_64_BIT_MODIFIER "u\n" + "Message bytes per second queued %" PRINTF_64_BIT_MODIFIER "u\n" + "Message bytes per second processed %" PRINTF_64_BIT_MODIFIER "u\n" + "Message bytes per second ignored %" PRINTF_64_BIT_MODIFIER "u\n" + "Total bytes sent %" PRINTF_64_BIT_MODIFIER "u\n" + "Total bytes received %" PRINTF_64_BIT_MODIFIER "u\n" + "Total message bytes sent %" PRINTF_64_BIT_MODIFIER "u\n" + "Total message bytes resent %" PRINTF_64_BIT_MODIFIER "u\n" + "Total message bytes queued %" PRINTF_64_BIT_MODIFIER "u\n" + "Total message bytes received %" PRINTF_64_BIT_MODIFIER "u\n" + "Total message bytes ignored %" PRINTF_64_BIT_MODIFIER "u\n" + "Messages in send buffer, by priority %i,%i,%i,%i\n" + "Bytes in send buffer, by priority %i,%i,%i,%i\n" + "Messages in resend buffer %i\n" + "Bytes in resend buffer %" PRINTF_64_BIT_MODIFIER "u\n" + "Current packetloss %.0f%%\n" + "Average packetloss %.0f%%\n" + "Elapsed connection time in seconds %" PRINTF_64_BIT_MODIFIER "u\n", + s->valueOverLastSecond[ACTUAL_BYTES_SENT], + s->valueOverLastSecond[ACTUAL_BYTES_RECEIVED], + s->valueOverLastSecond[USER_MESSAGE_BYTES_SENT], + s->valueOverLastSecond[USER_MESSAGE_BYTES_RESENT], + s->valueOverLastSecond[USER_MESSAGE_BYTES_PUSHED], + s->valueOverLastSecond[USER_MESSAGE_BYTES_RECEIVED_PROCESSED], + s->valueOverLastSecond[USER_MESSAGE_BYTES_RECEIVED_IGNORED], + s->runningTotal[ACTUAL_BYTES_SENT], + s->runningTotal[ACTUAL_BYTES_RECEIVED], + s->runningTotal[USER_MESSAGE_BYTES_SENT], + s->runningTotal[USER_MESSAGE_BYTES_RESENT], + s->runningTotal[USER_MESSAGE_BYTES_PUSHED], + s->runningTotal[USER_MESSAGE_BYTES_RECEIVED_PROCESSED], + s->runningTotal[USER_MESSAGE_BYTES_RECEIVED_IGNORED], + s->messageInSendBuffer[IMMEDIATE_PRIORITY],s->messageInSendBuffer[HIGH_PRIORITY],s->messageInSendBuffer[MEDIUM_PRIORITY],s->messageInSendBuffer[LOW_PRIORITY], + (unsigned int) s->bytesInSendBuffer[IMMEDIATE_PRIORITY],(unsigned int) s->bytesInSendBuffer[HIGH_PRIORITY],(unsigned int) s->bytesInSendBuffer[MEDIUM_PRIORITY],(unsigned int) s->bytesInSendBuffer[LOW_PRIORITY], + s->messagesInResendBuffer, + s->bytesInResendBuffer, + s->packetlossLastSecond, + s->packetlossTotal, + (uint64_t)((RakNet::GetTimeUS()-s->connectionStartTime)/1000000) + ); + + if (s->BPSLimitByCongestionControl!=0) + { + char buff2[128]; + sprintf(buff2, + "Send capacity %" PRINTF_64_BIT_MODIFIER "u (%.0f%%)\n", + s->BPSLimitByCongestionControl, + 100.0f * s->valueOverLastSecond[ACTUAL_BYTES_SENT] / s->BPSLimitByCongestionControl + ); + strcat(buffer,buff2); + } + if (s->BPSLimitByOutgoingBandwidthLimit!=0) + { + char buff2[128]; + sprintf(buff2, + "Send limit %" PRINTF_64_BIT_MODIFIER "u (%.0f%%)\n", + s->BPSLimitByOutgoingBandwidthLimit, + 100.0f * s->valueOverLastSecond[ACTUAL_BYTES_SENT] / s->BPSLimitByOutgoingBandwidthLimit + ); + strcat(buffer,buff2); + } + } +} diff --git a/RakNet/Sources/RakNetStatistics.h b/RakNet/Sources/RakNetStatistics.h new file mode 100644 index 0000000..909c401 --- /dev/null +++ b/RakNet/Sources/RakNetStatistics.h @@ -0,0 +1,82 @@ +/// \file +/// \brief A structure that holds all statistical data returned by RakNet. +/// +/// This file is part of RakNet Copyright 2003 Jenkins Software LLC +/// +/// Usage of RakNet is subject to the appropriate license agreement. + + + +#ifndef __RAK_NET_STATISTICS_H +#define __RAK_NET_STATISTICS_H + +#include "PacketPriority.h" +#include "Export.h" +#include "RakNetTypes.h" + +enum RNSPerSecondMetrics +{ + USER_MESSAGE_BYTES_PUSHED, + USER_MESSAGE_BYTES_SENT, + USER_MESSAGE_BYTES_RESENT, + USER_MESSAGE_BYTES_RECEIVED_PROCESSED, + USER_MESSAGE_BYTES_RECEIVED_IGNORED, + ACTUAL_BYTES_SENT, + ACTUAL_BYTES_RECEIVED, + RNS_PER_SECOND_METRICS_COUNT +}; + +/// \brief Network Statisics Usage +/// +/// Store Statistics information related to network usage +struct RAK_DLL_EXPORT RakNetStatistics +{ + uint64_t valueOverLastSecond[RNS_PER_SECOND_METRICS_COUNT]; + uint64_t runningTotal[RNS_PER_SECOND_METRICS_COUNT]; + + RakNetTimeUS connectionStartTime; + + uint64_t BPSLimitByCongestionControl; + bool isLimitedByCongestionControl; + + uint64_t BPSLimitByOutgoingBandwidthLimit; + bool isLimitedByOutgoingBandwidthLimit; + + unsigned int messageInSendBuffer[NUMBER_OF_PRIORITIES]; + double bytesInSendBuffer[NUMBER_OF_PRIORITIES]; + + unsigned int messagesInResendBuffer; + uint64_t bytesInResendBuffer; + + float packetlossLastSecond, packetlossTotal; + + RakNetStatistics& operator +=(const RakNetStatistics& other) + { + unsigned i; + for (i=0; i < NUMBER_OF_PRIORITIES; i++) + { + messageInSendBuffer[i]+=other.messageInSendBuffer[i]; + bytesInSendBuffer[i]+=other.bytesInSendBuffer[i]; + } + + for (i=0; i < RNS_PER_SECOND_METRICS_COUNT; i++) + { + valueOverLastSecond[i]+=other.valueOverLastSecond[i]; + runningTotal[i]+=other.runningTotal[i]; + } + + return *this; + } +}; + +/// Verbosity level currently supports 0 (low), 1 (medium), 2 (high) +/// \param[in] s The Statistical information to format out +/// \param[in] buffer The buffer containing a formated report +/// \param[in] verbosityLevel +/// 0 low +/// 1 medium +/// 2 high +/// 3 debugging congestion control +void RAK_DLL_EXPORT StatisticsToString( RakNetStatistics *s, char *buffer, int verbosityLevel ); + +#endif diff --git a/RakNet/Sources/RakNetTime.h b/RakNet/Sources/RakNetTime.h new file mode 100644 index 0000000..1dfe0b2 --- /dev/null +++ b/RakNet/Sources/RakNetTime.h @@ -0,0 +1,19 @@ +#ifndef __RAKNET_TIME_H +#define __RAKNET_TIME_H + +#include "NativeTypes.h" +#include "RakNetDefines.h" + +// Define __GET_TIME_64BIT if you want to use large types for GetTime (takes more bandwidth when you transmit time though!) +// You would want to do this if your system is going to run long enough to overflow the millisecond counter (over a month) +#if __GET_TIME_64BIT==1 +typedef uint64_t RakNetTime; +typedef uint64_t RakNetTimeMS; +typedef uint64_t RakNetTimeUS; +#else +typedef uint32_t RakNetTime; +typedef uint32_t RakNetTimeMS; +typedef uint64_t RakNetTimeUS; +#endif + +#endif diff --git a/RakNet/Sources/RakNetTransport.cpp b/RakNet/Sources/RakNetTransport.cpp new file mode 100644 index 0000000..665c986 --- /dev/null +++ b/RakNet/Sources/RakNetTransport.cpp @@ -0,0 +1,186 @@ +#include "NativeFeatureIncludes.h" +#if _RAKNET_SUPPORT_RakNetTransport==1 + +#include "RakNetTransport.h" +#include "RakNetworkFactory.h" +#include "RakPeerInterface.h" +#include "BitStream.h" +#include "MessageIdentifiers.h" +#include +#include +#include +#include "LinuxStrings.h" + +#ifdef _MSC_VER +#pragma warning( push ) +#endif + +RakNetTransportCommandParser::RakNetTransportCommandParser() +{ + RegisterCommand(1, "SetPassword","Changes the console password to whatever."); + RegisterCommand(0, "ClearPassword","Removes the console passwords."); + RegisterCommand(0, "GetPassword","Gets the console password."); +} +RakNetTransportCommandParser::~RakNetTransportCommandParser() +{ +} +bool RakNetTransportCommandParser::OnCommand(const char *command, unsigned numParameters, char **parameterList, TransportInterface *transport, SystemAddress systemAddress, const char *originalString) +{ + (void) originalString; + (void) numParameters; + + RakNetTransport *rnt = (RakNetTransport*) transport; + if (strcmp(command, "SetPassword")==0) + { + rnt->SetIncomingPassword(parameterList[0]); + rnt->Send(systemAddress, "Password changed to %s\r\n", parameterList[0]); + } + else if (strcmp(command, "ClearPassword")==0) + { + rnt->SetIncomingPassword(0); + rnt->Send(systemAddress, "Password cleared\r\n"); + } + else if (strcmp(command, "GetPassword")==0) + { + char *password; + password=rnt->GetIncomingPassword(); + if (password[0]) + rnt->Send(systemAddress, "Password is %s\r\n",password); + else + rnt->Send(systemAddress, "No password is set.\r\n"); + } + return true; +} +const char *RakNetTransportCommandParser::GetName(void) const +{ + return "RakNetTransport"; +} +void RakNetTransportCommandParser::SendHelp(TransportInterface *transport, SystemAddress systemAddress) +{ + transport->Send(systemAddress, "Provides a secure connection between your console\r\n"); + transport->Send(systemAddress, "and the console server. Used to modify the console password.\r\n"); +} +RakNetTransport::RakNetTransport() +{ + rakPeer=0; +} +RakNetTransport::~RakNetTransport() +{ + if (rakPeer) + RakNetworkFactory::DestroyRakPeerInterface(rakPeer); +} +bool RakNetTransport::Start(unsigned short port, bool serverMode) +{ + AutoAllocate(); + rakPeer->InitializeSecurity(0,0,0,0); + + if (serverMode) + { + // Allow up to 8 remote systems to login + rakPeer->SetMaximumIncomingConnections(8); + } + + SocketDescriptor socketDescriptor(port,0); + return rakPeer->Startup(8, 250, &socketDescriptor, 1); +} +void RakNetTransport::Stop(void) +{ + if (rakPeer==0) return; + rakPeer->Shutdown(1000, 0); + newConnections.Clear(__FILE__, __LINE__); + lostConnections.Clear(__FILE__, __LINE__); +} +void RakNetTransport::Send( SystemAddress systemAddress, const char *data, ... ) +{ + if (rakPeer==0) return; + if (data==0 || data[0]==0) return; + + char text[REMOTE_MAX_TEXT_INPUT]; + va_list ap; + va_start(ap, data); + _vsnprintf(text, REMOTE_MAX_TEXT_INPUT, data, ap); + va_end(ap); + text[REMOTE_MAX_TEXT_INPUT-1]=0; + + RakNet::BitStream str; + str.Write((MessageID)ID_TRANSPORT_STRING); + str.Write(text, (int) strlen(text)); + str.Write((unsigned char) 0); // Null terminate the string + rakPeer->Send(&str, MEDIUM_PRIORITY, RELIABLE_ORDERED, 0, systemAddress, (systemAddress==UNASSIGNED_SYSTEM_ADDRESS)!=0); +} +void RakNetTransport::CloseConnection( SystemAddress systemAddress ) +{ + rakPeer->CloseConnection(systemAddress, true, 0); +} +Packet* RakNetTransport::Receive( void ) +{ + if (rakPeer==0) return 0; + Packet *p; + p=rakPeer->Receive(); + if (p==0) + return 0; + if (p->data[0]==ID_TRANSPORT_STRING) + { + p->data++; // Go past ID_TRANSPORT_STRING, since the transport protocol is only supposed to send strings. + return p; + } + if (p->data[0]==ID_NEW_INCOMING_CONNECTION) + { + newConnections.Push(p->systemAddress, __FILE__, __LINE__ ); + } + else if (p->data[0]==ID_DISCONNECTION_NOTIFICATION || p->data[0]==ID_CONNECTION_LOST) + { + lostConnections.Push(p->systemAddress, __FILE__, __LINE__ ); + } + rakPeer->DeallocatePacket(p); + + return 0; +} +SystemAddress RakNetTransport::HasNewIncomingConnection(void) +{ + if (newConnections.Size()) + return newConnections.Pop(); + return UNASSIGNED_SYSTEM_ADDRESS; +} +SystemAddress RakNetTransport::HasLostConnection(void) +{ + if (lostConnections.Size()) + return lostConnections.Pop(); + return UNASSIGNED_SYSTEM_ADDRESS; +} +void RakNetTransport::SetIncomingPassword(const char *password) +{ + if (password) + rakPeer->SetIncomingPassword(password, (int) strlen(password)+1); + else + rakPeer->SetIncomingPassword(0, 0); +} +char * RakNetTransport::GetIncomingPassword(void) +{ + static char password[256]; + int passwordLength=255; + rakPeer->GetIncomingPassword((char*)password, &passwordLength); + password[passwordLength]=0; + return (char*) password; +} +void RakNetTransport::DeallocatePacket( Packet *packet ) +{ + if (rakPeer==0) return; + packet->data--; // Go back to ID_TRANSPORT_STRING, which we passed up in Receive() + rakPeer->DeallocatePacket(packet); +} +void RakNetTransport::AutoAllocate(void) +{ + if (rakPeer==0) + rakPeer=RakNetworkFactory::GetRakPeerInterface(); +} +CommandParserInterface* RakNetTransport::GetCommandParser(void) +{ + return &rakNetTransportCommandParser; +} + +#ifdef _MSC_VER +#pragma warning( pop ) +#endif + +#endif // _RAKNET_SUPPORT_* diff --git a/RakNet/Sources/RakNetTransport.h b/RakNet/Sources/RakNetTransport.h new file mode 100644 index 0000000..20c189b --- /dev/null +++ b/RakNet/Sources/RakNetTransport.h @@ -0,0 +1,126 @@ +/// \file +/// \brief Contains RakNetTransportCommandParser and RakNetTransport used to provide a secure console connection. +/// +/// This file is part of RakNet Copyright 2003 Jenkins Software LLC +/// +/// Usage of RakNet is subject to the appropriate license agreement. + +#include "NativeFeatureIncludes.h" +#if _RAKNET_SUPPORT_RakNetTransport==1 + +#ifndef __RAKNET_TRANSPORT +#define __RAKNET_TRANSPORT + +#include "TransportInterface.h" +#include "DS_Queue.h" +#include "CommandParserInterface.h" +#include "Export.h" + +class RakPeerInterface; +class RakNetTransport; +namespace RakNet +{ + class BitStream; +} + +/// \brief RakNetTransport has its own command parser to enable remote users to change the command console's password. +class RAK_DLL_EXPORT RakNetTransportCommandParser : public CommandParserInterface +{ +public: + RakNetTransportCommandParser(); + ~RakNetTransportCommandParser(); + + /// 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, 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, SystemAddress systemAddress); +protected: +}; + +/// \brief Use RakNetTransport if you need a secure connection between the client and the console server. +/// \details RakNetTransport automatically initializes security for the system. Use the project CommandConsoleClient to connect +/// To the ConsoleServer if you use RakNetTransport +/// \deprecated Use RakNetTransport2 +class RAK_DLL_EXPORT RakNetTransport : public TransportInterface +{ +public: + RakNetTransport(); + virtual ~RakNetTransport(); + + /// Start the transport provider on the indicated port. + /// \param[in] port The port to start the transport provider on + /// \param[in] serverMode If true, you should allow incoming connections (I don't actually use this anywhere) + /// \return Return true on success, false on failure. + bool Start(unsigned short port, bool serverMode); + + /// Stop the transport provider. You can clear memory and shutdown threads here. + void Stop(void); + + /// Send a null-terminated string to \a systemAddress + /// If your transport method requires particular formatting of the outgoing data (e.g. you don't just send strings) you can do it here + /// and parse it out in Receive(). + /// \param[in] systemAddress The player to send the string to + /// \param[in] data format specifier - same as RAKNET_DEBUG_PRINTF + /// \param[in] ... format specification arguments - same as RAKNET_DEBUG_PRINTF + void Send( SystemAddress systemAddress, const char *data, ... ); + + /// Return a string. The string should be allocated and written to Packet::data . + /// The byte length should be written to Packet::length . The player/address should be written to Packet::systemAddress + /// If your transport protocol adds special formatting to the data stream you should parse it out before returning it in the packet + /// and thus only return a string in Packet::data + /// \return The packet structure containing the result of Receive, or 0 if no data is available + Packet* Receive( void ); + + /// Deallocate the Packet structure returned by Receive + /// \param[in] The packet to deallocate + void DeallocatePacket( Packet *packet ); + + /// Disconnect \a systemAddress . The binary address and port defines the SystemAddress structure. + /// \param[in] systemAddress The player/address to disconnect + void CloseConnection( SystemAddress systemAddress ); + + /// If a new system connects to you, you should queue that event and return the systemAddress/address of that player in this function. + /// \return The SystemAddress/address of the system + SystemAddress HasNewIncomingConnection(void); + + /// If a system loses the connection, you should queue that event and return the systemAddress/address of that player in this function. + /// \return The SystemAddress/address of the system + SystemAddress HasLostConnection(void); + + /// Sets the password which incoming connections must match. + /// While not required, it is highly recommended you set this in a real game environment or anyone can login and control your server. + /// Don't set it to a fixed value, but instead require that the server admin sets it when you start the application server + /// \param[in] password Null-terminated string to use as a password. + void SetIncomingPassword(const char *password); + + /// Returns the password set by SetIncomingPassword(). + /// \return The password set by SetIncomingPassword() + char * GetIncomingPassword(void); + + /// Returns RakNetTransportCommandParser so the console admin can change the password + CommandParserInterface* GetCommandParser(void); + +protected: + RakPeerInterface *rakPeer; + void AutoAllocate(void); + DataStructures::Queue newConnections, lostConnections; + RakNetTransportCommandParser rakNetTransportCommandParser; +}; + +#endif + +#endif // _RAKNET_SUPPORT_* diff --git a/RakNet/Sources/RakNetTransport2.cpp b/RakNet/Sources/RakNetTransport2.cpp new file mode 100644 index 0000000..ac20787 --- /dev/null +++ b/RakNet/Sources/RakNetTransport2.cpp @@ -0,0 +1,119 @@ +#include "RakNetTransport2.h" +#include "RakNetworkFactory.h" +#include "RakPeerInterface.h" +#include "BitStream.h" +#include "MessageIdentifiers.h" +#include +#include +#include +#include "LinuxStrings.h" + +#ifdef _MSC_VER +#pragma warning( push ) +#endif + +RakNetTransport2::RakNetTransport2() +{ +} +RakNetTransport2::~RakNetTransport2() +{ + Stop(); +} +bool RakNetTransport2::Start(unsigned short port, bool serverMode) +{ + (void) port; + (void) serverMode; + return true; +} +void RakNetTransport2::Stop(void) +{ + newConnections.Clear(__FILE__, __LINE__); + lostConnections.Clear(__FILE__, __LINE__); + for (unsigned int i=0; i < packetQueue.Size(); i++) + { + rakFree_Ex(packetQueue[i]->data,__FILE__,__LINE__); + RakNet::OP_DELETE(packetQueue[i],__FILE__,__LINE__); + } + packetQueue.Clear(__FILE__, __LINE__); +} +void RakNetTransport2::Send( SystemAddress systemAddress, const char *data, ... ) +{ + if (data==0 || data[0]==0) return; + + char text[REMOTE_MAX_TEXT_INPUT]; + va_list ap; + va_start(ap, data); + _vsnprintf(text, REMOTE_MAX_TEXT_INPUT, data, ap); + va_end(ap); + text[REMOTE_MAX_TEXT_INPUT-1]=0; + + RakNet::BitStream str; + str.Write((MessageID)ID_TRANSPORT_STRING); + str.Write(text, (int) strlen(text)); + str.Write((unsigned char) 0); // Null terminate the string + rakPeerInterface->Send(&str, MEDIUM_PRIORITY, RELIABLE_ORDERED, 0, systemAddress, (systemAddress==UNASSIGNED_SYSTEM_ADDRESS)!=0); +} +void RakNetTransport2::CloseConnection( SystemAddress systemAddress ) +{ + rakPeerInterface->CloseConnection(systemAddress, true, 0); +} +Packet* RakNetTransport2::Receive( void ) +{ + if (packetQueue.Size()==0) + return 0; + return packetQueue.Pop(); +} +SystemAddress RakNetTransport2::HasNewIncomingConnection(void) +{ + if (newConnections.Size()) + return newConnections.Pop(); + return UNASSIGNED_SYSTEM_ADDRESS; +} +SystemAddress RakNetTransport2::HasLostConnection(void) +{ + if (lostConnections.Size()) + return lostConnections.Pop(); + return UNASSIGNED_SYSTEM_ADDRESS; +} +void RakNetTransport2::DeallocatePacket( Packet *packet ) +{ + rakFree_Ex(packet->data, __FILE__, __LINE__ ); + RakNet::OP_DELETE(packet, __FILE__, __LINE__ ); +} +PluginReceiveResult RakNetTransport2::OnReceive(Packet *packet) +{ + switch (packet->data[0]) + { + case ID_TRANSPORT_STRING: + { + if (packet->length==sizeof(MessageID)) + return RR_STOP_PROCESSING_AND_DEALLOCATE; + + Packet *p = RakNet::OP_NEW(__FILE__,__LINE__); + *p=*packet; + p->bitSize-=8; + p->length--; + p->data=(unsigned char*) rakMalloc_Ex(p->length,__FILE__,__LINE__); + memcpy(p->data, packet->data+1, p->length); + packetQueue.Push(p, __FILE__, __LINE__ ); + + } + return RR_STOP_PROCESSING_AND_DEALLOCATE; + } + return RR_CONTINUE_PROCESSING; +} +void RakNetTransport2::OnClosedConnection(SystemAddress systemAddress, RakNetGUID rakNetGUID, PI2_LostConnectionReason lostConnectionReason ) +{ + (void) rakNetGUID; + (void) lostConnectionReason; + lostConnections.Push(systemAddress, __FILE__, __LINE__ ); +} +void RakNetTransport2::OnNewConnection(SystemAddress systemAddress, RakNetGUID rakNetGUID, bool isIncoming) +{ + (void) rakNetGUID; + (void) isIncoming; + newConnections.Push(systemAddress, __FILE__, __LINE__ ); +} +#ifdef _MSC_VER +#pragma warning( pop ) +#endif diff --git a/RakNet/Sources/RakNetTransport2.h b/RakNet/Sources/RakNetTransport2.h new file mode 100644 index 0000000..aa398b7 --- /dev/null +++ b/RakNet/Sources/RakNetTransport2.h @@ -0,0 +1,93 @@ +/// \file +/// \brief Contains RakNetTransportCommandParser and RakNetTransport used to provide a secure console connection. +/// +/// This file is part of RakNet Copyright 2003 Jenkins Software LLC +/// +/// Usage of RakNet is subject to the appropriate license agreement. + + +#ifndef __RAKNET_TRANSPORT_2 +#define __RAKNET_TRANSPORT_2 + +#include "TransportInterface.h" +#include "DS_Queue.h" +#include "CommandParserInterface.h" +#include "PluginInterface2.h" +#include "Export.h" + +class RakPeerInterface; +class RakNetTransport; +namespace RakNet +{ + class BitStream; +} + +/// \defgroup RAKNET_TRANSPORT_GROUP RakNetTransport +/// \brief UDP based transport implementation for the ConsoleServer +/// \details +/// \ingroup PLUGINS_GROUP + +/// \brief Use RakNetTransport if you need a secure connection between the client and the console server. +/// \details RakNetTransport automatically initializes security for the system. Use the project CommandConsoleClient to connect +/// To the ConsoleServer if you use RakNetTransport +/// \ingroup RAKNET_TRANSPORT_GROUP +class RAK_DLL_EXPORT RakNetTransport2 : public TransportInterface, public PluginInterface2 +{ +public: + RakNetTransport2(); + virtual ~RakNetTransport2(); + + /// Start the transport provider on the indicated port. + /// \param[in] port The port to start the transport provider on + /// \param[in] serverMode If true, you should allow incoming connections (I don't actually use this anywhere) + /// \return Return true on success, false on failure. + bool Start(unsigned short port, bool serverMode); + + /// Stop the transport provider. You can clear memory and shutdown threads here. + void Stop(void); + + /// Send a null-terminated string to \a systemAddress + /// If your transport method requires particular formatting of the outgoing data (e.g. you don't just send strings) you can do it here + /// and parse it out in Receive(). + /// \param[in] systemAddress The player to send the string to + /// \param[in] data format specifier - same as RAKNET_DEBUG_PRINTF + /// \param[in] ... format specification arguments - same as RAKNET_DEBUG_PRINTF + void Send( SystemAddress systemAddress, const char *data, ... ); + + /// Disconnect \a systemAddress . The binary address and port defines the SystemAddress structure. + /// \param[in] systemAddress The player/address to disconnect + void CloseConnection( SystemAddress systemAddress ); + + /// Return a string. The string should be allocated and written to Packet::data . + /// The byte length should be written to Packet::length . The player/address should be written to Packet::systemAddress + /// If your transport protocol adds special formatting to the data stream you should parse it out before returning it in the packet + /// and thus only return a string in Packet::data + /// \return The packet structure containing the result of Receive, or 0 if no data is available + Packet* Receive( void ); + + /// Deallocate the Packet structure returned by Receive + /// \param[in] The packet to deallocate + void DeallocatePacket( Packet *packet ); + + /// If a new system connects to you, you should queue that event and return the systemAddress/address of that player in this function. + /// \return The SystemAddress/address of the system + SystemAddress HasNewIncomingConnection(void); + + /// If a system loses the connection, you should queue that event and return the systemAddress/address of that player in this function. + /// \return The SystemAddress/address of the system + SystemAddress HasLostConnection(void); + + virtual CommandParserInterface* GetCommandParser(void) {return 0;} + + /// \internal + virtual PluginReceiveResult OnReceive(Packet *packet); + /// \internal + virtual void OnClosedConnection(SystemAddress systemAddress, RakNetGUID rakNetGUID, PI2_LostConnectionReason lostConnectionReason ); + /// \internal + virtual void OnNewConnection(SystemAddress systemAddress, RakNetGUID rakNetGUID, bool isIncoming); +protected: + DataStructures::Queue newConnections, lostConnections; + DataStructures::Queue packetQueue; +}; + +#endif diff --git a/RakNet/Sources/RakNetTypes.cpp b/RakNet/Sources/RakNetTypes.cpp new file mode 100644 index 0000000..6cd26dd --- /dev/null +++ b/RakNet/Sources/RakNetTypes.cpp @@ -0,0 +1,324 @@ +/// \file +/// +/// This file is part of RakNet Copyright 2003 Jenkins Software LLC +/// +/// Usage of RakNet is subject to the appropriate license agreement. + + +#include "RakNetTypes.h" +#include "RakAssert.h" +#include +#include + +#if defined(_XBOX) || defined(X360) +#elif 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 +#else +#include +#include +#include +#endif + +#include // strncasecmp +#include "Itoa.h" +#include "SocketLayer.h" +#include + +bool NonNumericHostString( const char *host ) +{ + if ( host[ 0 ] >= '0' && host[ 0 ] <= '9' ) + return false; + + if ( (host[ 0 ] == '-') && ( host[ 1 ] >= '0' && host[ 1 ] <= '9' ) ) + return false; + + return true; +} + +SocketDescriptor::SocketDescriptor() {port=0; hostAddress[0]=0; remotePortRakNetWasStartedOn_PS3=0;} +SocketDescriptor::SocketDescriptor(unsigned short _port, const char *_hostAddress) +{ + remotePortRakNetWasStartedOn_PS3=0; + port=_port; + if (_hostAddress) + strcpy(hostAddress, _hostAddress); + else + hostAddress[0]=0; +} + +// Defaults to not in peer to peer mode for NetworkIDs. This only sends the localSystemAddress portion in the BitStream class +// This is what you want for client/server, where the server assigns all NetworkIDs and it is unnecessary to transmit the full structure. +// For peer to peer, this will transmit the systemAddress of the system that created the object in addition to localSystemAddress. This allows +// Any system to create unique ids locally. +// All systems must use the same value for this variable. +//bool RAK_DLL_EXPORT NetworkID::peerToPeerMode=false; + +bool SystemAddress::operator==( const SystemAddress& right ) const +{ + return binaryAddress == right.binaryAddress && port == right.port; +} + +bool SystemAddress::operator!=( const SystemAddress& right ) const +{ + return binaryAddress != right.binaryAddress || port != right.port; +} + +bool SystemAddress::operator>( const SystemAddress& right ) const +{ + return ( ( binaryAddress > right.binaryAddress ) || ( ( binaryAddress == right.binaryAddress ) && ( port > right.port ) ) ); +} + +bool SystemAddress::operator<( const SystemAddress& right ) const +{ + return ( ( binaryAddress < right.binaryAddress ) || ( ( binaryAddress == right.binaryAddress ) && ( port < right.port ) ) ); +} +const char *SystemAddress::ToString(bool writePort) const +{ + static unsigned char strIndex=0; + static char str[8][30]; + + unsigned char lastStrIndex=strIndex; + strIndex++; + ToString(writePort, str[lastStrIndex&7]); + return (char*) str[lastStrIndex&7]; +} +void SystemAddress::ToString(bool writePort, char *dest) const +{ + if (*this==UNASSIGNED_SYSTEM_ADDRESS) + { + strcpy(dest, "UNASSIGNED_SYSTEM_ADDRESS"); + return; + } + +#if defined(_XBOX) || defined(X360) + +#else + + + in_addr in; + in.s_addr = binaryAddress; +// cellSysmoduleLoadModule(CELL_SYSMODULE_NETCTL); +// sys_net_initialize_network(); + const char *ntoaStr = inet_ntoa( in ); + strcpy(dest, ntoaStr); + if (writePort) + { + strcat(dest, ":"); + Itoa(port, dest+strlen(dest), 10); + } +#endif +} +SystemAddress::SystemAddress() {*this=UNASSIGNED_SYSTEM_ADDRESS; systemIndex=(SystemIndex)-1;} +SystemAddress::SystemAddress(const char *a, unsigned short b) {SetBinaryAddress(a); port=b; systemIndex=(SystemIndex)-1;}; +SystemAddress::SystemAddress(unsigned int a, unsigned short b) {binaryAddress=a; port=b; systemIndex=(SystemIndex)-1;}; +#ifdef _MSC_VER +#pragma warning( disable : 4996 ) // The POSIX name for this item is deprecated. Instead, use the ISO C++ conformant name: _strnicmp. See online help for details. +#endif +void SystemAddress::SetBinaryAddress(const char *str) +{ + if ( NonNumericHostString( str ) ) + { + + #if defined(_WIN32) + if (_strnicmp(str,"localhost", 9)==0) + #else + if (strncasecmp(str,"localhost", 9)==0) + #endif + { + binaryAddress=inet_addr("127.0.0.1"); + if (str[9]) + port=(unsigned short) atoi(str+9); + return; + } + + const char *ip = ( char* ) SocketLayer::Instance()->DomainNameToIP( str ); + if (ip) + { + binaryAddress=inet_addr(ip); + } + } + else + { + //#ifdef _XBOX + // binaryAddress=UNASSIGNED_SYSTEM_ADDRESS.binaryAddress; + //#else + // Split the string into the first part, and the : part + int index, portIndex; + char IPPart[22]; + char portPart[10]; + // Only write the valid parts, don't change existing if invalid + // binaryAddress=UNASSIGNED_SYSTEM_ADDRESS.binaryAddress; + // port=UNASSIGNED_SYSTEM_ADDRESS.port; + for (index=0; str[index] && str[index]!=':' && index<22; index++) + { + IPPart[index]=str[index]; + } + IPPart[index]=0; + portPart[0]=0; + if (str[index] && str[index+1]) + { + index++; + for (portIndex=0; portIndex<10 && str[index] && index < 22+10; index++, portIndex++) + portPart[portIndex]=str[index]; + portPart[portIndex]=0; + } + +#if defined(_XBOX) || defined(X360) + +#else + if (IPPart[0]) + binaryAddress=inet_addr(IPPart); + +#endif + + if (portPart[0]) + port=(unsigned short) atoi(portPart); + //#endif + } + +} + +NetworkID& NetworkID::operator = ( const NetworkID& input ) +{ +#if NETWORK_ID_SUPPORTS_PEER_TO_PEER==1 + systemAddress = input.systemAddress; + guid = input.guid; +#endif + localSystemAddress = input.localSystemAddress; + return *this; +} + +bool NetworkID::operator==( const NetworkID& right ) const +{ +#if NETWORK_ID_SUPPORTS_PEER_TO_PEER==1 +// if (NetworkID::peerToPeerMode) + { + if (guid!=UNASSIGNED_RAKNET_GUID) + return guid == right.guid && localSystemAddress == right.localSystemAddress; + else + return systemAddress == right.systemAddress && localSystemAddress == right.localSystemAddress; + } +#else +// else + return localSystemAddress==right.localSystemAddress; +#endif +} + +bool NetworkID::operator!=( const NetworkID& right ) const +{ +#if NETWORK_ID_SUPPORTS_PEER_TO_PEER==1 +// if (NetworkID::peerToPeerMode) + { + if (guid!=UNASSIGNED_RAKNET_GUID) + return guid != right.guid || localSystemAddress != right.localSystemAddress; + else + return systemAddress != right.systemAddress || localSystemAddress != right.localSystemAddress; + } +#else +// else + return localSystemAddress!=right.localSystemAddress; +#endif +} + +bool NetworkID::operator>( const NetworkID& right ) const +{ +#if NETWORK_ID_SUPPORTS_PEER_TO_PEER==1 +// if (NetworkID::peerToPeerMode) + { + if (guid!=UNASSIGNED_RAKNET_GUID) + return ( ( guid > right.guid ) || ( ( guid == right.guid ) && ( localSystemAddress > right.localSystemAddress ) ) ); + else + return ( ( systemAddress > right.systemAddress ) || ( ( systemAddress == right.systemAddress ) && ( localSystemAddress > right.localSystemAddress ) ) ); + } +#else +// else + return localSystemAddress>right.localSystemAddress; +#endif +} + +bool NetworkID::operator<( const NetworkID& right ) const +{ +#if NETWORK_ID_SUPPORTS_PEER_TO_PEER==1 +// if (NetworkID::peerToPeerMode) + { + if (guid!=UNASSIGNED_RAKNET_GUID) + return ( ( guid < right.guid ) || ( ( guid == right.guid ) && ( localSystemAddress < right.localSystemAddress ) ) ); + else + return ( ( systemAddress < right.systemAddress ) || ( ( systemAddress == right.systemAddress ) && ( localSystemAddress < right.localSystemAddress ) ) ); + } +#else + //else + return localSystemAddress ( const RakNetGUID& right ) const +{ + return g > right.g; +} +bool RakNetGUID::operator < ( const RakNetGUID& right ) const +{ + return g < right.g; +} +const char *RakNetGUID::ToString(void) const +{ + static unsigned char strIndex=0; + static char str[8][22]; + + unsigned char lastStrIndex=strIndex; + strIndex++; + ToString(str[lastStrIndex&7]); + return (char*) str[lastStrIndex&7]; +} +void RakNetGUID::ToString(char *dest) const +{ + if (*this==UNASSIGNED_RAKNET_GUID) + strcpy(dest, "UNASSIGNED_RAKNET_GUID"); + + //sprintf(dest, "%u.%u.%u.%u.%u.%u", g[0], g[1], g[2], g[3], g[4], g[5]); + sprintf(dest, "%" PRINTF_64_BIT_MODIFIER "u", g); + // sprintf(dest, "%u.%u.%u.%u.%u.%u", g[0], g[1], g[2], g[3], g[4], g[5]); +} +bool RakNetGUID::FromString(const char *source) +{ + if (source==0) + return false; + +#if defined(_XBOX) || defined(_X360) + +#elif defined(WIN32) + g=_strtoui64(source, (char **)NULL, 10); +#else + g=strtoull (source, (char **)NULL, 10); +#endif + return true; + +} diff --git a/RakNet/Sources/RakNetTypes.h b/RakNet/Sources/RakNetTypes.h new file mode 100644 index 0000000..a73f78e --- /dev/null +++ b/RakNet/Sources/RakNetTypes.h @@ -0,0 +1,450 @@ +/// \file +/// \brief Types used by RakNet, most of which involve user code. +/// +/// This file is part of RakNet Copyright 2003 Jenkins Software LLC +/// +/// Usage of RakNet is subject to the appropriate license agreement. + + +#ifndef __NETWORK_TYPES_H +#define __NETWORK_TYPES_H + +#include "RakNetDefines.h" +#include "NativeTypes.h" +#include "RakNetTime.h" +#include "Export.h" + +/// Forward declaration +namespace RakNet +{ + class BitStream; +}; + +/// Given a number of bits, return how many bytes are needed to represent that. +#define BITS_TO_BYTES(x) (((x)+7)>>3) +#define BYTES_TO_BITS(x) ((x)<<3) + +/// \sa NetworkIDObject.h +typedef unsigned char UniqueIDType; +typedef unsigned short SystemIndex; +typedef unsigned char RPCIndex; +const int MAX_RPC_MAP_SIZE=((RPCIndex)-1)-1; +const int UNDEFINED_RPC_INDEX=((RPCIndex)-1); + +/// First byte of a network message +typedef unsigned char MessageID; + +typedef uint32_t BitSize_t; + +#if defined(_MSC_VER) && _MSC_VER > 0 +#define PRINTF_64_BIT_MODIFIER "I64" +#else +#define PRINTF_64_BIT_MODIFIER "ll" +#endif + +/// Describes the local socket to use for RakPeer::Startup +struct RAK_DLL_EXPORT SocketDescriptor +{ + SocketDescriptor(); + SocketDescriptor(unsigned short _port, const char *_hostAddress); + + /// The local port to bind to. Pass 0 to have the OS autoassign a port. + unsigned short port; + + /// The local network card address to bind to, such as "127.0.0.1". Pass an empty string to use INADDR_ANY. + char hostAddress[32]; + + // Only need to set for the PS3, when using signaling. + // Connect with the port returned by signaling. Set this to whatever port RakNet was actually started on + unsigned short remotePortRakNetWasStartedOn_PS3; +}; + +extern bool NonNumericHostString( const char *host ); + +/// \brief Network address for a system +/// \details Corresponds to a network address
+/// This is not necessarily a unique identifier. For example, if a system has both LAN and internet connections, the system may be identified by either one, depending on who is communicating
+/// Use RakNetGUID for a unique per-instance of RakPeer to identify systems +struct RAK_DLL_EXPORT SystemAddress +{ + SystemAddress(); + explicit SystemAddress(const char *a, unsigned short b); + explicit SystemAddress(unsigned int a, unsigned short b); + + ///The peer address from inet_addr. + uint32_t binaryAddress; + ///The port number + unsigned short port; + // Used internally for fast lookup. Optional (use -1 to do regular lookup). Don't transmit this. + SystemIndex systemIndex; + static const int size() {return (int) sizeof(uint32_t)+sizeof(unsigned short);} + + // Return the systemAddress as a string in the format : + // Returns a static string + // NOT THREADSAFE + const char *ToString(bool writePort=true) const; + + // Return the systemAddress as a string in the format : + // dest must be large enough to hold the output + // THREADSAFE + void ToString(bool writePort, char *dest) const; + + // Sets the binary address part from a string. Doesn't set the port + void SetBinaryAddress(const char *str); + + SystemAddress& operator = ( const SystemAddress& input ) + { + binaryAddress = input.binaryAddress; + port = input.port; + systemIndex = input.systemIndex; + return *this; + } + + bool operator==( const SystemAddress& right ) const; + bool operator!=( const SystemAddress& right ) const; + bool operator > ( const SystemAddress& right ) const; + bool operator < ( const SystemAddress& right ) const; +}; + + +/// Size of SystemAddress data +#define SystemAddress_Size 6 + +class RakPeerInterface; + +/// All RPC functions have the same parameter list - this structure. +/// \deprecated use RakNet::RPC3 instead +struct RPCParameters +{ + /// The data from the remote system + unsigned char *input; + + /// How many bits long \a input is + BitSize_t numberOfBitsOfData; + + /// Which system called this RPC + SystemAddress sender; + + /// Which instance of RakPeer (or a derived RakPeer or RakPeer) got this call + RakPeerInterface *recipient; + + RakNetTime remoteTimestamp; + + /// The name of the function that was called. + char *functionName; + + /// You can return values from RPC calls by writing them to this BitStream. + /// This is only sent back if the RPC call originally passed a BitStream to receive the reply. + /// If you do so and your send is reliable, it will block until you get a reply or you get disconnected from the system you are sending to, whichever is first. + /// If your send is not reliable, it will block for triple the ping time, or until you are disconnected, or you get a reply, whichever is first. + RakNet::BitStream *replyToSender; +}; + +/// Uniquely identifies an instance of RakPeer. Use RakPeer::GetGuidFromSystemAddress() and RakPeer::GetSystemAddressFromGuid() to go between SystemAddress and RakNetGUID +/// Use RakPeer::GetGuidFromSystemAddress(UNASSIGNED_SYSTEM_ADDRESS) to get your own GUID +struct RAK_DLL_EXPORT RakNetGUID +{ + RakNetGUID() {systemIndex=(SystemIndex)-1;} + explicit RakNetGUID(uint64_t _g) {g=_g; systemIndex=(SystemIndex)-1;} +// uint32_t g[6]; + uint64_t g; + + // Return the GUID as a string + // Returns a static string + // NOT THREADSAFE + const char *ToString(void) const; + + // Return the GUID as a string + // dest must be large enough to hold the output + // THREADSAFE + void ToString(char *dest) const; + + bool FromString(const char *source); + + RakNetGUID& operator = ( const RakNetGUID& input ) + { + g=input.g; + systemIndex=input.systemIndex; + return *this; + } + + // Used internally for fast lookup. Optional (use -1 to do regular lookup). Don't transmit this. + SystemIndex systemIndex; + static const int size() {return (int) sizeof(uint64_t);} + + bool operator==( const RakNetGUID& right ) const; + bool operator!=( const RakNetGUID& right ) const; + bool operator > ( const RakNetGUID& right ) const; + bool operator < ( const RakNetGUID& right ) const; +}; + +/// Index of an invalid SystemAddress +//const SystemAddress UNASSIGNED_SYSTEM_ADDRESS = +//{ +// 0xFFFFFFFF, 0xFFFF +//}; +#ifndef SWIG +const SystemAddress UNASSIGNED_SYSTEM_ADDRESS(0xFFFFFFFF, 0xFFFF); +const RakNetGUID UNASSIGNED_RAKNET_GUID((uint64_t)-1); +#endif +//{ +// {0xFFFFFFFF,0xFFFFFFFF,0xFFFFFFFF,0xFFFFFFFF,0xFFFFFFFF,0xFFFFFFFF} +// 0xFFFFFFFFFFFFFFFF +//}; + + +struct RAK_DLL_EXPORT AddressOrGUID +{ + RakNetGUID rakNetGuid; + SystemAddress systemAddress; + + SystemIndex GetSystemIndex(void) const {if (rakNetGuid!=UNASSIGNED_RAKNET_GUID) return rakNetGuid.systemIndex; else return systemAddress.systemIndex;} + bool IsUndefined(void) const {return rakNetGuid==UNASSIGNED_RAKNET_GUID && systemAddress==UNASSIGNED_SYSTEM_ADDRESS;} + void SetUndefined(void) {rakNetGuid=UNASSIGNED_RAKNET_GUID; systemAddress=UNASSIGNED_SYSTEM_ADDRESS;} + + AddressOrGUID() {} + AddressOrGUID( const AddressOrGUID& input ) + { + rakNetGuid=input.rakNetGuid; + systemAddress=input.systemAddress; + } + AddressOrGUID( const SystemAddress& input ) + { + rakNetGuid=UNASSIGNED_RAKNET_GUID; + systemAddress=input; + } + AddressOrGUID( const RakNetGUID& input ) + { + rakNetGuid=input; + systemAddress=UNASSIGNED_SYSTEM_ADDRESS; + } + AddressOrGUID& operator = ( const AddressOrGUID& input ) + { + rakNetGuid=input.rakNetGuid; + systemAddress=input.systemAddress; + return *this; + } + + AddressOrGUID& operator = ( const SystemAddress& input ) + { + rakNetGuid=UNASSIGNED_RAKNET_GUID; + systemAddress=input; + return *this; + } + + AddressOrGUID& operator = ( const RakNetGUID& input ) + { + rakNetGuid=input; + systemAddress=UNASSIGNED_SYSTEM_ADDRESS; + return *this; + } +}; + +struct RAK_DLL_EXPORT NetworkID +{ + // This is done because we don't know the global constructor order + NetworkID() + : +#if NETWORK_ID_SUPPORTS_PEER_TO_PEER + guid((uint64_t)-1), systemAddress(0xFFFFFFFF, 0xFFFF), +#endif // NETWORK_ID_SUPPORTS_PEER_TO_PEER + localSystemAddress(65535) + { + } + ~NetworkID() {} + + /// \deprecated Use NETWORK_ID_SUPPORTS_PEER_TO_PEER in RakNetDefines.h + // Set this to true to use peer to peer mode for NetworkIDs. + // Obviously the value of this must match on all systems. + // True, and this will write the systemAddress portion with network sends. Takes more bandwidth, but NetworkIDs can be locally generated + // False, and only localSystemAddress is used. +// static bool peerToPeerMode; + +#if NETWORK_ID_SUPPORTS_PEER_TO_PEER==1 + + RakNetGUID guid; + + // deprecated: Use guid instead + // In peer to peer, we use both systemAddress and localSystemAddress + // In client / server, we only use localSystemAddress + SystemAddress systemAddress; +#endif + unsigned short localSystemAddress; + + NetworkID& operator = ( const NetworkID& input ); + + static bool IsPeerToPeerMode(void); + static void SetPeerToPeerMode(bool isPeerToPeer); + bool operator==( const NetworkID& right ) const; + bool operator!=( const NetworkID& right ) const; + bool operator > ( const NetworkID& right ) const; + bool operator < ( const NetworkID& right ) const; +}; + +/// This represents a user message from another system. +struct Packet +{ + // This is now in the systemAddress struct and is used for lookups automatically + /// Server only - this is the index into the player array that this systemAddress maps to +// SystemIndex systemIndex; + + /// The system that send this packet. + SystemAddress systemAddress; + + /// A unique identifier for the system that sent this packet, regardless of IP address (internal / external / remote system) + /// Only valid once a connection has been established (ID_CONNECTION_REQUEST_ACCEPTED, or ID_NEW_INCOMING_CONNECTION) + /// Until that time, will be UNASSIGNED_RAKNET_GUID + RakNetGUID guid; + + /// The length of the data in bytes + unsigned int length; + + /// The length of the data in bits + BitSize_t bitSize; + + /// The local receive port to which the packet was sent + unsigned short rcvPort; + + /// The data from the sender + unsigned char* data; + + /// @internal + /// Indicates whether to delete the data, or to simply delete the packet. + bool deleteData; +}; + +/// Index of an unassigned player +const SystemIndex UNASSIGNED_PLAYER_INDEX = 65535; + +/// Unassigned object ID +const NetworkID UNASSIGNED_NETWORK_ID; + +const int PING_TIMES_ARRAY_SIZE = 5; + +/// \brief RPC Function Implementation +/// \Deprecated Use RPC3 +/// \details The Remote Procedure Call Subsystem provide the RPC paradigm to +/// RakNet user. It consists in providing remote function call over the +/// network. A call to a remote function require you to prepare the +/// data for each parameter (using BitStream) for example. +/// +/// Use the following C function prototype for your callbacks +/// @code +/// void functionName(RPCParameters *rpcParms); +/// @endcode +/// If you pass input data, you can parse the input data in two ways. +/// 1. +/// Cast input to a struct (such as if you sent a struct) +/// i.e. MyStruct *s = (MyStruct*) input; +/// Make sure that the sizeof(MyStruct) is equal to the number of bytes passed! +/// 2. +/// Create a BitStream instance with input as data and the number of bytes +/// i.e. BitStream myBitStream(input, (numberOfBitsOfData-1)/8+1) +/// (numberOfBitsOfData-1)/8+1 is how convert from bits to bytes +/// Full example: +/// @code +/// void MyFunc(RPCParameters *rpcParms) {} +/// RakPeer *rakClient; +/// REGISTER_AS_REMOTE_PROCEDURE_CALL(rakClient, MyFunc); +/// This would allow MyFunc to be called from the server using (for example) +/// rakServer->RPC("MyFunc", 0, clientID, false); +/// @endcode + + +/// \def REGISTER_STATIC_RPC +/// \ingroup RAKNET_RPC +/// Register a C function as a Remote procedure. +/// \param[in] networkObject Your instance of RakPeer, RakPeer, or RakPeer +/// \param[in] functionName The name of the C function to call +/// \attention 12/01/05 REGISTER_AS_REMOTE_PROCEDURE_CALL renamed to REGISTER_STATIC_RPC. Delete the old name sometime in the future +//#pragma deprecated(REGISTER_AS_REMOTE_PROCEDURE_CALL) +//#define REGISTER_AS_REMOTE_PROCEDURE_CALL(networkObject, functionName) REGISTER_STATIC_RPC(networkObject, functionName) +/// \deprecated Use RakNet::RPC3 instead +#define REGISTER_STATIC_RPC(networkObject, functionName) (networkObject)->RegisterAsRemoteProcedureCall((#functionName),(functionName)) + +/// \def CLASS_MEMBER_ID +/// \ingroup RAKNET_RPC +/// \brief Concatenate two strings + +/// \def REGISTER_CLASS_MEMBER_RPC +/// \ingroup RAKNET_RPC +/// \brief Register a member function of an instantiated object as a Remote procedure call. +/// \details RPC member Functions MUST be marked __cdecl! +/// \sa ObjectMemberRPC.cpp +/// \b CLASS_MEMBER_ID is a utility macro to generate a unique signature for a class and function pair and can be used for the Raknet functions RegisterClassMemberRPC(...) and RPC(...) +/// \b REGISTER_CLASS_MEMBER_RPC is a utility macro to more easily call RegisterClassMemberRPC +/// \param[in] networkObject Your instance of RakPeer, RakPeer, or RakPeer +/// \param[in] className The class containing the function +/// \param[in] functionName The name of the function (not in quotes, just the name) +/// \deprecated Use RakNet::RPC3 instead +#define CLASS_MEMBER_ID(className, functionName) #className "_" #functionName +/// \deprecated Use RakNet::RPC3 instead +#define REGISTER_CLASS_MEMBER_RPC(networkObject, className, functionName) {union {void (__cdecl className::*cFunc)( RPCParameters *rpcParms ); void* voidFunc;}; cFunc=&className::functionName; networkObject->RegisterClassMemberRPC(CLASS_MEMBER_ID(className, functionName),voidFunc);} + +/// \def UNREGISTER_AS_REMOTE_PROCEDURE_CALL +/// \brief Only calls UNREGISTER_STATIC_RPC + +/// \def UNREGISTER_STATIC_RPC +/// \ingroup RAKNET_RPC +/// Unregisters a remote procedure call +/// RPC member Functions MUST be marked __cdecl! See the ObjectMemberRPC example. +/// \param[in] networkObject The object that manages the function +/// \param[in] functionName The function name +// 12/01/05 UNREGISTER_AS_REMOTE_PROCEDURE_CALL Renamed to UNREGISTER_STATIC_RPC. Delete the old name sometime in the future +//#pragma deprecated(UNREGISTER_AS_REMOTE_PROCEDURE_CALL) +//#define UNREGISTER_AS_REMOTE_PROCEDURE_CALL(networkObject,functionName) UNREGISTER_STATIC_RPC(networkObject,functionName) +/// \deprecated Use RakNet::RPC3 instead +#define UNREGISTER_STATIC_RPC(networkObject,functionName) (networkObject)->UnregisterAsRemoteProcedureCall((#functionName)) + +/// \def UNREGISTER_CLASS_INST_RPC +/// \ingroup RAKNET_RPC +/// \deprecated Use the AutoRPC plugin instead +/// \brief Unregisters a member function of an instantiated object as a Remote procedure call. +/// \param[in] networkObject The object that manages the function +/// \param[in] className The className that was originally passed to REGISTER_AS_REMOTE_PROCEDURE_CALL +/// \param[in] functionName The function name +/// \deprecated Use RakNet::RPC3 instead +#define UNREGISTER_CLASS_MEMBER_RPC(networkObject, className, functionName) (networkObject)->UnregisterAsRemoteProcedureCall((#className "_" #functionName)) + +struct RAK_DLL_EXPORT uint24_t +{ + uint32_t val; + + uint24_t() {} + inline operator uint32_t() { return val; } + inline operator uint32_t() const { return val; } + + inline uint24_t(const uint24_t& a) {val=a.val;} + inline uint24_t operator++() {++val; val&=0x00FFFFFF; return *this;} + inline uint24_t operator--() {--val; val&=0x00FFFFFF; return *this;} + inline uint24_t operator++(int) {uint24_t temp(val); ++val; val&=0x00FFFFFF; return temp;} + inline uint24_t operator--(int) {uint24_t temp(val); --val; val&=0x00FFFFFF; return temp;} + inline uint24_t operator&(const uint24_t& a) {return uint24_t(val&a.val);} + inline uint24_t& operator=(const uint24_t& a) { val=a.val; return *this; } + inline uint24_t& operator+=(const uint24_t& a) { val+=a.val; val&=0x00FFFFFF; return *this; } + inline uint24_t& operator-=(const uint24_t& a) { val-=a.val; val&=0x00FFFFFF; return *this; } + inline bool operator==( const uint24_t& right ) const {return val==right.val;} + inline bool operator!=( const uint24_t& right ) const {return val!=right.val;} + inline bool operator > ( const uint24_t& right ) const {return val>right.val;} + inline bool operator < ( const uint24_t& right ) const {return val ( const uint32_t& right ) const {return val>(right&0x00FFFFFF);} + inline bool operator < ( const uint32_t& right ) const {return val<(right&0x00FFFFFF);} + inline const uint24_t operator+( const uint32_t &other ) const { return uint24_t(val+other); } + inline const uint24_t operator-( const uint32_t &other ) const { return uint24_t(val-other); } + inline const uint24_t operator/( const uint32_t &other ) const { return uint24_t(val/other); } + inline const uint24_t operator*( const uint32_t &other ) const { return uint24_t(val*other); } +}; + +#endif diff --git a/RakNet/Sources/RakNetVersion.h b/RakNet/Sources/RakNetVersion.h new file mode 100644 index 0000000..d372d86 --- /dev/null +++ b/RakNet/Sources/RakNetVersion.h @@ -0,0 +1,11 @@ +#define RAKNET_VERSION "3.731" +#define RAKNET_VERSION_NUMBER 3.731 + +#define RAKNET_REVISION "$Revision$" + +#define RAKNET_DATE "05/01/2010" + +// What compatible protocol version RakNet is using. When this value changes, it indicates this version of RakNet cannot connection to an older version. +// ID_INCOMPATIBLE_PROTOCOL_VERSION will be returned on connection attempt in this case +// Version 10 - Reordered enumerations +#define RAKNET_PROTOCOL_VERSION 11 diff --git a/RakNet/Sources/RakNetworkFactory.cpp b/RakNet/Sources/RakNetworkFactory.cpp new file mode 100644 index 0000000..f22ab96 --- /dev/null +++ b/RakNet/Sources/RakNetworkFactory.cpp @@ -0,0 +1,74 @@ +/// \file +/// +/// This file is part of RakNet Copyright 2003 Jenkins Software LLC +/// +/// Usage of RakNet is subject to the appropriate license agreement. + + +#include "LogCommandParser.h" +#include "RakNetworkFactory.h" +#include "RakPeerInterface.h" +#include "RakPeer.h" +#include "ConsoleServer.h" +#include "PacketLogger.h" +#include "RakNetCommandParser.h" +#include "ReplicaManager.h" +#include "RakNetTransport.h" +#include "TelnetTransport.h" +#include "PacketConsoleLogger.h" +#include "PacketFileLogger.h" +#include "Router.h" +#include "ConnectionGraph.h" + + +RakPeerInterface* RakNetworkFactory::GetRakPeerInterface( void ) {return RakNet::OP_NEW( __FILE__, __LINE__ );} +void RakNetworkFactory::DestroyRakPeerInterface( RakPeerInterface* i ) {RakNet::OP_DELETE(( RakPeer* ) i, __FILE__, __LINE__);} + +#if _RAKNET_SUPPORT_ConsoleServer==1 +ConsoleServer* RakNetworkFactory::GetConsoleServer( void ) {return RakNet::OP_NEW( __FILE__, __LINE__ );} +void RakNetworkFactory::DestroyConsoleServer( ConsoleServer* i) {RakNet::OP_DELETE(( ConsoleServer* ) i, __FILE__, __LINE__);} +#endif + +#if _RAKNET_SUPPORT_ReplicaManager==1 +ReplicaManager* RakNetworkFactory::GetReplicaManager( void ) {return RakNet::OP_NEW( __FILE__, __LINE__ );} +void RakNetworkFactory::DestroyReplicaManager( ReplicaManager* i) {RakNet::OP_DELETE(( ReplicaManager* ) i, __FILE__, __LINE__);} +#endif + +#if _RAKNET_SUPPORT_LogCommandParser==1 +LogCommandParser* RakNetworkFactory::GetLogCommandParser( void ) {return RakNet::OP_NEW( __FILE__, __LINE__ );} +void RakNetworkFactory::DestroyLogCommandParser( LogCommandParser* i) {RakNet::OP_DELETE(( LogCommandParser* ) i, __FILE__, __LINE__);} +#endif + +#if _RAKNET_SUPPORT_PacketLogger==1 +PacketLogger* RakNetworkFactory::GetPacketLogger( void ) {return RakNet::OP_NEW( __FILE__, __LINE__ );} +void RakNetworkFactory::DestroyPacketLogger( PacketLogger* i) {RakNet::OP_DELETE(( PacketLogger* ) i, __FILE__, __LINE__);} +//PacketConsoleLogger* RakNetworkFactory::GetPacketConsoleLogger( void ) {return RakNet::OP_NEW( __FILE__, __LINE__ );} +//void RakNetworkFactory::DestroyPacketConsoleLogger( PacketConsoleLogger* i ) {RakNet::OP_DELETE(( PacketConsoleLogger* ) i, __FILE__, __LINE__);} +PacketFileLogger* RakNetworkFactory::GetPacketFileLogger( void ) {return RakNet::OP_NEW( __FILE__, __LINE__ );} +void RakNetworkFactory::DestroyPacketFileLogger( PacketFileLogger* i ) {RakNet::OP_DELETE(( PacketFileLogger* ) i, __FILE__, __LINE__);} +#endif + +#if _RAKNET_SUPPORT_RakNetCommandParser==1 +RakNetCommandParser* RakNetworkFactory::GetRakNetCommandParser( void ) {return RakNet::OP_NEW( __FILE__, __LINE__ );} +void RakNetworkFactory::DestroyRakNetCommandParser( RakNetCommandParser* i ) {RakNet::OP_DELETE(( RakNetCommandParser* ) i, __FILE__, __LINE__);} +#endif + +#if _RAKNET_SUPPORT_RakNetTransport==1 +RakNetTransport* RakNetworkFactory::GetRakNetTransport( void ) {return RakNet::OP_NEW( __FILE__, __LINE__ );} +void RakNetworkFactory::DestroyRakNetTransport( RakNetTransport* i ) {RakNet::OP_DELETE(( RakNetTransport* ) i, __FILE__, __LINE__);} +#endif + +#if _RAKNET_SUPPORT_TelnetTransport==1 +TelnetTransport* RakNetworkFactory::GetTelnetTransport( void ) {return RakNet::OP_NEW( __FILE__, __LINE__ );} +void RakNetworkFactory::DestroyTelnetTransport( TelnetTransport* i ) {RakNet::OP_DELETE(( TelnetTransport* ) i, __FILE__, __LINE__);} +#endif + +#if _RAKNET_SUPPORT_Router==1 +Router* RakNetworkFactory::GetRouter( void ) {return RakNet::OP_NEW( __FILE__, __LINE__ );} +void RakNetworkFactory::DestroyRouter( Router* i ) {RakNet::OP_DELETE(( Router* ) i, __FILE__, __LINE__);} +#endif + +#if _RAKNET_SUPPORT_ConnectionGraph==1 +ConnectionGraph* RakNetworkFactory::GetConnectionGraph( void ) {return RakNet::OP_NEW( __FILE__, __LINE__ );} +void RakNetworkFactory::DestroyConnectionGraph( ConnectionGraph* i ) {RakNet::OP_DELETE(( ConnectionGraph* ) i, __FILE__, __LINE__);} +#endif diff --git a/RakNet/Sources/RakNetworkFactory.h b/RakNet/Sources/RakNetworkFactory.h new file mode 100644 index 0000000..52581a4 --- /dev/null +++ b/RakNet/Sources/RakNetworkFactory.h @@ -0,0 +1,88 @@ +/// \file +/// \brief Factory class for RakNet objects +/// +/// This file is part of RakNet Copyright 2003 Jenkins Software LLC +/// +/// Usage of RakNet is subject to the appropriate license agreement. + + +#ifndef __RAK_NETWORK_FACTORY_H +#define __RAK_NETWORK_FACTORY_H + +#include "Export.h" + +class RakPeerInterface; +class ConsoleServer; +class ReplicaManager; +class LogCommandParser; +class PacketLogger; +class RakNetCommandParser; +class RakNetTransport; +class TelnetTransport; +class PacketConsoleLogger; +class PacketFileLogger; +class Router; +class ConnectionGraph; + +// TODO RakNet 4 - move these to the classes themselves +class RAK_DLL_EXPORT RakNetworkFactory +{ +public: + // For DLL's, these are user classes that you might want to new and delete. + // You can't instantiate exported classes directly in your program. The instantiation + // has to take place inside the DLL. So these functions will do the news and deletes for you. + // if you're using the source or static library you don't need these functions, but can use them if you want. + static RakPeerInterface* GetRakPeerInterface( void ); + static void DestroyRakPeerInterface( RakPeerInterface* i ); + +#if _RAKNET_SUPPORT_ConsoleServer==1 + static ConsoleServer* GetConsoleServer( void ); + static void DestroyConsoleServer( ConsoleServer* i); +#endif + +#if _RAKNET_SUPPORT_ReplicaManager==1 + static ReplicaManager* GetReplicaManager( void ); + static void DestroyReplicaManager( ReplicaManager* i); +#endif + +#if _RAKNET_SUPPORT_LogCommandParser==1 + static LogCommandParser* GetLogCommandParser( void ); + static void DestroyLogCommandParser( LogCommandParser* i); +#endif + +#if _RAKNET_SUPPORT_PacketLogger==1 + static PacketLogger* GetPacketLogger( void ); + static void DestroyPacketLogger( PacketLogger* i); + static PacketConsoleLogger* GetPacketConsoleLogger( void ); + static void DestroyPacketConsoleLogger( PacketConsoleLogger* i ); + static PacketFileLogger* GetPacketFileLogger( void ); + static void DestroyPacketFileLogger( PacketFileLogger* i ); +#endif + +#if _RAKNET_SUPPORT_RakNetCommandParser==1 + static RakNetCommandParser* GetRakNetCommandParser( void ); + static void DestroyRakNetCommandParser( RakNetCommandParser* i ); +#endif + +#if _RAKNET_SUPPORT_RakNetTransport==1 + static RakNetTransport* GetRakNetTransport( void ); + static void DestroyRakNetTransport( RakNetTransport* i ); +#endif + +#if _RAKNET_SUPPORT_TelnetTransport==1 + static TelnetTransport* GetTelnetTransport( void ); + static void DestroyTelnetTransport( TelnetTransport* i ); +#endif + +#if _RAKNET_SUPPORT_Router==1 + static Router* GetRouter( void ); + static void DestroyRouter( Router* i ); +#endif + +#if _RAKNET_SUPPORT_ConnectionGraph==1 + static ConnectionGraph* GetConnectionGraph( void ); + static void DestroyConnectionGraph( ConnectionGraph* i ); +#endif +}; + +#endif diff --git a/RakNet/Sources/RakPeer.cpp b/RakNet/Sources/RakPeer.cpp new file mode 100644 index 0000000..358a5d7 --- /dev/null +++ b/RakNet/Sources/RakPeer.cpp @@ -0,0 +1,7114 @@ +// \file +// +// This file is part of RakNet Copyright 2003 Jenkins Software LLC +// +// Usage of RakNet is subject to the appropriate license agreement. + + +#include "RakNetDefines.h" +#include "RakPeer.h" +#include "RakNetTypes.h" + +#ifdef _WIN32 +#elif defined(_PS3) || defined(__PS3__) || defined(SN_TARGET_PS3) + +#else +#define closesocket close +#include +#endif + +#if defined(new) +#pragma push_macro("new") +#undef new +#define RMO_NEW_UNDEF_ALLOCATING_QUEUE +#endif + + +#include + +#include // toupper +#include +#include "GetTime.h" +#include "MessageIdentifiers.h" +#include "DS_HuffmanEncodingTree.h" +#include "Rand.h" +#include "PluginInterface2.h" +#include "StringCompressor.h" +#include "StringTable.h" +#include "NetworkIDObject.h" +#include "RakNetTypes.h" +#include "SHA1.h" +#include "RakSleep.h" +#include "RouterInterface.h" +#include "RakAssert.h" +#include "RakNetVersion.h" +#include "NetworkIDManager.h" +#include "DataBlockEncryptor.h" +#include "gettimeofday.h" +#include "SignaledEvent.h" +#include "SuperFastHash.h" + +RAK_THREAD_DECLARATION(UpdateNetworkLoop); +RAK_THREAD_DECLARATION(RecvFromLoop); +RAK_THREAD_DECLARATION(UDTConnect); + +#define REMOTE_SYSTEM_LOOKUP_HASH_MULTIPLE 8 + +enum { + ID_PROXY_SERVER_MESSAGE = 134 +}; + +#if !defined ( __APPLE__ ) && !defined ( __APPLE_CC__ ) +#include // malloc +#endif + +#if defined(_XBOX) || defined(X360) + +#elif defined(_WIN32) +// +#else +/* +#include // Console 2 +#include +extern bool _extern_Console2LoadModules(void); +extern int _extern_Console2GetConnectionStatus(void); +extern int _extern_Console2GetLobbyStatus(void); +//extern bool Console2StartupFluff(unsigned int *); +extern void Console2ShutdownFluff(void); +//extern unsigned int Console2ActivateConnection(unsigned int, void *); +//extern bool Console2BlockOnEstablished(void); +extern void Console2GetIPAndPort(unsigned int, char *, unsigned short *, unsigned int ); +//extern void Console2DeactivateConnection(unsigned int, unsigned int); +*/ +#endif + + +static const int NUM_MTU_SIZES=3; +#if defined(_XBOX) || defined(X360) + +#elif __PC__ +static const int mtuSizes[NUM_MTU_SIZES]={1200, 1200, 576}; +#else +static const int mtuSizes[NUM_MTU_SIZES]={MAXIMUM_MTU_SIZE, 1200, 576}; +#endif + + +#include "RakAlloca.h" + +// Note to self - if I change this it might affect RECIPIENT_OFFLINE_MESSAGE_INTERVAL in Natpunchthrough.cpp +//static const int MAX_OPEN_CONNECTION_REQUESTS=8; +//static const int TIME_BETWEEN_OPEN_CONNECTION_REQUESTS=500; + +#ifdef _MSC_VER +#pragma warning( push ) +#endif + +using namespace RakNet; + +static RakNetRandom rnr; + +struct RakPeerAndIndex +{ + SOCKET s; + unsigned short remotePortRakNetWasStartedOn_PS3; + RakPeer *rakPeer; +}; + +// On a Little-endian machine the RSA key and message are mangled, but we're +// trying to be friendly to the little endians, so we do byte order +// mangling on Big-Endian machines. Note that this mangling is independent +// of the byte order used on the network (which also defaults to little-end). +#ifdef HOST_ENDIAN_IS_BIG + void __inline BSWAPCPY(unsigned char *dest, unsigned char *source, int bytesize) + { + #ifdef _DEBUG + RakAssert( (bytesize % 4 == 0)&&(bytesize)&& "Something is wrong with your exponent or modulus size."); + #endif + int i; + for (i=0; ip; +// p->data=((PacketFollowedByData *)data)->data; +// p->length=dataSize; +// p->bitSize=BYTES_TO_BITS(dataSize); +// p->deleteData=false; +// p->guid=UNASSIGNED_RAKNET_GUID; +// return p; + + Packet *p; + packetAllocationPoolMutex.Lock(); + p = packetAllocationPool.Allocate(file,line); + packetAllocationPoolMutex.Unlock(); + p = new ((void*)p) Packet; + p->data=(unsigned char*) rakMalloc_Ex(dataSize,file,line); + p->length=dataSize; + p->bitSize=BYTES_TO_BITS(dataSize); + p->deleteData=true; + p->guid=UNASSIGNED_RAKNET_GUID; + p->rcvPort=0; + return p; +} + +Packet *RakPeer::AllocPacket(unsigned dataSize, unsigned char *data, const char *file, unsigned int line) +{ + // Packet *p = (Packet *)rakMalloc_Ex(sizeof(Packet), file, line); + Packet *p; + packetAllocationPoolMutex.Lock(); + p = packetAllocationPool.Allocate(file,line); + packetAllocationPoolMutex.Unlock(); + p = new ((void*)p) Packet; + p->data=data; + p->length=dataSize; + p->bitSize=BYTES_TO_BITS(dataSize); + p->deleteData=true; + p->guid=UNASSIGNED_RAKNET_GUID; + p->rcvPort=0; + return p; +} + +// -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +// Constructor +// -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +RakPeer::RakPeer() +{ + StringCompressor::AddReference(); + RakNet::StringTable::AddReference(); + +#if !defined(_XBOX) && !defined(_WIN32_WCE) && !defined(X360) + usingSecurity = false; +#endif + memset( frequencyTable, 0, sizeof( unsigned int ) * 256 ); + rawBytesSent = rawBytesReceived = compressedBytesSent = compressedBytesReceived = 0; + outputTree = inputTree = 0; + defaultMTUSize = mtuSizes[NUM_MTU_SIZES-1]; + trackFrequencyTable = false; + maximumIncomingConnections = 0; + maximumNumberOfPeers = 0; + //remoteSystemListSize=0; + remoteSystemList = 0; + remoteSystemLookup=0; + bytesSentPerSecond = bytesReceivedPerSecond = 0; + endThreads = true; + isMainLoopThreadActive = false; + isRecvFromLoopThreadActive = false; + // isRecvfromThreadActive=false; + occasionalPing = false; + allowInternalRouting=false; + for (unsigned int i=0; i < MAXIMUM_NUMBER_OF_INTERNAL_IDS; i++) + mySystemAddress[i]=UNASSIGNED_SYSTEM_ADDRESS; + allowConnectionResponseIPMigration = false; + blockOnRPCReply=false; + //incomingPasswordLength=outgoingPasswordLength=0; + incomingPasswordLength=0; + router=0; + splitMessageProgressInterval=0; + //unreliableTimeout=0; + unreliableTimeout=1000; + networkIDManager=0; + maxOutgoingBPS=0; + firstExternalID=UNASSIGNED_SYSTEM_ADDRESS; + myGuid=UNASSIGNED_RAKNET_GUID; + networkIDManager=0; + userUpdateThreadPtr=0; + userUpdateThreadData=0; + +#ifdef _DEBUG + // Wait longer to disconnect in debug so I don't get disconnected while tracing + defaultTimeoutTime=30000; +#else + defaultTimeoutTime=10000; +#endif + +#ifdef _DEBUG + _packetloss=0.0; + _minExtraPing=0; + _extraPingVariance=0; +#endif + +#ifdef _RAKNET_THREADSAFE + bufferedCommands.SetPageSize(sizeof(BufferedCommandStruct)*32); + socketQueryOutput.SetPageSize(sizeof(SocketQueryOutput)*8); + bufferedPackets.SetPageSize(sizeof(RecvFromStruct)*32); +#endif + + packetAllocationPoolMutex.Lock(); + packetAllocationPool.SetPageSize(sizeof(DataStructures::MemoryPool::MemoryWithPage)*32); + packetAllocationPoolMutex.Unlock(); + + remoteSystemIndexPool.SetPageSize(sizeof(DataStructures::MemoryPool::MemoryWithPage)*32); + + GenerateGUID(); + + quitAndDataEvents.InitEvent(); + limitConnectionFrequencyFromTheSameIP=false; + ResetSendReceipt(); +} + +// -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +// Destructor +// -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +RakPeer::~RakPeer() +{ +// unsigned i; + + + Shutdown( 0, 0 ); + + // Free the ban list. + ClearBanList(); + + StringCompressor::RemoveReference(); + RakNet::StringTable::RemoveReference(); + + quitAndDataEvents.CloseEvent(); +} + +// -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +// \brief Starts the network threads, opens the listen port. +// You must call this before calling Connect(). +// Multiple calls while already active are ignored. To call this function again with different settings, you must first call Shutdown(). +// \note Call SetMaximumIncomingConnections if you want to accept incoming connections +// \note Set _RAKNET_THREADSAFE in RakNetDefines.h if you want to call RakNet functions from multiple threads (not recommended, as it is much slower and RakNet is already asynchronous). +// \param[in] maxConnections The maximum number of connections between this instance of RakPeer and another instance of RakPeer. Required so the network can preallocate and for thread safety. A pure client would set this to 1. A pure server would set it to the number of allowed clients.- A hybrid would set it to the sum of both types of connections +// \param[in] localPort The port to listen for connections on. +// \param[in] _threadSleepTimer How many ms to Sleep each internal update cycle. With new congestion control, the best results will be obtained by passing 10. +// \param[in] socketDescriptors An array of SocketDescriptor structures to force RakNet to listen on a particular IP address or port (or both). Each SocketDescriptor will represent one unique socket. Do not pass redundant structures. To listen on a specific port, you can pass &socketDescriptor, 1SocketDescriptor(myPort,0); such as for a server. For a client, it is usually OK to just pass SocketDescriptor(); +// \param[in] socketDescriptorCount The size of the \a socketDescriptors array. Pass 1 if you are not sure what to pass. +// \return False on failure (can't create socket or thread), true on success. +// -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +bool RakPeer::Startup( unsigned short maxConnections, int _threadSleepTimer, SocketDescriptor *socketDescriptors, unsigned socketDescriptorCount, int threadPriority ) +{ + if (IsActive()) + return false; + + if (threadPriority==-99999) + { +#if defined(_XBOX) || defined(X360) + +#elif defined(_WIN32) + threadPriority=0; +#elif defined(_PS3) || defined(__PS3__) || defined(SN_TARGET_PS3) + +#else + threadPriority=1000; +#endif + } + + // Fill out ipList structure +#if !defined(_XBOX) && !defined(X360) + memset( ipList, 0, sizeof( char ) * 16 * MAXIMUM_NUMBER_OF_INTERNAL_IDS ); + SocketLayer::Instance()->GetMyIP( ipList,binaryAddresses ); +#endif + + unsigned int i; + if (myGuid==UNASSIGNED_RAKNET_GUID) + { + seedMT( GenerateSeedFromGuid() ); + } + + rnr.SeedMT( GenerateSeedFromGuid() ); + + RakPeerAndIndex rpai[256]; + RakAssert(socketDescriptorCount<=256); + + RakAssert(socketDescriptors && socketDescriptorCount>=1); + + if (socketDescriptors==0 || socketDescriptorCount<1) + return false; + + //unsigned short localPort; + //localPort=socketDescriptors[0].port; + + RakAssert( maxConnections > 0 ); + + if ( maxConnections <= 0 ) + return false; + + DerefAllSockets(); + + + // Go through all socket descriptors and precreate sockets on the specified addresses + for (i=0; iIsPortInUse(socketDescriptors[i].port, socketDescriptors[i].hostAddress)==true) + { + DerefAllSockets(); + return false; + } + + RakNetSmartPtr rns(RakNet::OP_NEW(__FILE__,__LINE__)); + if (socketDescriptors[i].remotePortRakNetWasStartedOn_PS3==0) + rns->s = (unsigned int) SocketLayer::Instance()->CreateBoundSocket( socketDescriptors[i].port, true, socketDescriptors[i].hostAddress, 100 ); + else // if (socketDescriptors[i].socketType==SocketDescriptor::PS3_LOBBY_UDP) + rns->s = (unsigned int) SocketLayer::Instance()->CreateBoundSocket_PS3Lobby( socketDescriptors[i].port, true, socketDescriptors[i].hostAddress ); + + if ((SOCKET)rns->s==(SOCKET)-1) + { + DerefAllSockets(); + return false; + } + + + rns->boundAddress=SocketLayer::GetSystemAddress( rns->s ); + rns->remotePortRakNetWasStartedOn_PS3=socketDescriptors[i].remotePortRakNetWasStartedOn_PS3; + rns->userConnectionSocketIndex=i; + + // Test the socket + int zero=0; + if (SocketLayer::Instance()->SendTo((SOCKET)rns->s, (const char*) &zero,4,"127.0.0.1", rns->boundAddress.port, rns->remotePortRakNetWasStartedOn_PS3)!=0) + { + DerefAllSockets(); + return false; + } + + socketList.Push(rns, __FILE__, __LINE__ ); + + /* +#if defined (_WIN32) && defined(USE_WAIT_FOR_MULTIPLE_EVENTS) + if (_threadSleepTimer>0) + { + rns->recvEvent=CreateEvent(0,FALSE,FALSE,0); + WSAEventSelect(rns->s,rns->recvEvent,FD_READ); + } +#endif + */ + } + + // 05/05/09 - Updated to dynamically bind sockets on IP addresses, so we always reply on the same address we recieved from + /* + + for (i=0; i(socketDescriptorCount, __FILE__, __LINE__ ); + for (i=0; iCreateBoundSocket( socketDescriptors[i].port, true, socketDescriptors[i].hostAddress ); + else if (socketDescriptors[i].socketType==SocketDescriptor::PS3_LOBBY_UDP) + connectionSockets[i].s = SocketLayer::Instance()->CreateBoundSocket_PS3Lobby( socketDescriptors[i].port, true, socketDescriptors[i].hostAddress ); + connectionSockets[i].haveRakNetCloseSocket=true; + connectionSockets[i].socketType=socketDescriptors[i].socketType; + + if (connectionSockets[i].s==(SOCKET)-1 && socketDescriptors[i].socketType!=SocketDescriptor::NONE) + { + unsigned int j; + for (j=0; j < i; j++) + { + if (connectionSockets[j].haveRakNetCloseSocket) + closesocket(connectionSockets[j].s); + } + if (connectionSockets) + { + RakNet::OP_DELETE_ARRAY(connectionSockets, __FILE__, __LINE__); + } + connectionSocketsLength=0; + connectionSockets=0; + return false; + } + } + + connectionSocketsLength=socketDescriptorCount; + +#if defined (_WIN32) && defined(USE_WAIT_FOR_MULTIPLE_EVENTS) + if (_threadSleepTimer>0) + { + recvEvent=CreateEvent(0,FALSE,FALSE,0); + for (i=0; i maxConnections ) + maximumIncomingConnections = maxConnections; + + maximumNumberOfPeers = maxConnections; + // 04/19/2006 - Don't overallocate because I'm no longer allowing connected pings. + // The disconnects are not consistently processed and the process was sloppy and complicated. + // Allocate 10% extra to handle new connections from players trying to connect when the server is full + //remoteSystemListSize = maxConnections;// * 11 / 10 + 1; + + // remoteSystemList in Single thread + //remoteSystemList = RakNet::OP_NEW( __FILE__, __LINE__ ); + remoteSystemList = RakNet::OP_NEW_ARRAY(maximumNumberOfPeers, __FILE__, __LINE__ ); + + remoteSystemLookup = RakNet::OP_NEW_ARRAY((unsigned int) maximumNumberOfPeers * REMOTE_SYSTEM_LOOKUP_HASH_MULTIPLE, __FILE__, __LINE__ ); + + for ( i = 0; i < maximumNumberOfPeers; i++ ) + //for ( i = 0; i < remoteSystemListSize; i++ ) + { + // remoteSystemList in Single thread + remoteSystemList[ i ].isActive = false; + remoteSystemList[ i ].systemAddress = UNASSIGNED_SYSTEM_ADDRESS; + remoteSystemList[ i ].guid = UNASSIGNED_RAKNET_GUID; + remoteSystemList[ i ].myExternalSystemAddress = UNASSIGNED_SYSTEM_ADDRESS; + remoteSystemList[ i ].connectMode=RemoteSystemStruct::NO_ACTION; + remoteSystemList[ i ].MTUSize = defaultMTUSize; + #ifdef _DEBUG + remoteSystemList[ i ].reliabilityLayer.ApplyNetworkSimulator(_packetloss, _minExtraPing, _extraPingVariance); + #endif + } + + for (unsigned int i=0; i < (unsigned int) maximumNumberOfPeers*REMOTE_SYSTEM_LOOKUP_HASH_MULTIPLE; i++) + { + remoteSystemLookup[i]=0; + } + } + + // For histogram statistics + // nextReadBytesTime=0; + // lastSentBytes=lastReceivedBytes=0; + + if ( endThreads ) + { + // lastUserUpdateCycle = 0; + + // Reset the frequency table that we use to save outgoing data + memset( frequencyTable, 0, sizeof( unsigned int ) * 256 ); + + // Reset the statistical data + rawBytesSent = rawBytesReceived = compressedBytesSent = compressedBytesReceived = 0; + + updateCycleIsRunning = false; + endThreads = false; + // Create the threads + threadSleepTimer = _threadSleepTimer; + + ClearBufferedCommands(); + ClearBufferedPackets(); + ClearSocketQueryOutput(); + + + for (int ipIndex=0; ipIndex < MAXIMUM_NUMBER_OF_INTERNAL_IDS; ipIndex++) + { +#if !defined(_XBOX) && !defined(X360) + if (ipList[ipIndex][0]) + { + mySystemAddress[ipIndex].port = SocketLayer::Instance()->GetLocalPort(socketList[0]->s); +// if (socketDescriptors[0].hostAddress==0 || socketDescriptors[0].hostAddress[0]==0) + mySystemAddress[ipIndex].binaryAddress = inet_addr( ipList[ ipIndex ] ); + // else + // mySystemAddress[ipIndex].binaryAddress = inet_addr( socketDescriptors[0].hostAddress ); + } + else + mySystemAddress[ipIndex]=UNASSIGNED_SYSTEM_ADDRESS; +#else + mySystemAddress[ipIndex]=UNASSIGNED_SYSTEM_ADDRESS; +#endif + } + + if ( isMainLoopThreadActive == false ) + { + + int errorCode = RakNet::RakThread::Create(UpdateNetworkLoop, this, threadPriority); + + if ( errorCode != 0 ) + { + Shutdown( 0, 0 ); + return false; + } + + for (i=0; is; + rpai[i].rakPeer=this; + isRecvFromLoopThreadActive=false; + errorCode = RakNet::RakThread::Create(RecvFromLoop, &rpai[i], threadPriority); + + if ( errorCode != 0 ) + { + Shutdown( 0, 0 ); + return false; + } + + while ( isRecvFromLoopThreadActive == false ) + RakSleep(10); + } + + } + + // Wait for the threads to activate. When they are active they will set these variables to true + + while ( isMainLoopThreadActive == false ) + RakSleep(10); + + } + + for (i=0; i < messageHandlerList.Size(); i++) + { + messageHandlerList[i]->OnRakPeerStartup(); + } + +#ifdef USE_THREADED_SEND + SendToThread::AddRef(); +#endif + + return true; +} + +// -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +// Description: +// Must be called while offline +// Secures connections though a combination of SHA1, AES128, SYN Cookies, and RSA to prevent +// connection spoofing, replay attacks, data eavesdropping, packet tampering, and MitM attacks. +// There is a significant amount of processing and a slight amount of bandwidth +// overhead for this feature. +// +// If you accept connections, you must call this or else secure connections will not be enabled +// for incoming connections. +// If you are connecting to another system, you can call this with values for the +// (e and p,q) public keys before connecting to prevent MitM +// +// Parameters: +// pubKeyE, pubKeyN - A pointer to the public keys from the RSACrypt class. See the Encryption sample +// privKeyP, privKeyQ - Private keys generated from the RSACrypt class. See the Encryption sample +// If the private keys are 0, then a new key will be generated when this function is called +// -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +void RakPeer::InitializeSecurity(const char *pubKeyE, const char *pubKeyN, const char *privKeyP, const char *privKeyQ ) +{ +#if !defined(_XBOX) && !defined(_WIN32_WCE) && !defined(X360) + if ( endThreads == false ) + return ; + + // Setting the client key is e,n, + // Setting the server key is p,q + if ( //( privKeyP && privKeyQ && ( pubKeyE || pubKeyN ) ) || + //( pubKeyE && pubKeyN && ( privKeyP || privKeyQ ) ) || + ( privKeyP && privKeyQ == 0 ) || + ( privKeyQ && privKeyP == 0 ) || + ( pubKeyE && pubKeyN == 0 ) || + ( pubKeyN && pubKeyE == 0 ) ) + { + // Invalid parameters + RakAssert( 0 ); + } + + GenerateSYNCookieRandomNumber(); + + usingSecurity = true; + + if ( privKeyP == 0 && privKeyQ == 0 && pubKeyE == 0 && pubKeyN == 0 ) + { + keysLocallyGenerated = true; + //rsacrypt.generateKeys(); + rsacrypt.generatePrivateKey(RAKNET_RSA_FACTOR_LIMBS); + } + + else + { + if ( pubKeyE && pubKeyN ) + { + // Save public keys + memcpy( ( char* ) & publicKeyE, pubKeyE, sizeof( publicKeyE ) ); + memcpy( publicKeyN, pubKeyN, sizeof( publicKeyN ) ); + } + + if ( privKeyP && privKeyQ ) + { + bool b = rsacrypt.setPrivateKey( (const uint32_t*) privKeyP, (const uint32_t*) privKeyQ, RAKNET_RSA_FACTOR_LIMBS/2); + (void) b; + RakAssert(b); + // BIGHALFSIZE( RSA_BIT_SIZE, p ); + // BIGHALFSIZE( RSA_BIT_SIZE, q ); + +// memcpy( p, privKeyP, sizeof( p ) ); +// memcpy( q, privKeyQ, sizeof( q ) ); + // Save private keys +// rsacrypt.setPrivateKey( p, q ); + } + + keysLocallyGenerated = false; + } +#endif +} + +// -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +// Description +// Must be called while offline +// Disables all security. +// -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +void RakPeer::DisableSecurity( void ) +{ +#if !defined(_XBOX) && !defined(_WIN32_WCE) && !defined(X360) + if ( endThreads == false ) + return ; + + usingSecurity = false; +#endif +} + +// -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +void RakPeer::AddToSecurityExceptionList(const char *ip) +{ + securityExceptionMutex.Lock(); + securityExceptionList.Insert(RakString(ip), __FILE__, __LINE__); + securityExceptionMutex.Unlock(); +} + +// -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +void RakPeer::RemoveFromSecurityExceptionList(const char *ip) +{ + if (securityExceptionList.Size()==0) + return; + + if (ip==0) + { + securityExceptionMutex.Lock(); + securityExceptionList.Clear(false, __FILE__, __LINE__); + securityExceptionMutex.Unlock(); + } + else + { + unsigned i=0; + securityExceptionMutex.Lock(); + while (i < securityExceptionList.Size()) + { + if (securityExceptionList[i].IPAddressMatch(ip)) + { + securityExceptionList[i]=securityExceptionList[securityExceptionList.Size()-1]; + securityExceptionList.RemoveAtIndex(securityExceptionList.Size()-1); + } + else + i++; + } + securityExceptionMutex.Unlock(); + } +} +// -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +bool RakPeer::IsInSecurityExceptionList(const char *ip) +{ + if (securityExceptionList.Size()==0) + return false; + + unsigned i=0; + securityExceptionMutex.Lock(); + for (; i < securityExceptionList.Size(); i++) + { + if (securityExceptionList[i].IPAddressMatch(ip)) + { + securityExceptionMutex.Unlock(); + return true; + } + } + securityExceptionMutex.Unlock(); + return false; +} + +// -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +// Description: +// Sets how many incoming connections are allowed. If this is less than the number of players currently connected, no +// more players will be allowed to connect. If this is greater than the maximum number of peers allowed, it will be reduced +// to the maximum number of peers allowed. Defaults to 0. +// +// Parameters: +// numberAllowed - Maximum number of incoming connections allowed. +// -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +void RakPeer::SetMaximumIncomingConnections( unsigned short numberAllowed ) +{ + maximumIncomingConnections = numberAllowed; +} + +// -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +// Description: +// Returns the maximum number of incoming connections, which is always <= maxConnections +// -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +unsigned short RakPeer::GetMaximumIncomingConnections( void ) const +{ + return maximumIncomingConnections; +} + +// -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +// Returns how many open connections there are at this time +// \return the number of open connections +// -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +unsigned short RakPeer::NumberOfConnections(void) const +{ + unsigned short i, count=0; + for (i=0; i < maximumNumberOfPeers; i++) + if (remoteSystemList[i].isActive) + count++; + return count; +} + +// -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +// Description: +// Sets the password incoming connections must match in the call to Connect (defaults to none) +// Pass 0 to passwordData to specify no password +// +// Parameters: +// passwordData: A data block that incoming connections must match. This can be just a password, or can be a stream of data. +// - Specify 0 for no password data +// passwordDataLength: The length in bytes of passwordData +// -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +void RakPeer::SetIncomingPassword( const char* passwordData, int passwordDataLength ) +{ + //if (passwordDataLength > MAX_OFFLINE_DATA_LENGTH) + // passwordDataLength=MAX_OFFLINE_DATA_LENGTH; + + if (passwordDataLength > 255) + passwordDataLength=255; + + if (passwordData==0) + passwordDataLength=0; + + // Not threadsafe but it's not important enough to lock. Who is going to change the password a lot during runtime? + // It won't overflow at least because incomingPasswordLength is an unsigned char + if (passwordDataLength>0) + memcpy(incomingPassword, passwordData, passwordDataLength); + incomingPasswordLength=(unsigned char)passwordDataLength; +} + +// -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +void RakPeer::GetIncomingPassword( char* passwordData, int *passwordDataLength ) +{ + if (passwordData==0) + { + *passwordDataLength=incomingPasswordLength; + return; + } + + if (*passwordDataLength > incomingPasswordLength) + *passwordDataLength=incomingPasswordLength; + + if (*passwordDataLength>0) + memcpy(passwordData, incomingPassword, *passwordDataLength); +} + +// -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +// Description: +// Call this to connect to the specified host (ip or domain name) and server port. +// Calling Connect and not calling SetMaximumIncomingConnections acts as a dedicated client. Calling both acts as a true peer. +// This is a non-blocking connection. You know the connection is successful when IsConnected() returns true +// or receive gets a packet with the type identifier ID_CONNECTION_ACCEPTED. If the connection is not +// successful, such as rejected connection or no response then neither of these things will happen. +// Requires that you first call Initialize +// +// Parameters: +// host: Either a dotted IP address or a domain name +// remotePort: Which port to connect to on the remote machine. +// passwordData: A data block that must match the data block on the server. This can be just a password, or can be a stream of data +// passwordDataLength: The length in bytes of passwordData +// +// Returns: +// True on successful initiation. False on incorrect parameters, internal error, or too many existing peers +// -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +bool RakPeer::Connect( const char* host, unsigned short remotePort, const char *passwordData, int passwordDataLength, unsigned connectionSocketIndex, unsigned sendConnectionAttemptCount, unsigned timeBetweenSendConnectionAttemptsMS, RakNetTime timeoutTime ) +{ + // If endThreads is true here you didn't call Startup() first. + if ( host == 0 || endThreads || connectionSocketIndex>=socketList.Size() ) + return false; + + connectionSocketIndex=GetRakNetSocketFromUserConnectionSocketIndex(connectionSocketIndex); + + if (passwordDataLength>255) + passwordDataLength=255; + + if (passwordData==0) + passwordDataLength=0; + + // Not threadsafe but it's not important enough to lock. Who is going to change the password a lot during runtime? + // It won't overflow at least because outgoingPasswordLength is an unsigned char +// if (passwordDataLength>0) +// memcpy(outgoingPassword, passwordData, passwordDataLength); +// outgoingPasswordLength=(unsigned char) passwordDataLength; + + if ( NonNumericHostString( host ) ) + { + host = ( char* ) SocketLayer::Instance()->DomainNameToIP( host ); + + if (host==0) + return false; + } + + // 04/02/09 - Can't remember why I disabled connecting to self, but it seems to work + // Connecting to ourselves in the same instance of the program? +// if ( ( strcmp( host, "127.0.0.1" ) == 0 || strcmp( host, "0.0.0.0" ) == 0 ) && remotePort == mySystemAddress[0].port ) +// return false; + + return SendConnectionRequest( host, remotePort, passwordData, passwordDataLength, connectionSocketIndex, 0, sendConnectionAttemptCount, timeBetweenSendConnectionAttemptsMS, timeoutTime); +} + +// -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + +bool RakPeer::ConnectWithSocket(const char* host, unsigned short remotePort, const char *passwordData, int passwordDataLength, RakNetSmartPtr socket, unsigned sendConnectionAttemptCount, unsigned timeBetweenSendConnectionAttemptsMS, RakNetTime timeoutTime) +{ + if ( host == 0 || endThreads || socket.IsNull() ) + return false; + + if (passwordDataLength>255) + passwordDataLength=255; + + if (passwordData==0) + passwordDataLength=0; + + if ( NonNumericHostString( host ) ) + { + host = ( char* ) SocketLayer::Instance()->DomainNameToIP( host ); + + if (host==0) + return false; + } + + return SendConnectionRequest( host, remotePort, passwordData, passwordDataLength, 0, 0, sendConnectionAttemptCount, timeBetweenSendConnectionAttemptsMS, timeoutTime, socket ); + +} + +// -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +// Description: +// Stops the network threads and close all connections. Multiple calls are ok. +// -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +void RakPeer::Shutdown( unsigned int blockDuration, unsigned char orderingChannel, PacketPriority disconnectionNotificationPriority ) +{ + unsigned i,j; + bool anyActive; + RakNetTime startWaitingTime; +// SystemAddress systemAddress; + RakNetTime time; + //unsigned short systemListSize = remoteSystemListSize; // This is done for threading reasons + unsigned short systemListSize = maximumNumberOfPeers; + + if ( blockDuration > 0 ) + { + for ( i = 0; i < systemListSize; i++ ) + { + // remoteSystemList in user thread + if (remoteSystemList[i].isActive) + NotifyAndFlagForShutdown(remoteSystemList[i].systemAddress, false, orderingChannel, disconnectionNotificationPriority); + } + + time = RakNet::GetTime(); + startWaitingTime = time; + while ( time - startWaitingTime < blockDuration ) + { + anyActive=false; + for (j=0; j < systemListSize; j++) + { + // remoteSystemList in user thread + if (remoteSystemList[j].isActive) + { + anyActive=true; + break; + } + } + + // If this system is out of packets to send, then stop waiting + if ( anyActive==false ) + break; + + // This will probably cause the update thread to run which will probably + // send the disconnection notification + + RakSleep(15); + time = RakNet::GetTime(); + } + } + + for (i=0; i < messageHandlerList.Size(); i++) + { + messageHandlerList[i]->OnRakPeerShutdown(); + } + + quitAndDataEvents.SetEvent(); + + endThreads = true; + // Get recvfrom to unblock + for (i=0; i < socketList.Size(); i++) + { + if (SocketLayer::Instance()->SendTo(socketList[i]->s, (const char*) &i,1,"127.0.0.1", socketList[i]->boundAddress.port, socketList[i]->remotePortRakNetWasStartedOn_PS3)!=0) + break; + } + while ( isMainLoopThreadActive ) + { + endThreads = true; + RakSleep(15); + } + +// char c=0; +// unsigned int socketIndex; + // remoteSystemList in Single thread + for ( i = 0; i < systemListSize; i++ ) + { + // Reserve this reliability layer for ourselves + remoteSystemList[ i ].isActive = false; + + // Remove any remaining packets + remoteSystemList[ i ].reliabilityLayer.Reset(false, remoteSystemList[ i ].MTUSize); + + remoteSystemList[ i ].rakNetSocket.SetNull(); + } + + + // Setting maximumNumberOfPeers to 0 allows remoteSystemList to be reallocated in Initialize. + // Setting remoteSystemListSize prevents threads from accessing the reliability layer + maximumNumberOfPeers = 0; + //remoteSystemListSize = 0; + + // Free any packets the user didn't deallocate + packetReturnMutex.Lock(); + for (unsigned int i=0; i < packetReturnQueue.Size(); i++) + DeallocatePacket(packetReturnQueue[i]); + packetReturnQueue.Clear(__FILE__,__LINE__); + packetReturnMutex.Unlock(); + packetAllocationPoolMutex.Lock(); + packetAllocationPool.Clear(__FILE__,__LINE__); + packetAllocationPoolMutex.Unlock(); + + blockOnRPCReply=false; + + RakNetTimeMS timeout = RakNet::GetTimeMS()+1000; + while ( isRecvFromLoopThreadActive && RakNet::GetTimeMS()SendTo(socketList[i]->s, (const char*) &i,1,"127.0.0.1", socketList[i]->boundAddress.port, socketList[i]->remotePortRakNetWasStartedOn_PS3); + } + + RakSleep(30); + } + + if (isRecvFromLoopThreadActive) + { + timeout = RakNet::GetTimeMS()+1000; + while ( isRecvFromLoopThreadActive && RakNet::GetTimeMS() 0 ); +#endif + RakAssert( !( reliability >= NUMBER_OF_RELIABILITIES || reliability < 0 ) ); + RakAssert( !( priority > NUMBER_OF_PRIORITIES || priority < 0 ) ); + RakAssert( !( orderingChannel >= NUMBER_OF_ORDERED_STREAMS ) ); + + if ( data == 0 || length < 0 ) + return 0; + + if ( remoteSystemList == 0 || endThreads == true ) + return 0; + + if ( broadcast == false && systemIdentifier.IsUndefined()) + return 0; + + uint32_t usedSendReceipt; + if (forceReceipt!=0) + usedSendReceipt=forceReceipt; + else + usedSendReceipt=IncrementNextSendReceipt(); + + if (broadcast==false && IsLoopbackAddress(systemIdentifier,true)) + { + SendLoopback(data,length); + + if (reliability>=UNRELIABLE_WITH_ACK_RECEIPT) + { + char buff[5]; + buff[0]=ID_SND_RECEIPT_ACKED; + sendReceiptSerialMutex.Lock(); + memcpy(buff+1, &sendReceiptSerial, 4); + sendReceiptSerialMutex.Unlock(); + SendLoopback( buff, 5 ); + } + + return usedSendReceipt; + } + + if (broadcast==false && router && IsConnected(systemIdentifier.systemAddress)==false) + { + router->Send(data, BYTES_TO_BITS(length), priority, reliability, orderingChannel, systemIdentifier.systemAddress); + } + else + { + SendBuffered(data, length*8, priority, reliability, orderingChannel, systemIdentifier, broadcast, RemoteSystemStruct::NO_ACTION, usedSendReceipt); + } + + return usedSendReceipt; +} + +void RakPeer::SendLoopback( const char *data, const int length ) +{ + if ( data == 0 || length < 0 ) + return; + + Packet *packet = AllocPacket(length, __FILE__, __LINE__); + memcpy(packet->data, data, length); + packet->systemAddress = GetLoopbackAddress(); + packet->guid=myGuid; + PushBackPacket(packet, false); +} + +uint32_t RakPeer::Send( const RakNet::BitStream * bitStream, PacketPriority priority, PacketReliability reliability, char orderingChannel, const AddressOrGUID systemIdentifier, bool broadcast, uint32_t forceReceipt ) +{ +#ifdef _DEBUG + RakAssert( bitStream->GetNumberOfBytesUsed() > 0 ); +#endif + + RakAssert( !( reliability >= NUMBER_OF_RELIABILITIES || reliability < 0 ) ); + RakAssert( !( priority > NUMBER_OF_PRIORITIES || priority < 0 ) ); + RakAssert( !( orderingChannel >= NUMBER_OF_ORDERED_STREAMS ) ); + + if ( bitStream->GetNumberOfBytesUsed() == 0 ) + return 0; + + if ( remoteSystemList == 0 || endThreads == true ) + return 0; + + if ( broadcast == false && systemIdentifier.IsUndefined() ) + return 0; + + uint32_t usedSendReceipt; + if (forceReceipt!=0) + usedSendReceipt=forceReceipt; + else + usedSendReceipt=IncrementNextSendReceipt(); + + if (broadcast==false && IsLoopbackAddress(systemIdentifier,true)) + { + SendLoopback((const char*) bitStream->GetData(),bitStream->GetNumberOfBytesUsed()); + if (reliability>=UNRELIABLE_WITH_ACK_RECEIPT) + { + char buff[5]; + buff[0]=ID_SND_RECEIPT_ACKED; + sendReceiptSerialMutex.Lock(); + memcpy(buff+1, &sendReceiptSerial,4); + sendReceiptSerialMutex.Unlock(); + SendLoopback( buff, 5 ); + } + return usedSendReceipt; + } + + if (broadcast==false && router && IsConnected(systemIdentifier.systemAddress)==false) + { + router->Send((const char*)bitStream->GetData(), bitStream->GetNumberOfBitsUsed(), priority, reliability, orderingChannel, systemIdentifier.systemAddress); + } + else + { + // Sends need to be buffered and processed in the update thread because the systemAddress associated with the reliability layer can change, + // from that thread, resulting in a send to the wrong player! While I could mutex the systemAddress, that is much slower than doing this + SendBuffered((const char*)bitStream->GetData(), bitStream->GetNumberOfBitsUsed(), priority, reliability, orderingChannel, systemIdentifier, broadcast, RemoteSystemStruct::NO_ACTION, usedSendReceipt); + } + + return usedSendReceipt; +} +// -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +// Sends multiple blocks of data, concatenating them automatically. +// +// This is equivalent to: +// RakNet::BitStream bs; +// bs.WriteAlignedBytes(block1, blockLength1); +// bs.WriteAlignedBytes(block2, blockLength2); +// bs.WriteAlignedBytes(block3, blockLength3); +// Send(&bs, ...) +// +// This function only works while the connected +// \param[in] data An array of pointers to blocks of data +// \param[in] lengths An array of integers indicating the length of each block of data +// \param[in] numParameters Length of the arrays data and lengths +// \param[in] priority What priority level to send on. See PacketPriority.h +// \param[in] reliability How reliability to send this data. See PacketPriority.h +// \param[in] orderingChannel When using ordered or sequenced messages, what channel to order these on. Messages are only ordered relative to other messages on the same stream +// \param[in] systemIdentifier Who to send this packet to, or in the case of broadcasting who not to send it to. Pass either a SystemAddress structure or a RakNetGUID structure. Use UNASSIGNED_SYSTEM_ADDRESS or to specify none +// \param[in] broadcast True to send this packet to all connected systems. If true, then systemAddress specifies who not to send the packet to. +// \return False if we are not connected to the specified recipient. True otherwise +// -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +uint32_t RakPeer::SendList( const char **data, const int *lengths, const int numParameters, PacketPriority priority, PacketReliability reliability, char orderingChannel, const AddressOrGUID systemIdentifier, bool broadcast, uint32_t forceReceipt ) +{ +#ifdef _DEBUG + RakAssert( data ); +#endif + + if ( data == 0 || lengths == 0 ) + return 0; + + if ( remoteSystemList == 0 || endThreads == true ) + return 0; + + if (numParameters==0) + return 0; + + if (lengths==0) + return 0; + + if ( broadcast == false && systemIdentifier.IsUndefined() ) + return 0; + + uint32_t usedSendReceipt; + if (forceReceipt!=0) + usedSendReceipt=forceReceipt; + else + usedSendReceipt=IncrementNextSendReceipt(); + + SendBufferedList(data, lengths, numParameters, priority, reliability, orderingChannel, systemIdentifier, broadcast, RemoteSystemStruct::NO_ACTION, usedSendReceipt); + + return usedSendReceipt; +} + +// -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +// Description: +// Gets a packet from the incoming packet queue. Use DeallocatePacket to deallocate the packet after you are done with it. Packets must be deallocated in the same order they are received. +// Check the Packet struct at the top of CoreNetworkStructures.h for the format of the struct +// +// Returns: +// 0 if no packets are waiting to be handled, otherwise an allocated packet +// If the client is not active this will also return 0, as all waiting packets are flushed when the client is Disconnected +// This also updates all memory blocks associated with synchronized memory and distributed objects +// -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +Packet* RakPeer::Receive( void ) +{ + Packet *packet = ReceiveIgnoreRPC(); + while (packet && (packet->data[ 0 ] == ID_RPC || (packet->length>sizeof(unsigned char)+sizeof(RakNetTime) && packet->data[0]==ID_TIMESTAMP && packet->data[sizeof(unsigned char)+sizeof(RakNetTime)]==ID_RPC))) + { + // Do RPC calls from the user thread, not the network update thread + // If we are currently blocking on an RPC reply, send ID_RPC to the blocker to handle rather than handling RPCs automatically + HandleRPCPacket( ( char* ) packet->data, packet->length, packet->systemAddress ); + DeallocatePacket( packet ); + + packet = ReceiveIgnoreRPC(); + } + + return packet; +} + +// -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +// Internal - Gets a packet without checking for RPCs +// -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +#ifdef _MSC_VER +#pragma warning( disable : 4701 ) // warning C4701: local variable may be used without having been initialized +#endif +Packet* RakPeer::ReceiveIgnoreRPC( void ) +{ + if ( !( IsActive() ) ) + return 0; + + Packet *packet; +// Packet **threadPacket; + PluginReceiveResult pluginResult; + + int offset; + unsigned int i; + + for (i=0; i < messageHandlerList.Size(); i++) + { + messageHandlerList[i]->Update(); + } + + do + { + packetReturnMutex.Lock(); + if (packetReturnQueue.IsEmpty()) + packet=0; + else + packet = packetReturnQueue.Pop(); + packetReturnMutex.Unlock(); + if (packet==0) + return 0; + + if ( ( packet->length >= sizeof(unsigned char) + sizeof( RakNetTime ) ) && + ( (unsigned char) packet->data[ 0 ] == ID_TIMESTAMP ) ) + { + offset = sizeof(unsigned char); + ShiftIncomingTimestamp( packet->data + offset, packet->systemAddress ); + } + if ( (unsigned char) packet->data[ 0 ] == ID_RPC_REPLY ) + { + HandleRPCReplyPacket( ( char* ) packet->data, packet->length, packet->systemAddress ); + DeallocatePacket( packet ); + packet=0; // Will do the loop again and get another packet + } + else + { + for (i=0; i < messageHandlerList.Size(); i++) + { + switch (packet->data[0]) + { + case ID_DISCONNECTION_NOTIFICATION: + messageHandlerList[i]->OnClosedConnection(packet->systemAddress, packet->guid, LCR_DISCONNECTION_NOTIFICATION); + break; + case ID_CONNECTION_LOST: + messageHandlerList[i]->OnClosedConnection(packet->systemAddress, packet->guid, LCR_CONNECTION_LOST); + break; + case ID_NEW_INCOMING_CONNECTION: + messageHandlerList[i]->OnNewConnection(packet->systemAddress, packet->guid, true); + break; + case ID_CONNECTION_REQUEST_ACCEPTED: + messageHandlerList[i]->OnNewConnection(packet->systemAddress, packet->guid, false); + break; + case ID_CONNECTION_ATTEMPT_FAILED: + messageHandlerList[i]->OnFailedConnectionAttempt(packet->systemAddress, FCAR_CONNECTION_ATTEMPT_FAILED); + break; + case ID_ALREADY_CONNECTED: + messageHandlerList[i]->OnFailedConnectionAttempt(packet->systemAddress, FCAR_ALREADY_CONNECTED); + break; + case ID_NO_FREE_INCOMING_CONNECTIONS: + messageHandlerList[i]->OnFailedConnectionAttempt(packet->systemAddress, FCAR_NO_FREE_INCOMING_CONNECTIONS); + break; + case ID_RSA_PUBLIC_KEY_MISMATCH: + messageHandlerList[i]->OnFailedConnectionAttempt(packet->systemAddress, FCAR_RSA_PUBLIC_KEY_MISMATCH); + break; + case ID_CONNECTION_BANNED: + messageHandlerList[i]->OnFailedConnectionAttempt(packet->systemAddress, FCAR_CONNECTION_BANNED); + break; + case ID_INVALID_PASSWORD: + messageHandlerList[i]->OnFailedConnectionAttempt(packet->systemAddress, FCAR_INVALID_PASSWORD); + break; + case ID_INCOMPATIBLE_PROTOCOL_VERSION: + messageHandlerList[i]->OnFailedConnectionAttempt(packet->systemAddress, FCAR_INCOMPATIBLE_PROTOCOL); + break; + case ID_IP_RECENTLY_CONNECTED: + messageHandlerList[i]->OnFailedConnectionAttempt(packet->systemAddress, FCAR_IP_RECENTLY_CONNECTED); + break; + } + + + pluginResult=messageHandlerList[i]->OnReceive(packet); + if (pluginResult==RR_STOP_PROCESSING_AND_DEALLOCATE) + { + DeallocatePacket( packet ); + packet=0; // Will do the loop again and get another packet + break; // break out of the enclosing for + } + else if (pluginResult==RR_STOP_PROCESSING) + { + packet=0; + break; + } + } + } + + } while(packet==0); + +#ifdef _DEBUG + RakAssert( packet->data ); +#endif + + return packet; +} + +// -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +// Description: +// Call this to deallocate a packet returned by Receive +// -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +void RakPeer::DeallocatePacket( Packet *packet ) +{ + if ( packet == 0 ) + return; + + if (packet->deleteData) + { + rakFree_Ex(packet->data, __FILE__, __LINE__ ); + packet->~Packet(); + packetAllocationPoolMutex.Lock(); + packetAllocationPool.Release(packet,__FILE__,__LINE__); + packetAllocationPoolMutex.Unlock(); + } + else + { + rakFree_Ex(packet, __FILE__, __LINE__ ); + } +} + +// -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +// Description: +// Return the total number of connections we are allowed +// -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +unsigned short RakPeer::GetMaximumNumberOfPeers( void ) const +{ + return maximumNumberOfPeers; +} + +// -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +// Description: +// Register a C function as available for calling as a remote procedure call +// +// Parameters: +// uniqueID: A string of only letters to identify this procedure. Recommended you use the macro CLASS_MEMBER_ID for class member functions +// functionName(...): The name of the C function or C++ singleton to be used as a function pointer +// This can be called whether the client is active or not, and registered functions stay registered unless unregistered with +// UnregisterAsRemoteProcedureCall +// -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +void RakPeer::RegisterAsRemoteProcedureCall( const char* uniqueID, void ( *functionPointer ) ( RPCParameters *rpcParms ) ) +{ + if ( uniqueID == 0 || uniqueID[ 0 ] == 0 || functionPointer == 0 ) + return; + + rpcMap.AddIdentifierWithFunction(uniqueID, (void*)functionPointer, false); + + /* + char uppercaseUniqueID[ 256 ]; + + int counter = 0; + + while ( uniqueID[ counter ] ) + { + uppercaseUniqueID[ counter ] = ( char ) toupper( uniqueID[ counter ] ); + counter++; + } + + uppercaseUniqueID[ counter ] = 0; + + // Each id must be unique +//#ifdef _DEBUG +// RakAssert( rpcTree.IsIn( RPCNode( uppercaseUniqueID, functionName ) ) == false ); +//#endif + + if (rpcTree.IsIn( RPCNode( uppercaseUniqueID, functionName ) )) + return; + + rpcTree.Add( RPCNode( uppercaseUniqueID, functionName ) ); + */ +} + +void RakPeer::RegisterClassMemberRPC( const char* uniqueID, void *functionPointer ) +{ + if ( uniqueID == 0 || uniqueID[ 0 ] == 0 || functionPointer == 0 ) + return; + + rpcMap.AddIdentifierWithFunction(uniqueID, functionPointer, true); +} + +// -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +// Description: +// Unregisters a C function as available for calling as a remote procedure call that was formerly registered +// with RegisterAsRemoteProcedureCall +// +// Parameters: +// uniqueID: A null terminated string to identify this procedure. Must match the parameter +// passed to RegisterAsRemoteProcedureCall +// -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +void RakPeer::UnregisterAsRemoteProcedureCall( const char* uniqueID ) +{ + if ( uniqueID == 0 || uniqueID[ 0 ] == 0 ) + return; + +// Don't call this while running because if you remove RPCs and add them they will not match the indices on the other systems anymore +#ifdef _DEBUG + RakAssert(IsActive()==false); + //RakAssert( strlen( uniqueID ) < 256 ); +#endif + + rpcMap.RemoveNode(uniqueID); + + /* + char uppercaseUniqueID[ 256 ]; + + strcpy( uppercaseUniqueID, uniqueID ); + + int counter = 0; + + while ( uniqueID[ counter ] ) + { + uppercaseUniqueID[ counter ] = ( char ) toupper( uniqueID[ counter ] ); + counter++; + } + + uppercaseUniqueID[ counter ] = 0; + + // Unique ID must exist +#ifdef _DEBUG + RakAssert( rpcTree.IsIn( RPCNode( uppercaseUniqueID, 0 ) ) == true ); +#endif + + rpcTree.Del( RPCNode( uppercaseUniqueID, 0 ) ); + */ +} + +// -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +void RakPeer::SetNetworkIDManager( NetworkIDManager *manager ) +{ + networkIDManager=manager; + if (manager) + manager->SetGuid(myGuid); +} +// -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +NetworkIDManager *RakPeer::GetNetworkIDManager(void) const +{ + return networkIDManager; +} +// -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +bool RakPeer::RPC( const char* uniqueID, const char *data, BitSize_t bitLength, PacketPriority priority, PacketReliability reliability, char orderingChannel, const AddressOrGUID systemIdentifier, bool broadcast, RakNetTime *includedTimestamp, NetworkID networkID, RakNet::BitStream *replyFromTarget ) +{ + return RPC( uniqueID, data, bitLength, priority, reliability, orderingChannel, systemIdentifier, broadcast, includedTimestamp, networkID, replyFromTarget, 0, UNASSIGNED_SYSTEM_ADDRESS); +} + +bool RakPeer::RPC( const char* uniqueID, const char *data, BitSize_t bitLength, PacketPriority priority, PacketReliability reliability, char orderingChannel, const AddressOrGUID systemIdentifier, bool broadcast, RakNetTime *includedTimestamp, NetworkID networkID, RakNet::BitStream *replyFromTarget, unsigned char proxyType, SystemAddress proxyTargetAddress ) +{ +#ifdef _DEBUG + RakAssert( uniqueID && uniqueID[ 0 ] ); + RakAssert(orderingChannel >=0 && orderingChannel < 32); +#endif + + if ( uniqueID == 0 ) + return false; + + if ( strlen( uniqueID ) > 256 ) + { +#ifdef _DEBUG + RakAssert( 0 ); +#endif + return false; // Unique ID is too long + } + if (replyFromTarget && blockOnRPCReply==true) + { + // TODO - this should be fixed eventually + // Prevent a bug where function A calls B (blocking) which calls C back on the sender, which calls D, and C is blocking. + // blockOnRPCReply is a shared variable so making it unset would unset both blocks, rather than the lowest on the callstack + // Fix by tracking which function the reply is for. + return false; + } + + unsigned *sendList; +// bool callerAllocationDataUsed; + unsigned sendListSize; + + // All this code modifies bcs->data and bcs->numberOfBitsToSend in order to transform an RPC request into an actual packet for SendImmediate + RPCIndex rpcIndex; // Index into the list of RPC calls so we know what number to encode in the packet +// char *userData; // RPC ID (the name of it) and a pointer to the data sent by the user +// int extraBuffer; // How many data bytes were allocated to hold the RPC header + unsigned remoteSystemIndex, sendListIndex; // Iterates into the list of remote systems +// int dataBlockAllocationLength; // Total number of bytes to allocate for the packet +// char *writeTarget; // Used to hold either a block of allocated data or the externally allocated data + + sendListSize=0; + bool routeSend; + routeSend=false; + + if (broadcast==false) + { +#if !defined(_XBOX) && !defined(X360) + sendList=(unsigned *)alloca(sizeof(unsigned)); +#else + sendList = (unsigned int*) rakMalloc_Ex(sizeof(unsigned), __FILE__, __LINE__); +#endif + remoteSystemIndex=GetIndexFromSystemAddress( systemIdentifier.systemAddress, false ); + if (remoteSystemIndex!=(unsigned)-1 && + remoteSystemList[remoteSystemIndex].isActive && + remoteSystemList[remoteSystemIndex].connectMode!=RemoteSystemStruct::DISCONNECT_ASAP && + remoteSystemList[remoteSystemIndex].connectMode!=RemoteSystemStruct::DISCONNECT_ASAP_SILENTLY && + remoteSystemList[remoteSystemIndex].connectMode!=RemoteSystemStruct::DISCONNECT_ON_NO_ACK) + { + sendList[0]=remoteSystemIndex; + sendListSize=1; + } + else if (router) + routeSend=true; + } + else + { +#if !defined(_XBOX) && !defined(X360) + sendList=(unsigned *)alloca(sizeof(unsigned)*maximumNumberOfPeers); +#else + sendList = (unsigned int*) rakMalloc_Ex(sizeof(unsigned)*maximumNumberOfPeers, __FILE__, __LINE__); +#endif + + for ( remoteSystemIndex = 0; remoteSystemIndex < maximumNumberOfPeers; remoteSystemIndex++ ) + { + if ( remoteSystemList[ remoteSystemIndex ].isActive && remoteSystemList[ remoteSystemIndex ].systemAddress != systemIdentifier.systemAddress ) + sendList[sendListSize++]=remoteSystemIndex; + } + } + + if (sendListSize==0 && routeSend==false) + { +#if defined(_XBOX) || defined(X360) + +#endif + + return false; + } + if (routeSend) + sendListSize=1; + + RakNet::BitStream outgoingBitStream; + if (proxyTargetAddress != UNASSIGNED_SYSTEM_ADDRESS) + { + //printf("Sending proxied RPC of type %d\n", proxyType); + outgoingBitStream.Write(proxyType); + if (proxyType == ID_PROXY_SERVER_MESSAGE) + outgoingBitStream.Write(proxyTargetAddress); + } + + // remoteSystemList in network thread + for (sendListIndex=0; sendListIndex < (unsigned)sendListSize; sendListIndex++) + { + if (includedTimestamp) + { + outgoingBitStream.Write((MessageID)ID_TIMESTAMP); + outgoingBitStream.Write(*includedTimestamp); + } + outgoingBitStream.Write((MessageID)ID_RPC); + if (routeSend) + rpcIndex=UNDEFINED_RPC_INDEX; + else + rpcIndex=remoteSystemList[sendList[sendListIndex]].rpcMap.GetIndexFromFunctionName(uniqueID); // Lots of trouble but we can only use remoteSystem->[whatever] in this thread so that is why this command was buffered + if (rpcIndex!=UNDEFINED_RPC_INDEX) + { + // We have an RPC name to an index mapping, so write the index + outgoingBitStream.Write(false); + outgoingBitStream.WriteCompressed(rpcIndex); + } + else + { + // No mapping, so write the encoded RPC name + outgoingBitStream.Write(true); + stringCompressor->EncodeString(uniqueID, 256, &outgoingBitStream); + } + outgoingBitStream.Write((bool) ((replyFromTarget!=0)==true)); + outgoingBitStream.WriteCompressed( bitLength ); + if (networkID==UNASSIGNED_NETWORK_ID) + { + // No object ID + outgoingBitStream.Write(false); + } + else + { + // Encode an object ID. This will use pointer to class member RPC + outgoingBitStream.Write(true); + outgoingBitStream.Write(networkID); + } + + + if ( bitLength > 0 ) + outgoingBitStream.WriteBits( (const unsigned char *) data, bitLength, false ); // Last param is false to write the raw data originally from another bitstream, rather than shifting from user data + else + outgoingBitStream.WriteCompressed( ( unsigned int ) 0 ); + + if (routeSend) + router->Send((const char*)outgoingBitStream.GetData(), outgoingBitStream.GetNumberOfBitsUsed(), priority,reliability,orderingChannel,systemIdentifier.systemAddress); + else + Send(&outgoingBitStream, priority, reliability, orderingChannel, remoteSystemList[sendList[sendListIndex]].systemAddress, false); + } + +#if defined(_XBOX) || defined(X360) + +#endif + + if (replyFromTarget) + { + blockOnRPCReply=true; + // 04/20/06 Just do this transparently. + // We have to be able to read blocking packets out of order. Otherwise, if two systems were to send blocking RPC calls to each other at the same time, + // and they also had ordered packets waiting before the block, it would be impossible to unblock. + // RakAssert(reliability==RELIABLE || reliability==UNRELIABLE); + replyFromTargetBS=replyFromTarget; + replyFromTargetPlayer=systemIdentifier.systemAddress; + replyFromTargetBroadcast=broadcast; + } + + // Do not enter this loop on blockOnRPCReply because it is a global which could be set to true by an RPC higher on the callstack, where one RPC was called while waiting for another RPC + if (replyFromTarget) +// if (blockOnRPCReply) + { +// Packet *p; + RakNetTime stopWaitingTime=RakNet::GetTime()+30000; +// RPCIndex arrivedRPCIndex; +// char uniqueIdentifier[256]; + if (reliability==UNRELIABLE) + if (systemIdentifier.systemAddress==UNASSIGNED_SYSTEM_ADDRESS) + stopWaitingTime=RakNet::GetTime()+1500; // Lets guess the ave. ping is 500. Not important to be very accurate + else + stopWaitingTime=RakNet::GetTime()+GetAveragePing(systemIdentifier.systemAddress)*3; + + // For reliable messages, block until we get a reply or the connection is lost + // For unreliable messages, block until we get a reply, the connection is lost, or 3X the ping passes + while (blockOnRPCReply && + (( + reliability==RELIABLE || + reliability==RELIABLE_ORDERED || + reliability==RELIABLE_SEQUENCED || + reliability==RELIABLE_WITH_ACK_RECEIPT || + reliability==RELIABLE_ORDERED_WITH_ACK_RECEIPT +// || +// reliability==RELIABLE_SEQUENCED_WITH_ACK_RECEIPT + ) || + RakNet::GetTime() < stopWaitingTime)) + { + + RakSleep(30); + + if (routeSend==false && ValidSendTarget(systemIdentifier.systemAddress, broadcast)==false) + return false; + + unsigned i; + i=0; + + + packetReturnMutex.Lock(); + while (i < packetReturnQueue.Size()) + { + if ((unsigned char) packetReturnQueue[i]->data[ 0 ] == ID_RPC_REPLY ) + { + HandleRPCReplyPacket( ( char* ) packetReturnQueue[i]->data, packetReturnQueue[i]->length, packetReturnQueue[i]->systemAddress ); + DeallocatePacket( packetReturnQueue[i] ); + packetReturnQueue.RemoveAtIndex(i); + } + else + i++; + } + packetReturnMutex.Unlock(); + + /* + // Scan for RPC reply packets to break out of this loop + while (i < packetPool.Size()) + { + if ((unsigned char) packetPool[i]->data[ 0 ] == ID_RPC_REPLY ) + { + HandleRPCReplyPacket( ( char* ) packetPool[i]->data, packetPool[i]->length, packetPool[i]->systemAddress ); + DeallocatePacket( packetPool[i] ); + packetPool.RemoveAtIndex(i); + } + else + i++; + } +#ifdef _RAKNET_THREADSAFE + rakPeerMutexes[packetPool_Mutex].Unlock(); +#endif + */ + + PushBackPacket(ReceiveIgnoreRPC(), false); + + + // I might not support processing other RPCs while blocking on one due to complexities I can't control + // Problem is FuncA calls FuncB which calls back to the sender FuncC. Sometimes it is desirable to call FuncC before returning a return value + // from FuncB - sometimes not. There is also a problem with recursion where FuncA calls FuncB which calls FuncA - sometimes valid if + // a different control path is taken in FuncA. (This can take many different forms) + /* + // Same as Receive, but doesn't automatically do RPCs + p = ReceiveIgnoreRPC(); + if (p) + { + // Process all RPC calls except for those calling the function we are currently blocking in (to prevent recursion). + if ( p->data[ 0 ] == ID_RPC ) + { + RakNet::BitStream temp((unsigned char *) p->data, p->length, false); + RPCNode *rpcNode; + temp.IgnoreBits(8); + bool nameIsEncoded; + temp.Read(nameIsEncoded); + if (nameIsEncoded) + { + stringCompressor->DecodeString((char*)uniqueIdentifier, 256, &temp); + } + else + { + temp.ReadCompressed( arrivedRPCIndex ); + rpcNode=rpcMap.GetNodeFromIndex( arrivedRPCIndex ); + if (rpcNode==0) + { + // Invalid RPC format +#ifdef _DEBUG + RakAssert(0); +#endif + DeallocatePacket(p); + continue; + } + else + strcpy(uniqueIdentifier, rpcNode->uniqueIdentifier); + } + + if (strcmp(uniqueIdentifier, uniqueID)!=0) + { + HandleRPCPacket( ( char* ) p->data, p->length, p->systemAddress ); + DeallocatePacket(p); + } + else + { + PushBackPacket(p, false); + } + } + else + { + PushBackPacket(p, false); + } + } + */ + } + + blockOnRPCReply=false; + } + + return true; +} + + +#ifdef _MSC_VER +#pragma warning( disable : 4701 ) // warning C4701: local variable may be used without having been initialized +#endif +bool RakPeer::RPC( const char* uniqueID, const RakNet::BitStream *bitStream, PacketPriority priority, PacketReliability reliability, char orderingChannel, const AddressOrGUID systemIdentifier, bool broadcast, RakNetTime *includedTimestamp, NetworkID networkID, RakNet::BitStream *replyFromTarget ) +{ + if (bitStream) + return RPC(uniqueID, (const char*) bitStream->GetData(), bitStream->GetNumberOfBitsUsed(), priority, reliability, orderingChannel, systemIdentifier.systemAddress, broadcast, includedTimestamp, networkID, replyFromTarget); + else + return RPC(uniqueID, 0,0, priority, reliability, orderingChannel, systemIdentifier.systemAddress, broadcast, includedTimestamp, networkID, replyFromTarget); +} + +bool RakPeer::RPC( const char* uniqueID, const RakNet::BitStream *bitStream, PacketPriority priority, PacketReliability reliability, char orderingChannel, const AddressOrGUID systemIdentifier, bool broadcast, RakNetTime *includedTimestamp, NetworkID networkID, RakNet::BitStream *replyFromTarget, unsigned char proxyType, SystemAddress proxyTargetAddress ) +{ + if (bitStream) + return RPC(uniqueID, (const char*) bitStream->GetData(), bitStream->GetNumberOfBitsUsed(), priority, reliability, orderingChannel, systemIdentifier.systemAddress, broadcast, includedTimestamp, networkID, replyFromTarget, proxyType, proxyTargetAddress); + else + return RPC(uniqueID, 0,0, priority, reliability, orderingChannel, systemIdentifier.systemAddress, broadcast, includedTimestamp, networkID, replyFromTarget, proxyType, proxyTargetAddress); +} + +// -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +// Description: +// Close the connection to another host (if we initiated the connection it will disconnect, if they did it will kick them out). +// +// Parameters: +// target: Which connection to close +// sendDisconnectionNotification: True to send ID_DISCONNECTION_NOTIFICATION to the recipient. False to close it silently. +// channel: If blockDuration > 0, the disconnect packet will be sent on this channel +// -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +void RakPeer::CloseConnection( const SystemAddress target, bool sendDisconnectionNotification, unsigned char orderingChannel, PacketPriority disconnectionNotificationPriority ) +{ + // This only be called from the user thread, for the user shutting down. + // From the network thread, this should occur because of ID_DISCONNECTION_NOTIFICATION and ID_CONNECTION_LOST + unsigned j; + for (j=0; j < messageHandlerList.Size(); j++) + { + messageHandlerList[j]->OnClosedConnection(target, GetGuidFromSystemAddress(target), LCR_CLOSED_BY_USER); + } + + CloseConnectionInternal(target, sendDisconnectionNotification, false, orderingChannel, disconnectionNotificationPriority); + + // 12/14/09 Return ID_CONNECTION_LOST when calling CloseConnection with sendDisconnectionNotification==false, elsewise it is never returned + if (sendDisconnectionNotification==false && IsConnected(target,false,false)) + { + Packet *packet=AllocPacket(sizeof( char ), __FILE__, __LINE__); + packet->data[ 0 ] = ID_CONNECTION_LOST; // DeadConnection + packet->guid = GetGuidFromSystemAddress(target); + packet->systemAddress = target; + packet->systemAddress.systemIndex = (SystemIndex) GetIndexFromSystemAddress(target,false); + packet->guid.systemIndex=packet->systemAddress.systemIndex; + AddPacketToProducer(packet); + } +} + +// -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +// Cancel a pending connection attempt +// If we are already connected, the connection stays open +// \param[in] target Which system to cancel +// -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +void RakPeer::CancelConnectionAttempt( const SystemAddress target ) +{ + unsigned int i; + + // Cancel pending connection attempt, if there is one + i=0; + requestedConnectionQueueMutex.Lock(); + while (i < requestedConnectionQueue.Size()) + { + if (requestedConnectionQueue[i]->systemAddress==target) + { + RakNet::OP_DELETE(requestedConnectionQueue[i], __FILE__, __LINE__ ); + requestedConnectionQueue.RemoveAtIndex(i); + break; + } + else + i++; + } + requestedConnectionQueueMutex.Unlock(); + +} +// -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +bool RakPeer::IsConnectionAttemptPending( const SystemAddress systemAddress ) +{ + unsigned int i=0; + requestedConnectionQueueMutex.Lock(); + for (; i < requestedConnectionQueue.Size(); i++) + { + if (requestedConnectionQueue[i]->systemAddress==systemAddress) + { + requestedConnectionQueueMutex.Unlock(); + return true; + } + } + requestedConnectionQueueMutex.Unlock(); + + int index = GetIndexFromSystemAddress(systemAddress, false); + return index!=-1 && remoteSystemList[index].isActive && + (((remoteSystemList[index].connectMode==RemoteSystemStruct::REQUESTED_CONNECTION || + remoteSystemList[index].connectMode==RemoteSystemStruct::HANDLING_CONNECTION_REQUEST || + remoteSystemList[index].connectMode==RemoteSystemStruct::UNVERIFIED_SENDER || + remoteSystemList[index].connectMode==RemoteSystemStruct::SET_ENCRYPTION_ON_MULTIPLE_16_BYTE_PACKET)) + ) + ; +} + +// -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +// Returns if a particular systemAddress is connected to us +// \param[in] systemAddress The SystemAddress we are referring to +// \return True if this system is connected and active, false otherwise. +// -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +bool RakPeer::IsConnected( const AddressOrGUID systemIdentifier, bool includeInProgress, bool includeDisconnecting ) +{ + if (includeInProgress && systemIdentifier.systemAddress!=UNASSIGNED_SYSTEM_ADDRESS) + { + unsigned int i=0; + requestedConnectionQueueMutex.Lock(); + for (; i < requestedConnectionQueue.Size(); i++) + { + if (requestedConnectionQueue[i]->systemAddress==systemIdentifier.systemAddress) + { + requestedConnectionQueueMutex.Unlock(); + return true; + } + } + requestedConnectionQueueMutex.Unlock(); + } + + + int index; + if (systemIdentifier.systemAddress!=UNASSIGNED_SYSTEM_ADDRESS) + { + if (IsLoopbackAddress(systemIdentifier.systemAddress,true)) + return true; + index = GetIndexFromSystemAddress(systemIdentifier.systemAddress, false); + return index!=-1 && remoteSystemList[index].isActive && + (((includeInProgress && (remoteSystemList[index].connectMode==RemoteSystemStruct::REQUESTED_CONNECTION || + remoteSystemList[index].connectMode==RemoteSystemStruct::HANDLING_CONNECTION_REQUEST || + remoteSystemList[index].connectMode==RemoteSystemStruct::UNVERIFIED_SENDER || + remoteSystemList[index].connectMode==RemoteSystemStruct::SET_ENCRYPTION_ON_MULTIPLE_16_BYTE_PACKET)) + || + (includeDisconnecting && ( + remoteSystemList[index].connectMode==RemoteSystemStruct::DISCONNECT_ASAP || + remoteSystemList[index].connectMode==RemoteSystemStruct::DISCONNECT_ASAP_SILENTLY || + remoteSystemList[index].connectMode==RemoteSystemStruct::DISCONNECT_ON_NO_ACK)) + || + remoteSystemList[index].connectMode==RemoteSystemStruct::CONNECTED)) + ; + } + else + { + index = GetIndexFromGuid(systemIdentifier.rakNetGuid); + return index!=-1 && remoteSystemList[index].isActive && + (((includeInProgress && (remoteSystemList[index].connectMode==RemoteSystemStruct::REQUESTED_CONNECTION || + remoteSystemList[index].connectMode==RemoteSystemStruct::HANDLING_CONNECTION_REQUEST || + remoteSystemList[index].connectMode==RemoteSystemStruct::UNVERIFIED_SENDER || + remoteSystemList[index].connectMode==RemoteSystemStruct::SET_ENCRYPTION_ON_MULTIPLE_16_BYTE_PACKET)) + || + (includeDisconnecting && ( + remoteSystemList[index].connectMode==RemoteSystemStruct::DISCONNECT_ASAP || + remoteSystemList[index].connectMode==RemoteSystemStruct::DISCONNECT_ASAP_SILENTLY || + remoteSystemList[index].connectMode==RemoteSystemStruct::DISCONNECT_ON_NO_ACK)) + || + remoteSystemList[index].connectMode==RemoteSystemStruct::CONNECTED)) + ; + } +} + +// -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +// Description: +// Given a systemAddress, returns an index from 0 to the maximum number of players allowed - 1. +// +// Parameters +// systemAddress - The systemAddress to search for +// +// Returns +// An integer from 0 to the maximum number of peers -1, or -1 if that player is not found +// -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +int RakPeer::GetIndexFromSystemAddress( const SystemAddress systemAddress ) const +{ + return GetIndexFromSystemAddress(systemAddress, false); +} + +// -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +// Description: +// This function is only useful for looping through all players. +// +// Parameters +// index - an integer between 0 and the maximum number of players allowed - 1. +// +// Returns +// A valid systemAddress or UNASSIGNED_SYSTEM_ADDRESS if no such player at that index +// -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +SystemAddress RakPeer::GetSystemAddressFromIndex( int index ) +{ + // remoteSystemList in user thread + //if ( index >= 0 && index < remoteSystemListSize ) + if ( index >= 0 && index < maximumNumberOfPeers ) + if (remoteSystemList[index].isActive && remoteSystemList[ index ].connectMode==RakPeer::RemoteSystemStruct::CONNECTED) // Don't give the user players that aren't fully connected, since sends will fail + return remoteSystemList[ index ].systemAddress; + + return UNASSIGNED_SYSTEM_ADDRESS; +} + +// -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +// Same as GetSystemAddressFromIndex but returns RakNetGUID +// \param[in] index Index should range between 0 and the maximum number of players allowed - 1. +// \return The RakNetGUID +// -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +RakNetGUID RakPeer::GetGUIDFromIndex( int index ) +{ + // remoteSystemList in user thread + //if ( index >= 0 && index < remoteSystemListSize ) + if ( index >= 0 && index < maximumNumberOfPeers ) + if (remoteSystemList[index].isActive && remoteSystemList[ index ].connectMode==RakPeer::RemoteSystemStruct::CONNECTED) // Don't give the user players that aren't fully connected, since sends will fail + return remoteSystemList[ index ].guid; + + return UNASSIGNED_RAKNET_GUID; +} + +// -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +// Same as calling GetSystemAddressFromIndex and GetGUIDFromIndex for all systems, but more efficient +// Indices match each other, so \a addresses[0] and \a guids[0] refer to the same system +// \param[out] addresses All system addresses. Size of the list is the number of connections. Size of the list will match the size of the \a guids list. +// \param[out] guids All guids. Size of the list is the number of connections. Size of the list will match the size of the \a addresses list. +// -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +void RakPeer::GetSystemList(DataStructures::List &addresses, DataStructures::List &guids) +{ + addresses.Clear(false, __FILE__, __LINE__); + guids.Clear(false, __FILE__, __LINE__); + int index; + for (index=0; index < maximumNumberOfPeers; index++) + { + // Don't give the user players that aren't fully connected, since sends will fail + if (remoteSystemList[index].isActive && remoteSystemList[ index ].connectMode==RakPeer::RemoteSystemStruct::CONNECTED) + { + addresses.Push(remoteSystemList[index].systemAddress, __FILE__, __LINE__ ); + guids.Push(remoteSystemList[index].guid, __FILE__, __LINE__ ); + } + } +} + +// -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +// Description: +// Bans an IP from connecting. Banned IPs persist between connections. +// +// Parameters +// IP - Dotted IP address. Can use * as a wildcard, such as 128.0.0.* will ban +// All IP addresses starting with 128.0.0 +// milliseconds - how many ms for a temporary ban. Use 0 for a permanent ban +// -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +void RakPeer::AddToBanList( const char *IP, RakNetTime milliseconds ) +{ + unsigned index; + RakNetTime time = RakNet::GetTime(); + + if ( IP == 0 || IP[ 0 ] == 0 || strlen( IP ) > 15 ) + return ; + + // If this guy is already in the ban list, do nothing + index = 0; + + banListMutex.Lock(); + + for ( ; index < banList.Size(); index++ ) + { + if ( strcmp( IP, banList[ index ]->IP ) == 0 ) + { + // Already in the ban list. Just update the time + if (milliseconds==0) + banList[ index ]->timeout=0; // Infinite + else + banList[ index ]->timeout=time+milliseconds; + banListMutex.Unlock(); + return; + } + } + + banListMutex.Unlock(); + + BanStruct *banStruct = RakNet::OP_NEW( __FILE__, __LINE__ ); + banStruct->IP = (char*) rakMalloc_Ex( 16, __FILE__, __LINE__ ); + if (milliseconds==0) + banStruct->timeout=0; // Infinite + else + banStruct->timeout=time+milliseconds; + strcpy( banStruct->IP, IP ); + banListMutex.Lock(); + banList.Insert( banStruct, __FILE__, __LINE__ ); + banListMutex.Unlock(); +} + +// -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +// Description: +// Allows a previously banned IP to connect. +// +// Parameters +// IP - Dotted IP address. Can use * as a wildcard, such as 128.0.0.* will ban +// All IP addresses starting with 128.0.0 +// -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +void RakPeer::RemoveFromBanList( const char *IP ) +{ + unsigned index; + BanStruct *temp; + + if ( IP == 0 || IP[ 0 ] == 0 || strlen( IP ) > 15 ) + return ; + + index = 0; + temp=0; + + banListMutex.Lock(); + + for ( ; index < banList.Size(); index++ ) + { + if ( strcmp( IP, banList[ index ]->IP ) == 0 ) + { + temp = banList[ index ]; + banList[ index ] = banList[ banList.Size() - 1 ]; + banList.RemoveAtIndex( banList.Size() - 1 ); + break; + } + } + + banListMutex.Unlock(); + + if (temp) + { + rakFree_Ex(temp->IP, __FILE__, __LINE__ ); + RakNet::OP_DELETE(temp, __FILE__, __LINE__); + } + +} + +// -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +// Description: +// Allows all previously banned IPs to connect. +// -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +void RakPeer::ClearBanList( void ) +{ + unsigned index; + index = 0; + banListMutex.Lock(); + + for ( ; index < banList.Size(); index++ ) + { + rakFree_Ex(banList[ index ]->IP, __FILE__, __LINE__ ); + RakNet::OP_DELETE(banList[ index ], __FILE__, __LINE__); + } + + banList.Clear(false, __FILE__, __LINE__); + + banListMutex.Unlock(); +} +// -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +void RakPeer::SetLimitIPConnectionFrequency(bool b) +{ + limitConnectionFrequencyFromTheSameIP=b; +} + +// -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +// Description: +// Determines if a particular IP is banned. +// +// Parameters +// IP - Complete dotted IP address +// +// Returns +// True if IP matches any IPs in the ban list, accounting for any wildcards. +// False otherwise. +// -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +bool RakPeer::IsBanned( const char *IP ) +{ + unsigned banListIndex, characterIndex; + RakNetTime time; + BanStruct *temp; + + if ( IP == 0 || IP[ 0 ] == 0 || strlen( IP ) > 15 ) + return false; + + banListIndex = 0; + + if ( banList.Size() == 0 ) + return false; // Skip the mutex if possible + + time = RakNet::GetTime(); + + banListMutex.Lock(); + + while ( banListIndex < banList.Size() ) + { + if (banList[ banListIndex ]->timeout>0 && banList[ banListIndex ]->timeoutIP, __FILE__, __LINE__ ); + RakNet::OP_DELETE(temp, __FILE__, __LINE__); + } + else + { + characterIndex = 0; + +#ifdef _MSC_VER +#pragma warning( disable : 4127 ) // warning C4127: conditional expression is constant +#endif + while ( true ) + { + if ( banList[ banListIndex ]->IP[ characterIndex ] == IP[ characterIndex ] ) + { + // Equal characters + + if ( IP[ characterIndex ] == 0 ) + { + banListMutex.Unlock(); + // End of the string and the strings match + + return true; + } + + characterIndex++; + } + + else + { + if ( banList[ banListIndex ]->IP[ characterIndex ] == 0 || IP[ characterIndex ] == 0 ) + { + // End of one of the strings + break; + } + + // Characters do not match + if ( banList[ banListIndex ]->IP[ characterIndex ] == '*' ) + { + banListMutex.Unlock(); + + // Domain is banned. + return true; + } + + // Characters do not match and it is not a * + break; + } + } + + banListIndex++; + } + } + + banListMutex.Unlock(); + + // No match found. + return false; +} + +// -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +// Description: +// Send a ping to the specified connected system. +// +// Parameters: +// target - who to ping +// -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +void RakPeer::Ping( const SystemAddress target ) +{ + PingInternal(target, false, UNRELIABLE); +} + +// -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +// Description: +// Send a ping to the specified unconnected system. +// The remote system, if it is Initialized, will respond with ID_PONG. +// The final ping time will be encoded in the following sizeof(RakNetTime) bytes. (Default is 4 bytes - See __GET_TIME_64BIT in RakNetTypes.h +// +// Parameters: +// host: Either a dotted IP address or a domain name. Can be 255.255.255.255 for LAN broadcast. +// remotePort: Which port to connect to on the remote machine. +// onlyReplyOnAcceptingConnections: Only request a reply if the remote system has open connections +// connectionSocketIndex Index into the array of socket descriptors passed to socketDescriptors in RakPeer::Startup() to send on. +// -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +bool RakPeer::Ping( const char* host, unsigned short remotePort, bool onlyReplyOnAcceptingConnections, unsigned connectionSocketIndex ) +{ + if ( host == 0 ) + return false; + + // If this assert hits then Startup wasn't called or the call failed. + RakAssert(connectionSocketIndex < socketList.Size()); + +// if ( IsActive() == false ) +// return; + + if ( NonNumericHostString( host ) ) + { + host = ( char* ) SocketLayer::Instance()->DomainNameToIP( host ); + if (host==0) + return false; + } + + SystemAddress systemAddress; + systemAddress.SetBinaryAddress(host); + systemAddress.port=remotePort; + + RakNet::BitStream bitStream( sizeof(unsigned char) + sizeof(RakNetTime) ); + if ( onlyReplyOnAcceptingConnections ) + bitStream.Write((MessageID)ID_PING_OPEN_CONNECTIONS); + else + bitStream.Write((MessageID)ID_PING); + + bitStream.Write(RakNet::GetTime()); + + bitStream.WriteAlignedBytes((const unsigned char*) OFFLINE_MESSAGE_DATA_ID, sizeof(OFFLINE_MESSAGE_DATA_ID)); + + unsigned i; + for (i=0; i < messageHandlerList.Size(); i++) + messageHandlerList[i]->OnDirectSocketSend((const char*)bitStream.GetData(), bitStream.GetNumberOfBitsUsed(), systemAddress); + // No timestamp for 255.255.255.255 + unsigned int realIndex = GetRakNetSocketFromUserConnectionSocketIndex(connectionSocketIndex); + SocketLayer::Instance()->SendTo( socketList[realIndex]->s, (const char*)bitStream.GetData(), (int) bitStream.GetNumberOfBytesUsed(), ( char* ) host, remotePort, socketList[realIndex]->remotePortRakNetWasStartedOn_PS3 ); + + return true; +} + +// -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +// Description: +// Returns the average of all ping times read for a specified target +// +// Parameters: +// target - whose time to read +// -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +int RakPeer::GetAveragePing( const AddressOrGUID systemIdentifier ) +{ + int sum, quantity; + RemoteSystemStruct *remoteSystem = GetRemoteSystem( systemIdentifier, false, false ); + + if ( remoteSystem == 0 ) + return -1; + + for ( sum = 0, quantity = 0; quantity < PING_TIMES_ARRAY_SIZE; quantity++ ) + { + if ( remoteSystem->pingAndClockDifferential[ quantity ].pingTime == 65535 ) + break; + else + sum += remoteSystem->pingAndClockDifferential[ quantity ].pingTime; + } + + if ( quantity > 0 ) + return sum / quantity; + else + return -1; +} + +// -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +// Description: +// Returns the last ping time read for the specific player or -1 if none read yet +// +// Parameters: +// target - whose time to read +// -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +int RakPeer::GetLastPing( const AddressOrGUID systemIdentifier ) const +{ + RemoteSystemStruct * remoteSystem = GetRemoteSystem( systemIdentifier, false, false ); + + if ( remoteSystem == 0 ) + return -1; + +// return (int)(remoteSystem->reliabilityLayer.GetAckPing()/(RakNetTimeUS)1000); + + if ( remoteSystem->pingAndClockDifferentialWriteIndex == 0 ) + return remoteSystem->pingAndClockDifferential[ PING_TIMES_ARRAY_SIZE - 1 ].pingTime; + else + return remoteSystem->pingAndClockDifferential[ remoteSystem->pingAndClockDifferentialWriteIndex - 1 ].pingTime; +} + +// -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +// Description: +// Returns the lowest ping time read or -1 if none read yet +// +// Parameters: +// target - whose time to read +// -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +int RakPeer::GetLowestPing( const AddressOrGUID systemIdentifier ) const +{ + RemoteSystemStruct * remoteSystem = GetRemoteSystem( systemIdentifier, false, false ); + + if ( remoteSystem == 0 ) + return -1; + + return remoteSystem->lowestPing; +} + +// -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +// Description: +// Ping the remote systems every so often. This is off by default +// This will work anytime +// +// Parameters: +// doPing - True to start occasional pings. False to stop them. +// -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +void RakPeer::SetOccasionalPing( bool doPing ) +{ + occasionalPing = doPing; +} +// -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +// Description: +// Length should be under 400 bytes, as a security measure against flood attacks +// Sets the data to send with an (LAN server discovery) /(offline ping) response +// See the Ping sample project for how this is used. +// data: a block of data to store, or 0 for none +// length: The length of data in bytes, or 0 for none +// -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +void RakPeer::SetOfflinePingResponse( const char *data, const unsigned int length ) +{ + RakAssert(length < 400); + + rakPeerMutexes[ offlinePingResponse_Mutex ].Lock(); + offlinePingResponse.Reset(); + + if ( data && length > 0 ) + offlinePingResponse.Write( data, length ); + + rakPeerMutexes[ offlinePingResponse_Mutex ].Unlock(); +} + +// -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +// Returns pointers to a copy of the data passed to SetOfflinePingResponse +// \param[out] data A pointer to a copy of the data passed to \a SetOfflinePingResponse() +// \param[out] length A pointer filled in with the length parameter passed to SetOfflinePingResponse() +// \sa SetOfflinePingResponse +// -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +void RakPeer::GetOfflinePingResponse( char **data, unsigned int *length ) +{ + rakPeerMutexes[ offlinePingResponse_Mutex ].Lock(); + *data = (char*) offlinePingResponse.GetData(); + *length = (int) offlinePingResponse.GetNumberOfBytesUsed(); + rakPeerMutexes[ offlinePingResponse_Mutex ].Unlock(); +} + +// -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +// Description: +// Return the unique SystemAddress that represents you on the the network +// Note that unlike in previous versions, this is a struct and is not sequential +// -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +SystemAddress RakPeer::GetInternalID( const SystemAddress systemAddress, const int index ) const +{ + if (systemAddress==UNASSIGNED_SYSTEM_ADDRESS) + { + return mySystemAddress[index]; + } + else + { +#if !defined(_XBOX) && !defined(X360) +// SystemAddress returnValue; + RemoteSystemStruct * remoteSystem = GetRemoteSystemFromSystemAddress( systemAddress, false, true ); + if (remoteSystem==0) + return UNASSIGNED_SYSTEM_ADDRESS; + + return remoteSystem->theirInternalSystemAddress[index]; + /* + sockaddr_in sa; + socklen_t len = sizeof(sa); + if (getsockname(connectionSockets[remoteSystem->connectionSocketIndex], (sockaddr*)&sa, &len)!=0) + return UNASSIGNED_SYSTEM_ADDRESS; + returnValue.port=ntohs(sa.sin_port); + returnValue.binaryAddress=sa.sin_addr.s_addr; + return returnValue; +*/ +#else + return UNASSIGNED_SYSTEM_ADDRESS; +#endif + } +} + +// -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +// Description: +// Return the unique address identifier that represents you on the the network and is based on your external +// IP / port (the IP / port the specified player uses to communicate with you) +// Note that unlike in previous versions, this is a struct and is not sequential +// +// Parameters: +// target: Which remote system you are referring to for your external ID +// -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +SystemAddress RakPeer::GetExternalID( const SystemAddress target ) const +{ + unsigned i; + SystemAddress inactiveExternalId; + + inactiveExternalId=UNASSIGNED_SYSTEM_ADDRESS; + + if (target==UNASSIGNED_SYSTEM_ADDRESS) + return firstExternalID; + + // First check for active connection with this systemAddress + for ( i = 0; i < maximumNumberOfPeers; i++ ) + { + if (remoteSystemList[ i ].systemAddress == target ) + { + if ( remoteSystemList[ i ].isActive ) + return remoteSystemList[ i ].myExternalSystemAddress; + else if (remoteSystemList[ i ].myExternalSystemAddress!=UNASSIGNED_SYSTEM_ADDRESS) + inactiveExternalId=remoteSystemList[ i ].myExternalSystemAddress; + } + } + + return inactiveExternalId; +} + +// -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + +const RakNetGUID& RakPeer::GetGuidFromSystemAddress( const SystemAddress input ) const +{ + if (input==UNASSIGNED_SYSTEM_ADDRESS) + return myGuid; + + if (input.systemIndex!=(SystemIndex)-1 && input.systemIndexreliabilityLayer.SetTimeoutTime(timeMS); + } +} + +// -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + +RakNetTime RakPeer::GetTimeoutTime( const SystemAddress target ) +{ + if (target==UNASSIGNED_SYSTEM_ADDRESS) + { + return defaultTimeoutTime; + } + else + { + RemoteSystemStruct * remoteSystem = GetRemoteSystemFromSystemAddress( target, false, true ); + + if ( remoteSystem != 0 ) + remoteSystem->reliabilityLayer.GetTimeoutTime(); + } + return defaultTimeoutTime; +} + +// -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +// Description: +// Change the MTU size in order to improve performance when sending large packets +// This can only be called when not connected. +// A too high of value will cause packets not to arrive at worst and be fragmented at best. +// A too low of value will split packets unnecessarily. +// +// Parameters: +// size: Set according to the following table: +// 1500. The largest Ethernet packet size +// This is the typical setting for non-PPPoE, non-VPN connections. The default value for NETGEAR routers, adapters and switches. +// 1492. The size PPPoE prefers. +// 1472. Maximum size to use for pinging. (Bigger packets are fragmented.) +// 1468. The size DHCP prefers. +// 1460. Usable by AOL if you don't have large email attachments, etc. +// 1430. The size VPN and PPTP prefer. +// 1400. Maximum size for AOL DSL. +// 576. Typical value to connect to dial-up ISPs. (Default) +// +// Returns: +// False on failure (we are connected). True on success. Maximum allowed size is MAXIMUM_MTU_SIZE +// -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +/* +bool RakPeer::SetMTUSize( int size, const SystemAddress target ) +{ + if ( IsActive() ) + return false; + + if ( size < 512 ) + size = 512; + else if ( size > MAXIMUM_MTU_SIZE ) + size = MAXIMUM_MTU_SIZE; + + if (target==UNASSIGNED_SYSTEM_ADDRESS) + { + defaultMTUSize = size; + + int i; + // Active connections take priority. But if there are no active connections, return the first systemAddress match found + for ( i = 0; i < maximumNumberOfPeers; i++ ) + { + remoteSystemList[i].MTUSize=size; + } + } + else + { + RemoteSystemStruct *rss=GetRemoteSystemFromSystemAddress(target, false, true); + if (rss) + rss->MTUSize=size; + } + + return true; + } +*/ + +// -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +// Description: +// Returns the current MTU size +// +// Returns: +// The MTU sized specified in SetMTUSize +// -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +int RakPeer::GetMTUSize( const SystemAddress target ) const +{ + if (target!=UNASSIGNED_SYSTEM_ADDRESS) + { + RemoteSystemStruct *rss=GetRemoteSystemFromSystemAddress(target, false, true); + if (rss) + return rss->MTUSize; + } + return defaultMTUSize; +} +// -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +// Description: +// Returns the number of IP addresses we have +// -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +unsigned int RakPeer::GetNumberOfAddresses( void ) +{ +#if !defined(_XBOX) && !defined(X360) + int i = 0; + + while ( ipList[ i ][ 0 ] ) + i++; + + return i; +#else + RakAssert(0); + return 0; +#endif +} + +// -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +// Returns an IP address at index 0 to GetNumberOfAddresses-1 +// \param[in] index index into the list of IP addresses +// \return The local IP address at this index +// -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +const char* RakPeer::GetLocalIP( unsigned int index ) +{ + if (IsActive()==false) + { + // Fill out ipList structure +#if !defined(_XBOX) && !defined(X360) + memset( ipList, 0, sizeof( char ) * 16 * MAXIMUM_NUMBER_OF_INTERNAL_IDS ); + SocketLayer::Instance()->GetMyIP( ipList,binaryAddresses ); +#endif + } + +#if !defined(_XBOX) && !defined(X360) + return ipList[ index ]; +#else + RakAssert(0); + return 0; +#endif +} + +// -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +// Is this a local IP? +// \param[in] An IP address to check +// \return True if this is one of the IP addresses returned by GetLocalIP +// -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +bool RakPeer::IsLocalIP( const char *ip ) +{ + if (ip==0 || ip[0]==0) + return false; + +#if !defined(_XBOX) && !defined(X360) + if (strcmp(ip, "127.0.0.1")==0) + return true; + + int num = GetNumberOfAddresses(); + int i; + for (i=0; i < num; i++) + { + if (strcmp(ip, GetLocalIP(i))==0) + return true; + } +#else + if (strcmp(ip, "2130706433")==0) // 127.0.0.1 big endian + return true; +#endif + return false; +} + +// -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +// Description: +// Allow or disallow connection responses from any IP. Normally this should be false, but may be necessary +// when connection to servers with multiple IP addresses +// +// Parameters: +// allow - True to allow this behavior, false to not allow. Defaults to false. Value persists between connections +// -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +void RakPeer::AllowConnectionResponseIPMigration( bool allow ) +{ + allowConnectionResponseIPMigration = allow; +} + +// -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +// Description: +// Sends a message ID_ADVERTISE_SYSTEM to the remote unconnected system. +// This will tell the remote system our external IP outside the LAN, and can be used for NAT punch through +// +// Requires: +// The sender and recipient must already be started via a successful call to Initialize +// +// host: Either a dotted IP address or a domain name +// remotePort: Which port to connect to on the remote machine. +// -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +bool RakPeer::AdvertiseSystem( const char *host, unsigned short remotePort, const char *data, int dataLength, unsigned connectionSocketIndex ) +{ + return SendOutOfBand(host, remotePort, ID_ADVERTISE_SYSTEM, data, dataLength, connectionSocketIndex ); +} + +// -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +// Controls how often to return ID_DOWNLOAD_PROGRESS for large message downloads. +// ID_DOWNLOAD_PROGRESS is returned to indicate a new partial message chunk, roughly the MTU size, has arrived +// As it can be slow or cumbersome to get this notification for every chunk, you can set the interval at which it is returned. +// Defaults to 0 (never return this notification) +// -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +void RakPeer::SetSplitMessageProgressInterval(int interval) +{ + RakAssert(interval>=0); + splitMessageProgressInterval=interval; + for ( unsigned short i = 0; i < maximumNumberOfPeers; i++ ) + remoteSystemList[ i ].reliabilityLayer.SetSplitMessageProgressInterval(splitMessageProgressInterval); +} + +// -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +// Returns what was passed to SetSplitMessageProgressInterval() +// -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +int RakPeer::GetSplitMessageProgressInterval(void) const +{ + return splitMessageProgressInterval; +} + +// -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +// Set how long to wait before giving up on sending an unreliable message +// Useful if the network is clogged up. +// Set to 0 or less to never timeout. Defaults to 0. +// timeoutMS How many ms to wait before simply not sending an unreliable message. +// -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +void RakPeer::SetUnreliableTimeout(RakNetTime timeoutMS) +{ + unreliableTimeout=timeoutMS; + for ( unsigned short i = 0; i < maximumNumberOfPeers; i++ ) + remoteSystemList[ i ].reliabilityLayer.SetUnreliableTimeout(unreliableTimeout); +} + +// -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +// Send a message to host, with the IP socket option TTL set to 3 +// This message will not reach the host, but will open the router. +// Used for NAT-Punchthrough +// -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +void RakPeer::SendTTL( const char* host, unsigned short remotePort, int ttl, unsigned connectionSocketIndex ) +{ + char fakeData[2]; + fakeData[0]=0; + fakeData[1]=1; + unsigned int realIndex = GetRakNetSocketFromUserConnectionSocketIndex(connectionSocketIndex); + SocketLayer::Instance()->SendToTTL( socketList[realIndex]->s, (char*)fakeData, 2, (char*) host, remotePort, ttl ); +} + +// -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +// Description: +// Enables or disables our tracking of bytes input to and output from the network. +// This is required to get a frequency table, which is used to generate a new compression layer. +// You can call this at any time - however you SHOULD only call it when disconnected. Otherwise you will only track +// part of the values sent over the network. +// This value persists between connect calls and defaults to false (no frequency tracking) +// +// Parameters: +// doCompile - true to track bytes. Defaults to false +// -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +void RakPeer::SetCompileFrequencyTable( bool doCompile ) +{ + trackFrequencyTable = doCompile; +} + +// -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +// Description: +// Returns the frequency of outgoing bytes into outputFrequencyTable +// The purpose is to save to file as either a master frequency table from a sample game session for passing to +// GenerateCompressionLayer(false) +// You should only call this when disconnected. +// Requires that you first enable data frequency tracking by calling SetCompileFrequencyTable(true) +// +// Parameters: +// outputFrequencyTable (out): The frequency of each corresponding byte +// +// Returns: +// Ffalse (failure) if connected or if frequency table tracking is not enabled. Otherwise true (success) +// -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +bool RakPeer::GetOutgoingFrequencyTable( unsigned int outputFrequencyTable[ 256 ] ) +{ + if ( IsActive() ) + return false; + + if ( trackFrequencyTable == false ) + return false; + + memcpy( outputFrequencyTable, frequencyTable, sizeof( unsigned int ) * 256 ); + + return true; +} + +// -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +// Description: +// Generates the compression layer from the input frequency table. +// You should call this twice - once with inputLayer as true and once as false. +// The frequency table passed here with inputLayer=true should match the frequency table on the recipient with inputLayer=false. +// Likewise, the frequency table passed here with inputLayer=false should match the frequency table on the recipient with inputLayer=true +// Calling this function when there is an existing layer will overwrite the old layer +// You should only call this when disconnected +// +// Parameters: +// inputFrequencyTable: The frequency table returned from GetSendFrequencyTable(...) +// inputLayer - Whether inputFrequencyTable represents incoming data from other systems (true) or outgoing data from this system (false) +// +// Returns: +// False on failure (we are connected). True otherwise +// -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +bool RakPeer::GenerateCompressionLayer( unsigned int inputFrequencyTable[ 256 ], bool inputLayer ) +{ + if ( IsActive() ) + return false; + + DeleteCompressionLayer( inputLayer ); + + if ( inputLayer ) + { + inputTree = RakNet::OP_NEW( __FILE__, __LINE__ ); + inputTree->GenerateFromFrequencyTable( inputFrequencyTable ); + } + + else + { + outputTree = RakNet::OP_NEW( __FILE__, __LINE__ ); + outputTree->GenerateFromFrequencyTable( inputFrequencyTable ); + } + + return true; +} + +// -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +// Description: +// Deletes the output or input layer as specified. This is not necessary to call and is only valuable for freeing memory +// You should only call this when disconnected +// +// Parameters: +// inputLayer - Specifies the corresponding compression layer generated by GenerateCompressionLayer. +// +// Returns: +// False on failure (we are connected). True otherwise +// -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +bool RakPeer::DeleteCompressionLayer( bool inputLayer ) +{ + if ( IsActive() ) + return false; + + if ( inputLayer ) + { + if ( inputTree ) + { + RakNet::OP_DELETE(inputTree, __FILE__, __LINE__); + inputTree = 0; + } + } + + else + { + if ( outputTree ) + { + RakNet::OP_DELETE(outputTree, __FILE__, __LINE__); + outputTree = 0; + } + } + + return true; +} + +// -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +// Returns: +// The compression ratio. A low compression ratio is good. Compression is for outgoing data +// -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +float RakPeer::GetCompressionRatio( void ) const +{ + if ( rawBytesSent > 0 ) + { + return ( float ) compressedBytesSent / ( float ) rawBytesSent; + } + + else + return 0.0f; +} + +// -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +// Returns: +// The decompression ratio. A high decompression ratio is good. Decompression is for incoming data +// -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +float RakPeer::GetDecompressionRatio( void ) const +{ + if ( rawBytesReceived > 0 ) + { + return ( float ) compressedBytesReceived / ( float ) rawBytesReceived; + } + + else + return 0.0f; +} + +// -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +// Attatches a Plugin interface to run code automatically on message receipt in the Receive call +// +// \param messageHandler Pointer to a plugin to attach +// -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +void RakPeer::AttachPlugin( PluginInterface2 *plugin ) +{ + if (messageHandlerList.GetIndexOf(plugin)==MAX_UNSIGNED_LONG) + { + plugin->SetRakPeerInterface(this); + plugin->OnAttach(); + messageHandlerList.Insert(plugin, __FILE__, __LINE__); + } +} + +// -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +// Detaches a Plugin interface to run code automatically on message receipt +// +// \param messageHandler Pointer to a plugin to detach +// -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +void RakPeer::DetachPlugin( PluginInterface2 *plugin ) +{ + if (plugin==0) + return; + + unsigned int index; + index = messageHandlerList.GetIndexOf(plugin); + if (index!=MAX_UNSIGNED_LONG) + { + // Unordered list so delete from end for speed + messageHandlerList[index]=messageHandlerList[messageHandlerList.Size()-1]; + messageHandlerList.RemoveFromEnd(); + plugin->OnDetach(); + plugin->SetRakPeerInterface(0); + } +} + +// -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +// Put a packet back at the end of the receive queue in case you don't want to deal with it immediately +// +// packet The packet you want to push back. +// pushAtHead True to push the packet so that the next receive call returns it. False to push it at the end of the queue (obviously pushing it at the end makes the packets out of order) +// -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +void RakPeer::PushBackPacket( Packet *packet, bool pushAtHead) +{ + if (packet==0) + return; + + unsigned i; + for (i=0; i < messageHandlerList.Size(); i++) + messageHandlerList[i]->OnPushBackPacket((const char*) packet->data, packet->bitSize, packet->systemAddress); + + packetReturnMutex.Lock(); + if (pushAtHead) + packetReturnQueue.PushAtHead(packet,0,__FILE__,__LINE__); + else + packetReturnQueue.Push(packet,__FILE__,__LINE__); + packetReturnMutex.Unlock(); +} + +// -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +void RakPeer::SetRouterInterface( RouterInterface *routerInterface ) +{ + router=routerInterface; +} +// -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +void RakPeer::RemoveRouterInterface( RouterInterface *routerInterface ) +{ + if (router==routerInterface) + router=0; +} +// -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +void RakPeer::ChangeSystemAddress(RakNetGUID guid, SystemAddress systemAddress) +{ + BufferedCommandStruct *bcs; + +#ifdef _RAKNET_THREADSAFE + bcs=bufferedCommands.Allocate( __FILE__, __LINE__ ); +#else + bcs=bufferedCommands.WriteLock(); +#endif + bcs->data = 0; + bcs->systemIdentifier.systemAddress=systemAddress; + bcs->systemIdentifier.rakNetGuid=guid; + bcs->command=BufferedCommandStruct::BCS_CHANGE_SYSTEM_ADDRESS; +#ifdef _RAKNET_THREADSAFE + bufferedCommands.Push(bcs); +#else + bufferedCommands.WriteUnlock(); +#endif +} +// -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +Packet* RakPeer::AllocatePacket(unsigned dataSize) +{ + return AllocPacket(dataSize, __FILE__, __LINE__); +} +// -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +RakNetSmartPtr RakPeer::GetSocket( const SystemAddress target ) +{ + // Send a query to the thread to get the socket, and return when we got it + BufferedCommandStruct *bcs; +#ifdef _RAKNET_THREADSAFE + bcs=bufferedCommands.Allocate( __FILE__, __LINE__ ); + bcs->command=BufferedCommandStruct::BCS_GET_SOCKET; + bcs->systemIdentifier=target; + bcs->data=0; + bufferedCommands.Push(bcs); +#else + bcs=bufferedCommands.WriteLock(); + bcs->command=BufferedCommandStruct::BCS_GET_SOCKET; + bcs->systemIdentifier=target; + bcs->data=0; + bufferedCommands.WriteUnlock(); +#endif + + // Block up to one second to get the socket, although it should actually take virtually no time + SocketQueryOutput *sqo; + RakNetTime stopWaiting = RakNet::GetTime()+1000; + DataStructures::List > output; + while (RakNet::GetTime() < stopWaiting) + { + if (isMainLoopThreadActive==false) + return RakNetSmartPtr(); + + RakSleep(0); + +#ifdef _RAKNET_THREADSAFE + sqo = socketQueryOutput.Pop(); + if (sqo) + { + output=sqo->sockets; + sqo->sockets.Clear(false, __FILE__, __LINE__); + socketQueryOutput.Deallocate(sqo, __FILE__,__LINE__); + if (output.Size()) + return output[0]; + break; + } +#else + sqo = socketQueryOutput.ReadLock(); + if (sqo) + { + output=sqo->sockets; + sqo->sockets.Clear(false, __FILE__, __LINE__); + socketQueryOutput.ReadUnlock(); + if (output.Size()) + return output[0]; + break; + } +#endif + } + return RakNetSmartPtr(); +} +// -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +void RakPeer::GetSockets( DataStructures::List > &sockets ) +{ + sockets.Clear(false, __FILE__, __LINE__); + + // Send a query to the thread to get the socket, and return when we got it + BufferedCommandStruct *bcs; + +#ifdef _RAKNET_THREADSAFE + bcs=bufferedCommands.Allocate( __FILE__, __LINE__ ); + bcs->command=BufferedCommandStruct::BCS_GET_SOCKET; + bcs->systemIdentifier=UNASSIGNED_SYSTEM_ADDRESS; + bcs->data=0; + bufferedCommands.Push(bcs); +#else + bcs=bufferedCommands.WriteLock(); + bcs->command=BufferedCommandStruct::BCS_GET_SOCKET; + bcs->systemIdentifier=UNASSIGNED_SYSTEM_ADDRESS; + bcs->data=0; + bufferedCommands.WriteUnlock(); +#endif + + // Block up to one second to get the socket, although it should actually take virtually no time + SocketQueryOutput *sqo; + RakNetTime stopWaiting = RakNet::GetTime()+1000; + RakNetSmartPtr output; + while (RakNet::GetTime() < stopWaiting) + { + if (isMainLoopThreadActive==false) + return; + + RakSleep(0); + +#ifdef _RAKNET_THREADSAFE + sqo = socketQueryOutput.Pop(); + if (sqo) + { + sockets=sqo->sockets; + sqo->sockets.Clear(false, __FILE__, __LINE__); + socketQueryOutput.Deallocate(sqo, __FILE__,__LINE__); + return; + } +#else + sqo = socketQueryOutput.ReadLock(); + if (sqo) + { + sockets=sqo->sockets; + sqo->sockets.Clear(false, __FILE__, __LINE__); + socketQueryOutput.ReadUnlock(); + return; + } +#endif + } + return; +} +// -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +// Adds simulated ping and packet loss to the outgoing data flow. +// To simulate bi-directional ping and packet loss, you should call this on both the sender and the recipient, with half the total ping and maxSendBPS value on each. +// -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +void RakPeer::ApplyNetworkSimulator( float packetloss, unsigned short minExtraPing, unsigned short extraPingVariance) +{ +#ifdef _DEBUG + if (remoteSystemList) + { + unsigned short i; + for (i=0; i < maximumNumberOfPeers; i++) + //for (i=0; i < remoteSystemListSize; i++) + remoteSystemList[i].reliabilityLayer.ApplyNetworkSimulator(packetloss, minExtraPing, extraPingVariance); + } + + _packetloss=packetloss; + _minExtraPing=minExtraPing; + _extraPingVariance=extraPingVariance; +#endif +} + +// -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + +void RakPeer::SetPerConnectionOutgoingBandwidthLimit( unsigned maxBitsPerSecond ) +{ + maxOutgoingBPS=maxBitsPerSecond; +} + +// -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +// Returns if you previously called ApplyNetworkSimulator +// -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +bool RakPeer::IsNetworkSimulatorActive( void ) +{ +#ifdef _DEBUG + return _packetloss>0 || _minExtraPing>0 || _extraPingVariance>0; +#else + return false; +#endif +} +/* +// -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +// Have RakNet use a socket you created yourself +// The socket should not be in use - it is up to you to either shutdown or close the connections using it. Otherwise existing connections on that socket will eventually disconnect +// This socket will be forgotten after calling Shutdown(), so rebind again if you need to. +// -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +void RakPeer::UseUserSocket( int socket, bool haveRakNetCloseSocket, unsigned connectionSocketIndex) +{ + BufferedCommandStruct *bcs; +#ifdef _RAKNET_THREADSAFE + rakPeerMutexes[bufferedCommands_Mutex].Lock(); +#endif + bcs=bufferedCommands.WriteLock(); + bcs->command=BufferedCommandStruct::BCS_USE_USER_SOCKET; + bcs->data=0; + bcs->socket=socket; + bcs->haveRakNetCloseSocket=haveRakNetCloseSocket; + bcs->connectionSocketIndex=connectionSocketIndex; + bufferedCommands.WriteUnlock(); +#ifdef _RAKNET_THREADSAFE + rakPeerMutexes[bufferedCommands_Mutex].Unlock(); +#endif +} + +// -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +// Have RakNet recreate a socket using a different port. +// The socket should not be in use - it is up to you to either shutdown or close the connections using it. Otherwise existing connections on that socket will eventually disconnect +// \param[in] connectionSocketIndex Index into the array of socket descriptors passed to socketDescriptors in RakPeer::Startup() to send on. +// -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +void RakPeer::RebindSocketAddress(unsigned connectionSocketIndex, SocketDescriptor &sd) +{ + BufferedCommandStruct *bcs; +#ifdef _RAKNET_THREADSAFE + rakPeerMutexes[bufferedCommands_Mutex].Lock(); +#endif + bcs=bufferedCommands.WriteLock(); + bcs->command=BufferedCommandStruct::BCS_REBIND_SOCKET_ADDRESS; + bcs->data=(char*) rakMalloc_Ex(sizeof(sd.hostAddress), __FILE__, __LINE__); + memcpy(bcs->data, sd.hostAddress, sizeof(sd.hostAddress)); + bcs->port=sd.port; + bcs->connectionSocketIndex=connectionSocketIndex; + bcs->socketType=sd.socketType; + bufferedCommands.WriteUnlock(); +#ifdef _RAKNET_THREADSAFE + rakPeerMutexes[bufferedCommands_Mutex].Unlock(); +#endif +} +*/ + + +// -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +// For internal use +// -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +char *RakPeer::GetRPCString( const char *data, const BitSize_t bitSize, const SystemAddress systemAddress) +{ + bool nameIsEncoded=false; + static char uniqueIdentifier[256]; + RPCIndex rpcIndex; + RPCMap *_rpcMap; + RakNet::BitStream rpcDecode((unsigned char*) data, BITS_TO_BYTES(bitSize), false); + rpcDecode.IgnoreBits(8); + if (data[0]==ID_TIMESTAMP) + rpcDecode.IgnoreBits(sizeof(unsigned char)+sizeof(RakNetTime)); + rpcDecode.Read(nameIsEncoded); + if (nameIsEncoded) + { + stringCompressor->DecodeString((char*)uniqueIdentifier, 256, &rpcDecode); + } + else + { + rpcDecode.ReadCompressed( rpcIndex ); + RPCNode *rpcNode; + + if (systemAddress==UNASSIGNED_SYSTEM_ADDRESS) + _rpcMap=&rpcMap; + else + { + RemoteSystemStruct *rss=GetRemoteSystemFromSystemAddress(systemAddress, false, true); + if (rss) + _rpcMap=&(rss->rpcMap); + else + _rpcMap=0; + } + + if (_rpcMap) + rpcNode = _rpcMap->GetNodeFromIndex(rpcIndex); + else + rpcNode=0; + + if (_rpcMap && rpcNode) + strcpy((char*)uniqueIdentifier, rpcNode->uniqueIdentifier); + else + strcpy((char*)uniqueIdentifier, "[UNKNOWN]"); + } + + return uniqueIdentifier; +} + +// -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +void RakPeer::WriteOutOfBandHeader(RakNet::BitStream *bitStream, MessageID header) +{ + bitStream->Write((MessageID)ID_OUT_OF_BAND_INTERNAL); + bitStream->Write(header); + bitStream->Write(myGuid); + bitStream->WriteAlignedBytes((const unsigned char*) OFFLINE_MESSAGE_DATA_ID, sizeof(OFFLINE_MESSAGE_DATA_ID)); +} +// -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +void RakPeer::SetUserUpdateThread(void (*_userUpdateThreadPtr)(RakPeerInterface *, void *), void *_userUpdateThreadData) +{ + userUpdateThreadPtr=_userUpdateThreadPtr; + userUpdateThreadData=_userUpdateThreadData; +} +// -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +bool RakPeer::SendOutOfBand(const char *host, unsigned short remotePort, MessageID header, const char *data, BitSize_t dataLength, unsigned connectionSocketIndex ) +{ + if ( IsActive() == false ) + return false; + + if (host==0 || host[0]==0) + return false; + + // If this assert hits then Startup wasn't called or the call failed. + RakAssert(connectionSocketIndex < socketList.Size()); + + // This is a security measure. Don't send data longer than this value + RakAssert(dataLength <= MAX_OFFLINE_DATA_LENGTH); + + if ( NonNumericHostString( host ) ) + { + host = ( char* ) SocketLayer::Instance()->DomainNameToIP( host ); + + if (host==0) + return false; + } + + if (host==0) + return false; + + SystemAddress systemAddress; + systemAddress.SetBinaryAddress(host); + systemAddress.port=remotePort; + + // 34 bytes + RakNet::BitStream bitStream; + WriteOutOfBandHeader(&bitStream, header); + + if (dataLength>0) + { + bitStream.Write(data, dataLength); + } +// unsigned int dataLengthTest = bitStream.GetNumberOfBytesUsed()-sizeof(OFFLINE_MESSAGE_DATA_ID)-RakNetGUID::size()-sizeof(MessageID)*2; +// RakAssert(dataLengthTest==dataLength); + + unsigned i; + for (i=0; i < messageHandlerList.Size(); i++) + messageHandlerList[i]->OnDirectSocketSend((const char*)bitStream.GetData(), bitStream.GetNumberOfBitsUsed(), systemAddress); + unsigned int realIndex = GetRakNetSocketFromUserConnectionSocketIndex(connectionSocketIndex); + SocketLayer::Instance()->SendTo( socketList[realIndex]->s, (const char*)bitStream.GetData(), (int) bitStream.GetNumberOfBytesUsed(), ( char* ) host, remotePort, socketList[realIndex]->remotePortRakNetWasStartedOn_PS3 ); + + // removeme + //bitStream.PrintHex(); + //printf("\n"); + + + return true; +} + +// -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +RakNetStatistics * const RakPeer::GetStatistics( const SystemAddress systemAddress, RakNetStatistics *rns ) +{ + static RakNetStatistics staticStatistics; + RakNetStatistics *systemStats; + if (rns==0) + systemStats=&staticStatistics; + else + systemStats=rns; + + if (systemAddress==UNASSIGNED_SYSTEM_ADDRESS) + { + bool firstWrite=false; + // Return a crude sum + for ( unsigned short i = 0; i < maximumNumberOfPeers; i++ ) + { + if (remoteSystemList[ i ].isActive) + { + RakNetStatistics rnsTemp; + remoteSystemList[ i ].reliabilityLayer.GetStatistics(&rnsTemp); + + if (firstWrite==false) + { + memcpy(systemStats, &rnsTemp, sizeof(RakNetStatistics)); + firstWrite=true; + } + else + (*systemStats)+=rnsTemp; + } + } + return systemStats; + } + else + { + RemoteSystemStruct * rss; + rss = GetRemoteSystemFromSystemAddress( systemAddress, false, false ); + if ( rss && endThreads==false ) + { + rss->reliabilityLayer.GetStatistics(systemStats); + return systemStats; + } + } + + return 0; +} + +// -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +bool RakPeer::GetStatistics( const int index, RakNetStatistics *rns ) +{ + if (index < maximumNumberOfPeers && remoteSystemList[ index ].isActive) + { + remoteSystemList[ index ].reliabilityLayer.GetStatistics(rns); + return true; + } + return false; +} +// -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +unsigned int RakPeer::GetReceiveBufferSize(void) +{ + unsigned int size; + packetReturnMutex.Lock(); + size=packetReturnQueue.Size(); + packetReturnMutex.Unlock(); + return size; +} +// -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +int RakPeer::GetIndexFromSystemAddress( const SystemAddress systemAddress, bool calledFromNetworkThread ) const +{ + unsigned i; + + if ( systemAddress == UNASSIGNED_SYSTEM_ADDRESS ) + return -1; + + if (systemAddress.systemIndex!=(SystemIndex)-1 && systemAddress.systemIndex < maximumNumberOfPeers && remoteSystemList[systemAddress.systemIndex].systemAddress==systemAddress && remoteSystemList[ systemAddress.systemIndex ].isActive) + return systemAddress.systemIndex; + + if (calledFromNetworkThread) + { + return GetRemoteSystemIndex(systemAddress); + } + else + { + // remoteSystemList in user and network thread + for ( i = 0; i < maximumNumberOfPeers; i++ ) + if ( remoteSystemList[ i ].isActive && remoteSystemList[ i ].systemAddress == systemAddress ) + return i; + + // If no active results found, try previously active results. + for ( i = 0; i < maximumNumberOfPeers; i++ ) + if ( remoteSystemList[ i ].systemAddress == systemAddress ) + return i; + } + + return -1; +} +// -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +int RakPeer::GetIndexFromGuid( const RakNetGUID guid ) +{ + unsigned i; + + if ( guid == UNASSIGNED_RAKNET_GUID ) + return -1; + + if (guid.systemIndex!=(SystemIndex)-1 && guid.systemIndex < maximumNumberOfPeers && remoteSystemList[guid.systemIndex].guid==guid && remoteSystemList[ guid.systemIndex ].isActive) + return guid.systemIndex; + + // remoteSystemList in user and network thread + for ( i = 0; i < maximumNumberOfPeers; i++ ) + if ( remoteSystemList[ i ].isActive && remoteSystemList[ i ].guid == guid ) + return i; + + // If no active results found, try previously active results. + for ( i = 0; i < maximumNumberOfPeers; i++ ) + if ( remoteSystemList[ i ].guid == guid ) + return i; + + return -1; +} +// -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +bool RakPeer::SendConnectionRequest( const char* host, unsigned short remotePort, const char *passwordData, int passwordDataLength, unsigned connectionSocketIndex, unsigned int extraData, unsigned sendConnectionAttemptCount, unsigned timeBetweenSendConnectionAttemptsMS, RakNetTime timeoutTime ) +{ + RakAssert(passwordDataLength <= 256); + RakAssert(remotePort!=0); + SystemAddress systemAddress; + systemAddress.SetBinaryAddress(host); + systemAddress.port=remotePort; + + // Already connected? + if (GetRemoteSystemFromSystemAddress(systemAddress, false, true)) + return false; + + //RequestedConnectionStruct *rcs = (RequestedConnectionStruct *) rakMalloc_Ex(sizeof(RequestedConnectionStruct), __FILE__, __LINE__); + RequestedConnectionStruct *rcs = RakNet::OP_NEW(__FILE__,__LINE__); + + rcs->systemAddress=systemAddress; + rcs->nextRequestTime=RakNet::GetTime(); + rcs->requestsMade=0; + rcs->data=0; + rcs->extraData=extraData; + rcs->socketIndex=connectionSocketIndex; + rcs->actionToTake=RequestedConnectionStruct::CONNECT; + rcs->sendConnectionAttemptCount=sendConnectionAttemptCount; + rcs->timeBetweenSendConnectionAttemptsMS=timeBetweenSendConnectionAttemptsMS; + memcpy(rcs->outgoingPassword, passwordData, passwordDataLength); + rcs->outgoingPasswordLength=(unsigned char) passwordDataLength; + rcs->timeoutTime=timeoutTime; + + // Return false if already pending, else push on queue + unsigned int i=0; + requestedConnectionQueueMutex.Lock(); + for (; i < requestedConnectionQueue.Size(); i++) + { + if (requestedConnectionQueue[i]->systemAddress==systemAddress) + { + requestedConnectionQueueMutex.Unlock(); + RakNet::OP_DELETE(rcs,__FILE__,__LINE__); + return false; + } + } + requestedConnectionQueue.Push(rcs, __FILE__, __LINE__ ); + requestedConnectionQueueMutex.Unlock(); + + return true; +} +bool RakPeer::SendConnectionRequest( const char* host, unsigned short remotePort, const char *passwordData, int passwordDataLength, unsigned connectionSocketIndex, unsigned int extraData, unsigned sendConnectionAttemptCount, unsigned timeBetweenSendConnectionAttemptsMS, RakNetTime timeoutTime, RakNetSmartPtr socket ) +{ + RakAssert(passwordDataLength <= 256); + SystemAddress systemAddress; + systemAddress.SetBinaryAddress(host); + systemAddress.port=remotePort; + + // Already connected? + if (GetRemoteSystemFromSystemAddress(systemAddress, false, true)) + return false; + + //RequestedConnectionStruct *rcs = (RequestedConnectionStruct *) rakMalloc_Ex(sizeof(RequestedConnectionStruct), __FILE__, __LINE__); + RequestedConnectionStruct *rcs = RakNet::OP_NEW(__FILE__,__LINE__); + + rcs->systemAddress=systemAddress; + rcs->nextRequestTime=RakNet::GetTime(); + rcs->requestsMade=0; + rcs->data=0; + rcs->extraData=extraData; + rcs->socketIndex=connectionSocketIndex; + rcs->actionToTake=RequestedConnectionStruct::CONNECT; + rcs->sendConnectionAttemptCount=sendConnectionAttemptCount; + rcs->timeBetweenSendConnectionAttemptsMS=timeBetweenSendConnectionAttemptsMS; + memcpy(rcs->outgoingPassword, passwordData, passwordDataLength); + rcs->outgoingPasswordLength=(unsigned char) passwordDataLength; + rcs->timeoutTime=timeoutTime; + rcs->socket=socket; + + // Return false if already pending, else push on queue + unsigned int i=0; + requestedConnectionQueueMutex.Lock(); + for (; i < requestedConnectionQueue.Size(); i++) + { + if (requestedConnectionQueue[i]->systemAddress==systemAddress) + { + requestedConnectionQueueMutex.Unlock(); + RakNet::OP_DELETE(rcs,__FILE__,__LINE__); + return false; + } + } + requestedConnectionQueue.Push(rcs, __FILE__, __LINE__ ); + requestedConnectionQueueMutex.Unlock(); + + return true; +} +// -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +void RakPeer::ValidateRemoteSystemLookup(void) const +{ +} +// -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +RakPeer::RemoteSystemStruct *RakPeer::GetRemoteSystem( const AddressOrGUID systemIdentifier, bool calledFromNetworkThread, bool onlyActive ) const +{ + if (systemIdentifier.rakNetGuid!=UNASSIGNED_RAKNET_GUID) + return GetRemoteSystemFromGUID(systemIdentifier.rakNetGuid, onlyActive); + else + return GetRemoteSystemFromSystemAddress(systemIdentifier.systemAddress, calledFromNetworkThread, onlyActive); +} +// -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +RakPeer::RemoteSystemStruct *RakPeer::GetRemoteSystemFromSystemAddress( const SystemAddress systemAddress, bool calledFromNetworkThread, bool onlyActive ) const +{ + unsigned i; + + if ( systemAddress == UNASSIGNED_SYSTEM_ADDRESS ) + return 0; + + if (calledFromNetworkThread) + { + unsigned int index = GetRemoteSystemIndex(systemAddress); + if (index!=(unsigned int) -1) + { + if (onlyActive==false || remoteSystemList[ index ].isActive==true ) + { + RakAssert(remoteSystemList[index].systemAddress==systemAddress); + return remoteSystemList + index; + } + } + } + else + { + int deadConnectionIndex=-1; + + // Active connections take priority. But if there are no active connections, return the first systemAddress match found + for ( i = 0; i < maximumNumberOfPeers; i++ ) + { + if (remoteSystemList[ i ].systemAddress == systemAddress) + { + if ( remoteSystemList[ i ].isActive ) + return remoteSystemList + i; + else if (deadConnectionIndex==-1) + deadConnectionIndex=i; + } + } + + if (deadConnectionIndex!=-1 && onlyActive==false) + return remoteSystemList + deadConnectionIndex; + } + + return 0; +} +// -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +RakPeer::RemoteSystemStruct *RakPeer::GetRemoteSystemFromGUID( const RakNetGUID guid, bool onlyActive ) const +{ + if (guid==UNASSIGNED_RAKNET_GUID) + return 0; + + unsigned i; + for ( i = 0; i < maximumNumberOfPeers; i++ ) + { + if (remoteSystemList[ i ].guid == guid && (onlyActive==false || remoteSystemList[ i ].isActive)) + { + return remoteSystemList + i; + } + } + return 0; +} +// -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +void RakPeer::ParseConnectionRequestPacket( RakPeer::RemoteSystemStruct *remoteSystem, SystemAddress systemAddress, const char *data, int byteSize ) +{ + RakNet::BitStream bs((unsigned char*) data,byteSize,false); + bs.IgnoreBytes(sizeof(MessageID)); + bs.IgnoreBytes(sizeof(OFFLINE_MESSAGE_DATA_ID)); + RakNetGUID guid; + bs.Read(guid); + + // If we are full tell the sender. + // Not needed + if ( 0 ) //!AllowIncomingConnections() ) + { + RakNet::BitStream bs; + bs.Write((MessageID)ID_NO_FREE_INCOMING_CONNECTIONS); + bs.WriteAlignedBytes((const unsigned char*) OFFLINE_MESSAGE_DATA_ID, sizeof(OFFLINE_MESSAGE_DATA_ID)); + bs.Write(GetGuidFromSystemAddress(UNASSIGNED_SYSTEM_ADDRESS)); + SendImmediate((char*) bs.GetData(), bs.GetNumberOfBitsUsed(), IMMEDIATE_PRIORITY, RELIABLE, 0, systemAddress, false, false, RakNet::GetTimeNS(), 0); + remoteSystem->connectMode=RemoteSystemStruct::DISCONNECT_ASAP_SILENTLY; + } + else + { + const char *password = data + sizeof(MessageID) + RakNetGUID::size() + sizeof(OFFLINE_MESSAGE_DATA_ID); + int passwordLength = byteSize - (int) (sizeof(MessageID) + RakNetGUID::size() + sizeof(OFFLINE_MESSAGE_DATA_ID)); + + if ( incomingPasswordLength == passwordLength && + memcmp( password, incomingPassword, incomingPasswordLength ) == 0 ) + { + remoteSystem->connectMode=RemoteSystemStruct::HANDLING_CONNECTION_REQUEST; + +#if !defined(_XBOX) && !defined(X360) + char str1[64]; + systemAddress.ToString(false, str1); + if ( usingSecurity == false || + IsInSecurityExceptionList(str1)) +#endif + { +#ifdef _TEST_AES + unsigned char AESKey[ 16 ]; + // Save the AES key + for ( i = 0; i < 16; i++ ) + AESKey[ i ] = i; + + OnConnectionRequest( remoteSystem, AESKey, true ); +#else + // Connect this player assuming we have open slots + OnConnectionRequest( remoteSystem, 0, false ); +#endif + } +#if !defined(_XBOX) && !defined(X360) + else + SecuredConnectionResponse( systemAddress ); +#endif + } + else + { + // This one we only send once since we don't care if it arrives. + RakNet::BitStream bitStream; + bitStream.Write((MessageID)ID_INVALID_PASSWORD); + bitStream.Write(GetGuidFromSystemAddress(UNASSIGNED_SYSTEM_ADDRESS)); + SendImmediate((char*) bitStream.GetData(), bitStream.GetNumberOfBytesUsed(), IMMEDIATE_PRIORITY, RELIABLE, 0, systemAddress, false, false, RakNet::GetTimeNS(), 0); + remoteSystem->connectMode=RemoteSystemStruct::DISCONNECT_ASAP_SILENTLY; + } + } +} +// -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +void RakPeer::OnConnectionRequest( RakPeer::RemoteSystemStruct *remoteSystem, unsigned char *AESKey, bool setAESKey ) +{ + // Already handled by caller + //if ( AllowIncomingConnections() ) + { + SendConnectionRequestAccepted(remoteSystem); + + // Don't set secure connections immediately because we need the ack from the remote system to know ID_CONNECTION_REQUEST_ACCEPTED + // As soon as a 16 byte packet arrives, we will turn on AES. This works because all encrypted packets are multiples of 16 and the + // packets I happen to be sending are less than 16 bytes + remoteSystem->setAESKey=setAESKey; + if ( setAESKey ) + { + memcpy(remoteSystem->AESKey, AESKey, 16); + remoteSystem->connectMode=RemoteSystemStruct::SET_ENCRYPTION_ON_MULTIPLE_16_BYTE_PACKET; + } + } + /* + else + { + unsigned char c = ID_NO_FREE_INCOMING_CONNECTIONS; + //SocketLayer::Instance()->SendTo( connectionSocket, ( char* ) & c, sizeof( char ), systemAddress.binaryAddress, systemAddress.port ); + + SendImmediate((char*)&c, sizeof(c)*8, IMMEDIATE_PRIORITY, RELIABLE, 0, remoteSystem->systemAddress, false, false, RakNet::GetTimeNS()); + remoteSystem->connectMode=RemoteSystemStruct::DISCONNECT_ASAP_SILENTLY; + } + */ +} +// -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +void RakPeer::SendConnectionRequestAccepted(RakPeer::RemoteSystemStruct *remoteSystem) +{ + RakNet::BitStream bitStream((const unsigned int)(sizeof(unsigned char)+sizeof(unsigned short)+sizeof(unsigned int)+sizeof(unsigned short)+sizeof(SystemIndex)+SystemAddress::size())); + bitStream.Write((MessageID)ID_CONNECTION_REQUEST_ACCEPTED); + bitStream.Write(remoteSystem->systemAddress); + SystemIndex systemIndex = (SystemIndex) GetIndexFromSystemAddress( remoteSystem->systemAddress, true ); + RakAssert(systemIndex!=65535); + bitStream.Write(systemIndex); + for (unsigned int i=0; i < MAXIMUM_NUMBER_OF_INTERNAL_IDS; i++) + bitStream.Write(mySystemAddress[i]); + + // Removeme +// static int count=1; +// printf("Send ID_CONNECTION_REQUEST_ACCEPTED count=%i\n", count++); + + + SendImmediate((char*)bitStream.GetData(), bitStream.GetNumberOfBitsUsed(), IMMEDIATE_PRIORITY, RELIABLE_ORDERED, 0, remoteSystem->systemAddress, false, false, RakNet::GetTimeNS(), 0); +} + +// -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +void RakPeer::NotifyAndFlagForShutdown( const SystemAddress systemAddress, bool performImmediate, unsigned char orderingChannel, PacketPriority disconnectionNotificationPriority ) +{ + RakNet::BitStream temp( sizeof(unsigned char) ); + temp.Write( (MessageID)ID_DISCONNECTION_NOTIFICATION ); + if (performImmediate) + { + SendImmediate((char*)temp.GetData(), temp.GetNumberOfBitsUsed(), disconnectionNotificationPriority, RELIABLE_ORDERED, orderingChannel, systemAddress, false, false, RakNet::GetTimeNS(), 0); + RemoteSystemStruct *rss=GetRemoteSystemFromSystemAddress(systemAddress, true, true); + rss->connectMode=RemoteSystemStruct::DISCONNECT_ASAP; + } + else + { + SendBuffered((const char*)temp.GetData(), temp.GetNumberOfBitsUsed(), disconnectionNotificationPriority, RELIABLE_ORDERED, orderingChannel, systemAddress, false, RemoteSystemStruct::DISCONNECT_ASAP, 0); + } +} +// -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +unsigned short RakPeer::GetNumberOfRemoteInitiatedConnections( void ) const +{ + unsigned short i, numberOfIncomingConnections; + + if ( remoteSystemList == 0 || endThreads == true ) + return 0; + + numberOfIncomingConnections = 0; + + // remoteSystemList in network thread + for ( i = 0; i < maximumNumberOfPeers; i++ ) + //for ( i = 0; i < remoteSystemListSize; i++ ) + { + if ( remoteSystemList[ i ].isActive && remoteSystemList[ i ].weInitiatedTheConnection == false && remoteSystemList[i].connectMode==RemoteSystemStruct::CONNECTED) + numberOfIncomingConnections++; + } + + return numberOfIncomingConnections; +} + +// -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +RakPeer::RemoteSystemStruct * RakPeer::AssignSystemAddressToRemoteSystemList( const SystemAddress systemAddress, RemoteSystemStruct::ConnectMode connectionMode, RakNetSmartPtr incomingRakNetSocket, bool *thisIPConnectedRecently, SystemAddress bindingAddress, int incomingMTU, RakNetGUID guid, unsigned short rcvPort ) +{ + RemoteSystemStruct * remoteSystem; + unsigned i,j,assignedIndex; + RakNetTime time = RakNet::GetTime(); +#ifdef _DEBUG + RakAssert(systemAddress!=UNASSIGNED_SYSTEM_ADDRESS); +#endif + + if (limitConnectionFrequencyFromTheSameIP) + { + if (IsLoopbackAddress(systemAddress,false)==false) + { + for ( i = 0; i < maximumNumberOfPeers; i++ ) + { + if ( remoteSystemList[ i ].isActive==true && + remoteSystemList[ i ].systemAddress.binaryAddress==systemAddress.binaryAddress && + time >= remoteSystemList[ i ].connectionTime && + time - remoteSystemList[ i ].connectionTime < 100 + ) + { + // 4/13/09 Attackers can flood ID_OPEN_CONNECTION_REQUEST and use up all available connection slots + // Ignore connection attempts if this IP address connected within the last 100 milliseconds + *thisIPConnectedRecently=true; + ValidateRemoteSystemLookup(); + return 0; + } + } + } + } + + // Don't use a different port than what we received on + bindingAddress.port=incomingRakNetSocket->boundAddress.port; + + *thisIPConnectedRecently=false; + for ( assignedIndex = 0; assignedIndex < maximumNumberOfPeers; assignedIndex++ ) + { + if ( remoteSystemList[ assignedIndex ].isActive==false ) + { + remoteSystem=remoteSystemList+assignedIndex; + remoteSystem->rpcMap.Clear(); + ReferenceRemoteSystem(systemAddress, assignedIndex); + remoteSystem->MTUSize=defaultMTUSize; + remoteSystem->guid=guid; + remoteSystem->isActive = true; // This one line causes future incoming packets to go through the reliability layer + // Reserve this reliability layer for ourselves. + if (incomingMTU > remoteSystem->MTUSize) + remoteSystem->MTUSize=incomingMTU; + remoteSystem->reliabilityLayer.Reset(true, remoteSystem->MTUSize); + remoteSystem->reliabilityLayer.SetSplitMessageProgressInterval(splitMessageProgressInterval); + remoteSystem->reliabilityLayer.SetUnreliableTimeout(unreliableTimeout); + remoteSystem->reliabilityLayer.SetTimeoutTime(defaultTimeoutTime); + remoteSystem->reliabilityLayer.SetEncryptionKey( 0 ); + remoteSystem->rcvPort = rcvPort; + if (incomingRakNetSocket->boundAddress==bindingAddress) + { + remoteSystem->rakNetSocket=incomingRakNetSocket; + } + else + { + char str[256]; + bindingAddress.ToString(true,str); + // See if this is an internal IP address. + // If so, force binding on it so we reply on the same IP address as they sent to. + unsigned int ipListIndex, foundIndex=(unsigned int)-1; + + for (ipListIndex=0; ipListIndex < MAXIMUM_NUMBER_OF_INTERNAL_IDS; ipListIndex++) + { + if (ipList[ipListIndex][0]==0) + break; + if (bindingAddress.binaryAddress==binaryAddresses[ipListIndex]) + { + foundIndex=ipListIndex; + break; + } + } + + // 06/26/09 Unconfirmed report that Vista firewall blocks the reply if we force a binding + // For now use the incoming socket only + // Originally this code was to force a machine with multiple IP addresses to reply back on the IP + // that the datagram came in on + if (1 || foundIndex==(unsigned int)-1) + { + // Must not be an internal LAN address. Just use whatever socket it came in on + remoteSystem->rakNetSocket=incomingRakNetSocket; + } + else + { + // Force binding + unsigned int socketListIndex; + for (socketListIndex=0; socketListIndex < socketList.Size(); socketListIndex++) + { + if (socketList[socketListIndex]->boundAddress==bindingAddress) + { + // Force binding with existing socket + remoteSystem->rakNetSocket=socketList[socketListIndex]; + break; + } + } + + if (socketListIndex==socketList.Size()) + { + // Force binding with new socket + RakNetSmartPtr rns(RakNet::OP_NEW(__FILE__,__LINE__)); + if (incomingRakNetSocket->remotePortRakNetWasStartedOn_PS3==0) + rns->s = (unsigned int) SocketLayer::Instance()->CreateBoundSocket( bindingAddress.port, true, ipList[foundIndex], 0 ); + else + rns->s = (unsigned int) SocketLayer::Instance()->CreateBoundSocket_PS3Lobby( bindingAddress.port, true, ipList[foundIndex] ); + if ((SOCKET)rns->s==(SOCKET)-1) + { + // Can't bind. Just use whatever socket it came in on + remoteSystem->rakNetSocket=incomingRakNetSocket; + } + else + { + rns->boundAddress=bindingAddress; + rns->remotePortRakNetWasStartedOn_PS3=incomingRakNetSocket->remotePortRakNetWasStartedOn_PS3; + rns->userConnectionSocketIndex=(unsigned int)-1; + socketList.Push(rns, __FILE__, __LINE__ ); + remoteSystem->rakNetSocket=rns; + + RakPeerAndIndex rpai; + rpai.remotePortRakNetWasStartedOn_PS3=rns->remotePortRakNetWasStartedOn_PS3; + rpai.s=rns->s; + rpai.rakPeer=this; +#ifdef _WIN32 + int highPriority=THREAD_PRIORITY_ABOVE_NORMAL; +#else + int highPriority=-10; +#endif + +//#if !defined(_XBOX) && !defined(X360) + highPriority=0; +//#endif + isRecvFromLoopThreadActive=false; + int errorCode = RakNet::RakThread::Create(RecvFromLoop, &rpai, highPriority); + RakAssert(errorCode!=0); + while ( isRecvFromLoopThreadActive == false ) + RakSleep(10); + + + /* +#if defined (_WIN32) && defined(USE_WAIT_FOR_MULTIPLE_EVENTS) + if (threadSleepTimer>0) + { + rns->recvEvent=CreateEvent(0,FALSE,FALSE,0); + WSAEventSelect(rns->s,rns->recvEvent,FD_READ); + } +#endif + */ + } + } + } + } + + for ( j = 0; j < (unsigned) PING_TIMES_ARRAY_SIZE; j++ ) + { + remoteSystem->pingAndClockDifferential[ j ].pingTime = 65535; + remoteSystem->pingAndClockDifferential[ j ].clockDifferential = 0; + } + + remoteSystem->connectMode=connectionMode; + remoteSystem->pingAndClockDifferentialWriteIndex = 0; + remoteSystem->lowestPing = 65535; + remoteSystem->nextPingTime = 0; // Ping immediately + remoteSystem->weInitiatedTheConnection = false; + remoteSystem->connectionTime = time; + remoteSystem->myExternalSystemAddress = UNASSIGNED_SYSTEM_ADDRESS; + remoteSystem->setAESKey=false; + remoteSystem->lastReliableSend=time; + +#ifdef _DEBUG + int indexLoopupCheck=GetIndexFromSystemAddress( systemAddress, true ); + if (indexLoopupCheck!=assignedIndex) + { + RakAssert(indexLoopupCheck==assignedIndex); + } +#endif + + return remoteSystem; + } + } + + return 0; +} + +// -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +// Adjust the first four bytes (treated as unsigned int) of the pointer +// -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +void RakPeer::ShiftIncomingTimestamp( unsigned char *data, SystemAddress systemAddress ) const +{ +#ifdef _DEBUG + RakAssert( IsActive() ); + RakAssert( data ); +#endif + + RakNet::BitStream timeBS( data, sizeof(RakNetTime), false); + RakNetTime encodedTimestamp; + timeBS.Read(encodedTimestamp); + + encodedTimestamp = encodedTimestamp - GetBestClockDifferential( systemAddress ); + timeBS.SetWriteOffset(0); + timeBS.Write(encodedTimestamp); +} + +// -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +// Thanks to Chris Taylor (cat02e@fsu.edu) for the improved timestamping algorithm +RakNetTime RakPeer::GetBestClockDifferential( const SystemAddress systemAddress ) const +{ + int counter, lowestPingSoFar; + RakNetTime clockDifferential; + RemoteSystemStruct *remoteSystem = GetRemoteSystemFromSystemAddress( systemAddress, true, true ); + + if ( remoteSystem == 0 ) + return 0; + + lowestPingSoFar = 65535; + + clockDifferential = 0; + + for ( counter = 0; counter < PING_TIMES_ARRAY_SIZE; counter++ ) + { + if ( remoteSystem->pingAndClockDifferential[ counter ].pingTime == 65535 ) + break; + + if ( remoteSystem->pingAndClockDifferential[ counter ].pingTime < lowestPingSoFar ) + { + clockDifferential = remoteSystem->pingAndClockDifferential[ counter ].clockDifferential; + lowestPingSoFar = remoteSystem->pingAndClockDifferential[ counter ].pingTime; + } + } + + return clockDifferential; +} +// -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +unsigned int RakPeer::RemoteSystemLookupHashIndex(SystemAddress sa) const +{ + unsigned int lastHash = SuperFastHashIncremental ((const char*) & sa.binaryAddress, 4, 4 ); + lastHash = SuperFastHashIncremental ((const char*) & sa.port, 2, lastHash ); + return lastHash % ((unsigned int) maximumNumberOfPeers * REMOTE_SYSTEM_LOOKUP_HASH_MULTIPLE); +} +// -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +void RakPeer::ReferenceRemoteSystem(SystemAddress sa, unsigned int remoteSystemListIndex) +{ +// #ifdef _DEBUG +// for ( int remoteSystemIndex = 0; remoteSystemIndex < maximumNumberOfPeers; ++remoteSystemIndex ) +// { +// if (remoteSystemList[remoteSystemIndex].isActive ) +// { +// unsigned int hashIndex = GetRemoteSystemIndex(remoteSystemList[remoteSystemIndex].systemAddress); +// RakAssert(hashIndex==remoteSystemIndex); +// } +// } +// #endif + + + SystemAddress oldAddress = remoteSystemList[remoteSystemListIndex].systemAddress; + if (oldAddress!=UNASSIGNED_SYSTEM_ADDRESS) + { + // The system might be active if rerouting +// RakAssert(remoteSystemList[remoteSystemListIndex].isActive==false); + + // Remove the reference if the reference is pointing to this inactive system + if (GetRemoteSystem(oldAddress)==&remoteSystemList[remoteSystemListIndex]) + DereferenceRemoteSystem(oldAddress); + } + DereferenceRemoteSystem(sa); + +// #ifdef _DEBUG +// for ( int remoteSystemIndex = 0; remoteSystemIndex < maximumNumberOfPeers; ++remoteSystemIndex ) +// { +// if (remoteSystemList[remoteSystemIndex].isActive ) +// { +// unsigned int hashIndex = GetRemoteSystemIndex(remoteSystemList[remoteSystemIndex].systemAddress); +// if (hashIndex!=remoteSystemIndex) +// { +// RakAssert(hashIndex==remoteSystemIndex); +// } +// } +// } +// #endif + + + remoteSystemList[remoteSystemListIndex].systemAddress=sa; + + unsigned int hashIndex = RemoteSystemLookupHashIndex(sa); + RemoteSystemIndex *rsi; + rsi = remoteSystemIndexPool.Allocate(__FILE__,__LINE__); + if (remoteSystemLookup[hashIndex]==0) + { + rsi->next=0; + rsi->index=remoteSystemListIndex; + remoteSystemLookup[hashIndex]=rsi; + } + else + { + RemoteSystemIndex *cur = remoteSystemLookup[hashIndex]; + while (cur->next!=0) + { + cur=cur->next; + } + + rsi = remoteSystemIndexPool.Allocate(__FILE__,__LINE__); + rsi->next=0; + rsi->index=remoteSystemListIndex; + cur->next=rsi; + } + +// #ifdef _DEBUG +// for ( int remoteSystemIndex = 0; remoteSystemIndex < maximumNumberOfPeers; ++remoteSystemIndex ) +// { +// if (remoteSystemList[remoteSystemIndex].isActive ) +// { +// unsigned int hashIndex = GetRemoteSystemIndex(remoteSystemList[remoteSystemIndex].systemAddress); +// RakAssert(hashIndex==remoteSystemIndex); +// } +// } +// #endif + + + RakAssert(GetRemoteSystemIndex(sa)==remoteSystemListIndex); +} +// -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +void RakPeer::DereferenceRemoteSystem(SystemAddress sa) +{ + unsigned int hashIndex = RemoteSystemLookupHashIndex(sa); + RemoteSystemIndex *cur = remoteSystemLookup[hashIndex]; + RemoteSystemIndex *last = 0; + while (cur!=0) + { + if (remoteSystemList[cur->index].systemAddress==sa) + { + if (last==0) + { + remoteSystemLookup[hashIndex]=cur->next; + } + else + { + last->next=cur->next; + } + remoteSystemIndexPool.Release(cur,__FILE__,__LINE__); + break; + } + last=cur; + cur=cur->next; + } +} +// -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +unsigned int RakPeer::GetRemoteSystemIndex(SystemAddress sa) const +{ + unsigned int hashIndex = RemoteSystemLookupHashIndex(sa); + RemoteSystemIndex *cur = remoteSystemLookup[hashIndex]; + while (cur!=0) + { + if (remoteSystemList[cur->index].systemAddress==sa) + return cur->index; + cur=cur->next; + } + return (unsigned int) -1; +} +// -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +RakPeer::RemoteSystemStruct* RakPeer::GetRemoteSystem(SystemAddress sa) const +{ + unsigned int remoteSystemIndex = GetRemoteSystemIndex(sa); + if (remoteSystemIndex==(unsigned int)-1) + return 0; + return remoteSystemList + remoteSystemIndex; +} +// -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +void RakPeer::ClearRemoteSystemLookup(void) +{ + remoteSystemIndexPool.Clear(__FILE__,__LINE__); + RakNet::OP_DELETE_ARRAY(remoteSystemLookup,__FILE__,__LINE__); + remoteSystemLookup=0; +} +// -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +/* +// -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +unsigned int RakPeer::LookupIndexUsingHashIndex(SystemAddress sa) const +{ + unsigned int scanCount=0; + unsigned int index = RemoteSystemLookupHashIndex(sa); + if (remoteSystemLookup[index].index==(unsigned int)-1) + return (unsigned int) -1; + while (remoteSystemList[remoteSystemLookup[index].index].systemAddress!=sa) + { + if (++index==(unsigned int) maximumNumberOfPeers*REMOTE_SYSTEM_LOOKUP_HASH_MULTIPLE) + index=0; + if (++scanCount>(unsigned int) maximumNumberOfPeers*REMOTE_SYSTEM_LOOKUP_HASH_MULTIPLE) + return (unsigned int) -1; + if (remoteSystemLookup[index].index==-1) + return (unsigned int) -1; + } + return index; +} +// -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +unsigned int RakPeer::RemoteSystemListIndexUsingHashIndex(SystemAddress sa) const +{ + unsigned int index = LookupIndexUsingHashIndex(sa); + if (index!=(unsigned int) -1) + { + return remoteSystemLookup[index].index; + } + return (unsigned int) -1; +} +// -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +unsigned int RakPeer::FirstFreeRemoteSystemLookupIndex(SystemAddress sa) const +{ +// unsigned int collisionCount=0; + unsigned int index = RemoteSystemLookupHashIndex(sa); + while (remoteSystemLookup[index].index!=(unsigned int)-1) + { + if (++index==(unsigned int) maximumNumberOfPeers*REMOTE_SYSTEM_LOOKUP_HASH_MULTIPLE) + index=0; +// collisionCount++; + } +// printf("%i collisions. Using index %i\n", collisionCount, index); + return index; +} +*/ +// -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +// Description: +// Handles an RPC packet. If you get a packet with the ID ID_RPC you should pass it to this function +// +// Parameters: +// packet - A packet returned from Receive with the ID ID_RPC +// +// Returns: +// true on success, false on a bad packet or an unregistered function +// -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +#ifdef _MSC_VER +#pragma warning( disable : 4701 ) // warning C4701: local variable may be used without having been initialized +#endif +char* RakPeer::HandleRPCPacket( const char *data, int length, SystemAddress systemAddress ) +{ + // RPC BitStream format is + // ID_RPC - unsigned char + // Unique identifier string length - unsigned char + // The unique ID - string with each letter in upper case, subtracted by 'A' and written in 5 bits. + // Number of bits of the data (int) + // The data + + RakNet::BitStream incomingBitStream( (unsigned char *) data, length, false ); + char uniqueIdentifier[ 256 ]; +// BitSize_t bitLength; + unsigned char *userData; + //bool hasTimestamp; + bool nameIsEncoded, networkIDIsEncoded; + RPCIndex rpcIndex; + RPCNode *node; + RPCParameters rpcParms; + NetworkID networkID; + bool blockingCommand; + RakNet::BitStream replyToSender; + rpcParms.replyToSender=&replyToSender; + + rpcParms.recipient=this; + rpcParms.sender=systemAddress; + + // Note to self - if I change this format then I have to change the PacketLogger class too + incomingBitStream.IgnoreBits(8); + if (data[0]==ID_TIMESTAMP) + { + incomingBitStream.Read(rpcParms.remoteTimestamp); + incomingBitStream.IgnoreBits(8); + } + else + rpcParms.remoteTimestamp=0; + if ( incomingBitStream.Read( nameIsEncoded ) == false ) + { +#ifdef _DEBUG + RakAssert( 0 ); // bitstream was not long enough. Some kind of internal error +#endif + return "Internal RPC error. Bitstream not long enough, could not see if RPC name was encoded.";; + } + + if (nameIsEncoded) + { + if ( stringCompressor->DecodeString(uniqueIdentifier, 256, &incomingBitStream) == false ) + { +#ifdef _DEBUG + RakAssert( 0 ); // bitstream was not long enough. Some kind of internal error +#endif + return "Internal RPC error. Could not decode unique RPC name identifier.";; + } + + rpcIndex = rpcMap.GetIndexFromFunctionName(uniqueIdentifier); + } + else + { + if ( incomingBitStream.ReadCompressed( rpcIndex ) == false ) + { +#ifdef _DEBUG + RakAssert( 0 ); // bitstream was not long enough. Some kind of internal error +#endif + return "Internal RPC error. Could not read RPC index value from bitstream."; + } + } + if ( incomingBitStream.Read( blockingCommand ) == false ) + { +#ifdef _DEBUG + RakAssert( 0 ); // bitstream was not long enough. Some kind of internal error +#endif + return "Internal RPC error. Bitstream not long enough, could not check blocking command status."; + } + + /* + if ( incomingBitStream.Read( rpcParms.hasTimestamp ) == false ) + { +#ifdef _DEBUG + RakAssert( 0 ); // bitstream was not long enough. Some kind of internal error +#endif + return false; + } + */ + + if ( incomingBitStream.ReadCompressed( rpcParms.numberOfBitsOfData ) == false ) + { +#ifdef _DEBUG + RakAssert( 0 ); // bitstream was not long enough. Some kind of internal error +#endif + return "Internal RPC error. Could not get RPC parameter length."; + } + + if ( incomingBitStream.Read( networkIDIsEncoded ) == false ) + { +#ifdef _DEBUG + RakAssert( 0 ); // bitstream was not long enough. Some kind of internal error +#endif + return "Internal RPC error. Bitstream not long enough, could not see if network ID was encoded."; + } + + if (networkIDIsEncoded) + { + if ( incomingBitStream.Read( networkID ) == false ) + { +#ifdef _DEBUG + RakAssert( 0 ); // bitstream was not long enough. Some kind of internal error +#endif + return "Internal RPC error. Could not read encoded network ID."; + } + } + + if (rpcIndex==UNDEFINED_RPC_INDEX) + { + // Unregistered function + RakAssert(0); + return "Internal RPC error. Invalid RPC index, function not registered."; + } + + node = rpcMap.GetNodeFromIndex(rpcIndex); + if (node==0) + { +#ifdef _DEBUG + RakAssert( 0 ); // Should never happen except perhaps from threading errors? No harm in checking anyway +#endif + return "Internal RPC error. Unable to retreive RPC node from map."; + } + + // Make sure the call type matches - if this is a pointer to a class member then networkID must be defined. Otherwise it must not be defined + if (node->isPointerToMember==true && networkIDIsEncoded==false) + { + // If this hits then this pointer was registered as a class member function but the packet does not have an NetworkID. + // Most likely this means this system registered a function with REGISTER_CLASS_MEMBER_RPC and the remote system called it + // using the unique ID for a function registered with REGISTER_STATIC_RPC. + RakAssert(0); + return "Internal RPC error. Using pointer to class member without network ID."; + } + + if (node->isPointerToMember==false && networkIDIsEncoded==true) + { + // If this hits then this pointer was not registered as a class member function but the packet does have an NetworkID. + // Most likely this means this system registered a function with REGISTER_STATIC_RPC and the remote system called it + // using the unique ID for a function registered with REGISTER_CLASS_MEMBER_RPC. + RakAssert(0); + return "Internal RPC error. Network ID used for static RPC function, function incorrectly called."; + } + + if (nameIsEncoded && GetRemoteSystemFromSystemAddress(systemAddress, false, true)) + { + // Send ID_RPC_MAPPING to the sender so they know what index to use next time + RakNet::BitStream rpcMapBitStream; + rpcMapBitStream.Write((MessageID)ID_RPC_MAPPING); + stringCompressor->EncodeString(node->uniqueIdentifier, 256, &rpcMapBitStream); + rpcMapBitStream.WriteCompressed(rpcIndex); + SendBuffered( (const char*)rpcMapBitStream.GetData(), rpcMapBitStream.GetNumberOfBitsUsed(), HIGH_PRIORITY, UNRELIABLE, 0, systemAddress, false, RemoteSystemStruct::NO_ACTION, 0 ); + } + + rpcParms.functionName=node->uniqueIdentifier; + + // Call the function + if ( rpcParms.numberOfBitsOfData == 0 ) + { + rpcParms.input=0; + if (networkIDIsEncoded) + { + // If this assert hits, you tried to use object member RPC but didn't call RakPeer::SetNetworkIDManager first as required. + RakAssert(networkIDManager); + if (networkIDManager) + { + void *object = networkIDManager->GET_OBJECT_FROM_ID(networkID); + if (object) + (node->memberFunctionPointer(object, &rpcParms)); + } + } + else + { + node->staticFunctionPointer( &rpcParms ); + } + } + else + { + if ( incomingBitStream.GetNumberOfUnreadBits() == 0 ) + { +#ifdef _DEBUG + RakAssert( 0 ); +#endif + "Internal RPC error. No data appended to call."; + } + + // We have to copy into a new data chunk because the user data might not be byte aligned. + bool usedAlloca=false; +#if !defined(_XBOX) && !defined(X360) + if (BITS_TO_BYTES( incomingBitStream.GetNumberOfUnreadBits() ) < MAX_ALLOCA_STACK_ALLOCATION) + { + userData = ( unsigned char* ) alloca( (size_t) BITS_TO_BYTES( incomingBitStream.GetNumberOfUnreadBits() ) ); + usedAlloca=true; + } + else +#endif + userData = (unsigned char*) rakMalloc_Ex((size_t) BITS_TO_BYTES(incomingBitStream.GetNumberOfUnreadBits()), __FILE__, __LINE__); + + + // The false means read out the internal representation of the bitstream data rather than + // aligning it as we normally would with user data. This is so the end user can cast the data received + // into a bitstream for reading + if ( incomingBitStream.ReadBits( ( unsigned char* ) userData, rpcParms.numberOfBitsOfData, false ) == false ) + { +#ifdef _DEBUG + RakAssert( 0 ); +#endif + #if defined(_XBOX) || defined(X360) + + #endif + + return "Internal RPC error. Not enough data to read."; + } + +// if ( rpcParms.hasTimestamp ) +// ShiftIncomingTimestamp( userData, systemAddress ); + + // Call the function callback + rpcParms.input=userData; + if (networkIDIsEncoded) + { + // If this assert hits, you tried to use object member RPC but didn't call RakPeer::SetNetworkIDManager first as required. + RakAssert(networkIDManager); + if (networkIDManager) + { + void *object = networkIDManager->GET_OBJECT_FROM_ID(networkID); + if (object) + (node->memberFunctionPointer(object, &rpcParms)); + } + } + else + { + node->staticFunctionPointer( &rpcParms ); + } + + + if (usedAlloca==false) + rakFree_Ex(userData, __FILE__, __LINE__ ); + } + + if (blockingCommand) + { + RakNet::BitStream reply; + reply.Write((MessageID)ID_RPC_REPLY); + reply.Write((char*)replyToSender.GetData(), replyToSender.GetNumberOfBytesUsed()); + Send(&reply, HIGH_PRIORITY, RELIABLE, 0, systemAddress, false); + } + + return NULL; +} + +// -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +/** +* Handles an RPC reply packet. This is data returned from an RPC call +* +* \param data A packet returned from Receive with the ID ID_RPC +* \param length The size of the packet data +* \param systemAddress The sender of the packet +* +* \return true on success, false on a bad packet or an unregistered function +*/ +// -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +void RakPeer::HandleRPCReplyPacket( const char *data, int length, SystemAddress systemAddress ) +{ + if (blockOnRPCReply) + { + if ((systemAddress==replyFromTargetPlayer && replyFromTargetBroadcast==false) || + (systemAddress!=replyFromTargetPlayer && replyFromTargetBroadcast==true)) + { + replyFromTargetBS->Write(data+1, length-1); + blockOnRPCReply=false; + } + } +} +// -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +bool RakPeer::IsLoopbackAddress(const AddressOrGUID &systemIdentifier, bool matchPort) const +{ + if (systemIdentifier.rakNetGuid!=UNASSIGNED_RAKNET_GUID) + return systemIdentifier.rakNetGuid==myGuid; + + const SystemAddress sa = systemIdentifier.systemAddress; + + // Used to see if we are sending to ourselves + char str[64]; + sa.ToString(false,str); +#if !defined(_XBOX) && !defined(X360) + bool isLoopback=strcmp(str,"127.0.0.1")==0; + if (matchPort==false && isLoopback) + return true; + if (matchPort==false) + { + for (int ipIndex=0; ipIndex < MAXIMUM_NUMBER_OF_INTERNAL_IDS; ipIndex++) + if (mySystemAddress[ipIndex].binaryAddress==sa.binaryAddress) + return true; + } + else + { + for (int ipIndex=0; ipIndex < MAXIMUM_NUMBER_OF_INTERNAL_IDS; ipIndex++) + if (mySystemAddress[ipIndex]==sa || + (isLoopback && sa.port==mySystemAddress[ipIndex].port)) + return true; + } +#else + bool isLoopback=strcmp(str,"2130706433")==0; + if (isLoopback) + { + if (matchPort==false) + { + return true; + } + else + { + for (int ipIndex=0; ipIndex < MAXIMUM_NUMBER_OF_INTERNAL_IDS; ipIndex++) + if (mySystemAddress[ipIndex]==sa || + (isLoopback && sa.port==mySystemAddress[ipIndex].port)) + return true; + } + } +#endif + return sa==firstExternalID; +} +// -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +SystemAddress RakPeer::GetLoopbackAddress(void) const +{ +#if !defined(_XBOX) && !defined(X360) + return mySystemAddress[0]; +#else + return firstExternalID; +#endif +} +// -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +void RakPeer::GenerateSYNCookieRandomNumber( void ) +{ +#if !defined(_XBOX) && !defined(_WIN32_WCE) && !defined(X360) + unsigned int number; + int i; + memcpy( oldRandomNumber, newRandomNumber, sizeof( newRandomNumber ) ); + + for ( i = 0; i < (int) sizeof( newRandomNumber ); i += (int) sizeof( number ) ) + { + number = randomMT(); + memcpy( newRandomNumber + i, ( char* ) & number, sizeof( number ) ); + } + + randomNumberExpirationTime = RakNet::GetTime() + SYN_COOKIE_OLD_RANDOM_NUMBER_DURATION; +#endif +} + +// -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +void RakPeer::SecuredConnectionResponse( const SystemAddress systemAddress ) +{ +#if !defined(_XBOX) && !defined(_WIN32_WCE) && !defined(X360) + CSHA1 sha1; +// RSA_BIT_SIZE n; +// big::uint32_t e; +// unsigned char connectionRequestResponse[ 1 + sizeof( big::uint32_t ) + sizeof( RSA_BIT_SIZE ) + 20 ]; + uint32_t modulus[RAKNET_RSA_FACTOR_LIMBS]; + uint32_t e; + unsigned char connectionRequestResponse[ 1 + sizeof( e ) + sizeof( modulus ) + 20 ]; + connectionRequestResponse[ 0 ] = ID_SECURED_CONNECTION_RESPONSE; + + if ( randomNumberExpirationTime < RakNet::GetTime() ) + GenerateSYNCookieRandomNumber(); + + // Hash the SYN-Cookie + // s2c syn-cookie = SHA1_HASH(source ip address + source port + random number) + sha1.Reset(); + sha1.Update( ( unsigned char* ) & systemAddress.binaryAddress, sizeof( systemAddress.binaryAddress ) ); + sha1.Update( ( unsigned char* ) & systemAddress.port, sizeof( systemAddress.port ) ); + sha1.Update( ( unsigned char* ) & ( newRandomNumber ), sizeof(newRandomNumber) ); + sha1.Final(); + + // Write the cookie (not endian swapped) + memcpy( connectionRequestResponse + 1, sha1.GetHash(), 20 ); + + // Write the public keys + e = rsacrypt.getPublicExponent(); + //rsacrypt.getPublicModulus(n); + rsacrypt.getPublicModulus(modulus); + //rsacrypt.getPublicKey( e, n ); + + if (RakNet::BitStream::DoEndianSwap()) + { + RakNet::BitStream::ReverseBytesInPlace(( unsigned char* ) & e, sizeof(e)); + for (int i=0; i < RAKNET_RSA_FACTOR_LIMBS; i++) + RakNet::BitStream::ReverseBytesInPlace((unsigned char*) &modulus[i], sizeof(modulus[i])); + } + + memcpy( connectionRequestResponse + 1 + 20, ( char* ) & e, sizeof( e ) ); + memcpy( connectionRequestResponse + 1 + 20 + sizeof( e ), modulus, sizeof( modulus ) ); + + /* +#ifdef HOST_ENDIAN_IS_BIG + // Mangle the keys on a Big-endian machine before sending + BSWAPCPY( (unsigned char *)(connectionRequestResponse + 1 + 20), + (unsigned char *)&e, sizeof( big::uint32_t ) ); + BSWAPCPY( (unsigned char *)(connectionRequestResponse + 1 + 20 + sizeof( big::uint32_t ) ), + (unsigned char *)n, sizeof( RSA_BIT_SIZE ) ); +#else + memcpy( connectionRequestResponse + 1 + 20, ( char* ) & e, sizeof( big::uint32_t ) ); + memcpy( connectionRequestResponse + 1 + 20 + sizeof( big::uint32_t ), n, sizeof( RSA_BIT_SIZE ) ); +#endif + */ + + // s2c public key, syn-cookie + //SocketLayer::Instance()->SendTo( connectionSocket, ( char* ) connectionRequestResponse, 1 + sizeof( big::uint32_t ) + sizeof( RSA_BIT_SIZE ) + 20, systemAddress.binaryAddress, systemAddress.port ); + // All secure connection requests are unreliable because the entire process needs to be restarted if any part fails. + // Connection requests are resent periodically + SendImmediate(( char* ) connectionRequestResponse, (1 + sizeof( e ) + sizeof( modulus ) + 20) * 8, IMMEDIATE_PRIORITY, UNRELIABLE, 0, systemAddress, false, false, RakNet::GetTimeNS(), 0); +#endif +} + +void RakPeer::SecuredConnectionConfirmation( RakPeer::RemoteSystemStruct * remoteSystem, char* data ) +{ +#if !defined(_XBOX) && !defined(_WIN32_WCE) && !defined(X360) + int i, j; + unsigned char randomNumber[ 20 ]; + unsigned int number; + //bool doSend; + Packet *packet; +// big::uint32_t e; +// RSA_BIT_SIZE n, message, encryptedMessage; +// big::RSACrypt privKeyPncrypt; + uint32_t e; + uint32_t n[RAKNET_RSA_FACTOR_LIMBS], message[RAKNET_RSA_FACTOR_LIMBS], encryptedMessage[RAKNET_RSA_FACTOR_LIMBS]; + RSACrypt privKeyPncrypt; + + // Make sure that we still want to connect + if (remoteSystem->connectMode!=RemoteSystemStruct::REQUESTED_CONNECTION) + return; + + // Copy out e and n + /* +#ifdef HOST_ENDIAN_IS_BIG + BSWAPCPY( (unsigned char *)&e, (unsigned char *)(data + 1 + 20), sizeof( big::uint32_t ) ); + BSWAPCPY( (unsigned char *)n, (unsigned char *)(data + 1 + 20 + sizeof( big::uint32_t )), sizeof( RSA_BIT_SIZE ) ); +#else + memcpy( ( char* ) & e, data + 1 + 20, sizeof( big::uint32_t ) ); + memcpy( n, data + 1 + 20 + sizeof( big::uint32_t ), sizeof( RSA_BIT_SIZE ) ); +#endif + */ + + memcpy( ( char* ) & e, data + 1 + 20, sizeof( e ) ); + memcpy( n, data + 1 + 20 + sizeof( e ), sizeof( n ) ); + + if (RakNet::BitStream::DoEndianSwap()) + { + RakNet::BitStream::ReverseBytesInPlace((unsigned char*) &e, sizeof(e)); + for (int i=0; i < RAKNET_RSA_FACTOR_LIMBS; i++) + RakNet::BitStream::ReverseBytesInPlace((unsigned char*) &n[i], sizeof(n[i])); + } + + + // If we preset a size and it doesn't match, or the keys do not match, then tell the user + if ( usingSecurity == true && keysLocallyGenerated == false ) + { + if ( memcmp( ( char* ) & e, ( char* ) & publicKeyE, sizeof( e ) ) != 0 || + memcmp( n, publicKeyN, sizeof( n ) ) != 0 ) + { + packet=AllocPacket(1, __FILE__, __LINE__); + packet->data[ 0 ] = ID_RSA_PUBLIC_KEY_MISMATCH; + packet->bitSize = sizeof( char ) * 8; + packet->systemAddress = remoteSystem->systemAddress; + packet->systemAddress.systemIndex = ( SystemIndex ) GetIndexFromSystemAddress( packet->systemAddress, true ); + packet->guid.systemIndex=packet->systemAddress.systemIndex; + packet->rcvPort = remoteSystem->rcvPort; + AddPacketToProducer(packet); + remoteSystem->connectMode=RemoteSystemStruct::DISCONNECT_ASAP_SILENTLY; + return; + } + } + + // Create a random number + for ( i = 0; i < (int) sizeof( randomNumber ); i += (int) sizeof( number ) ) + { + number = randomMT(); + memcpy( randomNumber + i, ( char* ) & number, sizeof( number ) ); + } + + memset( message, 0, sizeof( message ) ); + RakAssert( sizeof( message ) >= sizeof( randomNumber ) ); + +// if (RakNet::BitStream::DoEndianSwap()) +// { +// for (int i=0; i < 20; i++) +// RakNet::BitStream::ReverseBytesInPlace((unsigned char*) randomNumber[i], sizeof(randomNumber[i])); +// } + + memcpy( message, randomNumber, sizeof( randomNumber ) ); + + /* + + +#ifdef HOST_ENDIAN_IS_BIG + // Scramble the plaintext message (the random number) + BSWAPCPY( (unsigned char *)message, randomNumber, sizeof(randomNumber) ); +#else + memcpy( message, randomNumber, sizeof( randomNumber ) ); +#endif + */ + privKeyPncrypt.setPublicKey( n, RAKNET_RSA_FACTOR_LIMBS, e ); +// privKeyPncrypt.encrypt( message, encryptedMessage ); + +// printf("message[0]=%i,%i\n", message[0], message[19]); + + privKeyPncrypt.encrypt( encryptedMessage, message ); + +// printf("enc1[0]=%i,%i\n", encryptedMessage[0], encryptedMessage[19]); + + // A big-endian machine needs to scramble the byte order of an outgoing (encrypted) message + if (RakNet::BitStream::DoEndianSwap()) + { + for (int i=0; i < RAKNET_RSA_FACTOR_LIMBS; i++) + RakNet::BitStream::ReverseBytesInPlace((unsigned char*) &encryptedMessage[i], sizeof(encryptedMessage[i])); + } + +// printf("enc2[0]=%i,%i\n", encryptedMessage[0], encryptedMessage[19]); + + + /* +#ifdef HOST_ENDIAN_IS_BIG + // A big-endian machine needs to scramble the byte order of an outgoing (encrypted) message + BSWAPSELF( (unsigned char *)encryptedMessage, sizeof( RSA_BIT_SIZE ) ); +#endif + */ + + /* + rakPeerMutexes[ RakPeer::requestedConnections_MUTEX ].Lock(); + for ( i = 0; i < ( int ) requestedConnectionsList.Size(); i++ ) + { + if ( requestedConnectionsList[ i ]->systemAddress == systemAddress ) + { + doSend = true; + // Generate the AES key + + for ( j = 0; j < 16; j++ ) + requestedConnectionsList[ i ]->AESKey[ j ] = data[ 1 + j ] ^ randomNumber[ j ]; + + requestedConnectionsList[ i ]->setAESKey = true; + + break; + } + } + rakPeerMutexes[ RakPeer::requestedConnections_MUTEX ].Unlock(); + */ + + // Take the remote system's AESKey (SynCookie) and XOR with our random number. + for ( j = 0; j < 16; j++ ) + remoteSystem->AESKey[ j ] = data[ 1 + j ] ^ randomNumber[ j ]; + remoteSystem->setAESKey = true; + + /* +// if ( doSend ) +// { + char reply[ 1 + 20 + sizeof( RSA_BIT_SIZE ) ]; + // c2s RSA(random number), same syn-cookie + reply[ 0 ] = ID_SECURED_CONNECTION_CONFIRMATION; + memcpy( reply + 1, data + 1, 20 ); // Copy the syn-cookie + memcpy( reply + 1 + 20, encryptedMessage, sizeof( RSA_BIT_SIZE ) ); // Copy the encoded random number + + //SocketLayer::Instance()->SendTo( connectionSocket, reply, 1 + 20 + sizeof( RSA_BIT_SIZE ), systemAddress.binaryAddress, systemAddress.port ); + // All secure connection requests are unreliable because the entire process needs to be restarted if any part fails. + // Connection requests are resent periodically + SendImmediate((char*)reply, (1 + 20 + sizeof( RSA_BIT_SIZE )) * 8, IMMEDIATE_PRIORITY, UNRELIABLE, 0, remoteSystem->systemAddress, false, false, RakNet::GetTimeNS()); +// } +*/ + + char reply[ 1 + 20 + sizeof( encryptedMessage ) ]; + // c2s RSA(random number), same syn-cookie + reply[ 0 ] = ID_SECURED_CONNECTION_CONFIRMATION; + memcpy( reply + 1, data + 1, 20 ); // Copy the syn-cookie + memcpy( reply + 1 + 20, encryptedMessage, sizeof( encryptedMessage ) ); // Copy the encoded random number + + // All secure connection requests are unreliable because the entire process needs to be restarted if any part fails. + // Connection requests are resent periodically + SendImmediate((char*)reply, (1 + 20 + sizeof( encryptedMessage )) * 8, IMMEDIATE_PRIORITY, UNRELIABLE, 0, remoteSystem->systemAddress, false, false, RakNet::GetTimeNS(), 0); + +#endif +} +// -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +bool RakPeer::AllowIncomingConnections(void) const +{ + return GetNumberOfRemoteInitiatedConnections() < GetMaximumIncomingConnections(); +} +// -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +void RakPeer::PingInternal( const SystemAddress target, bool performImmediate, PacketReliability reliability ) +{ + if ( IsActive() == false ) + return ; + + RakNet::BitStream bitStream(sizeof(unsigned char)+sizeof(RakNetTime)); + bitStream.Write((MessageID)ID_INTERNAL_PING); + RakNetTimeUS currentTimeNS = RakNet::GetTimeNS(); + RakNetTime currentTime = RakNet::GetTime(); + bitStream.Write(currentTime); + if (performImmediate) + SendImmediate( (char*)bitStream.GetData(), bitStream.GetNumberOfBitsUsed(), IMMEDIATE_PRIORITY, reliability, 0, target, false, false, currentTimeNS, 0 ); + else + Send( &bitStream, IMMEDIATE_PRIORITY, reliability, 0, target, false ); +} +// -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +void RakPeer::CloseConnectionInternal( const AddressOrGUID& systemIdentifier, bool sendDisconnectionNotification, bool performImmediate, unsigned char orderingChannel, PacketPriority disconnectionNotificationPriority ) +{ +#ifdef _DEBUG + RakAssert(orderingChannel < 32); +#endif + + if (systemIdentifier.IsUndefined()) + return; + + if ( remoteSystemList == 0 || endThreads == true ) + return; + + SystemAddress target; + if (systemIdentifier.systemAddress!=UNASSIGNED_SYSTEM_ADDRESS) + { + target=systemIdentifier.systemAddress; + } + else + { + target=GetSystemAddressFromGuid(systemIdentifier.rakNetGuid); + } + + if (sendDisconnectionNotification) + { + NotifyAndFlagForShutdown(target, performImmediate, orderingChannel, disconnectionNotificationPriority); + } + else + { + if (performImmediate) + { + unsigned int index = GetRemoteSystemIndex(target); + if (index!=(unsigned int) -1) + { + if ( remoteSystemList[index].isActive ) + { + // Found the index to stop + remoteSystemList[index].isActive = false; + + remoteSystemList[index].guid=UNASSIGNED_RAKNET_GUID; + + // Reserve this reliability layer for ourselves + //remoteSystemList[ remoteSystemLookup[index].index ].systemAddress = UNASSIGNED_SYSTEM_ADDRESS; + + // Clear any remaining messages + remoteSystemList[index].reliabilityLayer.Reset(false, remoteSystemList[index].MTUSize); + + // Not using this socket + remoteSystemList[index].rakNetSocket.SetNull(); + } + } + } + else + { + BufferedCommandStruct *bcs; +#ifdef _RAKNET_THREADSAFE + bcs=bufferedCommands.Allocate( __FILE__, __LINE__ ); + bcs->command=BufferedCommandStruct::BCS_CLOSE_CONNECTION; + bcs->systemIdentifier=target; + bcs->data=0; + bcs->orderingChannel=orderingChannel; + bcs->priority=disconnectionNotificationPriority; + bufferedCommands.Push(bcs); +#else + bcs=bufferedCommands.WriteLock(); + bcs->command=BufferedCommandStruct::BCS_CLOSE_CONNECTION; + bcs->systemIdentifier=target; + bcs->data=0; + bcs->orderingChannel=orderingChannel; + bcs->priority=disconnectionNotificationPriority; + bufferedCommands.WriteUnlock(); +#endif + } + } +} +// -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +bool RakPeer::ValidSendTarget(SystemAddress systemAddress, bool broadcast) +{ + unsigned remoteSystemIndex; + + // remoteSystemList in user thread. This is slow so only do it in debug + for ( remoteSystemIndex = 0; remoteSystemIndex < maximumNumberOfPeers; remoteSystemIndex++ ) + //for ( remoteSystemIndex = 0; remoteSystemIndex < remoteSystemListSize; remoteSystemIndex++ ) + { + if ( remoteSystemList[ remoteSystemIndex ].isActive && + remoteSystemList[ remoteSystemIndex ].connectMode==RakPeer::RemoteSystemStruct::CONNECTED && // Not fully connected players are not valid user-send targets because the reliability layer wasn't reset yet + ( ( broadcast == false && remoteSystemList[ remoteSystemIndex ].systemAddress == systemAddress ) || + ( broadcast == true && remoteSystemList[ remoteSystemIndex ].systemAddress != systemAddress ) ) + ) + return true; + } + + return false; +} +// -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +void RakPeer::SendBuffered( const char *data, BitSize_t numberOfBitsToSend, PacketPriority priority, PacketReliability reliability, char orderingChannel, const AddressOrGUID systemIdentifier, bool broadcast, RemoteSystemStruct::ConnectMode connectionMode, uint32_t receipt ) +{ + BufferedCommandStruct *bcs; + +#ifdef _RAKNET_THREADSAFE + bcs=bufferedCommands.Allocate( __FILE__, __LINE__ ); +#else + bcs=bufferedCommands.WriteLock(); +#endif + bcs->data = (char*) rakMalloc_Ex( (size_t) BITS_TO_BYTES(numberOfBitsToSend), __FILE__, __LINE__ ); // Making a copy doesn't lose efficiency because I tell the reliability layer to use this allocation for its own copy + if (bcs->data==0) + { + notifyOutOfMemory(__FILE__, __LINE__); +#ifdef _RAKNET_THREADSAFE + bufferedCommands.Deallocate(bcs, __FILE__,__LINE__); +#else + bufferedCommands.WriteUnlock(); +#endif + return; + } + memcpy(bcs->data, data, (size_t) BITS_TO_BYTES(numberOfBitsToSend)); + bcs->numberOfBitsToSend=numberOfBitsToSend; + bcs->priority=priority; + bcs->reliability=reliability; + bcs->orderingChannel=orderingChannel; + bcs->systemIdentifier=systemIdentifier; + bcs->broadcast=broadcast; + bcs->connectionMode=connectionMode; + bcs->receipt=receipt; + bcs->command=BufferedCommandStruct::BCS_SEND; +#ifdef _RAKNET_THREADSAFE + bufferedCommands.Push(bcs); +#else + bufferedCommands.WriteUnlock(); +#endif + + if (priority==IMMEDIATE_PRIORITY) + { + // Forces pending sends to go out now, rather than waiting to the next update interval + quitAndDataEvents.SetEvent(); + } +} +// -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +void RakPeer::SendBufferedList( const char **data, const int *lengths, const int numParameters, PacketPriority priority, PacketReliability reliability, char orderingChannel, const AddressOrGUID systemIdentifier, bool broadcast, RemoteSystemStruct::ConnectMode connectionMode, uint32_t receipt ) +{ + BufferedCommandStruct *bcs; + unsigned int totalLength=0; + unsigned int lengthOffset; + int i; + for (i=0; i < numParameters; i++) + { + if (lengths[i]>0) + totalLength+=lengths[i]; + } + if (totalLength==0) + return; + + char *dataAggregate; + dataAggregate = (char*) rakMalloc_Ex( (size_t) totalLength, __FILE__, __LINE__ ); // Making a copy doesn't lose efficiency because I tell the reliability layer to use this allocation for its own copy + if (dataAggregate==0) + { + notifyOutOfMemory(__FILE__, __LINE__); + return; + } + for (i=0, lengthOffset=0; i < numParameters; i++) + { + if (lengths[i]>0) + { + memcpy(dataAggregate+lengthOffset, data[i], lengths[i]); + lengthOffset+=lengths[i]; + } + } + + if (broadcast==false && IsLoopbackAddress(systemIdentifier.systemAddress,true)) + { + SendLoopback(dataAggregate,totalLength); + rakFree_Ex(dataAggregate,__FILE__,__LINE__); + return; + } + +#ifdef _RAKNET_THREADSAFE + bcs=bufferedCommands.Allocate( __FILE__, __LINE__ ); +#else + bcs=bufferedCommands.WriteLock(); +#endif + bcs->data = dataAggregate; + bcs->numberOfBitsToSend=BYTES_TO_BITS(totalLength); + bcs->priority=priority; + bcs->reliability=reliability; + bcs->orderingChannel=orderingChannel; + bcs->systemIdentifier=systemIdentifier; + bcs->broadcast=broadcast; + bcs->connectionMode=connectionMode; + bcs->receipt=receipt; + bcs->command=BufferedCommandStruct::BCS_SEND; +#ifdef _RAKNET_THREADSAFE + bufferedCommands.Push(bcs); +#else + bufferedCommands.WriteUnlock(); +#endif + + if (priority==IMMEDIATE_PRIORITY) + { + // Forces pending sends to go out now, rather than waiting to the next update interval + quitAndDataEvents.SetEvent(); + } +} +// -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +bool RakPeer::SendImmediate( char *data, BitSize_t numberOfBitsToSend, PacketPriority priority, PacketReliability reliability, char orderingChannel, const AddressOrGUID systemIdentifier, bool broadcast, bool useCallerDataAllocation, RakNetTimeUS currentTime, uint32_t receipt ) +{ + unsigned *sendList; + unsigned sendListSize; + bool callerDataAllocationUsed; + unsigned int remoteSystemIndex, sendListIndex; // Iterates into the list of remote systems + unsigned numberOfBytesUsed = (unsigned) BITS_TO_BYTES(numberOfBitsToSend); + callerDataAllocationUsed=false; + + sendListSize=0; + + if (systemIdentifier.systemAddress!=UNASSIGNED_SYSTEM_ADDRESS) + remoteSystemIndex=GetIndexFromSystemAddress( systemIdentifier.systemAddress, true ); + else if (systemIdentifier.rakNetGuid!=UNASSIGNED_RAKNET_GUID) + remoteSystemIndex=GetSystemIndexFromGuid(systemIdentifier.rakNetGuid); + else + remoteSystemIndex=(unsigned int) -1; + + // 03/06/06 - If broadcast is false, use the optimized version of GetIndexFromSystemAddress + if (broadcast==false) + { + if (remoteSystemIndex==(unsigned int) -1) + { +#ifdef _DEBUG +// int debugIndex = GetRemoteSystemIndex(systemIdentifier.systemAddress); +#endif + return false; + } + +#if !defined(_XBOX) && !defined(X360) + sendList=(unsigned *)alloca(sizeof(unsigned)); +#else + sendList = (unsigned *) rakMalloc_Ex(sizeof(unsigned), __FILE__, __LINE__); +#endif + + if (remoteSystemList[remoteSystemIndex].isActive && + remoteSystemList[remoteSystemIndex].connectMode!=RemoteSystemStruct::DISCONNECT_ASAP && + remoteSystemList[remoteSystemIndex].connectMode!=RemoteSystemStruct::DISCONNECT_ASAP_SILENTLY && + remoteSystemList[remoteSystemIndex].connectMode!=RemoteSystemStruct::DISCONNECT_ON_NO_ACK) + { + sendList[0]=remoteSystemIndex; + sendListSize=1; + } + } + else + { +#if !defined(_XBOX) && !defined(X360) + //sendList=(unsigned *)alloca(sizeof(unsigned)*remoteSystemListSize); + sendList=(unsigned *)alloca(sizeof(unsigned)*maximumNumberOfPeers); +#else + //sendList = RakNet::OP_NEW( __FILE__, __LINE__ ); + sendList = (unsigned *) rakMalloc_Ex(sizeof(unsigned)*maximumNumberOfPeers, __FILE__, __LINE__); +#endif + + // remoteSystemList in network thread + unsigned int idx; + for ( idx = 0; idx < maximumNumberOfPeers; idx++ ) + { + if (remoteSystemIndex!=(unsigned int) -1 && idx==remoteSystemIndex) + continue; + + if ( remoteSystemList[ idx ].isActive && remoteSystemList[ idx ].systemAddress != UNASSIGNED_SYSTEM_ADDRESS ) + sendList[sendListSize++]=idx; + } + } + + if (sendListSize==0) + { +#if defined(_XBOX) && !defined(X360) + +#endif + return false; + } + + for (sendListIndex=0; sendListIndex < sendListSize; sendListIndex++) + { + if ( trackFrequencyTable ) + { + unsigned i; + // Store output frequency + for (i=0 ; i < numberOfBytesUsed; i++ ) + frequencyTable[ (unsigned char)(data[i]) ]++; + } + + if ( outputTree ) + { + RakNet::BitStream bitStreamCopy( numberOfBytesUsed ); + outputTree->EncodeArray( (unsigned char*) data, numberOfBytesUsed, &bitStreamCopy ); + rawBytesSent += numberOfBytesUsed; + compressedBytesSent += (unsigned int) bitStreamCopy.GetNumberOfBytesUsed(); + remoteSystemList[sendList[sendListIndex]].reliabilityLayer.Send( (char*) bitStreamCopy.GetData(), bitStreamCopy.GetNumberOfBitsUsed(), priority, reliability, orderingChannel, true, remoteSystemList[sendList[sendListIndex]].MTUSize, currentTime, receipt ); + } + else + { + // Send may split the packet and thus deallocate data. Don't assume data is valid if we use the callerAllocationData + bool useData = useCallerDataAllocation && callerDataAllocationUsed==false && sendListIndex+1==sendListSize; + remoteSystemList[sendList[sendListIndex]].reliabilityLayer.Send( data, numberOfBitsToSend, priority, reliability, orderingChannel, useData==false, remoteSystemList[sendList[sendListIndex]].MTUSize, currentTime, receipt ); + if (useData) + callerDataAllocationUsed=true; + } + + if (reliability==RELIABLE || + reliability==RELIABLE_ORDERED || + reliability==RELIABLE_SEQUENCED || + reliability==RELIABLE_WITH_ACK_RECEIPT || + reliability==RELIABLE_ORDERED_WITH_ACK_RECEIPT +// || +// reliability==RELIABLE_SEQUENCED_WITH_ACK_RECEIPT + ) + remoteSystemList[sendList[sendListIndex]].lastReliableSend=(RakNetTime)(currentTime/(RakNetTimeUS)1000); + } + +#if defined(_XBOX) && !defined(X360) + +#endif + + // Return value only meaningful if true was passed for useCallerDataAllocation. Means the reliability layer used that data copy, so the caller should not deallocate it + return callerDataAllocationUsed; +} +// -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +void RakPeer::ResetSendReceipt(void) +{ + sendReceiptSerialMutex.Lock(); + sendReceiptSerial=1; + sendReceiptSerialMutex.Unlock(); +} +// -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +void RakPeer::ClearBufferedCommands(void) +{ + BufferedCommandStruct *bcs; + +#ifdef _RAKNET_THREADSAFE + while ((bcs=bufferedCommands.Pop())!=0) + { + if (bcs->data) + rakFree_Ex(bcs->data, __FILE__, __LINE__ ); + + bufferedCommands.Deallocate(bcs, __FILE__,__LINE__); + } + bufferedCommands.Clear(__FILE__, __LINE__); +#else + while ((bcs=bufferedCommands.ReadLock())!=0) + { + if (bcs->data) + rakFree_Ex(bcs->data, __FILE__, __LINE__ ); + + bufferedCommands.ReadUnlock(); + } + bufferedCommands.Clear(__FILE__, __LINE__); +#endif +} +// -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +void RakPeer::ClearBufferedPackets(void) +{ + RecvFromStruct *bcs; + +#ifdef _RAKNET_THREADSAFE + while ((bcs=bufferedPackets.Pop())!=0) + { + bufferedPackets.Deallocate(bcs, __FILE__,__LINE__); + } + bufferedPackets.Clear(__FILE__, __LINE__); +#else + while ((bcs=bufferedPackets.ReadLock())!=0) + { + bufferedPackets.ReadUnlock(); + } + bufferedPackets.Clear(__FILE__, __LINE__); +#endif +} +// -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +void RakPeer::ClearSocketQueryOutput(void) +{ + socketQueryOutput.Clear(__FILE__, __LINE__); +} +// -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +void RakPeer::ClearRequestedConnectionList(void) +{ + DataStructures::Queue freeQueue; + requestedConnectionQueueMutex.Lock(); + while (requestedConnectionQueue.Size()) + freeQueue.Push(requestedConnectionQueue.Pop(), __FILE__, __LINE__ ); + requestedConnectionQueueMutex.Unlock(); + unsigned i; + for (i=0; i < freeQueue.Size(); i++) + RakNet::OP_DELETE(freeQueue[i], __FILE__, __LINE__ ); +} +inline void RakPeer::AddPacketToProducer(Packet *p) +{ + packetReturnMutex.Lock(); + packetReturnQueue.Push(p,__FILE__,__LINE__); + packetReturnMutex.Unlock(); +} +// -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +void RakPeer::GenerateGUID(void) +{ + // Mac address is a poor solution because you can't have multiple connections from the same system +#if defined(_XBOX) || defined(X360) + +#elif defined(_PS3) || defined(__PS3__) || defined(SN_TARGET_PS3) + +#elif defined(_WIN32) + myGuid.g=RakNet::GetTimeUS(); + + RakNetTimeUS lastTime, thisTime; + int j; + // Sleep a small random time, then use the last 4 bits as a source of randomness + for (j=0; j < 8; j++) + { + lastTime = RakNet::GetTimeUS(); + RakSleep(1); + RakSleep(0); + thisTime = RakNet::GetTimeUS(); + RakNetTimeUS diff = thisTime-lastTime; + unsigned int diff4Bits = (unsigned int) (diff & 15); + diff4Bits <<= 32-4; + diff4Bits >>= j*4; + ((char*)&myGuid.g)[j] ^= diff4Bits; + } + +#else + struct timeval tv; + gettimeofday(&tv, NULL); + myGuid.g=tv.tv_usec + tv.tv_sec * 1000000; +#endif +} +// -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +void ProcessPortUnreachable( unsigned int binaryAddress, unsigned short port, RakPeer *rakPeer ) +{ + (void) binaryAddress; + (void) port; + (void) rakPeer; + +} +// -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +bool ProcessOfflineNetworkPacket( const SystemAddress systemAddress, const char *data, const int length, RakPeer *rakPeer, RakNetSmartPtr rakNetSocket, bool *isOfflineMessage, RakNetTimeUS timeRead ) +{ + (void) timeRead; + RakPeer::RemoteSystemStruct *remoteSystem; + Packet *packet; + unsigned i; + + struct sockaddr_in saRemote; + unsigned short rcvPort = 0; + socklen_t saLength = sizeof(saRemote); + if (getsockname(rakNetSocket->s, (sockaddr*)&saRemote, &saLength) == 0) + rcvPort = (unsigned short)ntohs(saRemote.sin_port); + +#if !defined(_XBOX) && !defined(X360) + char str1[64]; + systemAddress.ToString(false, str1); + if (rakPeer->IsBanned( str1 )) + { + for (i=0; i < rakPeer->messageHandlerList.Size(); i++) + rakPeer->messageHandlerList[i]->OnDirectSocketReceive(data, length*8, systemAddress); + + RakNet::BitStream bs; + bs.Write((MessageID)ID_CONNECTION_BANNED); + bs.WriteAlignedBytes((const unsigned char*) OFFLINE_MESSAGE_DATA_ID, sizeof(OFFLINE_MESSAGE_DATA_ID)); + bs.Write(rakPeer->GetGuidFromSystemAddress(UNASSIGNED_SYSTEM_ADDRESS)); + + unsigned i; + for (i=0; i < rakPeer->messageHandlerList.Size(); i++) + rakPeer->messageHandlerList[i]->OnDirectSocketSend((char*) bs.GetData(), bs.GetNumberOfBitsUsed(), systemAddress); + SocketLayer::Instance()->SendTo( rakNetSocket->s, (char*) bs.GetData(), bs.GetNumberOfBytesUsed(), systemAddress.binaryAddress, systemAddress.port, rakNetSocket->remotePortRakNetWasStartedOn_PS3 ); + + return true; + } +#endif + + + // The reason for all this is that the reliability layer has no way to tell between offline messages that arrived late for a player that is now connected, + // and a regular encoding. So I insert OFFLINE_MESSAGE_DATA_ID into the stream, the encoding of which is essentially impossible to hit by random chance + if (length <=2) + { + *isOfflineMessage=true; + } + else if ( + ((unsigned char)data[0] == ID_PING || + (unsigned char)data[0] == ID_PING_OPEN_CONNECTIONS) && + length == sizeof(unsigned char) + sizeof(RakNetTime) + sizeof(OFFLINE_MESSAGE_DATA_ID)) + { + *isOfflineMessage=memcmp(data+sizeof(unsigned char) + sizeof(RakNetTime), OFFLINE_MESSAGE_DATA_ID, sizeof(OFFLINE_MESSAGE_DATA_ID))==0; + } + else if ((unsigned char)data[0] == ID_PONG && (size_t) length >= sizeof(unsigned char) + sizeof(RakNetTime) + RakNetGUID::size() + sizeof(OFFLINE_MESSAGE_DATA_ID)) + { + *isOfflineMessage=memcmp(data+sizeof(unsigned char) + sizeof(RakNetTime) + RakNetGUID::size(), OFFLINE_MESSAGE_DATA_ID, sizeof(OFFLINE_MESSAGE_DATA_ID))==0; + } + else if ( + ((unsigned char)data[0] == ID_OUT_OF_BAND_INTERNAL || (unsigned char)data[0] == ID_OPEN_CONNECTION_REQUEST) && + (size_t) length >= sizeof(MessageID)*2 + RakNetGUID::size() + sizeof(OFFLINE_MESSAGE_DATA_ID)) + { + *isOfflineMessage=memcmp(data+sizeof(MessageID)*2 + RakNetGUID::size(), OFFLINE_MESSAGE_DATA_ID, sizeof(OFFLINE_MESSAGE_DATA_ID))==0; + } + else if ( + ( + (unsigned char)data[0] == ID_OPEN_CONNECTION_REPLY || + (unsigned char)data[0] == ID_CONNECTION_ATTEMPT_FAILED || + (unsigned char)data[0] == ID_NO_FREE_INCOMING_CONNECTIONS || + (unsigned char)data[0] == ID_CONNECTION_BANNED || + (unsigned char)data[0] == ID_ALREADY_CONNECTED || + (unsigned char)data[0] == ID_IP_RECENTLY_CONNECTED || + (unsigned char)data[0] == ID_CONNECTION_REQUEST) && + (size_t) length >= sizeof(MessageID) + RakNetGUID::size() + sizeof(OFFLINE_MESSAGE_DATA_ID)) + { + *isOfflineMessage=memcmp(data+sizeof(MessageID), OFFLINE_MESSAGE_DATA_ID, sizeof(OFFLINE_MESSAGE_DATA_ID))==0; + } + else if (((unsigned char)data[0] == ID_INCOMPATIBLE_PROTOCOL_VERSION&& + (size_t) length == sizeof(MessageID)*2 + RakNetGUID::size() + sizeof(OFFLINE_MESSAGE_DATA_ID))) + { + *isOfflineMessage=memcmp(data+sizeof(MessageID)*2, OFFLINE_MESSAGE_DATA_ID, sizeof(OFFLINE_MESSAGE_DATA_ID))==0; + } + else + { + *isOfflineMessage=false; + } + + if (*isOfflineMessage) + { + for (i=0; i < rakPeer->messageHandlerList.Size(); i++) + rakPeer->messageHandlerList[i]->OnDirectSocketReceive(data, length*8, systemAddress); + + // These are all messages from unconnected systems. Messages here can be any size, but are never processed from connected systems. + if ( ( (unsigned char) data[ 0 ] == ID_PING_OPEN_CONNECTIONS + || (unsigned char)(data)[0] == ID_PING) && length == sizeof(unsigned char)+sizeof(RakNetTime)+sizeof(OFFLINE_MESSAGE_DATA_ID) ) + { + if ( (unsigned char)(data)[0] == ID_PING || + rakPeer->AllowIncomingConnections() ) // Open connections with players + { +// #if !defined(_XBOX) && !defined(X360) + RakNet::BitStream inBitStream( (unsigned char *) data, length, false ); + inBitStream.IgnoreBits(8); + RakNetTime sendPingTime; + inBitStream.Read(sendPingTime); + + RakNet::BitStream outBitStream; + outBitStream.Write((MessageID)ID_PONG); // Should be named ID_UNCONNECTED_PONG eventually + outBitStream.Write(sendPingTime); + outBitStream.Write(rakPeer->myGuid); + outBitStream.WriteAlignedBytes((const unsigned char*) OFFLINE_MESSAGE_DATA_ID, sizeof(OFFLINE_MESSAGE_DATA_ID)); + + rakPeer->rakPeerMutexes[ RakPeer::offlinePingResponse_Mutex ].Lock(); + // They are connected, so append offline ping data + outBitStream.Write( (char*)rakPeer->offlinePingResponse.GetData(), rakPeer->offlinePingResponse.GetNumberOfBytesUsed() ); + rakPeer->rakPeerMutexes[ RakPeer::offlinePingResponse_Mutex ].Unlock(); + + unsigned i; + for (i=0; i < rakPeer->messageHandlerList.Size(); i++) + rakPeer->messageHandlerList[i]->OnDirectSocketSend((const char*)outBitStream.GetData(), outBitStream.GetNumberOfBytesUsed(), systemAddress); + + char str1[64]; + systemAddress.ToString(false, str1); + SocketLayer::Instance()->SendTo( rakNetSocket->s, (const char*)outBitStream.GetData(), (unsigned int) outBitStream.GetNumberOfBytesUsed(), str1 , systemAddress.port, rakNetSocket->remotePortRakNetWasStartedOn_PS3 ); + + packet=rakPeer->AllocPacket(sizeof(MessageID), __FILE__, __LINE__); + packet->data[0]=data[0]; + packet->systemAddress = systemAddress; + packet->guid=UNASSIGNED_RAKNET_GUID; + packet->systemAddress.systemIndex = ( SystemIndex ) rakPeer->GetIndexFromSystemAddress( systemAddress, true ); + packet->guid.systemIndex=packet->systemAddress.systemIndex; + packet->rcvPort = rcvPort; + rakPeer->AddPacketToProducer(packet); +// #endif + } + } + // UNCONNECTED MESSAGE Pong with no data. + else if ((unsigned char) data[ 0 ] == ID_PONG && (size_t) length >= sizeof(unsigned char)+sizeof(RakNetTime)+RakNetGUID::size()+sizeof(OFFLINE_MESSAGE_DATA_ID) && (size_t) length < sizeof(unsigned char)+sizeof(RakNetTime)+RakNetGUID::size()+sizeof(OFFLINE_MESSAGE_DATA_ID)+MAX_OFFLINE_DATA_LENGTH) + { + packet=rakPeer->AllocPacket((unsigned int) (length-sizeof(OFFLINE_MESSAGE_DATA_ID)-RakNetGUID::size()), __FILE__, __LINE__); + RakNet::BitStream bs((unsigned char*) data, length, false); + bs.IgnoreBytes(sizeof(unsigned char)+sizeof(RakNetTime)); + bs.Read(packet->guid); + packet->data[0]=ID_PONG; + // Don't endian swap the time, so the user can do so when reading out as a bitstream + memcpy(packet->data+sizeof(unsigned char), data+sizeof(unsigned char), sizeof(RakNetTime)); +// RakNetTime test1; +// memcpy(&test1,data+sizeof(unsigned char), sizeof(RakNetTime)); +// RakNetTime test2; +// test2=RakNet::GetTime(); + memcpy(packet->data+sizeof(unsigned char)+sizeof(RakNetTime), data+sizeof(unsigned char)+sizeof(RakNetTime)+RakNetGUID::size()+sizeof(OFFLINE_MESSAGE_DATA_ID), length-sizeof(unsigned char)-sizeof(RakNetTime)-RakNetGUID::size()-sizeof(OFFLINE_MESSAGE_DATA_ID)); + packet->bitSize = BYTES_TO_BITS(packet->length); + packet->systemAddress = systemAddress; + packet->systemAddress.systemIndex = ( SystemIndex ) rakPeer->GetIndexFromSystemAddress( systemAddress, true ); + packet->guid.systemIndex=packet->systemAddress.systemIndex; + packet->rcvPort = rcvPort; + rakPeer->AddPacketToProducer(packet); + } + else if ((unsigned char) data[ 0 ] == ID_OUT_OF_BAND_INTERNAL && + (size_t) length < MAX_OFFLINE_DATA_LENGTH+sizeof(OFFLINE_MESSAGE_DATA_ID)+sizeof(MessageID)*2+RakNetGUID::size()) + { + unsigned int dataLength = (unsigned int) (length-sizeof(OFFLINE_MESSAGE_DATA_ID)-RakNetGUID::size()-sizeof(MessageID)*2); + RakAssert(dataLength<1024); + packet=rakPeer->AllocPacket(dataLength+sizeof(MessageID), __FILE__, __LINE__); + RakAssert(packet->length<1024); + + RakNet::BitStream bs2((unsigned char*) data, length, false); + bs2.IgnoreBytes(sizeof(MessageID)*2); + bs2.Read(packet->guid); + + packet->data[0]=data[1]; + memcpy(packet->data+1, data+sizeof(OFFLINE_MESSAGE_DATA_ID)+sizeof(MessageID)*2 + RakNetGUID::size(), dataLength); + packet->systemAddress = systemAddress; + packet->systemAddress.systemIndex = ( SystemIndex ) rakPeer->GetIndexFromSystemAddress( systemAddress, true ); + packet->guid.systemIndex=packet->systemAddress.systemIndex; + packet->rcvPort = rcvPort; + rakPeer->AddPacketToProducer(packet); + } + else if ((unsigned char)(data)[0] == (MessageID)ID_OPEN_CONNECTION_REPLY) + { + for (i=0; i < rakPeer->messageHandlerList.Size(); i++) + rakPeer->messageHandlerList[i]->OnDirectSocketReceive(data, length*8, systemAddress); + + RakNet::BitStream bs((unsigned char*) data,length,false); + bs.IgnoreBytes(sizeof(MessageID)); + bs.IgnoreBytes(sizeof(OFFLINE_MESSAGE_DATA_ID)); + RakNetGUID guid; + bs.Read(guid); + SystemAddress bindingAddress; + bs.Read(bindingAddress); + + RakPeer::RequestedConnectionStruct *rcs; + bool unlock=true; + unsigned i; + rakPeer->requestedConnectionQueueMutex.Lock(); + for (i=0; i < rakPeer->requestedConnectionQueue.Size(); i++) + { + rcs=rakPeer->requestedConnectionQueue[i]; + if (rcs->systemAddress==systemAddress) + { + rakPeer->requestedConnectionQueueMutex.Unlock(); + unlock=false; + + RakAssert(rcs->actionToTake==RakPeer::RequestedConnectionStruct::CONNECT); + // You might get this when already connected because of cross-connections + bool thisIPConnectedRecently=false; + remoteSystem=rakPeer->GetRemoteSystemFromSystemAddress( systemAddress, true, true ); + // Removeme + // printf("1 p=%i\n", rakPeer->mySystemAddress->port); + if (remoteSystem==0) + { + // Removeme + // printf("2 p=%i\n", rakPeer->mySystemAddress->port); + if (rcs->socket.IsNull()) + { + // Removeme + // printf("3 p=%i\n", rakPeer->mySystemAddress->port); + remoteSystem=rakPeer->AssignSystemAddressToRemoteSystemList(systemAddress, RakPeer::RemoteSystemStruct::UNVERIFIED_SENDER, rakNetSocket, &thisIPConnectedRecently, bindingAddress, length+UDP_HEADER_SIZE, guid, rcvPort); + } + else + { + // Removeme + // printf("4 p=%i\n", rakPeer->mySystemAddress->port); + remoteSystem=rakPeer->AssignSystemAddressToRemoteSystemList(systemAddress, RakPeer::RemoteSystemStruct::UNVERIFIED_SENDER, rcs->socket, &thisIPConnectedRecently, bindingAddress, length+UDP_HEADER_SIZE, guid, rcvPort); + } + + + // printf("System %i got ID_OPEN_CONNECTION_REPLY from %i\n", rakPeer->mySystemAddress[0].port, systemAddress.port); + } + + // 4/13/09 Attackers can flood ID_OPEN_CONNECTION_REQUEST and use up all available connection slots + // Ignore connection attempts if this IP address connected within the last 100 milliseconds + if (thisIPConnectedRecently==false) + { + // Don't check GetRemoteSystemFromGUID, server will verify + if (remoteSystem) + { + // RakNetTimeUS time = RakNet::GetTimeNS(); + remoteSystem->weInitiatedTheConnection=true; + remoteSystem->connectMode=RakPeer::RemoteSystemStruct::REQUESTED_CONNECTION; + if (rcs->timeoutTime!=0) + remoteSystem->reliabilityLayer.SetTimeoutTime(rcs->timeoutTime); + + // Removeme + if (rakPeer->mySystemAddress[0].port==60000 && systemAddress.port==60001) + { + printf("Sending ID_CONNECTION_REQUEST\n"); + } + + RakNet::BitStream temp; + temp.Write( (MessageID)ID_CONNECTION_REQUEST ); + temp.WriteAlignedBytes((const unsigned char*) OFFLINE_MESSAGE_DATA_ID, sizeof(OFFLINE_MESSAGE_DATA_ID)); + temp.Write(rakPeer->GetGuidFromSystemAddress(UNASSIGNED_SYSTEM_ADDRESS)); + if ( rcs->outgoingPasswordLength > 0 ) + temp.Write( ( char* ) rcs->outgoingPassword, rcs->outgoingPasswordLength ); + + rakPeer->SendImmediate((char*)temp.GetData(), temp.GetNumberOfBitsUsed(), IMMEDIATE_PRIORITY, RELIABLE, 0, systemAddress, false, false, timeRead, 0 ); + } + else + { + // Failed, no connections available anymore + packet=rakPeer->AllocPacket(sizeof( char ), __FILE__, __LINE__); + packet->data[ 0 ] = ID_CONNECTION_ATTEMPT_FAILED; // Attempted a connection and couldn't + packet->bitSize = ( sizeof( char ) * 8); + packet->systemAddress = rcs->systemAddress; + packet->guid=guid; + packet->rcvPort = rcvPort; + rakPeer->AddPacketToProducer(packet); + } + } + + rakPeer->requestedConnectionQueueMutex.Lock(); + for (unsigned int k=0; k < rakPeer->requestedConnectionQueue.Size(); k++) + { + if (rakPeer->requestedConnectionQueue[k]->systemAddress==systemAddress) + { + rakPeer->requestedConnectionQueue.RemoveAtIndex(k); + break; + } + } + rakPeer->requestedConnectionQueueMutex.Unlock(); + + RakNet::OP_DELETE(rcs,__FILE__,__LINE__); + + break; + } + } + + if (unlock) + rakPeer->requestedConnectionQueueMutex.Unlock(); + + return true; + + } + else if ((unsigned char)(data)[0] == (MessageID)ID_CONNECTION_ATTEMPT_FAILED || + (unsigned char)(data)[0] == (MessageID)ID_NO_FREE_INCOMING_CONNECTIONS || + (unsigned char)(data)[0] == (MessageID)ID_CONNECTION_BANNED || + (unsigned char)(data)[0] == (MessageID)ID_ALREADY_CONNECTED || + (unsigned char)(data)[0] == (MessageID)ID_INVALID_PASSWORD || + (unsigned char)(data)[0] == (MessageID)ID_IP_RECENTLY_CONNECTED || + (unsigned char)(data)[0] == (MessageID)ID_INCOMPATIBLE_PROTOCOL_VERSION) + { + + RakNet::BitStream bs((unsigned char*) data,length,false); + bs.IgnoreBytes(sizeof(MessageID)); + bs.IgnoreBytes(sizeof(OFFLINE_MESSAGE_DATA_ID)); + if ((unsigned char)(data)[0] == (MessageID)ID_INCOMPATIBLE_PROTOCOL_VERSION) + bs.IgnoreBytes(sizeof(unsigned char)); + + RakNetGUID guid; + bs.Read(guid); + + RakPeer::RequestedConnectionStruct *rcs; + bool connectionAttemptCancelled=false; + unsigned i; + rakPeer->requestedConnectionQueueMutex.Lock(); + for (i=0; i < rakPeer->requestedConnectionQueue.Size(); i++) + { + rcs=rakPeer->requestedConnectionQueue[i]; + if (rcs->actionToTake==RakPeer::RequestedConnectionStruct::CONNECT && rcs->systemAddress==systemAddress) + { + connectionAttemptCancelled=true; +#if defined(_PS3) || defined(__PS3__) || defined(SN_TARGET_PS3) + +#endif + + rakPeer->requestedConnectionQueue.RemoveAtIndex(i); + RakNet::OP_DELETE(rcs,__FILE__,__LINE__); + break; + } + } + + rakPeer->requestedConnectionQueueMutex.Unlock(); + + if (connectionAttemptCancelled) + { + // Tell user of connection attempt failed + packet=rakPeer->AllocPacket(sizeof( char ), __FILE__, __LINE__); + packet->data[ 0 ] = data[0]; // Attempted a connection and couldn't + packet->bitSize = ( sizeof( char ) * 8); + packet->systemAddress = systemAddress; + packet->guid=guid; + packet->rcvPort = rcvPort; + rakPeer->AddPacketToProducer(packet); + } + } + else if ((unsigned char)(data)[0] == ID_OPEN_CONNECTION_REQUEST && length >= sizeof(unsigned char)*2) + { + // if (rakPeer->mySystemAddress[0].port!=60481) + // return; + + unsigned int i; + //RAKNET_DEBUG_PRINTF("%i:IOCR, ", __LINE__); + char remoteProtocol=data[1]; + if (remoteProtocol!=RAKNET_PROTOCOL_VERSION) + { + RakNet::BitStream bs; + bs.Write((MessageID)ID_INCOMPATIBLE_PROTOCOL_VERSION); + bs.Write((unsigned char)RAKNET_PROTOCOL_VERSION); + bs.WriteAlignedBytes((const unsigned char*) OFFLINE_MESSAGE_DATA_ID, sizeof(OFFLINE_MESSAGE_DATA_ID)); + bs.Write(rakPeer->GetGuidFromSystemAddress(UNASSIGNED_SYSTEM_ADDRESS)); + + for (i=0; i < rakPeer->messageHandlerList.Size(); i++) + rakPeer->messageHandlerList[i]->OnDirectSocketSend((char*)bs.GetData(), bs.GetNumberOfBitsUsed(), systemAddress); + + SocketLayer::Instance()->SendTo( rakNetSocket->s, (char*)bs.GetData(), bs.GetNumberOfBytesUsed(), systemAddress.binaryAddress, systemAddress.port, rakNetSocket->remotePortRakNetWasStartedOn_PS3 ); + return true; + } + + for (i=0; i < rakPeer->messageHandlerList.Size(); i++) + rakPeer->messageHandlerList[i]->OnDirectSocketReceive(data, length*8, systemAddress); + + RakNetGUID guid; + RakNet::BitStream bsOut; + RakNet::BitStream bs((unsigned char*) data, length, false); + bs.IgnoreBytes(sizeof(MessageID)*2); + bs.Read(guid); + bs.AlignReadToByteBoundary(); + bs.IgnoreBytes(sizeof(OFFLINE_MESSAGE_DATA_ID)); + SystemAddress bindingAddress; + bs.Read(bindingAddress); + + RakPeer::RemoteSystemStruct *rssFromSA = rakPeer->GetRemoteSystemFromSystemAddress( systemAddress, true, true ); + bool IPAddrInUse = rssFromSA != 0 && rssFromSA->isActive; + RakPeer::RemoteSystemStruct *rssFromGuid = rakPeer->GetRemoteSystemFromGUID(guid, true); + bool GUIDInUse = rssFromGuid != 0 && rssFromGuid->isActive; + + // IPAddrInUse, GuidInUse, outcome + // TRUE, , TRUE , ID_OPEN_CONNECTION_REPLY if they are the same, else ID_ALREADY_CONNECTED + // FALSE, , TRUE , ID_ALREADY_CONNECTED (someone else took this guid) + // TRUE, , FALSE , ID_ALREADY_CONNECTED (silently disconnected, restarted rakNet) + // FALSE , FALSE , Allow connection + + int outcome; + if (IPAddrInUse & GUIDInUse) + { + if (rssFromSA==rssFromGuid && rssFromSA->connectMode==RakPeer::RemoteSystemStruct::UNVERIFIED_SENDER) + { + // ID_OPEN_CONNECTION_REPLY if they are the same + outcome=1; + } + else + { + // ID_ALREADY_CONNECTED (restarted raknet, connected again from same ip, plus someone else took this guid) + outcome=2; + } + } + else if (IPAddrInUse==false && GUIDInUse==true) + { + // ID_ALREADY_CONNECTED (someone else took this guid) + outcome=3; + } + else if (IPAddrInUse==true && GUIDInUse==false) + { + // ID_ALREADY_CONNECTED (silently disconnected, restarted rakNet) + outcome=4; + } + else + { + // Allow connection + outcome=0; + } + + if (outcome==1) + { + // Duplicate connection request packet + bsOut.Write((MessageID)ID_OPEN_CONNECTION_REPLY); + bsOut.WriteAlignedBytes((const unsigned char*) OFFLINE_MESSAGE_DATA_ID, sizeof(OFFLINE_MESSAGE_DATA_ID)); + bsOut.Write(rakPeer->GetGuidFromSystemAddress(UNASSIGNED_SYSTEM_ADDRESS)); + bsOut.Write(systemAddress); + bsOut.PadWithZeroToByteLength(length); // Pad to the same MTU + for (i=0; i < rakPeer->messageHandlerList.Size(); i++) + rakPeer->messageHandlerList[i]->OnDirectSocketSend((const char*) bsOut.GetData(), bsOut.GetNumberOfBitsUsed(), systemAddress); + SocketLayer::SetDoNotFragment(rakNetSocket->s, 1); + SocketLayer::Instance()->SendTo( rakNetSocket->s, (const char*) bsOut.GetData(), bsOut.GetNumberOfBytesUsed(), systemAddress.binaryAddress, systemAddress.port, rakNetSocket->remotePortRakNetWasStartedOn_PS3 ); + SocketLayer::SetDoNotFragment(rakNetSocket->s, 0); + return true; + } + else if (outcome!=0) + { + bsOut.Write((MessageID)ID_ALREADY_CONNECTED); + bsOut.WriteAlignedBytes((const unsigned char*) OFFLINE_MESSAGE_DATA_ID, sizeof(OFFLINE_MESSAGE_DATA_ID)); + bsOut.Write(guid); + for (i=0; i < rakPeer->messageHandlerList.Size(); i++) + rakPeer->messageHandlerList[i]->OnDirectSocketSend((const char*) bsOut.GetData(), bsOut.GetNumberOfBitsUsed(), systemAddress); + SocketLayer::Instance()->SendTo( rakNetSocket->s, (const char*) bsOut.GetData(), bsOut.GetNumberOfBytesUsed(), systemAddress.binaryAddress, systemAddress.port, rakNetSocket->remotePortRakNetWasStartedOn_PS3 ); + + return true; + } + + if (rakPeer->AllowIncomingConnections()==false) + { + bsOut.Write((MessageID)ID_NO_FREE_INCOMING_CONNECTIONS); + bsOut.WriteAlignedBytes((const unsigned char*) OFFLINE_MESSAGE_DATA_ID, sizeof(OFFLINE_MESSAGE_DATA_ID)); + bsOut.Write(guid); + for (i=0; i < rakPeer->messageHandlerList.Size(); i++) + rakPeer->messageHandlerList[i]->OnDirectSocketSend((const char*) bsOut.GetData(), bsOut.GetNumberOfBitsUsed(), systemAddress); + SocketLayer::Instance()->SendTo( rakNetSocket->s, (const char*) bsOut.GetData(), bsOut.GetNumberOfBytesUsed(), systemAddress.binaryAddress, systemAddress.port, rakNetSocket->remotePortRakNetWasStartedOn_PS3 ); + + return true; + } + + bool thisIPConnectedRecently=false; + rakPeer->AssignSystemAddressToRemoteSystemList(systemAddress, RakPeer::RemoteSystemStruct::UNVERIFIED_SENDER, rakNetSocket, &thisIPConnectedRecently, bindingAddress, length+UDP_HEADER_SIZE, guid, rcvPort); + + if (thisIPConnectedRecently==true) + { + bsOut.Write((MessageID)ID_IP_RECENTLY_CONNECTED); + bsOut.WriteAlignedBytes((const unsigned char*) OFFLINE_MESSAGE_DATA_ID, sizeof(OFFLINE_MESSAGE_DATA_ID)); + bsOut.Write(guid); + for (i=0; i < rakPeer->messageHandlerList.Size(); i++) + rakPeer->messageHandlerList[i]->OnDirectSocketSend((const char*) bsOut.GetData(), bsOut.GetNumberOfBitsUsed(), systemAddress); + SocketLayer::Instance()->SendTo( rakNetSocket->s, (const char*) bsOut.GetData(), bsOut.GetNumberOfBytesUsed(), systemAddress.binaryAddress, systemAddress.port, rakNetSocket->remotePortRakNetWasStartedOn_PS3 ); + + return true; + } + + bsOut.Write((MessageID)ID_OPEN_CONNECTION_REPLY); + bsOut.WriteAlignedBytes((const unsigned char*) OFFLINE_MESSAGE_DATA_ID, sizeof(OFFLINE_MESSAGE_DATA_ID)); + bsOut.Write(rakPeer->GetGuidFromSystemAddress(UNASSIGNED_SYSTEM_ADDRESS)); + bsOut.Write(systemAddress); + bsOut.PadWithZeroToByteLength(length); // Pad to the same MTU + for (i=0; i < rakPeer->messageHandlerList.Size(); i++) + rakPeer->messageHandlerList[i]->OnDirectSocketSend((const char*) bsOut.GetData(), bsOut.GetNumberOfBitsUsed(), systemAddress); + SocketLayer::SetDoNotFragment(rakNetSocket->s, 1); + SocketLayer::Instance()->SendTo( rakNetSocket->s, (const char*) bsOut.GetData(), bsOut.GetNumberOfBytesUsed(), systemAddress.binaryAddress, systemAddress.port, rakNetSocket->remotePortRakNetWasStartedOn_PS3 ); + SocketLayer::SetDoNotFragment(rakNetSocket->s, 0); + + + } + return true; + } + + return false; +} +// -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +void ProcessNetworkPacket( const SystemAddress systemAddress, const char *data, const int length, RakPeer *rakPeer, RakNetTimeUS timeRead ) +{ + ProcessNetworkPacket(systemAddress,data,length,rakPeer,rakPeer->socketList[0],timeRead); +} +void ProcessNetworkPacket( const SystemAddress systemAddress, const char *data, const int length, RakPeer *rakPeer, RakNetSmartPtr rakNetSocket, RakNetTimeUS timeRead ) +{ + RakAssert(systemAddress.port); + bool isOfflineMessage; + if (ProcessOfflineNetworkPacket(systemAddress, data, length, rakPeer, rakNetSocket, &isOfflineMessage, timeRead)) + { + return; + } + + struct sockaddr_in saRemote; + unsigned short rcvPort = 0; + socklen_t saLength = sizeof(saRemote); + if (getsockname(rakNetSocket->s, (sockaddr*)&saRemote, &saLength) == 0) + rcvPort = (unsigned short)ntohs(saRemote.sin_port); + + Packet *packet; + RakPeer::RemoteSystemStruct *remoteSystem; + + // See if this datagram came from a connected system + remoteSystem = rakPeer->GetRemoteSystemFromSystemAddress( systemAddress, true, true ); + if ( remoteSystem ) + { + if (remoteSystem->connectMode==RakPeer::RemoteSystemStruct::SET_ENCRYPTION_ON_MULTIPLE_16_BYTE_PACKET && (length & 15)==0) // & 15 = mod 16 + { + // Test the key before setting it + unsigned int newLength; + char output[ MAXIMUM_MTU_SIZE ]; + DataBlockEncryptor testEncryptor; + testEncryptor.SetKey(remoteSystem->AESKey); + //if ( testEncryptor.Decrypt( ( unsigned char* ) data, length, (unsigned char*) output,&newLength ) == true ) + if ( testEncryptor.Decrypt( ( unsigned char* ) data, length, (unsigned char*) output, &newLength ) == true ) + remoteSystem->reliabilityLayer.SetEncryptionKey( remoteSystem->AESKey); + } + + // Handle regular incoming data + // HandleSocketReceiveFromConnectedPlayer is only safe to be called from the same thread as Update, which is this thread + if ( isOfflineMessage==false) + { + if (remoteSystem->reliabilityLayer.HandleSocketReceiveFromConnectedPlayer( + data, length, systemAddress, rakPeer->messageHandlerList, remoteSystem->MTUSize, + rakNetSocket->s, &rnr, rakNetSocket->remotePortRakNetWasStartedOn_PS3, timeRead) == false) + { + // These kinds of packets may have been duplicated and incorrectly determined to be + // cheat packets. Anything else really is a cheat packet + if ( !( + ( (unsigned char)data[0] == ID_CONNECTION_BANNED ) || + ( (unsigned char)data[0] == ID_OPEN_CONNECTION_REQUEST ) || + ( (unsigned char)data[0] == ID_OPEN_CONNECTION_REPLY ) || + ( (unsigned char)data[0] == ID_CONNECTION_ATTEMPT_FAILED ) || + ( (unsigned char)data[0] == ID_IP_RECENTLY_CONNECTED ) || + ( (unsigned char)data[0] == ID_INCOMPATIBLE_PROTOCOL_VERSION )) + ) + { + // Unknown message. Could be caused by old out of order stuff from unconnected or no longer connected systems, etc. + packet=rakPeer->AllocPacket(1, __FILE__, __LINE__); + packet->data[ 0 ] = ID_MODIFIED_PACKET; + packet->bitSize = sizeof( char ) * 8; + packet->systemAddress = systemAddress; + packet->systemAddress.systemIndex = ( SystemIndex ) rakPeer->GetIndexFromSystemAddress( systemAddress, true ); + packet->guid=remoteSystem->guid; + packet->guid.systemIndex=packet->systemAddress.systemIndex; + packet->rcvPort = rcvPort; + rakPeer->AddPacketToProducer(packet); + } + } + } + } +} +// -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +unsigned int RakPeer::GenerateSeedFromGuid(void) +{ + /* + // Construct a random seed based on the initial guid value, and the last digits of the difference to each subsequent number + // This assumes that only the last 3 bits of each guidId integer has a meaningful amount of randomness between it and the prior number + unsigned int t = guid.g[0]; + unsigned int i; + for (i=1; i < sizeof(guid.g) / sizeof(guid.g[0]); i++) + { + unsigned int diff = guid.g[i]-guid.g[i-1]; + unsigned int diff3Bits = diff & 0x0007; + diff3Bits <<= 29; + diff3Bits >>= (i-1)*3; + t ^= diff3Bits; + } + + return t; + */ + return (unsigned int) ((myGuid.g >> 32) ^ myGuid.g); +} +// -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +void RakPeer::DerefAllSockets(void) +{ + unsigned int i; + for (i=0; i < socketList.Size(); i++) + { + socketList[i].SetNull(); + } + socketList.Clear(false, __FILE__, __LINE__); +} +// -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +unsigned int RakPeer::GetRakNetSocketFromUserConnectionSocketIndex(unsigned int userIndex) const +{ + unsigned int i; + for (i=0; i < socketList.Size(); i++) + { + if (socketList[i]->userConnectionSocketIndex==userIndex) + return i; + } + RakAssert("GetRakNetSocketFromUserConnectionSocketIndex failed" && 0); + return (unsigned int) -1; +} +// -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +bool RakPeer::RunUpdateCycle( void ) +{ + RakPeer::RemoteSystemStruct * remoteSystem; + unsigned remoteSystemIndex; + Packet *packet; + RakNetTime ping, lastPing; + // int currentSentBytes,currentReceivedBytes; +// unsigned numberOfBytesUsed; + BitSize_t numberOfBitsUsed; + //SystemAddress authoritativeClientSystemAddress; + BitSize_t bitSize; + unsigned int byteSize; + unsigned char *data; + RakNetTimeUS timeNS; + RakNetTime timeMS; + SystemAddress systemAddress; + BufferedCommandStruct *bcs; + bool callerDataAllocationUsed; + RakNetStatistics *rnss; + + /* + int errorCode; + int gotData; + unsigned connectionSocketIndex; + for (connectionSocketIndex=0; connectionSocketIndex < socketList.Size(); connectionSocketIndex++) + { + do + { + gotData = SocketLayer::Instance()->RecvFrom( socketList[connectionSocketIndex]->s, this, &errorCode, socketList[connectionSocketIndex], socketList[connectionSocketIndex]->remotePortRakNetWasStartedOn_PS3 ); + + if ( gotData == -1 ) + { +#ifdef _WIN32 + if ( errorCode == WSAECONNRESET ) + { + gotData=false; + } + else + if ( errorCode != 0 && endThreads == false ) + { +#ifdef _DO_PRINTF + RAKNET_DEBUG_PRINTF( "Server RecvFrom critical failure!\n" ); +#endif + // Some kind of critical error + // peer->isRecvfromThreadActive=false; + endThreads = true; + Shutdown( 0, 0 ); + return false; + } + +#else + if ( errorCode == -1 ) + { + // isRecvfromThreadActive=false; + endThreads = true; + Shutdown( 0 ); + return false; + } +#endif + } + + if ( endThreads ) + return false; + } + while ( gotData>0 ); // Read until there is nothing left + } + */ + + // This is here so RecvFromBlocking actually gets data from the same thread + if (SocketLayer::Instance()->GetSocketLayerOverride()) + { + SystemAddress sender; + char dataOut[ MAXIMUM_MTU_SIZE ]; + int len = SocketLayer::Instance()->GetSocketLayerOverride()->RakNetRecvFrom(socketList[0]->s,this,dataOut,&sender,true); + if (len>0) + { + ProcessNetworkPacket( sender, dataOut, len, this, socketList[0], RakNet::GetTimeUS() ); + return 1; + } + } + + unsigned int socketListIndex; + RakPeer::RecvFromStruct *recvFromStruct; + while ((recvFromStruct=bufferedPackets.PopInaccurate())!=0) + { + for (socketListIndex=0; socketListIndex < socketList.Size(); socketListIndex++) + { + if (socketList[socketListIndex]->s==recvFromStruct->s) + break; + } + if (socketListIndex!=socketList.Size()) + ProcessNetworkPacket(recvFromStruct->systemAddress, recvFromStruct->data, recvFromStruct->bytesRead, this, socketList[socketListIndex], recvFromStruct->timeRead); + bufferedPackets.Deallocate(recvFromStruct, __FILE__,__LINE__); + } + + + timeNS=0; + timeMS=0; + +#ifdef _RAKNET_THREADSAFE + while ((bcs=bufferedCommands.PopInaccurate())!=0) +#else + // Process all the deferred user thread Send and connect calls + while ((bcs=bufferedCommands.ReadLock())!=0) +#endif + { + if (bcs->command==BufferedCommandStruct::BCS_SEND) + { + // GetTime is a very slow call so do it once and as late as possible + if (timeNS==0) + { + timeNS = RakNet::GetTimeNS(); + timeMS = (RakNetTime)(timeNS/(RakNetTimeUS)1000); + } + + callerDataAllocationUsed=SendImmediate((char*)bcs->data, bcs->numberOfBitsToSend, bcs->priority, bcs->reliability, bcs->orderingChannel, bcs->systemIdentifier, bcs->broadcast, true, timeNS, bcs->receipt); + if ( callerDataAllocationUsed==false ) + rakFree_Ex(bcs->data, __FILE__, __LINE__ ); + + // Set the new connection state AFTER we call sendImmediate in case we are setting it to a disconnection state, which does not allow further sends + if (bcs->connectionMode!=RemoteSystemStruct::NO_ACTION ) + { + remoteSystem=GetRemoteSystem( bcs->systemIdentifier, true, true ); + if (remoteSystem) + remoteSystem->connectMode=bcs->connectionMode; + } + } + else if (bcs->command==BufferedCommandStruct::BCS_CLOSE_CONNECTION) + { + CloseConnectionInternal(bcs->systemIdentifier, false, true, bcs->orderingChannel, bcs->priority); + } + else if (bcs->command==BufferedCommandStruct::BCS_CHANGE_SYSTEM_ADDRESS) + { + // Reroute + RakPeer::RemoteSystemStruct *rssFromGuid = GetRemoteSystem(bcs->systemIdentifier.rakNetGuid,true,true); + if (rssFromGuid!=0) + { + unsigned int existingSystemIndex = GetRemoteSystemIndex(rssFromGuid->systemAddress); + ReferenceRemoteSystem(bcs->systemIdentifier.systemAddress, existingSystemIndex); + } + } + else if (bcs->command==BufferedCommandStruct::BCS_GET_SOCKET) + { + SocketQueryOutput *sqo; + if (bcs->systemIdentifier.IsUndefined()) + { +#ifdef _RAKNET_THREADSAFE + sqo = socketQueryOutput.Allocate( __FILE__, __LINE__ ); + sqo->sockets=socketList; + socketQueryOutput.Push(sqo); +#else + sqo = socketQueryOutput.WriteLock(); + sqo->sockets=socketList; + socketQueryOutput.WriteUnlock(); +#endif + } + else + { + remoteSystem=GetRemoteSystem( bcs->systemIdentifier, true, true ); +#ifdef _RAKNET_THREADSAFE + sqo = socketQueryOutput.Allocate( __FILE__, __LINE__ ); +#else + sqo = socketQueryOutput.WriteLock(); +#endif + sqo->sockets.Clear(false, __FILE__, __LINE__); + if (remoteSystem) + { + sqo->sockets.Push(remoteSystem->rakNetSocket, __FILE__, __LINE__ ); + } + else + { + // Leave empty smart pointer + } +#ifdef _RAKNET_THREADSAFE + socketQueryOutput.Push(sqo); +#else + socketQueryOutput.WriteUnlock(); +#endif + } + + } + +#ifdef _DEBUG + bcs->data=0; +#endif + +#ifdef _RAKNET_THREADSAFE + bufferedCommands.Deallocate(bcs, __FILE__,__LINE__); +#else + bufferedCommands.ReadUnlock(); +#endif + } + + if (requestedConnectionQueue.IsEmpty()==false) + { + if (timeNS==0) + { + timeNS = RakNet::GetTimeNS(); + timeMS = (RakNetTime)(timeNS/(RakNetTimeUS)1000); + } + + bool condition1, condition2; + RequestedConnectionStruct *rcs; + unsigned requestedConnectionQueueIndex=0; + requestedConnectionQueueMutex.Lock(); + while (requestedConnectionQueueIndex < requestedConnectionQueue.Size()) + { + rcs = requestedConnectionQueue[requestedConnectionQueueIndex]; + requestedConnectionQueueMutex.Unlock(); + if (rcs->nextRequestTime < timeMS) + { + condition1=rcs->requestsMade==rcs->sendConnectionAttemptCount+1; + condition2=(bool)((rcs->systemAddress==UNASSIGNED_SYSTEM_ADDRESS)==1); + // If too many requests made or a hole then remove this if possible, otherwise invalidate it + if (condition1 || condition2) + { + if (rcs->data) + { + rakFree_Ex(rcs->data, __FILE__, __LINE__ ); + rcs->data=0; + } + + if (condition1 && !condition2 && rcs->actionToTake==RequestedConnectionStruct::CONNECT) + { + // Tell user of connection attempt failed + packet=AllocPacket(sizeof( char ), __FILE__, __LINE__); + packet->data[ 0 ] = ID_CONNECTION_ATTEMPT_FAILED; // Attempted a connection and couldn't + packet->bitSize = ( sizeof( char ) * 8); + packet->systemAddress = rcs->systemAddress; + AddPacketToProducer(packet); + } + + RakNet::OP_DELETE(rcs,__FILE__,__LINE__); + + requestedConnectionQueueMutex.Lock(); + for (unsigned int k=0; k < requestedConnectionQueue.Size(); k++) + { + if (requestedConnectionQueue[k]==rcs) + { + requestedConnectionQueue.RemoveAtIndex(k); + break; + } + } + requestedConnectionQueueMutex.Unlock(); + } + else + { + + int MTUSizeIndex = rcs->requestsMade / (rcs->sendConnectionAttemptCount/NUM_MTU_SIZES); + if (MTUSizeIndex>=NUM_MTU_SIZES) + MTUSizeIndex=NUM_MTU_SIZES-1; + rcs->requestsMade++; + rcs->nextRequestTime=timeMS+rcs->timeBetweenSendConnectionAttemptsMS; + // char c[MAXIMUM_MTU_SIZE]; + // c[0] = ID_OPEN_CONNECTION_REQUEST; + // c[1] = RAKNET_PROTOCOL_VERSION; + + RakNet::BitStream bitStream; + bitStream.Write((MessageID)ID_OPEN_CONNECTION_REQUEST); + bitStream.Write((MessageID)RAKNET_PROTOCOL_VERSION); + bitStream.Write(myGuid); + bitStream.WriteAlignedBytes((const unsigned char*) OFFLINE_MESSAGE_DATA_ID, sizeof(OFFLINE_MESSAGE_DATA_ID)); + bitStream.Write(rcs->systemAddress); + // Pad out to MTU test size + bitStream.PadWithZeroToByteLength(mtuSizes[MTUSizeIndex]-UDP_HEADER_SIZE); + +// bool isProperOfflineMessage=memcmp(bitStream.GetData()+sizeof(MessageID)*2 + RakNetGUID::size(), OFFLINE_MESSAGE_DATA_ID, sizeof(OFFLINE_MESSAGE_DATA_ID))==0; +// RakAssert(isProperOfflineMessage); + + char str[256]; + rcs->systemAddress.ToString(true,str); + + //RAKNET_DEBUG_PRINTF("%i:IOCR, ", __LINE__); + + unsigned i; + for (i=0; i < messageHandlerList.Size(); i++) + messageHandlerList[i]->OnDirectSocketSend((const char*) bitStream.GetData(), bitStream.GetNumberOfBitsUsed(), rcs->systemAddress); + + if (rcs->socket.IsNull()) + { + SocketLayer::SetDoNotFragment(socketList[rcs->socketIndex]->s, 1); + if (SocketLayer::Instance()->SendTo( socketList[rcs->socketIndex]->s, (const char*) bitStream.GetData(), bitStream.GetNumberOfBytesUsed(), rcs->systemAddress.binaryAddress, rcs->systemAddress.port, socketList[rcs->socketIndex]->remotePortRakNetWasStartedOn_PS3 )==-10040) + { + // Don't use this MTU size again + rcs->requestsMade = (unsigned char) ((MTUSizeIndex + 1) * (rcs->sendConnectionAttemptCount/NUM_MTU_SIZES)); + rcs->nextRequestTime=timeMS; + } + SocketLayer::SetDoNotFragment(socketList[rcs->socketIndex]->s, 0); + } + else + { + SocketLayer::SetDoNotFragment(rcs->socket->s, 1); + if (SocketLayer::Instance()->SendTo( rcs->socket->s, (const char*) bitStream.GetData(), bitStream.GetNumberOfBytesUsed(), rcs->systemAddress.binaryAddress, rcs->systemAddress.port, socketList[rcs->socketIndex]->remotePortRakNetWasStartedOn_PS3 )==-10040) + { + // Don't use this MTU size again + rcs->requestsMade = (unsigned char) ((MTUSizeIndex + 1) * (rcs->sendConnectionAttemptCount/NUM_MTU_SIZES)); + rcs->nextRequestTime=timeMS; + } + SocketLayer::SetDoNotFragment(socketList[rcs->socketIndex]->s, 0); + } + // printf("ID_OPEN_CONNECTION_REQUEST\n"); + + requestedConnectionQueueIndex++; + } + } + else + requestedConnectionQueueIndex++; + + requestedConnectionQueueMutex.Lock(); + } + requestedConnectionQueueMutex.Unlock(); + } + + // remoteSystemList in network thread + for ( remoteSystemIndex = 0; remoteSystemIndex < maximumNumberOfPeers; ++remoteSystemIndex ) + //for ( remoteSystemIndex = 0; remoteSystemIndex < remoteSystemListSize; ++remoteSystemIndex ) + { + // I'm using systemAddress from remoteSystemList but am not locking it because this loop is called very frequently and it doesn't + // matter if we miss or do an extra update. The reliability layers themselves never care which player they are associated with + //systemAddress = remoteSystemList[ remoteSystemIndex ].systemAddress; + // Allow the systemAddress for this remote system list to change. We don't care if it changes now. + // remoteSystemList[ remoteSystemIndex ].allowSystemAddressAssigment=true; + if ( remoteSystemList[ remoteSystemIndex ].isActive ) + { + systemAddress = remoteSystemList[ remoteSystemIndex ].systemAddress; + //rcvPort = remoteSystemList[ remoteSystemIndex ].rcvPort; + RakAssert(systemAddress!=UNASSIGNED_SYSTEM_ADDRESS); + + // Found an active remote system + remoteSystem = remoteSystemList + remoteSystemIndex; + // Update is only safe to call from the same thread that calls HandleSocketReceiveFromConnectedPlayer, + // which is this thread + + if (timeNS==0) + { + timeNS = RakNet::GetTimeNS(); + timeMS = (RakNetTime)(timeNS/(RakNetTimeUS)1000); + //RAKNET_DEBUG_PRINTF("timeNS = %I64i timeMS=%i\n", timeNS, timeMS); + } + + + if (timeMS > remoteSystem->lastReliableSend && timeMS-remoteSystem->lastReliableSend > defaultTimeoutTime/2 && remoteSystem->connectMode==RemoteSystemStruct::CONNECTED) + { + // If no reliable packets are waiting for an ack, do a one byte reliable send so that disconnections are noticed + RakNetStatistics rakNetStatistics; + rnss=remoteSystem->reliabilityLayer.GetStatistics(&rakNetStatistics); + if (rnss->messagesInResendBuffer==0) + { + PingInternal( systemAddress, true, RELIABLE ); + /* + unsigned char keepAlive=ID_DETECT_LOST_CONNECTIONS; + SendImmediate((char*)&keepAlive,8,LOW_PRIORITY, RELIABLE, 0, remoteSystem->systemAddress, false, false, timeNS); + */ + //remoteSystem->lastReliableSend=timeMS+remoteSystem->reliabilityLayer.GetTimeoutTime(); + remoteSystem->lastReliableSend=timeMS; + } + } + + remoteSystem->reliabilityLayer.Update( remoteSystem->rakNetSocket->s, systemAddress, remoteSystem->MTUSize, timeNS, maxOutgoingBPS, messageHandlerList, &rnr, remoteSystem->rakNetSocket->remotePortRakNetWasStartedOn_PS3 ); // systemAddress only used for the internet simulator test + + // Check for failure conditions + if ( remoteSystem->reliabilityLayer.IsDeadConnection() || + ((remoteSystem->connectMode==RemoteSystemStruct::DISCONNECT_ASAP || remoteSystem->connectMode==RemoteSystemStruct::DISCONNECT_ASAP_SILENTLY) && remoteSystem->reliabilityLayer.IsOutgoingDataWaiting()==false) || + (remoteSystem->connectMode==RemoteSystemStruct::DISCONNECT_ON_NO_ACK && (remoteSystem->reliabilityLayer.AreAcksWaiting()==false || remoteSystem->reliabilityLayer.AckTimeout(timeMS)==true)) || + (( + (remoteSystem->connectMode==RemoteSystemStruct::REQUESTED_CONNECTION || + remoteSystem->connectMode==RemoteSystemStruct::HANDLING_CONNECTION_REQUEST || + remoteSystem->connectMode==RemoteSystemStruct::UNVERIFIED_SENDER || + remoteSystem->connectMode==RemoteSystemStruct::SET_ENCRYPTION_ON_MULTIPLE_16_BYTE_PACKET) + && timeMS > remoteSystem->connectionTime && timeMS - remoteSystem->connectionTime > 10000)) + ) + { + // RAKNET_DEBUG_PRINTF("timeMS=%i remoteSystem->connectionTime=%i\n", timeMS, remoteSystem->connectionTime ); + + // Failed. Inform the user? + // TODO - RakNet 4.0 - Return a different message identifier for DISCONNECT_ASAP_SILENTLY and DISCONNECT_ASAP than for DISCONNECT_ON_NO_ACK + // The first two mean we called CloseConnection(), the last means the other system sent us ID_DISCONNECTION_NOTIFICATION + if (remoteSystem->connectMode==RemoteSystemStruct::CONNECTED || remoteSystem->connectMode==RemoteSystemStruct::REQUESTED_CONNECTION + || remoteSystem->connectMode==RemoteSystemStruct::DISCONNECT_ASAP_SILENTLY || remoteSystem->connectMode==RemoteSystemStruct::DISCONNECT_ASAP || remoteSystem->connectMode==RemoteSystemStruct::DISCONNECT_ON_NO_ACK) + { + +// RakNet::BitStream undeliveredMessages; +// remoteSystem->reliabilityLayer.GetUndeliveredMessages(&undeliveredMessages,remoteSystem->MTUSize); + +// packet=AllocPacket(sizeof( char ) + undeliveredMessages.GetNumberOfBytesUsed()); + packet=AllocPacket(sizeof( char ), __FILE__, __LINE__); + if (remoteSystem->connectMode==RemoteSystemStruct::REQUESTED_CONNECTION) + packet->data[ 0 ] = ID_CONNECTION_ATTEMPT_FAILED; // Attempted a connection and couldn't + else if (remoteSystem->connectMode==RemoteSystemStruct::CONNECTED) + packet->data[ 0 ] = ID_CONNECTION_LOST; // DeadConnection + else + packet->data[ 0 ] = ID_DISCONNECTION_NOTIFICATION; // DeadConnection + +// memcpy(packet->data+1, undeliveredMessages.GetData(), undeliveredMessages.GetNumberOfBytesUsed()); + + packet->guid = remoteSystem->guid; + packet->systemAddress = systemAddress; + packet->systemAddress.systemIndex = ( SystemIndex ) remoteSystemIndex; + packet->guid.systemIndex=packet->systemAddress.systemIndex; + packet->rcvPort = remoteSystem->rcvPort; + + AddPacketToProducer(packet); + } + // else connection shutting down, don't bother telling the user + +#ifdef _DO_PRINTF + RAKNET_DEBUG_PRINTF("Connection dropped for player %i:%i\n", systemAddress.binaryAddress, systemAddress.port); +#endif + CloseConnectionInternal( systemAddress, false, true, 0, LOW_PRIORITY ); + continue; + } + + // Did the reliability layer detect a modified packet? + if ( remoteSystem->reliabilityLayer.IsCheater() ) + { + packet=AllocPacket(sizeof(char), __FILE__, __LINE__); + packet->bitSize=8; + packet->data[ 0 ] = (MessageID)ID_MODIFIED_PACKET; + packet->systemAddress = systemAddress; + packet->systemAddress.systemIndex = ( SystemIndex ) remoteSystemIndex; + packet->guid = remoteSystem->guid; + packet->guid.systemIndex=packet->systemAddress.systemIndex; + packet->rcvPort = remoteSystem->rcvPort; + AddPacketToProducer(packet); + continue; + } + + // Ping this guy if it is time to do so + if ( remoteSystem->connectMode==RemoteSystemStruct::CONNECTED && timeMS > remoteSystem->nextPingTime && ( occasionalPing || remoteSystem->lowestPing == (unsigned short)-1 ) ) + { + remoteSystem->nextPingTime = timeMS + 5000; + PingInternal( systemAddress, true, UNRELIABLE ); + + // Update again immediately after this tick so the ping goes out right away + quitAndDataEvents.SetEvent(); + } + + // Find whoever has the lowest player ID + //if (systemAddress < authoritativeClientSystemAddress) + // authoritativeClientSystemAddress=systemAddress; + + // Does the reliability layer have any packets waiting for us? + // To be thread safe, this has to be called in the same thread as HandleSocketReceiveFromConnectedPlayer + bitSize = remoteSystem->reliabilityLayer.Receive( &data ); + + while ( bitSize > 0 ) + { + // These types are for internal use and should never arrive from a network packet + if (data[0]==ID_CONNECTION_ATTEMPT_FAILED || data[0]==ID_MODIFIED_PACKET) + { + RakAssert(0); + continue; + } + + // Put the input through compression if necessary + if ( inputTree ) + { + RakNet::BitStream dataBitStream( MAXIMUM_MTU_SIZE ); + // Since we are decompressing input, we need to copy to a bitstream, decompress, then copy back to a probably + // larger data block. It's slow, but the user should have known that anyway + dataBitStream.Reset(); + dataBitStream.WriteAlignedBytes( ( unsigned char* ) data, BITS_TO_BYTES( bitSize ) ); + rawBytesReceived += (unsigned int) dataBitStream.GetNumberOfBytesUsed(); + +// numberOfBytesUsed = dataBitStream.GetNumberOfBytesUsed(); + numberOfBitsUsed = dataBitStream.GetNumberOfBitsUsed(); + //rawBytesReceived += numberOfBytesUsed; + // Decompress the input data. + + if (numberOfBitsUsed>0) + { + unsigned char *dataCopy = (unsigned char*) rakMalloc_Ex( (unsigned int) dataBitStream.GetNumberOfBytesUsed(), __FILE__, __LINE__ ); + memcpy( dataCopy, dataBitStream.GetData(), (size_t) dataBitStream.GetNumberOfBytesUsed() ); + dataBitStream.Reset(); + inputTree->DecodeArray( dataCopy, numberOfBitsUsed, &dataBitStream ); + compressedBytesReceived += (unsigned int) dataBitStream.GetNumberOfBytesUsed(); + rakFree_Ex(dataCopy, __FILE__, __LINE__ ); + + byteSize = (unsigned int) dataBitStream.GetNumberOfBytesUsed(); + + if ( byteSize > BITS_TO_BYTES( bitSize ) ) // Probably the case - otherwise why decompress? + { + rakFree_Ex(data, __FILE__, __LINE__ ); + data = (unsigned char*) rakMalloc_Ex( (size_t) byteSize, __FILE__, __LINE__ ); + } + bitSize = (BitSize_t) dataBitStream.GetNumberOfBitsUsed(); + memcpy( data, dataBitStream.GetData(), byteSize ); + } + else + byteSize=0; + } + else + // Fast and easy - just use the data that was returned + byteSize = (unsigned int) BITS_TO_BYTES( bitSize ); + + // For unknown senders we only accept a few specific packets + if (remoteSystem->connectMode==RemoteSystemStruct::UNVERIFIED_SENDER) + { + if ( (unsigned char)(data)[0] == ID_CONNECTION_REQUEST ) + { + // Removeme + if (mySystemAddress[0].port==60001 && systemAddress.port==60000) + { + printf("Got ID_CONNECTION_REQUEST\n"); + } + + ParseConnectionRequestPacket(remoteSystem, systemAddress, (const char*)data, byteSize); + rakFree_Ex(data, __FILE__, __LINE__ ); + } + else + { + CloseConnectionInternal( systemAddress, false, true, 0, LOW_PRIORITY ); +#ifdef _DO_PRINTF + RAKNET_DEBUG_PRINTF("Temporarily banning %i:%i for sending nonsense data\n", systemAddress.binaryAddress, systemAddress.port); +#endif +#if !defined(_XBOX) && !defined(X360) + char str1[64]; + systemAddress.ToString(false, str1); + AddToBanList(str1, remoteSystem->reliabilityLayer.GetTimeoutTime()); +#endif + + rakFree_Ex(data, __FILE__, __LINE__ ); + } + } + else + { + // However, if we are connected we still take a connection request in case both systems are trying to connect to each other + // at the same time + if ( (unsigned char)(data)[0] == ID_CONNECTION_REQUEST ) + { + // Removeme + if (mySystemAddress[0].port==60001 && systemAddress.port==60000) + { + printf("Got ID_CONNECTION_REQUEST\n"); + } + + // 04/27/06 This is wrong. With cross connections, we can both have initiated the connection are in state REQUESTED_CONNECTION + // 04/28/06 Downgrading connections from connected will close the connection due to security at ((remoteSystem->connectMode!=RemoteSystemStruct::CONNECTED && time > remoteSystem->connectionTime && time - remoteSystem->connectionTime > 10000)) + if (remoteSystem->connectMode==RemoteSystemStruct::REQUESTED_CONNECTION) + { + ParseConnectionRequestPacket(remoteSystem, systemAddress, (const char*)data, byteSize); + } + else + { + // Got a connection request message from someone we are already connected to. Just reply normally. + // This can happen due to race conditions with the fully connected mesh + SendConnectionRequestAccepted(remoteSystem); + } + rakFree_Ex(data, __FILE__, __LINE__ ); + } + else if ( (unsigned char) data[ 0 ] == ID_NEW_INCOMING_CONNECTION && byteSize > sizeof(unsigned char)+sizeof(unsigned int)+sizeof(unsigned short) ) + { + // Removeme +// static int count4=1; +// printf("Got ID_NEW_INCOMING_CONNECTION count=%i\n", count4++); + + if (remoteSystem->connectMode==RemoteSystemStruct::HANDLING_CONNECTION_REQUEST || + remoteSystem->connectMode==RemoteSystemStruct::REQUESTED_CONNECTION || + remoteSystem->connectMode==RemoteSystemStruct::SET_ENCRYPTION_ON_MULTIPLE_16_BYTE_PACKET) + { + // Removeme +// static int count5=1; +// printf("Processed ID_NEW_INCOMING_CONNECTION count=%i\n", count5++); + + remoteSystem->connectMode=RemoteSystemStruct::CONNECTED; + PingInternal( systemAddress, true, UNRELIABLE ); + + // Update again immediately after this tick so the ping goes out right away + quitAndDataEvents.SetEvent(); + + RakNet::BitStream inBitStream((unsigned char *) data, byteSize, false); + SystemAddress bsSystemAddress; + + inBitStream.IgnoreBits(8); + inBitStream.Read(bsSystemAddress); + for (unsigned int i=0; i < MAXIMUM_NUMBER_OF_INTERNAL_IDS; i++) + inBitStream.Read(remoteSystem->theirInternalSystemAddress[i]); + + // Overwrite the data in the packet + // NewIncomingConnectionStruct newIncomingConnectionStruct; + // RakNet::BitStream nICS_BS( data, NewIncomingConnectionStruct_Size, false ); + // newIncomingConnectionStruct.Deserialize( nICS_BS ); + + remoteSystem->myExternalSystemAddress = bsSystemAddress; + firstExternalID=bsSystemAddress; + } + else + { + // Send to game even if already connected. This could happen when connecting to 127.0.0.1 + // Ignore, already connected + // rakFree_Ex(data, __FILE__, __LINE__ ); + } + + // Send this info down to the game + packet=AllocPacket(byteSize, data, __FILE__, __LINE__); + packet->bitSize = bitSize; + packet->systemAddress = systemAddress; + packet->systemAddress.systemIndex = ( SystemIndex ) remoteSystemIndex; + packet->guid = remoteSystem->guid; + packet->guid.systemIndex=packet->systemAddress.systemIndex; + packet->rcvPort = remoteSystem->rcvPort; + AddPacketToProducer(packet); + } + else if ( (unsigned char) data[ 0 ] == ID_CONNECTED_PONG && byteSize == sizeof(unsigned char)+sizeof(RakNetTime)*2 ) + { + RakNetTime sendPingTime, sendPongTime; + + // Copy into the ping times array the current time - the value returned + // First extract the sent ping + RakNet::BitStream inBitStream( (unsigned char *) data, byteSize, false ); + //PingStruct ps; + //ps.Deserialize(psBS); + inBitStream.IgnoreBits(8); + inBitStream.Read(sendPingTime); + inBitStream.Read(sendPongTime); + + timeNS = RakNet::GetTimeNS(); // Update the time value to be accurate + timeMS = (RakNetTime)(timeNS/(RakNetTimeUS)1000); + if (timeMS > sendPingTime) + ping = timeMS - sendPingTime; + else + ping=0; + lastPing = remoteSystem->pingAndClockDifferential[ remoteSystem->pingAndClockDifferentialWriteIndex ].pingTime; + + // Ignore super high spikes in the average + if ( lastPing <= 0 || ( ( ping < ( lastPing * 3 ) ) && ping < 1200 ) ) + { + remoteSystem->pingAndClockDifferential[ remoteSystem->pingAndClockDifferentialWriteIndex ].pingTime = ( unsigned short ) ping; + // Thanks to Chris Taylor (cat02e@fsu.edu) for the improved timestamping algorithm + // Divide each integer by 2, rather than the sum by 2, to prevent overflow + remoteSystem->pingAndClockDifferential[ remoteSystem->pingAndClockDifferentialWriteIndex ].clockDifferential = sendPongTime - ( timeMS/2 + sendPingTime/2 ); + + if ( remoteSystem->lowestPing == (unsigned short)-1 || remoteSystem->lowestPing > (int) ping ) + remoteSystem->lowestPing = (unsigned short) ping; + + // Reliability layer calculates its own ping + // Most packets should arrive by the ping time. + //RakAssert(ping < 10000); // Sanity check - could hit due to negative pings causing the var to overflow + //remoteSystem->reliabilityLayer.SetPing( (unsigned short) ping ); + + if ( ++( remoteSystem->pingAndClockDifferentialWriteIndex ) == PING_TIMES_ARRAY_SIZE ) + remoteSystem->pingAndClockDifferentialWriteIndex = 0; + } + + rakFree_Ex(data, __FILE__, __LINE__ ); + } + else if ( (unsigned char)data[0] == ID_INTERNAL_PING && byteSize == sizeof(unsigned char)+sizeof(RakNetTime) ) + { + RakNet::BitStream inBitStream( (unsigned char *) data, byteSize, false ); + inBitStream.IgnoreBits(8); + RakNetTime sendPingTime; + inBitStream.Read(sendPingTime); + + RakNet::BitStream outBitStream; + outBitStream.Write((MessageID)ID_CONNECTED_PONG); + outBitStream.Write(sendPingTime); + timeMS = RakNet::GetTime(); + timeNS = RakNet::GetTimeNS(); + outBitStream.Write(timeMS); + SendImmediate( (char*)outBitStream.GetData(), outBitStream.GetNumberOfBitsUsed(), IMMEDIATE_PRIORITY, UNRELIABLE, 0, systemAddress, false, false, timeNS, 0 ); + + // Update again immediately after this tick so the ping goes out right away + quitAndDataEvents.SetEvent(); + + rakFree_Ex(data, __FILE__, __LINE__ ); + } + else if ( (unsigned char) data[ 0 ] == ID_DISCONNECTION_NOTIFICATION ) + { + // We shouldn't close the connection immediately because we need to ack the ID_DISCONNECTION_NOTIFICATION + remoteSystem->connectMode=RemoteSystemStruct::DISCONNECT_ON_NO_ACK; + rakFree_Ex(data, __FILE__, __LINE__ ); + + // AddPacketToProducer(packet); + } + else if ( (unsigned char) data[ 0 ] == ID_RPC_MAPPING ) + { + RakNet::BitStream inBitStream( (unsigned char *) data, byteSize, false ); + RPCIndex index; + char output[256]; + inBitStream.IgnoreBits(8); + stringCompressor->DecodeString(output, 255, &inBitStream); + inBitStream.ReadCompressed(index); + remoteSystem->rpcMap.AddIdentifierAtIndex((char*)output,index); + rakFree_Ex(data, __FILE__, __LINE__ ); + } +#if !defined(_XBOX) && !defined(_WIN32_WCE) && !defined(X360) + else if ( (unsigned char)(data)[0] == ID_SECURED_CONNECTION_RESPONSE && + byteSize == 1 + sizeof( uint32_t ) + sizeof( uint32_t ) * RAKNET_RSA_FACTOR_LIMBS + 20 ) + { + SecuredConnectionConfirmation( remoteSystem, (char*)data ); + rakFree_Ex(data, __FILE__, __LINE__ ); + } + else if ( (unsigned char)(data)[0] == ID_SECURED_CONNECTION_CONFIRMATION && + byteSize == 1 + 20 + sizeof( uint32_t ) * RAKNET_RSA_FACTOR_LIMBS ) + { + CSHA1 sha1; + bool confirmedHash; + //bool newRandNumber; + + confirmedHash = false; + + // Hash the SYN-Cookie + // s2c syn-cookie = SHA1_HASH(source ip address + source port + random number) + sha1.Reset(); + sha1.Update( ( unsigned char* ) & systemAddress.binaryAddress, sizeof( systemAddress.binaryAddress ) ); + sha1.Update( ( unsigned char* ) & systemAddress.port, sizeof( systemAddress.port ) ); + sha1.Update( ( unsigned char* ) & ( newRandomNumber ), 20 ); + sha1.Final(); + + //newRandNumber = false; + + // Confirm if + //syn-cookie ?= HASH(source ip address + source port + last random number) + //syn-cookie ?= HASH(source ip address + source port + current random number) + if ( memcmp( sha1.GetHash(), data + 1, 20 ) == 0 ) + { + confirmedHash = true; + } + else + { + sha1.Reset(); + sha1.Update( ( unsigned char* ) & systemAddress.binaryAddress, sizeof( systemAddress.binaryAddress ) ); + sha1.Update( ( unsigned char* ) & systemAddress.port, sizeof( systemAddress.port ) ); + sha1.Update( ( unsigned char* ) & ( oldRandomNumber ), 20 ); + sha1.Final(); + + if ( memcmp( sha1.GetHash(), data + 1, 20 ) == 0 ) + confirmedHash = true; + } + if ( confirmedHash ) + { + int i; + unsigned char AESKey[ 16 ]; + //RSA_BIT_SIZE message, encryptedMessage; + uint32_t message[RAKNET_RSA_FACTOR_LIMBS], encryptedMessage[RAKNET_RSA_FACTOR_LIMBS]; + + // On connection accept, AES key is c2s RSA_Decrypt(random number) XOR s2c syn-cookie + // Get the random number first + + memcpy( encryptedMessage, data + 1 + 20, sizeof( encryptedMessage ) ); + + // printf("enc3[0]=%i,%i\n", encryptedMessage[0], encryptedMessage[19]); + + if (RakNet::BitStream::DoEndianSwap()) + { + for (int i=0; i < RAKNET_RSA_FACTOR_LIMBS; i++) + RakNet::BitStream::ReverseBytesInPlace((unsigned char*) &encryptedMessage[i], sizeof(encryptedMessage[i])); + } + + // printf("enc4[0]=%i,%i\n", encryptedMessage[0], encryptedMessage[19]); + + // rsacrypt.decrypt( encryptedMessage, message ); + rsacrypt.decrypt( message, encryptedMessage ); + + // printf("message[0]=%i,%i\n", message[0], message[19]); + + + +// if (RakNet::BitStream::DoEndianSwap()) +// { + // The entire message is endian swapped, then just the random number +// unsigned char randomNumber[ 20 ]; +// if (RakNet::BitStream::DoEndianSwap()) +// { +// for (int i=0; i < 20; i++) +// RakNet::BitStream::ReverseBytesInPlace((unsigned char*) message[i], sizeof(message[i])); +// } +// } + + /* + // On connection accept, AES key is c2s RSA_Decrypt(random number) XOR s2c syn-cookie + // Get the random number first + #ifdef HOST_ENDIAN_IS_BIG + BSWAPCPY( (unsigned char *) encryptedMessage, (unsigned char *)(data + 1 + 20), sizeof( RSA_BIT_SIZE ) ); + #else + memcpy( encryptedMessage, data + 1 + 20, sizeof( RSA_BIT_SIZE ) ); + #endif + rsacrypt.decrypt( encryptedMessage, message ); + #ifdef HOST_ENDIAN_IS_BIG + BSWAPSELF( (unsigned char *) message, sizeof( RSA_BIT_SIZE ) ); + #endif + */ + + // Save the AES key + for ( i = 0; i < 16; i++ ) + AESKey[ i ] = data[ 1 + i ] ^ ( ( unsigned char* ) ( message ) ) [ i ]; + + // Connect this player assuming we have open slots + OnConnectionRequest( remoteSystem, AESKey, true ); + } + rakFree_Ex(data, __FILE__, __LINE__ ); + } +#endif // #if !defined(_XBOX) && !defined(_WIN32_WCE) + else if ( (unsigned char)(data)[0] == ID_DETECT_LOST_CONNECTIONS && byteSize == sizeof(unsigned char) ) + { + // Do nothing + rakFree_Ex(data, __FILE__, __LINE__ ); + } + else if ( (unsigned char)(data)[0] == ID_CONNECTION_REQUEST_ACCEPTED ) + { + // Removeme +// static int count2=1; +// printf("Got ID_CONNECTION_REQUEST_ACCEPTED count=%i\n", count2++); + + if (byteSize > sizeof(MessageID)+sizeof(unsigned int)+sizeof(unsigned short)+sizeof(SystemIndex)) + { + // Make sure this connection accept is from someone we wanted to connect to + bool allowConnection, alreadyConnected; + + if (remoteSystem->connectMode==RemoteSystemStruct::HANDLING_CONNECTION_REQUEST || + remoteSystem->connectMode==RemoteSystemStruct::REQUESTED_CONNECTION || + allowConnectionResponseIPMigration) + allowConnection=true; + else + allowConnection=false; + + if (remoteSystem->connectMode==RemoteSystemStruct::HANDLING_CONNECTION_REQUEST) + alreadyConnected=true; + else + alreadyConnected=false; + + if ( allowConnection ) + { + SystemAddress externalID; + SystemIndex systemIndex; +// SystemAddress internalID; + + RakNet::BitStream inBitStream((unsigned char *) data, byteSize, false); + inBitStream.IgnoreBits(8); // ID_CONNECTION_REQUEST_ACCEPTED + // inBitStream.Read(remotePort); + inBitStream.Read(externalID); + inBitStream.Read(systemIndex); + for (unsigned int i=0; i < MAXIMUM_NUMBER_OF_INTERNAL_IDS; i++) + inBitStream.Read(remoteSystem->theirInternalSystemAddress[i]); + + // Find a free remote system struct to use + // RakNet::BitStream casBitS(data, byteSize, false); + // ConnectionAcceptStruct cas; + // cas.Deserialize(casBitS); + // systemAddress.port = remotePort; + + // The remote system told us our external IP, so save it + remoteSystem->myExternalSystemAddress = externalID; + remoteSystem->connectMode=RemoteSystemStruct::CONNECTED; + + firstExternalID=externalID; + + if (alreadyConnected==false) + { + // Use the stored encryption key + if (remoteSystem->setAESKey) + remoteSystem->reliabilityLayer.SetEncryptionKey( remoteSystem->AESKey ); + else + remoteSystem->reliabilityLayer.SetEncryptionKey( 0 ); + } + + // Send the connection request complete to the game + packet=AllocPacket(byteSize, data, __FILE__, __LINE__); + packet->bitSize = byteSize * 8; + packet->systemAddress = systemAddress; + packet->systemAddress.systemIndex = ( SystemIndex ) GetIndexFromSystemAddress( systemAddress, true ); + packet->guid = remoteSystem->guid; + packet->guid.systemIndex=packet->systemAddress.systemIndex; + packet->rcvPort = remoteSystem->rcvPort; + AddPacketToProducer(packet); + + + // Removeme +// static int count3=1; +// printf("Send ID_NEW_INCOMING_CONNECTION count=%i\n", count3++); + + RakNet::BitStream outBitStream; + outBitStream.Write((MessageID)ID_NEW_INCOMING_CONNECTION); + outBitStream.Write(systemAddress); + for (unsigned int i=0; i < MAXIMUM_NUMBER_OF_INTERNAL_IDS; i++) + outBitStream.Write(mySystemAddress[i]); + // We turned on encryption with SetEncryptionKey. This pads packets to up to a multiple of 16 bytes. + // As soon as a multiple of 16 byte packet arrives on the remote system, we will turn on AES. This works because all encrypted packets are multiples of 16 and the + // packets I happen to be sending before this are not a multiple of 16 bytes. Otherwise there is no way to know if a packet that arrived is + // encrypted or not so the other side won't know to turn on encryption or not. + RakAssert((outBitStream.GetNumberOfBytesUsed()&15)!=0); + SendImmediate( (char*)outBitStream.GetData(), outBitStream.GetNumberOfBitsUsed(), IMMEDIATE_PRIORITY, RELIABLE_ORDERED, 0, systemAddress, false, false, RakNet::GetTimeNS(), 0 ); + + if (alreadyConnected==false) + { + PingInternal( systemAddress, true, UNRELIABLE ); + } + } + else + { + // Ignore, already connected + rakFree_Ex(data, __FILE__, __LINE__ ); + } + } + else + { + // Version mismatch error? + RakAssert(0); + rakFree_Ex(data, __FILE__, __LINE__ ); + } + } + else + { + // What do I do if I get a message from a system, before I am fully connected? + // I can either ignore it or give it to the user + // It seems like giving it to the user is a better option + if (data[0]>=(MessageID)ID_RPC && + remoteSystem->isActive + /* + (remoteSystem->connectMode==RemoteSystemStruct::CONNECTED || + remoteSystem->connectMode==RemoteSystemStruct::DISCONNECT_ASAP || + remoteSystem->connectMode==RemoteSystemStruct::DISCONNECT_ASAP_SILENTLY || + remoteSystem->connectMode==RemoteSystemStruct::DISCONNECT_ON_NO_ACK) + */ + ) + { + packet=AllocPacket(byteSize, data, __FILE__, __LINE__); + packet->bitSize = bitSize; + packet->systemAddress = systemAddress; + packet->systemAddress.systemIndex = ( SystemIndex ) remoteSystemIndex; + packet->guid = remoteSystem->guid; + packet->guid.systemIndex=packet->systemAddress.systemIndex; + packet->rcvPort = remoteSystem->rcvPort; + AddPacketToProducer(packet); + } + else + { + rakFree_Ex(data, __FILE__, __LINE__ ); + } + } + } + + // Does the reliability layer have any more packets waiting for us? + // To be thread safe, this has to be called in the same thread as HandleSocketReceiveFromConnectedPlayer + bitSize = remoteSystem->reliabilityLayer.Receive( &data ); + } + } + } + + return true; +} +// -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +RAK_THREAD_DECLARATION(RecvFromLoop) +{ + RakPeerAndIndex *rpai = ( RakPeerAndIndex * ) arguments; + RakPeer * rakPeer = rpai->rakPeer; + SOCKET s = rpai->s; + unsigned short remotePortRakNetWasStartedOn_PS3 = rpai->remotePortRakNetWasStartedOn_PS3; + + rakPeer->isRecvFromLoopThreadActive = true; + + RakPeer::RecvFromStruct *recvFromStruct; + while ( rakPeer->endThreads == false ) + { + recvFromStruct=rakPeer->bufferedPackets.Allocate( __FILE__, __LINE__ ); + recvFromStruct->s=s; + recvFromStruct->remotePortRakNetWasStartedOn_PS3=remotePortRakNetWasStartedOn_PS3; + SocketLayer::RecvFromBlocking(s, rakPeer, remotePortRakNetWasStartedOn_PS3, recvFromStruct->data, &recvFromStruct->bytesRead, &recvFromStruct->systemAddress, &recvFromStruct->timeRead); + if (recvFromStruct->bytesRead>0) + { + RakAssert(recvFromStruct->systemAddress.port); + rakPeer->bufferedPackets.Push(recvFromStruct); + + rakPeer->quitAndDataEvents.SetEvent(); + } + else + { + rakPeer->bufferedPackets.Deallocate(recvFromStruct, __FILE__,__LINE__); + } + } + rakPeer->isRecvFromLoopThreadActive = false; + return 0; +} +// -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +RAK_THREAD_DECLARATION(UpdateNetworkLoop) +{ + RakPeer * rakPeer = ( RakPeer * ) arguments; + +/* + // 11/15/05 - this is slower than Sleep() +#ifdef _WIN32 +#if (_WIN32_WINNT >= 0x0400) || (_WIN32_WINDOWS > 0x0400) + // Lets see if these timers give better performance than Sleep + HANDLE timerHandle; + LARGE_INTEGER dueTime; + + if ( rakPeer->threadSleepTimer <= 0 ) + rakPeer->threadSleepTimer = 1; + + // 2nd parameter of false means synchronization timer instead of manual-reset timer + timerHandle = CreateWaitableTimer( NULL, FALSE, 0 ); + + RakAssert( timerHandle ); + + dueTime.QuadPart = -10000 * rakPeer->threadSleepTimer; // 10000 is 1 ms? + + BOOL success = SetWaitableTimer( timerHandle, &dueTime, rakPeer->threadSleepTimer, NULL, NULL, FALSE ); + (void) success; + RakAssert( success ); + +#endif +#endif +*/ + + rakPeer->isMainLoopThreadActive = true; + + while ( rakPeer->endThreads == false ) + { + if (rakPeer->userUpdateThreadPtr) + rakPeer->userUpdateThreadPtr(rakPeer, rakPeer->userUpdateThreadData); + + rakPeer->RunUpdateCycle(); + + + // Pending sends go out this often, unless quitAndDataEvents is set + rakPeer->quitAndDataEvents.WaitOnEvent(10); + + /* + +// #if ((_WIN32_WINNT >= 0x0400) || (_WIN32_WINDOWS > 0x0400)) && +#if defined(USE_WAIT_FOR_MULTIPLE_EVENTS) && defined(_WIN32) + + if (rakPeer->threadSleepTimer>0) + { + WSAEVENT eventArray[256]; + unsigned int i, eventArrayIndex; + for (i=0,eventArrayIndex=0; i < rakPeer->socketList.Size(); i++) + { + if (rakPeer->socketList[i]->recvEvent!=INVALID_HANDLE_VALUE) + { + eventArray[eventArrayIndex]=rakPeer->socketList[i]->recvEvent; + eventArrayIndex++; + if (eventArrayIndex==256) + break; + } + } + WSAWaitForMultipleEvents(eventArrayIndex,(const HANDLE*) &eventArray,FALSE,rakPeer->threadSleepTimer,FALSE); + } + else + { + RakSleep(0); + } + +#else // ((_WIN32_WINNT >= 0x0400) || (_WIN32_WINDOWS > 0x0400)) && defined(USE_WAIT_FOR_MULTIPLE_EVENTS) + #pragma message("-- RakNet: Using Sleep(). Uncomment USE_WAIT_FOR_MULTIPLE_EVENTS in RakNetDefines.h if you want to use WaitForSingleObject instead. --") + + RakSleep( rakPeer->threadSleepTimer ); +#endif + */ + } + + rakPeer->isMainLoopThreadActive = false; + + /* +#ifdef _WIN32 +#if (_WIN32_WINNT >= 0x0400) || (_WIN32_WINDOWS > 0x0400) + CloseHandle(timerHandle); +#endif +#endif + */ + + return 0; +} + +#if defined(RMO_NEW_UNDEF_ALLOCATING_QUEUE) +#pragma pop_macro("new") +#undef RMO_NEW_UNDEF_ALLOCATING_QUEUE +#endif + + +#ifdef _MSC_VER +#pragma warning( pop ) +#endif diff --git a/RakNet/Sources/RakPeer.h b/RakNet/Sources/RakPeer.h new file mode 100644 index 0000000..0da4c69 --- /dev/null +++ b/RakNet/Sources/RakPeer.h @@ -0,0 +1,1157 @@ +/// \file +/// \brief Declares RakPeer class. +/// +/// This file is part of RakNet Copyright 2003 Jenkins Software LLC +/// +/// Usage of RakNet is subject to the appropriate license agreement. + + +#ifndef __RAK_PEER_H +#define __RAK_PEER_H + +#include "ReliabilityLayer.h" +#include "RakPeerInterface.h" +#include "RPCNode.h" +#include "RSACrypt.h" +#include "BitStream.h" +#include "SingleProducerConsumer.h" +#include "RPCMap.h" +#include "SimpleMutex.h" +#include "DS_OrderedList.h" +#include "Export.h" +#include "RakString.h" +#include "RakThread.h" +#include "RakNetSocket.h" +#include "RakNetSmartPtr.h" +#include "DS_ThreadsafeAllocatingQueue.h" +#include "SignaledEvent.h" + +class HuffmanEncodingTree; +class PluginInterface2; + +// Sucks but this struct has to be outside the class. Inside and DevCPP won't let you refer to the struct as RakPeer::RemoteSystemIndex while GCC +// forces you to do RakPeer::RemoteSystemIndex +struct RemoteSystemIndex{unsigned index; RemoteSystemIndex *next;}; +//int RAK_DLL_EXPORT SystemAddressAndIndexComp( const SystemAddress &key, const RemoteSystemIndex &data ); // GCC requires RakPeer::RemoteSystemIndex or it won't compile + +// TODO - RakNet 4 Move plugins to each sample directory respectively, to reduce the lib size + +///\brief Main interface for network communications. +/// \details It implements most of RakNet's functionality and is the primary interface for RakNet. +/// +/// Inherits RakPeerInterface. +/// +/// See the individual functions for what the class can do. +/// +class RAK_DLL_EXPORT RakPeer : public RakPeerInterface +{ +public: + ///Constructor + RakPeer(); + + ///Destructor + virtual ~RakPeer(); + + // --------------------------------------------------------------------------------------------Major Low Level Functions - Functions needed by most users-------------------------------------------------------------------------------------------- + /// \brief Starts the network threads and opens the listen port. + /// \details You must call this before calling Connect(). + /// \note Multiple calls while already active are ignored. To call this function again with different settings, you must first call Shutdown(). + /// \note Call SetMaximumIncomingConnections if you want to accept incoming connections. + /// \note Set _RAKNET_THREADSAFE in RakNetDefines.h if you want to call RakNet functions from multiple threads (not recommended, as it is much slower and RakNet is already asynchronous). + /// \param[in] maxConnections Maximum number of connections between this instance of RakPeer and another instance of RakPeer. Required so that the network can preallocate and for thread safety. A pure client would set this to 1. A pure server would set it to the number of allowed clients.A hybrid would set it to the sum of both types of connections. + /// \param[in] localPort Port to listen for connections on. + /// \param[in] _threadSleepTimer Time in milliseconds for the thread to Sleep in each internal update cycle. With new congestion control, the best results will be obtained by passing 10. + /// \param[in] socketDescriptors An array of SocketDescriptor structures to force RakNet to listen on a particular IP address or port (or both). Each SocketDescriptor will represent one unique socket. Do not pass redundant structures. To listen on a specific port, you can pass SocketDescriptor(myPort,0); for a server. For a client, it is usually OK to pass SocketDescriptor(); + /// \param[in] socketDescriptorCount The size of the \a socketDescriptors array. Pass 1 if you are not sure what to pass. + /// \param[in] threadPriority Passed to the thread creation routine. Use THREAD_PRIORITY_NORMAL for Windows. WARNING!!! On the PS3, 0 means highest priority! + /// \return False on failure (can't create socket or thread), true on success. + bool Startup( unsigned short maxConnections, int _threadSleepTimer, SocketDescriptor *socketDescriptors, unsigned socketDescriptorCount, int threadPriority=-99999 ); + + /// \brief Secures connections though a combination of SHA1, AES128, SYN Cookies, and RSA to prevent connection spoofing, replay attacks, data eavesdropping, packet tampering, and MitM attacks. + /// \details If you accept connections, you must call this for the secure connection to be enabled for incoming connections. + /// If you are connecting to another system, you can call this with public key values for p,q and e before connecting to prevent MitM. + /// Define how many bits are used in RakNetDefines.h with RAKNET_RSA_FACTOR_LIMBS. + /// \note There is a significant amount of processing and a slight amount of bandwidth overhead for this feature. + /// \pre Must be called before Initialize. + /// \param[in] pubKeyE A pointer to the public keys from the RSACrypt class. + /// \param[in] pubKeyN A pointer to the public keys from the RSACrypt class. + /// \param[in] privKeyP Public key generated from the RSACrypt class. + /// \param[in] privKeyQ Public key generated from the RSACrypt class. If the private keys are 0, then a new key will be generated when this function is called@see the Encryption sample + void InitializeSecurity(const char *pubKeyE, const char *pubKeyN, const char *privKeyP, const char *privKeyQ ); + + /// \brief Disables all security. + /// \note Must be called while offline. + void DisableSecurity( void ); + + /// \brief This is useful if you have a fixed-address internal server behind a LAN. + /// + /// Secure connections are determined by the recipient of an incoming connection. This has no effect if called on the system attempting to connect. + /// \note If secure connections are on, do not use secure connections for a specific IP address. + /// \param[in] ip IP address to add. * wildcards are supported. + void AddToSecurityExceptionList(const char *ip); + + /// \brief Remove a specific connection previously added via AddToSecurityExceptionList. + /// \param[in] ip IP address to remove. Pass 0 to remove all IP addresses. * wildcards are supported. + void RemoveFromSecurityExceptionList(const char *ip); + + /// \brief Checks to see if a given IP is in the security exception list. + /// \param[in] IP address to check. + /// \return True if the IP address is found in security exception list, else returns false. + bool IsInSecurityExceptionList(const char *ip); + + /// \brief Sets the maximum number of incoming connections allowed. + /// \details If the number of incoming connections is less than the number of players currently connected, + /// no more players will be allowed to connect. If this is greater than the maximum number of peers allowed, + /// it will be reduced to the maximum number of peers allowed. + /// + /// Defaults to 0, meaning by default, nobody can connect to you + /// \param[in] numberAllowed Maximum number of incoming connections allowed. + void SetMaximumIncomingConnections( unsigned short numberAllowed ); + + /// \brief Returns the value passed to SetMaximumIncomingConnections(). + /// \return Maximum number of incoming connections, which is always <= maxConnections + unsigned short GetMaximumIncomingConnections( void ) const; + + /// \brief Returns how many open connections exist at this time. + /// \return Number of open connections. + unsigned short NumberOfConnections(void) const; + + /// \brief Sets the password for the incoming connections. + /// \details The password must match in the call to Connect (defaults to none). + /// Pass 0 to passwordData to specify no password. + /// This is a way to set a low level password for all incoming connections. To selectively reject connections, implement your own scheme using CloseConnection() to remove unwanted connections. + /// \param[in] passwordData A data block that incoming connections must match. This can be just a password, or can be a stream of data. Specify 0 for no password data + /// \param[in] passwordDataLength The length in bytes of passwordData + void SetIncomingPassword( const char* passwordData, int passwordDataLength ); + + /// \brief Gets the password passed to SetIncomingPassword + /// \param[out] passwordData Should point to a block large enough to hold the password data you passed to SetIncomingPassword() + /// \param[in,out] passwordDataLength Maximum size of the passwordData array. Modified to hold the number of bytes actually written. + void GetIncomingPassword( char* passwordData, int *passwordDataLength ); + + /// \brief Connect to the specified host (ip or domain name) and server port. + /// \details Calling Connect and not calling SetMaximumIncomingConnections acts as a dedicated client. + /// Calling both acts as a true peer. + /// + /// This is a non-blocking connection. + /// + /// The connection is successful when IsConnected() returns true or Receive() gets a message with the type identifier ID_CONNECTION_ACCEPTED. + /// If the connection is not successful, such as a rejected connection or no response then neither of these things will happen. + /// \pre Requires that you first call Initialize. + /// \param[in] host Either a dotted IP address or a domain name. + /// \param[in] remotePort Port to connect to on the remote machine. + /// \param[in] passwordData A data block that must match the data block on the server passed to SetIncomingPassword(). This can be a string or can be a stream of data. Use 0 for no password. + /// \param[in] passwordDataLength The length in bytes of passwordData. + /// \param[in] connectionSocketIndex Index into the array of socket descriptors passed to socketDescriptors in RakPeer::Startup() to determine the one to send on. + /// \param[in] sendConnectionAttemptCount Number of datagrams to send to the other system to try to connect. + /// \param[in] timeBetweenSendConnectionAttemptsMS Time to elapse before a datagram is sent to the other system to try to connect. After sendConnectionAttemptCount number of attempts, ID_CONNECTION_ATTEMPT_FAILED is returned. + /// \param[in] timeoutTime Time to elapse before dropping the connection if a reliable message could not be sent. 0 to use the default value from SetTimeoutTime(UNASSIGNED_SYSTEM_ADDRESS); + /// \return True on successful initiation. False if you are already connected to this system, a connection to the system is pending, the domain name cannot be resolved, incorrect parameters, internal error, or too many existing peers. + /// \note Returning true does not mean you are connected! + /// TODO - RakNet 4 - return enum + bool Connect( const char* host, unsigned short remotePort, const char *passwordData, int passwordDataLength, unsigned connectionSocketIndex=0, unsigned sendConnectionAttemptCount=12, unsigned timeBetweenSendConnectionAttemptsMS=500, RakNetTime timeoutTime=0 ); + + /// \brief Connect to the specified host (ip or domain name) and server port. + /// \param[in] host Either a dotted IP address or a domain name. + /// \param[in] remotePort Which port to connect to on the remote machine. + /// \param[in] passwordData A data block that must match the data block on the server passed to SetIncomingPassword(). This can be a string or can be a stream of data. Use 0 for no password. + /// \param[in] passwordDataLength The length in bytes of passwordData. + /// \param[in] socket A bound socket returned by another instance of RakPeerInterface. + /// \param[in] sendConnectionAttemptCount Number of datagrams to send to the other system to try to connect. + /// \param[in] timeBetweenSendConnectionAttemptsMS Time to elapse before a datagram is sent to the other system to try to connect. After sendConnectionAttemptCount number of attempts, ID_CONNECTION_ATTEMPT_FAILED is returned. + /// \param[in] timeoutTime Time to elapse before dropping the connection if a reliable message could not be sent. 0 to use the default from SetTimeoutTime(UNASSIGNED_SYSTEM_ADDRESS); + /// \return True on successful initiation. False on incorrect parameters, internal error, or too many existing peers. + /// \note Returning true does not mean you arebconnected! + virtual bool ConnectWithSocket(const char* host, unsigned short remotePort, const char *passwordData, int passwordDataLength, RakNetSmartPtr socket, unsigned sendConnectionAttemptCount=12, unsigned timeBetweenSendConnectionAttemptsMS=500, RakNetTime timeoutTime=0); + + /* /// \brief Connect to the specified network ID (Platform specific console function) + /// \details Does built-in NAT traversal + /// \param[in] networkServiceId Network ID structure for the online service + /// \param[in] passwordData A data block that must match the data block on the server passed to SetIncomingPassword(). This can be a string or can be a stream of data. Use 0 for no password. + /// \param[in] passwordDataLength The length in bytes of passwordData. + //bool Console2LobbyConnect( void *networkServiceId, const char *passwordData, int passwordDataLength );*/ + + /// \brief Stops the network threads and closes all connections. + /// \param[in] blockDuration Wait time(milli seconds) for all remaining messages to go out, including ID_DISCONNECTION_NOTIFICATION. If 0, it doesn't wait at all. + /// \param[in] orderingChannel Channel on which ID_DISCONNECTION_NOTIFICATION will be sent, if blockDuration > 0. + /// \param[in] disconnectionNotificationPriority Priority of sending ID_DISCONNECTION_NOTIFICATION. + /// If set to 0, the disconnection notification won't be sent. + void Shutdown( unsigned int blockDuration, unsigned char orderingChannel=0, PacketPriority disconnectionNotificationPriority=LOW_PRIORITY ); + + /// \brief Returns true if the network thread is running. + /// \return True if the network thread is running, False otherwise + bool IsActive( void ) const; + + /// \brief Fills the array remoteSystems with the SystemAddress of all the systems we are connected to. + /// \param[out] remoteSystems An array of SystemAddress structures, to be filled with the SystemAddresss of the systems we are connected to. Pass 0 to remoteSystems to get the number of systems we are connected to. + /// \param[in, out] numberOfSystems As input, the size of remoteSystems array. As output, the number of elements put into the array. + bool GetConnectionList( SystemAddress *remoteSystems, unsigned short *numberOfSystems ) const; + + /// Returns the next uint32_t that Send() will return + /// \note If using RakPeer from multiple threads, this may not be accurate for your thread. Use IncrementNextSendReceipt() in that case. + /// \return The next uint32_t that Send() or SendList will return + virtual uint32_t GetNextSendReceipt(void); + + /// Returns the next uint32_t that Send() will return, and increments the value by one + /// \note If using RakPeer from multiple threads, pass this to forceReceipt in the send function + /// \return The next uint32_t that Send() or SendList will return + virtual uint32_t IncrementNextSendReceipt(void); + + /// \brief Sends a block of data to the specified system that you are connected to. + /// \note This function only works while the connected. + /// \note The first byte should be a message identifier starting at ID_USER_PACKET_ENUM. + /// \param[in] data Block of data to send. + /// \param[in] length Size in bytes of the data to send. + /// \param[in] priority Priority level to send on. See PacketPriority.h + /// \param[in] reliability How reliably to send this data. See PacketPriority.h + /// \param[in] orderingChannel When using ordered or sequenced messages, the channel to order these on. Messages are only ordered relative to other messages on the same stream. + /// \param[in] systemIdentifier Who to send this packet to, or in the case of broadcasting who not to send it to. Pass either a SystemAddress structure or a RakNetGUID structure. Use UNASSIGNED_SYSTEM_ADDRESS or to specify none + /// \param[in] broadcast True to send this packet to all connected systems. If true, then systemAddress specifies who not to send the packet to. + /// \param[in] forceReceipt If 0, will automatically determine the receipt number to return. If non-zero, will return what you give it. + /// \return 0 on bad input. Otherwise a number that identifies this message. If \a reliability is a type that returns a receipt, on a later call to Receive() you will get ID_SND_RECEIPT_ACKED or ID_SND_RECEIPT_LOSS with bytes 1-4 inclusive containing this number + uint32_t Send( const char *data, const int length, PacketPriority priority, PacketReliability reliability, char orderingChannel, const AddressOrGUID systemIdentifier, bool broadcast, uint32_t forceReceipt=0 ); + + /// \brief "Send" to yourself rather than a remote system. + /// \details The message will be processed through the plugins and returned to the game as usual. + /// This function works anytime + /// \note The first byte should be a message identifier starting at ID_USER_PACKET_ENUM + /// \param[in] data Block of data to send. + /// \param[in] length Size in bytes of the data to send. + void SendLoopback( const char *data, const int length ); + + /// \brief Sends a block of data to the specified system that you are connected to. + /// + /// Same as the above version, but takes a BitStream as input. + /// \param[in] bitStream Bitstream to send + /// \param[in] priority Priority level to send on. See PacketPriority.h + /// \param[in] reliability How reliably to send this data. See PacketPriority.h + /// \param[in] orderingChannel Channel to order the messages on, when using ordered or sequenced messages. Messages are only ordered relative to other messages on the same stream. + /// \param[in] systemIdentifier System Address or RakNetGUID to send this packet to, or in the case of broadcasting, the address not to send it to. Use UNASSIGNED_SYSTEM_ADDRESS to specify none. + /// \param[in] broadcast True to send this packet to all connected systems. If true, then systemAddress specifies who not to send the packet to. + /// \param[in] forceReceipt If 0, will automatically determine the receipt number to return. If non-zero, will return what you give it. + /// \return 0 on bad input. Otherwise a number that identifies this message. If \a reliability is a type that returns a receipt, on a later call to Receive() you will get ID_SND_RECEIPT_ACKED or ID_SND_RECEIPT_LOSS with bytes 1-4 inclusive containing this number + /// \note COMMON MISTAKE: When writing the first byte, bitStream->Write((unsigned char) ID_MY_TYPE) be sure it is casted to a byte, and you are not writing a 4 byte enumeration. + uint32_t Send( const RakNet::BitStream * bitStream, PacketPriority priority, PacketReliability reliability, char orderingChannel, const AddressOrGUID systemIdentifier, bool broadcast, uint32_t forceReceipt=0 ); + + /// \brief Sends multiple blocks of data, concatenating them automatically. + /// + /// This is equivalent to: + /// RakNet::BitStream bs; + /// bs.WriteAlignedBytes(block1, blockLength1); + /// bs.WriteAlignedBytes(block2, blockLength2); + /// bs.WriteAlignedBytes(block3, blockLength3); + /// Send(&bs, ...) + /// + /// This function only works when connected. + /// \param[in] data An array of pointers to blocks of data + /// \param[in] lengths An array of integers indicating the length of each block of data + /// \param[in] numParameters Length of the arrays data and lengths + /// \param[in] priority Priority level to send on. See PacketPriority.h + /// \param[in] reliability How reliably to send this data. See PacketPriority.h + /// \param[in] orderingChannel Channel to order the messages on, when using ordered or sequenced messages. Messages are only ordered relative to other messages on the same stream. + /// \param[in] systemIdentifier System Address or RakNetGUID to send this packet to, or in the case of broadcasting, the address not to send it to. Use UNASSIGNED_SYSTEM_ADDRESS to specify none. + /// \param[in] broadcast True to send this packet to all connected systems. If true, then systemAddress specifies who not to send the packet to. + /// \param[in] forceReceipt If 0, will automatically determine the receipt number to return. If non-zero, will return what you give it. + /// \return 0 on bad input. Otherwise a number that identifies this message. If \a reliability is a type that returns a receipt, on a later call to Receive() you will get ID_SND_RECEIPT_ACKED or ID_SND_RECEIPT_LOSS with bytes 1-4 inclusive containing this number + /// \note Doesn't support the router plugin. + uint32_t SendList( const char **data, const int *lengths, const int numParameters, PacketPriority priority, PacketReliability reliability, char orderingChannel, const AddressOrGUID systemIdentifier, bool broadcast, uint32_t forceReceipt=0 ); + + /// \brief Gets a message from the incoming message queue. + /// \details Use DeallocatePacket() to deallocate the message after you are done with it. + /// User-thread functions, such as RPC calls and the plugin function PluginInterface::Update occur here. + /// \return 0 if no packets are waiting to be handled, otherwise a pointer to a packet. + /// \note COMMON MISTAKE: Be sure to call this in a loop, once per game tick, until it returns 0. If you only process one packet per game tick they will buffer up. + /// \sa RakNetTypes.h contains struct Packet. + Packet* Receive( void ); + + /// \brief Call this to deallocate a message returned by Receive() when you are done handling it. + /// \param[in] packet Message to deallocate. + void DeallocatePacket( Packet *packet ); + + /// \brief Return the total number of connections we are allowed. + /// \return Total number of connections allowed. + unsigned short GetMaximumNumberOfPeers( void ) const; + + // --------------------------------------------------------------------------------------------Remote Procedure Call Functions - Functions to initialize and perform RPC-------------------------------------------------------------------------------------------- + /// \ingroup RAKNET_RPC + /// \brief Register a C or static member function as available for calling a remote procedure call. + /// \param[in] uniqueID A null-terminated unique string to identify this procedure. See RegisterClassMemberRPC() for class member functions. + /// \param[in] functionPointer(...) The name of the function to be used as a function pointer. This can be called whether active or not, and registered functions stay registered unless unregistered + /// \deprecated Use RakNet::RPC3 + void RegisterAsRemoteProcedureCall( const char* uniqueID, void ( *functionPointer ) ( RPCParameters *rpcParms ) ); + + /// \ingroup RAKNET_RPC + /// \brief Register a C++ member function as available for calling as a remote procedure call. + /// \param[in] uniqueID A null terminated string to identify this procedure. It is recommended to use the macro REGISTER_CLASS_MEMBER_RPC to create the string. Use RegisterAsRemoteProcedureCall() for static functions. + /// \param[in] functionPointer The name of the function to be used as a function pointer. This can be called whether active or not, and registered functions stay registered unless unregistered with UnregisterAsRemoteProcedureCall. + /// \sa The sample ObjectMemberRPC.cpp + /// \deprecated Use RakNet::RPC3 + void RegisterClassMemberRPC( const char* uniqueID, void *functionPointer ); + + /// \ingroup RAKNET_RPC + /// \brief Unregisters a C function as available for calling as a remote procedure call that was formerly registered with RegisterAsRemoteProcedureCall. Only call offline. + /// \param[in] uniqueID A string of only letters to identify this procedure. Recommended you use the macro CLASS_MEMBER_ID for class member functions. + /// \deprecated Use RakNet::RPC3 + void UnregisterAsRemoteProcedureCall( const char* uniqueID ); + + /// \ingroup RAKNET_RPC + /// \brief Used by Object member RPC to lookup objects given that object's ID. + /// Also used by the ReplicaManager plugin + /// \param[in] An instance of NetworkIDManager to use for the loookup. + void SetNetworkIDManager( NetworkIDManager *manager ); + + /// \return Returns the value passed to SetNetworkIDManager or 0 if never called. + NetworkIDManager *GetNetworkIDManager(void) const; + + /// ------------------------------------------- Deprecated ------------------------- + /// \ingroup RAKNET_RPC + /// Calls a C function on the remote system that was already registered using RegisterAsRemoteProcedureCall(). + /// \pre To use object member RPC (networkID!=UNASSIGNED_OBJECT_ID), The recipient must have called SetNetworkIDManager so the system can handle the object lookups + /// \param[in] uniqueID A NULL terminated string identifying the function to call. Recommended you use the macro CLASS_MEMBER_ID for class member functions. + /// \param[in] data The data to send + /// \param[in] bitLength The number of bits of \a data + /// \param[in] priority What priority level to send on. See PacketPriority.h. + /// \param[in] reliability How reliability to send this data. See PacketPriority.h. + /// \param[in] orderingChannel When using ordered or sequenced message, what channel to order these on. + /// \param[in] systemAddress Who to send this message to, or in the case of broadcasting who not to send it to. Pass either a SystemAddress structure or a RakNetGUID structure. Use UNASSIGNED_SYSTEM_ADDRESS or to specify none + /// \param[in] broadcast True to send this packet to all connected systems. If true, then systemAddress specifies who not to send the packet to. + /// \param[in] includedTimestamp Pass a timestamp if you wish, to be adjusted in the usual fashion as per ID_TIMESTAMP. Pass 0 to not include a timestamp. + /// \param[in] networkID For static functions, pass UNASSIGNED_NETWORK_ID. For member functions, you must derive from NetworkIDObject and pass the value returned by NetworkIDObject::GetNetworkID for that object. + /// \param[in] replyFromTarget If 0, this function is non-blocking. Otherwise it will block while waiting for a reply from the target procedure, which should be remotely written to RPCParameters::replyToSender and copied to replyFromTarget. The block will return early on disconnect or if the sent packet is unreliable and more than 3X the ping has elapsed. + /// \return True on a successful packet send (this does not indicate the recipient performed the call), false on failure + /// \deprecated Use RakNet::RPC3. This only correctly works for C functions. For C++, it only works for Windows. It is less flexible than RPC3 + /// ------------------------------------------- Deprecated ------------------------- + bool RPC( const char* uniqueID, const char *data, BitSize_t bitLength, PacketPriority priority, PacketReliability reliability, char orderingChannel, const AddressOrGUID systemIdentifier, bool broadcast, RakNetTime *includedTimestamp, NetworkID networkID, RakNet::BitStream *replyFromTarget ); + + bool RPC( const char* uniqueID, const char *data, BitSize_t bitLength, PacketPriority priority, PacketReliability reliability, char orderingChannel, const AddressOrGUID systemIdentifier, bool broadcast, RakNetTime *includedTimestamp, NetworkID networkID, RakNet::BitStream *replyFromTarget, unsigned char proxyType, SystemAddress proxyTargetAddress ); + + /// ------------------------------------------- Deprecated ------------------------- + /// \ingroup RAKNET_RPC + /// \brief Calls a C function on the remote system that was already registered using RegisterAsRemoteProcedureCall. + /// \details If you want that function to return data you should call RPC from that system in the same way.Returns true on a successful packet + /// send (this does not indicate the recipient performed the call), false on failure. + /// \pre To use object member RPC (networkID!=UNASSIGNED_OBJECT_ID), the recipient must have called SetNetworkIDManager so the system can handle the object lookups. + /// \param[in] uniqueID A NULL terminated string identifying the function to call. Recommended you use the macro CLASS_MEMBER_ID for class member functions. + /*/// \param[in] data The data to send + /// \param[in] bitLength The number of bits of \a data*/ + /// \param[in] bitStream The bit stream to send. + /// \param[in] priority What priority level to send on. See PacketPriority.h. + /// \param[in] reliability How reliably to send this data. See PacketPriority.h. + /// \param[in] orderingChannel When using ordered or sequenced message, what channel to order these on. + /// \param[in] systemAddress Who to send this message to, or in the case of broadcasting who not to send it to. Pass either a SystemAddress structure or a RakNetGUID structure. Use UNASSIGNED_SYSTEM_ADDRESS or to specify none + /// \param[in] broadcast True to send this packet to all connected systems. If true, then systemAddress specifies who not to send the packet to. + /// \param[in] includedTimestamp Pass a timestamp if you wish, to be adjusted in the usual fashion as per ID_TIMESTAMP. Pass 0 to not include a timestamp. + /// \param[in] networkID For static functions, pass UNASSIGNED_NETWORK_ID. For member functions, you must derive from NetworkIDObject and pass the value returned by NetworkIDObject::GetNetworkID for that object. + /// \param[in] replyFromTarget If 0, this function is non-blocking. Otherwise it will block while waiting for a reply from the target procedure, which should be remotely written to RPCParameters::replyToSender and copied to replyFromTarget. The block will return early on disconnect or if the sent packet is unreliable and more than 3X the ping has elapsed. + /// \return True on a successful packet send (this does not indicate the recipient performed the call), false on failure + /// \deprecated Use RakNet::RPC3. This only correctly works for C functions. For C++, it only works for Windows. It is less flexible than RPC3 + /// ------------------------------------------- Deprecated ------------------------- + bool RPC( const char* uniqueID, const RakNet::BitStream *bitStream, PacketPriority priority, PacketReliability reliability, char orderingChannel, const AddressOrGUID systemIdentifier, bool broadcast, RakNetTime *includedTimestamp, NetworkID networkID, RakNet::BitStream *replyFromTarget ); + + bool RPC( const char* uniqueID, const RakNet::BitStream *bitStream, PacketPriority priority, PacketReliability reliability, char orderingChannel, const AddressOrGUID systemIdentifier, bool broadcast, RakNetTime *includedTimestamp, NetworkID networkID, RakNet::BitStream *replyFromTarget, unsigned char proxyType, SystemAddress proxyTargetAddress ); + + // -------------------------------------------------------------------------------------------- Connection Management Functions-------------------------------------------------------------------------------------------- + /// \brief Close the connection to another host (if we initiated the connection it will disconnect, if they did it will kick them out). + /// \details This method closes the connection irrespective of who initiated the connection. + /// \param[in] target Which system to close the connection to. + /// \param[in] sendDisconnectionNotification True to send ID_DISCONNECTION_NOTIFICATION to the recipient. False to close it silently. + /// \param[in] channel Which ordering channel to send the disconnection notification on, if any + /// \param[in] disconnectionNotificationPriority Priority to send ID_DISCONNECTION_NOTIFICATION on. + void CloseConnection( const SystemAddress target, bool sendDisconnectionNotification, unsigned char orderingChannel=0, PacketPriority disconnectionNotificationPriority=LOW_PRIORITY ); + + /// \brief Cancel a pending connection attempt. + /// \details If we are already connected, the connection stays open + /// \param[in] target Target system to cancel. + void CancelConnectionAttempt( const SystemAddress target ); + + /// Returns if a particular systemAddress has a pending or in progress connection attempt + /// \param[in] systemAddress The SystemAddress we are referring to + bool IsConnectionAttemptPending( const SystemAddress systemAddress ); + + /// \brief Returns if a particular systemAddress is connected to us. + /// \note This can also be made to return true if we are in the process of connecting. + /// \param[in] systemAddress The SystemAddress we are referring to + /// \param[in] includeInProgress If true, also return true for connections that are in progress but haven't completed + /// \param[in] includeDisconnecting If true, also return true for connections that are in the process of disconnecting + /// \return True if this system is connected and active, false otherwise. + bool IsConnected(const AddressOrGUID systemIdentifier, bool includeInProgress=false, bool includeDisconnecting=false); + + /// \brief Given \a systemAddress, returns its index into remoteSystemList. + /// \details Values range from 0 to the maximum number of players allowed - 1. + /// This includes systems which were formerly connected, but are now not connected. + /// \param[in] systemAddress The SystemAddress we are referring to + /// \return The index of this SystemAddress or -1 on system not found. + int GetIndexFromSystemAddress( const SystemAddress systemAddress ) const; + + /// \brief Given \a index into remoteSystemList, will return a SystemAddress. + /// This function is only useful for looping through all systems. + /// + /// \param[in] index Index should range between 0 and the maximum number of players allowed - 1. + /// \return The SystemAddress structure corresponding to \a index in remoteSystemList. + SystemAddress GetSystemAddressFromIndex( int index ); + + /// \brief Same as GetSystemAddressFromIndex but returns RakNetGUID + /// \param[in] index Index should range between 0 and the maximum number of players allowed - 1. + /// \return The RakNetGUID + RakNetGUID GetGUIDFromIndex( int index ); + + /// \brief Same as calling GetSystemAddressFromIndex and GetGUIDFromIndex for all systems, but more efficient + /// Indices match each other, so \a addresses[0] and \a guids[0] refer to the same system + /// \param[out] addresses All system addresses. Size of the list is the number of connections. Size of the \a addresses list will match the size of the \a guids list. + /// \param[out] guids All guids. Size of the list is the number of connections. Size of the list will match the size of the \a addresses list. + void GetSystemList(DataStructures::List &addresses, DataStructures::List &guids); + + /// \brief Bans an IP from connecting. + /// \details Banned IPs persist between connections but are not saved on shutdown nor loaded on startup. + /// \param[in] IP Dotted IP address. You can use * for a wildcard address, such as 128.0.0. * will ban all IP addresses starting with 128.0.0. + /// \param[in] milliseconds Gives time in milli seconds for a temporary ban of the IP address. Use 0 for a permanent ban. + void AddToBanList( const char *IP, RakNetTime milliseconds=0 ); + + /// \brief Allows a previously banned IP to connect. + /// param[in] Dotted IP address. You can use * as a wildcard. An IP such as 128.0.0.* will ban all IP addresses starting with 128.0.0. + void RemoveFromBanList( const char *IP ); + + /// \brief Allows all previously banned IPs to connect. + void ClearBanList( void ); + + /// \brief Returns true or false indicating if a particular IP is banned. + /// \param[in] IP Dotted IP address. + /// \return True if IP matches any IPs in the ban list, accounting for any wildcards. False otherwise. + bool IsBanned( const char *IP ); + + /// \brief Enable or disable allowing frequent connections from the same IP adderss + /// \details This is a security measure which is disabled by default, but can be set to true to prevent attackers from using up all connection slots. + /// \param[in] b True to limit connections from the same ip to at most 1 per 100 milliseconds. + void SetLimitIPConnectionFrequency(bool b); + + // --------------------------------------------------------------------------------------------Pinging Functions - Functions dealing with the automatic ping mechanism-------------------------------------------------------------------------------------------- + /// Send a ping to the specified connected system. + /// \pre The sender and recipient must already be started via a successful call to Startup() + /// \param[in] target Which system to ping + void Ping( const SystemAddress target ); + + /// \brief Send a ping to the specified unconnected system. + /// \details The remote system, if it is Initialized, will respond with ID_PONG followed by sizeof(RakNetTime) containing the system time the ping was sent. Default is 4 bytes - See __GET_TIME_64BIT in RakNetTypes.h + /// System should reply with ID_PONG if it is active + /// \param[in] host Either a dotted IP address or a domain name. Can be 255.255.255.255 for LAN broadcast. + /// \param[in] remotePort Which port to connect to on the remote machine. + /// \param[in] onlyReplyOnAcceptingConnections Only request a reply if the remote system is accepting connections + /// \param[in] connectionSocketIndex Index into the array of socket descriptors passed to socketDescriptors in RakPeer::Startup() to send on. + /// \return true on success, false on failure (unknown hostname) + bool Ping( const char* host, unsigned short remotePort, bool onlyReplyOnAcceptingConnections, unsigned connectionSocketIndex=0 ); + + /// \brief Returns the average of all ping times read for the specific system or -1 if none read yet + /// \param[in] systemAddress Which system we are referring to + /// \return The ping time for this system, or -1 + int GetAveragePing( const AddressOrGUID systemIdentifier ); + + /// \brief Returns the last ping time read for the specific system or -1 if none read yet. + /// \param[in] systemAddress Which system we are referring to + /// \return The last ping time for this system, or -1. + int GetLastPing( const AddressOrGUID systemIdentifier ) const; + + /// \brief Returns the lowest ping time read or -1 if none read yet. + /// \param[in] systemAddress Which system we are referring to + /// \return The lowest ping time for this system, or -1. + int GetLowestPing( const AddressOrGUID systemIdentifier ) const; + + /// \brief Ping the remote systems every so often, or not. + /// \details This is off by default. Can be called anytime. + /// \param[in] doPing True to start occasional pings. False to stop them. + void SetOccasionalPing( bool doPing ); + + // --------------------------------------------------------------------------------------------Static Data Functions - Functions dealing with API defined synchronized memory-------------------------------------------------------------------------------------------- + /// \brief Sets the data to send along with a LAN server discovery or offline ping reply. + /// \param[in] data Block of data to send, or 0 for none + /// \param[in] length Length of the data in bytes, or 0 for none + /// \note \a length should be under 400 bytes, as a security measure against flood attacks + /// \sa Ping.cpp + void SetOfflinePingResponse( const char *data, const unsigned int length ); + + /// \brief Returns pointers to a copy of the \a data passed to SetOfflinePingResponse. + /// \param[out] data A pointer to a copy of the data passed to SetOfflinePingResponse() + /// \param[out] length A pointer filled in with the length parameter passed to SetOfflinePingResponse() + /// \sa SetOfflinePingResponse + void GetOfflinePingResponse( char **data, unsigned int *length ); + + //--------------------------------------------------------------------------------------------Network Functions - Functions dealing with the network in general-------------------------------------------------------------------------------------------- + /// \brief Returns the unique address identifier that represents you or another system on the the network and is based on your local IP / port. + /// \param[in] systemAddress Use UNASSIGNED_SYSTEM_ADDRESS to get your behind-LAN address. Use a connected system to get their behind-LAN address + /// \param[in] index When you have multiple internal IDs, which index to return? Currently limited to MAXIMUM_NUMBER_OF_INTERNAL_IDS (so the maximum value of this variable is MAXIMUM_NUMBER_OF_INTERNAL_IDS-1) + /// \return Identifier of your system internally, which may not be how other systems see if you if you are behind a NAT or proxy + SystemAddress GetInternalID( const SystemAddress systemAddress=UNASSIGNED_SYSTEM_ADDRESS, const int index=0 ) const; + + /// \brief Returns the unique address identifier that represents the target on the the network and is based on the target's external IP / port. + /// \param[in] target The SystemAddress of the remote system. Usually the same for all systems, unless you have two or more network cards. + SystemAddress GetExternalID( const SystemAddress target ) const; + + /// \brief Given a connected system address, this method gives the unique GUID representing that instance of RakPeer. + /// This will be the same on all systems connected to that instance of RakPeer, even if the external system addresses are different. + /// Complexity is O(log2(n)). + /// If \a input is UNASSIGNED_SYSTEM_ADDRESS, will return your own GUID + /// \pre Call Startup() first, or the function will return UNASSIGNED_RAKNET_GUID + /// \param[in] input The system address of the target system we are connected to. + const RakNetGUID& GetGuidFromSystemAddress( const SystemAddress input ) const; + + /// \brief Gives the system address of a connected system, given its GUID. + /// The GUID will be the same on all systems connected to that instance of RakPeer, even if the external system addresses are different. + /// Currently O(log(n)), but this may be improved in the future + /// If \a input is UNASSIGNED_RAKNET_GUID, UNASSIGNED_SYSTEM_ADDRESS is returned. + /// \param[in] input The RakNetGUID of the target system. + SystemAddress GetSystemAddressFromGuid( const RakNetGUID input ) const; + + /// \brief Set the time, in MS, to use before considering ourselves disconnected after not being able to deliver a reliable message. + /// \details Default time is 10,000 or 10 seconds in release and 30,000 or 30 seconds in debug. + /// \param[in] timeMS Time, in MS + /// \param[in] target SystemAddress structure of the target system. Pass UNASSIGNED_SYSTEM_ADDRESS for all systems. + void SetTimeoutTime( RakNetTime timeMS, const SystemAddress target ); + + /// \brief Returns the Timeout time for the given system. + /// \param[in] target Target system to get the TimeoutTime for. Pass UNASSIGNED_SYSTEM_ADDRESS to get the default value. + /// \return Timeout time for a given system. + RakNetTime GetTimeoutTime( const SystemAddress target ); + + /*/// \deprecated 8/12/09 + /// \brief MTU automatically calculated during connection process + /// \details Set the MTU per datagram. It's important to set this correctly - otherwise packets will be needlessly split, decreasing performance and throughput. + /// Maximum allowed size is MAXIMUM_MTU_SIZE. + /// Too high of a value will cause packets not to arrive at worst and be fragmented at best. + /// Too low of a value will split packets unnecessarily. + /// sa MTUSize.h + /// \param[in] size The MTU size + /// \param[in] target Which system to set this for. UNASSIGNED_SYSTEM_ADDRESS to set the default, for new systems + /// \pre Can only be called when not connected. + /// \return false on failure (we are connected), else true + /// bool SetMTUSize( int size, const SystemAddress target );*/ + + /// \brief Returns the current MTU size + /// \param[in] target Which system to get MTU for. UNASSIGNED_SYSTEM_ADDRESS to get the default + /// \return The current MTU size of the target system. + int GetMTUSize( const SystemAddress target ) const; + + /// \brief Returns the number of IP addresses this system has internally. + /// \details Get the actual addresses from GetLocalIP() + unsigned GetNumberOfAddresses( void ); + + /// Returns an IP address at index 0 to GetNumberOfAddresses-1 in ipList array. + /// \param[in] index index into the list of IP addresses + /// \return The local IP address at this index + const char* GetLocalIP( unsigned int index ); + + /// Is this a local IP? + /// Checks if this ip is in the ipList array. + /// \param[in] An IP address to check, excluding the port. + /// \return True if this is one of the IP addresses returned by GetLocalIP + bool IsLocalIP( const char *ip ); + + /// \brief Allow or disallow connection responses from any IP. + /// \details Normally this should be false, but may be necessary when connecting to servers with multiple IP addresses. + /// \param[in] allow - True to allow this behavior, false to not allow. Defaults to false. Value persists between connections. + void AllowConnectionResponseIPMigration( bool allow ); + + /// \brief Sends a one byte message ID_ADVERTISE_SYSTEM to the remote unconnected system. + /// This will send our external IP outside the LAN along with some user data to the remote system. + /// \pre The sender and recipient must already be started via a successful call to Initialize + /// \param[in] host Either a dotted IP address or a domain name + /// \param[in] remotePort Which port to connect to on the remote machine. + /// \param[in] data Optional data to append to the packet. + /// \param[in] dataLength Length of data in bytes. Use 0 if no data. + /// \param[in] connectionSocketIndex Index into the array of socket descriptors passed to socketDescriptors in RakPeer::Startup() to send on. + /// \return False if IsActive()==false or the host is unresolvable. True otherwise. + bool AdvertiseSystem( const char *host, unsigned short remotePort, const char *data, int dataLength, unsigned connectionSocketIndex=0 ); + + /// \brief Controls how often to return ID_DOWNLOAD_PROGRESS for large message downloads. + /// \details ID_DOWNLOAD_PROGRESS is returned to indicate a new partial message chunk, roughly the MTU size, has arrived. + /// As it can be slow or cumbersome to get this notification for every chunk, you can set the interval at which it is returned. + /// Defaults to 0 (never return this notification). + /// \param[in] interval How many messages to use as an interval before a download progress notification is returned. + void SetSplitMessageProgressInterval(int interval); + + /// \brief Returns what was passed to SetSplitMessageProgressInterval(). + /// \return Number of messages to be recieved before a download progress notification is returned. Default to 0. + int GetSplitMessageProgressInterval(void) const; + + /// \brief Set how long to wait before giving up on sending an unreliable message. + /// Useful if the network is clogged up. + /// Set to 0 or less to never timeout. Defaults to 0. + /// \param[in] timeoutMS How many ms to wait before simply not sending an unreliable message. + void SetUnreliableTimeout(RakNetTime timeoutMS); + + /// \brief Send a message to a host, with the IP socket option TTL set to 3. + /// \details This message will not reach the host, but will open the router. + /// \param[in] host The address of the remote host in dotted notation. + /// \param[in] remotePort The port number to send to. + /// \param[in] ttl Max hops of datagram, set to 3 + /// \param[in] connectionSocketIndex userConnectionSocketIndex. + /// \remarks Used for NAT-Punchthrough + void SendTTL( const char* host, unsigned short remotePort, int ttl, unsigned connectionSocketIndex=0 ); + + // --------------------------------------------------------------------------------------------Compression Functions - Functions related to the compression layer-------------------------------------------------------------------------------------------- + /// \brief Enables or disables frequency table tracking. + /// \details This is required to get a frequency table, which is used in GenerateCompressionLayer(). + /// This value persists between connect calls and defaults to false (no frequency tracking). + /// \pre You can call this at any time - however you SHOULD only call it when disconnected. Otherwise you will only trackpart of the values sent over the network. + /// \param[in] doCompile True to enable tracking + void SetCompileFrequencyTable( bool doCompile ); + + /// \brief Returns the frequency of outgoing bytes into outputFrequencyTable + /// The purpose is to save to file as either a master frequency table from a sample game session for passing to + /// GenerateCompressionLayer() + /// \pre You should only call this when disconnected. Requires that you first enable data frequency tracking by calling SetCompileFrequencyTable(true) + /// \param[out] outputFrequencyTable The frequency of each corresponding byte + /// \return False (failure) if connected or if frequency table tracking is not enabled. Otherwise true (success) + bool GetOutgoingFrequencyTable( unsigned int outputFrequencyTable[ 256 ] ); + + /// \brief This is an optional function to generate the compression layer based on the input frequency table. + /// \details If you want to use it you should call this twice - once with inputLayer as true and once as false. + /// The frequency table passed here with inputLayer=true should match the frequency table on the recipient with inputLayer=false. + /// Likewise, the frequency table passed here with inputLayer=false should match the frequency table on the recipient with inputLayer=true. + /// Calling this function when there is an existing layer will overwrite the old layer. + /// \pre You should only call this when disconnected. + /// \param[in] inputFrequencyTable A frequency table for your data returned from GetOutgoingFrequencyTable() + /// \param[in] inputLayer Whether inputFrequencyTable represents incoming data from other systems (true) or outgoing data from this system (false). + /// \return false (failure) if connected. Otherwise true (success) + /// \sa Compression.cpp + bool GenerateCompressionLayer( unsigned int inputFrequencyTable[ 256 ], bool inputLayer ); + + /// \brief Delete the output or input layer as specified. + /// + /// This is not necessary to call and is only useful for freeing memory. + /// \pre You should only call this when disconnected. + /// \param[in] inputLayer True to mean the inputLayer, false to mean the output layer. + /// \return False (failure) if connected. Otherwise True (success). + bool DeleteCompressionLayer( bool inputLayer ); + + /// \brief Returns the compression ratio. + /// \details A low compression ratio is good. Compression is defined for outgoing data. + /// \return The compression ratio + float GetCompressionRatio( void ) const; + + /// \brief Returns the decompression ratio. + /// \details A high decompression ratio is good. Decompression ratio is defined for incoming data. + /// \return The decompression ratio + float GetDecompressionRatio( void ) const; + + // -------------------------------------------------------------------------------------------- Plugin Functions-------------------------------------------------------------------------------------------- + /// \brief Attatches a Plugin interface to an instance of the base class (RakPeer or PacketizedTCP) to run code automatically on message receipt in the Receive call. + /// \note If plugins have dependencies on each other then the order does matter - for example the router plugin should go first because it might route messages for other plugins. + /// \param[in] messageHandler Pointer to the plugin to attach. + void AttachPlugin( PluginInterface2 *plugin ); + + /// \brief Detaches a Plugin interface from the instance of the base class (RakPeer or PacketizedTCP) it is attached to. + /// \details This method disables the plugin code from running automatically on base class's updates or message receipt. + /// \param[in] messageHandler Pointer to a plugin to detach. + void DetachPlugin( PluginInterface2 *messageHandler ); + + // --------------------------------------------------------------------------------------------Miscellaneous Functions-------------------------------------------------------------------------------------------- + /// \brief Puts a message back in the receive queue in case you don't want to deal with it immediately. + /// \param[in] packet The pointer to the packet you want to push back. + /// \param[in] pushAtHead True to push the packet at the start of the queue so that the next receive call returns it. False to push it at the end of the queue. + /// \note Setting pushAtHead to false end makes the packets out of order. + void PushBackPacket( Packet *packet, bool pushAtHead ); + + /// ------------------------------------------- Deprecated ------------------------- + /// \Internal + /// \deprecated This was added without considering proper architecture + // \param[in] routerInterface The router to use to route messages to systems not directly connected to this system. + /// ------------------------------------------- Deprecated ------------------------- + void SetRouterInterface( RouterInterface *routerInterface ); + + /// \Internal + /// \deprecated This was added without considering proper architecture + // \param[in] routerInterface The router to use to route messages to systems not directly connected to this system. + void RemoveRouterInterface( RouterInterface *routerInterface ); + + /// \internal + /// \brief For a given system identified by \a guid, change the SystemAddress to send to. + /// \param[in] guid The connection we are referring to + /// \param[in] systemAddress The new address to send to + void ChangeSystemAddress(RakNetGUID guid, SystemAddress systemAddress); + + /// \brief Returns a packet for you to write to if you want to create a Packet for some reason. + /// You can add it to the receive buffer with PushBackPacket + /// \param[in] dataSize How many bytes to allocate for the buffer + /// \return A packet. + Packet* AllocatePacket(unsigned dataSize); + + /// \brief Get the socket used with a particular active connection. + /// The smart pointer reference counts the RakNetSocket object, so the socket will remain active as long as the smart pointer does, even if RakNet were to shutdown or close the connection. + /// \note This sends a query to the thread and blocks on the return value for up to one second. In practice it should only take a millisecond or so. + /// \param[in] target Which system. + /// \return A smart pointer object containing the socket information about the target. Be sure to check IsNull() which is returned if the update thread is unresponsive, shutting down, or if this system is not connected. + virtual RakNetSmartPtr GetSocket( const SystemAddress target ); + + /// \brief Gets all sockets in use. + /// \note This sends a query to the thread and blocks on the return value for up to one second. In practice it should only take a millisecond or so. + /// \param[out] sockets List of RakNetSocket structures in use. Sockets will not be closed until \a sockets goes out of scope + virtual void GetSockets( DataStructures::List > &sockets ); + + /// \internal + virtual void WriteOutOfBandHeader(RakNet::BitStream *bitStream, MessageID header); + + /// If you need code to run in the same thread as RakNet's update thread, this function can be used for that + /// \param[in] _userUpdateThreadPtr C callback function + /// \param[in] _userUpdateThreadData Passed to C callback function + virtual void SetUserUpdateThread(void (*_userUpdateThreadPtr)(RakPeerInterface *, void *), void *_userUpdateThreadData); + + // --------------------------------------------------------------------------------------------Network Simulator Functions-------------------------------------------------------------------------------------------- + /// \brief Adds simulated ping and packet loss to the outgoing data flow. + /// \details To simulate bi-directional ping and packet loss, you should call this on both the sender and the recipient, with half the total ping and maxSendBPS values on each. + /// \deprecated Use http://www.jenkinssoftware.com/raknet/forum/index.php?topic=1671.0 instead. + /// \note You can exclude network simulator code with the #define _RELEASE to decrease code size. + /// \note Doesn't work past version 3.6201 + /// \param[in] packetloss Chance to lose a packet. Ranges from 0 to 1. + /// \param[in] minExtraPing The minimum time to delay sends. + /// \param[in] extraPingVariance The additional random time to delay sends. + void ApplyNetworkSimulator( float packetloss, unsigned short minExtraPing, unsigned short extraPingVariance); + + /// \brief Limits how much outgoing bandwidth can be used per-connection. + /// This limit does not apply to the sum of all connections! + /// Exceeding the limit queues up outgoing traffic. + /// \param[in] maxBitsPerSecond Maximum bits per second to send. Use 0 for unlimited (default). Once set, it takes effect immedately and persists until called again. + void SetPerConnectionOutgoingBandwidthLimit( unsigned maxBitsPerSecond ); + + /// Returns true if you previously called ApplyNetworkSimulator. + /// \return Ture if you previously called ApplyNetworkSimulator. False otherwise. + bool IsNetworkSimulatorActive( void ); + + // --------------------------------------------------------------------------------------------Statistical Functions - Functions dealing with API performance-------------------------------------------------------------------------------------------- + + /// \brief Returns a structure containing a large set of network statistics for the specified system. + /// You can map this data to a string using the C style StatisticsToString() function + /// \param[in] systemAddress Which connected system to get statistics for. + /// \param[in] rns If you supply this structure,the network statistics will be written to it. Otherwise the method uses a static struct to write the data, which is not threadsafe. + /// \return 0 if the specified system can't be found. Otherwise a pointer to the struct containing the specified system's network statistics. + /// \sa RakNetStatistics.h + RakNetStatistics * const GetStatistics( const SystemAddress systemAddress, RakNetStatistics *rns=0 ); + /// \brief Returns the network statistics of the system at the given index in the remoteSystemList. + /// \return True if the index is less than the maximum number of peers allowed and the system is active. False otherwise. + bool GetStatistics( const int index, RakNetStatistics *rns ); + + /// \Returns how many messages are waiting when you call Receive() + virtual unsigned int GetReceiveBufferSize(void); + + // --------------------------------------------------------------------------------------------EVERYTHING AFTER THIS COMMENT IS FOR INTERNAL USE ONLY-------------------------------------------------------------------------------------------- + /// \internal + char *GetRPCString( const char *data, const BitSize_t bitSize, const SystemAddress systemAddress); + + /// \internal + bool SendOutOfBand(const char *host, unsigned short remotePort, MessageID header, const char *data, BitSize_t dataLength, unsigned connectionSocketIndex=0 ); + + // static Packet *AllocPacket(unsigned dataSize, const char *file, unsigned int line); + + /// \internal + /// \brief Holds the clock differences between systems, along with the ping + struct PingAndClockDifferential + { + unsigned short pingTime; + RakNetTime clockDifferential; + }; + + /// \internal + /// \brief All the information representing a connected system system + struct RemoteSystemStruct + { + bool isActive; // Is this structure in use? + SystemAddress systemAddress; /// Their external IP on the internet + SystemAddress myExternalSystemAddress; /// Your external IP on the internet, from their perspective + SystemAddress theirInternalSystemAddress[MAXIMUM_NUMBER_OF_INTERNAL_IDS]; /// Their internal IP, behind the LAN + ReliabilityLayer reliabilityLayer; /// The reliability layer associated with this player + bool weInitiatedTheConnection; /// True if we started this connection via Connect. False if someone else connected to us. + PingAndClockDifferential pingAndClockDifferential[ PING_TIMES_ARRAY_SIZE ]; /// last x ping times and calculated clock differentials with it + int pingAndClockDifferentialWriteIndex; /// The index we are writing into the pingAndClockDifferential circular buffer + unsigned short lowestPing; ///The lowest ping value encountered + RakNetTime nextPingTime; /// When to next ping this player + RakNetTime lastReliableSend; /// When did the last reliable send occur. Reliable sends must occur at least once every timeoutTime/2 units to notice disconnects + RakNetTime connectionTime; /// connection time, if active. + unsigned char AESKey[ 16 ]; /// Security key. + bool setAESKey; /// true if security is enabled. +// int connectionSocketIndex; // index into connectionSockets to send back on. + RPCMap rpcMap; /// Mapping of RPC calls to single byte integers to save transmission bandwidth. + RakNetGUID guid; + int MTUSize; + // The local port which received communication from this remote system + unsigned short rcvPort; + // Reference counted socket to send back on + RakNetSmartPtr rakNetSocket; + + enum ConnectMode {NO_ACTION, DISCONNECT_ASAP, DISCONNECT_ASAP_SILENTLY, DISCONNECT_ON_NO_ACK, REQUESTED_CONNECTION, HANDLING_CONNECTION_REQUEST, UNVERIFIED_SENDER, SET_ENCRYPTION_ON_MULTIPLE_16_BYTE_PACKET, CONNECTED} connectMode; + }; + + // This is done to provide custom RPC handling when in a blocking RPC + Packet* ReceiveIgnoreRPC( void ); + + //void PushPortRefused( const SystemAddress target ); + /// \brief Handles an RPC packet. This packet has an RPC request + /// \param[in] data A packet returned from Receive with the ID ID_RPC + /// \param[in] length The size of the packet data + /// \param[in] systemAddress The sender of the packet + /// \return True on success, false on a bad packet or an unregistered function + char* HandleRPCPacket( const char *data, int length, SystemAddress systemAddress ); + +protected: + + friend RAK_THREAD_DECLARATION(UpdateNetworkLoop); + friend RAK_THREAD_DECLARATION(RecvFromLoop); + friend RAK_THREAD_DECLARATION(UDTConnect); + + /* +#ifdef _WIN32 + // friend unsigned __stdcall RecvFromNetworkLoop(LPVOID arguments); + friend void __stdcall ProcessPortUnreachable( const unsigned int binaryAddress, const unsigned short port, RakPeer *rakPeer ); + friend void __stdcall ProcessNetworkPacket( const unsigned int binaryAddress, const unsigned short port, const char *data, const int length, RakPeer *rakPeer, unsigned connectionSocketIndex ); + friend unsigned __stdcall UpdateNetworkLoop( LPVOID arguments ); +#else + // friend void* RecvFromNetworkLoop( void* arguments ); + friend void ProcessPortUnreachable( const unsigned int binaryAddress, const unsigned short port, RakPeer *rakPeer ); + friend void ProcessNetworkPacket( const unsigned int binaryAddress, const unsigned short port, const char *data, const int length, RakPeer *rakPeer, unsigned connectionSocketIndex ); + friend void* UpdateNetworkLoop( void* arguments ); +#endif + */ + + friend void ProcessPortUnreachable( const unsigned int binaryAddress, const unsigned short port, RakPeer *rakPeer ); + friend bool ProcessOfflineNetworkPacket( const SystemAddress systemAddress, const char *data, const int length, RakPeer *rakPeer, RakNetSmartPtr rakNetSocket, bool *isOfflineMessage, RakNetTimeUS timeRead ); + friend void ProcessNetworkPacket( const SystemAddress systemAddress, const char *data, const int length, RakPeer *rakPeer, RakNetSmartPtr rakNetSocket, RakNetTimeUS timeRead ); + friend void ProcessNetworkPacket( const SystemAddress systemAddress, const char *data, const int length, RakPeer *rakPeer, RakNetTimeUS timeRead ); + + int GetIndexFromSystemAddress( const SystemAddress systemAddress, bool calledFromNetworkThread ) const; + int GetIndexFromGuid( const RakNetGUID guid ); + + //void RemoveFromRequestedConnectionsList( const SystemAddress systemAddress ); + // Two versions needed because some buggy compilers strip the last parameter if unused, and crashes + bool SendConnectionRequest( const char* host, unsigned short remotePort, const char *passwordData, int passwordDataLength, unsigned connectionSocketIndex, unsigned int extraData, unsigned sendConnectionAttemptCount, unsigned timeBetweenSendConnectionAttemptsMS, RakNetTime timeoutTime, RakNetSmartPtr socket ); + bool SendConnectionRequest( const char* host, unsigned short remotePort, const char *passwordData, int passwordDataLength, unsigned connectionSocketIndex, unsigned int extraData, unsigned sendConnectionAttemptCount, unsigned timeBetweenSendConnectionAttemptsMS, RakNetTime timeoutTime ); + ///Get the reliability layer associated with a systemAddress. + /// \param[in] systemAddress The player identifier + /// \return 0 if none + RemoteSystemStruct *GetRemoteSystemFromSystemAddress( const SystemAddress systemAddress, bool calledFromNetworkThread, bool onlyActive ) const; + RakPeer::RemoteSystemStruct *GetRemoteSystem( const AddressOrGUID systemIdentifier, bool calledFromNetworkThread, bool onlyActive ) const; + void ValidateRemoteSystemLookup(void) const; + RemoteSystemStruct *GetRemoteSystemFromGUID( const RakNetGUID guid, bool onlyActive ) const; + ///Parse out a connection request packet + void ParseConnectionRequestPacket( RakPeer::RemoteSystemStruct *remoteSystem, SystemAddress systemAddress, const char *data, int byteSize); + ///When we get a connection request from an ip / port, accept it unless full + void OnConnectionRequest( RakPeer::RemoteSystemStruct *remoteSystem, unsigned char *AESKey, bool setAESKey ); + void SendConnectionRequestAccepted(RakPeer::RemoteSystemStruct *remoteSystem); + ///Send a reliable disconnect packet to this player and disconnect them when it is delivered + void NotifyAndFlagForShutdown( const SystemAddress systemAddress, bool performImmediate, unsigned char orderingChannel, PacketPriority disconnectionNotificationPriority ); + ///Returns how many remote systems initiated a connection to us + unsigned short GetNumberOfRemoteInitiatedConnections( void ) const; + /// \brief Get a free remote system from the list and assign our systemAddress to it. + /// \note Should only be called from the update thread - not the user thread. + /// \param[in] systemAddress systemAddress to be assigned + /// \param[in] connectionMode connection mode of the RemoteSystem. + /// \param[in] rakNetSocket + /// \param[in] thisIPConnectedRecently Is this IP connected recently? set to False; + /// \param[in] bindingAddress Address to be binded with the remote system + /// \param[in] incomingMTU MTU for the remote system + RemoteSystemStruct * AssignSystemAddressToRemoteSystemList( const SystemAddress systemAddress, RemoteSystemStruct::ConnectMode connectionMode, RakNetSmartPtr incomingRakNetSocket, bool *thisIPConnectedRecently, SystemAddress bindingAddress, int incomingMTU, RakNetGUID guid, unsigned short rcvPort ); + /// \brief Adjust the timestamp of the incoming packet to be relative to this system. + /// \param[in] data Data in the incoming packet. + /// \param[in] systemAddress Sender of the incoming packet. + void ShiftIncomingTimestamp( unsigned char *data, SystemAddress systemAddress ) const; + /// Get the most accurate clock differential for a certain player. + /// \param[in] systemAddress The player with whose clock the time difference is calculated. + /// \returns The clock differential for a certain player. + RakNetTime GetBestClockDifferential( const SystemAddress systemAddress ) const; + + /// \brief Handles an RPC reply packet. The reply packet has data returned from an RPC call + /// \param[in] data A packet returned from Receive with the ID ID_RPC + /// \param[in] length The size of the packet data + /// \param[in] systemAddress The sender of the packet + void HandleRPCReplyPacket( const char *data, int length, SystemAddress systemAddress ); + + bool IsLoopbackAddress(const AddressOrGUID &systemIdentifier, bool matchPort) const; + SystemAddress GetLoopbackAddress(void) const; + + ///Set this to true to terminate the Peer thread execution + volatile bool endThreads; + ///true if the peer thread is active. + volatile bool isMainLoopThreadActive,isRecvFromLoopThreadActive; + bool occasionalPing; /// Do we occasionally ping the other systems?*/ + ///Store the maximum number of peers allowed to connect + unsigned short maximumNumberOfPeers; + //05/02/06 Just using maximumNumberOfPeers instead + ///Store the maximum number of peers able to connect, including reserved connection slots for pings, etc. + //unsigned short remoteSystemListSize; + ///Store the maximum incoming connection allowed + unsigned short maximumIncomingConnections; + RakNet::BitStream offlinePingResponse; + ///Local Player ID + SystemAddress mySystemAddress[MAXIMUM_NUMBER_OF_INTERNAL_IDS]; + char incomingPassword[256]; + unsigned char incomingPasswordLength; + + /// This is an array of pointers to RemoteSystemStruct + /// This allows us to preallocate the list when starting, so we don't have to allocate or delete at runtime. + /// Another benefit is that is lets us add and remove active players simply by setting systemAddress + /// and moving elements in the list by copying pointers variables without affecting running threads, even if they are in the reliability layer + RemoteSystemStruct* remoteSystemList; + + // Use a hash, with binaryAddress plus port mod length as the index + RemoteSystemIndex **remoteSystemLookup; + unsigned int RemoteSystemLookupHashIndex(SystemAddress sa) const; + void ReferenceRemoteSystem(SystemAddress sa, unsigned int remoteSystemListIndex); + void DereferenceRemoteSystem(SystemAddress sa); + RemoteSystemStruct* GetRemoteSystem(SystemAddress sa) const; + unsigned int GetRemoteSystemIndex(SystemAddress sa) const; + void ClearRemoteSystemLookup(void); + DataStructures::MemoryPool remoteSystemIndexPool; + +// unsigned int LookupIndexUsingHashIndex(SystemAddress sa) const; +// unsigned int RemoteSystemListIndexUsingHashIndex(SystemAddress sa) const; +// unsigned int FirstFreeRemoteSystemLookupIndex(SystemAddress sa) const; + + enum + { + // Only put these mutexes in user thread functions! +#ifdef _RAKNET_THREADSAFE + requestedConnectionList_Mutex, +#endif + offlinePingResponse_Mutex, + NUMBER_OF_RAKPEER_MUTEXES + }; + SimpleMutex rakPeerMutexes[ NUMBER_OF_RAKPEER_MUTEXES ]; + ///RunUpdateCycle is not thread safe but we don't need to mutex calls. Just skip calls if it is running already + + bool updateCycleIsRunning; + ///The list of people we have tried to connect to recently + + //DataStructures::Queue requestedConnectionsList; + ///Data that both the client and the server needs + + unsigned int bytesSentPerSecond, bytesReceivedPerSecond; + // bool isSocketLayerBlocking; + // bool continualPing,isRecvfromThreadActive,isMainLoopThreadActive, endThreads, isSocketLayerBlocking; + unsigned int validationInteger; + SimpleMutex incomingQueueMutex, banListMutex; //,synchronizedMemoryQueueMutex, automaticVariableSynchronizationMutex; + //DataStructures::Queue incomingpacketSingleProducerConsumer; //, synchronizedMemorypacketSingleProducerConsumer; + // BitStream enumerationData; + + struct BanStruct + { + char *IP; + RakNetTime timeout; // 0 for none + }; + + struct RequestedConnectionStruct + { + SystemAddress systemAddress; + RakNetTime nextRequestTime; + unsigned char requestsMade; + char *data; + unsigned short dataLength; + char outgoingPassword[256]; + unsigned char outgoingPasswordLength; + unsigned socketIndex; + unsigned int extraData; + unsigned sendConnectionAttemptCount; + unsigned timeBetweenSendConnectionAttemptsMS; + RakNetTime timeoutTime; + RakNetSmartPtr socket; + enum {CONNECT=1, /*PING=2, PING_OPEN_CONNECTIONS=4,*/ /*ADVERTISE_SYSTEM=2*/} actionToTake; + }; + + //DataStructures::List* > automaticVariableSynchronizationList; + DataStructures::List banList; + DataStructures::List messageHandlerList; + + DataStructures::Queue requestedConnectionQueue; + SimpleMutex requestedConnectionQueueMutex; + + /// Compression stuff + unsigned int frequencyTable[ 256 ]; + HuffmanEncodingTree *inputTree, *outputTree; + unsigned int rawBytesSent, rawBytesReceived, compressedBytesSent, compressedBytesReceived; + // void DecompressInput(RakNet::BitStream *bitStream); + // void UpdateOutgoingFrequencyTable(RakNet::BitStream * bitStream); + void GenerateSYNCookieRandomNumber( void ); + void SecuredConnectionResponse( const SystemAddress systemAddress ); + void SecuredConnectionConfirmation( RakPeer::RemoteSystemStruct * remoteSystem, char* data ); + bool RunUpdateCycle( void ); + // void RunMutexedUpdateCycle(void); + + struct BufferedCommandStruct + { + BitSize_t numberOfBitsToSend; + PacketPriority priority; + PacketReliability reliability; + char orderingChannel; + AddressOrGUID systemIdentifier; + bool broadcast; + RemoteSystemStruct::ConnectMode connectionMode; + NetworkID networkID; + bool blockingCommand; // Only used for RPC + char *data; + bool haveRakNetCloseSocket; + unsigned connectionSocketIndex; + unsigned short remotePortRakNetWasStartedOn_PS3; + SOCKET socket; + unsigned short port; + uint32_t receipt; + enum {BCS_SEND, BCS_CLOSE_CONNECTION, BCS_GET_SOCKET, BCS_CHANGE_SYSTEM_ADDRESS,/* BCS_USE_USER_SOCKET, BCS_REBIND_SOCKET_ADDRESS, BCS_RPC, BCS_RPC_SHIFT,*/ BCS_DO_NOTHING} command; + }; + + // Single producer single consumer queue using a linked list + //BufferedCommandStruct* bufferedCommandReadIndex, bufferedCommandWriteIndex; + +#ifndef _RAKNET_THREADSAFE + DataStructures::SingleProducerConsumer bufferedCommands; +#else + DataStructures::ThreadsafeAllocatingQueue bufferedCommands; +#endif + + // Constructor not called! + struct RecvFromStruct + { +#if (defined(_XBOX) || defined(_X360)) && defined(RAKNET_USE_VDP) + char data[MAXIMUM_MTU_SIZE*2]; +#else + char data[MAXIMUM_MTU_SIZE]; +#endif + int bytesRead; + SystemAddress systemAddress; + RakNetTimeUS timeRead; + SOCKET s; + unsigned short remotePortRakNetWasStartedOn_PS3; + }; + +#ifndef _RAKNET_THREADSAFE + DataStructures::SingleProducerConsumer bufferedPackets; +#else + DataStructures::ThreadsafeAllocatingQueue bufferedPackets; +#endif + + struct SocketQueryOutput + { + SocketQueryOutput() {} + ~SocketQueryOutput() {} + DataStructures::List > sockets; + }; +#ifndef _RAKNET_THREADSAFE + DataStructures::SingleProducerConsumer< SocketQueryOutput > socketQueryOutput; +#else + DataStructures::ThreadsafeAllocatingQueue socketQueryOutput; +#endif + + bool AllowIncomingConnections(void) const; + + void PingInternal( const SystemAddress target, bool performImmediate, PacketReliability reliability ); + bool ValidSendTarget(SystemAddress systemAddress, bool broadcast); + // This stores the user send calls to be handled by the update thread. This way we don't have thread contention over systemAddresss + void CloseConnectionInternal( const AddressOrGUID& systemIdentifier, bool sendDisconnectionNotification, bool performImmediate, unsigned char orderingChannel, PacketPriority disconnectionNotificationPriority ); + void SendBuffered( const char *data, BitSize_t numberOfBitsToSend, PacketPriority priority, PacketReliability reliability, char orderingChannel, const AddressOrGUID systemIdentifier, bool broadcast, RemoteSystemStruct::ConnectMode connectionMode, uint32_t receipt ); + void SendBufferedList( const char **data, const int *lengths, const int numParameters, PacketPriority priority, PacketReliability reliability, char orderingChannel, const AddressOrGUID systemIdentifier, bool broadcast, RemoteSystemStruct::ConnectMode connectionMode, uint32_t receipt ); + bool SendImmediate( char *data, BitSize_t numberOfBitsToSend, PacketPriority priority, PacketReliability reliability, char orderingChannel, const AddressOrGUID systemIdentifier, bool broadcast, bool useCallerDataAllocation, RakNetTimeUS currentTime, uint32_t receipt ); + //bool HandleBufferedRPC(BufferedCommandStruct *bcs, RakNetTime time); + void ClearBufferedCommands(void); + void ClearBufferedPackets(void); + void ClearSocketQueryOutput(void); + void ClearRequestedConnectionList(void); + void AddPacketToProducer(Packet *p); + unsigned int GenerateSeedFromGuid(void); + SimpleMutex securityExceptionMutex; + + //DataStructures::AVLBalancedBinarySearchTree rpcTree; + RPCMap rpcMap; // Can't use StrPtrHash because runtime insertions will screw up the indices + int defaultMTUSize; + bool trackFrequencyTable; + int threadSleepTimer; + + // Smart pointer so I can return the object to the user + DataStructures::List > socketList; + void DerefAllSockets(void); + unsigned int GetRakNetSocketFromUserConnectionSocketIndex(unsigned int userIndex) const; + // Used for RPC replies + RakNet::BitStream *replyFromTargetBS; + SystemAddress replyFromTargetPlayer; + bool replyFromTargetBroadcast; + + RakNetTime defaultTimeoutTime; + + // Problem: + // Waiting in function A: + // Wait function gets RPC B: + // + bool blockOnRPCReply; + + // For redirecting sends through the router plugin. Unfortunate I have to use this architecture. + RouterInterface *router; + + // Generate and store a unique GUID + void GenerateGUID(void); + unsigned int GetSystemIndexFromGuid( const RakNetGUID input ) const; + RakNetGUID myGuid; + + unsigned maxOutgoingBPS; + + // Nobody would use the internet simulator in a final build. +#ifdef _DEBUG + double _packetloss; + unsigned short _minExtraPing, _extraPingVariance; +#endif + +#if !defined(_XBOX) && !defined(_WIN32_WCE) && !defined(X360) + /// Encryption and security + RSACrypt rsacrypt; + uint32_t publicKeyE; + uint32_t publicKeyN[RAKNET_RSA_FACTOR_LIMBS]; + bool keysLocallyGenerated, usingSecurity; + RakNetTime randomNumberExpirationTime; + unsigned char newRandomNumber[ 20 ], oldRandomNumber[ 20 ]; + + + /* + big::RSACrypt rsacrypt; + big::u32 publicKeyE; + RSA_BIT_SIZE publicKeyN; + bool keysLocallyGenerated, usingSecurity; + RakNetTime randomNumberExpirationTime; + unsigned char newRandomNumber[ 20 ], oldRandomNumber[ 20 ]; + */ +#endif + + ///How long it has been since things were updated by a call to receiveUpdate thread uses this to determine how long to sleep for + //unsigned int lastUserUpdateCycle; + /// True to allow connection accepted packets from anyone. False to only allow these packets from servers we requested a connection to. + bool allowConnectionResponseIPMigration; + + SystemAddress firstExternalID; + int splitMessageProgressInterval; + RakNetTime unreliableTimeout; + + + // Used for object lookup for RPC (actually deprecated, since RPC is deprecated) + NetworkIDManager *networkIDManager; + // Systems in this list will not go through the secure connection process, even when secure connections are turned on. Wildcards are accepted. + DataStructures::List securityExceptionList; + + char ipList[ MAXIMUM_NUMBER_OF_INTERNAL_IDS ][ 16 ]; + unsigned int binaryAddresses[MAXIMUM_NUMBER_OF_INTERNAL_IDS]; + + bool allowInternalRouting; + + void (*userUpdateThreadPtr)(RakPeerInterface *, void *); + void *userUpdateThreadData; + + + SignaledEvent quitAndDataEvents; + bool limitConnectionFrequencyFromTheSameIP; + + SimpleMutex packetAllocationPoolMutex; + DataStructures::MemoryPool packetAllocationPool; + + SimpleMutex packetReturnMutex; + DataStructures::Queue packetReturnQueue; + Packet *AllocPacket(unsigned dataSize, const char *file, unsigned int line); + Packet *AllocPacket(unsigned dataSize, unsigned char *data, const char *file, unsigned int line); + + /// This is used to return a number to the user when they call Send identifying the message + /// This number will be returned back with ID_SND_RECEIPT_ACKED or ID_SND_RECEIPT_LOSS and is only returned + /// with the reliability types that contain RECEIPT in the name + SimpleMutex sendReceiptSerialMutex; + uint32_t sendReceiptSerial; + void ResetSendReceipt(void); +}; + +#endif diff --git a/RakNet/Sources/RakPeerInterface.h b/RakNet/Sources/RakPeerInterface.h new file mode 100644 index 0000000..4bc12c1 --- /dev/null +++ b/RakNet/Sources/RakPeerInterface.h @@ -0,0 +1,680 @@ +/// \file +/// \brief An interface for RakPeer. Simply contains all user functions as pure virtuals. +/// +/// This file is part of RakNet Copyright 2003 Jenkins Software LLC +/// +/// Usage of RakNet is subject to the appropriate license agreement. + + +#ifndef __RAK_PEER_INTERFACE_H +#define __RAK_PEER_INTERFACE_H + +#include "PacketPriority.h" +#include "RakNetTypes.h" +#include "RakMemoryOverride.h" +#include "Export.h" +#include "DS_List.h" +#include "RakNetSmartPtr.h" +#include "RakNetSocket.h" + +// Forward declarations +namespace RakNet +{ + class BitStream; +} +class PluginInterface2; +struct RPCMap; +struct RakNetStatistics; +struct RakNetBandwidth; +class RouterInterface; +class NetworkIDManager; + +/// The primary interface for RakNet, RakPeer contains all major functions for the library. +/// See the individual functions for what the class can do. +/// \brief The main interface for network communications +class RAK_DLL_EXPORT RakPeerInterface +{ +public: + ///Destructor + virtual ~RakPeerInterface() {} + + // --------------------------------------------------------------------------------------------Major Low Level Functions - Functions needed by most users-------------------------------------------------------------------------------------------- + /// \brief Starts the network threads, opens the listen port. + /// \details You must call this before calling Connect(). + /// Multiple calls while already active are ignored. To call this function again with different settings, you must first call Shutdown(). + /// \note Call SetMaximumIncomingConnections if you want to accept incoming connections + /// \note Set _RAKNET_THREADSAFE in RakNetDefines.h if you want to call RakNet functions from multiple threads (not recommended, as it is much slower and RakNet is already asynchronous). + /// \param[in] maxConnections The maximum number of connections between this instance of RakPeer and another instance of RakPeer. Required so the network can preallocate and for thread safety. A pure client would set this to 1. A pure server would set it to the number of allowed clients.- A hybrid would set it to the sum of both types of connections + /// \param[in] localPort The port to listen for connections on. + /// \param[in] _threadSleepTimer How many ms to Sleep each internal update cycle. With new congestion control, the best results will be obtained by passing 10. + /// \param[in] socketDescriptors An array of SocketDescriptor structures to force RakNet to listen on a particular IP address or port (or both). Each SocketDescriptor will represent one unique socket. Do not pass redundant structures. To listen on a specific port, you can pass SocketDescriptor(myPort,0); such as for a server. For a client, it is usually OK to just pass SocketDescriptor(); + /// \param[in] socketDescriptorCount The size of the \a socketDescriptors array. Pass 1 if you are not sure what to pass. + /// \param[in] threadPriority Passed to thread creation routine. Use THREAD_PRIORITY_NORMAL for Windows. WARNING!!! On Linux, 0 means highest priority! You MUST set this to something valid based on the values used by your other threads + /// \return False on failure (can't create socket or thread), true on success. + virtual bool Startup( unsigned short maxConnections, int _threadSleepTimer, SocketDescriptor *socketDescriptors, unsigned socketDescriptorCount, int threadPriority=-99999 )=0; + + /// Secures connections though a combination of SHA1, AES128, SYN Cookies, and RSA to prevent connection spoofing, replay attacks, data eavesdropping, packet tampering, and MitM attacks. + /// There is a significant amount of processing and a slight amount of bandwidth overhead for this feature. + /// If you accept connections, you must call this or else secure connections will not be enabled for incoming connections. + /// If you are connecting to another system, you can call this with values for the (e and p,q) public keys before connecting to prevent MitM + /// \pre Must be called while offline + /// \param[in] pubKeyE A pointer to the public keys from the RSACrypt class. + /// \param[in] pubKeyN A pointer to the public keys from the RSACrypt class. + /// \param[in] privKeyP Public key generated from the RSACrypt class. + /// \param[in] privKeyQ Public key generated from the RSACrypt class. If the private keys are 0, then a new key will be generated when this function is called@see the Encryption sample + virtual void InitializeSecurity(const char *pubKeyE, const char *pubKeyN, const char *privKeyP, const char *privKeyQ )=0; + + /// Disables all security. + /// \note Must be called while offline + virtual void DisableSecurity( void )=0; + + /// If secure connections are on, do not use secure connections for a specific IP address. + /// This is useful if you have a fixed-address internal server behind a LAN. + /// \note Secure connections are determined by the recipient of an incoming connection. This has no effect if called on the system attempting to connect. + /// \param[in] ip IP address to add. * wildcards are supported. + virtual void AddToSecurityExceptionList(const char *ip)=0; + + /// Remove a specific connection previously added via AddToSecurityExceptionList + /// \param[in] ip IP address to remove. Pass 0 to remove all IP addresses. * wildcards are supported. + virtual void RemoveFromSecurityExceptionList(const char *ip)=0; + + /// Checks to see if a given IP is in the security exception list + /// \param[in] IP address to check. + virtual bool IsInSecurityExceptionList(const char *ip)=0; + + /// Sets how many incoming connections are allowed. If this is less than the number of players currently connected, + /// no more players will be allowed to connect. If this is greater than the maximum number of peers allowed, + /// it will be reduced to the maximum number of peers allowed. + /// Defaults to 0, meaning by default, nobody can connect to you + /// \param[in] numberAllowed Maximum number of incoming connections allowed. + virtual void SetMaximumIncomingConnections( unsigned short numberAllowed )=0; + + /// Returns the value passed to SetMaximumIncomingConnections() + /// \return the maximum number of incoming connections, which is always <= maxConnections + virtual unsigned short GetMaximumIncomingConnections( void ) const=0; + + /// Returns how many open connections there are at this time + /// \return the number of open connections + virtual unsigned short NumberOfConnections(void) const=0; + + /// Sets the password incoming connections must match in the call to Connect (defaults to none). Pass 0 to passwordData to specify no password + /// This is a way to set a low level password for all incoming connections. To selectively reject connections, implement your own scheme using CloseConnection() to remove unwanted connections + /// \param[in] passwordData A data block that incoming connections must match. This can be just a password, or can be a stream of data. Specify 0 for no password data + /// \param[in] passwordDataLength The length in bytes of passwordData + virtual void SetIncomingPassword( const char* passwordData, int passwordDataLength )=0; + + /// Gets the password passed to SetIncomingPassword + /// \param[out] passwordData Should point to a block large enough to hold the password data you passed to SetIncomingPassword() + /// \param[in,out] passwordDataLength Maximum size of the array passwordData. Modified to hold the number of bytes actually written + virtual void GetIncomingPassword( char* passwordData, int *passwordDataLength )=0; + + /// \brief Connect to the specified host (ip or domain name) and server port. + /// Calling Connect and not calling SetMaximumIncomingConnections acts as a dedicated client. + /// Calling both acts as a true peer. This is a non-blocking connection. + /// You know the connection is successful when IsConnected() returns true or Receive() gets a message with the type identifier ID_CONNECTION_ACCEPTED. + /// If the connection is not successful, such as a rejected connection or no response then neither of these things will happen. + /// \pre Requires that you first call Initialize + /// \param[in] host Either a dotted IP address or a domain name + /// \param[in] remotePort Which port to connect to on the remote machine. + /// \param[in] passwordData A data block that must match the data block on the server passed to SetIncomingPassword. This can be a string or can be a stream of data. Use 0 for no password. + /// \param[in] passwordDataLength The length in bytes of passwordData + /// \param[in] connectionSocketIndex Index into the array of socket descriptors passed to socketDescriptors in RakPeer::Startup() to send on. + /// \param[in] sendConnectionAttemptCount How many datagrams to send to the other system to try to connect. + /// \param[in] timeBetweenSendConnectionAttemptsMS How often to send datagrams to the other system to try to connect. After this many times, ID_CONNECTION_ATTEMPT_FAILED is returned + /// \param[in] timeoutTime How long to keep the connection alive before dropping it on unable to send a reliable message. 0 to use the default from SetTimeoutTime(UNASSIGNED_SYSTEM_ADDRESS); + /// \return True on successful initiation. False if you are already connected to this system, a connection to the system is pending, the domain name cannot be resolved, incorrect parameters, internal error, or too many existing peers. Returning true does not mean you connected! + virtual bool Connect( const char* host, unsigned short remotePort, const char *passwordData, int passwordDataLength, unsigned connectionSocketIndex=0, unsigned sendConnectionAttemptCount=12, unsigned timeBetweenSendConnectionAttemptsMS=500, RakNetTime timeoutTime=0 )=0; + + /// \brief Connect to the specified host (ip or domain name) and server port, using a shared socket from another instance of RakNet + /// \param[in] host Either a dotted IP address or a domain name + /// \param[in] remotePort Which port to connect to on the remote machine. + /// \param[in] passwordData A data block that must match the data block on the server passed to SetIncomingPassword. This can be a string or can be a stream of data. Use 0 for no password. + /// \param[in] passwordDataLength The length in bytes of passwordData + /// \param[in] socket A bound socket returned by another instance of RakPeerInterface + /// \param[in] sendConnectionAttemptCount How many datagrams to send to the other system to try to connect. + /// \param[in] timeBetweenSendConnectionAttemptsMS How often to send datagrams to the other system to try to connect. After this many times, ID_CONNECTION_ATTEMPT_FAILED is returned + /// \param[in] timeoutTime How long to keep the connection alive before dropping it on unable to send a reliable message. 0 to use the default from SetTimeoutTime(UNASSIGNED_SYSTEM_ADDRESS); + /// \return True on successful initiation. False on incorrect parameters, internal error, or too many existing peers. Returning true does not mean you connected! + virtual bool ConnectWithSocket(const char* host, unsigned short remotePort, const char *passwordData, int passwordDataLength, RakNetSmartPtr socket, unsigned sendConnectionAttemptCount=12, unsigned timeBetweenSendConnectionAttemptsMS=500, RakNetTime timeoutTime=0)=0; + + /// \brief Connect to the specified network ID (Platform specific console function) + /// \details Does built-in NAt traversal + /// \param[in] passwordData A data block that must match the data block on the server passed to SetIncomingPassword. This can be a string or can be a stream of data. Use 0 for no password. + /// \param[in] passwordDataLength The length in bytes of passwordData + //virtual bool Console2LobbyConnect( void *networkServiceId, const char *passwordData, int passwordDataLength )=0; + + /// \brief Stops the network threads and closes all connections. + /// \param[in] blockDuration How long, in milliseconds, you should wait for all remaining messages to go out, including ID_DISCONNECTION_NOTIFICATION. If 0, it doesn't wait at all. + /// \param[in] orderingChannel If blockDuration > 0, ID_DISCONNECTION_NOTIFICATION will be sent on this channel + /// \param[in] disconnectionNotificationPriority Priority to send ID_DISCONNECTION_NOTIFICATION on. + /// If you set it to 0 then the disconnection notification won't be sent + virtual void Shutdown( unsigned int blockDuration, unsigned char orderingChannel=0, PacketPriority disconnectionNotificationPriority=LOW_PRIORITY )=0; + + /// Returns if the network thread is running + /// \return true if the network thread is running, false otherwise + virtual bool IsActive( void ) const=0; + + /// Fills the array remoteSystems with the SystemAddress of all the systems we are connected to + /// \param[out] remoteSystems An array of SystemAddress structures to be filled with the SystemAddresss of the systems we are connected to. Pass 0 to remoteSystems to only get the number of systems we are connected to + /// \param[in, out] numberOfSystems As input, the size of remoteSystems array. As output, the number of elements put into the array + virtual bool GetConnectionList( SystemAddress *remoteSystems, unsigned short *numberOfSystems ) const=0; + + /// Returns the next uint32_t that Send() will return + /// \note If using RakPeer from multiple threads, this may not be accurate for your thread. Use IncrementNextSendReceipt() in that case. + /// \return The next uint32_t that Send() or SendList will return + virtual uint32_t GetNextSendReceipt(void)=0; + + /// Returns the next uint32_t that Send() will return, and increments the value by one + /// \note If using RakPeer from multiple threads, pass this to forceReceipt in the send function + /// \return The next uint32_t that Send() or SendList will return + virtual uint32_t IncrementNextSendReceipt(void)=0; + + /// Sends a block of data to the specified system that you are connected to. + /// This function only works while the connected + /// The first byte should be a message identifier starting at ID_USER_PACKET_ENUM + /// \param[in] data The block of data to send + /// \param[in] length The size in bytes of the data to send + /// \param[in] priority What priority level to send on. See PacketPriority.h + /// \param[in] reliability How reliability to send this data. See PacketPriority.h + /// \param[in] orderingChannel When using ordered or sequenced messages, what channel to order these on. Messages are only ordered relative to other messages on the same stream + /// \param[in] systemIdentifier Who to send this packet to, or in the case of broadcasting who not to send it to. Pass either a SystemAddress structure or a RakNetGUID structure. Use UNASSIGNED_SYSTEM_ADDRESS or to specify none + /// \param[in] broadcast True to send this packet to all connected systems. If true, then systemAddress specifies who not to send the packet to. + /// \param[in] forceReceipt If 0, will automatically determine the receipt number to return. If non-zero, will return what you give it. + /// \return 0 on bad input. Otherwise a number that identifies this message. If \a reliability is a type that returns a receipt, on a later call to Receive() you will get ID_SND_RECEIPT_ACKED or ID_SND_RECEIPT_LOSS with bytes 1-4 inclusive containing this number + virtual uint32_t Send( const char *data, const int length, PacketPriority priority, PacketReliability reliability, char orderingChannel, const AddressOrGUID systemIdentifier, bool broadcast, uint32_t forceReceipt=0 )=0; + + /// "Send" to yourself rather than a remote system. The message will be processed through the plugins and returned to the game as usual + /// This function works anytime + /// The first byte should be a message identifier starting at ID_USER_PACKET_ENUM + /// \param[in] data The block of data to send + /// \param[in] length The size in bytes of the data to send + virtual void SendLoopback( const char *data, const int length )=0; + + /// Sends a block of data to the specified system that you are connected to. Same as the above version, but takes a BitStream as input. + /// \param[in] bitStream The bitstream to send + /// \param[in] priority What priority level to send on. See PacketPriority.h + /// \param[in] reliability How reliability to send this data. See PacketPriority.h + /// \param[in] orderingChannel When using ordered or sequenced messages, what channel to order these on. Messages are only ordered relative to other messages on the same stream + /// \param[in] systemIdentifier Who to send this packet to, or in the case of broadcasting who not to send it to. Pass either a SystemAddress structure or a RakNetGUID structure. Use UNASSIGNED_SYSTEM_ADDRESS or to specify none + /// \param[in] broadcast True to send this packet to all connected systems. If true, then systemAddress specifies who not to send the packet to. + /// \param[in] forceReceipt If 0, will automatically determine the receipt number to return. If non-zero, will return what you give it. + /// \return 0 on bad input. Otherwise a number that identifies this message. If \a reliability is a type that returns a receipt, on a later call to Receive() you will get ID_SND_RECEIPT_ACKED or ID_SND_RECEIPT_LOSS with bytes 1-4 inclusive containing this number + /// \note COMMON MISTAKE: When writing the first byte, bitStream->Write((unsigned char) ID_MY_TYPE) be sure it is casted to a byte, and you are not writing a 4 byte enumeration. + virtual uint32_t Send( const RakNet::BitStream * bitStream, PacketPriority priority, PacketReliability reliability, char orderingChannel, const AddressOrGUID systemIdentifier, bool broadcast, uint32_t forceReceipt=0 )=0; + + /// Sends multiple blocks of data, concatenating them automatically. + /// + /// This is equivalent to: + /// RakNet::BitStream bs; + /// bs.WriteAlignedBytes(block1, blockLength1); + /// bs.WriteAlignedBytes(block2, blockLength2); + /// bs.WriteAlignedBytes(block3, blockLength3); + /// Send(&bs, ...) + /// + /// This function only works while the connected + /// \param[in] data An array of pointers to blocks of data + /// \param[in] lengths An array of integers indicating the length of each block of data + /// \param[in] numParameters Length of the arrays data and lengths + /// \param[in] priority What priority level to send on. See PacketPriority.h + /// \param[in] reliability How reliability to send this data. See PacketPriority.h + /// \param[in] orderingChannel When using ordered or sequenced messages, what channel to order these on. Messages are only ordered relative to other messages on the same stream + /// \param[in] systemIdentifier Who to send this packet to, or in the case of broadcasting who not to send it to. Pass either a SystemAddress structure or a RakNetGUID structure. Use UNASSIGNED_SYSTEM_ADDRESS or to specify none + /// \param[in] broadcast True to send this packet to all connected systems. If true, then systemAddress specifies who not to send the packet to. + /// \param[in] forceReceipt If 0, will automatically determine the receipt number to return. If non-zero, will return what you give it. + /// \return 0 on bad input. Otherwise a number that identifies this message. If \a reliability is a type that returns a receipt, on a later call to Receive() you will get ID_SND_RECEIPT_ACKED or ID_SND_RECEIPT_LOSS with bytes 1-4 inclusive containing this number + virtual uint32_t SendList( const char **data, const int *lengths, const int numParameters, PacketPriority priority, PacketReliability reliability, char orderingChannel, const AddressOrGUID systemIdentifier, bool broadcast, uint32_t forceReceipt=0 )=0; + + /// Gets a message from the incoming message queue. + /// Use DeallocatePacket() to deallocate the message after you are done with it. + /// User-thread functions, such as RPC calls and the plugin function PluginInterface::Update occur here. + /// \return 0 if no packets are waiting to be handled, otherwise a pointer to a packet. + /// \note COMMON MISTAKE: Be sure to call this in a loop, once per game tick, until it returns 0. If you only process one packet per game tick they will buffer up. + /// sa RakNetTypes.h contains struct Packet + virtual Packet* Receive( void )=0; + + virtual Packet* ReceiveIgnoreRPC( void )=0; + + /// Call this to deallocate a message returned by Receive() when you are done handling it. + /// \param[in] packet The message to deallocate. + virtual void DeallocatePacket( Packet *packet )=0; + + /// Return the total number of connections we are allowed + // TODO - rename for RakNet 3.0 + virtual unsigned short GetMaximumNumberOfPeers( void ) const=0; + + // --------------------------------------------------------------------------------------------Remote Procedure Call Functions - Functions to initialize and perform RPC-------------------------------------------------------------------------------------------- + /// \ingroup RAKNET_RPC + /// Register a C or static member function as available for calling as a remote procedure call + /// \param[in] uniqueID A null-terminated unique string to identify this procedure. See RegisterClassMemberRPC() for class member functions. + /// \param[in] functionPointer(...) The name of the function to be used as a function pointer. This can be called whether active or not, and registered functions stay registered unless unregistered + /// \deprecated Use RakNet::RPC3 instead + virtual void RegisterAsRemoteProcedureCall( const char* uniqueID, void ( *functionPointer ) ( RPCParameters *rpcParms ) )=0; + + /// \ingroup RAKNET_RPC + /// Register a C++ member function as available for calling as a remote procedure call. + /// \param[in] uniqueID A null terminated string to identify this procedure. Recommended you use the macro REGISTER_CLASS_MEMBER_RPC to create the string. Use RegisterAsRemoteProcedureCall() for static functions. + /// \param[in] functionPointer The name of the function to be used as a function pointer. This can be called whether active or not, and registered functions stay registered unless unregistered with UnregisterAsRemoteProcedureCall + /// \sa The sample ObjectMemberRPC.cpp + /// \deprecated Use RakNet::RPC3 instead + virtual void RegisterClassMemberRPC( const char* uniqueID, void *functionPointer )=0; + + /// \ingroup RAKNET_RPC + /// Unregisters a C function as available for calling as a remote procedure call that was formerly registered with RegisterAsRemoteProcedureCall. Only call offline. + /// \param[in] uniqueID A string of only letters to identify this procedure. Recommended you use the macro CLASS_MEMBER_ID for class member functions. + /// \deprecated Use RakNet::RPC3 instead + virtual void UnregisterAsRemoteProcedureCall( const char* uniqueID )=0; + + /// \ingroup RAKNET_RPC + /// Used by Object member RPC to lookup objects given that object's ID + /// Also used by the ReplicaManager plugin + /// \param[in] An instance of NetworkIDManager to use for the lookup. + virtual void SetNetworkIDManager( NetworkIDManager *manager )=0; + + /// \return Returns the value passed to SetNetworkIDManager or 0 if never called. + virtual NetworkIDManager *GetNetworkIDManager(void) const=0; + + /// \ingroup RAKNET_RPC + /// Calls a C function on the remote system that was already registered using RegisterAsRemoteProcedureCall(). + /// \param[in] uniqueID A NULL terminated string identifying the function to call. Recommended you use the macro CLASS_MEMBER_ID for class member functions. + /// \param[in] data The data to send + /// \param[in] bitLength The number of bits of \a data + /// \param[in] priority What priority level to send on. See PacketPriority.h. + /// \param[in] reliability How reliability to send this data. See PacketPriority.h. + /// \param[in] orderingChannel When using ordered or sequenced message, what channel to order these on. + /// \param[in] systemAddress Who to send this message to, or in the case of broadcasting who not to send it to. Pass either a SystemAddress structure or a RakNetGUID structure. Use UNASSIGNED_SYSTEM_ADDRESS or to specify none + /// \param[in] broadcast True to send this packet to all connected systems. If true, then systemAddress specifies who not to send the packet to. + /// \param[in] includedTimestamp Pass a timestamp if you wish, to be adjusted in the usual fashion as per ID_TIMESTAMP. Pass 0 to not include a timestamp. + /// \param[in] networkID For static functions, pass UNASSIGNED_NETWORK_ID. For member functions, you must derive from NetworkIDObject and pass the value returned by NetworkIDObject::GetNetworkID for that object. + /// \param[in] replyFromTarget If 0, this function is non-blocking. Otherwise it will block while waiting for a reply from the target procedure, which should be remotely written to RPCParameters::replyToSender and copied to replyFromTarget. The block will return early on disconnect or if the sent packet is unreliable and more than 3X the ping has elapsed. + /// \return True on a successful packet send (this does not indicate the recipient performed the call), false on failure + /// \deprecated Use RakNet::RPC3 + virtual bool RPC( const char* uniqueID, const char *data, BitSize_t bitLength, PacketPriority priority, PacketReliability reliability, char orderingChannel, const AddressOrGUID systemIdentifier, bool broadcast, RakNetTime *includedTimestamp, NetworkID networkID, RakNet::BitStream *replyFromTarget )=0; + + virtual bool RPC( const char* uniqueID, const char *data, BitSize_t bitLength, PacketPriority priority, PacketReliability reliability, char orderingChannel, const AddressOrGUID systemIdentifier, bool broadcast, RakNetTime *includedTimestamp, NetworkID networkID, RakNet::BitStream *replyFromTarget, unsigned char proxyType, SystemAddress proxyTargetAddress )=0; + + /// \ingroup RAKNET_RPC + /// Calls a C function on the remote system that was already registered using RegisterAsRemoteProcedureCall. + /// If you want that function to return data you should call RPC from that system in the same wayReturns true on a successful packet + /// send (this does not indicate the recipient performed the call), false on failure + /// \param[in] uniqueID A NULL terminated string identifying the function to call. Recommended you use the macro CLASS_MEMBER_ID for class member functions. + /// \param[in] data The data to send + /// \param[in] bitLength The number of bits of \a data + /// \param[in] priority What priority level to send on. See PacketPriority.h. + /// \param[in] reliability How reliability to send this data. See PacketPriority.h. + /// \param[in] orderingChannel When using ordered or sequenced message, what channel to order these on. + /// \param[in] systemAddress Who to send this message to, or in the case of broadcasting who not to send it to. Pass either a SystemAddress structure or a RakNetGUID structure. Use UNASSIGNED_SYSTEM_ADDRESS or to specify none + /// \param[in] broadcast True to send this packet to all connected systems. If true, then systemAddress specifies who not to send the packet to. + /// \param[in] includedTimestamp Pass a timestamp if you wish, to be adjusted in the usual fashion as per ID_TIMESTAMP. Pass 0 to not include a timestamp. + /// \param[in] networkID For static functions, pass UNASSIGNED_NETWORK_ID. For member functions, you must derive from NetworkIDObject and pass the value returned by NetworkIDObject::GetNetworkID for that object. + /// \param[in] replyFromTarget If 0, this function is non-blocking. Otherwise it will block while waiting for a reply from the target procedure, which should be remotely written to RPCParameters::replyToSender and copied to replyFromTarget. The block will return early on disconnect or if the sent packet is unreliable and more than 3X the ping has elapsed. + /// \return True on a successful packet send (this does not indicate the recipient performed the call), false on failure + /// \deprecated Use RakNet::RPC3 + virtual bool RPC( const char* uniqueID, const RakNet::BitStream *bitStream, PacketPriority priority, PacketReliability reliability, char orderingChannel, const AddressOrGUID systemIdentifier, bool broadcast, RakNetTime *includedTimestamp, NetworkID networkID, RakNet::BitStream *replyFromTarget )=0; + + virtual bool RPC( const char* uniqueID, const RakNet::BitStream *bitStream, PacketPriority priority, PacketReliability reliability, char orderingChannel, const AddressOrGUID systemIdentifier, bool broadcast, RakNetTime *includedTimestamp, NetworkID networkID, RakNet::BitStream *replyFromTarget, unsigned char proxyType, SystemAddress proxyTargetAddress )=0; + + // -------------------------------------------------------------------------------------------- Connection Management Functions-------------------------------------------------------------------------------------------- + /// Close the connection to another host (if we initiated the connection it will disconnect, if they did it will kick them out). + /// \param[in] target Which system to close the connection to. + /// \param[in] sendDisconnectionNotification True to send ID_DISCONNECTION_NOTIFICATION to the recipient. False to close it silently. + /// \param[in] channel Which ordering channel to send the disconnection notification on, if any + /// \param[in] disconnectionNotificationPriority Priority to send ID_DISCONNECTION_NOTIFICATION on. + virtual void CloseConnection( const SystemAddress target, bool sendDisconnectionNotification, unsigned char orderingChannel=0, PacketPriority disconnectionNotificationPriority=LOW_PRIORITY )=0; + + /// Cancel a pending connection attempt + /// If we are already connected, the connection stays open + /// \param[in] target Which system to cancel + virtual void CancelConnectionAttempt( const SystemAddress target )=0; + + /// Returns if a particular systemAddress has a pending or in progress connection attempt + /// \param[in] systemAddress The SystemAddress we are referring to + virtual bool IsConnectionAttemptPending( const SystemAddress systemAddress )=0; + + /// Returns if a particular systemAddress is connected to us (this also returns true if we are in the process of connecting) + /// \param[in] systemAddress The SystemAddress we are referring to + /// \param[in] includeInProgress If true, also return true for connections that are in progress but haven't completed + /// \param[in] includeDisconnecting If true, also return true for connections that are in the process of disconnecting + /// \return True if this system is connected and active, false otherwise. + virtual bool IsConnected(const AddressOrGUID systemIdentifier, bool includeInProgress=false, bool includeDisconnecting=false)=0; + + /// Given a systemAddress, returns an index from 0 to the maximum number of players allowed - 1. + /// \param[in] systemAddress The SystemAddress we are referring to + /// \return The index of this SystemAddress or -1 on system not found. + virtual int GetIndexFromSystemAddress( const SystemAddress systemAddress ) const=0; + + /// This function is only useful for looping through all systems + /// Given an index, will return a SystemAddress. + /// \param[in] index Index should range between 0 and the maximum number of players allowed - 1. + /// \return The SystemAddress + virtual SystemAddress GetSystemAddressFromIndex( int index )=0; + + /// Same as GetSystemAddressFromIndex but returns RakNetGUID + /// \param[in] index Index should range between 0 and the maximum number of players allowed - 1. + /// \return The RakNetGUID + virtual RakNetGUID GetGUIDFromIndex( int index )=0; + + /// Same as calling GetSystemAddressFromIndex and GetGUIDFromIndex for all systems, but more efficient + /// Indices match each other, so \a addresses[0] and \a guids[0] refer to the same system + /// \param[out] addresses All system addresses. Size of the list is the number of connections. Size of the list will match the size of the \a guids list. + /// \param[out] guids All guids. Size of the list is the number of connections. Size of the list will match the size of the \a addresses list. + virtual void GetSystemList(DataStructures::List &addresses, DataStructures::List &guids)=0; + + /// Bans an IP from connecting. Banned IPs persist between connections but are not saved on shutdown nor loaded on startup. + /// param[in] IP Dotted IP address. Can use * as a wildcard, such as 128.0.0.* will ban all IP addresses starting with 128.0.0 + /// \param[in] milliseconds how many ms for a temporary ban. Use 0 for a permanent ban + virtual void AddToBanList( const char *IP, RakNetTime milliseconds=0 )=0; + + /// Allows a previously banned IP to connect. + /// param[in] Dotted IP address. Can use * as a wildcard, such as 128.0.0.* will banAll IP addresses starting with 128.0.0 + virtual void RemoveFromBanList( const char *IP )=0; + + /// Allows all previously banned IPs to connect. + virtual void ClearBanList( void )=0; + + /// Returns true or false indicating if a particular IP is banned. + /// \param[in] IP - Dotted IP address. + /// \return true if IP matches any IPs in the ban list, accounting for any wildcards. False otherwise. + virtual bool IsBanned( const char *IP )=0; + + /// Enable or disable allowing frequent connections from the same IP adderss + /// This is a security measure which is disabled by default, but can be set to true to prevent attackers from using up all connection slots + /// \param[in] b True to limit connections from the same ip to at most 1 per 100 milliseconds. + virtual void SetLimitIPConnectionFrequency(bool b)=0; + + // --------------------------------------------------------------------------------------------Pinging Functions - Functions dealing with the automatic ping mechanism-------------------------------------------------------------------------------------------- + /// Send a ping to the specified connected system. + /// \pre The sender and recipient must already be started via a successful call to Startup() + /// \param[in] target Which system to ping + virtual void Ping( const SystemAddress target )=0; + + /// Send a ping to the specified unconnected system. The remote system, if it is Initialized, will respond with ID_PONG followed by sizeof(RakNetTime) containing the system time the ping was sent.(Default is 4 bytes - See __GET_TIME_64BIT in RakNetTypes.h + /// System should reply with ID_PONG if it is active + /// \param[in] host Either a dotted IP address or a domain name. Can be 255.255.255.255 for LAN broadcast. + /// \param[in] remotePort Which port to connect to on the remote machine. + /// \param[in] onlyReplyOnAcceptingConnections Only request a reply if the remote system is accepting connections + /// \param[in] connectionSocketIndex Index into the array of socket descriptors passed to socketDescriptors in RakPeer::Startup() to send on. + /// \return true on success, false on failure (unknown hostname) + virtual bool Ping( const char* host, unsigned short remotePort, bool onlyReplyOnAcceptingConnections, unsigned connectionSocketIndex=0 )=0; + + /// Returns the average of all ping times read for the specific system or -1 if none read yet + /// \param[in] systemAddress Which system we are referring to + /// \return The ping time for this system, or -1 + virtual int GetAveragePing( const AddressOrGUID systemIdentifier )=0; + + /// Returns the last ping time read for the specific system or -1 if none read yet + /// \param[in] systemAddress Which system we are referring to + /// \return The last ping time for this system, or -1 + virtual int GetLastPing( const AddressOrGUID systemIdentifier ) const=0; + + /// Returns the lowest ping time read or -1 if none read yet + /// \param[in] systemAddress Which system we are referring to + /// \return The lowest ping time for this system, or -1 + virtual int GetLowestPing( const AddressOrGUID systemIdentifier ) const=0; + + /// Ping the remote systems every so often, or not. This is off by default. Can be called anytime. + /// \param[in] doPing True to start occasional pings. False to stop them. + virtual void SetOccasionalPing( bool doPing )=0; + + // --------------------------------------------------------------------------------------------Static Data Functions - Functions dealing with API defined synchronized memory-------------------------------------------------------------------------------------------- + /// Sets the data to send along with a LAN server discovery or offline ping reply. + /// \a length should be under 400 bytes, as a security measure against flood attacks + /// \param[in] data a block of data to store, or 0 for none + /// \param[in] length The length of data in bytes, or 0 for none + /// \sa Ping.cpp + virtual void SetOfflinePingResponse( const char *data, const unsigned int length )=0; + + /// Returns pointers to a copy of the data passed to SetOfflinePingResponse + /// \param[out] data A pointer to a copy of the data passed to \a SetOfflinePingResponse() + /// \param[out] length A pointer filled in with the length parameter passed to SetOfflinePingResponse() + /// \sa SetOfflinePingResponse + virtual void GetOfflinePingResponse( char **data, unsigned int *length )=0; + + //--------------------------------------------------------------------------------------------Network Functions - Functions dealing with the network in general-------------------------------------------------------------------------------------------- + /// Return the unique address identifier that represents you or another system on the the network and is based on your local IP / port. + /// \param[in] systemAddress Use UNASSIGNED_SYSTEM_ADDRESS to get your behind-LAN address. Use a connected system to get their behind-LAN address + /// \param[in] index When you have multiple internal IDs, which index to return? Currently limited to MAXIMUM_NUMBER_OF_INTERNAL_IDS (so the maximum value of this variable is MAXIMUM_NUMBER_OF_INTERNAL_IDS-1) + /// \return the identifier of your system internally, which may not be how other systems see if you if you are behind a NAT or proxy + virtual SystemAddress GetInternalID( const SystemAddress systemAddress=UNASSIGNED_SYSTEM_ADDRESS, const int index=0 ) const=0; + + /// Return the unique address identifier that represents you on the the network and is based on your externalIP / port + /// (the IP / port the specified player uses to communicate with you) + /// \param[in] target Which remote system you are referring to for your external ID. Usually the same for all systems, unless you have two or more network cards. + virtual SystemAddress GetExternalID( const SystemAddress target ) const=0; + + /// Given a connected system, give us the unique GUID representing that instance of RakPeer. + /// This will be the same on all systems connected to that instance of RakPeer, even if the external system addresses are different + /// Currently O(log(n)), but this may be improved in the future. If you use this frequently, you may want to cache the value as it won't change. + /// Returns UNASSIGNED_RAKNET_GUID if system address can't be found. + /// If \a input is UNASSIGNED_SYSTEM_ADDRESS, will return your own GUID + /// \pre Call Startup() first, or the function will return UNASSIGNED_RAKNET_GUID + /// \param[in] input The system address of the system we are connected to + virtual const RakNetGUID& GetGuidFromSystemAddress( const SystemAddress input ) const=0; + + /// Given the GUID of a connected system, give us the system address of that system. + /// The GUID will be the same on all systems connected to that instance of RakPeer, even if the external system addresses are different + /// Currently O(log(n)), but this may be improved in the future. If you use this frequently, you may want to cache the value as it won't change. + /// If \a input is UNASSIGNED_RAKNET_GUID, will return UNASSIGNED_SYSTEM_ADDRESS + /// \param[in] input The RakNetGUID of the system we are checking to see if we are connected to + virtual SystemAddress GetSystemAddressFromGuid( const RakNetGUID input ) const=0; + + /// Set the time, in MS, to use before considering ourselves disconnected after not being able to deliver a reliable message. + /// Default time is 10,000 or 10 seconds in release and 30,000 or 30 seconds in debug. + /// \param[in] timeMS Time, in MS + /// \param[in] target Which system to do this for. Pass UNASSIGNED_SYSTEM_ADDRESS for all systems. + virtual void SetTimeoutTime( RakNetTime timeMS, const SystemAddress target )=0; + + /// \param[in] target Which system to do this for. Pass UNASSIGNED_SYSTEM_ADDRESS to get the default value + /// \return timeoutTime for a given system. + virtual RakNetTime GetTimeoutTime( const SystemAddress target )=0; + + /// \Deprecated 8/12/09 MTU automatically calculated during connection process + /// Set the MTU per datagram. It's important to set this correctly - otherwise packets will be needlessly split, decreasing performance and throughput. + /// Maximum allowed size is MAXIMUM_MTU_SIZE. + /// Too high of a value will cause packets not to arrive at worst and be fragmented at best. + /// Too low of a value will split packets unnecessarily. + /// sa MTUSize.h + /// \param[in] size The MTU size + /// \param[in] target Which system to set this for. UNASSIGNED_SYSTEM_ADDRESS to set the default, for new systems + /// \pre Can only be called when not connected. + /// \return false on failure (we are connected), else true + // virtual bool SetMTUSize( int size, const SystemAddress target=UNASSIGNED_SYSTEM_ADDRESS )=0; + + /// Returns the current MTU size + /// \param[in] target Which system to get this for. UNASSIGNED_SYSTEM_ADDRESS to get the default + /// \return The current MTU size + virtual int GetMTUSize( const SystemAddress target ) const=0; + + /// Returns the number of IP addresses this system has internally. Get the actual addresses from GetLocalIP() + virtual unsigned GetNumberOfAddresses( void )=0; + + /// Returns an IP address at index 0 to GetNumberOfAddresses-1 + /// \param[in] index index into the list of IP addresses + /// \return The local IP address at this index + virtual const char* GetLocalIP( unsigned int index )=0; + + /// Is this a local IP? + /// \param[in] An IP address to check, excluding the port + /// \return True if this is one of the IP addresses returned by GetLocalIP + virtual bool IsLocalIP( const char *ip )=0; + + /// Allow or disallow connection responses from any IP. Normally this should be false, but may be necessary + /// when connecting to servers with multiple IP addresses. + /// \param[in] allow - True to allow this behavior, false to not allow. Defaults to false. Value persists between connections + virtual void AllowConnectionResponseIPMigration( bool allow )=0; + + /// Sends a one byte message ID_ADVERTISE_SYSTEM to the remote unconnected system. + /// This will tell the remote system our external IP outside the LAN along with some user data. + /// \pre The sender and recipient must already be started via a successful call to Initialize + /// \param[in] host Either a dotted IP address or a domain name + /// \param[in] remotePort Which port to connect to on the remote machine. + /// \param[in] data Optional data to append to the packet. + /// \param[in] dataLength length of data in bytes. Use 0 if no data. + /// \param[in] connectionSocketIndex Index into the array of socket descriptors passed to socketDescriptors in RakPeer::Startup() to send on. + /// \return false if IsActive()==false or the host is unresolvable. True otherwise + virtual bool AdvertiseSystem( const char *host, unsigned short remotePort, const char *data, int dataLength, unsigned connectionSocketIndex=0 )=0; + + /// Controls how often to return ID_DOWNLOAD_PROGRESS for large message downloads. + /// ID_DOWNLOAD_PROGRESS is returned to indicate a new partial message chunk, roughly the MTU size, has arrived + /// As it can be slow or cumbersome to get this notification for every chunk, you can set the interval at which it is returned. + /// Defaults to 0 (never return this notification) + /// \param[in] interval How many messages to use as an interval + virtual void SetSplitMessageProgressInterval(int interval)=0; + + /// Returns what was passed to SetSplitMessageProgressInterval() + /// \return What was passed to SetSplitMessageProgressInterval(). Default to 0. + virtual int GetSplitMessageProgressInterval(void) const=0; + + /// Set how long to wait before giving up on sending an unreliable message + /// Useful if the network is clogged up. + /// Set to 0 or less to never timeout. Defaults to 0. + /// \param[in] timeoutMS How many ms to wait before simply not sending an unreliable message. + virtual void SetUnreliableTimeout(RakNetTime timeoutMS)=0; + + /// Send a message to host, with the IP socket option TTL set to 3 + /// This message will not reach the host, but will open the router. + /// Used for NAT-Punchthrough + virtual void SendTTL( const char* host, unsigned short remotePort, int ttl, unsigned connectionSocketIndex=0 )=0; + + // --------------------------------------------------------------------------------------------Compression Functions - Functions related to the compression layer-------------------------------------------------------------------------------------------- + /// Enables or disables frequency table tracking. This is required to get a frequency table, which is used in GenerateCompressionLayer() + /// This value persists between connect calls and defaults to false (no frequency tracking) + /// \pre You can call this at any time - however you SHOULD only call it when disconnected. Otherwise you will only trackpart of the values sent over the network. + /// \param[in] doCompile True to enable tracking + virtual void SetCompileFrequencyTable( bool doCompile )=0; + + /// Returns the frequency of outgoing bytes into output frequency table + /// The purpose is to save to file as either a master frequency table from a sample game session for passing to + /// GenerateCompressionLayer() + /// \pre You should only call this when disconnected. Requires that you first enable data frequency tracking by calling SetCompileFrequencyTable(true) + /// \param[out] outputFrequencyTable The frequency of each corresponding byte + /// \return False (failure) if connected or if frequency table tracking is not enabled. Otherwise true (success) + virtual bool GetOutgoingFrequencyTable( unsigned int outputFrequencyTable[ 256 ] )=0; + + /// This is an optional function to generate the compression layer based on the input frequency table. + /// If you want to use it you should call this twice - once with inputLayer as true and once as false. + /// The frequency table passed here with inputLayer=true should match the frequency table on the recipient with inputLayer=false. + /// Likewise, the frequency table passed here with inputLayer=false should match the frequency table on the recipient with inputLayer=true. + /// Calling this function when there is an existing layer will overwrite the old layer. + /// \pre You should only call this when disconnected + /// \param[in] inputFrequencyTable A frequency table for your data + /// \param[in] inputLayer Is this the input layer? + /// \return false (failure) if connected. Otherwise true (success) + /// \sa Compression.cpp + virtual bool GenerateCompressionLayer( unsigned int inputFrequencyTable[ 256 ], bool inputLayer )=0; + + /// Delete the output or input layer as specified. This is not necessary to call and is only valuable for freeing memory. + /// \pre You should only call this when disconnected + /// \param[in] inputLayer True to mean the inputLayer, false to mean the output layer + /// \return false (failure) if connected. Otherwise true (success) + virtual bool DeleteCompressionLayer( bool inputLayer )=0; + + /// Returns the compression ratio. A low compression ratio is good. Compression is for outgoing data + /// \return The compression ratio + virtual float GetCompressionRatio( void ) const=0; + + ///Returns the decompression ratio. A high decompression ratio is good. Decompression is for incoming data + /// \return The decompression ratio + virtual float GetDecompressionRatio( void ) const=0; + + // -------------------------------------------------------------------------------------------- Plugin Functions-------------------------------------------------------------------------------------------- + /// Attatches a Plugin interface to run code automatically on message receipt in the Receive call + /// \note If plugins have dependencies on each other then the order does matter - for example the router plugin should go first because it might route messages for other plugins + /// \param[in] messageHandler Pointer to a plugin to attach + virtual void AttachPlugin( PluginInterface2 *plugin )=0; + + /// Detaches a Plugin interface to run code automatically on message receipt + /// \param[in] messageHandler Pointer to a plugin to detach + virtual void DetachPlugin( PluginInterface2 *messageHandler )=0; + + // --------------------------------------------------------------------------------------------Miscellaneous Functions-------------------------------------------------------------------------------------------- + /// Put a message back at the end of the receive queue in case you don't want to deal with it immediately + /// \param[in] packet The packet you want to push back. + /// \param[in] pushAtHead True to push the packet so that the next receive call returns it. False to push it at the end of the queue (obviously pushing it at the end makes the packets out of order) + virtual void PushBackPacket( Packet *packet, bool pushAtHead )=0; + + /// \Internal + // \param[in] routerInterface The router to use to route messages to systems not directly connected to this system. + virtual void SetRouterInterface( RouterInterface *routerInterface )=0; + + /// \Internal + // \param[in] routerInterface The router to use to route messages to systems not directly connected to this system. + virtual void RemoveRouterInterface( RouterInterface *routerInterface )=0; + + /// \internal + /// \brief For a given system identified by \a guid, change the SystemAddress to send to. + /// \param[in] guid The connection we are referring to + /// \param[in] systemAddress The new address to send to + virtual void ChangeSystemAddress(RakNetGUID guid, SystemAddress systemAddress)=0; + + /// \returns a packet for you to write to if you want to create a Packet for some reason. + /// You can add it to the receive buffer with PushBackPacket + /// \param[in] dataSize How many bytes to allocate for the buffer + /// \return A packet you can write to + virtual Packet* AllocatePacket(unsigned dataSize)=0; + + /// Get the socket used with a particular active connection + /// The smart pointer reference counts the RakNetSocket object, so the socket will remain active as long as the smart pointer does, even if RakNet were to shutdown or close the connection. + /// \note This sends a query to the thread and blocks on the return value for up to one second. In practice it should only take a millisecond or so. + /// \param[in] target Which system + /// \return A smart pointer object containing the socket information about the socket. Be sure to check IsNull() which is returned if the update thread is unresponsive, shutting down, or if this system is not connected + virtual RakNetSmartPtr GetSocket( const SystemAddress target )=0; + + /// Get all sockets in use + /// \note This sends a query to the thread and blocks on the return value for up to one second. In practice it should only take a millisecond or so. + /// \param[out] sockets List of RakNetSocket structures in use. Sockets will not be closed until \a sockets goes out of scope + virtual void GetSockets( DataStructures::List > &sockets )=0; + + virtual void WriteOutOfBandHeader(RakNet::BitStream *bitStream, MessageID header)=0; + + /// If you need code to run in the same thread as RakNet's update thread, this function can be used for that + /// \param[in] _userUpdateThreadPtr C callback function + /// \param[in] _userUpdateThreadData Passed to C callback function + virtual void SetUserUpdateThread(void (*_userUpdateThreadPtr)(RakPeerInterface *, void *), void *_userUpdateThreadData)=0; + + // --------------------------------------------------------------------------------------------Network Simulator Functions-------------------------------------------------------------------------------------------- + /// Adds simulated ping and packet loss to the outgoing data flow. + /// To simulate bi-directional ping and packet loss, you should call this on both the sender and the recipient, with half the total ping and packetloss value on each. + /// You can exclude network simulator code with the _RELEASE #define to decrease code size + /// \deprecated Use http://www.jenkinssoftware.com/forum/index.php?topic=1671.0 instead. + /// \note Doesn't work past version 3.6201 + /// \param[in] packetloss Chance to lose a packet. Ranges from 0 to 1. + /// \param[in] minExtraPing The minimum time to delay sends. + /// \param[in] extraPingVariance The additional random time to delay sends. + virtual void ApplyNetworkSimulator( float packetloss, unsigned short minExtraPing, unsigned short extraPingVariance)=0; + + /// Limits how much outgoing bandwidth can be sent per-connection. + /// This limit does not apply to the sum of all connections! + /// Exceeding the limit queues up outgoing traffic + /// \param[in] maxBitsPerSecond Maximum bits per second to send. Use 0 for unlimited (default). Once set, it takes effect immedately and persists until called again. + virtual void SetPerConnectionOutgoingBandwidthLimit( unsigned maxBitsPerSecond )=0; + + /// Returns if you previously called ApplyNetworkSimulator + /// \return If you previously called ApplyNetworkSimulator + virtual bool IsNetworkSimulatorActive( void )=0; + + // --------------------------------------------------------------------------------------------Statistical Functions - Functions dealing with API performance-------------------------------------------------------------------------------------------- + + /// Returns a structure containing a large set of network statistics for the specified system. + /// You can map this data to a string using the C style StatisticsToString() function + /// \param[in] systemAddress: Which connected system to get statistics for + /// \param[in] rns If you supply this structure, it will be written to it. Otherwise it will use a static struct, which is not threadsafe + /// \return 0 on can't find the specified system. A pointer to a set of data otherwise. + /// \sa RakNetStatistics.h + virtual RakNetStatistics * const GetStatistics( const SystemAddress systemAddress, RakNetStatistics *rns=0 )=0; + virtual bool GetStatistics( const int index, RakNetStatistics *rns )=0; + + /// \Returns how many messages are waiting when you call Receive() + virtual unsigned int GetReceiveBufferSize(void)=0; + + // --------------------------------------------------------------------------------------------EVERYTHING AFTER THIS COMMENT IS FOR INTERNAL USE ONLY-------------------------------------------------------------------------------------------- + /// \internal + virtual char *GetRPCString( const char *data, const BitSize_t bitSize, const SystemAddress systemAddress)=0; + + /// \internal + virtual bool SendOutOfBand(const char *host, unsigned short remotePort, MessageID header, const char *data, BitSize_t dataLength, unsigned connectionSocketIndex=0 )=0; + + virtual char* HandleRPCPacket( const char *data, int length, SystemAddress systemAddress )=0; + +}; + +#endif diff --git a/RakNet/Sources/RakSleep.cpp b/RakNet/Sources/RakSleep.cpp new file mode 100644 index 0000000..43a2a1b --- /dev/null +++ b/RakNet/Sources/RakSleep.cpp @@ -0,0 +1,48 @@ +#if defined(_XBOX) || defined(X360) + +#elif defined(_WIN32) +#include "WindowsIncludes.h" // Sleep +#elif defined(_PS3) || defined(__PS3__) || defined(SN_TARGET_PS3) + +#else +#include +#include +#include +#endif + + +#include "RakSleep.h" + +void RakSleep(unsigned int ms) +{ +#ifdef _WIN32 + Sleep(ms); +#elif defined(_PS3) || defined(__PS3__) || defined(SN_TARGET_PS3) + +#else + //Single thread sleep code thanks to Furquan Shaikh, http://somethingswhichidintknow.blogspot.com/2009/09/sleep-in-pthread.html + //Modified slightly from the original + pthread_mutex_t fakeMutex = PTHREAD_MUTEX_INITIALIZER; + pthread_cond_t fakeCond = PTHREAD_COND_INITIALIZER; + struct timespec timeToWait; + struct timeval now; + int rt; + + gettimeofday(&now,NULL); + + long seconds = ms/1000; + long nanoseconds = (ms - seconds * 1000) * 1000000; + timeToWait.tv_sec = now.tv_sec + seconds; + timeToWait.tv_nsec = now.tv_usec*1000 + nanoseconds; + + if (timeToWait.tv_nsec >= 1000000000) + { + timeToWait.tv_nsec -= 1000000000; + timeToWait.tv_sec++; + } + + pthread_mutex_lock(&fakeMutex); + rt = pthread_cond_timedwait(&fakeCond, &fakeMutex, &timeToWait); + pthread_mutex_unlock(&fakeMutex); +#endif +} diff --git a/RakNet/Sources/RakSleep.h b/RakNet/Sources/RakSleep.h new file mode 100644 index 0000000..27936bf --- /dev/null +++ b/RakNet/Sources/RakSleep.h @@ -0,0 +1,8 @@ +#ifndef __RAK_SLEEP_H +#define __RAK_SLEEP_H + +#include "Export.h" + +void RAK_DLL_EXPORT RakSleep(unsigned int ms); + +#endif diff --git a/RakNet/Sources/RakString.cpp b/RakNet/Sources/RakString.cpp new file mode 100644 index 0000000..b09cbfa --- /dev/null +++ b/RakNet/Sources/RakString.cpp @@ -0,0 +1,1199 @@ +#include "RakString.h" +#include "RakAssert.h" +#include "RakMemoryOverride.h" +#include "BitStream.h" +#include +#include +#include "LinuxStrings.h" +#include "StringCompressor.h" +#include "SimpleMutex.h" + +using namespace RakNet; + +//DataStructures::MemoryPool RakString::pool; +unsigned int RakString::nPos=(unsigned int) -1; +RakString::SharedString RakString::emptyString={0,0,0,"",""}; +//RakString::SharedString *RakString::sharedStringFreeList=0; +//unsigned int RakString::sharedStringFreeListAllocationCount=0; +DataStructures::List RakString::freeList; + +class RakStringCleanup +{ +public: + ~RakStringCleanup() + { + RakNet::RakString::FreeMemoryNoMutex(); + } +}; + +static RakStringCleanup cleanup; + +SimpleMutex& GetPoolMutex(void) +{ + static SimpleMutex poolMutex; + return poolMutex; +} + +int RakString::RakStringComp( RakString const &key, RakString const &data ) +{ + return key.StrCmp(data); +} + +RakString::RakString() +{ + sharedString=&emptyString; +} +RakString::RakString( RakString::SharedString *_sharedString ) +{ + sharedString=_sharedString; +} +RakString::RakString(char input) +{ + char str[2]; + str[0]=input; + str[1]=0; + Assign(str); +} +RakString::RakString(unsigned char input) +{ + char str[2]; + str[0]=(char) input; + str[1]=0; + Assign(str); +} +RakString::RakString(const unsigned char *format, ...){ + char text[8096]; + va_list ap; + va_start(ap, format); + _vsnprintf(text, 8096, (const char*) format, ap); + va_end(ap); + text[8096-1]=0; + Assign(text); +} +RakString::RakString(const char *format, ...){ + char text[8096]; + va_list ap; + va_start(ap, format); + _vsnprintf(text, 8096, format, ap); + va_end(ap); + text[8096-1]=0; + Assign(text); +} +RakString::RakString( const RakString & rhs) +{ + if (rhs.sharedString==&emptyString) + { + sharedString=&emptyString; + return; + } + + rhs.sharedString->refCountMutex->Lock(); + if (rhs.sharedString->refCount==0) + { + sharedString=&emptyString; + } + else + { + rhs.sharedString->refCount++; + sharedString=rhs.sharedString; + } + rhs.sharedString->refCountMutex->Unlock(); +} +RakString::~RakString() +{ + Free(); +} +RakString& RakString::operator = ( const RakString& rhs ) +{ + Free(); + if (rhs.sharedString==&emptyString) + return *this; + + rhs.sharedString->refCountMutex->Lock(); + if (rhs.sharedString->refCount==0) + { + sharedString=&emptyString; + } + else + { + sharedString=rhs.sharedString; + sharedString->refCount++; + } + rhs.sharedString->refCountMutex->Unlock(); + return *this; +} +RakString& RakString::operator = ( const char *str ) +{ + Free(); + Assign(str); + return *this; +} +RakString& RakString::operator = ( char *str ) +{ + return operator = ((const char*)str); +} +RakString& RakString::operator = ( const char c ) +{ + char buff[2]; + buff[0]=c; + buff[1]=0; + return operator = ((const char*)buff); +} +void RakString::Realloc(SharedString *sharedString, size_t bytes) +{ + if (bytes<=sharedString->bytesUsed) + return; + RakAssert(bytes>0); + size_t oldBytes = sharedString->bytesUsed; + size_t newBytes; + const size_t smallStringSize = 128-sizeof(unsigned int)-sizeof(size_t)-sizeof(char*)*2; + newBytes = GetSizeToAllocate(bytes); + if (oldBytes <=(size_t) smallStringSize && newBytes > (size_t) smallStringSize) + { + sharedString->bigString=(char*) rakMalloc_Ex(newBytes, __FILE__, __LINE__); + strcpy(sharedString->bigString, sharedString->smallString); + sharedString->c_str=sharedString->bigString; + } + else if (oldBytes > smallStringSize) + { + sharedString->bigString=(char*) rakRealloc_Ex(sharedString->bigString,newBytes, __FILE__, __LINE__); + sharedString->c_str=sharedString->bigString; + } + sharedString->bytesUsed=newBytes; +} +RakString& RakString::operator +=( const RakString& rhs) +{ + if (rhs.IsEmpty()) + return *this; + + if (IsEmpty()) + { + return operator=(rhs); + } + else + { + Clone(); + size_t strLen=rhs.GetLength()+GetLength()+1; + Realloc(sharedString, strLen+GetLength()); + strcat(sharedString->c_str,rhs.C_String()); + } + return *this; +} +RakString& RakString::operator +=( const char *str ) +{ + if (str==0 || str[0]==0) + return *this; + + if (IsEmpty()) + { + Assign(str); + } + else + { + Clone(); + size_t strLen=strlen(str)+GetLength()+1; + Realloc(sharedString, strLen); + strcat(sharedString->c_str,str); + } + return *this; +} +RakString& RakString::operator +=( char *str ) +{ + return operator += ((const char*)str); +} +RakString& RakString::operator +=( const char c ) +{ + char buff[2]; + buff[0]=c; + buff[1]=0; + return operator += ((const char*)buff); +} +unsigned char RakString::operator[] ( const unsigned int position ) const +{ + RakAssert(positionc_str[position]; +} +bool RakString::operator==(const RakString &rhs) const +{ + return strcmp(sharedString->c_str,rhs.sharedString->c_str)==0; +} +bool RakString::operator==(const char *str) const +{ + return strcmp(sharedString->c_str,str)==0; +} +bool RakString::operator==(char *str) const +{ + return strcmp(sharedString->c_str,str)==0; +} +bool RakString::operator < ( const RakString& right ) const +{ + return strcmp(sharedString->c_str,right.C_String()) < 0; +} +bool RakString::operator <= ( const RakString& right ) const +{ + return strcmp(sharedString->c_str,right.C_String()) <= 0; +} +bool RakString::operator > ( const RakString& right ) const +{ + return strcmp(sharedString->c_str,right.C_String()) > 0; +} +bool RakString::operator >= ( const RakString& right ) const +{ + return strcmp(sharedString->c_str,right.C_String()) >= 0; +} +bool RakString::operator!=(const RakString &rhs) const +{ + return strcmp(sharedString->c_str,rhs.sharedString->c_str)!=0; +} +bool RakString::operator!=(const char *str) const +{ + return strcmp(sharedString->c_str,str)!=0; +} +bool RakString::operator!=(char *str) const +{ + return strcmp(sharedString->c_str,str)!=0; +} +const RakNet::RakString operator+(const RakNet::RakString &lhs, const RakNet::RakString &rhs) +{ + if (lhs.IsEmpty() && rhs.IsEmpty()) + { + return RakString(&RakString::emptyString); + } + if (lhs.IsEmpty()) + { + rhs.sharedString->refCountMutex->Lock(); + if (rhs.sharedString->refCount==0) + { + rhs.sharedString->refCountMutex->Unlock(); + lhs.sharedString->refCountMutex->Lock(); + lhs.sharedString->refCount++; + lhs.sharedString->refCountMutex->Unlock(); + return RakString(lhs.sharedString); + } + else + { + rhs.sharedString->refCount++; + rhs.sharedString->refCountMutex->Unlock(); + return RakString(rhs.sharedString); + } + // rhs.sharedString->refCountMutex->Unlock(); + } + if (rhs.IsEmpty()) + { + lhs.sharedString->refCountMutex->Lock(); + lhs.sharedString->refCount++; + lhs.sharedString->refCountMutex->Unlock(); + return RakString(lhs.sharedString); + } + + size_t len1 = lhs.GetLength(); + size_t len2 = rhs.GetLength(); + size_t allocatedBytes = len1 + len2 + 1; + allocatedBytes = RakString::GetSizeToAllocate(allocatedBytes); + RakString::SharedString *sharedString; + + RakString::LockMutex(); + // sharedString = RakString::pool.Allocate( __FILE__, __LINE__ ); + if (RakString::freeList.Size()==0) + { + //RakString::sharedStringFreeList=(RakString::SharedString*) rakRealloc_Ex(RakString::sharedStringFreeList,(RakString::sharedStringFreeListAllocationCount+1024)*sizeof(RakString::SharedString), __FILE__, __LINE__); + unsigned i; + for (i=0; i < 128; i++) + { + // RakString::freeList.Insert(RakString::sharedStringFreeList+i+RakString::sharedStringFreeListAllocationCount); + RakString::SharedString *ss; + ss = (RakString::SharedString*) rakMalloc_Ex(sizeof(RakString::SharedString), __FILE__, __LINE__); + ss->refCountMutex=RakNet::OP_NEW(__FILE__,__LINE__); + RakString::freeList.Insert(ss, __FILE__, __LINE__); + + } + //RakString::sharedStringFreeListAllocationCount+=1024; + } + sharedString = RakString::freeList[RakString::freeList.Size()-1]; + RakString::freeList.RemoveAtIndex(RakString::freeList.Size()-1); + RakString::UnlockMutex(); + + const int smallStringSize = 128-sizeof(unsigned int)-sizeof(size_t)-sizeof(char*)*2; + sharedString->bytesUsed=allocatedBytes; + sharedString->refCount=1; + if (allocatedBytes <= (size_t) smallStringSize) + { + sharedString->c_str=sharedString->smallString; + } + else + { + sharedString->bigString=(char*)rakMalloc_Ex(sharedString->bytesUsed, __FILE__, __LINE__); + sharedString->c_str=sharedString->bigString; + } + + strcpy(sharedString->c_str, lhs); + strcat(sharedString->c_str, rhs); + + return RakString(sharedString); +} +const char * RakString::ToLower(void) +{ + Clone(); + + size_t strLen = strlen(sharedString->c_str); + unsigned i; + for (i=0; i < strLen; i++) + sharedString->c_str[i]=ToLower(sharedString->c_str[i]); + return sharedString->c_str; +} +const char * RakString::ToUpper(void) +{ + Clone(); + + size_t strLen = strlen(sharedString->c_str); + unsigned i; + for (i=0; i < strLen; i++) + sharedString->c_str[i]=ToUpper(sharedString->c_str[i]); + return sharedString->c_str; +} +void RakString::Set(const char *format, ...) +{ + char text[8096]; + va_list ap; + va_start(ap, format); + _vsnprintf(text, 8096, format, ap); + va_end(ap); + text[8096-1]=0; + Clear(); + Assign(text); +} +bool RakString::IsEmpty(void) const +{ + return sharedString==&emptyString; +} +size_t RakString::GetLength(void) const +{ + return strlen(sharedString->c_str); +} +void RakString::Replace(unsigned index, unsigned count, unsigned char c) +{ + RakAssert(index+count < GetLength()); + Clone(); + unsigned countIndex=0; + while (countIndexc_str[index]=c; + index++; + countIndex++; + } +} +void RakString::SetChar( unsigned index, unsigned char c ) +{ + RakAssert(index < GetLength()); + Clone(); + sharedString->c_str[index]=c; +} +void RakString::SetChar( unsigned index, RakNet::RakString s ) +{ + RakAssert(index < GetLength()); + Clone(); + RakNet::RakString firstHalf = SubStr(0, index); + RakNet::RakString secondHalf = SubStr(index+1, (unsigned int)-1); + *this = firstHalf; + *this += s; + *this += secondHalf; +} + +size_t RakString::Find(const char *stringToFind,size_t pos) +{ + size_t len=GetLength(); + if (pos>=len || stringToFind==0 || stringToFind[0]==0) + { + return nPos; + } + size_t matchLen= strlen(stringToFind); + size_t matchPos=0; + size_t iStart=0; + + for (size_t i=pos;ic_str[i]) + { + if(matchPos==0) + { + iStart=i; + } + matchPos++; + } + else + { + matchPos=0; + } + + if (matchPos>=matchLen) + { + return iStart; + } + } + + return nPos; +} + +void RakString::Truncate(unsigned length) +{ + if (length < GetLength()) + { + SetChar(length, 0); + } +} +RakString RakString::SubStr(unsigned int index, unsigned int count) const +{ + size_t length = GetLength(); + if (index >= length || count==0) + return RakString(); + RakString copy; + size_t numBytes = length-index; + if (count < numBytes) + numBytes=count; + copy.Allocate(numBytes+1); + size_t i; + for (i=0; i < numBytes; i++) + copy.sharedString->c_str[i]=sharedString->c_str[index+i]; + copy.sharedString->c_str[i]=0; + return copy; +} +void RakString::Erase(unsigned int index, unsigned int count) +{ + size_t len = GetLength(); + RakAssert(index+count <= len); + + Clone(); + unsigned i; + for (i=index; i < len-count; i++) + { + sharedString->c_str[i]=sharedString->c_str[i+count]; + } + sharedString->c_str[i]=0; +} +void RakString::TerminateAtLastCharacter(char c) +{ + int i, len=(int) GetLength(); + for (i=len-1; i >= 0; i--) + { + if (sharedString->c_str[i]==c) + { + Clone(); + sharedString->c_str[i]=0; + return; + } + } +} +void RakString::TerminateAtFirstCharacter(char c) +{ + unsigned int i, len=(unsigned int) GetLength(); + for (i=0; i < len; i++) + { + if (sharedString->c_str[i]==c) + { + Clone(); + sharedString->c_str[i]=0; + return; + } + } +} +void RakString::RemoveCharacter(char c) +{ + if (c==0) + return; + + unsigned int readIndex, writeIndex=0; + for (readIndex=0; sharedString->c_str[readIndex]; readIndex++) + { + if (sharedString->c_str[readIndex]!=c) + sharedString->c_str[writeIndex++]=sharedString->c_str[readIndex]; + else + Clone(); + } + sharedString->c_str[writeIndex]=0; +} +int RakString::StrCmp(const RakString &rhs) const +{ + return strcmp(sharedString->c_str, rhs); +} +int RakString::StrICmp(const RakString &rhs) const +{ + return _stricmp(sharedString->c_str, rhs); +} +void RakString::Printf(void) +{ + RAKNET_DEBUG_PRINTF(sharedString->c_str); +} +void RakString::FPrintf(FILE *fp) +{ + fprintf(fp,sharedString->c_str); +} +bool RakString::IPAddressMatch(const char *IP) +{ + unsigned characterIndex; + + if ( IP == 0 || IP[ 0 ] == 0 || strlen( IP ) > 15 ) + return false; + + characterIndex = 0; + +#ifdef _MSC_VER +#pragma warning( disable : 4127 ) // warning C4127: conditional expression is constant +#endif + while ( true ) + { + if (sharedString->c_str[ characterIndex ] == IP[ characterIndex ] ) + { + // Equal characters + if ( IP[ characterIndex ] == 0 ) + { + // End of the string and the strings match + + return true; + } + + characterIndex++; + } + + else + { + if ( sharedString->c_str[ characterIndex ] == 0 || IP[ characterIndex ] == 0 ) + { + // End of one of the strings + break; + } + + // Characters do not match + if ( sharedString->c_str[ characterIndex ] == '*' ) + { + // Domain is banned. + return true; + } + + // Characters do not match and it is not a * + break; + } + } + + + // No match found. + return false; +} +bool RakString::ContainsNonprintableExceptSpaces(void) const +{ + size_t strLen = strlen(sharedString->c_str); + unsigned i; + for (i=0; i < strLen; i++) + { + if (sharedString->c_str[i] < ' ' || sharedString->c_str[i] >126) + return true; + } + return false; +} +bool RakString::IsEmailAddress(void) const +{ + if (IsEmpty()) + return false; + size_t strLen = strlen(sharedString->c_str); + if (strLen < 7) // a@b.com + return false; + if (sharedString->c_str[strLen-4]!='.') // .com, .net., .org + return false; + unsigned i; + // Has non-printable? + for (i=0; i < strLen; i++) + { + if (sharedString->c_str[i] <= ' ' || sharedString->c_str[i] >126) + return false; + } + int atCount=0; + for (i=0; i < strLen; i++) + { + if (sharedString->c_str[i]=='@') + { + atCount++; + } + } + if (atCount!=1) + return false; + int dotCount=0; + for (i=0; i < strLen; i++) + { + if (sharedString->c_str[i]=='.') + { + dotCount++; + } + } + if (dotCount==0) + return false; + + // There's more I could check, but this is good enough + return true; +} +RakNet::RakString& RakString::URLEncode(void) +{ + RakString result; + size_t strLen = strlen(sharedString->c_str); + result.Allocate(strLen*3); + char *output=result.sharedString->c_str; + unsigned int outputIndex=0; + unsigned i; + char c; + for (i=0; i < strLen; i++) + { + c=sharedString->c_str[i]; + if ( + (c<=47) || + (c>=58 && c<=64) || + (c>=91 && c<=96) || + (c>=123) + ) + { + RakNet::RakString tmp("%2X", c); + output[outputIndex++]='%'; + output[outputIndex++]=tmp.sharedString->c_str[0]; + output[outputIndex++]=tmp.sharedString->c_str[1]; + } + else + { + output[outputIndex++]=c; + } + } + + output[outputIndex]=0; + + *this = result; + return *this; +} +RakNet::RakString& RakString::URLDecode(void) +{ + RakString result; + size_t strLen = strlen(sharedString->c_str); + result.Allocate(strLen); + char *output=result.sharedString->c_str; + unsigned int outputIndex=0; + char c; + char hexDigits[2]; + char hexValues[2]; + unsigned int i; + for (i=0; i < strLen; i++) + { + c=sharedString->c_str[i]; + if (c=='%') + { + hexDigits[0]=sharedString->c_str[++i]; + hexDigits[1]=sharedString->c_str[++i]; + if (hexDigits[0]==' ') + hexValues[0]=0; + else if (hexDigits[0]>='A') + hexValues[0]=hexDigits[0]-'A'+10; + else + hexValues[0]=hexDigits[0]-'0'; + if (hexDigits[1]>='A') + hexValues[1]=hexDigits[1]-'A'+10; + else + hexValues[1]=hexDigits[1]-'0'; + output[outputIndex++]=hexValues[0]*16+hexValues[1]; + } + else + { + output[outputIndex++]=c; + } + } + + output[outputIndex]=0; + + *this = result; + return *this; +} +RakNet::RakString& RakString::SQLEscape(void) +{ + int strLen=(int)GetLength(); + int escapedCharacterCount=0; + int index; + for (index=0; index < strLen; index++) + { + if (sharedString->c_str[index]=='\'' || + sharedString->c_str[index]=='"' || + sharedString->c_str[index]=='\\') + escapedCharacterCount++; + } + if (escapedCharacterCount==0) + return *this; + + Clone(); + Realloc(sharedString, strLen+escapedCharacterCount); + int writeIndex, readIndex; + writeIndex = strLen+escapedCharacterCount; + readIndex=strLen; + while (readIndex>=0) + { + if (sharedString->c_str[readIndex]=='\'' || + sharedString->c_str[readIndex]=='"' || + sharedString->c_str[readIndex]=='\\') + { + sharedString->c_str[writeIndex--]=sharedString->c_str[readIndex--]; + sharedString->c_str[writeIndex--]='\\'; + } + else + { + sharedString->c_str[writeIndex--]=sharedString->c_str[readIndex--]; + } + } + return *this; +} +RakNet::RakString& RakString::MakeFilePath(void) +{ + if (IsEmpty()) + return *this; + + RakNet::RakString fixedString = *this; + fixedString.Clone(); + for (int i=0; fixedString.sharedString->c_str[i]; i++) + { +#ifdef _WIN32 + if (fixedString.sharedString->c_str[i]=='/') + fixedString.sharedString->c_str[i]='\\'; +#else + if (fixedString.sharedString->c_str[i]=='\\') + fixedString.sharedString->c_str[i]='/'; +#endif + } + +#ifdef _WIN32 + if (fixedString.sharedString->c_str[strlen(fixedString.sharedString->c_str)-1]!='\\') + { + fixedString+='\\'; + } +#else + if (fixedString.sharedString->c_str[strlen(fixedString.sharedString->c_str)-1]!='/') + { + fixedString+='/'; + } +#endif + + if (fixedString!=*this) + *this = fixedString; + return *this; +} +void RakString::FreeMemory(void) +{ + LockMutex(); + FreeMemoryNoMutex(); + UnlockMutex(); +} +void RakString::FreeMemoryNoMutex(void) +{ + for (unsigned int i=0; i < freeList.Size(); i++) + { + RakNet::OP_DELETE(freeList[i]->refCountMutex,__FILE__,__LINE__); + rakFree_Ex(freeList[i], __FILE__, __LINE__ ); + } + freeList.Clear(false, __FILE__, __LINE__); +} +void RakString::Serialize(BitStream *bs) const +{ + Serialize(sharedString->c_str, bs); +} +void RakString::Serialize(const char *str, BitStream *bs) +{ + unsigned short l = (unsigned short) strlen(str); + bs->Write(l); + bs->WriteAlignedBytes((const unsigned char*) str, (const unsigned int) l); +} +void RakString::SerializeCompressed(BitStream *bs, int languageId, bool writeLanguageId) const +{ + SerializeCompressed(C_String(), bs, languageId, writeLanguageId); +} +void RakString::SerializeCompressed(const char *str, BitStream *bs, int languageId, bool writeLanguageId) +{ + if (writeLanguageId) + bs->WriteCompressed(languageId); + stringCompressor->EncodeString(str,0xFFFF,bs,languageId); +} +bool RakString::Deserialize(BitStream *bs) +{ + Clear(); + + bool b; + unsigned short l; + b=bs->Read(l); + if (l>0) + { + Allocate(((unsigned int) l)+1); + b=bs->ReadAlignedBytes((unsigned char*) sharedString->c_str, l); + if (b) + sharedString->c_str[l]=0; + else + Clear(); + } + else + bs->AlignReadToByteBoundary(); + return b; +} +bool RakString::Deserialize(char *str, BitStream *bs) +{ + bool b; + unsigned short l; + b=bs->Read(l); + if (b && l>0) + b=bs->ReadAlignedBytes((unsigned char*) str, l); + + if (b==false) + str[0]=0; + + str[l]=0; + return b; +} +bool RakString::DeserializeCompressed(BitStream *bs, bool readLanguageId) +{ + unsigned int languageId; + if (readLanguageId) + bs->ReadCompressed(languageId); + else + languageId=0; + return stringCompressor->DecodeString(this,0xFFFF,bs,languageId); +} +bool RakString::DeserializeCompressed(char *str, BitStream *bs, bool readLanguageId) +{ + unsigned int languageId; + if (readLanguageId) + bs->ReadCompressed(languageId); + else + languageId=0; + return stringCompressor->DecodeString(str,0xFFFF,bs,languageId); +} +const char *RakString::ToString(int64_t i) +{ + static int index=0; + static char buff[64][64]; +#if defined(_WIN32) + sprintf(buff[index], "%I64d", i); +#else + sprintf(buff[index], "%lld", i); +#endif + int lastIndex=index; + if (++index==64) + index=0; + return buff[lastIndex]; +} +const char *RakString::ToString(uint64_t i) +{ + static int index=0; + static char buff[64][64]; +#if defined(_WIN32) + sprintf(buff[index], "%I64u", i); +#else + sprintf(buff[index], "%llu", i); +#endif + int lastIndex=index; + if (++index==64) + index=0; + return buff[lastIndex]; +} +void RakString::Clear(void) +{ + Free(); +} +void RakString::Allocate(size_t len) +{ + RakString::LockMutex(); + // sharedString = RakString::pool.Allocate( __FILE__, __LINE__ ); + if (RakString::freeList.Size()==0) + { + //RakString::sharedStringFreeList=(RakString::SharedString*) rakRealloc_Ex(RakString::sharedStringFreeList,(RakString::sharedStringFreeListAllocationCount+1024)*sizeof(RakString::SharedString), __FILE__, __LINE__); + unsigned i; + for (i=0; i < 128; i++) + { + // RakString::freeList.Insert(RakString::sharedStringFreeList+i+RakString::sharedStringFreeListAllocationCount); + // RakString::freeList.Insert((RakString::SharedString*)rakMalloc_Ex(sizeof(RakString::SharedString), __FILE__, __LINE__), __FILE__, __LINE__); + + RakString::SharedString *ss; + ss = (RakString::SharedString*) rakMalloc_Ex(sizeof(RakString::SharedString), __FILE__, __LINE__); + ss->refCountMutex=RakNet::OP_NEW(__FILE__,__LINE__); + RakString::freeList.Insert(ss, __FILE__, __LINE__); + } + //RakString::sharedStringFreeListAllocationCount+=1024; + } + sharedString = RakString::freeList[RakString::freeList.Size()-1]; + RakString::freeList.RemoveAtIndex(RakString::freeList.Size()-1); + RakString::UnlockMutex(); + + const size_t smallStringSize = 128-sizeof(unsigned int)-sizeof(size_t)-sizeof(char*)*2; + sharedString->refCount=1; + if (len <= smallStringSize) + { + sharedString->bytesUsed=smallStringSize; + sharedString->c_str=sharedString->smallString; + } + else + { + sharedString->bytesUsed=len<<1; + sharedString->bigString=(char*)rakMalloc_Ex(sharedString->bytesUsed, __FILE__, __LINE__); + sharedString->c_str=sharedString->bigString; + } +} +void RakString::Assign(const char *str) +{ + if (str==0 || str[0]==0) + { + sharedString=&emptyString; + return; + } + + size_t len = strlen(str)+1; + Allocate(len); + memcpy(sharedString->c_str, str, len); +} + +RakNet::RakString RakString::Assign(const char *str,size_t pos, size_t n ) +{ + + size_t incomingLen=strlen(str); + + Clone(); + + if (str==0 || str[0]==0||pos>=incomingLen) + { + sharedString=&emptyString; + return (*this); + } + + if (pos+n>=incomingLen) + { + n=incomingLen-pos; + + } + const char * tmpStr=&(str[pos]); + + size_t len = n+1; + Allocate(len); + memcpy(sharedString->c_str, tmpStr, len); + sharedString->c_str[n]=0; + + return (*this); +} + +RakNet::RakString RakString::NonVariadic(const char *str) +{ + RakNet::RakString rs; + rs=str; + return rs; +} +unsigned long RakString::ToInteger(const char *str) +{ + unsigned long hash = 0; + int c; + + while (c = *str++) + hash = c + (hash << 6) + (hash << 16) - hash; + + return hash; +} +unsigned long RakString::ToInteger(const RakString &rs) +{ + return RakString::ToInteger(rs.C_String()); +} +void RakString::AppendBytes(const char *bytes, unsigned int count) +{ + Clone(); + Realloc(sharedString, count); + unsigned int length=(unsigned int) GetLength(); + memcpy(sharedString->c_str+length, bytes, count); + sharedString->c_str[length+count]=0; +} +void RakString::Clone(void) +{ + if (sharedString==&emptyString) + { + return; + } + + // Empty or solo then no point to cloning + sharedString->refCountMutex->Lock(); + if (sharedString->refCount==1) + { + sharedString->refCountMutex->Unlock(); + return; + } + + sharedString->refCount--; + sharedString->refCountMutex->Unlock(); + Assign(sharedString->c_str); +} +void RakString::Free(void) +{ + if (sharedString==&emptyString) + return; + sharedString->refCountMutex->Lock(); + sharedString->refCount--; + if (sharedString->refCount==0) + { + sharedString->refCountMutex->Unlock(); + const size_t smallStringSize = 128-sizeof(unsigned int)-sizeof(size_t)-sizeof(char*)*2; + if (sharedString->bytesUsed>smallStringSize) + rakFree_Ex(sharedString->bigString, __FILE__, __LINE__ ); + /* + poolMutex->Lock(); + pool.Release(sharedString); + poolMutex->Unlock(); + */ + + RakString::LockMutex(); + RakString::freeList.Insert(sharedString, __FILE__, __LINE__); + RakString::UnlockMutex(); + + sharedString=&emptyString; + } + else + { + sharedString->refCountMutex->Unlock(); + } + sharedString=&emptyString; +} +unsigned char RakString::ToLower(unsigned char c) +{ + if (c >= 'A' && c <= 'Z') + return c-'A'+'a'; + return c; +} +unsigned char RakString::ToUpper(unsigned char c) +{ + if (c >= 'a' && c <= 'z') + return c-'a'+'A'; + return c; +} +void RakString::LockMutex(void) +{ + GetPoolMutex().Lock(); +} +void RakString::UnlockMutex(void) +{ + GetPoolMutex().Unlock(); +} + +/* +#include "RakString.h" +#include +#include "GetTime.h" + +using namespace RakNet; + +int main(void) +{ + RakString s3("Hello world"); + RakString s5=s3; + + RakString s1; + RakString s2('a'); + + RakString s4("%i %f", 5, 6.0); + + RakString s6=s3; + RakString s7=s6; + RakString s8=s6; + RakString s9; + s9=s9; + RakString s10(s3); + RakString s11=s10 + s4 + s9 + s2; + s11+=RakString("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"); + RakString s12("Test"); + s12+=s11; + bool b1 = s12==s12; + s11=s5; + s12.ToUpper(); + s12.ToLower(); + RakString s13; + bool b3 = s13.IsEmpty(); + s13.Set("blah %s", s12.C_String()); + bool b4 = s13.IsEmpty(); + size_t i1=s13.GetLength(); + s3.Clear(__FILE__, __LINE__); + s4.Clear(__FILE__, __LINE__); + s5.Clear(__FILE__, __LINE__); + s5.Clear(__FILE__, __LINE__); + s6.Printf(); + s7.Printf(); + RAKNET_DEBUG_PRINTF("\n"); + + static const int repeatCount=750; + DataStructures::List rakStringList; + DataStructures::List stdStringList; + DataStructures::List referenceStringList; + char *c; + unsigned i; + RakNetTime beforeReferenceList, beforeRakString, beforeStdString, afterStdString; + + unsigned loop; + for (loop=0; loop<2; loop++) + { + beforeReferenceList=RakNet::GetTime(); + for (i=0; i < repeatCount; i++) + { + c = RakNet::OP_NEW_ARRAY(56,__FILE__, __LINE__ ); + strcpy(c, "Aalsdkj alsdjf laksdjf ;lasdfj ;lasjfd"); + referenceStringList.Insert(c); + } + beforeRakString=RakNet::GetTime(); + for (i=0; i < repeatCount; i++) + rakStringList.Insert("Aalsdkj alsdjf laksdjf ;lasdfj ;lasjfd"); + beforeStdString=RakNet::GetTime(); + + for (i=0; i < repeatCount; i++) + stdStringList.Insert("Aalsdkj alsdjf laksdjf ;lasdfj ;lasjfd"); + afterStdString=RakNet::GetTime(); + RAKNET_DEBUG_PRINTF("Insertion 1 Ref=%i Rak=%i, Std=%i\n", beforeRakString-beforeReferenceList, beforeStdString-beforeRakString, afterStdString-beforeStdString); + + beforeReferenceList=RakNet::GetTime(); + for (i=0; i < repeatCount; i++) + { + RakNet::OP_DELETE_ARRAY(referenceStringList[0], __FILE__, __LINE__); + referenceStringList.RemoveAtIndex(0); + } + beforeRakString=RakNet::GetTime(); + for (i=0; i < repeatCount; i++) + rakStringList.RemoveAtIndex(0); + beforeStdString=RakNet::GetTime(); + for (i=0; i < repeatCount; i++) + stdStringList.RemoveAtIndex(0); + afterStdString=RakNet::GetTime(); + RAKNET_DEBUG_PRINTF("RemoveHead Ref=%i Rak=%i, Std=%i\n", beforeRakString-beforeReferenceList, beforeStdString-beforeRakString, afterStdString-beforeStdString); + + beforeReferenceList=RakNet::GetTime(); + for (i=0; i < repeatCount; i++) + { + c = RakNet::OP_NEW_ARRAY(56, __FILE__, __LINE__ ); + strcpy(c, "Aalsdkj alsdjf laksdjf ;lasdfj ;lasjfd"); + referenceStringList.Insert(0); + } + beforeRakString=RakNet::GetTime(); + for (i=0; i < repeatCount; i++) + rakStringList.Insert("Aalsdkj alsdjf laksdjf ;lasdfj ;lasjfd"); + beforeStdString=RakNet::GetTime(); + for (i=0; i < repeatCount; i++) + stdStringList.Insert("Aalsdkj alsdjf laksdjf ;lasdfj ;lasjfd"); + afterStdString=RakNet::GetTime(); + RAKNET_DEBUG_PRINTF("Insertion 2 Ref=%i Rak=%i, Std=%i\n", beforeRakString-beforeReferenceList, beforeStdString-beforeRakString, afterStdString-beforeStdString); + + beforeReferenceList=RakNet::GetTime(); + for (i=0; i < repeatCount; i++) + { + RakNet::OP_DELETE_ARRAY(referenceStringList[referenceStringList.Size()-1], __FILE__, __LINE__); + referenceStringList.RemoveAtIndex(referenceStringList.Size()-1); + } + beforeRakString=RakNet::GetTime(); + for (i=0; i < repeatCount; i++) + rakStringList.RemoveAtIndex(rakStringList.Size()-1); + beforeStdString=RakNet::GetTime(); + for (i=0; i < repeatCount; i++) + stdStringList.RemoveAtIndex(stdStringList.Size()-1); + afterStdString=RakNet::GetTime(); + RAKNET_DEBUG_PRINTF("RemoveTail Ref=%i Rak=%i, Std=%i\n", beforeRakString-beforeReferenceList, beforeStdString-beforeRakString, afterStdString-beforeStdString); + + } + + printf("Done."); + char str[128]; + gets(str); + return 1; +} +*/ diff --git a/RakNet/Sources/RakString.h b/RakNet/Sources/RakString.h new file mode 100644 index 0000000..e360e69 --- /dev/null +++ b/RakNet/Sources/RakString.h @@ -0,0 +1,287 @@ +#ifndef __RAK_STRING_H +#define __RAK_STRING_H + +#include "Export.h" +#include "DS_List.h" +#include "RakNetTypes.h" // int64_t +#include + +class SimpleMutex; + +namespace RakNet +{ + +class BitStream; + +/// \brief String class +/// \details Has the following improvements over std::string +/// -Reference counting: Suitable to store in lists +/// -Variadic assignment operator +/// -Doesn't cause linker errors +class RAK_DLL_EXPORT RakString +{ +public: + // Constructors + RakString(); + RakString(char input); + RakString(unsigned char input); + RakString(const unsigned char *format, ...); + RakString(const char *format, ...); + ~RakString(); + RakString( const RakString & rhs); + + /// Implicit return of const char* + operator const char* () const {return sharedString->c_str;} + + /// Same as std::string::c_str + const char *C_String(void) const {return sharedString->c_str;} + + // Lets you modify the string. Do not make the string longer - however, you can make it shorter, or change the contents. + // Pointer is only valid in the scope of RakString itself + char *C_StringUnsafe(void) {Clone(); return sharedString->c_str;} + + /// Assigment operators + RakString& operator = ( const RakString& rhs ); + RakString& operator = ( const char *str ); + RakString& operator = ( char *str ); + RakString& operator = ( const char c ); + + /// Concatenation + RakString& operator +=( const RakString& rhs); + RakString& operator += ( const char *str ); + RakString& operator += ( char *str ); + RakString& operator += ( const char c ); + + /// Character index. Do not use to change the string however. + unsigned char operator[] ( const unsigned int position ) const; + + + ///String class find replacement + ///Searches the string for the content specified in stringToFind and returns the position of the first occurrence in the string. + ///Search only includes characters on or after position pos, ignoring any possible occurrences in previous locations. + /// \param[in] stringToFind The string to find inside of this object's string + /// \param[in] pos The position in the string to start the search + /// \return Returns the position of the first occurrence in the string. + size_t Find(const char *stringToFind,size_t pos = 0 ); + + /// Equality + bool operator==(const RakString &rhs) const; + bool operator==(const char *str) const; + bool operator==(char *str) const; + + // Comparison + bool operator < ( const RakString& right ) const; + bool operator <= ( const RakString& right ) const; + bool operator > ( const RakString& right ) const; + bool operator >= ( const RakString& right ) const; + + /// Inequality + bool operator!=(const RakString &rhs) const; + bool operator!=(const char *str) const; + bool operator!=(char *str) const; + + /// Change all characters to lowercase + const char * ToLower(void); + + /// Change all characters to uppercase + const char * ToUpper(void); + + /// Set the value of the string + void Set(const char *format, ...); + + /// Sets a copy of a substring of str as the new content. The substring is the portion of str + /// that begins at the character position pos and takes up to n characters + /// (it takes less than n if the end of str is reached before). + /// \param[in] str The string to copy in + /// \param[in] pos The position on str to start the copy + /// \param[in] n How many chars to copy + /// \return Returns the string, note that the current string is set to that value as well + RakString Assign(const char *str,size_t pos, size_t n ); + + /// Returns if the string is empty. Also, C_String() would return "" + bool IsEmpty(void) const; + + /// Returns the length of the string + size_t GetLength(void) const; + + /// Replace character(s) in starting at index, for count, with c + void Replace(unsigned index, unsigned count, unsigned char c); + + /// Replace character at index with c + void SetChar( unsigned index, unsigned char c ); + + /// Replace character at index with string s + void SetChar( unsigned index, RakNet::RakString s ); + + /// Make sure string is no longer than \a length + void Truncate(unsigned length); + + // Gets the substring starting at index for count characters + RakString SubStr(unsigned int index, unsigned int count) const; + + /// Erase characters out of the string at index for count + void Erase(unsigned int index, unsigned int count); + + /// Set the first instance of c with a NULL terminator + void TerminateAtFirstCharacter(char c); + /// Set the last instance of c with a NULL terminator + void TerminateAtLastCharacter(char c); + + /// Remove all instances of c + void RemoveCharacter(char c); + + /// Create a RakString with a value, without doing printf style parsing + /// Equivalent to assignment operator + static RakNet::RakString NonVariadic(const char *str); + + /// Has the string into an unsigned int + static unsigned long ToInteger(const char *str); + static unsigned long ToInteger(const RakString &rs); + + // Like strncat, but for a fixed length + void AppendBytes(const char *bytes, unsigned int count); + + /// Compare strings (case sensitive) + int StrCmp(const RakString &rhs) const; + + /// Compare strings (not case sensitive) + int StrICmp(const RakString &rhs) const; + + /// Clear the string + void Clear(void); + + /// Print the string to the screen + void Printf(void); + + /// Print the string to a file + void FPrintf(FILE *fp); + + /// Does the given IP address match the IP address encoded into this string, accounting for wildcards? + bool IPAddressMatch(const char *IP); + + /// Does the string contain non-printable characters other than spaces? + bool ContainsNonprintableExceptSpaces(void) const; + + /// Is this a valid email address? + bool IsEmailAddress(void) const; + + /// URL Encode the string. See http://www.codeguru.com/cpp/cpp/cpp_mfc/article.php/c4029/ + RakNet::RakString& URLEncode(void); + + /// URL decode the string + RakNet::RakString& URLDecode(void); + + /// Scan for quote, double quote, and backslash and prepend with backslash + RakNet::RakString& SQLEscape(void); + + /// Fix to be a file path, ending with / + RakNet::RakString& MakeFilePath(void); + + /// RakString uses a freeList of old no-longer used strings + /// Call this function to clear this memory on shutdown + static void FreeMemory(void); + /// \internal + static void FreeMemoryNoMutex(void); + + /// Serialize to a bitstream, uncompressed (slightly faster) + /// \param[out] bs Bitstream to serialize to + void Serialize(BitStream *bs) const; + + /// Static version of the Serialize function + static void Serialize(const char *str, BitStream *bs); + + /// Serialize to a bitstream, compressed (better bandwidth usage) + /// \param[out] bs Bitstream to serialize to + /// \param[in] languageId languageId to pass to the StringCompressor class + /// \param[in] writeLanguageId encode the languageId variable in the stream. If false, 0 is assumed, and DeserializeCompressed will not look for this variable in the stream (saves bandwidth) + /// \pre StringCompressor::AddReference must have been called to instantiate the class (Happens automatically from RakPeer::Startup()) + void SerializeCompressed(BitStream *bs, int languageId=0, bool writeLanguageId=false) const; + + /// Static version of the SerializeCompressed function + static void SerializeCompressed(const char *str, BitStream *bs, int languageId=0, bool writeLanguageId=false); + + /// Deserialize what was written by Serialize + /// \param[in] bs Bitstream to serialize from + /// \return true if the deserialization was successful + bool Deserialize(BitStream *bs); + + /// Static version of the Deserialize() function + static bool Deserialize(char *str, BitStream *bs); + + /// Deserialize compressed string, written by SerializeCompressed + /// \param[in] bs Bitstream to serialize from + /// \param[in] readLanguageId If true, looks for the variable langaugeId in the data stream. Must match what was passed to SerializeCompressed + /// \return true if the deserialization was successful + /// \pre StringCompressor::AddReference must have been called to instantiate the class (Happens automatically from RakPeer::Startup()) + bool DeserializeCompressed(BitStream *bs, bool readLanguageId=false); + + /// Static version of the DeserializeCompressed() function + static bool DeserializeCompressed(char *str, BitStream *bs, bool readLanguageId=false); + + static const char *ToString(int64_t i); + static const char *ToString(uint64_t i); + + /// \internal + static size_t GetSizeToAllocate(size_t bytes) + { + const size_t smallStringSize = 128-sizeof(unsigned int)-sizeof(size_t)-sizeof(char*)*2; + if (bytes<=smallStringSize) + return smallStringSize; + else + return bytes*2; + } + + /// \internal + struct SharedString + { + SimpleMutex *refCountMutex; + unsigned int refCount; + size_t bytesUsed; + char *bigString; + char *c_str; + char smallString[128-sizeof(unsigned int)-sizeof(size_t)-sizeof(char*)*2]; + }; + + /// \internal + RakString( SharedString *_sharedString ); + + /// \internal + SharedString *sharedString; + +// static SimpleMutex poolMutex; +// static DataStructures::MemoryPool pool; + /// \internal + static SharedString emptyString; + + //static SharedString *sharedStringFreeList; + //static unsigned int sharedStringFreeListAllocationCount; + /// \internal + /// List of free objects to reduce memory reallocations + static DataStructures::List freeList; + + /// Means undefined position + static unsigned int nPos; + + + static int RakStringComp( RakString const &key, RakString const &data ); + + static void LockMutex(void); + static void UnlockMutex(void); + +protected: + void Allocate(size_t len); + void Assign(const char *str); + + void Clone(void); + void Free(void); + unsigned char ToLower(unsigned char c); + unsigned char ToUpper(unsigned char c); + void Realloc(SharedString *sharedString, size_t bytes); +}; + +} + +const RakNet::RakString RAK_DLL_EXPORT operator+(const RakNet::RakString &lhs, const RakNet::RakString &rhs); + + +#endif diff --git a/RakNet/Sources/RakThread.cpp b/RakNet/Sources/RakThread.cpp new file mode 100644 index 0000000..af28031 --- /dev/null +++ b/RakNet/Sources/RakThread.cpp @@ -0,0 +1,68 @@ +#include "RakThread.h" +#include "RakAssert.h" +#include "RakNetDefines.h" +#include "RakSleep.h" + +#if defined(_XBOX) || defined(X360) + +#elif defined(_WIN32) +#include "WindowsIncludes.h" +#include + #if !defined(_WIN32_WCE) + #include + #endif +#else +#include +#endif + +using namespace RakNet; + +#if defined(_WIN32_WCE) +int RakThread::Create( LPTHREAD_START_ROUTINE start_address, void *arglist, int priority) +#elif defined(_WIN32) +int RakThread::Create( unsigned __stdcall start_address( void* ), void *arglist, int priority) +#else +int RakThread::Create( void* start_address( void* ), void *arglist, int priority) +#endif +{ +#ifdef _WIN32 + HANDLE threadHandle; + unsigned threadID = 0; +#if defined(_XBOX) || defined(X360) + +#elif defined (_WIN32_WCE) + threadHandle = CreateThread(NULL,MAX_ALLOCA_STACK_ALLOCATION*2,start_address,arglist,0,(DWORD*)&threadID); + SetThreadPriority(threadHandle, priority); +#else + threadHandle = (HANDLE) _beginthreadex( NULL, MAX_ALLOCA_STACK_ALLOCATION*2, start_address, arglist, 0, &threadID ); +#endif + SetThreadPriority(threadHandle, priority); + + if (threadHandle==0) + { + return 1; + } + else + { + CloseHandle( threadHandle ); + return 0; + } +#else + pthread_t threadHandle; + // Create thread linux + pthread_attr_t attr; + sched_param param; + param.sched_priority=priority; + pthread_attr_init( &attr ); + pthread_attr_setschedparam(&attr, ¶m); +#if defined(_PS3) || defined(__PS3__) || defined(SN_TARGET_PS3) + +#else + pthread_attr_setstacksize(&attr, MAX_ALLOCA_STACK_ALLOCATION*2); +#endif + pthread_attr_setdetachstate( &attr, PTHREAD_CREATE_DETACHED ); + int res = pthread_create( &threadHandle, &attr, start_address, arglist ); + RakAssert(res==0 && "pthread_create in RakThread.cpp failed.") + return res; +#endif +} diff --git a/RakNet/Sources/RakThread.h b/RakNet/Sources/RakThread.h new file mode 100644 index 0000000..d6ff728 --- /dev/null +++ b/RakNet/Sources/RakThread.h @@ -0,0 +1,55 @@ +#ifndef __RAK_THREAD_H +#define __RAK_THREAD_H + +#if defined(_WIN32_WCE) +#include "WindowsIncludes.h" +#endif + +#include "Export.h" + +namespace RakNet +{ + +/// To define a thread, use RAK_THREAD_DECLARATION(functionName); +#if defined(_WIN32_WCE) +#define RAK_THREAD_DECLARATION(functionName) DWORD WINAPI functionName(LPVOID arguments) +#elif defined(_XBOX) || defined(X360) + +#elif defined(_WIN32) +#define RAK_THREAD_DECLARATION(functionName) unsigned __stdcall functionName( void* arguments ) +#else +#define RAK_THREAD_DECLARATION(functionName) void* functionName( void* arguments ) +#endif + +class RAK_DLL_EXPORT RakThread +{ +public: + + /// Create a thread, simplified to be cross platform without all the extra junk + /// To then start that thread, call RakCreateThread(functionName, arguments); + /// \param[in] start_address Function you want to call + /// \param[in] arglist Arguments to pass to the function + /// \return 0=success. >0 = error code + + /* + nice value Win32 Priority + -20 to -16 THREAD_PRIORITY_HIGHEST + -15 to -6 THREAD_PRIORITY_ABOVE_NORMAL + -5 to +4 THREAD_PRIORITY_NORMAL + +5 to +14 THREAD_PRIORITY_BELOW_NORMAL + +15 to +19 THREAD_PRIORITY_LOWEST + */ +#if defined(_WIN32_WCE) + static int Create( LPTHREAD_START_ROUTINE start_address, void *arglist, int priority=0); +#elif defined(_XBOX) || defined(X360) + +#elif defined(_WIN32) + static int Create( unsigned __stdcall start_address( void* ), void *arglist, int priority=0); +#else + static int Create( void* start_address( void* ), void *arglist, int priority=0); +#endif +}; + +} + +#endif diff --git a/RakNet/Sources/Rand.cpp b/RakNet/Sources/Rand.cpp new file mode 100644 index 0000000..b41a604 --- /dev/null +++ b/RakNet/Sources/Rand.cpp @@ -0,0 +1,271 @@ +/** +* +* Grabbed by Kevin from http://www.math.keio.ac.jp/~matumoto/cokus.c +* This is the ``Mersenne Twister'' random number generator MT19937, which +* generates pseudorandom integers uniformly distributed in 0..(2^32 - 1) +* starting from any odd seed in 0..(2^32 - 1). This version is a recode +* by Shawn Cokus (Cokus@math.washington.edu) on March 8, 1998 of a version by +* Takuji Nishimura (who had suggestions from Topher Cooper and Marc Rieffel in +* July-August 1997). +* +* Effectiveness of the recoding (on Goedel2.math.washington.edu, a DEC Alpha +* running OSF/1) using GCC -O3 as a compiler: before recoding: 51.6 sec. to +* generate 300 million random numbers; after recoding: 24.0 sec. for the same +* (i.e., 46.5% of original time), so speed is now about 12.5 million random +* number generations per second on this machine. +* +* According to the URL +* (and paraphrasing a bit in places), the Mersenne Twister is ``designed +* with consideration of the flaws of various existing generators,'' has +* a period of 2^19937 - 1, gives a sequence that is 623-dimensionally +* equidistributed, and ``has passed many stringent tests, including the +* die-hard test of G. Marsaglia and the load test of P. Hellekalek and +* S. Wegenkittl.'' It is efficient in memory usage (typically using 2506 +* to 5012 bytes of static data, depending on data type sizes, and the code +* is quite short as well). It generates random numbers in batches of 624 +* at a time, so the caching and pipelining of modern systems is exploited. +* It is also divide- and mod-free. +* +* Licensing is free http://www.math.sci.hiroshima-u.ac.jp/~m-mat/MT/MT2002/elicense.html +* +* The code as Shawn received it included the following notice: +* +* Copyright (C) 1997 Makoto Matsumoto and Takuji Nishimura. When +* you use this, send an e-mail to with +* an appropriate reference to your work. +* +* It would be nice to CC: when you write. +*/ + +#include +#include +#include +#include "Rand.h" + +// +// uint32 must be an unsigned integer type capable of holding at least 32 +// bits; exactly 32 should be fastest, but 64 is better on an Alpha with +// GCC at -O3 optimization so try your options and see what's best for you +// + +//typedef unsigned int uint32; + +#define N (624) // length of state vector +#define M (397) // a period parameter +#define K (0x9908B0DFU) // a magic constant +#define hiBit(u) ((u) & 0x80000000U) // mask all but highest bit of u +#define loBit(u) ((u) & 0x00000001U) // mask all but lowest bit of u +#define loBits(u) ((u) & 0x7FFFFFFFU) // mask the highest bit of u +#define mixBits(u, v) (hiBit(u)|loBits(v)) // move hi bit of u to hi bit of v + +static unsigned int _state[ N + 1 ]; // state vector + 1 extra to not violate ANSI C +static unsigned int *_next; // next random value is computed from here +static int _left = -1; // can *next++ this many times before reloading + +void seedMT( unsigned int seed, unsigned int *state, unsigned int *&next, int &left ); +unsigned int reloadMT( unsigned int *state, unsigned int *&next, int &left ); +unsigned int randomMT( unsigned int *state, unsigned int *&next, int &left ); +void fillBufferMT( void *buffer, unsigned int bytes, unsigned int *state, unsigned int *&next, int &left ); +float frandomMT( unsigned int *state, unsigned int *&next, int &left ); + +// Uses global vars +void seedMT( unsigned int seed ) +{ + seedMT(seed, _state, _next, _left); +} +unsigned int reloadMT( void ) +{ + return reloadMT(_state, _next, _left); +} +unsigned int randomMT( void ) +{ + return randomMT(_state, _next, _left); +} +float frandomMT( void ) +{ + return frandomMT(_state, _next, _left); +} +void fillBufferMT( void *buffer, unsigned int bytes ) +{ + fillBufferMT(buffer, bytes, _state, _next, _left); +} + +void seedMT( unsigned int seed, unsigned int *state, unsigned int *&next, int &left ) // Defined in cokus_c.c +{ + (void) next; + + // + // We initialize state[0..(N-1)] via the generator + // + // x_new = (69069 * x_old) mod 2^32 + // + // from Line 15 of Table 1, p. 106, Sec. 3.3.4 of Knuth's + // _The Art of Computer Programming_, Volume 2, 3rd ed. + // + // Notes (SJC): I do not know what the initial state requirements + // of the Mersenne Twister are, but it seems this seeding generator + // could be better. It achieves the maximum period for its modulus + // (2^30) iff x_initial is odd (p. 20-21, Sec. 3.2.1.2, Knuth); if + // x_initial can be even, you have sequences like 0, 0, 0, ...; + // 2^31, 2^31, 2^31, ...; 2^30, 2^30, 2^30, ...; 2^29, 2^29 + 2^31, + // 2^29, 2^29 + 2^31, ..., etc. so I force seed to be odd below. + // + // Even if x_initial is odd, if x_initial is 1 mod 4 then + // + // the lowest bit of x is always 1, + // the next-to-lowest bit of x is always 0, + // the 2nd-from-lowest bit of x alternates ... 0 1 0 1 0 1 0 1 ... , + // the 3rd-from-lowest bit of x 4-cycles ... 0 1 1 0 0 1 1 0 ... , + // the 4th-from-lowest bit of x has the 8-cycle ... 0 0 0 1 1 1 1 0 ... , + // ... + // + // and if x_initial is 3 mod 4 then + // + // the lowest bit of x is always 1, + // the next-to-lowest bit of x is always 1, + // the 2nd-from-lowest bit of x alternates ... 0 1 0 1 0 1 0 1 ... , + // the 3rd-from-lowest bit of x 4-cycles ... 0 0 1 1 0 0 1 1 ... , + // the 4th-from-lowest bit of x has the 8-cycle ... 0 0 1 1 1 1 0 0 ... , + // ... + // + // The generator's potency (min. s>=0 with (69069-1)^s = 0 mod 2^32) is + // 16, which seems to be alright by p. 25, Sec. 3.2.1.3 of Knuth. It + // also does well in the dimension 2..5 spectral tests, but it could be + // better in dimension 6 (Line 15, Table 1, p. 106, Sec. 3.3.4, Knuth). + // + // Note that the random number user does not see the values generated + // here directly since reloadMT() will always munge them first, so maybe + // none of all of this matters. In fact, the seed values made here could + // even be extra-special desirable if the Mersenne Twister theory says + // so-- that's why the only change I made is to restrict to odd seeds. + // + + register unsigned int x = ( seed | 1U ) & 0xFFFFFFFFU, *s = state; + register int j; + + for ( left = 0, *s++ = x, j = N; --j; + *s++ = ( x *= 69069U ) & 0xFFFFFFFFU ) + + ; +} + + +unsigned int reloadMT( unsigned int *state, unsigned int *&next, int &left ) +{ + register unsigned int * p0 = state, *p2 = state + 2, *pM = state + M, s0, s1; + register int j; + + if ( left < -1 ) + seedMT( 4357U ); + + left = N - 1, next = state + 1; + + for ( s0 = state[ 0 ], s1 = state[ 1 ], j = N - M + 1; --j; s0 = s1, s1 = *p2++ ) + * p0++ = *pM++ ^ ( mixBits( s0, s1 ) >> 1 ) ^ ( loBit( s1 ) ? K : 0U ); + + for ( pM = state, j = M; --j; s0 = s1, s1 = *p2++ ) + * p0++ = *pM++ ^ ( mixBits( s0, s1 ) >> 1 ) ^ ( loBit( s1 ) ? K : 0U ); + + s1 = state[ 0 ], *p0 = *pM ^ ( mixBits( s0, s1 ) >> 1 ) ^ ( loBit( s1 ) ? K : 0U ); + + s1 ^= ( s1 >> 11 ); + + s1 ^= ( s1 << 7 ) & 0x9D2C5680U; + + s1 ^= ( s1 << 15 ) & 0xEFC60000U; + + return ( s1 ^ ( s1 >> 18 ) ); +} + + +unsigned int randomMT( unsigned int *state, unsigned int *&next, int &left ) +{ + unsigned int y; + + if ( --left < 0 ) + return ( reloadMT(state, next, left) ); + + y = *next++; + + y ^= ( y >> 11 ); + + y ^= ( y << 7 ) & 0x9D2C5680U; + + y ^= ( y << 15 ) & 0xEFC60000U; + + return ( y ^ ( y >> 18 ) ); + + // This change made so the value returned is in the same range as what rand() returns + // return(y ^ (y >> 18)) % 32767; +} + +void fillBufferMT( void *buffer, unsigned int bytes, unsigned int *state, unsigned int *&next, int &left ) +{ + unsigned int offset=0; + unsigned int r; + while (bytes-offset>=sizeof(r)) + { + r = randomMT(); + memcpy((char*)buffer+offset, &r, sizeof(r)); + offset+=sizeof(r); + } + + r = randomMT(state, next, left); + memcpy((char*)buffer+offset, &r, bytes-offset); +} + +float frandomMT( unsigned int *state, unsigned int *&next, int &left ) +{ + return ( float ) ( ( double ) randomMT(state, next, left) / 4294967296.0 ); +} +RakNetRandom::RakNetRandom() +{ + left=-1; +} +RakNetRandom::~RakNetRandom() +{ +} +void RakNetRandom::SeedMT( unsigned int seed ) +{ + seedMT(seed, state, next, left); +} + +unsigned int RakNetRandom::ReloadMT( void ) +{ + return reloadMT(state, next, left); +} + +unsigned int RakNetRandom::RandomMT( void ) +{ + return randomMT(state, next, left); +} + +float RakNetRandom::FrandomMT( void ) +{ + return frandomMT(state, next, left); +} + +void RakNetRandom::FillBufferMT( void *buffer, unsigned int bytes ) +{ + fillBufferMT(buffer, bytes, state, next, left); +} + +/* +int main(void) +{ +int j; + +// you can seed with any uint32, but the best are odds in 0..(2^32 - 1) + +seedMT(4357U); + +// print the first 2,002 random numbers seven to a line as an example + +for(j=0; j<2002; j++) +RAKNET_DEBUG_PRINTF(" %10lu%s", (unsigned int) randomMT(), (j%7)==6 ? "\n" : ""); + +return(EXIT_SUCCESS); +} + +*/ + diff --git a/RakNet/Sources/Rand.h b/RakNet/Sources/Rand.h new file mode 100644 index 0000000..1b03906 --- /dev/null +++ b/RakNet/Sources/Rand.h @@ -0,0 +1,56 @@ +/// \file +/// \brief \b [Internal] Random number generator +/// +/// This file is part of RakNet Copyright 2003 Jenkins Software LLC +/// +/// Usage of RakNet is subject to the appropriate license agreement. + + + +#ifndef __RAND_H +#define __RAND_H + +#include "Export.h" + +/// Initialise seed for Random Generator +/// \note not threadSafe, use an instance of RakNetRandom if necessary per thread +/// \param[in] seed The seed value for the random number generator. +extern void RAK_DLL_EXPORT seedMT( unsigned int seed ); + +/// \internal +/// \note not threadSafe, use an instance of RakNetRandom if necessary per thread +extern unsigned int RAK_DLL_EXPORT reloadMT( void ); + +/// Gets a random unsigned int +/// \note not threadSafe, use an instance of RakNetRandom if necessary per thread +/// \return an integer random value. +extern unsigned int RAK_DLL_EXPORT randomMT( void ); + +/// Gets a random float +/// \note not threadSafe, use an instance of RakNetRandom if necessary per thread +/// \return 0 to 1.0f, inclusive +extern float RAK_DLL_EXPORT frandomMT( void ); + +/// Randomizes a buffer +/// \note not threadSafe, use an instance of RakNetRandom if necessary per thread +extern void RAK_DLL_EXPORT fillBufferMT( void *buffer, unsigned int bytes ); + +// Same thing as above functions, but not global +class RAK_DLL_EXPORT RakNetRandom +{ +public: + RakNetRandom(); + ~RakNetRandom(); + void SeedMT( unsigned int seed ); + unsigned int ReloadMT( void ); + unsigned int RandomMT( void ); + float FrandomMT( void ); + void FillBufferMT( void *buffer, unsigned int bytes ); + +protected: + unsigned int state[ 624 + 1 ]; + unsigned int *next; + int left; +}; + +#endif diff --git a/RakNet/Sources/ReadyEvent.cpp b/RakNet/Sources/ReadyEvent.cpp new file mode 100644 index 0000000..a02d05f --- /dev/null +++ b/RakNet/Sources/ReadyEvent.cpp @@ -0,0 +1,561 @@ +#include "NativeFeatureIncludes.h" +#if _RAKNET_SUPPORT_ReadyEvent==1 + +#include "ReadyEvent.h" +#include "RakPeerInterface.h" +#include "BitStream.h" +#include "MessageIdentifiers.h" +#include "RakAssert.h" + +#ifdef _MSC_VER +#pragma warning( push ) +#endif + +int ReadyEvent::RemoteSystemCompBySystemAddress( const SystemAddress &key, const RemoteSystem &data ) +{ + if (key < data.systemAddress) + return -1; + else if (key==data.systemAddress) + return 0; + else + return 1; +} + +int ReadyEvent::ReadyEventNodeComp( const int &key, ReadyEvent::ReadyEventNode * const &data ) +{ + if (key < data->eventId) + return -1; + else if (key==data->eventId) + return 0; + else + return 1; +} + + +ReadyEvent::ReadyEvent() +{ + channel=0; +} + +ReadyEvent::~ReadyEvent() +{ + Clear(); +} + + +bool ReadyEvent::SetEvent(int eventId, bool isReady) +{ + bool objectExists; + unsigned eventIndex = readyEventNodeList.GetIndexFromKey(eventId, &objectExists); + if (objectExists==false) + { + // Totally new event + CreateNewEvent(eventId, isReady); + } + else + { + return SetEventByIndex(eventIndex, isReady); + } + return true; +} +bool ReadyEvent::ForceCompletion(int eventId) +{ + bool objectExists; + unsigned eventIndex = readyEventNodeList.GetIndexFromKey(eventId, &objectExists); + if (objectExists==false) + { + // Totally new event + CreateNewEvent(eventId, true); + eventIndex = readyEventNodeList.GetIndexFromKey(eventId, &objectExists); + } + + ReadyEventNode *ren = readyEventNodeList[eventIndex]; + ren->eventStatus=ID_READY_EVENT_FORCE_ALL_SET; + UpdateReadyStatus(eventIndex); + + return true; +} +bool ReadyEvent::DeleteEvent(int eventId) +{ + bool objectExists; + unsigned eventIndex = readyEventNodeList.GetIndexFromKey(eventId, &objectExists); + if (objectExists) + { + RakNet::OP_DELETE(readyEventNodeList[eventIndex], __FILE__, __LINE__); + readyEventNodeList.RemoveAtIndex(eventIndex); + return true; + } + return false; +} +bool ReadyEvent::IsEventSet(int eventId) +{ + bool objectExists; + unsigned eventIndex = readyEventNodeList.GetIndexFromKey(eventId, &objectExists); + if (objectExists) + { + return readyEventNodeList[eventIndex]->eventStatus==ID_READY_EVENT_SET || readyEventNodeList[eventIndex]->eventStatus==ID_READY_EVENT_ALL_SET; + } + return false; +} +bool ReadyEvent::IsEventCompletionProcessing(int eventId) const +{ + bool objectExists; + unsigned eventIndex = readyEventNodeList.GetIndexFromKey(eventId, &objectExists); + if (objectExists) + { + bool anyAllReady=false; + bool allAllReady=true; + ReadyEventNode *ren = readyEventNodeList[eventIndex]; + if (ren->eventStatus==ID_READY_EVENT_FORCE_ALL_SET) + return false; + for (unsigned i=0; i < ren->systemList.Size(); i++) + { + if (ren->systemList[i].lastReceivedStatus==ID_READY_EVENT_ALL_SET) + anyAllReady=true; + else + allAllReady=false; + } + return anyAllReady==true && allAllReady==false; + } + return false; +} +bool ReadyEvent::IsEventCompleted(int eventId) const +{ + bool objectExists; + unsigned eventIndex = readyEventNodeList.GetIndexFromKey(eventId, &objectExists); + if (objectExists) + { + return IsEventCompletedByIndex(eventIndex); + } + return false; +} + +bool ReadyEvent::HasEvent(int eventId) +{ + return readyEventNodeList.HasData(eventId); +} + +unsigned ReadyEvent::GetEventListSize(void) const +{ + return readyEventNodeList.Size(); +} + +int ReadyEvent::GetEventAtIndex(unsigned index) const +{ + return readyEventNodeList[index]->eventId; +} + +bool ReadyEvent::AddToWaitList(int eventId, SystemAddress address) +{ + bool eventExists; + unsigned eventIndex = readyEventNodeList.GetIndexFromKey(eventId, &eventExists); + if (eventExists==false) + eventIndex=CreateNewEvent(eventId, false); + + // Don't do this, otherwise if we are trying to start a 3 player game, it will not allow the 3rd player to hit ready if the first two players have already done so + //if (IsLocked(eventIndex)) + // return false; // Not in the list, but event is already completed, or is starting to complete, and adding more waiters would fail this. + + unsigned i; + unsigned numAdded=0; + if (address==UNASSIGNED_SYSTEM_ADDRESS) + { + for (i=0; i < rakPeerInterface->GetMaximumNumberOfPeers(); i++) + { + SystemAddress internalAddress = rakPeerInterface->GetSystemAddressFromIndex(i); + if (internalAddress!=UNASSIGNED_SYSTEM_ADDRESS) + { + numAdded+=AddToWaitListInternal(eventIndex, internalAddress); + } + } + } + else + { + numAdded=AddToWaitListInternal(eventIndex, address); + } + + if (numAdded>0) + UpdateReadyStatus(eventIndex); + return numAdded>0; +} +bool ReadyEvent::RemoveFromWaitList(int eventId, SystemAddress address) +{ + bool eventExists; + unsigned eventIndex = readyEventNodeList.GetIndexFromKey(eventId, &eventExists); + if (eventExists) + { + if (address==UNASSIGNED_SYSTEM_ADDRESS) + { + // Remove all waiters + readyEventNodeList[eventIndex]->systemList.Clear(false, __FILE__, __LINE__); + UpdateReadyStatus(eventIndex); + } + else + { + bool systemExists; + unsigned systemIndex = readyEventNodeList[eventIndex]->systemList.GetIndexFromKey(address, &systemExists); + if (systemExists) + { + bool isCompleted = IsEventCompletedByIndex(eventIndex); + readyEventNodeList[eventIndex]->systemList.RemoveAtIndex(systemIndex); + + if (isCompleted==false && IsEventCompletedByIndex(eventIndex)) + PushCompletionPacket(readyEventNodeList[eventIndex]->eventId); + + UpdateReadyStatus(eventIndex); + + return true; + } + } + } + + return false; +} +bool ReadyEvent::IsInWaitList(int eventId, SystemAddress address) +{ + bool objectExists; + unsigned readyIndex = readyEventNodeList.GetIndexFromKey(eventId, &objectExists); + if (objectExists) + { + return readyEventNodeList[readyIndex]->systemList.HasData(address); + } + return false; +} + +unsigned ReadyEvent::GetRemoteWaitListSize(int eventId) const +{ + bool objectExists; + unsigned readyIndex = readyEventNodeList.GetIndexFromKey(eventId, &objectExists); + if (objectExists) + { + return readyEventNodeList[readyIndex]->systemList.Size(); + } + return 0; +} + +SystemAddress ReadyEvent::GetFromWaitListAtIndex(int eventId, unsigned index) const +{ + bool objectExists; + unsigned readyIndex = readyEventNodeList.GetIndexFromKey(eventId, &objectExists); + if (objectExists) + { + return readyEventNodeList[readyIndex]->systemList[index].systemAddress; + } + return UNASSIGNED_SYSTEM_ADDRESS; +} +ReadyEventSystemStatus ReadyEvent::GetReadyStatus(int eventId, SystemAddress address) +{ + bool objectExists; + unsigned readyIndex = readyEventNodeList.GetIndexFromKey(eventId, &objectExists); + if (objectExists) + { + ReadyEventNode *ren = readyEventNodeList[readyIndex]; + unsigned systemIndex = ren->systemList.GetIndexFromKey(address, &objectExists); + if (objectExists==false) + return RES_NOT_WAITING; + if (ren->systemList[systemIndex].lastReceivedStatus==ID_READY_EVENT_SET) + return RES_READY; + if (ren->systemList[systemIndex].lastReceivedStatus==ID_READY_EVENT_UNSET) + return RES_WAITING; + if (ren->systemList[systemIndex].lastReceivedStatus==ID_READY_EVENT_ALL_SET) + return RES_ALL_READY; + } + + return RES_UNKNOWN_EVENT; +} +void ReadyEvent::SetSendChannel(unsigned char newChannel) +{ + channel=newChannel; +} +PluginReceiveResult ReadyEvent::OnReceive(Packet *packet) +{ + unsigned char packetIdentifier; + packetIdentifier = ( unsigned char ) packet->data[ 0 ]; + +// bool doPrint = packet->systemAddress.port==60002 || rakPeerInterface->GetInternalID(UNASSIGNED_SYSTEM_ADDRESS).port==60002; + + switch (packetIdentifier) + { + case ID_READY_EVENT_UNSET: + case ID_READY_EVENT_SET: + case ID_READY_EVENT_ALL_SET: +// if (doPrint) {if (packet->systemAddress.port==60002) RAKNET_DEBUG_PRINTF("FROM 60002: "); else if (rakPeerInterface->GetInternalID(UNASSIGNED_SYSTEM_ADDRESS).port==60002) RAKNET_DEBUG_PRINTF("TO 60002: "); RAKNET_DEBUG_PRINTF("ID_READY_EVENT_SET\n");} + OnReadyEventPacketUpdate(packet); + return RR_CONTINUE_PROCESSING; + case ID_READY_EVENT_FORCE_ALL_SET: + OnReadyEventForceAllSet(packet); + return RR_CONTINUE_PROCESSING; + case ID_READY_EVENT_QUERY: +// if (doPrint) {if (packet->systemAddress.port==60002) RAKNET_DEBUG_PRINTF("FROM 60002: "); else if (rakPeerInterface->GetInternalID(UNASSIGNED_SYSTEM_ADDRESS).port==60002) RAKNET_DEBUG_PRINTF("TO 60002: "); RAKNET_DEBUG_PRINTF("ID_READY_EVENT_QUERY\n");} + OnReadyEventQuery(packet); + return RR_STOP_PROCESSING_AND_DEALLOCATE; + } + + return RR_CONTINUE_PROCESSING; +} +bool ReadyEvent::AddToWaitListInternal(unsigned eventIndex, SystemAddress address) +{ + ReadyEventNode *ren = readyEventNodeList[eventIndex]; + bool objectExists; + unsigned systemIndex = ren->systemList.GetIndexFromKey(address, &objectExists); + if (objectExists==false) + { + RemoteSystem rs; + rs.lastReceivedStatus=ID_READY_EVENT_UNSET; + rs.lastSentStatus=ID_READY_EVENT_UNSET; + rs.systemAddress=address; + ren->systemList.InsertAtIndex(rs,systemIndex, __FILE__,__LINE__); + + SendReadyStateQuery(ren->eventId, address); + return true; + } + return false; +} +void ReadyEvent::OnReadyEventForceAllSet(Packet *packet) +{ + RakNet::BitStream incomingBitStream(packet->data, packet->length, false); + incomingBitStream.IgnoreBits(8); + int eventId; + incomingBitStream.Read(eventId); + bool objectExists; + unsigned readyIndex = readyEventNodeList.GetIndexFromKey(eventId, &objectExists); + if (objectExists) + { + ReadyEventNode *ren = readyEventNodeList[readyIndex]; + if (ren->eventStatus!=ID_READY_EVENT_FORCE_ALL_SET) + { + ren->eventStatus=ID_READY_EVENT_FORCE_ALL_SET; + PushCompletionPacket(ren->eventId); + } + } +} +void ReadyEvent::OnReadyEventPacketUpdate(Packet *packet) +{ + RakNet::BitStream incomingBitStream(packet->data, packet->length, false); + incomingBitStream.IgnoreBits(8); + int eventId; + incomingBitStream.Read(eventId); + bool objectExists; + unsigned readyIndex = readyEventNodeList.GetIndexFromKey(eventId, &objectExists); + if (objectExists) + { + ReadyEventNode *ren = readyEventNodeList[readyIndex]; + bool systemExists; + unsigned systemIndex = ren->systemList.GetIndexFromKey(packet->systemAddress, &systemExists); + if (systemExists) + { + // Just return if no change + if (ren->systemList[systemIndex].lastReceivedStatus==packet->data[0]) + return; + + bool wasCompleted = IsEventCompletedByIndex(readyIndex); + ren->systemList[systemIndex].lastReceivedStatus=packet->data[0]; + // If forced all set, doesn't matter what the new packet is + if (ren->eventStatus==ID_READY_EVENT_FORCE_ALL_SET) + return; + UpdateReadyStatus(readyIndex); + if (wasCompleted==false && IsEventCompletedByIndex(readyIndex)) + PushCompletionPacket(readyIndex); + } + } +} +void ReadyEvent::OnReadyEventQuery(Packet *packet) +{ + RakNet::BitStream incomingBitStream(packet->data, packet->length, false); + incomingBitStream.IgnoreBits(8); + int eventId; + incomingBitStream.Read(eventId); + bool objectExists; + unsigned readyIndex = readyEventNodeList.GetIndexFromKey(eventId, &objectExists); + if (objectExists) + { + unsigned systemIndex = readyEventNodeList[readyIndex]->systemList.GetIndexFromKey(packet->systemAddress,&objectExists); + // Force the non-default send, because our initial send may have arrived at a system that didn't yet create the ready event + if (objectExists) + SendReadyUpdate(readyIndex, systemIndex, true); + } +} +void ReadyEvent::OnClosedConnection(SystemAddress systemAddress, RakNetGUID rakNetGUID, PI2_LostConnectionReason lostConnectionReason ) +{ + (void) systemAddress; + (void) rakNetGUID; + (void) lostConnectionReason; + + RemoveFromAllLists(systemAddress); +} +void ReadyEvent::OnRakPeerShutdown(void) +{ + Clear(); +} + +bool ReadyEvent::SetEventByIndex(int eventIndex, bool isReady) +{ + ReadyEventNode *ren = readyEventNodeList[eventIndex]; + if ((ren->eventStatus==ID_READY_EVENT_ALL_SET || ren->eventStatus==ID_READY_EVENT_SET) && isReady==true) + return true; // Success - no change + if (ren->eventStatus==ID_READY_EVENT_UNSET && isReady==false) + return true; // Success - no change + if (ren->eventStatus==ID_READY_EVENT_FORCE_ALL_SET) + return true; // Can't change + + if (isReady) + ren->eventStatus=ID_READY_EVENT_SET; + else + ren->eventStatus=ID_READY_EVENT_UNSET; + + UpdateReadyStatus(eventIndex); + + // Check if now completed, and if so, tell the user about it + if (IsEventCompletedByIndex(eventIndex)) + { + PushCompletionPacket(ren->eventId); + } + + return true; +} + +bool ReadyEvent::IsEventCompletedByIndex(unsigned eventIndex) const +{ + ReadyEventNode *ren = readyEventNodeList[eventIndex]; + unsigned i; + if (ren->eventStatus==ID_READY_EVENT_FORCE_ALL_SET) + return true; + if (ren->eventStatus!=ID_READY_EVENT_ALL_SET) + return false; + for (i=0; i < ren->systemList.Size(); i++) + if (ren->systemList[i].lastReceivedStatus!=ID_READY_EVENT_ALL_SET) + return false; + return true; +} + +void ReadyEvent::Clear(void) +{ + unsigned i; + for (i=0; i < readyEventNodeList.Size(); i++) + { + RakNet::OP_DELETE(readyEventNodeList[i], __FILE__, __LINE__); + } + readyEventNodeList.Clear(false, __FILE__, __LINE__); +} + +unsigned ReadyEvent::CreateNewEvent(int eventId, bool isReady) +{ + ReadyEventNode *ren = RakNet::OP_NEW( __FILE__, __LINE__ ); + ren->eventId=eventId; + if (isReady==false) + ren->eventStatus=ID_READY_EVENT_UNSET; + else + ren->eventStatus=ID_READY_EVENT_SET; + return readyEventNodeList.Insert(eventId, ren, true, __FILE__,__LINE__); +} +void ReadyEvent::UpdateReadyStatus(unsigned eventIndex) +{ + ReadyEventNode *ren = readyEventNodeList[eventIndex]; + bool anyUnset; + unsigned i; + if (ren->eventStatus==ID_READY_EVENT_SET) + { + // If you are set, and no other systems are ID_READY_EVENT_UNSET, then change your status to ID_READY_EVENT_ALL_SET + anyUnset=false; + for (i=0; i < ren->systemList.Size(); i++) + { + if (ren->systemList[i].lastReceivedStatus==ID_READY_EVENT_UNSET) + { + anyUnset=true; + break; + } + } + if (anyUnset==false) + { + ren->eventStatus=ID_READY_EVENT_ALL_SET; + } + } + else if (ren->eventStatus==ID_READY_EVENT_ALL_SET) + { + // If you are all set, and any systems are ID_READY_EVENT_UNSET, then change your status to ID_READY_EVENT_SET + anyUnset=false; + for (i=0; i < ren->systemList.Size(); i++) + { + if (ren->systemList[i].lastReceivedStatus==ID_READY_EVENT_UNSET) + { + anyUnset=true; + break; + } + } + if (anyUnset==true) + { + ren->eventStatus=ID_READY_EVENT_SET; + } + } + BroadcastReadyUpdate(eventIndex, false); +} +void ReadyEvent::SendReadyUpdate(unsigned eventIndex, unsigned systemIndex, bool forceIfNotDefault) +{ + ReadyEventNode *ren = readyEventNodeList[eventIndex]; + RakNet::BitStream bs; + // I do this rather than write true or false, so users that do not use BitStreams can still read the data + if ((ren->eventStatus!=ren->systemList[systemIndex].lastSentStatus) || + (forceIfNotDefault && ren->eventStatus!=ID_READY_EVENT_UNSET)) + { + bs.Write(ren->eventStatus); + bs.Write(ren->eventId); + SendUnified(&bs, HIGH_PRIORITY, RELIABLE_ORDERED, channel, ren->systemList[systemIndex].systemAddress, false); + + ren->systemList[systemIndex].lastSentStatus=ren->eventStatus; + } + +} +void ReadyEvent::BroadcastReadyUpdate(unsigned eventIndex, bool forceIfNotDefault) +{ + ReadyEventNode *ren = readyEventNodeList[eventIndex]; + unsigned systemIndex; + for (systemIndex=0; systemIndex < ren->systemList.Size(); systemIndex++) + { + SendReadyUpdate(eventIndex, systemIndex, forceIfNotDefault); + } +} +void ReadyEvent::SendReadyStateQuery(unsigned eventId, SystemAddress address) +{ + RakNet::BitStream bs; + bs.Write((MessageID)ID_READY_EVENT_QUERY); + bs.Write(eventId); + SendUnified(&bs, HIGH_PRIORITY, RELIABLE_ORDERED, channel, address, false); +} +void ReadyEvent::RemoveFromAllLists(SystemAddress address) +{ + unsigned eventIndex; + for (eventIndex=0; eventIndex < readyEventNodeList.Size(); eventIndex++) + { + bool isCompleted = IsEventCompletedByIndex(eventIndex); + bool systemExists; + unsigned systemIndex; + + systemIndex = readyEventNodeList[eventIndex]->systemList.GetIndexFromKey(address, &systemExists); + if (systemExists) + readyEventNodeList[eventIndex]->systemList.RemoveAtIndex(systemIndex); + + UpdateReadyStatus(eventIndex); + + if (isCompleted==false && IsEventCompletedByIndex(eventIndex)) + PushCompletionPacket(readyEventNodeList[eventIndex]->eventId); + } +} +void ReadyEvent::PushCompletionPacket(unsigned eventId) +{ + (void) eventId; + // Not necessary + /* + // Pass a packet to the user that we are now completed, as setting ourselves to signaled was the last thing being waited on + Packet *p = rakPeerInterface->AllocatePacket(sizeof(MessageID)+sizeof(int)); + RakNet::BitStream bs(p->data, sizeof(MessageID)+sizeof(int), false); + bs.SetWriteOffset(0); + bs.Write((MessageID)ID_READY_EVENT_ALL_SET); + bs.Write(eventId); + rakPeerInterface->PushBackPacket(p, false); + */ +} +#ifdef _MSC_VER +#pragma warning( pop ) +#endif + +#endif // _RAKNET_SUPPORT_* diff --git a/RakNet/Sources/ReadyEvent.h b/RakNet/Sources/ReadyEvent.h new file mode 100644 index 0000000..387be24 --- /dev/null +++ b/RakNet/Sources/ReadyEvent.h @@ -0,0 +1,226 @@ +/// \file +/// \brief Ready event plugin. This enables a set of systems to create a signal event, set this signal as ready or unready, and to trigger the event when all systems are ready +/// +/// This file is part of RakNet Copyright 2003 Jenkins Software LLC +/// +/// Usage of RakNet is subject to the appropriate license agreement. + +#include "NativeFeatureIncludes.h" +#if _RAKNET_SUPPORT_ReadyEvent==1 + +#ifndef __READY_EVENT_H +#define __READY_EVENT_H + +class RakPeerInterface; +#include "PluginInterface2.h" +#include "DS_OrderedList.h" + +/// \defgroup READY_EVENT_GROUP ReadyEvent +/// \brief Peer to peer synchronized ready and unready events +/// \details +/// \ingroup PLUGINS_GROUP + +/// \ingroup READY_EVENT_GROUP +/// Returns the status of a remote system when querying with ReadyEvent::GetReadyStatus +enum ReadyEventSystemStatus +{ + /// ----------- Normal states --------------- + /// The remote system is not in the wait list, and we have never gotten a ready or complete message from it. + /// This is the default state for valid events + RES_NOT_WAITING, + /// We are waiting for this remote system to call SetEvent(thisEvent,true). + RES_WAITING, + /// The remote system called SetEvent(thisEvent,true), but it still waiting for other systems before completing the ReadyEvent. + RES_READY, + /// The remote system called SetEvent(thisEvent,true), and is no longer waiting for any other systems. + /// This remote system has completed the ReadyEvent + RES_ALL_READY, + + /// Error code, we couldn't look up the system because the event was unknown + RES_UNKNOWN_EVENT, +}; + +/// \brief Peer to peer synchronized ready and unready events +/// \details For peer to peer networks in a fully connected mesh.
+/// Solves the problem of how to tell if all peers, relative to all other peers, are in a certain ready state.
+/// For example, if A is connected to B and C, A may see that B and C are ready, but does not know if B is ready to C, or vice-versa.
+/// This plugin uses two stages to solve that problem, first, everyone I know about is ready. Second, everyone I know about is ready to everyone they know about.
+/// The user will get ID_READY_EVENT_SET and ID_READY_EVENT_UNSET as the signal flag is set or unset
+/// The user will get ID_READY_EVENT_ALL_SET when all systems are done waiting for all other systems, in which case the event is considered complete, and no longer tracked.
+/// \sa FullyConnectedMesh2 +/// \ingroup READY_EVENT_GROUP +class ReadyEvent : public PluginInterface2 +{ +public: + // Constructor + ReadyEvent(); + + // Destructor + virtual ~ReadyEvent(); + + // -------------------------------------------------------------------------------------------- + // User functions + // -------------------------------------------------------------------------------------------- + /// Sets or updates the initial ready state for our local system. + /// If eventId is an unknown event the event is created. + /// If eventId was previously used and you want to reuse it, call DeleteEvent first, or else you will keep the same event signals from before + /// Systems previously or later added through AddToWaitList() with the same \a eventId when isReady=true will get ID_READY_EVENT_SET + /// Systems previously added through AddToWaitList with the same \a eventId will get ID_READY_EVENT_UNSET + /// For both ID_READY_EVENT_SET and ID_READY_EVENT_UNSET, eventId is encoded in bytes 1 through 1+sizeof(int) + /// \param[in] eventId A user-defined identifier to wait on. This can be a sequence counter, an event identifier, or anything else you want. + /// \param[in] isReady True to signal we are ready to proceed with this event, false to unsignal + /// \return True on success. False (failure) on unknown eventId + bool SetEvent(int eventId, bool isReady); + + /// When systems can call SetEvent() with isReady==false, it is possible for one system to return true from IsEventCompleted() while the other systems return false + /// This can occur if a system SetEvent() with isReady==false while the completion message is still being transmitted. + /// If your game has the situation where some action should be taken on all systems when IsEventCompleted() is true for any system, then call ForceCompletion() when the action begins. + /// This will force all systems to return true from IsEventCompleted(). + /// \param[in] eventId A user-defined identifier to immediately set as completed + bool ForceCompletion(int eventId); + + /// Deletes an event. We will no longer wait for this event, and any systems that we know have set the event will be forgotten. + /// Call this to clear memory when events are completed and you know you will never need them again. + /// \param[in] eventId A user-defined identifier + /// \return True on success. False (failure) on unknown eventId + bool DeleteEvent(int eventId); + + /// Returns what was passed to SetEvent() + /// \return The value of isReady passed to SetEvent(). Also returns false on unknown event. + bool IsEventSet(int eventId); + + /// Returns if the event is about to be ready and we are negotiating the final packets. + /// This will usually only be true for a very short time, after which IsEventCompleted should return true. + /// While this is true you cannot add to the wait list, or SetEvent() isReady to false anymore. + /// \param[in] eventId A user-defined identifier + /// \return True if any other system has completed processing. Will always be true if IsEventCompleted() is true + bool IsEventCompletionProcessing(int eventId) const; + + /// Returns if the wait list is a subset of the completion list. + /// Call this after all systems you want to wait for have been added with AddToWaitList + /// If you are waiting for a specific number of systems (such as players later connecting), also check GetRemoteWaitListSize(eventId) to be equal to 1 less than the total number of participants. + /// \param[in] eventId A user-defined identifier + /// \return True on completion. False (failure) on unknown eventId, or the set is not completed. + bool IsEventCompleted(int eventId) const; + + /// Returns if this is a known event. + /// Events may be known even if we never ourselves referenced them with SetEvent, because other systems created them via ID_READY_EVENT_SET. + /// \param[in] eventId A user-defined identifier + /// \return true if we have this event, false otherwise + bool HasEvent(int eventId); + + /// Returns the total number of events stored in the system. + /// \return The total number of events stored in the system. + unsigned GetEventListSize(void) const; + + /// Returns the event ID stored at a particular index. EventIDs are stored sorted from least to greatest. + /// \param[in] index Index into the array, from 0 to GetEventListSize() + /// \return The event ID stored at a particular index + int GetEventAtIndex(unsigned index) const; + + /// Adds a system to wait for to signal an event before considering the event complete and returning ID_READY_EVENT_ALL_SET. + /// As we add systems, if this event was previously set to true with SetEvent, these systems will get ID_READY_EVENT_SET. + /// As these systems disconnect (directly or indirectly through the router) they are removed. + /// \note If the event completion process has already started, you cannot add more systems, as this would cause the completion process to fail + /// \param[in] eventId A user-defined number previously passed to SetEvent that has not yet completed + /// \param[in] addressArray An address to wait for event replies from. Pass UNASSIGNED_SYSTEM_ADDRESS for all currently connected systems. Until all systems in this list have called SetEvent with this ID and true, and have this system in the list, we won't get ID_READY_EVENT_COMPLETE + /// \return True on success, false on unknown eventId (this should be considered an error), or if the completion process has already started. + bool AddToWaitList(int eventId, SystemAddress address); + + /// Removes systems from the wait list, which should have been previously added with AddToWaitList + /// \note Systems that directly or indirectly disconnect from us are automatically removed from the wait list + /// \param[in] address The system to remove from the wait list. Pass UNASSIGNED_SYSTEM_ADDRESS for all currently connected systems. + /// \return True on success, false on unknown eventId (this should be considered an error) + bool RemoveFromWaitList(int eventId, SystemAddress address); + + /// Returns if a particular system is waiting on a particular event. + /// \param[in] eventId A user-defined identifier + /// \param[in] The address of the system we are checking up on + /// \return True if this system is waiting on this event, false otherwise. + bool IsInWaitList(int eventId, SystemAddress address); + + /// Returns the total number of systems we are waiting on for this event. + /// Does not include yourself + /// \param[in] eventId A user-defined identifier + /// \return The total number of systems we are waiting on for this event. + unsigned GetRemoteWaitListSize(int eventId) const; + + /// Returns the system address of a system at a particular index, for this event. + /// \param[in] eventId A user-defined identifier + /// \param[in] index Index into the array, from 0 to GetWaitListSize() + /// \return The system address of a system at a particular index, for this event. + SystemAddress GetFromWaitListAtIndex(int eventId, unsigned index) const; + + /// For a remote system, find out what their ready status is (waiting, signaled, complete). + /// \param[in] eventId A user-defined identifier + /// \param[in] address Which system we are checking up on + /// \return The status of this system, for this particular event. \sa ReadyEventSystemStatus + ReadyEventSystemStatus GetReadyStatus(int eventId, SystemAddress address); + + /// This channel will be used for all RakPeer::Send calls + /// \param[in] newChannel The channel to use for internal RakPeer::Send calls from this system. Defaults to 0. + void SetSendChannel(unsigned char newChannel); + + // ---------------------------- ALL INTERNAL AFTER HERE ---------------------------- + /// \internal + /// Status of a remote system + struct RemoteSystem + { + MessageID lastSentStatus, lastReceivedStatus; + SystemAddress systemAddress; + }; + static int RemoteSystemCompBySystemAddress( const SystemAddress &key, const RemoteSystem &data ); + /// \internal + /// An event, with a set of systems we are waiting for, a set of systems that are signaled, and a set of systems with completed events + struct ReadyEventNode + { + int eventId; // Sorted on this + MessageID eventStatus; + DataStructures::OrderedList systemList; + }; + static int ReadyEventNodeComp( const int &key, ReadyEvent::ReadyEventNode * const &data ); + + +protected: + // -------------------------------------------------------------------------------------------- + // Packet handling functions + // -------------------------------------------------------------------------------------------- + virtual PluginReceiveResult OnReceive(Packet *packet); + virtual void OnClosedConnection(SystemAddress systemAddress, RakNetGUID rakNetGUID, PI2_LostConnectionReason lostConnectionReason ); + virtual void OnRakPeerShutdown(void); + + void Clear(void); + /* + bool AnyWaitersCompleted(unsigned eventIndex) const; + bool AllWaitersCompleted(unsigned eventIndex) const; + bool AllWaitersReady(unsigned eventIndex) const; + void SendAllReady(unsigned eventId, SystemAddress address); + void BroadcastAllReady(unsigned eventIndex); + void SendReadyStateQuery(unsigned eventId, SystemAddress address); + void BroadcastReadyUpdate(unsigned eventIndex); + bool AddToWaitListInternal(unsigned eventIndex, SystemAddress address); + bool IsLocked(unsigned eventIndex) const; + bool IsAllReadyByIndex(unsigned eventIndex) const; + */ + + void SendReadyStateQuery(unsigned eventId, SystemAddress address); + void SendReadyUpdate(unsigned eventIndex, unsigned systemIndex, bool forceIfNotDefault); + void BroadcastReadyUpdate(unsigned eventIndex, bool forceIfNotDefault); + void RemoveFromAllLists(SystemAddress address); + void OnReadyEventQuery(Packet *packet); + void PushCompletionPacket(unsigned eventId); + bool AddToWaitListInternal(unsigned eventIndex, SystemAddress address); + void OnReadyEventForceAllSet(Packet *packet); + void OnReadyEventPacketUpdate(Packet *packet); + void UpdateReadyStatus(unsigned eventIndex); + bool IsEventCompletedByIndex(unsigned eventIndex) const; + unsigned CreateNewEvent(int eventId, bool isReady); + bool SetEventByIndex(int eventIndex, bool isReady); + + DataStructures::OrderedList readyEventNodeList; + unsigned char channel; +}; + +#endif + +#endif // _RAKNET_SUPPORT_* diff --git a/RakNet/Sources/RefCountedObj.h b/RakNet/Sources/RefCountedObj.h new file mode 100644 index 0000000..ab715db --- /dev/null +++ b/RakNet/Sources/RefCountedObj.h @@ -0,0 +1,25 @@ +/// \file +/// \brief \b Reference counted object. Very simple class for quick and dirty uses. +/// +/// This file is part of RakNet Copyright 2003 Jenkins Software LLC +/// +/// Usage of RakNet is subject to the appropriate license agreement. + + +#ifndef __REF_COUNTED_OBJ_H +#define __REF_COUNTED_OBJ_H + +#include "RakMemoryOverride.h" + +/// World's simplest class :) +class RefCountedObj +{ + public: + RefCountedObj() {refCount=1;} + virtual ~RefCountedObj() {} + void AddRef(void) {refCount++;} + void Deref(void) {if (--refCount==0) RakNet::OP_DELETE(this, __FILE__, __LINE__);} + int refCount; +}; + +#endif diff --git a/RakNet/Sources/ReliabilityLayer.cpp b/RakNet/Sources/ReliabilityLayer.cpp new file mode 100644 index 0000000..ac2f57e --- /dev/null +++ b/RakNet/Sources/ReliabilityLayer.cpp @@ -0,0 +1,3373 @@ +/// \file +/// +/// This file is part of RakNet Copyright 2003 Jenkins Software LLC +/// +/// Usage of RakNet is subject to the appropriate license agreement. + + +#include "ReliabilityLayer.h" +#include "GetTime.h" +#include "SocketLayer.h" +#include "PluginInterface2.h" +#include "RakAssert.h" +#include "Rand.h" +#include "MessageIdentifiers.h" +#include + +// Can't figure out which library has this function on the PS3 +double Ceil(double d) {if (((double)((int)d))==d) return d; return (int) (d+1.0);} + +#if defined(new) +#pragma push_macro("new") +#undef new +#define RELIABILITY_LAYER_NEW_UNDEF_ALLOCATING_QUEUE +#endif + + +//#define _DEBUG_LOGGER + +#if CC_TIME_TYPE_BYTES==4 +static const CCTimeType MAX_TIME_BETWEEN_PACKETS= 350; // 350 milliseconds +static const CCTimeType HISTOGRAM_RESTART_CYCLE=10000; // Every 10 seconds reset the histogram +#else +static const CCTimeType MAX_TIME_BETWEEN_PACKETS= 350000; // 350 milliseconds +//static const CCTimeType HISTOGRAM_RESTART_CYCLE=10000000; // Every 10 seconds reset the histogram +#endif +static const int DEFAULT_HAS_RECEIVED_PACKET_QUEUE_SIZE=512; +static const CCTimeType STARTING_TIME_BETWEEN_PACKETS=MAX_TIME_BETWEEN_PACKETS; +//static const long double TIME_BETWEEN_PACKETS_INCREASE_MULTIPLIER_DEFAULT=.02; +//static const long double TIME_BETWEEN_PACKETS_DECREASE_MULTIPLIER_DEFAULT=1.0 / 9.0; + +typedef uint32_t BitstreamLengthEncoding; + +#ifdef _MSC_VER +#pragma warning( push ) +#endif + + +BPSTracker::TimeAndValue2::TimeAndValue2() {} +BPSTracker::TimeAndValue2::~TimeAndValue2() {} +BPSTracker::TimeAndValue2::TimeAndValue2(RakNetTimeUS t, uint64_t v1) : time(t), value1(v1) {} +//BPSTracker::TimeAndValue2::TimeAndValue2(RakNetTimeUS t, uint64_t v1, uint64_t v2) : time(t), value1(v1), value2(v2) {} +BPSTracker::BPSTracker() {Reset(__FILE__,__LINE__);} +BPSTracker::~BPSTracker() {} +//void BPSTracker::Reset(const char *file, unsigned int line) {total1=total2=lastSec1=lastSec2=0; dataQueue.Clear(file,line);} +void BPSTracker::Reset(const char *file, unsigned int line) {total1=lastSec1=0; dataQueue.Clear(file,line);} +void BPSTracker::Push1(RakNetTimeUS time, uint64_t value1) { + ClearExpired1(time); + dataQueue.Push(TimeAndValue2(time,value1),__FILE__,__LINE__); total1+=value1; lastSec1+=value1; +} +//void BPSTracker::Push2(RakNetTimeUS time, uint64_t value1, uint64_t value2) {dataQueue.Push(TimeAndValue2(time,value1,value2),__FILE__,__LINE__); total1+=value1; lastSec1+=value1; total2+=value2; lastSec2+=value2;} +uint64_t BPSTracker::GetBPS1(RakNetTimeUS time) {ClearExpired1(time); return lastSec1;} +//uint64_t BPSTracker::GetBPS2(RakNetTimeUS time) {ClearExpired2(time); return lastSec2;} +//void BPSTracker::GetBPS1And2(RakNetTimeUS time, uint64_t &out1, uint64_t &out2) {ClearExpired2(time); out1=lastSec1; out2=lastSec2;} +uint64_t BPSTracker::GetTotal1(void) const {return total1;} +//uint64_t BPSTracker::GetTotal2(void) const {return total2;} + +// void BPSTracker::ClearExpired2(RakNetTimeUS time) { +// RakNetTimeUS threshold=time; +// if (threshold < 1000000) +// return; +// threshold-=1000000; +// while (dataQueue.IsEmpty()==false && dataQueue.Peek().time < threshold) +// { +// lastSec1-=dataQueue.Peek().value1; +// lastSec2-=dataQueue.Peek().value2; +// dataQueue.Pop(); +// } +// } +void BPSTracker::ClearExpired1(RakNetTimeUS time) +{ + while (dataQueue.IsEmpty()==false && +#if CC_TIME_TYPE_BYTES==8 + dataQueue.Peek().time+1000000 < time +#else + dataQueue.Peek().time+1000 < time +#endif + ) + { + lastSec1-=dataQueue.Peek().value1; + dataQueue.Pop(); + } +} + +struct DatagramHeaderFormat +{ + // Time must be first, see SendToThread.cpp + CCTimeType sourceSystemTime; + DatagramSequenceNumberType datagramNumber; + // Use floats to save bandwidth + // float B; // Link capacity + float AS; // Data arrival rate + bool isACK; + bool isNAK; + bool isPacketPair; + bool hasBAndAS; + bool isContinuousSend; + bool needsBAndAs; + bool isValid; // To differentiate between what I serialized, and offline data + + static BitSize_t GetDataHeaderBitLength() + { + return BYTES_TO_BITS(GetDataHeaderByteLength()); + } + + static unsigned int GetDataHeaderByteLength() + { + //return 2 + 3 + sizeof(RakNetTimeMS) + sizeof(float)*2; + return 2 + 3 + sizeof(RakNetTimeMS) + sizeof(float)*1; + } + + void Serialize(RakNet::BitStream *b) + { + // Not endian safe + // RakAssert(GetDataHeaderByteLength()==sizeof(DatagramHeaderFormat)); + // b->WriteAlignedBytes((const unsigned char*) this, sizeof(DatagramHeaderFormat)); + // return; + + b->Write(true); // IsValid + if (isACK) + { + b->Write(true); + b->Write(hasBAndAS); + b->AlignWriteToByteBoundary(); + RakNetTimeMS timeMSLow=(RakNetTimeMS) sourceSystemTime&0xFFFFFFFF; b->Write(timeMSLow); + if (hasBAndAS) + { + // b->Write(B); + b->Write(AS); + } + } + else if (isNAK) + { + b->Write(false); + b->Write(true); + } + else + { + b->Write(false); + b->Write(false); + b->Write(isPacketPair); + b->Write(isContinuousSend); + b->Write(needsBAndAs); + b->AlignWriteToByteBoundary(); + RakNetTimeMS timeMSLow=(RakNetTimeMS) sourceSystemTime&0xFFFFFFFF; b->Write(timeMSLow); + b->Write(datagramNumber); + } + } + void Deserialize(RakNet::BitStream *b) + { + // Not endian safe + // b->ReadAlignedBytes((unsigned char*) this, sizeof(DatagramHeaderFormat)); + // return; + + b->Read(isValid); + b->Read(isACK); + if (isACK) + { + isNAK=false; + isPacketPair=false; + b->Read(hasBAndAS); + b->AlignReadToByteBoundary(); + RakNetTimeMS timeMS; b->Read(timeMS); sourceSystemTime=(CCTimeType) timeMS; + if (hasBAndAS) + { + // b->Read(B); + b->Read(AS); + } + } + else + { + b->Read(isNAK); + if (isNAK) + { + isPacketPair=false; + } + else + { + b->Read(isPacketPair); + b->Read(isContinuousSend); + b->Read(needsBAndAs); + b->AlignReadToByteBoundary(); + RakNetTimeMS timeMS; b->Read(timeMS); sourceSystemTime=(CCTimeType) timeMS; + b->Read(datagramNumber); + } + } + } +}; + +#pragma warning(disable:4702) // unreachable code + +#ifdef _WIN32 +//#define _DEBUG_LOGGER +#ifdef _DEBUG_LOGGER +#include "WindowsIncludes.h" +#endif +#endif + +//#define DEBUG_SPLIT_PACKET_PROBLEMS +#if defined (DEBUG_SPLIT_PACKET_PROBLEMS) +static int waitFlag=-1; +#endif + +using namespace RakNet; + +int SplitPacketChannelComp( SplitPacketIdType const &key, SplitPacketChannel* const &data ) +{ + if (key < data->splitPacketList[0]->splitPacketId) + return -1; + if (key == data->splitPacketList[0]->splitPacketId) + return 0; + return 1; +} + + +// DEFINE_MULTILIST_PTR_TO_MEMBER_COMPARISONS( InternalPacket, SplitPacketIndexType, splitPacketIndex ) + +bool operator<( const DataStructures::MLKeyRef &inputKey, const InternalPacket *cls ) +{ + return inputKey.Get() < cls->splitPacketIndex; +} +bool operator>( const DataStructures::MLKeyRef &inputKey, const InternalPacket *cls ) +{ + return inputKey.Get() > cls->splitPacketIndex; +} +bool operator==( const DataStructures::MLKeyRef &inputKey, const InternalPacket *cls ) +{ + return inputKey.Get() == cls->splitPacketIndex; +} +/// Semi-hack: This is necessary to call Sort() +bool operator<( const DataStructures::MLKeyRef &inputKey, const InternalPacket *cls ) +{ + return inputKey.Get()->splitPacketIndex < cls->splitPacketIndex; +} +bool operator>( const DataStructures::MLKeyRef &inputKey, const InternalPacket *cls ) +{ + return inputKey.Get()->splitPacketIndex > cls->splitPacketIndex; +} +bool operator==( const DataStructures::MLKeyRef &inputKey, const InternalPacket *cls ) +{ + return inputKey.Get()->splitPacketIndex == cls->splitPacketIndex; +} +/* +int SplitPacketIndexComp( SplitPacketIndexType const &key, InternalPacket* const &data ) +{ +if (key < data->splitPacketIndex) +return -1; +if (key == data->splitPacketIndex) +return 0; +return 1; +} +*/ + +//------------------------------------------------------------------------------------------------------- +// Constructor +//------------------------------------------------------------------------------------------------------- +// Add 21 to the default MTU so if we encrypt it can hold potentially 21 more bytes of extra data + padding. +ReliabilityLayer::ReliabilityLayer() : +updateBitStream( MAXIMUM_MTU_SIZE + 21 ) // preallocate the update bitstream so we can avoid a lot of reallocs at runtime +{ + freeThreadedMemoryOnNextUpdate = false; + +#if CC_TIME_TYPE_BYTES==4 +#else +#ifdef _DEBUG + // Wait longer to disconnect in debug so I don't get disconnected while tracing + timeoutTime=30000; +#else + timeoutTime=10000; +#endif +#endif + +#ifdef _DEBUG + minExtraPing=extraPingVariance=0; + packetloss=(double) minExtraPing; +#endif + + InitializeVariables(MAXIMUM_MTU_SIZE); + + datagramHistoryMessagePool.SetPageSize(sizeof(MessageNumberNode)*128); + internalPacketPool.SetPageSize(sizeof(InternalPacket)*128); + refCountedDataPool.SetPageSize(sizeof(InternalPacket)*32); +} + +//------------------------------------------------------------------------------------------------------- +// Destructor +//------------------------------------------------------------------------------------------------------- +ReliabilityLayer::~ReliabilityLayer() +{ + FreeMemory( true ); // Free all memory immediately +} +//------------------------------------------------------------------------------------------------------- +// Resets the layer for reuse +//------------------------------------------------------------------------------------------------------- +void ReliabilityLayer::Reset( bool resetVariables, int MTUSize ) +{ + FreeMemory( true ); // true because making a memory reset pending in the update cycle causes resets after reconnects. Instead, just call Reset from a single thread + if (resetVariables) + { + InitializeVariables(MTUSize); + + if ( encryptor.IsKeySet() ) + congestionManager.Init(RakNet::GetTimeUS(), MTUSize - UDP_HEADER_SIZE); + else + congestionManager.Init(RakNet::GetTimeUS(), MTUSize - UDP_HEADER_SIZE); + } +} + +//------------------------------------------------------------------------------------------------------- +// Sets up encryption +//------------------------------------------------------------------------------------------------------- +void ReliabilityLayer::SetEncryptionKey( const unsigned char* key ) +{ + if ( key ) + encryptor.SetKey( key ); + else + encryptor.UnsetKey(); +} + +//------------------------------------------------------------------------------------------------------- +// Set the time, in MS, to use before considering ourselves disconnected after not being able to deliver a reliable packet +//------------------------------------------------------------------------------------------------------- +void ReliabilityLayer::SetTimeoutTime( RakNetTimeMS time ) +{ + timeoutTime=time; +} + +//------------------------------------------------------------------------------------------------------- +// Returns the value passed to SetTimeoutTime. or the default if it was never called +//------------------------------------------------------------------------------------------------------- +RakNetTimeMS ReliabilityLayer::GetTimeoutTime(void) +{ + return timeoutTime; +} + +//------------------------------------------------------------------------------------------------------- +// Initialize the variables +//------------------------------------------------------------------------------------------------------- +void ReliabilityLayer::InitializeVariables( int MTUSize ) +{ + (void) MTUSize; + + memset( waitingForOrderedPacketReadIndex, 0, NUMBER_OF_ORDERED_STREAMS * sizeof(OrderingIndexType)); + memset( waitingForSequencedPacketReadIndex, 0, NUMBER_OF_ORDERED_STREAMS * sizeof(OrderingIndexType) ); + memset( waitingForOrderedPacketWriteIndex, 0, NUMBER_OF_ORDERED_STREAMS * sizeof(OrderingIndexType) ); + memset( waitingForSequencedPacketWriteIndex, 0, NUMBER_OF_ORDERED_STREAMS * sizeof(OrderingIndexType) ); + memset( &statistics, 0, sizeof( statistics ) ); + statistics.connectionStartTime = RakNet::GetTimeUS(); + splitPacketId = 0; + elapsedTimeSinceLastUpdate=0; + throughputCapCountdown=0; + sendReliableMessageNumberIndex = 0; + internalOrderIndex=0; + timeToNextUnreliableCull=0; + unreliableLinkedListHead=0; + lastUpdateTime= RakNet::GetTimeNS(); + bandwidthExceededStatistic=false; + remoteSystemTime=0; + unreliableTimeout=0; + + // Disable packet pairs + countdownToNextPacketPair=15; + + nextAllowedThroughputSample=0; + deadConnection = cheater = false; + timeOfLastContinualSend=0; + + // timeResendQueueNonEmpty = 0; + timeLastDatagramArrived=RakNet::GetTimeMS(); + // packetlossThisSample=false; + // backoffThisSample=0; + // packetlossThisSampleResendCount=0; + // lastPacketlossTime=0; + statistics.messagesInResendBuffer=0; + statistics.bytesInResendBuffer=0; + + receivedPacketsBaseIndex=0; + resetReceivedPackets=true; + receivePacketCount=0; + + // SetPing( 1000 ); + + timeBetweenPackets=STARTING_TIME_BETWEEN_PACKETS; + + ackPingIndex=0; + ackPingSum=(CCTimeType)0; + + nextSendTime=lastUpdateTime; + //nextLowestPingReset=(CCTimeType)0; + // continuousSend=false; + + // histogramStart=(CCTimeType)0; + // histogramBitsSent=0; + unacknowledgedBytes=0; + resendLinkedListHead=0; + totalUserDataBytesAcked=0; + + datagramHistoryPopCount=0; + + InitHeapWeights(); + for (int i=0; i < NUMBER_OF_PRIORITIES; i++) + { + statistics.messageInSendBuffer[i]=0; + statistics.bytesInSendBuffer[i]=0.0; + } + + for (int i=0; i < RNS_PER_SECOND_METRICS_COUNT; i++) + { + bpsMetrics[i].Reset(__FILE__,__LINE__); + } +} + +//------------------------------------------------------------------------------------------------------- +// Frees all allocated memory +//------------------------------------------------------------------------------------------------------- +void ReliabilityLayer::FreeMemory( bool freeAllImmediately ) +{ + if ( freeAllImmediately ) + { + FreeThreadedMemory(); + FreeThreadSafeMemory(); + } + else + { + FreeThreadSafeMemory(); + freeThreadedMemoryOnNextUpdate = true; + } +} + +void ReliabilityLayer::FreeThreadedMemory( void ) +{ +} + +void ReliabilityLayer::FreeThreadSafeMemory( void ) +{ + unsigned i,j; + InternalPacket *internalPacket; + + ClearPacketsAndDatagrams(false); + + for (i=0; i < splitPacketChannelList.Size(); i++) + { + for (j=0; j < splitPacketChannelList[i]->splitPacketList.Size(); j++) + { + FreeInternalPacketData(splitPacketChannelList[i]->splitPacketList[j], __FILE__, __LINE__ ); + ReleaseToInternalPacketPool( splitPacketChannelList[i]->splitPacketList[j] ); + } + RakNet::OP_DELETE(splitPacketChannelList[i], __FILE__, __LINE__); + } + splitPacketChannelList.Clear(false, __FILE__, __LINE__); + + while ( outputQueue.Size() > 0 ) + { + internalPacket = outputQueue.Pop(); + FreeInternalPacketData(internalPacket, __FILE__, __LINE__ ); + ReleaseToInternalPacketPool( internalPacket ); + } + + outputQueue.ClearAndForceAllocation( 32, __FILE__,__LINE__ ); + + for ( i = 0; i < orderingList.Size(); i++ ) + { + if ( orderingList[ i ] ) + { + DataStructures::LinkedList* theList = orderingList[ i ]; + + if ( theList ) + { + while ( theList->Size() ) + { + internalPacket = orderingList[ i ]->Pop(); + FreeInternalPacketData(internalPacket, __FILE__, __LINE__ ); + ReleaseToInternalPacketPool( internalPacket ); + } + + RakNet::OP_DELETE(theList, __FILE__, __LINE__); + } + } + } + + orderingList.Clear(false, __FILE__, __LINE__); + + //resendList.ForEachData(DeleteInternalPacket); + // resendTree.Clear(__FILE__, __LINE__); + memset(resendBuffer, 0, sizeof(resendBuffer)); + statistics.messagesInResendBuffer=0; + statistics.bytesInResendBuffer=0; + + if (resendLinkedListHead) + { + InternalPacket *prev; + InternalPacket *iter = resendLinkedListHead; + while (1) + { + if (iter->data) + FreeInternalPacketData(iter, __FILE__, __LINE__ ); + prev=iter; + iter=iter->resendNext; + if (iter==resendLinkedListHead) + { + ReleaseToInternalPacketPool(prev); + break; + } + ReleaseToInternalPacketPool(prev); + } + resendLinkedListHead=0; + } + unacknowledgedBytes=0; + + // acknowlegements.Clear(__FILE__, __LINE__); + + for ( j=0 ; j < outgoingPacketBuffer.Size(); j++ ) + { + if ( outgoingPacketBuffer[ j ]->data) + FreeInternalPacketData( outgoingPacketBuffer[ j ], __FILE__, __LINE__ ); + ReleaseToInternalPacketPool( outgoingPacketBuffer[ j ] ); + } + + outgoingPacketBuffer.Clear(true, __FILE__,__LINE__); + + +#ifdef _DEBUG + for (unsigned i = 0; i < delayList.Size(); i++ ) + RakNet::OP_DELETE(delayList[ i ], __FILE__, __LINE__); + delayList.Clear(__FILE__, __LINE__); +#endif + + packetsToSendThisUpdate.Clear(false, __FILE__, __LINE__); + packetsToSendThisUpdate.Preallocate(512, __FILE__, __LINE__); + packetsToDeallocThisUpdate.Clear(false, __FILE__, __LINE__); + packetsToDeallocThisUpdate.Preallocate(512, __FILE__, __LINE__); + packetsToSendThisUpdateDatagramBoundaries.Clear(false, __FILE__, __LINE__); + packetsToSendThisUpdateDatagramBoundaries.Preallocate(128, __FILE__, __LINE__); + datagramSizesInBytes.Clear(false, __FILE__, __LINE__); + datagramSizesInBytes.Preallocate(128, __FILE__, __LINE__); + + internalPacketPool.Clear(__FILE__, __LINE__); + + refCountedDataPool.Clear(__FILE__,__LINE__); + + /* + DataStructures::Page *cur = datagramMessageIDTree.GetListHead(); + while (cur) + { + int treeIndex; + for (treeIndex=0; treeIndex < cur->size; treeIndex++) + ReleaseToDatagramMessageIDPool(cur->data[treeIndex]); + cur=cur->resendNext; + } + datagramMessageIDTree.Clear(__FILE__, __LINE__); + datagramMessageIDPool.Clear(__FILE__, __LINE__); + */ + + while (datagramHistory.Size()) + { + RemoveFromDatagramHistory(datagramHistoryPopCount); + datagramHistory.Pop(); + datagramHistoryPopCount++; + } + datagramHistoryMessagePool.Clear(__FILE__,__LINE__); + datagramHistoryPopCount=0; + + acknowlegements.Clear(); + NAKs.Clear(); + + unreliableLinkedListHead=0; +} + +//------------------------------------------------------------------------------------------------------- +// Packets are read directly from the socket layer and skip the reliability +//layer because unconnected players do not use the reliability layer +// This function takes packet data after a player has been confirmed as +//connected. The game should not use that data directly +// because some data is used internally, such as packet acknowledgment and +//split packets +//------------------------------------------------------------------------------------------------------- +bool ReliabilityLayer::HandleSocketReceiveFromConnectedPlayer( + const char *buffer, unsigned int length, SystemAddress systemAddress, DataStructures::List &messageHandlerList, int MTUSize, + SOCKET s, RakNetRandom *rnr, unsigned short remotePortRakNetWasStartedOn_PS3, CCTimeType timeRead) +{ +#ifdef _DEBUG + RakAssert( !( buffer == 0 ) ); +#endif + +#if CC_TIME_TYPE_BYTES==4 + timeRead/=1000; +#endif + + bpsMetrics[(int) ACTUAL_BYTES_RECEIVED].Push1(timeRead,length); + + (void) MTUSize; + + if ( length <= 2 || buffer == 0 ) // Length of 1 is a connection request resend that we just ignore + { + for (unsigned int messageHandlerIndex=0; messageHandlerIndex < messageHandlerList.Size(); messageHandlerIndex++) + messageHandlerList[messageHandlerIndex]->OnReliabilityLayerPacketError("length <= 2 || buffer == 0", BYTES_TO_BITS(length), systemAddress); + return true; + } + + timeLastDatagramArrived=RakNet::GetTimeMS(); + + // CCTimeType time; + bool indexFound; + int count, size; + DatagramSequenceNumberType holeCount; + unsigned i; + // bool hasAcks=false; + + UpdateThreadedMemory(); + + // decode this whole chunk if the decoder is defined. + if ( encryptor.IsKeySet() ) + { + if ( encryptor.Decrypt( ( unsigned char* ) buffer, length, ( unsigned char* ) buffer, &length ) == false ) + { + for (unsigned int messageHandlerIndex=0; messageHandlerIndex < messageHandlerList.Size(); messageHandlerIndex++) + messageHandlerList[messageHandlerIndex]->OnReliabilityLayerPacketError("Decryption failed", BYTES_TO_BITS(length), systemAddress); + + return false; + } + } + RakNet::BitStream socketData( (unsigned char*) buffer, length, false ); // Convert the incoming data to a bitstream for easy parsing + // time = RakNet::GetTimeNS(); + + // Set to the current time if it is not zero, and we get incoming data + // if (timeResendQueueNonEmpty!=0) + // timeResendQueueNonEmpty=timeRead; + + DatagramHeaderFormat dhf; + dhf.Deserialize(&socketData); + if (dhf.isValid==false) + { + for (unsigned int messageHandlerIndex=0; messageHandlerIndex < messageHandlerList.Size(); messageHandlerIndex++) + messageHandlerList[messageHandlerIndex]->OnReliabilityLayerPacketError("dhf.isValid==false", BYTES_TO_BITS(length), systemAddress); + + return true; + } + if (dhf.isACK) + { + DatagramSequenceNumberType datagramNumber; + // datagramNumber=dhf.datagramNumber; + RakNetTimeMS timeMSLow=(RakNetTimeMS) timeRead&0xFFFFFFFF; + CCTimeType rtt = timeMSLow-dhf.sourceSystemTime; +#if CC_TIME_TYPE_BYTES==4 + if (rtt > 10000) +#else + if (rtt > 10000000) +#endif + { + // Sanity check. This could happen due to type overflow, especially since I only send the low 4 bytes to reduce bandwidth + rtt=(CCTimeType) congestionManager.GetRTT(); + } + // RakAssert(rtt < 500000); + // printf("%i ", (RakNetTimeMS)(rtt/1000)); + ackPing=rtt; +#ifdef _DEBUG + if (dhf.hasBAndAS==false) + { + // dhf.B=0; + dhf.AS=0; + } +#endif + // congestionManager.OnAck(timeRead, rtt, dhf.hasBAndAS, dhf.B, dhf.AS, totalUserDataBytesAcked ); + + + incomingAcks.Clear(); + if (incomingAcks.Deserialize(&socketData)==false) + { + for (unsigned int messageHandlerIndex=0; messageHandlerIndex < messageHandlerList.Size(); messageHandlerIndex++) + messageHandlerList[messageHandlerIndex]->OnReliabilityLayerPacketError("incomingAcks.Deserialize failed", BYTES_TO_BITS(length), systemAddress); + + return false; + } + for (i=0; iincomingAcks.ranges[i].maxIndex) + { + RakAssert(incomingAcks.ranges[i].minIndex<=incomingAcks.ranges[i].maxIndex); + + for (unsigned int messageHandlerIndex=0; messageHandlerIndex < messageHandlerList.Size(); messageHandlerIndex++) + messageHandlerList[messageHandlerIndex]->OnReliabilityLayerPacketError("incomingAcks minIndex > maxIndex", BYTES_TO_BITS(length), systemAddress); + return false; + } + for (datagramNumber=incomingAcks.ranges[i].minIndex; datagramNumber >= incomingAcks.ranges[i].minIndex && datagramNumber <= incomingAcks.ranges[i].maxIndex; datagramNumber++) + { + congestionManager.OnAck(timeRead, rtt, dhf.hasBAndAS, 0, dhf.AS, totalUserDataBytesAcked, bandwidthExceededStatistic, datagramNumber ); + + MessageNumberNode *messageNumberNode = GetMessageNumberNodeByDatagramIndex(datagramNumber); + while (messageNumberNode) + { + RemovePacketFromResendListAndDeleteOlderReliableSequenced( messageNumberNode->messageNumber, timeRead, messageHandlerList, systemAddress ); + messageNumberNode=messageNumberNode->next; + } + if (messageNumberNode) + RemoveFromDatagramHistory(datagramNumber); + } + } + } + else if (dhf.isNAK) + { + DatagramSequenceNumberType messageNumber; + DataStructures::RangeList incomingNAKs; + if (incomingNAKs.Deserialize(&socketData)==false) + { + for (unsigned int messageHandlerIndex=0; messageHandlerIndex < messageHandlerList.Size(); messageHandlerIndex++) + messageHandlerList[messageHandlerIndex]->OnReliabilityLayerPacketError("incomingNAKs.Deserialize failed", BYTES_TO_BITS(length), systemAddress); + + return false; + } + for (i=0; iincomingNAKs.ranges[i].maxIndex) + { + RakAssert(incomingNAKs.ranges[i].minIndex<=incomingNAKs.ranges[i].maxIndex); + + for (unsigned int messageHandlerIndex=0; messageHandlerIndex < messageHandlerList.Size(); messageHandlerIndex++) + messageHandlerList[messageHandlerIndex]->OnReliabilityLayerPacketError("incomingNAKs minIndex>maxIndex", BYTES_TO_BITS(length), systemAddress); + + return false; + } + // Sanity check + RakAssert(incomingNAKs.ranges[i].maxIndex.val-incomingNAKs.ranges[i].minIndex.val<1000); + for (messageNumber=incomingNAKs.ranges[i].minIndex; messageNumber >= incomingNAKs.ranges[i].minIndex && messageNumber <= incomingNAKs.ranges[i].maxIndex; messageNumber++) + { + congestionManager.OnNAK(timeRead, messageNumber); + + // REMOVEME + // printf("%p NAK %i\n", this, dhf.datagramNumber.val); + + MessageNumberNode *messageNumberNode = GetMessageNumberNodeByDatagramIndex(messageNumber); + while (messageNumberNode) + { + // Update timers so resends occur immediately + InternalPacket *internalPacket = resendBuffer[messageNumberNode->messageNumber & (uint32_t) RESEND_BUFFER_ARRAY_MASK]; + if (internalPacket) + { + if (internalPacket->nextActionTime!=0) + { + internalPacket->nextActionTime=timeRead; + } + } + + messageNumberNode=messageNumberNode->next; + } + } + } + } + else + { + uint32_t skippedMessageCount; + if (!congestionManager.OnGotPacket(dhf.datagramNumber, dhf.isContinuousSend, timeRead, length, &skippedMessageCount)) + { + for (unsigned int messageHandlerIndex=0; messageHandlerIndex < messageHandlerList.Size(); messageHandlerIndex++) + messageHandlerList[messageHandlerIndex]->OnReliabilityLayerPacketError("congestionManager.OnGotPacket failed", BYTES_TO_BITS(length), systemAddress); + + return true; + } + if (dhf.isPacketPair) + congestionManager.OnGotPacketPair(dhf.datagramNumber, length, timeRead); + + DatagramHeaderFormat dhfNAK; + dhfNAK.isNAK=true; + uint32_t skippedMessageOffset; + for (skippedMessageOffset=skippedMessageCount; skippedMessageOffset > 0; skippedMessageOffset--) + { + NAKs.Insert(dhf.datagramNumber-skippedMessageOffset); + } + remoteSystemNeedsBAndAS=dhf.needsBAndAs; + + // REMOVEME + // printf("%p Send ack %i\n", this, dhf.datagramNumber.val); + + // Ack dhf.datagramNumber + // Ack even unreliable messages for congestion control, just don't resend them on no ack + SendAcknowledgementPacket( dhf.datagramNumber, dhf.sourceSystemTime); + + InternalPacket* internalPacket = CreateInternalPacketFromBitStream( &socketData, timeRead ); + if (internalPacket==0) + { + for (unsigned int messageHandlerIndex=0; messageHandlerIndex < messageHandlerList.Size(); messageHandlerIndex++) + messageHandlerList[messageHandlerIndex]->OnReliabilityLayerPacketError("CreateInternalPacketFromBitStream failed", BYTES_TO_BITS(length), systemAddress); + + return true; + } + + while ( internalPacket ) + { + for (unsigned int messageHandlerIndex=0; messageHandlerIndex < messageHandlerList.Size(); messageHandlerIndex++) + { +#if CC_TIME_TYPE_BYTES==4 + messageHandlerList[messageHandlerIndex]->OnInternalPacket(internalPacket, receivePacketCount, systemAddress, timeRead, false); +#else + messageHandlerList[messageHandlerIndex]->OnInternalPacket(internalPacket, receivePacketCount, systemAddress, (RakNetTime)(timeRead/(CCTimeType)1000), false); +#endif + } + + { + + // resetReceivedPackets is set from a non-threadsafe function. + // We do the actual reset in this function so the data is not modified by multiple threads + if (resetReceivedPackets) + { + hasReceivedPacketQueue.ClearAndForceAllocation(DEFAULT_HAS_RECEIVED_PACKET_QUEUE_SIZE, __FILE__,__LINE__); + receivedPacketsBaseIndex=0; + resetReceivedPackets=false; + } + + // 8/12/09 was previously not checking if the message was reliable. However, on packetloss this would mean you'd eventually exceed the + // hole count because unreliable messages were never resent, and you'd stop getting messages + if (internalPacket->reliability == RELIABLE || internalPacket->reliability == RELIABLE_SEQUENCED || internalPacket->reliability == RELIABLE_ORDERED ) + { + // If the following conditional is true then this either a duplicate packet + // or an older out of order packet + // The subtraction unsigned overflow is intentional + holeCount = (DatagramSequenceNumberType)(internalPacket->reliableMessageNumber-receivedPacketsBaseIndex); + const DatagramSequenceNumberType typeRange = (DatagramSequenceNumberType)(const uint32_t)-1; + + if (holeCount==(DatagramSequenceNumberType) 0) + { + // Got what we were expecting + if (hasReceivedPacketQueue.Size()) + hasReceivedPacketQueue.Pop(); + ++receivedPacketsBaseIndex; + } + else if (holeCount > typeRange/(DatagramSequenceNumberType) 2) + { + // Duplicate packet + FreeInternalPacketData(internalPacket, __FILE__, __LINE__ ); + ReleaseToInternalPacketPool( internalPacket ); + + bpsMetrics[(int) USER_MESSAGE_BYTES_RECEIVED_IGNORED].Push1(timeRead,BITS_TO_BYTES(internalPacket->dataBitLength)); + + goto CONTINUE_SOCKET_DATA_PARSE_LOOP; + } + else if ((unsigned int) holeCountdataBitLength)); + + goto CONTINUE_SOCKET_DATA_PARSE_LOOP; + } + } + else // holeCount>=receivedPackets.Size() + { + if (holeCount > (DatagramSequenceNumberType) 1000000) + { + RakAssert("Hole count too high. See ReliabilityLayer.h" && 0); + + for (unsigned int messageHandlerIndex=0; messageHandlerIndex < messageHandlerList.Size(); messageHandlerIndex++) + messageHandlerList[messageHandlerIndex]->OnReliabilityLayerPacketError("holeCount > 1000000", BYTES_TO_BITS(length), systemAddress); + + // Would crash due to out of memory! + FreeInternalPacketData(internalPacket, __FILE__, __LINE__ ); + ReleaseToInternalPacketPool( internalPacket ); + + bpsMetrics[(int) USER_MESSAGE_BYTES_RECEIVED_IGNORED].Push1(timeRead,BITS_TO_BYTES(internalPacket->dataBitLength)); + + goto CONTINUE_SOCKET_DATA_PARSE_LOOP; + } + + + // Fix - sending on a higher priority gives us a very very high received packets base index if we formerly had pre-split a lot of messages and + // used that as the message number. Because of this, a lot of time is spent in this linear loop and the timeout time expires because not + // all of the message is sent in time. + // Fixed by late assigning message IDs on the sender + + // Add 0 times to the queue until (reliableMessageNumber - baseIndex) < queue size. + while ((unsigned int)(holeCount) > hasReceivedPacketQueue.Size()) + hasReceivedPacketQueue.Push(true, __FILE__, __LINE__ ); // time+(CCTimeType)60 * (CCTimeType)1000 * (CCTimeType)1000); // Didn't get this packet - set the time to give up waiting + hasReceivedPacketQueue.Push(false, __FILE__, __LINE__ ); // Got the packet +#ifdef _DEBUG + // If this assert hits then DatagramSequenceNumberType has overflowed + RakAssert(hasReceivedPacketQueue.Size() < (unsigned int)((DatagramSequenceNumberType)(const uint32_t)(-1))); +#endif + } + + while ( hasReceivedPacketQueue.Size()>0 && hasReceivedPacketQueue.Peek()==false ) + { + hasReceivedPacketQueue.Pop(); + ++receivedPacketsBaseIndex; + } + } + + // If the allocated buffer is > DEFAULT_HAS_RECEIVED_PACKET_QUEUE_SIZE and it is 3x greater than the number of elements actually being used + if (hasReceivedPacketQueue.AllocationSize() > (unsigned int) DEFAULT_HAS_RECEIVED_PACKET_QUEUE_SIZE && hasReceivedPacketQueue.AllocationSize() > hasReceivedPacketQueue.Size() * 3) + hasReceivedPacketQueue.Compress(__FILE__,__LINE__); + + if ( internalPacket->reliability == RELIABLE_SEQUENCED || internalPacket->reliability == UNRELIABLE_SEQUENCED ) + { +#ifdef _DEBUG + RakAssert( internalPacket->orderingChannel < NUMBER_OF_ORDERED_STREAMS ); +#endif + + if ( internalPacket->orderingChannel >= NUMBER_OF_ORDERED_STREAMS ) + { + + FreeInternalPacketData(internalPacket, __FILE__, __LINE__ ); + ReleaseToInternalPacketPool( internalPacket ); + + for (unsigned int messageHandlerIndex=0; messageHandlerIndex < messageHandlerList.Size(); messageHandlerIndex++) + messageHandlerList[messageHandlerIndex]->OnReliabilityLayerPacketError("internalPacket->orderingChannel >= NUMBER_OF_ORDERED_STREAMS", BYTES_TO_BITS(length), systemAddress); + + bpsMetrics[(int) USER_MESSAGE_BYTES_RECEIVED_IGNORED].Push1(timeRead,BITS_TO_BYTES(internalPacket->dataBitLength)); + + goto CONTINUE_SOCKET_DATA_PARSE_LOOP; + } + + if ( IsOlderOrderedPacket( internalPacket->orderingIndex, waitingForSequencedPacketReadIndex[ internalPacket->orderingChannel ] ) == false ) + { + // Is this a split packet? + if ( internalPacket->splitPacketCount > 0 ) + { + // Generate the split + // Verify some parameters to make sure we don't get junk data + + + // Check for a rebuilt packet + InsertIntoSplitPacketList( internalPacket, timeRead ); + bpsMetrics[(int) USER_MESSAGE_BYTES_RECEIVED_PROCESSED].Push1(timeRead,BITS_TO_BYTES(internalPacket->dataBitLength)); + + // Sequenced + internalPacket = BuildPacketFromSplitPacketList( internalPacket->splitPacketId, timeRead, + s, systemAddress, rnr, remotePortRakNetWasStartedOn_PS3); + + if ( internalPacket ) + { + // Update our index to the newest packet + waitingForSequencedPacketReadIndex[ internalPacket->orderingChannel ] = internalPacket->orderingIndex + (OrderingIndexType)1; + + // If there is a rebuilt packet, add it to the output queue + outputQueue.Push( internalPacket, __FILE__, __LINE__ ); + internalPacket = 0; + } + + // else don't have all the parts yet + } + else + { + // Update our index to the newest packet + waitingForSequencedPacketReadIndex[ internalPacket->orderingChannel ] = internalPacket->orderingIndex + (OrderingIndexType)1; + + // Not a split packet. Add the packet to the output queue + bpsMetrics[(int) USER_MESSAGE_BYTES_RECEIVED_PROCESSED].Push1(timeRead,BITS_TO_BYTES(internalPacket->dataBitLength)); + outputQueue.Push( internalPacket, __FILE__, __LINE__ ); + internalPacket = 0; + } + } + else + { + // Older sequenced packet. Discard it + FreeInternalPacketData(internalPacket, __FILE__, __LINE__ ); + ReleaseToInternalPacketPool( internalPacket ); + + bpsMetrics[(int) USER_MESSAGE_BYTES_RECEIVED_IGNORED].Push1(timeRead,BITS_TO_BYTES(internalPacket->dataBitLength)); + + } + + goto CONTINUE_SOCKET_DATA_PARSE_LOOP; + } + + // Is this an unsequenced split packet? + if ( internalPacket->splitPacketCount > 0 ) + { + // An unsequenced split packet. May be ordered though. + + // Check for a rebuilt packet + if ( internalPacket->reliability != RELIABLE_ORDERED ) + internalPacket->orderingChannel = 255; // Use 255 to designate not sequenced and not ordered + + InsertIntoSplitPacketList( internalPacket, timeRead ); + + internalPacket = BuildPacketFromSplitPacketList( internalPacket->splitPacketId, timeRead, + s, systemAddress, rnr, remotePortRakNetWasStartedOn_PS3); + + if ( internalPacket == 0 ) + { + + // Don't have all the parts yet + goto CONTINUE_SOCKET_DATA_PARSE_LOOP; + } + + // else continue down to handle RELIABLE_ORDERED + + } + + if ( internalPacket->reliability == RELIABLE_ORDERED ) + { +#ifdef _DEBUG + RakAssert( internalPacket->orderingChannel < NUMBER_OF_ORDERED_STREAMS ); +#endif + + if ( internalPacket->orderingChannel >= NUMBER_OF_ORDERED_STREAMS ) + { + // Invalid packet + FreeInternalPacketData(internalPacket, __FILE__, __LINE__ ); + ReleaseToInternalPacketPool( internalPacket ); + + bpsMetrics[(int) USER_MESSAGE_BYTES_RECEIVED_IGNORED].Push1(timeRead,BITS_TO_BYTES(internalPacket->dataBitLength)); + + goto CONTINUE_SOCKET_DATA_PARSE_LOOP; + } + + bpsMetrics[(int) USER_MESSAGE_BYTES_RECEIVED_PROCESSED].Push1(timeRead,BITS_TO_BYTES(internalPacket->dataBitLength)); + + if ( waitingForOrderedPacketReadIndex[ internalPacket->orderingChannel ] == internalPacket->orderingIndex ) + { + // Get the list to hold ordered packets for this stream + DataStructures::LinkedList *orderingListAtOrderingStream; + unsigned char orderingChannelCopy = internalPacket->orderingChannel; + + // Push the packet for the user to read + outputQueue.Push( internalPacket, __FILE__, __LINE__ ); + internalPacket = 0; // Don't reference this any longer since other threads access it + + // Wait for the resendNext ordered packet in sequence + waitingForOrderedPacketReadIndex[ orderingChannelCopy ] ++; // This wraps + + orderingListAtOrderingStream = GetOrderingListAtOrderingStream( orderingChannelCopy ); + + if ( orderingListAtOrderingStream != 0) + { + while ( orderingListAtOrderingStream->Size() > 0 ) + { + // Cycle through the list until nothing is found + orderingListAtOrderingStream->Beginning(); + indexFound=false; + size=orderingListAtOrderingStream->Size(); + count=0; + + while (count++ < size) + { + if ( orderingListAtOrderingStream->Peek()->orderingIndex == waitingForOrderedPacketReadIndex[ orderingChannelCopy ] ) + { + outputQueue.Push( orderingListAtOrderingStream->Pop(), __FILE__, __LINE__ ); + waitingForOrderedPacketReadIndex[ orderingChannelCopy ]++; + indexFound=true; + } + else + (*orderingListAtOrderingStream)++; + } + + if (indexFound==false) + break; + } + } + internalPacket = 0; + } + else + { + // This is a newer ordered packet than we are waiting for. Store it for future use + AddToOrderingList( internalPacket ); + } + + + goto CONTINUE_SOCKET_DATA_PARSE_LOOP; + } + + bpsMetrics[(int) USER_MESSAGE_BYTES_RECEIVED_PROCESSED].Push1(timeRead,BITS_TO_BYTES(internalPacket->dataBitLength)); + + // Nothing special about this packet. Add it to the output queue + outputQueue.Push( internalPacket, __FILE__, __LINE__ ); + + internalPacket = 0; + } + + // Used for a goto to jump to the resendNext packet immediately + +CONTINUE_SOCKET_DATA_PARSE_LOOP: + // Parse the bitstream to create an internal packet + internalPacket = CreateInternalPacketFromBitStream( &socketData, timeRead ); + } + + } + + + receivePacketCount++; + + return true; +} + +//------------------------------------------------------------------------------------------------------- +// This gets an end-user packet already parsed out. Returns number of BITS put into the buffer +//------------------------------------------------------------------------------------------------------- +BitSize_t ReliabilityLayer::Receive( unsigned char **data ) +{ + // Wait until the clear occurs + if (freeThreadedMemoryOnNextUpdate) + return 0; + + InternalPacket * internalPacket; + + if ( outputQueue.Size() > 0 ) + { + // #ifdef _DEBUG + // RakAssert(bitStream->GetNumberOfBitsUsed()==0); + // #endif + internalPacket = outputQueue.Pop(); + + BitSize_t bitLength; + *data = internalPacket->data; + bitLength = internalPacket->dataBitLength; + ReleaseToInternalPacketPool( internalPacket ); + return bitLength; + } + + else + { + return 0; + } + +} + +//------------------------------------------------------------------------------------------------------- +// Puts data on the send queue +// bitStream contains the data to send +// priority is what priority to send the data at +// reliability is what reliability to use +// ordering channel is from 0 to 255 and specifies what stream to use +//------------------------------------------------------------------------------------------------------- +bool ReliabilityLayer::Send( char *data, BitSize_t numberOfBitsToSend, PacketPriority priority, PacketReliability reliability, unsigned char orderingChannel, bool makeDataCopy, int MTUSize, CCTimeType currentTime, uint32_t receipt ) +{ +#ifdef _DEBUG + RakAssert( !( reliability >= NUMBER_OF_RELIABILITIES || reliability < 0 ) ); + RakAssert( !( priority > NUMBER_OF_PRIORITIES || priority < 0 ) ); + RakAssert( !( orderingChannel >= NUMBER_OF_ORDERED_STREAMS ) ); + RakAssert( numberOfBitsToSend > 0 ); +#endif + +#if CC_TIME_TYPE_BYTES==4 + currentTime/=1000; +#endif + + (void) MTUSize; + + // int a = BITS_TO_BYTES(numberOfBitsToSend); + + // Fix any bad parameters + if ( reliability > RELIABLE_ORDERED_WITH_ACK_RECEIPT || reliability < 0 ) + reliability = RELIABLE; + + if ( priority > NUMBER_OF_PRIORITIES || priority < 0 ) + priority = HIGH_PRIORITY; + + if ( orderingChannel >= NUMBER_OF_ORDERED_STREAMS ) + orderingChannel = 0; + + unsigned int numberOfBytesToSend=(unsigned int) BITS_TO_BYTES(numberOfBitsToSend); + if ( numberOfBitsToSend == 0 ) + { + return false; + } + InternalPacket * internalPacket = AllocateFromInternalPacketPool(); + if (internalPacket==0) + { + notifyOutOfMemory(__FILE__, __LINE__); + return false; // Out of memory + } + + bpsMetrics[(int) USER_MESSAGE_BYTES_PUSHED].Push1(currentTime,numberOfBytesToSend); + + internalPacket->creationTime = currentTime; + + if ( makeDataCopy ) + { + AllocInternalPacketData(internalPacket, numberOfBytesToSend, __FILE__, __LINE__ ); + //internalPacket->data = (unsigned char*) rakMalloc_Ex( numberOfBytesToSend, __FILE__, __LINE__ ); + memcpy( internalPacket->data, data, numberOfBytesToSend ); + } + else + { + // Allocated the data elsewhere, delete it in here + //internalPacket->data = ( unsigned char* ) data; + AllocInternalPacketData(internalPacket, (unsigned char*) data ); + } + + internalPacket->dataBitLength = numberOfBitsToSend; + internalPacket->messageInternalOrder = internalOrderIndex++; + internalPacket->priority = priority; + internalPacket->reliability = reliability; + internalPacket->sendReceiptSerial=receipt; + + // Calculate if I need to split the packet + // int headerLength = BITS_TO_BYTES( GetMessageHeaderLengthBits( internalPacket, true ) ); + + unsigned int maxDataSizeBytes = GetMaxDatagramSizeExcludingMessageHeaderBytes() - BITS_TO_BYTES(GetMaxMessageHeaderLengthBits()); + + bool splitPacket = numberOfBytesToSend > maxDataSizeBytes; + + // If a split packet, we might have to upgrade the reliability + if ( splitPacket ) + { + // Split packets cannot be unreliable, in case that one part doesn't arrive and the whole cannot be reassembled. + // One part could not arrive either due to packetloss or due to unreliable discard + if (internalPacket->reliability==UNRELIABLE) + internalPacket->reliability=RELIABLE; + else if (internalPacket->reliability==UNRELIABLE_WITH_ACK_RECEIPT) + internalPacket->reliability=RELIABLE_WITH_ACK_RECEIPT; + else if (internalPacket->reliability==UNRELIABLE_SEQUENCED) + internalPacket->reliability=RELIABLE_SEQUENCED; +// else if (internalPacket->reliability==UNRELIABLE_SEQUENCED_WITH_ACK_RECEIPT) +// internalPacket->reliability=RELIABLE_SEQUENCED_WITH_ACK_RECEIPT; + } + + // ++sendMessageNumberIndex; + + if ( internalPacket->reliability == RELIABLE_SEQUENCED || + internalPacket->reliability == UNRELIABLE_SEQUENCED +// || +// internalPacket->reliability == RELIABLE_SEQUENCED_WITH_ACK_RECEIPT || +// internalPacket->reliability == UNRELIABLE_SEQUENCED_WITH_ACK_RECEIPT + ) + { + // Assign the sequence stream and index + internalPacket->orderingChannel = orderingChannel; + internalPacket->orderingIndex = waitingForSequencedPacketWriteIndex[ orderingChannel ] ++; + + // This packet supersedes all other sequenced packets on the same ordering channel + // Delete all packets in all send lists that are sequenced and on the same ordering channel + // UPDATE: + // Disabled. We don't have enough info to consistently do this. Sometimes newer data does supercede + // older data such as with constantly declining health, but not in all cases. + // For example, with sequenced unreliable sound packets just because you send a newer one doesn't mean you + // don't need the older ones because the odds are they will still arrive in order + /* + for (int i=0; i < NUMBER_OF_PRIORITIES; i++) + { + DeleteSequencedPacketsInList(orderingChannel, sendQueue[i]); + } + */ + } + else if ( internalPacket->reliability == RELIABLE_ORDERED || internalPacket->reliability == RELIABLE_ORDERED_WITH_ACK_RECEIPT ) + { + // Assign the ordering channel and index + internalPacket->orderingChannel = orderingChannel; + internalPacket->orderingIndex = waitingForOrderedPacketWriteIndex[ orderingChannel ] ++; + } + + + + if ( splitPacket ) // If it uses a secure header it will be generated here + { + // Must split the packet. This will also generate the SHA1 if it is required. It also adds it to the send list. + //InternalPacket packetCopy; + //memcpy(&packetCopy, internalPacket, sizeof(InternalPacket)); + //sendPacketSet[priority].CancelWriteLock(internalPacket); + //SplitPacket( &packetCopy, MTUSize ); + SplitPacket( internalPacket ); + //RakNet::OP_DELETE_ARRAY(packetCopy.data, __FILE__, __LINE__); + return true; + } + + RakAssert(internalPacket->dataBitLengthdataBitLengthmessageNumberAssigned==false); + outgoingPacketBuffer.Push( GetNextWeight(internalPacket->priority), internalPacket, __FILE__, __LINE__ ); + RakAssert(outgoingPacketBuffer.Size()==0 || outgoingPacketBuffer.Peek()->dataBitLengthpriority]++; + statistics.bytesInSendBuffer[(int)internalPacket->priority]+=(double) BITS_TO_BYTES(internalPacket->dataBitLength); + + // sendPacketSet[priority].WriteUnlock(); + return true; +} +//------------------------------------------------------------------------------------------------------- +// Run this once per game cycle. Handles internal lists and actually does the send +//------------------------------------------------------------------------------------------------------- +void ReliabilityLayer::Update( SOCKET s, SystemAddress systemAddress, int MTUSize, CCTimeType time, + unsigned bitsPerSecondLimit, + DataStructures::List &messageHandlerList, + RakNetRandom *rnr, unsigned short remotePortRakNetWasStartedOn_PS3) + +{ + (void) bitsPerSecondLimit; + (void) MTUSize; + + RakNetTimeMS timeMs; +#if CC_TIME_TYPE_BYTES==4 + time/=1000; + timeMs=time; +#else + timeMs=time/1000; +#endif + + // int i; + +#ifdef _DEBUG + while (delayList.Size()) + { + if (delayList.Peek()->sendTime <= timeMs) + { + DataAndTime *dat = delayList.Pop(); + SocketLayer::Instance()->SendTo( dat->s, dat->data, dat->length, systemAddress.binaryAddress, systemAddress.port, dat->remotePortRakNetWasStartedOn_PS3 ); + RakNet::OP_DELETE(dat,__FILE__,__LINE__); + } + break; + } +#endif + + // This line is necessary because the timer isn't accurate + if (time <= lastUpdateTime) + { + // Always set the last time in case of overflow + lastUpdateTime=time; + return; + } + + CCTimeType timeSinceLastTick = time - lastUpdateTime; + lastUpdateTime=time; +#if CC_TIME_TYPE_BYTES==4 + if (timeSinceLastTick>100) + timeSinceLastTick=100; +#else + if (timeSinceLastTick>100000) + timeSinceLastTick=100000; +#endif + + if (unreliableTimeout>0) + { + if (timeSinceLastTick>=timeToNextUnreliableCull) + { + if (unreliableLinkedListHead) + { + // Cull out all unreliable messages that have exceeded the timeout + InternalPacket *cur = unreliableLinkedListHead; + InternalPacket *end = unreliableLinkedListHead->unreliablePrev; + while (1) + { + if (time > cur->creationTime+(CCTimeType)unreliableTimeout) + { + // Flag invalid, and clear the memory. Still needs to be removed from the sendPacketSet later + // This fixes a problem where a remote system disconnects, but we don't know it yet, and memory consumption increases to a huge value + FreeInternalPacketData(cur, __FILE__, __LINE__ ); + cur->data=0; + InternalPacket *next = cur->unreliableNext; + RemoveFromUnreliableLinkedList(cur); + + if (cur==end) + break; + + cur=next; + } + else + { + // if (cur==end) + // break; + // + // cur=cur->unreliableNext; + + // They should be inserted in-order, so no need to iterate past the first failure + break; + } + } + } + + timeToNextUnreliableCull=unreliableTimeout/(CCTimeType)2; + } + else + { + timeToNextUnreliableCull-=timeSinceLastTick; + } + } + + + // Due to thread vagarities and the way I store the time to avoid slow calls to RakNet::GetTime + // time may be less than lastAck +#if CC_TIME_TYPE_BYTES==4 + if ( statistics.messagesInResendBuffer!=0 && AckTimeout(time) ) +#else + if ( statistics.messagesInResendBuffer!=0 && AckTimeout(time/1000) ) +#endif + { + // SHOW - dead connection + // We've waited a very long time for a reliable packet to get an ack and it never has + deadConnection = true; + return; + } + + if (congestionManager.ShouldSendACKs(time,timeSinceLastTick)) + { + SendACKs(s, systemAddress, time, rnr, remotePortRakNetWasStartedOn_PS3); + } + + if (NAKs.Size()>0) + { + updateBitStream.Reset(); + DatagramHeaderFormat dhfNAK; + dhfNAK.isNAK=true; + dhfNAK.isACK=false; + dhfNAK.isPacketPair=false; + dhfNAK.Serialize(&updateBitStream); + NAKs.Serialize(&updateBitStream, GetMaxDatagramSizeExcludingMessageHeaderBits(), true); + SendBitStream( s, systemAddress, &updateBitStream, rnr, remotePortRakNetWasStartedOn_PS3, time ); + } + + DatagramHeaderFormat dhf; + dhf.needsBAndAs=congestionManager.GetIsInSlowStart(); + dhf.isContinuousSend=bandwidthExceededStatistic; + // bandwidthExceededStatistic=sendPacketSet[0].IsEmpty()==false || + // sendPacketSet[1].IsEmpty()==false || + // sendPacketSet[2].IsEmpty()==false || + // sendPacketSet[3].IsEmpty()==false; + bandwidthExceededStatistic=outgoingPacketBuffer.Size()>0; + + const bool hasDataToSendOrResend = IsResendQueueEmpty()==false || bandwidthExceededStatistic; + RakAssert(NUMBER_OF_PRIORITIES==4); + congestionManager.Update(time, hasDataToSendOrResend); + + uint64_t actualBPS = bpsMetrics[(int) ACTUAL_BYTES_SENT].GetBPS1(time); + statistics.BPSLimitByOutgoingBandwidthLimit = BITS_TO_BYTES(bitsPerSecondLimit); + statistics.BPSLimitByCongestionControl = congestionManager.GetBytesPerSecondLimitByCongestionControl(); + if (statistics.BPSLimitByOutgoingBandwidthLimit > 0 && statistics.BPSLimitByOutgoingBandwidthLimit < actualBPS) + { + statistics.BPSLimitByOutgoingBandwidthLimit=true; + return; + } + else + { + statistics.BPSLimitByOutgoingBandwidthLimit=false; + } + + if (hasDataToSendOrResend==true) + { + InternalPacket *internalPacket; + // bool forceSend=false; + bool pushedAnything; + BitSize_t nextPacketBitLength; + dhf.isACK=false; + dhf.isNAK=false; + dhf.hasBAndAS=false; + ResetPacketsAndDatagrams(); + + int transmissionBandwidth = congestionManager.GetTransmissionBandwidth(time, timeSinceLastTick, unacknowledgedBytes,dhf.isContinuousSend); + int retransmissionBandwidth = congestionManager.GetRetransmissionBandwidth(time, timeSinceLastTick, unacknowledgedBytes,dhf.isContinuousSend); + if (retransmissionBandwidth>0 || transmissionBandwidth>0) + { + statistics.isLimitedByCongestionControl=false; + + allDatagramSizesSoFar=0; + + // Keep filling datagrams until we exceed retransmission bandwidth + while ((int)BITS_TO_BYTES(allDatagramSizesSoFar)messageNumberAssigned==true); + + if ( internalPacket->nextActionTime < time ) + { + // If this is unreliable, then it was just in this list for a receipt. Don't actually resend, and remove from the list + if (internalPacket->reliability==UNRELIABLE_WITH_ACK_RECEIPT + // || internalPacket->reliability==UNRELIABLE_SEQUENCED_WITH_ACK_RECEIPT + ) + { + PopListHead(false); + + InternalPacket *ackReceipt = AllocateFromInternalPacketPool(); + AllocInternalPacketData(ackReceipt, 5, __FILE__, __LINE__ ); + ackReceipt->dataBitLength=BYTES_TO_BITS(5); + ackReceipt->data[0]=(MessageID)ID_SND_RECEIPT_LOSS; + ackReceipt->allocationScheme=InternalPacket::NORMAL; + memcpy(ackReceipt->data+sizeof(MessageID), &internalPacket->sendReceiptSerial, sizeof(internalPacket->sendReceiptSerial)); + outputQueue.Push(ackReceipt, __FILE__, __LINE__ ); + + statistics.messagesInResendBuffer--; + statistics.bytesInResendBuffer-=BITS_TO_BYTES(internalPacket->dataBitLength); + + ReleaseToInternalPacketPool( internalPacket ); + + resendBuffer[internalPacket->reliableMessageNumber & (uint32_t) RESEND_BUFFER_ARRAY_MASK] = 0; + + + continue; + } + + nextPacketBitLength = internalPacket->headerLength + internalPacket->dataBitLength; + if ( datagramSizeSoFar + nextPacketBitLength > GetMaxDatagramSizeExcludingMessageHeaderBits() ) + { + // Gathers all PushPackets() + PushDatagram(); + break; + } + + PopListHead(false); + + CC_DEBUG_PRINTF_2("Rs %i ", internalPacket->reliableMessageNumber.val); + + bpsMetrics[(int) USER_MESSAGE_BYTES_RESENT].Push1(time,BITS_TO_BYTES(internalPacket->dataBitLength)); + PushPacket(time,internalPacket,true); // Affects GetNewTransmissionBandwidth() + internalPacket->timesSent++; + internalPacket->nextActionTime = congestionManager.GetRTOForRetransmission()+time; +#if CC_TIME_TYPE_BYTES==4 + if (internalPacket->nextActionTime-time > 10000) +#else + if (internalPacket->nextActionTime-time > 10000000) +#endif + { + // int a=5; + RakAssert(0); + } + + congestionManager.OnResend(time); + + pushedAnything=true; + + for (unsigned int messageHandlerIndex=0; messageHandlerIndex < messageHandlerList.Size(); messageHandlerIndex++) + { +#if CC_TIME_TYPE_BYTES==4 + messageHandlerList[messageHandlerIndex]->OnInternalPacket(internalPacket, internalPacket->reliableMessageNumber, systemAddress, time, true); +#else + messageHandlerList[messageHandlerIndex]->OnInternalPacket(internalPacket, internalPacket->reliableMessageNumber, systemAddress, (RakNetTime)(time/(CCTimeType)1000), true); +#endif + } + + // Put the packet back into the resend list at the correct spot + // Don't make a copy since I'm reinserting an allocated struct + InsertPacketIntoResendList( internalPacket, time, false, false ); + + // Removeme + // printf("Resend:%i ", internalPacket->reliableMessageNumber); + } + else + { + // Filled one datagram. + // If the 2nd and it's time to send a datagram pair, will be marked as a pair + PushDatagram(); + break; + } + } + + if (pushedAnything==false) + break; + } + } + else + { + statistics.isLimitedByCongestionControl=true; + } + +// CCTimeType timeSinceLastContinualSend; +// if (timeOfLastContinualSend!=0) +// timeSinceLastContinualSend=time-timeOfLastContinualSend; +// else +// timeSinceLastContinualSend=0; + + + //RakAssert(bufferOverflow==false); // If asserts, buffer is too small. We are using a slot that wasn't acked yet + if ((int)BITS_TO_BYTES(allDatagramSizesSoFar)messageNumberAssigned==false); + RakAssert(outgoingPacketBuffer.Size()==0 || outgoingPacketBuffer.Peek()->dataBitLengthdata==0) + { + //sendPacketSet[ i ].Pop(); + outgoingPacketBuffer.Pop(0); + RakAssert(outgoingPacketBuffer.Size()==0 || outgoingPacketBuffer.Peek()->dataBitLengthpriority]--; + statistics.bytesInSendBuffer[(int)internalPacket->priority]-=(double) BITS_TO_BYTES(internalPacket->dataBitLength); + ReleaseToInternalPacketPool( internalPacket ); + continue; + } + + internalPacket->headerLength=GetMessageHeaderLengthBits(internalPacket); + nextPacketBitLength = internalPacket->headerLength + internalPacket->dataBitLength; + if ( datagramSizeSoFar + nextPacketBitLength > GetMaxDatagramSizeExcludingMessageHeaderBits() ) + { + // Hit MTU. May still push packets if smaller ones exist at a lower priority + RakAssert(internalPacket->dataBitLengthreliability == RELIABLE || + internalPacket->reliability == RELIABLE_SEQUENCED || + internalPacket->reliability == RELIABLE_ORDERED || + internalPacket->reliability == RELIABLE_WITH_ACK_RECEIPT || +// internalPacket->reliability == RELIABLE_SEQUENCED_WITH_ACK_RECEIPT || + internalPacket->reliability == RELIABLE_ORDERED_WITH_ACK_RECEIPT + ) + isReliable = true; + else + isReliable = false; + + //sendPacketSet[ i ].Pop(); + outgoingPacketBuffer.Pop(0); + RakAssert(outgoingPacketBuffer.Size()==0 || outgoingPacketBuffer.Peek()->dataBitLengthmessageNumberAssigned==false); + statistics.messageInSendBuffer[(int)internalPacket->priority]--; + statistics.bytesInSendBuffer[(int)internalPacket->priority]-=(double) BITS_TO_BYTES(internalPacket->dataBitLength); + if (isReliable + /* + I thought about this and agree that UNRELIABLE_SEQUENCED_WITH_ACK_RECEIPT and RELIABLE_SEQUENCED_WITH_ACK_RECEIPT is not useful unless you also know if the message was discarded. + + The problem is that internally, message numbers are only assigned to reliable messages, because message numbers are only used to discard duplicate message receipt and only reliable messages get sent more than once. However, without message numbers getting assigned and transmitted, there is no way to tell the sender about which messages were discarded. In fact, in looking this over I realized that UNRELIABLE_SEQUENCED_WITH_ACK_RECEIPT introduced a bug, because the remote system assumes all message numbers are used (no holes). With that send type, on packetloss, a permanent hole would have been created which eventually would cause the system to discard all further packets. + + So I have two options. Either do not support ack receipts when sending sequenced, or write complex and major new systems. UNRELIABLE_SEQUENCED_WITH_ACK_RECEIPT would need to send the message ID number on a special channel which allows for non-delivery. And both of them would need to have a special range list to indicate which message numbers were not delivered, so when acks are sent that can be indicated as well. A further problem is that the ack itself can be lost - it is possible that the message can arrive but be discarded, yet the ack is lost. On resend, the resent message would be ignored as duplicate, and you'd never get the discard message either (unless I made a special buffer for that case too). +*/ +// || + // If needs an ack receipt, keep the internal packet around in the list +// internalPacket->reliability == UNRELIABLE_WITH_ACK_RECEIPT || +// internalPacket->reliability == UNRELIABLE_SEQUENCED_WITH_ACK_RECEIPT + ) + { + internalPacket->messageNumberAssigned=true; + internalPacket->reliableMessageNumber=sendReliableMessageNumberIndex; + internalPacket->nextActionTime = congestionManager.GetRTOForRetransmission()+time; +#if CC_TIME_TYPE_BYTES==4 + const CCTimeType threshhold = 10000; +#else + const CCTimeType threshhold = 10000000; +#endif + if (internalPacket->nextActionTime-time > threshhold) + { + // int a=5; + RakAssert(time-internalPacket->nextActionTime < threshhold); + } + //resendTree.Insert( internalPacket->reliableMessageNumber, internalPacket); + if (resendBuffer[internalPacket->reliableMessageNumber & (uint32_t) RESEND_BUFFER_ARRAY_MASK]!=0) + { + // bool overflow = ResendBufferOverflow(); + RakAssert(0); + } + resendBuffer[internalPacket->reliableMessageNumber & (uint32_t) RESEND_BUFFER_ARRAY_MASK] = internalPacket; + statistics.messagesInResendBuffer++; + statistics.bytesInResendBuffer+=BITS_TO_BYTES(internalPacket->dataBitLength); + + // printf("pre:%i ", unacknowledgedBytes); + + InsertPacketIntoResendList( internalPacket, time, true, isReliable); + + + // printf("post:%i ", unacknowledgedBytes); + sendReliableMessageNumberIndex++; + } + internalPacket->timesSent=1; + // If isReliable is false, the packet and its contents will be added to a list to be freed in ClearPacketsAndDatagrams + // However, the internalPacket structure will remain allocated and be in the resendBuffer list if it requires a receipt + bpsMetrics[(int) USER_MESSAGE_BYTES_SENT].Push1(time,BITS_TO_BYTES(internalPacket->dataBitLength)); + PushPacket(time,internalPacket, isReliable); + + for (unsigned int messageHandlerIndex=0; messageHandlerIndex < messageHandlerList.Size(); messageHandlerIndex++) + { +#if CC_TIME_TYPE_BYTES==4 + messageHandlerList[messageHandlerIndex]->OnInternalPacket(internalPacket, internalPacket->reliableMessageNumber, systemAddress, time, true); +#else + messageHandlerList[messageHandlerIndex]->OnInternalPacket(internalPacket, internalPacket->reliableMessageNumber, systemAddress, (RakNetTime)(time/(CCTimeType)1000), true); +#endif + } + pushedAnything=true; + + if (ResendBufferOverflow()) + break; + } + // if (ResendBufferOverflow()) + // break; + // }z + + // No datagrams pushed? + if (datagramSizeSoFar==0) + break; + + // Filled one datagram. + // If the 2nd and it's time to send a datagram pair, will be marked as a pair + PushDatagram(); + } + } + else + { + // printf("S- "); + + } + + + + for (unsigned int datagramIndex=0; datagramIndex < packetsToSendThisUpdateDatagramBoundaries.Size(); datagramIndex++) + { + if (datagramIndex>0) + dhf.isContinuousSend=true; + MessageNumberNode* messageNumberNode = 0; + dhf.datagramNumber=congestionManager.GetNextDatagramSequenceNumber(); + dhf.isPacketPair=datagramsToSendThisUpdateIsPair[datagramIndex]; + + bool isSecondOfPacketPair=dhf.isPacketPair && datagramIndex>0 && datagramsToSendThisUpdateIsPair[datagramIndex-1]; + unsigned int msgIndex, msgTerm; + if (datagramIndex==0) + { + msgIndex=0; + msgTerm=packetsToSendThisUpdateDatagramBoundaries[0]; + } + else + { + msgIndex=packetsToSendThisUpdateDatagramBoundaries[datagramIndex-1]; + msgTerm=packetsToSendThisUpdateDatagramBoundaries[datagramIndex]; + } + + // More accurate time to reset here + dhf.sourceSystemTime=RakNet::GetTimeUS(); + updateBitStream.Reset(); + dhf.Serialize(&updateBitStream); + CC_DEBUG_PRINTF_2("S%i ",dhf.datagramNumber.val); + + while (msgIndex < msgTerm) + { + // If reliable or needs receipt + if ( packetsToSendThisUpdate[msgIndex]->reliability != UNRELIABLE && + packetsToSendThisUpdate[msgIndex]->reliability != UNRELIABLE_SEQUENCED + ) + { + if (messageNumberNode==0) + { + messageNumberNode = AddFirstToDatagramHistory(dhf.datagramNumber, packetsToSendThisUpdate[msgIndex]->reliableMessageNumber); + } + else + { + messageNumberNode = AddSubsequentToDatagramHistory(messageNumberNode, packetsToSendThisUpdate[msgIndex]->reliableMessageNumber); + } + } + + RakAssert(updateBitStream.GetNumberOfBytesUsed()<=MAXIMUM_MTU_SIZE-UDP_HEADER_SIZE); + WriteToBitStreamFromInternalPacket( &updateBitStream, packetsToSendThisUpdate[msgIndex], time ); + RakAssert(updateBitStream.GetNumberOfBytesUsed()<=MAXIMUM_MTU_SIZE-UDP_HEADER_SIZE); + msgIndex++; + } + + if (isSecondOfPacketPair) + { + // Pad to size of first datagram + RakAssert(updateBitStream.GetNumberOfBytesUsed()<=MAXIMUM_MTU_SIZE-UDP_HEADER_SIZE); + updateBitStream.PadWithZeroToByteLength(datagramSizesInBytes[datagramIndex-1]); + RakAssert(updateBitStream.GetNumberOfBytesUsed()<=MAXIMUM_MTU_SIZE-UDP_HEADER_SIZE); + } + + if (messageNumberNode==0) + { + // REMOVEME + // printf("%p AddFirstToDatagramHistory (no reliable) %i\n", this, dhf.datagramNumber.val); + AddFirstToDatagramHistory(dhf.datagramNumber); + } + + // Store what message ids were sent with this datagram + // datagramMessageIDTree.Insert(dhf.datagramNumber,idList); + + congestionManager.OnSendBytes(time,UDP_HEADER_SIZE+DatagramHeaderFormat::GetDataHeaderByteLength()); + + SendBitStream( s, systemAddress, &updateBitStream, rnr, remotePortRakNetWasStartedOn_PS3, time ); + + bandwidthExceededStatistic=outgoingPacketBuffer.Size()>0; + // bandwidthExceededStatistic=sendPacketSet[0].IsEmpty()==false || + // sendPacketSet[1].IsEmpty()==false || + // sendPacketSet[2].IsEmpty()==false || + // sendPacketSet[3].IsEmpty()==false; + + + + if (bandwidthExceededStatistic==true) + timeOfLastContinualSend=time; + else + timeOfLastContinualSend=0; + } + + ClearPacketsAndDatagrams(true); + + // Any data waiting to send after attempting to send, then bandwidth is exceeded + bandwidthExceededStatistic=outgoingPacketBuffer.Size()>0; + // bandwidthExceededStatistic=sendPacketSet[0].IsEmpty()==false || + // sendPacketSet[1].IsEmpty()==false || + // sendPacketSet[2].IsEmpty()==false || + // sendPacketSet[3].IsEmpty()==false; + } + + + // Keep on top of deleting old unreliable split packets so they don't clog the list. + //DeleteOldUnreliableSplitPackets( time ); +} + +//------------------------------------------------------------------------------------------------------- +// Writes a bitstream to the socket +//------------------------------------------------------------------------------------------------------- +void ReliabilityLayer::SendBitStream( SOCKET s, SystemAddress systemAddress, RakNet::BitStream *bitStream, RakNetRandom *rnr, unsigned short remotePortRakNetWasStartedOn_PS3, CCTimeType currentTime) +{ + (void) systemAddress; + + unsigned int oldLength, length; + + + if ( encryptor.IsKeySet() ) + { + length = (unsigned int) bitStream->GetNumberOfBytesUsed(); + oldLength = length; + + encryptor.Encrypt( ( unsigned char* ) bitStream->GetData(), length, ( unsigned char* ) bitStream->GetData(), &length, rnr ); + + RakAssert( ( length % 16 ) == 0 ); + } + else + { + length = (unsigned int) bitStream->GetNumberOfBytesUsed(); + } + +#ifdef _DEBUG + if (packetloss > 0.0) + { + if (frandomMT() < packetloss) + return; + } + + if (minExtraPing > 0 || extraPingVariance > 0) + { + RakNetTimeMS delay = minExtraPing; + if (extraPingVariance>0) + delay += (randomMT() % extraPingVariance); + if (delay > 0) + { + DataAndTime *dat = RakNet::OP_NEW(__FILE__,__LINE__); + memcpy(dat->data, ( char* ) bitStream->GetData(), length ); + dat->s=s; + dat->length=length; + dat->sendTime = RakNet::GetTimeMS() + delay; + dat->remotePortRakNetWasStartedOn_PS3=remotePortRakNetWasStartedOn_PS3; + for (unsigned int i=0; i < delayList.Size(); i++) + { + if (dat->sendTime < delayList[i]->sendTime) + { + delayList.PushAtHead(dat, i, __FILE__, __LINE__); + dat=0; + break; + } + } + if (dat!=0) + delayList.Push(dat,__FILE__,__LINE__); + return; + } + } +#endif + + // Test packetloss +// if (frandomMT()<.1) +// return; + + // printf("%i/%i\n", length,congestionManager.GetMTU()); + + bpsMetrics[(int) ACTUAL_BYTES_SENT].Push1(currentTime,length); + + RakAssert(length <= congestionManager.GetMTU()); + SocketLayer::Instance()->SendTo( s, ( char* ) bitStream->GetData(), length, systemAddress.binaryAddress, systemAddress.port, remotePortRakNetWasStartedOn_PS3 ); +} + +//------------------------------------------------------------------------------------------------------- +// Are we waiting for any data to be sent out or be processed by the player? +//------------------------------------------------------------------------------------------------------- +bool ReliabilityLayer::IsOutgoingDataWaiting(void) +{ + if (outgoingPacketBuffer.Size()>0) + return true; + + // unsigned i; + // for ( i = 0; i < NUMBER_OF_PRIORITIES; i++ ) + // { + // if (sendPacketSet[ i ].Size() > 0) + // return true; + // } + + return + //acknowlegements.Size() > 0 || + //resendTree.IsEmpty()==false;// || outputQueue.Size() > 0 || orderingList.Size() > 0 || splitPacketChannelList.Size() > 0; + statistics.messagesInResendBuffer!=0; +} +bool ReliabilityLayer::AreAcksWaiting(void) +{ + return acknowlegements.Size() > 0; +} + +//------------------------------------------------------------------------------------------------------- +void ReliabilityLayer::ApplyNetworkSimulator( double _packetloss, RakNetTime _minExtraPing, RakNetTime _extraPingVariance ) +{ +#ifdef _DEBUG + packetloss=_packetloss; + minExtraPing=_minExtraPing; + extraPingVariance=_extraPingVariance; + // if (ping < (unsigned int)(minExtraPing+extraPingVariance)*2) + // ping=(minExtraPing+extraPingVariance)*2; +#endif +} +//------------------------------------------------------------------------------------------------------- +void ReliabilityLayer::SetSplitMessageProgressInterval(int interval) +{ + splitMessageProgressInterval=interval; +} +//------------------------------------------------------------------------------------------------------- +void ReliabilityLayer::SetUnreliableTimeout(RakNetTimeMS timeoutMS) +{ +#if CC_TIME_TYPE_BYTES==4 + unreliableTimeout=timeoutMS; +#else + unreliableTimeout=(CCTimeType)timeoutMS*(CCTimeType)1000; +#endif +} + +//------------------------------------------------------------------------------------------------------- +// This will return true if we should not send at this time +//------------------------------------------------------------------------------------------------------- +bool ReliabilityLayer::IsSendThrottled( int MTUSize ) +{ + (void) MTUSize; + + return false; + // return resendList.Size() > windowSize; + + // Disabling this, because it can get stuck here forever + /* + unsigned packetsWaiting; + unsigned resendListDataSize=0; + unsigned i; + for (i=0; i < resendList.Size(); i++) + { + if (resendList[i]) + resendListDataSize+=resendList[i]->dataBitLength; + } + packetsWaiting = 1 + ((BITS_TO_BYTES(resendListDataSize)) / (MTUSize - UDP_HEADER_SIZE - 10)); // 10 to roughly estimate the raknet header + + return packetsWaiting >= windowSize; + */ +} + +//------------------------------------------------------------------------------------------------------- +// We lost a packet +//------------------------------------------------------------------------------------------------------- +void ReliabilityLayer::UpdateWindowFromPacketloss( CCTimeType time ) +{ + (void) time; +} + +//------------------------------------------------------------------------------------------------------- +// Increase the window size +//------------------------------------------------------------------------------------------------------- +void ReliabilityLayer::UpdateWindowFromAck( CCTimeType time ) +{ + (void) time; +} + +//------------------------------------------------------------------------------------------------------- +// Does what the function name says +//------------------------------------------------------------------------------------------------------- +unsigned ReliabilityLayer::RemovePacketFromResendListAndDeleteOlderReliableSequenced( const MessageNumberType messageNumber, CCTimeType time, DataStructures::List &messageHandlerList, SystemAddress systemAddress ) +{ + (void) time; + (void) messageNumber; + InternalPacket * internalPacket; + //InternalPacket *temp; +// PacketReliability reliability; // What type of reliability algorithm to use with this packet +// unsigned char orderingChannel; // What ordering channel this packet is on, if the reliability type uses ordering channels + OrderingIndexType orderingIndex; // The ID used as identification for ordering channels + // unsigned j; + + for (unsigned int messageHandlerIndex=0; messageHandlerIndex < messageHandlerList.Size(); messageHandlerIndex++) + { +#if CC_TIME_TYPE_BYTES==4 + messageHandlerList[messageHandlerIndex]->OnAck(messageNumber, systemAddress, time); +#else + messageHandlerList[messageHandlerIndex]->OnAck(messageNumber, systemAddress, (RakNetTime)(time/(CCTimeType)1000)); +#endif + } + + // bool deleted; + // deleted=resendTree.Delete(messageNumber, internalPacket); + internalPacket = resendBuffer[messageNumber & RESEND_BUFFER_ARRAY_MASK]; + if (internalPacket) + { + ValidateResendList(); + resendBuffer[messageNumber & RESEND_BUFFER_ARRAY_MASK]=0; + CC_DEBUG_PRINTF_2("AckRcv %i ", messageNumber); + + statistics.messagesInResendBuffer--; + statistics.bytesInResendBuffer-=BITS_TO_BYTES(internalPacket->dataBitLength); + + orderingIndex = internalPacket->orderingIndex; + totalUserDataBytesAcked+=(double) BITS_TO_BYTES(internalPacket->headerLength+internalPacket->dataBitLength); + + // Return receipt if asked for + if (internalPacket->reliability>=UNRELIABLE_WITH_ACK_RECEIPT && + (internalPacket->splitPacketCount==0 || internalPacket->splitPacketIndex+1==internalPacket->splitPacketCount) + ) + { + InternalPacket *ackReceipt = AllocateFromInternalPacketPool(); + AllocInternalPacketData(ackReceipt, 5, __FILE__, __LINE__ ); + ackReceipt->dataBitLength=BYTES_TO_BITS(5); + ackReceipt->data[0]=(MessageID)ID_SND_RECEIPT_ACKED; + ackReceipt->allocationScheme=InternalPacket::NORMAL; + memcpy(ackReceipt->data+sizeof(MessageID), &internalPacket->sendReceiptSerial, sizeof(internalPacket->sendReceiptSerial)); + outputQueue.Push(ackReceipt, __FILE__, __LINE__ ); + } + + bool isReliable; + if ( internalPacket->reliability == RELIABLE || + internalPacket->reliability == RELIABLE_SEQUENCED || + internalPacket->reliability == RELIABLE_ORDERED || + internalPacket->reliability == RELIABLE_WITH_ACK_RECEIPT || +// internalPacket->reliability == RELIABLE_SEQUENCED_WITH_ACK_RECEIPT || + internalPacket->reliability == RELIABLE_ORDERED_WITH_ACK_RECEIPT + ) + isReliable = true; + else + isReliable = false; + + RemoveFromList(internalPacket, isReliable); + FreeInternalPacketData(internalPacket, __FILE__, __LINE__ ); + ReleaseToInternalPacketPool( internalPacket ); + + + return 0; + } + else + { + + } + + return (unsigned)-1; +} + +//------------------------------------------------------------------------------------------------------- +// Acknowledge receipt of the packet with the specified messageNumber +//------------------------------------------------------------------------------------------------------- +void ReliabilityLayer::SendAcknowledgementPacket( const DatagramSequenceNumberType messageNumber, CCTimeType time ) +{ + + nextAckTimeToSend=time; + acknowlegements.Insert(messageNumber); + + //printf("ACK_DG:%i ", messageNumber.val); + + CC_DEBUG_PRINTF_2("AckPush %i ", messageNumber); + +} + +//------------------------------------------------------------------------------------------------------- +// Parse an internalPacket and figure out how many header bits would be +// written. Returns that number +//------------------------------------------------------------------------------------------------------- +BitSize_t ReliabilityLayer::GetMaxMessageHeaderLengthBits( void ) +{ + InternalPacket ip; + ip.reliability=RELIABLE_ORDERED; + ip.splitPacketCount=1; + return GetMessageHeaderLengthBits(&ip); +} +//------------------------------------------------------------------------------------------------------- +BitSize_t ReliabilityLayer::GetMessageHeaderLengthBits( const InternalPacket *const internalPacket ) +{ + BitSize_t bitLength; + + // bitStream->AlignWriteToByteBoundary(); // Potentially unaligned + // tempChar=(unsigned char)internalPacket->reliability; bitStream->WriteBits( (const unsigned char *)&tempChar, 3, true ); // 3 bits to write reliability. + // bool hasSplitPacket = internalPacket->splitPacketCount>0; bitStream->Write(hasSplitPacket); // Write 1 bit to indicate if splitPacketCount>0 + bitLength = 8*1; + + // bitStream->AlignWriteToByteBoundary(); + // RakAssert(internalPacket->dataBitLength < 65535); + // unsigned short s; s = (unsigned short) internalPacket->dataBitLength; bitStream->WriteAlignedVar16((const char*)& s); + bitLength += 8*2; + + if ( internalPacket->reliability == RELIABLE || + internalPacket->reliability == RELIABLE_SEQUENCED || + internalPacket->reliability == RELIABLE_ORDERED || + internalPacket->reliability == RELIABLE_WITH_ACK_RECEIPT || +// internalPacket->reliability == RELIABLE_SEQUENCED_WITH_ACK_RECEIPT || + internalPacket->reliability == RELIABLE_ORDERED_WITH_ACK_RECEIPT + ) + bitLength += 8*3; // bitStream->Write(internalPacket->reliableMessageNumber); // Message sequence number + // bitStream->AlignWriteToByteBoundary(); // Potentially nothing else to write + if ( internalPacket->reliability == UNRELIABLE_SEQUENCED || + internalPacket->reliability == RELIABLE_SEQUENCED || + internalPacket->reliability == RELIABLE_ORDERED || +// internalPacket->reliability == UNRELIABLE_SEQUENCED_WITH_ACK_RECEIPT || +// internalPacket->reliability == RELIABLE_SEQUENCED_WITH_ACK_RECEIPT || + internalPacket->reliability == RELIABLE_ORDERED_WITH_ACK_RECEIPT + ) + { + bitLength += 8*3; // bitStream->Write(internalPacket->orderingIndex); // Used for UNRELIABLE_SEQUENCED, RELIABLE_SEQUENCED, RELIABLE_ORDERED. + bitLength += 8*1; // tempChar=internalPacket->orderingChannel; bitStream->WriteAlignedVar8((const char*)& tempChar); // Used for UNRELIABLE_SEQUENCED, RELIABLE_SEQUENCED, RELIABLE_ORDERED. 5 bits needed, write one byte + } + if (internalPacket->splitPacketCount>0) + { + bitLength += 8*4; // bitStream->WriteAlignedVar32((const char*)& internalPacket->splitPacketCount); RakAssert(sizeof(SplitPacketIndexType)==4); // Only needed if splitPacketCount>0. 4 bytes + bitLength += 8*sizeof(SplitPacketIdType); // bitStream->WriteAlignedVar16((const char*)& internalPacket->splitPacketId); RakAssert(sizeof(SplitPacketIdType)==2); // Only needed if splitPacketCount>0. + bitLength += 8*4; // bitStream->WriteAlignedVar32((const char*)& internalPacket->splitPacketIndex); // Only needed if splitPacketCount>0. 4 bytes + } + + return bitLength; +} + +//------------------------------------------------------------------------------------------------------- +// Parse an internalPacket and create a bitstream to represent this data +//------------------------------------------------------------------------------------------------------- +BitSize_t ReliabilityLayer::WriteToBitStreamFromInternalPacket( RakNet::BitStream *bitStream, const InternalPacket *const internalPacket, CCTimeType curTime ) +{ + (void) curTime; + + BitSize_t start = bitStream->GetNumberOfBitsUsed(); + unsigned char tempChar; + + // (Incoming data may be all zeros due to padding) + bitStream->AlignWriteToByteBoundary(); // Potentially unaligned + tempChar=(unsigned char)internalPacket->reliability; + if (tempChar>=(unsigned char) UNRELIABLE_WITH_ACK_RECEIPT) + tempChar-=5; + bitStream->WriteBits( (const unsigned char *)&tempChar, 3, true ); // 3 bits to write reliability. + + bool hasSplitPacket = internalPacket->splitPacketCount>0; bitStream->Write(hasSplitPacket); // Write 1 bit to indicate if splitPacketCount>0 + bitStream->AlignWriteToByteBoundary(); + RakAssert(internalPacket->dataBitLength < 65535); + unsigned short s; s = (unsigned short) internalPacket->dataBitLength; bitStream->WriteAlignedVar16((const char*)& s); + if ( internalPacket->reliability == RELIABLE || + internalPacket->reliability == RELIABLE_SEQUENCED || + internalPacket->reliability == RELIABLE_ORDERED || + internalPacket->reliability == RELIABLE_WITH_ACK_RECEIPT || +// internalPacket->reliability == RELIABLE_SEQUENCED_WITH_ACK_RECEIPT || + internalPacket->reliability == RELIABLE_ORDERED_WITH_ACK_RECEIPT + ) + bitStream->Write(internalPacket->reliableMessageNumber); // Message sequence number + bitStream->AlignWriteToByteBoundary(); // Potentially nothing else to write + if ( internalPacket->reliability == UNRELIABLE_SEQUENCED || + internalPacket->reliability == RELIABLE_SEQUENCED || + internalPacket->reliability == RELIABLE_ORDERED || + // internalPacket->reliability == UNRELIABLE_SEQUENCED_WITH_ACK_RECEIPT || + // internalPacket->reliability == RELIABLE_SEQUENCED_WITH_ACK_RECEIPT || + internalPacket->reliability == RELIABLE_ORDERED_WITH_ACK_RECEIPT + ) + { + bitStream->Write(internalPacket->orderingIndex); // Used for UNRELIABLE_SEQUENCED, RELIABLE_SEQUENCED, RELIABLE_ORDERED. + tempChar=internalPacket->orderingChannel; bitStream->WriteAlignedVar8((const char*)& tempChar); // Used for UNRELIABLE_SEQUENCED, RELIABLE_SEQUENCED, RELIABLE_ORDERED. 5 bits needed, write one byte + } + if (internalPacket->splitPacketCount>0) + { + bitStream->WriteAlignedVar32((const char*)& internalPacket->splitPacketCount); RakAssert(sizeof(SplitPacketIndexType)==4); // Only needed if splitPacketCount>0. 4 bytes + bitStream->WriteAlignedVar16((const char*)& internalPacket->splitPacketId); RakAssert(sizeof(SplitPacketIdType)==2); // Only needed if splitPacketCount>0. + bitStream->WriteAlignedVar32((const char*)& internalPacket->splitPacketIndex); // Only needed if splitPacketCount>0. 4 bytes + } + + // Write the actual data. + bitStream->WriteAlignedBytes( ( unsigned char* ) internalPacket->data, BITS_TO_BYTES( internalPacket->dataBitLength ) ); + + return bitStream->GetNumberOfBitsUsed() - start; +} + +//------------------------------------------------------------------------------------------------------- +// Parse a bitstream and create an internal packet to represent this data +//------------------------------------------------------------------------------------------------------- +InternalPacket* ReliabilityLayer::CreateInternalPacketFromBitStream( RakNet::BitStream *bitStream, CCTimeType time ) +{ + bool bitStreamSucceeded; + InternalPacket* internalPacket; + unsigned char tempChar; + bool hasSplitPacket; + bool readSuccess; + + if ( bitStream->GetNumberOfUnreadBits() < (int) sizeof( internalPacket->reliableMessageNumber ) * 8 ) + return 0; // leftover bits + + internalPacket = AllocateFromInternalPacketPool(); + if (internalPacket==0) + { + // Out of memory + RakAssert(0); + return 0; + } + internalPacket->creationTime = time; + + // (Incoming data may be all zeros due to padding) + bitStream->AlignReadToByteBoundary(); // Potentially unaligned + bitStream->ReadBits( ( unsigned char* ) ( &( tempChar ) ), 3 ); + internalPacket->reliability = ( const PacketReliability ) tempChar; + readSuccess=bitStream->Read(hasSplitPacket); // Read 1 bit to indicate if splitPacketCount>0 + bitStream->AlignReadToByteBoundary(); + unsigned short s; bitStream->ReadAlignedVar16((char*)&s); internalPacket->dataBitLength=s; // Length of message (2 bytes) + if ( internalPacket->reliability == RELIABLE || + internalPacket->reliability == RELIABLE_SEQUENCED || + internalPacket->reliability == RELIABLE_ORDERED + // I don't write ACK_RECEIPT to the remote system +// || +// internalPacket->reliability == RELIABLE_WITH_ACK_RECEIPT || +// internalPacket->reliability == RELIABLE_SEQUENCED_WITH_ACK_RECEIPT || +// internalPacket->reliability == RELIABLE_ORDERED_WITH_ACK_RECEIPT + ) + bitStream->Read(internalPacket->reliableMessageNumber); // Message sequence number + else + internalPacket->reliableMessageNumber=(MessageNumberType)(const uint32_t)-1; + bitStream->AlignReadToByteBoundary(); // Potentially nothing else to Read + if ( internalPacket->reliability == UNRELIABLE_SEQUENCED || + internalPacket->reliability == RELIABLE_SEQUENCED || + internalPacket->reliability == RELIABLE_ORDERED + // I don't write ACK_RECEIPT to the remote system +// || +// internalPacket->reliability == UNRELIABLE_SEQUENCED_WITH_ACK_RECEIPT || +// internalPacket->reliability == RELIABLE_SEQUENCED_WITH_ACK_RECEIPT || +// internalPacket->reliability == RELIABLE_ORDERED_WITH_ACK_RECEIPT + ) + { + bitStream->Read(internalPacket->orderingIndex); // Used for UNRELIABLE_SEQUENCED, RELIABLE_SEQUENCED, RELIABLE_ORDERED. 4 bytes. + readSuccess=bitStream->ReadAlignedVar8((char*)& internalPacket->orderingChannel); // Used for UNRELIABLE_SEQUENCED, RELIABLE_SEQUENCED, RELIABLE_ORDERED. 5 bits needed, Read one byte + } + else + internalPacket->orderingChannel=0; + if (hasSplitPacket) + { + bitStream->ReadAlignedVar32((char*)& internalPacket->splitPacketCount); // Only needed if splitPacketCount>0. 4 bytes + bitStream->ReadAlignedVar16((char*)& internalPacket->splitPacketId); // Only needed if splitPacketCount>0. + readSuccess=bitStream->ReadAlignedVar32((char*)& internalPacket->splitPacketIndex); // Only needed if splitPacketCount>0. 4 bytes + RakAssert(readSuccess); + } + else + { + internalPacket->splitPacketCount=0; + } + + if (readSuccess==false || + internalPacket->dataBitLength==0 || + internalPacket->reliability>=NUMBER_OF_RELIABILITIES || + internalPacket->orderingChannel>=32 || + (hasSplitPacket && (internalPacket->splitPacketIndex >= internalPacket->splitPacketCount))) + { + // If this assert hits, encoding is garbage + RakAssert(internalPacket->dataBitLength==0); + ReleaseToInternalPacketPool( internalPacket ); + return 0; + } + + // Allocate memory to hold our data + AllocInternalPacketData(internalPacket, BITS_TO_BYTES( internalPacket->dataBitLength ), __FILE__, __LINE__ ); + RakAssert(BITS_TO_BYTES( internalPacket->dataBitLength )data == 0) + { + RakAssert("Out of memory in ReliabilityLayer::CreateInternalPacketFromBitStream" && 0); + notifyOutOfMemory(__FILE__, __LINE__); + ReleaseToInternalPacketPool( internalPacket ); + return 0; + } + + // Set the last byte to 0 so if ReadBits does not read a multiple of 8 the last bits are 0'ed out + internalPacket->data[ BITS_TO_BYTES( internalPacket->dataBitLength ) - 1 ] = 0; + + // Read the data the packet holds + bitStreamSucceeded = bitStream->ReadAlignedBytes( ( unsigned char* ) internalPacket->data, BITS_TO_BYTES( internalPacket->dataBitLength ) ); + + if ( bitStreamSucceeded == false ) + { + // If this hits, most likely the variable buff is too small in RunUpdateCycle in RakPeer.cpp + RakAssert("Couldn't read all the data" && 0); + + FreeInternalPacketData(internalPacket, __FILE__, __LINE__ ); + ReleaseToInternalPacketPool( internalPacket ); + return 0; + } + + return internalPacket; +} + + +//------------------------------------------------------------------------------------------------------- +// Get the SHA1 code +//------------------------------------------------------------------------------------------------------- +void ReliabilityLayer::GetSHA1( unsigned char * const buffer, unsigned int + nbytes, char code[ SHA1_LENGTH ] ) +{ + CSHA1 sha1; + + sha1.Reset(); + sha1.Update( ( unsigned char* ) buffer, nbytes ); + sha1.Final(); + memcpy( code, sha1.GetHash(), SHA1_LENGTH ); +} + +//------------------------------------------------------------------------------------------------------- +// Check the SHA1 code +//------------------------------------------------------------------------------------------------------- +bool ReliabilityLayer::CheckSHA1( char code[ SHA1_LENGTH ], unsigned char * + const buffer, unsigned int nbytes ) +{ + char code2[ SHA1_LENGTH ]; + GetSHA1( buffer, nbytes, code2 ); + + for ( int i = 0; i < SHA1_LENGTH; i++ ) + if ( code[ i ] != code2[ i ] ) + return false; + + return true; +} + +//------------------------------------------------------------------------------------------------------- +// Search the specified list for sequenced packets on the specified ordering +// stream, optionally skipping those with splitPacketId, and delete them +//------------------------------------------------------------------------------------------------------- +void ReliabilityLayer::DeleteSequencedPacketsInList( unsigned char orderingChannel, DataStructures::List&theList, int splitPacketId ) +{ + unsigned i = 0; + + while ( i < theList.Size() ) + { + if ( ( + theList[ i ]->reliability == RELIABLE_SEQUENCED || + theList[ i ]->reliability == UNRELIABLE_SEQUENCED +// || +// theList[ i ]->reliability == RELIABLE_SEQUENCED_WITH_ACK_RECEIPT || +// theList[ i ]->reliability == UNRELIABLE_SEQUENCED_WITH_ACK_RECEIPT + ) && + theList[ i ]->orderingChannel == orderingChannel && ( splitPacketId == -1 || theList[ i ]->splitPacketId != (unsigned int) splitPacketId ) ) + { + InternalPacket * internalPacket = theList[ i ]; + theList.RemoveAtIndex( i ); + FreeInternalPacketData(internalPacket, __FILE__, __LINE__ ); + ReleaseToInternalPacketPool( internalPacket ); + } + + else + i++; + } +} + +//------------------------------------------------------------------------------------------------------- +// Search the specified list for sequenced packets with a value less than orderingIndex and delete them +// Note - I added functionality so you can use the Queue as a list (in this case for searching) but it is less efficient to do so than a regular list +//------------------------------------------------------------------------------------------------------- +void ReliabilityLayer::DeleteSequencedPacketsInList( unsigned char orderingChannel, DataStructures::Queue&theList ) +{ + InternalPacket * internalPacket; + int listSize = theList.Size(); + int i = 0; + + while ( i < listSize ) + { + if ( ( + theList[ i ]->reliability == RELIABLE_SEQUENCED || + theList[ i ]->reliability == UNRELIABLE_SEQUENCED +// || +// theList[ i ]->reliability == RELIABLE_SEQUENCED_WITH_ACK_RECEIPT || +// theList[ i ]->reliability == UNRELIABLE_SEQUENCED_WITH_ACK_RECEIPT + ) && theList[ i ]->orderingChannel == orderingChannel ) + { + internalPacket = theList[ i ]; + theList.RemoveAtIndex( i ); + FreeInternalPacketData(internalPacket, __FILE__, __LINE__ ); + ReleaseToInternalPacketPool( internalPacket ); + listSize--; + } + + else + i++; + } +} + +//------------------------------------------------------------------------------------------------------- +// Returns true if newPacketOrderingIndex is older than the waitingForPacketOrderingIndex +//------------------------------------------------------------------------------------------------------- +bool ReliabilityLayer::IsOlderOrderedPacket( OrderingIndexType newPacketOrderingIndex, OrderingIndexType waitingForPacketOrderingIndex ) +{ + OrderingIndexType maxRange = (OrderingIndexType) (const uint32_t)-1; + + if ( waitingForPacketOrderingIndex > maxRange/(OrderingIndexType)2 ) + { + if ( newPacketOrderingIndex >= waitingForPacketOrderingIndex - maxRange/(OrderingIndexType)2+(OrderingIndexType)1 && newPacketOrderingIndex < waitingForPacketOrderingIndex ) + { + return true; + } + } + + else + if ( newPacketOrderingIndex >= ( OrderingIndexType ) ( waitingForPacketOrderingIndex - (( OrderingIndexType ) maxRange/(OrderingIndexType)2+(OrderingIndexType)1) ) || + newPacketOrderingIndex < waitingForPacketOrderingIndex ) + { + return true; + } + + // Old packet + return false; +} + +//------------------------------------------------------------------------------------------------------- +// Split the passed packet into chunks under MTU_SIZEbytes (including headers) and save those new chunks +// Optimized version +//------------------------------------------------------------------------------------------------------- +void ReliabilityLayer::SplitPacket( InternalPacket *internalPacket ) +{ + // Doing all sizes in bytes in this function so I don't write partial bytes with split packets + internalPacket->splitPacketCount = 1; // This causes GetMessageHeaderLengthBits to account for the split packet header + unsigned int headerLength = (unsigned int) BITS_TO_BYTES( GetMessageHeaderLengthBits( internalPacket ) ); + unsigned int dataByteLength = (unsigned int) BITS_TO_BYTES( internalPacket->dataBitLength ); + int maximumSendBlockBytes, byteOffset, bytesToSend; + SplitPacketIndexType splitPacketIndex; + int i; + InternalPacket **internalPacketArray; + + maximumSendBlockBytes = GetMaxDatagramSizeExcludingMessageHeaderBytes() - BITS_TO_BYTES(GetMaxMessageHeaderLengthBits()); + + // Calculate how many packets we need to create + internalPacket->splitPacketCount = ( ( dataByteLength - 1 ) / ( maximumSendBlockBytes ) + 1 ); + + // Optimization + // internalPacketArray = RakNet::OP_NEW(internalPacket->splitPacketCount, __FILE__, __LINE__ ); + bool usedAlloca=false; +#if !defined(_XBOX) && !defined(_X360) + if (sizeof( InternalPacket* ) * internalPacket->splitPacketCount < MAX_ALLOCA_STACK_ALLOCATION) + { + internalPacketArray = ( InternalPacket** ) alloca( sizeof( InternalPacket* ) * internalPacket->splitPacketCount ); + usedAlloca=true; + } + else +#endif + internalPacketArray = (InternalPacket**) rakMalloc_Ex( sizeof(InternalPacket*) * internalPacket->splitPacketCount, __FILE__, __LINE__ ); + + for ( i = 0; i < ( int ) internalPacket->splitPacketCount; i++ ) + { + internalPacketArray[ i ] = AllocateFromInternalPacketPool(); + + //internalPacketArray[ i ] = (InternalPacket*) alloca( sizeof( InternalPacket ) ); + // internalPacketArray[ i ] = sendPacketSet[internalPacket->priority].WriteLock(); + *internalPacketArray[ i ]=*internalPacket; + internalPacketArray[ i ]->messageNumberAssigned=false; + + if (i!=0) + internalPacket->messageInternalOrder = internalOrderIndex++; + } + + // This identifies which packet this is in the set + splitPacketIndex = 0; + + InternalPacketRefCountedData *refCounter=0; + + // Do a loop to send out all the packets + do + { + byteOffset = splitPacketIndex * maximumSendBlockBytes; + bytesToSend = dataByteLength - byteOffset; + + if ( bytesToSend > maximumSendBlockBytes ) + bytesToSend = maximumSendBlockBytes; + + // Copy over our chunk of data + + AllocInternalPacketData(internalPacketArray[ splitPacketIndex ], &refCounter, internalPacket->data, internalPacket->data + byteOffset); + // internalPacketArray[ splitPacketIndex ]->data = (unsigned char*) rakMalloc_Ex( bytesToSend, __FILE__, __LINE__ ); + // memcpy( internalPacketArray[ splitPacketIndex ]->data, internalPacket->data + byteOffset, bytesToSend ); + + if ( bytesToSend != maximumSendBlockBytes ) + internalPacketArray[ splitPacketIndex ]->dataBitLength = internalPacket->dataBitLength - splitPacketIndex * ( maximumSendBlockBytes << 3 ); + else + internalPacketArray[ splitPacketIndex ]->dataBitLength = bytesToSend << 3; + + internalPacketArray[ splitPacketIndex ]->splitPacketIndex = splitPacketIndex; + internalPacketArray[ splitPacketIndex ]->splitPacketId = splitPacketId; + internalPacketArray[ splitPacketIndex ]->splitPacketCount = internalPacket->splitPacketCount; + RakAssert(internalPacketArray[ splitPacketIndex ]->dataBitLengthsplitPacketCount ); + + splitPacketId++; // It's ok if this wraps to 0 + + // InternalPacket *workingPacket; + + // Tell the heap we are going to push a list of elements where each element in the list follows the heap order + RakAssert(outgoingPacketBuffer.Size()==0 || outgoingPacketBuffer.Peek()->dataBitLengthsplitPacketCount; i++ ) + { + internalPacketArray[ i ]->headerLength=headerLength; + RakAssert(internalPacketArray[ i ]->dataBitLengthpriority ].Push( internalPacketArray[ i ], __FILE__, __LINE__ ); + RakAssert(internalPacketArray[ i ]->dataBitLengthmessageNumberAssigned==false); + outgoingPacketBuffer.PushSeries(GetNextWeight(internalPacketArray[ i ]->priority), internalPacketArray[ i ], __FILE__, __LINE__); + RakAssert(outgoingPacketBuffer.Size()==0 || outgoingPacketBuffer.Peek()->dataBitLengthpriority]++; + statistics.bytesInSendBuffer[(int)(int)internalPacketArray[ i ]->priority]+=(double) BITS_TO_BYTES(internalPacketArray[ i ]->dataBitLength); + // workingPacket=sendPacketSet[internalPacket->priority].WriteLock(); + // memcpy(workingPacket, internalPacketArray[ i ], sizeof(InternalPacket)); + // sendPacketSet[internalPacket->priority].WriteUnlock(); + } + + // Do not delete, original is referenced by all split packets to avoid numerous allocations. See AllocInternalPacketData above + // FreeInternalPacketData(internalPacket, __FILE__, __LINE__ ); + ReleaseToInternalPacketPool( internalPacket ); + + if (usedAlloca==false) + rakFree_Ex(internalPacketArray, __FILE__, __LINE__ ); +} + +//------------------------------------------------------------------------------------------------------- +// Insert a packet into the split packet list +//------------------------------------------------------------------------------------------------------- +void ReliabilityLayer::InsertIntoSplitPacketList( InternalPacket * internalPacket, CCTimeType time ) +{ + bool objectExists; + unsigned index; + index=splitPacketChannelList.GetIndexFromKey(internalPacket->splitPacketId, &objectExists); + if (objectExists==false) + { + SplitPacketChannel *newChannel = RakNet::OP_NEW( __FILE__, __LINE__ ); + //SplitPacketChannel *newChannel = RakNet::OP_NEW(1, __FILE__, __LINE__ ); + newChannel->firstPacket=0; + index=splitPacketChannelList.Insert(internalPacket->splitPacketId, newChannel, true, __FILE__,__LINE__); + // Preallocate to the final size, to avoid runtime copies + newChannel->splitPacketList.Preallocate(internalPacket->splitPacketCount, __FILE__,__LINE__); + } + splitPacketChannelList[index]->splitPacketList.Insert(internalPacket, __FILE__, __LINE__ ); + splitPacketChannelList[index]->lastUpdateTime=time; + + if (internalPacket->splitPacketIndex==0) + splitPacketChannelList[index]->firstPacket=internalPacket; + + if (splitMessageProgressInterval && + splitPacketChannelList[index]->firstPacket && + splitPacketChannelList[index]->splitPacketList.Size()!=splitPacketChannelList[index]->firstPacket->splitPacketCount && + (splitPacketChannelList[index]->splitPacketList.Size()%splitMessageProgressInterval)==0) + { + // Return ID_DOWNLOAD_PROGRESS + // Write splitPacketIndex (SplitPacketIndexType) + // Write splitPacketCount (SplitPacketIndexType) + // Write byteLength (4) + // Write data, splitPacketChannelList[index]->splitPacketList[0]->data + InternalPacket *progressIndicator = AllocateFromInternalPacketPool(); + unsigned int length = sizeof(MessageID) + sizeof(unsigned int)*2 + sizeof(unsigned int) + (unsigned int) BITS_TO_BYTES(splitPacketChannelList[index]->firstPacket->dataBitLength); + AllocInternalPacketData(progressIndicator, length, __FILE__, __LINE__ ); + progressIndicator->dataBitLength=BYTES_TO_BITS(length); + progressIndicator->data[0]=(MessageID)ID_DOWNLOAD_PROGRESS; + progressIndicator->allocationScheme=InternalPacket::NORMAL; + unsigned int temp; + temp=splitPacketChannelList[index]->splitPacketList.Size(); + memcpy(progressIndicator->data+sizeof(MessageID), &temp, sizeof(unsigned int)); + temp=(unsigned int)internalPacket->splitPacketCount; + memcpy(progressIndicator->data+sizeof(MessageID)+sizeof(unsigned int)*1, &temp, sizeof(unsigned int)); + temp=(unsigned int) BITS_TO_BYTES(splitPacketChannelList[index]->firstPacket->dataBitLength); + memcpy(progressIndicator->data+sizeof(MessageID)+sizeof(unsigned int)*2, &temp, sizeof(unsigned int)); + + memcpy(progressIndicator->data+sizeof(MessageID)+sizeof(unsigned int)*3, splitPacketChannelList[index]->firstPacket->data, (size_t) BITS_TO_BYTES(splitPacketChannelList[index]->firstPacket->dataBitLength)); + outputQueue.Push(progressIndicator, __FILE__, __LINE__ ); + } +} + +//------------------------------------------------------------------------------------------------------- +// Take all split chunks with the specified splitPacketId and try to +//reconstruct a packet. If we can, allocate and return it. Otherwise return 0 +// Optimized version +//------------------------------------------------------------------------------------------------------- +InternalPacket * ReliabilityLayer::BuildPacketFromSplitPacketList( SplitPacketChannel *splitPacketChannel, CCTimeType time ) +{ + unsigned int j; + InternalPacket * internalPacket, *splitPacket; + int splitPacketPartLength; + + // Reconstruct + internalPacket = CreateInternalPacketCopy( splitPacketChannel->splitPacketList[0], 0, 0, time ); + internalPacket->dataBitLength=0; + for (j=0; j < splitPacketChannel->splitPacketList.Size(); j++) + internalPacket->dataBitLength+=splitPacketChannel->splitPacketList[j]->dataBitLength; + splitPacketPartLength=BITS_TO_BYTES(splitPacketChannel->firstPacket->dataBitLength); + + internalPacket->data = (unsigned char*) rakMalloc_Ex( (size_t) BITS_TO_BYTES( internalPacket->dataBitLength ), __FILE__, __LINE__ ); + + for (j=0; j < splitPacketChannel->splitPacketList.Size(); j++) + { + splitPacket=splitPacketChannel->splitPacketList[j]; + memcpy(internalPacket->data+splitPacket->splitPacketIndex*splitPacketPartLength, splitPacket->data, (size_t) BITS_TO_BYTES(splitPacketChannel->splitPacketList[j]->dataBitLength)); + } + + for (j=0; j < splitPacketChannel->splitPacketList.Size(); j++) + { + FreeInternalPacketData(splitPacketChannel->splitPacketList[j], __FILE__, __LINE__ ); + ReleaseToInternalPacketPool(splitPacketChannel->splitPacketList[j]); + } + RakNet::OP_DELETE(splitPacketChannel, __FILE__, __LINE__); + + return internalPacket; +} +//------------------------------------------------------------------------------------------------------- +InternalPacket * ReliabilityLayer::BuildPacketFromSplitPacketList( SplitPacketIdType splitPacketId, CCTimeType time, + SOCKET s, SystemAddress systemAddress, RakNetRandom *rnr, unsigned short remotePortRakNetWasStartedOn_PS3) +{ + unsigned int i; + bool objectExists; + SplitPacketChannel *splitPacketChannel; + InternalPacket * internalPacket; + + i=splitPacketChannelList.GetIndexFromKey(splitPacketId, &objectExists); + splitPacketChannel=splitPacketChannelList[i]; + if (splitPacketChannel->splitPacketList.Size()==splitPacketChannel->splitPacketList[0]->splitPacketCount) + { + // Ack immediately, because for large files this can take a long time + SendACKs(s, systemAddress, time, rnr, remotePortRakNetWasStartedOn_PS3); + internalPacket=BuildPacketFromSplitPacketList(splitPacketChannel,time); + splitPacketChannelList.RemoveAtIndex(i); + return internalPacket; + } + else + { + return 0; + } +} +/* +//------------------------------------------------------------------------------------------------------- +// Delete any unreliable split packets that have long since expired +void ReliabilityLayer::DeleteOldUnreliableSplitPackets( CCTimeType time ) +{ +unsigned i,j; +i=0; +while (i < splitPacketChannelList.Size()) +{ +#if CC_TIME_TYPE_BYTES==4 +if (time > splitPacketChannelList[i]->lastUpdateTime + timeoutTime && +#else +if (time > splitPacketChannelList[i]->lastUpdateTime + (CCTimeType)timeoutTime*(CCTimeType)1000 && +#endif +(splitPacketChannelList[i]->splitPacketList[0]->reliability==UNRELIABLE || splitPacketChannelList[i]->splitPacketList[0]->reliability==UNRELIABLE_SEQUENCED)) +{ +for (j=0; j < splitPacketChannelList[i]->splitPacketList.Size(); j++) +{ +RakNet::OP_DELETE_ARRAY(splitPacketChannelList[i]->splitPacketList[j]->data, __FILE__, __LINE__); +ReleaseToInternalPacketPool(splitPacketChannelList[i]->splitPacketList[j]); +} +RakNet::OP_DELETE(splitPacketChannelList[i], __FILE__, __LINE__); +splitPacketChannelList.RemoveAtIndex(i); +} +else +i++; +} +} +*/ + +//------------------------------------------------------------------------------------------------------- +// Creates a copy of the specified internal packet with data copied from the original starting at dataByteOffset for dataByteLength bytes. +// Does not copy any split data parameters as that information is always generated does not have any reason to be copied +//------------------------------------------------------------------------------------------------------- +InternalPacket * ReliabilityLayer::CreateInternalPacketCopy( InternalPacket *original, int dataByteOffset, int dataByteLength, CCTimeType time ) +{ + InternalPacket * copy = AllocateFromInternalPacketPool(); +#ifdef _DEBUG + // Remove accessing undefined memory error + memset( copy, 255, sizeof( InternalPacket ) ); +#endif + // Copy over our chunk of data + + if ( dataByteLength > 0 ) + { + AllocInternalPacketData(copy, BITS_TO_BYTES(dataByteLength ), __FILE__, __LINE__ ); + memcpy( copy->data, original->data + dataByteOffset, dataByteLength ); + } + else + copy->data = 0; + + copy->dataBitLength = dataByteLength << 3; + copy->creationTime = time; + copy->nextActionTime = 0; + copy->orderingIndex = original->orderingIndex; + copy->orderingChannel = original->orderingChannel; + copy->reliableMessageNumber = original->reliableMessageNumber; + copy->priority = original->priority; + copy->reliability = original->reliability; + + + return copy; +} + +//------------------------------------------------------------------------------------------------------- +// Get the specified ordering list +//------------------------------------------------------------------------------------------------------- +DataStructures::LinkedList *ReliabilityLayer::GetOrderingListAtOrderingStream( unsigned char orderingChannel ) +{ + if ( orderingChannel >= orderingList.Size() ) + return 0; + + return orderingList[ orderingChannel ]; +} + +//------------------------------------------------------------------------------------------------------- +// Add the internal packet to the ordering list in order based on order index +//------------------------------------------------------------------------------------------------------- +void ReliabilityLayer::AddToOrderingList( InternalPacket * internalPacket ) +{ +#ifdef _DEBUG + RakAssert( internalPacket->orderingChannel < NUMBER_OF_ORDERED_STREAMS ); +#endif + + if ( internalPacket->orderingChannel >= NUMBER_OF_ORDERED_STREAMS ) + { + return; + } + + DataStructures::LinkedList *theList; + + if ( internalPacket->orderingChannel >= orderingList.Size() || orderingList[ internalPacket->orderingChannel ] == 0 ) + { + // Need a linked list in this index + orderingList.Replace( RakNet::OP_NEW >(__FILE__,__LINE__) , 0, internalPacket->orderingChannel, __FILE__,__LINE__ ); + theList=orderingList[ internalPacket->orderingChannel ]; + } + else + { + // Have a linked list in this index + if ( orderingList[ internalPacket->orderingChannel ]->Size() == 0 ) + { + theList=orderingList[ internalPacket->orderingChannel ]; + } + else + { + theList = GetOrderingListAtOrderingStream( internalPacket->orderingChannel ); + } + } + + theList->End(); + theList->Add(internalPacket); +} + +//------------------------------------------------------------------------------------------------------- +// Inserts a packet into the resend list in order +//------------------------------------------------------------------------------------------------------- +void ReliabilityLayer::InsertPacketIntoResendList( InternalPacket *internalPacket, CCTimeType time, bool firstResend, bool modifyUnacknowledgedBytes ) +{ + (void) firstResend; + (void) time; + (void) internalPacket; + + AddToListTail(internalPacket, modifyUnacknowledgedBytes); + RakAssert(internalPacket->nextActionTime!=0); + +} + +//------------------------------------------------------------------------------------------------------- +// If Read returns -1 and this returns true then a modified packet was detected +//------------------------------------------------------------------------------------------------------- +bool ReliabilityLayer::IsCheater( void ) const +{ + return cheater; +} + +//------------------------------------------------------------------------------------------------------- +// Were you ever unable to deliver a packet despite retries? +//------------------------------------------------------------------------------------------------------- +bool ReliabilityLayer::IsDeadConnection( void ) const +{ + return deadConnection; +} + +//------------------------------------------------------------------------------------------------------- +// Causes IsDeadConnection to return true +//------------------------------------------------------------------------------------------------------- +void ReliabilityLayer::KillConnection( void ) +{ + deadConnection=true; +} + + +//------------------------------------------------------------------------------------------------------- +// Statistics +//------------------------------------------------------------------------------------------------------- +RakNetStatistics * const ReliabilityLayer::GetStatistics( RakNetStatistics *rns ) +{ + unsigned i; + RakNetTimeUS time = RakNet::GetTimeUS(); + uint64_t uint64Denominator; + double doubleDenominator; + + for (i=0; i < RNS_PER_SECOND_METRICS_COUNT; i++) + { + statistics.valueOverLastSecond[i]=bpsMetrics[i].GetBPS1(time); + statistics.runningTotal[i]=bpsMetrics[i].GetTotal1(); + } + + memcpy(rns, &statistics, sizeof(statistics)); + + if (rns->valueOverLastSecond[USER_MESSAGE_BYTES_SENT]+rns->valueOverLastSecond[USER_MESSAGE_BYTES_RESENT]>0) + rns->packetlossLastSecond=(float)((double) rns->valueOverLastSecond[USER_MESSAGE_BYTES_RESENT]/((double) rns->valueOverLastSecond[USER_MESSAGE_BYTES_SENT]+(double) rns->valueOverLastSecond[USER_MESSAGE_BYTES_RESENT])); + else + rns->packetlossLastSecond=0.0f; + + rns->packetlossTotal=0.0f; + uint64Denominator=(rns->runningTotal[USER_MESSAGE_BYTES_SENT]+rns->runningTotal[USER_MESSAGE_BYTES_RESENT]); + if (uint64Denominator!=0&&rns->runningTotal[USER_MESSAGE_BYTES_SENT]/uint64Denominator>0) + { + doubleDenominator=((double) rns->runningTotal[USER_MESSAGE_BYTES_SENT]+(double) rns->runningTotal[USER_MESSAGE_BYTES_RESENT]); + if(doubleDenominator!=0) + { + rns->packetlossTotal=(float)((double) rns->runningTotal[USER_MESSAGE_BYTES_RESENT]/doubleDenominator); + } + } + + return rns; +} + +//------------------------------------------------------------------------------------------------------- +// Returns the number of packets in the resend queue, not counting holes +//------------------------------------------------------------------------------------------------------- +unsigned int ReliabilityLayer::GetResendListDataSize(void) const +{ + // Not accurate but thread-safe. The commented version might crash if the queue is cleared while we loop through it + // return resendTree.Size(); + return statistics.messagesInResendBuffer; +} + +//------------------------------------------------------------------------------------------------------- +// Process threaded commands +//------------------------------------------------------------------------------------------------------- +void ReliabilityLayer::UpdateThreadedMemory(void) +{ + if ( freeThreadedMemoryOnNextUpdate ) + { + freeThreadedMemoryOnNextUpdate = false; + FreeThreadedMemory(); + } +} +//------------------------------------------------------------------------------------------------------- +bool ReliabilityLayer::AckTimeout(RakNetTimeMS curTime) +{ + return curTime-timeLastDatagramArrived>timeoutTime; + // + // #if CC_TIME_TYPE_BYTES==4 + // return curTime > timeResendQueueNonEmpty && timeResendQueueNonEmpty && curTime - timeResendQueueNonEmpty > timeoutTime; + // #else + // return curTime > timeResendQueueNonEmpty/(RakNetTimeUS)1000 && timeResendQueueNonEmpty && curTime - timeResendQueueNonEmpty/(RakNetTimeUS)1000 > timeoutTime; + // #endif +} +//------------------------------------------------------------------------------------------------------- +CCTimeType ReliabilityLayer::GetNextSendTime(void) const +{ + return nextSendTime; +} +//------------------------------------------------------------------------------------------------------- +CCTimeType ReliabilityLayer::GetTimeBetweenPackets(void) const +{ + return timeBetweenPackets; +} +//------------------------------------------------------------------------------------------------------- +CCTimeType ReliabilityLayer::GetAckPing(void) const +{ + return ackPing; +} +//------------------------------------------------------------------------------------------------------- +void ReliabilityLayer::ResetPacketsAndDatagrams(void) +{ + packetsToSendThisUpdate.Clear(true, __FILE__, __LINE__); + packetsToDeallocThisUpdate.Clear(true, __FILE__, __LINE__); + packetsToSendThisUpdateDatagramBoundaries.Clear(true, __FILE__, __LINE__); + datagramsToSendThisUpdateIsPair.Clear(true, __FILE__, __LINE__); + datagramSizesInBytes.Clear(true, __FILE__, __LINE__); + datagramSizeSoFar=0; +} +//------------------------------------------------------------------------------------------------------- +void ReliabilityLayer::PushPacket(CCTimeType time, InternalPacket *internalPacket, bool isReliable) +{ + BitSize_t bitsForThisPacket=BYTES_TO_BITS(BITS_TO_BYTES(internalPacket->dataBitLength)+BITS_TO_BYTES(internalPacket->headerLength)); + datagramSizeSoFar+=bitsForThisPacket; + RakAssert(BITS_TO_BYTES(datagramSizeSoFar)headerLength==GetMessageHeaderLengthBits(internalPacket)); +// This code tells me how much time elapses between when you send, and when the message actually goes out +// if (internalPacket->data[0]==0) +// { +// RakNetTime t; +// RakNet::BitStream bs(internalPacket->data+1,sizeof(t),false); +// bs.Read(t); +// RakNetTime curTime=RakNet::GetTime(); +// RakNetTime diff = curTime-t; +// } + + congestionManager.OnSendBytes(time, BITS_TO_BYTES(internalPacket->dataBitLength)+BITS_TO_BYTES(internalPacket->headerLength)); +} +//------------------------------------------------------------------------------------------------------- +void ReliabilityLayer::PushDatagram(void) +{ + if (datagramSizeSoFar>0) + { + packetsToSendThisUpdateDatagramBoundaries.Push(packetsToSendThisUpdate.Size(), __FILE__, __LINE__ ); + datagramsToSendThisUpdateIsPair.Push(false, __FILE__, __LINE__ ); + RakAssert(BITS_TO_BYTES(datagramSizeSoFar)=2) + { + datagramsToSendThisUpdateIsPair[datagramsToSendThisUpdateIsPair.Size()-2]=true; + datagramsToSendThisUpdateIsPair[datagramsToSendThisUpdateIsPair.Size()-1]=true; + return true; + } + return false; +} +//------------------------------------------------------------------------------------------------------- +void ReliabilityLayer::ClearPacketsAndDatagrams(bool keepInternalPacketIfNeedsAck) +{ + unsigned int i; + for (i=0; i < packetsToDeallocThisUpdate.Size(); i++) + { + // packetsToDeallocThisUpdate holds a boolean indicating if packetsToSendThisUpdate at this index should be freed + if (packetsToDeallocThisUpdate[i]) + { + RemoveFromUnreliableLinkedList(packetsToSendThisUpdate[i]); + FreeInternalPacketData(packetsToSendThisUpdate[i], __FILE__, __LINE__ ); + if (keepInternalPacketIfNeedsAck==false || packetsToSendThisUpdate[i]->reliabilityresendNext=internalPacket; + internalPacket->resendPrev=internalPacket; + resendLinkedListHead=internalPacket; + return; + } + internalPacket->resendPrev->resendNext = internalPacket->resendNext; + internalPacket->resendNext->resendPrev = internalPacket->resendPrev; + internalPacket->resendNext=resendLinkedListHead; + internalPacket->resendPrev=resendLinkedListHead->resendPrev; + internalPacket->resendPrev->resendNext=internalPacket; + resendLinkedListHead->resendPrev=internalPacket; + resendLinkedListHead=internalPacket; + RakAssert(internalPacket->headerLength+internalPacket->dataBitLength>0); + + ValidateResendList(); +} +//------------------------------------------------------------------------------------------------------- +void ReliabilityLayer::RemoveFromList(InternalPacket *internalPacket, bool modifyUnacknowledgedBytes) +{ + InternalPacket *newPosition; + internalPacket->resendPrev->resendNext = internalPacket->resendNext; + internalPacket->resendNext->resendPrev = internalPacket->resendPrev; + newPosition = internalPacket->resendNext; + if ( internalPacket == resendLinkedListHead ) + resendLinkedListHead = newPosition; + if (resendLinkedListHead==internalPacket) + resendLinkedListHead=0; + + if (modifyUnacknowledgedBytes) + { + RakAssert(unacknowledgedBytes>=BITS_TO_BYTES(internalPacket->headerLength+internalPacket->dataBitLength)); + unacknowledgedBytes-=BITS_TO_BYTES(internalPacket->headerLength+internalPacket->dataBitLength); + // printf("-unacknowledgedBytes:%i ", unacknowledgedBytes); + + + ValidateResendList(); + } +} +//------------------------------------------------------------------------------------------------------- +void ReliabilityLayer::AddToListTail(InternalPacket *internalPacket, bool modifyUnacknowledgedBytes) +{ + if (modifyUnacknowledgedBytes) + { + unacknowledgedBytes+=BITS_TO_BYTES(internalPacket->headerLength+internalPacket->dataBitLength); + // printf("+unacknowledgedBytes:%i ", unacknowledgedBytes); + } + + if (resendLinkedListHead==0) + { + internalPacket->resendNext=internalPacket; + internalPacket->resendPrev=internalPacket; + resendLinkedListHead=internalPacket; + return; + } + internalPacket->resendNext=resendLinkedListHead; + internalPacket->resendPrev=resendLinkedListHead->resendPrev; + internalPacket->resendPrev->resendNext=internalPacket; + resendLinkedListHead->resendPrev=internalPacket; + + ValidateResendList(); + +} +//------------------------------------------------------------------------------------------------------- +void ReliabilityLayer::PopListHead(bool modifyUnacknowledgedBytes) +{ + RakAssert(resendLinkedListHead!=0); + RemoveFromList(resendLinkedListHead, modifyUnacknowledgedBytes); +} +//------------------------------------------------------------------------------------------------------- +bool ReliabilityLayer::IsResendQueueEmpty(void) const +{ + return resendLinkedListHead==0; +} +//------------------------------------------------------------------------------------------------------- +void ReliabilityLayer::SendACKs(SOCKET s, SystemAddress systemAddress, CCTimeType time, RakNetRandom *rnr, unsigned short remotePortRakNetWasStartedOn_PS3) +{ + BitSize_t maxDatagramPayload = GetMaxDatagramSizeExcludingMessageHeaderBits(); + + while (acknowlegements.Size()>0) + { + // Send acks + updateBitStream.Reset(); + DatagramHeaderFormat dhf; + dhf.isACK=true; + dhf.isNAK=false; + dhf.isPacketPair=false; + dhf.sourceSystemTime=time; + double B; + double AS; + bool hasBAndAS; + if (remoteSystemNeedsBAndAS) + { + congestionManager.OnSendAckGetBAndAS(time, &hasBAndAS,&B,&AS); + dhf.AS=(float)AS; + dhf.hasBAndAS=hasBAndAS; + } + else + dhf.hasBAndAS=false; + dhf.sourceSystemTime=nextAckTimeToSend; + // dhf.B=(float)B; + updateBitStream.Reset(); + dhf.Serialize(&updateBitStream); + CC_DEBUG_PRINTF_1("AckSnd "); + acknowlegements.Serialize(&updateBitStream, maxDatagramPayload, true); + SendBitStream( s, systemAddress, &updateBitStream, rnr, remotePortRakNetWasStartedOn_PS3, time ); + congestionManager.OnSendAck(time,updateBitStream.GetNumberOfBytesUsed()); + + // I think this is causing a bug where if the estimated bandwidth is very low for the recipient, only acks ever get sent + // congestionManager.OnSendBytes(time,UDP_HEADER_SIZE+updateBitStream.GetNumberOfBytesUsed()); + } + + +} +/* +//------------------------------------------------------------------------------------------------------- +ReliabilityLayer::DatagramMessageIDList* ReliabilityLayer::AllocateFromDatagramMessageIDPool(void) +{ +DatagramMessageIDList*s; +s=datagramMessageIDPool.Allocate( __FILE__, __LINE__ ); +// Call new operator, memoryPool doesn't do this +s = new ((void*)s) DatagramMessageIDList; +return s; +} +//------------------------------------------------------------------------------------------------------- +void ReliabilityLayer::ReleaseToDatagramMessageIDPool(DatagramMessageIDList* d) +{ +d->~DatagramMessageIDList(); +datagramMessageIDPool.Release(d); +} +*/ +//------------------------------------------------------------------------------------------------------- +InternalPacket* ReliabilityLayer::AllocateFromInternalPacketPool(void) +{ + InternalPacket *ip = internalPacketPool.Allocate( __FILE__, __LINE__ ); + ip->reliableMessageNumber = (MessageNumberType) (const uint32_t)-1; + ip->messageNumberAssigned=false; + ip->nextActionTime = 0; + ip->splitPacketCount = 0; + ip->allocationScheme=InternalPacket::NORMAL; + ip->data=0; + return ip; +} +//------------------------------------------------------------------------------------------------------- +void ReliabilityLayer::ReleaseToInternalPacketPool(InternalPacket *ip) +{ + internalPacketPool.Release(ip, __FILE__,__LINE__); +} +//------------------------------------------------------------------------------------------------------- +void ReliabilityLayer::RemoveFromUnreliableLinkedList(InternalPacket *internalPacket) +{ + if (internalPacket->reliability==UNRELIABLE || + internalPacket->reliability==UNRELIABLE_SEQUENCED || + internalPacket->reliability==UNRELIABLE_WITH_ACK_RECEIPT +// || +// internalPacket->reliability==UNRELIABLE_SEQUENCED_WITH_ACK_RECEIPT + ) + { + InternalPacket *newPosition; + internalPacket->unreliablePrev->unreliableNext = internalPacket->unreliableNext; + internalPacket->unreliableNext->unreliablePrev = internalPacket->unreliablePrev; + newPosition = internalPacket->unreliableNext; + if ( internalPacket == unreliableLinkedListHead ) + unreliableLinkedListHead = newPosition; + if (unreliableLinkedListHead==internalPacket) + unreliableLinkedListHead=0; + } +} +//------------------------------------------------------------------------------------------------------- +void ReliabilityLayer::AddToUnreliableLinkedList(InternalPacket *internalPacket) +{ + if (internalPacket->reliability==UNRELIABLE || + internalPacket->reliability==UNRELIABLE_SEQUENCED || + internalPacket->reliability==UNRELIABLE_WITH_ACK_RECEIPT +// || +// internalPacket->reliability==UNRELIABLE_SEQUENCED_WITH_ACK_RECEIPT + ) + { + if (unreliableLinkedListHead==0) + { + internalPacket->unreliableNext=internalPacket; + internalPacket->unreliablePrev=internalPacket; + unreliableLinkedListHead=internalPacket; + return; + } + internalPacket->unreliableNext=unreliableLinkedListHead; + internalPacket->unreliablePrev=unreliableLinkedListHead->unreliablePrev; + internalPacket->unreliablePrev->unreliableNext=internalPacket; + unreliableLinkedListHead->unreliablePrev=internalPacket; + } +} +//------------------------------------------------------------------------------------------------------- +void ReliabilityLayer::ValidateResendList(void) const +{ + /* + unsigned int count1=0, count2=0; + for (unsigned int i=0; i < RESEND_BUFFER_ARRAY_LENGTH; i++) + if (resendBuffer[i]) + count1++; + + if (resendLinkedListHead) + { + InternalPacket *internalPacket = resendLinkedListHead; + do + { + count2++; + internalPacket=internalPacket->resendNext; + } while (internalPacket!=resendLinkedListHead); + } + RakAssert(count1==count2); + RakAssert(count2<=RESEND_BUFFER_ARRAY_LENGTH); + */ +} +//------------------------------------------------------------------------------------------------------- +bool ReliabilityLayer::ResendBufferOverflow(void) const +{ + int index1 = sendReliableMessageNumberIndex & (uint32_t) RESEND_BUFFER_ARRAY_MASK; + // int index2 = (sendReliableMessageNumberIndex+(uint32_t)1) & (uint32_t) RESEND_BUFFER_ARRAY_MASK; + RakAssert(index1= datagramHistory.Size()) + return 0; + return datagramHistory[offsetIntoList].head; +} +//------------------------------------------------------------------------------------------------------- +void ReliabilityLayer::RemoveFromDatagramHistory(DatagramSequenceNumberType index) +{ + DatagramSequenceNumberType offsetIntoList = index - datagramHistoryPopCount; + MessageNumberNode *mnm = datagramHistory[offsetIntoList].head; + MessageNumberNode *next; + while (mnm) + { + next=mnm->next; + datagramHistoryMessagePool.Release(mnm, __FILE__,__LINE__); + mnm=next; + } + datagramHistory[offsetIntoList].head=0; +} +//------------------------------------------------------------------------------------------------------- +void ReliabilityLayer::AddFirstToDatagramHistory(DatagramSequenceNumberType datagramNumber) +{ + (void) datagramNumber; + if (datagramHistory.Size()>DATAGRAM_MESSAGE_ID_ARRAY_LENGTH) + { + RemoveFromDatagramHistory(datagramHistoryPopCount); + datagramHistory.Pop(); + datagramHistoryPopCount++; + } + + datagramHistory.Push(DatagramHistoryNode(0), __FILE__,__LINE__); +} +//------------------------------------------------------------------------------------------------------- +ReliabilityLayer::MessageNumberNode* ReliabilityLayer::AddFirstToDatagramHistory(DatagramSequenceNumberType datagramNumber, DatagramSequenceNumberType messageNumber) +{ + (void) datagramNumber; +// RakAssert(datagramHistoryPopCount+(unsigned int) datagramHistory.Size()==datagramNumber); + if (datagramHistory.Size()>DATAGRAM_MESSAGE_ID_ARRAY_LENGTH) + { + RemoveFromDatagramHistory(datagramHistoryPopCount); + datagramHistory.Pop(); + datagramHistoryPopCount++; + } + + MessageNumberNode *mnm = datagramHistoryMessagePool.Allocate(__FILE__,__LINE__); + mnm->next=0; + mnm->messageNumber=messageNumber; + datagramHistory.Push(DatagramHistoryNode(mnm), __FILE__,__LINE__); + return mnm; +} +//------------------------------------------------------------------------------------------------------- +ReliabilityLayer::MessageNumberNode* ReliabilityLayer::AddSubsequentToDatagramHistory(MessageNumberNode *messageNumberNode, DatagramSequenceNumberType messageNumber) +{ + messageNumberNode->next=datagramHistoryMessagePool.Allocate(__FILE__,__LINE__); + messageNumberNode->next->messageNumber=messageNumber; + messageNumberNode->next->next=0; + return messageNumberNode->next; +} +//------------------------------------------------------------------------------------------------------- +void ReliabilityLayer::AllocInternalPacketData(InternalPacket *internalPacket, InternalPacketRefCountedData **refCounter, unsigned char *externallyAllocatedPtr, unsigned char *ourOffset) +{ + internalPacket->allocationScheme=InternalPacket::REF_COUNTED; + internalPacket->data=ourOffset; + if (*refCounter==0) + { + *refCounter = refCountedDataPool.Allocate(__FILE__,__LINE__); + // *refCounter = RakNet::OP_NEW(__FILE__,__LINE__); + (*refCounter)->refCount=1; + (*refCounter)->sharedDataBlock=externallyAllocatedPtr; + } + else + (*refCounter)->refCount++; + internalPacket->refCountedData=(*refCounter); +} +//------------------------------------------------------------------------------------------------------- +void ReliabilityLayer::AllocInternalPacketData(InternalPacket *internalPacket, unsigned char *externallyAllocatedPtr) +{ + internalPacket->allocationScheme=InternalPacket::NORMAL; + internalPacket->data=externallyAllocatedPtr; +} +//------------------------------------------------------------------------------------------------------- +void ReliabilityLayer::AllocInternalPacketData(InternalPacket *internalPacket, unsigned int numBytes, const char *file, unsigned int line) +{ + internalPacket->allocationScheme=InternalPacket::NORMAL; + internalPacket->data=(unsigned char*) rakMalloc_Ex(numBytes,file,line); +} +//------------------------------------------------------------------------------------------------------- +void ReliabilityLayer::FreeInternalPacketData(InternalPacket *internalPacket, const char *file, unsigned int line) +{ + if (internalPacket==0) + return; + + if (internalPacket->allocationScheme==InternalPacket::REF_COUNTED) + { + if (internalPacket->refCountedData==0) + return; + + internalPacket->refCountedData->refCount--; + if (internalPacket->refCountedData->refCount==0) + { + rakFree_Ex(internalPacket->refCountedData->sharedDataBlock, file, line ); + internalPacket->refCountedData->sharedDataBlock=0; + // RakNet::OP_DELETE(internalPacket->refCountedData,file, line); + refCountedDataPool.Release(internalPacket->refCountedData,file, line); + internalPacket->refCountedData=0; + } + } + else + { + if (internalPacket->data==0) + return; + + rakFree_Ex(internalPacket->data, file, line ); + internalPacket->data=0; + } +} +//------------------------------------------------------------------------------------------------------- +unsigned int ReliabilityLayer::GetMaxDatagramSizeExcludingMessageHeaderBytes(void) +{ + // When using encryption, the data may be padded by up to 15 bytes in order to be a multiple of 16. + // I don't know how many exactly, it depends on the datagram header serialization + if (encryptor.IsKeySet()) + return congestionManager.GetMTU() - DatagramHeaderFormat::GetDataHeaderByteLength() - 15; + else + return congestionManager.GetMTU() - DatagramHeaderFormat::GetDataHeaderByteLength(); +} +//------------------------------------------------------------------------------------------------------- +BitSize_t ReliabilityLayer::GetMaxDatagramSizeExcludingMessageHeaderBits(void) +{ + return BYTES_TO_BITS(GetMaxDatagramSizeExcludingMessageHeaderBytes()); +} +//------------------------------------------------------------------------------------------------------- +void ReliabilityLayer::InitHeapWeights(void) +{ + for (int priorityLevel=0; priorityLevel < NUMBER_OF_PRIORITIES; priorityLevel++) + { + outgoingPacketBufferNextWeights[priorityLevel]=(1<0) + { + uint64_t min = outgoingPacketBuffer.PeekWeight()+(1< +{ + CCTimeType lastUpdateTime; + + // This is here for progress notifications, since progress notifications return the first packet data, if available + InternalPacket *firstPacket; + + // DataStructures::OrderedList splitPacketList; + DataStructures::List splitPacketList; +}; +int RAK_DLL_EXPORT SplitPacketChannelComp( SplitPacketIdType const &key, SplitPacketChannel* const &data ); + +// Helper class +struct BPSTracker +{ + BPSTracker(); + ~BPSTracker(); + void Reset(const char *file, unsigned int line); + void Push1(CCTimeType time, uint64_t value1); +// void Push2(RakNetTimeUS time, uint64_t value1, uint64_t value2); + uint64_t GetBPS1(CCTimeType time); +// uint64_t GetBPS2(RakNetTimeUS time); +// void GetBPS1And2(RakNetTimeUS time, uint64_t &out1, uint64_t &out2); + uint64_t GetTotal1(void) const; +// uint64_t GetTotal2(void) const; + + struct TimeAndValue2 + { + TimeAndValue2(); + ~TimeAndValue2(); + TimeAndValue2(CCTimeType t, uint64_t v1); + // TimeAndValue2(RakNetTimeUS t, uint64_t v1, uint64_t v2); + // uint64_t value1, value2; + uint64_t value1; + CCTimeType time; + }; + + uint64_t total1, lastSec1; +// uint64_t total2, lastSec2; + DataStructures::Queue dataQueue; + void ClearExpired1(CCTimeType time); +// void ClearExpired2(RakNetTimeUS time); +}; + +/// Datagram reliable, ordered, unordered and sequenced sends. Flow control. Message splitting, reassembly, and coalescence. +class ReliabilityLayer// +{ +public: + + // Constructor + ReliabilityLayer(); + + // Destructor + ~ReliabilityLayer(); + + /// Resets the layer for reuse + void Reset( bool resetVariables, int MTUSize ); + + ///Sets the encryption key. Doing so will activate secure connections + /// \param[in] key Byte stream for the encryption key + void SetEncryptionKey( const unsigned char *key ); + + /// Set the time, in MS, to use before considering ourselves disconnected after not being able to deliver a reliable packet + /// Default time is 10,000 or 10 seconds in release and 30,000 or 30 seconds in debug. + /// \param[in] time Time, in MS + void SetTimeoutTime( RakNetTimeMS time ); + + /// Returns the value passed to SetTimeoutTime. or the default if it was never called + /// \param[out] the value passed to SetTimeoutTime + RakNetTimeMS GetTimeoutTime(void); + + /// Packets are read directly from the socket layer and skip the reliability layer because unconnected players do not use the reliability layer + /// This function takes packet data after a player has been confirmed as connected. + /// \param[in] buffer The socket data + /// \param[in] length The length of the socket data + /// \param[in] systemAddress The player that this data is from + /// \param[in] messageHandlerList A list of registered plugins + /// \param[in] MTUSize maximum datagram size + /// \retval true Success + /// \retval false Modified packet + bool HandleSocketReceiveFromConnectedPlayer( + const char *buffer, unsigned int length, SystemAddress systemAddress, DataStructures::List &messageHandlerList, int MTUSize, + SOCKET s, RakNetRandom *rnr, unsigned short remotePortRakNetWasStartedOn_PS3, CCTimeType timeRead); + + /// This allocates bytes and writes a user-level message to those bytes. + /// \param[out] data The message + /// \return Returns number of BITS put into the buffer + BitSize_t Receive( unsigned char**data ); + + /// Puts data on the send queue + /// \param[in] data The data to send + /// \param[in] numberOfBitsToSend The length of \a data in bits + /// \param[in] priority The priority level for the send + /// \param[in] reliability The reliability type for the send + /// \param[in] orderingChannel 0 to 31. Specifies what channel to use, for relational ordering and sequencing of packets. + /// \param[in] makeDataCopy If true \a data will be copied. Otherwise, only a pointer will be stored. + /// \param[in] MTUSize maximum datagram size + /// \param[in] currentTime Current time, as per RakNet::GetTime() + /// \param[in] receipt This number will be returned back with ID_SND_RECEIPT_ACKED or ID_SND_RECEIPT_LOSS and is only returned with the reliability types that contain RECEIPT in the name + /// \return True or false for success or failure. + bool Send( char *data, BitSize_t numberOfBitsToSend, PacketPriority priority, PacketReliability reliability, unsigned char orderingChannel, bool makeDataCopy, int MTUSize, CCTimeType currentTime, uint32_t receipt ); + + /// Call once per game cycle. Handles internal lists and actually does the send. + /// \param[in] s the communication end point + /// \param[in] systemAddress The Unique Player Identifier who shouldhave sent some packets + /// \param[in] MTUSize maximum datagram size + /// \param[in] time current system time + /// \param[in] maxBitsPerSecond if non-zero, enforces that outgoing bandwidth does not exceed this amount + /// \param[in] messageHandlerList A list of registered plugins + void Update( SOCKET s, SystemAddress systemAddress, int MTUSize, CCTimeType time, + unsigned bitsPerSecondLimit, + DataStructures::List &messageHandlerList, + RakNetRandom *rnr, unsigned short remotePortRakNetWasStartedOn_PS3); + + + /// If Read returns -1 and this returns true then a modified packetwas detected + /// \return true when a modified packet is detected + bool IsCheater( void ) const; + + /// Were you ever unable to deliver a packet despite retries? + /// \return true means the connection has been lost. Otherwise not. + bool IsDeadConnection( void ) const; + + /// Causes IsDeadConnection to return true + void KillConnection(void); + + /// Get Statistics + /// \return A pointer to a static struct, filled out with current statistical information. + RakNetStatistics * const GetStatistics( RakNetStatistics *rns ); + + ///Are we waiting for any data to be sent out or be processed by the player? + bool IsOutgoingDataWaiting(void); + bool AreAcksWaiting(void); + + // Set outgoing lag and packet loss properties + void ApplyNetworkSimulator( double _maxSendBPS, RakNetTime _minExtraPing, RakNetTime _extraPingVariance ); + + /// Returns if you previously called ApplyNetworkSimulator + /// \return If you previously called ApplyNetworkSimulator + bool IsNetworkSimulatorActive( void ); + + void SetSplitMessageProgressInterval(int interval); + void SetUnreliableTimeout(RakNetTimeMS timeoutMS); + /// Has a lot of time passed since the last ack + bool AckTimeout(RakNetTimeMS curTime); + CCTimeType GetNextSendTime(void) const; + CCTimeType GetTimeBetweenPackets(void) const; + CCTimeType GetAckPing(void) const; + RakNetTimeMS GetTimeLastDatagramArrived(void) const {return timeLastDatagramArrived;} + + // If true, will update time between packets quickly based on ping calculations + //void SetDoFastThroughputReactions(bool fast); + + // Encoded as numMessages[unsigned int], message1BitLength[unsigned int], message1Data (aligned), ... + //void GetUndeliveredMessages(RakNet::BitStream *messages, int MTUSize); + +private: + /// Send the contents of a bitstream to the socket + /// \param[in] s The socket used for sending data + /// \param[in] systemAddress The address and port to send to + /// \param[in] bitStream The data to send. + void SendBitStream( SOCKET s, SystemAddress systemAddress, RakNet::BitStream *bitStream, RakNetRandom *rnr, unsigned short remotePortRakNetWasStartedOn_PS3, CCTimeType currentTime); + + ///Parse an internalPacket and create a bitstream to represent this data + /// \return Returns number of bits used + BitSize_t WriteToBitStreamFromInternalPacket( RakNet::BitStream *bitStream, const InternalPacket *const internalPacket, CCTimeType curTime ); + + + /// Parse a bitstream and create an internal packet to represent this data + InternalPacket* CreateInternalPacketFromBitStream( RakNet::BitStream *bitStream, CCTimeType time ); + + /// Does what the function name says + unsigned RemovePacketFromResendListAndDeleteOlderReliableSequenced( const MessageNumberType messageNumber, CCTimeType time, DataStructures::List &messageHandlerList, SystemAddress systemAddress ); + + /// Acknowledge receipt of the packet with the specified messageNumber + void SendAcknowledgementPacket( const DatagramSequenceNumberType messageNumber, CCTimeType time); + + /// This will return true if we should not send at this time + bool IsSendThrottled( int MTUSize ); + + /// We lost a packet + void UpdateWindowFromPacketloss( CCTimeType time ); + + /// Increase the window size + void UpdateWindowFromAck( CCTimeType time ); + + /// Parse an internalPacket and figure out how many header bits would be written. Returns that number + BitSize_t GetMaxMessageHeaderLengthBits( void ); + BitSize_t GetMessageHeaderLengthBits( const InternalPacket *const internalPacket ); + + /// Get the SHA1 code + void GetSHA1( unsigned char * const buffer, unsigned int nbytes, char code[ SHA1_LENGTH ] ); + + /// Check the SHA1 code + bool CheckSHA1( char code[ SHA1_LENGTH ], unsigned char * const buffer, unsigned int nbytes ); + + /// Search the specified list for sequenced packets on the specified ordering channel, optionally skipping those with splitPacketId, and delete them + void DeleteSequencedPacketsInList( unsigned char orderingChannel, DataStructures::List&theList, int splitPacketId = -1 ); + + /// Search the specified list for sequenced packets with a value less than orderingIndex and delete them + void DeleteSequencedPacketsInList( unsigned char orderingChannel, DataStructures::Queue&theList ); + + /// Returns true if newPacketOrderingIndex is older than the waitingForPacketOrderingIndex + bool IsOlderOrderedPacket( OrderingIndexType newPacketOrderingIndex, OrderingIndexType waitingForPacketOrderingIndex ); + + /// Split the passed packet into chunks under MTU_SIZE bytes (including headers) and save those new chunks + void SplitPacket( InternalPacket *internalPacket ); + + /// Insert a packet into the split packet list + void InsertIntoSplitPacketList( InternalPacket * internalPacket, CCTimeType time ); + + /// Take all split chunks with the specified splitPacketId and try to reconstruct a packet. If we can, allocate and return it. Otherwise return 0 + InternalPacket * BuildPacketFromSplitPacketList( SplitPacketIdType splitPacketId, CCTimeType time, + SOCKET s, SystemAddress systemAddress, RakNetRandom *rnr, unsigned short remotePortRakNetWasStartedOn_PS3); + InternalPacket * BuildPacketFromSplitPacketList( SplitPacketChannel *splitPacketChannel, CCTimeType time ); + + /// Delete any unreliable split packets that have long since expired + //void DeleteOldUnreliableSplitPackets( CCTimeType time ); + + /// Creates a copy of the specified internal packet with data copied from the original starting at dataByteOffset for dataByteLength bytes. + /// Does not copy any split data parameters as that information is always generated does not have any reason to be copied + InternalPacket * CreateInternalPacketCopy( InternalPacket *original, int dataByteOffset, int dataByteLength, CCTimeType time ); + + /// Get the specified ordering list + DataStructures::LinkedList *GetOrderingListAtOrderingStream( unsigned char orderingChannel ); + + /// Add the internal packet to the ordering list in order based on order index + void AddToOrderingList( InternalPacket * internalPacket ); + + /// Inserts a packet into the resend list in order + void InsertPacketIntoResendList( InternalPacket *internalPacket, CCTimeType time, bool firstResend, bool modifyUnacknowledgedBytes ); + + /// Memory handling + void FreeMemory( bool freeAllImmediately ); + + /// Memory handling + void FreeThreadedMemory( void ); + + /// Memory handling + void FreeThreadSafeMemory( void ); + + // Initialize the variables + void InitializeVariables( int MTUSize ); + + /// Given the current time, is this time so old that we should consider it a timeout? + bool IsExpiredTime(unsigned int input, CCTimeType currentTime) const; + + // Make it so we don't do resends within a minimum threshold of time + void UpdateNextActionTime(void); + + + /// Does this packet number represent a packet that was skipped (out of order?) + //unsigned int IsReceivedPacketHole(unsigned int input, RakNetTime currentTime) const; + + /// Skip an element in the received packets list + //unsigned int MakeReceivedPacketHole(unsigned int input) const; + + /// How many elements are waiting to be resent? + unsigned int GetResendListDataSize(void) const; + + /// Update all memory which is not threadsafe + void UpdateThreadedMemory(void); + + void CalculateHistogramAckSize(void); + + // Used ONLY for RELIABLE_ORDERED + // RELIABLE_SEQUENCED just returns the newest one + DataStructures::List*> orderingList; + DataStructures::Queue outputQueue; + int splitMessageProgressInterval; + CCTimeType unreliableTimeout; + + struct MessageNumberNode + { + DatagramSequenceNumberType messageNumber; + MessageNumberNode *next; + }; + struct DatagramHistoryNode + { + DatagramHistoryNode() {} + DatagramHistoryNode(MessageNumberNode *_head) : + head(_head) {} + MessageNumberNode *head; + }; + // Queue length is programmatically restricted to DATAGRAM_MESSAGE_ID_ARRAY_LENGTH + // This is essentially an O(1) lookup to get a DatagramHistoryNode given an index + DataStructures::Queue datagramHistory; + DataStructures::MemoryPool datagramHistoryMessagePool; + + + void RemoveFromDatagramHistory(DatagramSequenceNumberType index); + MessageNumberNode* GetMessageNumberNodeByDatagramIndex(DatagramSequenceNumberType index); + void AddFirstToDatagramHistory(DatagramSequenceNumberType datagramNumber); + MessageNumberNode* AddFirstToDatagramHistory(DatagramSequenceNumberType datagramNumber, DatagramSequenceNumberType messageNumber); + MessageNumberNode* AddSubsequentToDatagramHistory(MessageNumberNode *messageNumberNode, DatagramSequenceNumberType messageNumber); + DatagramSequenceNumberType datagramHistoryPopCount; + + DataStructures::MemoryPool internalPacketPool; + // DataStructures::BPlusTree resendTree; + InternalPacket *resendBuffer[RESEND_BUFFER_ARRAY_LENGTH]; + InternalPacket *resendLinkedListHead; + InternalPacket *unreliableLinkedListHead; + void RemoveFromUnreliableLinkedList(InternalPacket *internalPacket); + void AddToUnreliableLinkedList(InternalPacket *internalPacket); +// unsigned int numPacketsOnResendBuffer; + //unsigned int blockWindowIncreaseUntilTime; + // DataStructures::RangeList acknowlegements; + // Resend list is a tree of packets we need to resend + + // Set to the current time when the resend queue is no longer empty + // Set to zero when it becomes empty + // Set to the current time if it is not zero, and we get incoming data + // If the current time - timeResendQueueNonEmpty is greater than a threshold, we are disconnected +// CCTimeType timeResendQueueNonEmpty; + RakNetTimeMS timeLastDatagramArrived; + + + // If we backoff due to packetloss, don't remeasure until all waiting resends have gone out or else we overcount +// bool packetlossThisSample; +// int backoffThisSample; +// unsigned packetlossThisSampleResendCount; +// CCTimeType lastPacketlossTime; + + //DataStructures::Queue sendPacketSet[ NUMBER_OF_PRIORITIES ]; + DataStructures::Heap outgoingPacketBuffer; + reliabilityHeapWeightType outgoingPacketBufferNextWeights[NUMBER_OF_PRIORITIES]; + void InitHeapWeights(void); + reliabilityHeapWeightType GetNextWeight(int priorityLevel); +// unsigned int messageInSendBuffer[NUMBER_OF_PRIORITIES]; +// double bytesInSendBuffer[NUMBER_OF_PRIORITIES]; + + + DataStructures::OrderedList splitPacketChannelList; + + MessageNumberType sendReliableMessageNumberIndex; + MessageNumberType internalOrderIndex; + //unsigned int windowSize; + RakNet::BitStream updateBitStream; + OrderingIndexType waitingForOrderedPacketWriteIndex[ NUMBER_OF_ORDERED_STREAMS ], waitingForSequencedPacketWriteIndex[ NUMBER_OF_ORDERED_STREAMS ]; + + // STUFF TO NOT MUTEX HERE (called from non-conflicting threads, or value is not important) + OrderingIndexType waitingForOrderedPacketReadIndex[ NUMBER_OF_ORDERED_STREAMS ], waitingForSequencedPacketReadIndex[ NUMBER_OF_ORDERED_STREAMS ]; + bool deadConnection, cheater; + // unsigned int lastPacketSendTime,retransmittedFrames, sentPackets, sentFrames, receivedPacketsCount, bytesSent, bytesReceived,lastPacketReceivedTime; + SplitPacketIdType splitPacketId; + RakNetTimeMS timeoutTime; // How long to wait in MS before timing someone out + //int MAX_AVERAGE_PACKETS_PER_SECOND; // Name says it all +// int RECEIVED_PACKET_LOG_LENGTH, requestedReceivedPacketLogLength; // How big the receivedPackets array is +// unsigned int *receivedPackets; + RakNetStatistics statistics; + +// CCTimeType histogramStart; +// unsigned histogramBitsSent; + + + /// Memory-efficient receivedPackets algorithm: + /// receivedPacketsBaseIndex is the packet number we are expecting + /// Everything under receivedPacketsBaseIndex is a packet we already got + /// Everything over receivedPacketsBaseIndex is stored in hasReceivedPacketQueue + /// It stores the time to stop waiting for a particular packet number, where the packet number is receivedPacketsBaseIndex + the index into the queue + /// If 0, we got got that packet. Otherwise, the time to give up waiting for that packet. + /// If we get a packet number where (receivedPacketsBaseIndex-packetNumber) is less than half the range of receivedPacketsBaseIndex then it is a duplicate + /// Otherwise, it is a duplicate packet (and ignore it). + // DataStructures::Queue hasReceivedPacketQueue; + DataStructures::Queue hasReceivedPacketQueue; + DatagramSequenceNumberType receivedPacketsBaseIndex; + bool resetReceivedPackets; + + CCTimeType lastUpdateTime; + CCTimeType timeBetweenPackets, nextSendTime, ackPing; +// CCTimeType ackPingSamples[ACK_PING_SAMPLES_SIZE]; // Must be range of unsigned char to wrap ackPingIndex properly + CCTimeType ackPingSum; + unsigned char ackPingIndex; + //CCTimeType nextLowestPingReset; + RemoteSystemTimeType remoteSystemTime; +// bool continuousSend; +// CCTimeType lastTimeBetweenPacketsIncrease,lastTimeBetweenPacketsDecrease; + // Limit changes in throughput to once per ping - otherwise even if lag starts we don't know about it + // In the meantime the connection is flooded and overrun. + CCTimeType nextAllowedThroughputSample; + bool bandwidthExceededStatistic; + + // If Update::maxBitsPerSecond > 0, then throughputCapCountdown is used as a timer to prevent sends for some amount of time after each send, depending on + // the amount of data sent + long long throughputCapCountdown; + + DataBlockEncryptor encryptor; + unsigned receivePacketCount; + + ///This variable is so that free memory can be called by only the update thread so we don't have to mutex things so much + bool freeThreadedMemoryOnNextUpdate; + + //long double timeBetweenPacketsIncreaseMultiplier, timeBetweenPacketsDecreaseMultiplier; + +#ifdef _DEBUG + struct DataAndTime// + { + SOCKET s; + char data[ MAXIMUM_MTU_SIZE ]; + unsigned int length; + RakNetTimeMS sendTime; + // SystemAddress systemAddress; + unsigned short remotePortRakNetWasStartedOn_PS3; + }; + DataStructures::Queue delayList; + + // Internet simulator + double packetloss; + RakNetTimeMS minExtraPing, extraPingVariance; +#endif + + CCTimeType elapsedTimeSinceLastUpdate; + + CCTimeType nextAckTimeToSend; + + RakNet::CCRakNetUDT congestionManager; + uint32_t unacknowledgedBytes; + + bool ResendBufferOverflow(void) const; + void ValidateResendList(void) const; + void ResetPacketsAndDatagrams(void); + void PushPacket(CCTimeType time, InternalPacket *internalPacket, bool isReliable); + void PushDatagram(void); + bool TagMostRecentPushAsSecondOfPacketPair(void); + void ClearPacketsAndDatagrams(bool releaseToInternalPacketPoolIfNeedsAck); + void MoveToListHead(InternalPacket *internalPacket); + void RemoveFromList(InternalPacket *internalPacket, bool modifyUnacknowledgedBytes); + void AddToListTail(InternalPacket *internalPacket, bool modifyUnacknowledgedBytes); + void PopListHead(bool modifyUnacknowledgedBytes); + bool IsResendQueueEmpty(void) const; + void SortSplitPacketList(DataStructures::List &data, unsigned int leftEdge, unsigned int rightEdge) const; + void SendACKs(SOCKET s, SystemAddress systemAddress, CCTimeType time, RakNetRandom *rnr, unsigned short remotePortRakNetWasStartedOn_PS3); + + DataStructures::List packetsToSendThisUpdate; + DataStructures::List packetsToDeallocThisUpdate; + // boundary is in packetsToSendThisUpdate, inclusive + DataStructures::List packetsToSendThisUpdateDatagramBoundaries; + DataStructures::List datagramsToSendThisUpdateIsPair; + DataStructures::List datagramSizesInBytes; + BitSize_t datagramSizeSoFar; + BitSize_t allDatagramSizesSoFar; + double totalUserDataBytesAcked; + CCTimeType timeOfLastContinualSend; + CCTimeType timeToNextUnreliableCull; + + // This doesn't need to be a member, but I do it to avoid reallocations + DataStructures::RangeList incomingAcks; + + // Every 16 datagrams, we make sure the 17th datagram goes out the same update tick, and is the same size as the 16th + int countdownToNextPacketPair; + InternalPacket* AllocateFromInternalPacketPool(void); + void ReleaseToInternalPacketPool(InternalPacket *ip); + + DataStructures::RangeList acknowlegements; + DataStructures::RangeList NAKs; + bool remoteSystemNeedsBAndAS; + + unsigned int GetMaxDatagramSizeExcludingMessageHeaderBytes(void); + BitSize_t GetMaxDatagramSizeExcludingMessageHeaderBits(void); + + // ourOffset refers to a section within externallyAllocatedPtr. Do not deallocate externallyAllocatedPtr until all references are lost + void AllocInternalPacketData(InternalPacket *internalPacket, InternalPacketRefCountedData **refCounter, unsigned char *externallyAllocatedPtr, unsigned char *ourOffset); + // Set the data pointer to externallyAllocatedPtr, do not allocate + void AllocInternalPacketData(InternalPacket *internalPacket, unsigned char *externallyAllocatedPtr); + // Allocate new + void AllocInternalPacketData(InternalPacket *internalPacket, unsigned int numBytes, const char *file, unsigned int line); + void FreeInternalPacketData(InternalPacket *internalPacket, const char *file, unsigned int line); + DataStructures::MemoryPool refCountedDataPool; + + BPSTracker bpsMetrics[RNS_PER_SECOND_METRICS_COUNT]; +}; + +#endif diff --git a/RakNet/Sources/Replica.h b/RakNet/Sources/Replica.h new file mode 100644 index 0000000..f9fe189 --- /dev/null +++ b/RakNet/Sources/Replica.h @@ -0,0 +1,105 @@ +/// \file +/// \brief Contains interface Replica used by the ReplicaManager. +/// +/// This file is part of RakNet Copyright 2003 Jenkins Software LLC +/// +/// Usage of RakNet is subject to the appropriate license agreement. + + +#ifndef __REPLICA_H +#define __REPLICA_H + +#include "NetworkIDObject.h" +#include "PacketPriority.h" +#include "ReplicaEnums.h" + +/// This is an interface of a replicated object for use in the framework of ReplicaManager +/// You should derive from this class, implementing the functions to provide the behavior you want. +/// If your architecture doesn't allow you to derive from this class, you can store an instance of a derived instance of this class in your base game object. +/// In that case, use GetParent() and SetParent() and propagate the function calls up to your real classes. For an example where I do this, see Monster.h in the ReplicaManagerCS sample. +/// \note All send functions are called one for every target recipient, so you can customize the data sent per-user. +/// \brief The interface to derive your game's networked classes from +/// \ingroup REPLICA_MANAGER_GROUP +class Replica : public NetworkIDObject +{ +public: + /// This function is called in the first update tick after this object is first passed to ReplicaManager::Replicate for each player, and also when a new participant joins + /// The intent of this function is to tell another system to create an instance of this class. + /// You can do this however you want - I recommend having each class having a string identifier which is registered with StringTable, and then using EncodeString to write the name of the class. In the callback passed to SetReceiveConstructionCB create an instance of the object based on that string. + /// \note If you return true from IsNetworkIDAuthority, which you should do for a server or peer, I recommend encoding the value returned by GetNetworkID() into your bitstream and calling SetNetworkID with that value in your SetReceiveConstructionCB callback. + /// \note Dereplicate, SetScope, and SignalSerializeNeeded all rely on being able to call GET_OBJECT_FROM_ID which requires that SetNetworkID be called on that object. + /// \note SendConstruction is called once for every new player that connects and every existing player when an object is passed to Replicate. + /// \param[in] currentTime The current time that would be returned by RakNet::GetTime(). That's a slow call I do already, so you can use the parameter instead of having to call it yourself. + /// \param[in] systemAddress The participant to send to. + /// \param[in,out] flags Per-object per-system serialization flags modified by this function, ReplicaManager::SignalSerializationFlags, and ReplicaManager::AccessSerializationFlags. Useful for simple customization of what you serialize based on application events. This value is not automatically reset. + /// \param[out] outBitStream The data you want to write in the message. If you do not write to outBitStream and return true, then no send call will occur and the system will consider this object as not created on that remote system. + /// \param[out] includeTimestamp Set to true to include a timestamp with the message. This will be reflected in the timestamp parameter of the callback. Defaults to false. + /// \return See ReplicaReturnResult + virtual ReplicaReturnResult SendConstruction( RakNetTime currentTime, SystemAddress systemAddress, unsigned int &flags, RakNet::BitStream *outBitStream, bool *includeTimestamp )=0; + + /// The purpose of the function is to send a packet containing the data in \a outBitStream to \a systemAddress telling that system that Dereplicate was called. + /// In the code, this is called in the update cycle after you call ReplicaManager::Destruct(). Then, if you write to outBitStream, a message is sent to that participant. + /// This is the one send you cannot delay because objects may be deleted and we can't read into them past this. + /// \param[out] outBitStream The data to send. If you do not write to outBitStream, then no send call will occur + /// \param[in] systemAddress The participant to send to. + /// \param[out] includeTimestamp Set to true to include a timestamp with the message. This will be reflected in the timestamp parameter of the callback. Defaults to false. + /// \return See ReplicaReturnResult + virtual ReplicaReturnResult SendDestruction(RakNet::BitStream *outBitStream, SystemAddress systemAddress, bool *includeTimestamp )=0; + + /// This function is called when SendDestruction is sent from another system. Delete your object if you want. + /// \param[in] inBitStream What was sent in SendDestruction::outBitStream + /// \param[in] systemAddress The participant that sent this message to us. + /// \param[in] timestamp if Serialize::SendDestruction was set to true, the time the packet was sent. + /// \return See ReplicaReturnResult. Only REPLICA_PROCESSING_DONE is valid, and will send the destruction message. Anything else will not send any messages. + virtual ReplicaReturnResult ReceiveDestruction(RakNet::BitStream *inBitStream, SystemAddress systemAddress, RakNetTime timestamp)=0; + + /// Called when ReplicaManager::SetScope is called with a different value than what it currently has. + /// It is up to you to write \a inScope to \a outBitStream. Not doing so, and returning true, means you want to cancel the scope change call. + /// If \a inScope is true, you return true, and data is written to outBitStream, then Serialize will be called automatically + /// This is a convenience feature, since there's almost no case where an object would go in scope but not be serialized + /// \param[in] inScope The new scope that will be sent to ReceiveScopeChange that originally came from SetScope + /// \param[out] outBitStream The data to send. If you do not write to outBitStream and return true, then no send will occur and the object will keep its existing scope + /// \param[in] currentTime The current time that would be returned by RakNet::GetTime(). That's a slow call I do already, so you can use the parameter instead of having to call it yourself. + /// \param[in] systemAddress The participant to send to. + /// \param[out] includeTimestamp Set to true to include a timestamp with the message. This will be reflected in the timestamp parameter of the callback. Defaults to false. + /// \return See ReplicaReturnResult + virtual ReplicaReturnResult SendScopeChange(bool inScope, RakNet::BitStream *outBitStream, RakNetTime currentTime, SystemAddress systemAddress, bool *includeTimestamp )=0; + + /// Called when when we get the SendScopeChange message. The new scope should have been encoded (by you) into \a inBitStream + /// \param[in] inBitStream What was sent in SendScopeChange::outBitStream + /// \param[in] systemAddress The participant that sent this message to us. + /// \param[in] timestamp if Serialize::SendScopeChange was set to true, the time the packet was sent. + /// \return See ReplicaReturnResult + virtual ReplicaReturnResult ReceiveScopeChange(RakNet::BitStream *inBitStream,SystemAddress systemAddress, RakNetTime timestamp)=0; + + /// Called when ReplicaManager::SignalSerializeNeeded is called with this object as the parameter. + /// The system will ensure that Serialize only occurs for participants that have this object constructed and in scope + /// The intent of this function is to serialize all your class member variables for remote transmission. + /// \param[out] sendTimestamp Set to true to include a timestamp with the message. This will be reflected in the timestamp parameter Deserialize. Defaults to false. + /// \param[out] outBitStream The data you want to write in the message. If you do not write to outBitStream and return true, then no send will occur for this participant. + /// \param[in] lastSendTime The last time Serialize returned true and outBitStream was written to. 0 if this is the first time the function has ever been called for this \a systemAddress + /// \param[in] priority Passed to RakPeer::Send for the send call. + /// \param[in] reliability Passed to RakPeer::Send for the send call. + /// \param[in] currentTime The current time that would be returned by RakNet::GetTime(). That's a slow call I do already, so you can use the parameter instead of having to call it yourself. + /// \param[in] systemAddress The participant we are sending to. + /// \param[in,out] flags Per-object per-system serialization flags modified by this function, ReplicaManager::SignalSerializationFlags, and ReplicaManager::AccessSerializationFlags. Useful for simple customization of what you serialize based on application events. This value is not automatically reset. + /// \return See ReplicaReturnResult + virtual ReplicaReturnResult Serialize(bool *sendTimestamp, RakNet::BitStream *outBitStream, RakNetTime lastSendTime, PacketPriority *priority, PacketReliability *reliability, RakNetTime currentTime, SystemAddress systemAddress, unsigned int &flags)=0; + + /// Called when another participant called Serialize with our system as the target + /// \param[in] inBitStream What was written to Serialize::outBitStream + /// \param[in] timestamp if Serialize::SendTimestamp was set to true, the time the packet was sent. + /// \param[in] lastDeserializeTime Last time you returned true from this function for this object, or 0 if never, regardless of \a systemAddress. + /// \param[in] systemAddress The participant that sent this message to us. + virtual ReplicaReturnResult Deserialize(RakNet::BitStream *inBitStream, RakNetTime timestamp, RakNetTime lastDeserializeTime, SystemAddress systemAddress )=0; + + /// Used to sort the order that commands (construct, serialize) take place in. + /// Lower sort priority commands happen before higher sort priority commands. + /// Same sort priority commands take place in random order. + /// For example, if both players and player lists are replicas, you would want to create the players before the player lists if the player lists refer to the players. + /// So you could specify the players as priority 0, and the lists as priority 1, and the players would be created and serialized first + /// \return A higher value to process later, a lower value to process sooner, the same value to process in random order. + virtual int GetSortPriority(void) const=0; +}; + +#endif diff --git a/RakNet/Sources/ReplicaEnums.h b/RakNet/Sources/ReplicaEnums.h new file mode 100644 index 0000000..7b0b4a0 --- /dev/null +++ b/RakNet/Sources/ReplicaEnums.h @@ -0,0 +1,43 @@ +/// \file +/// \brief Contains enumerations used by the ReplicaManager system. This file is a lightweight header, so you can include it without worrying about linking in lots of other crap +/// +/// This file is part of RakNet Copyright 2003 Jenkins Software LLC +/// +/// Usage of RakNet is subject to the appropriate license agreement. + + +#ifndef __REPLICA_ENUMS_H +#define __REPLICA_ENUMS_H + +/// Replica interface flags, used to enable and disable function calls on the Replica object +/// Passed to ReplicaManager::EnableReplicaInterfaces and ReplicaManager::DisableReplicaInterfaces +enum +{ + REPLICA_RECEIVE_DESTRUCTION=1<<0, + REPLICA_RECEIVE_SERIALIZE=1<<1, + REPLICA_RECEIVE_SCOPE_CHANGE=1<<2, + REPLICA_SEND_CONSTRUCTION=1<<3, + REPLICA_SEND_DESTRUCTION=1<<4, + REPLICA_SEND_SCOPE_CHANGE=1<<5, + REPLICA_SEND_SERIALIZE=1<<6, + REPLICA_SET_ALL = 0xFF // Allow all of the above +}; + +enum ReplicaReturnResult +{ + /// This means call the function again later, with the same parameters + REPLICA_PROCESS_LATER, + /// This means we are done processing (the normal result to return) + REPLICA_PROCESSING_DONE, + /// This means cancel the processing - don't send any network messages and don't change the current state. + REPLICA_CANCEL_PROCESS, + /// Same as REPLICA_PROCESSING_DONE, where a message is sent, but does not clear the send bit. + /// Useful for multi-part sends with different reliability levels. + /// Only currently used by Replica::Serialize + REPLICA_PROCESS_AGAIN, + /// Only returned from the Replica::SendConstruction interface, means act as if the other system had this object but don't actually + /// Send a construction packet. This way you will still send scope and serialize packets to that system + REPLICA_PROCESS_IMPLICIT +}; + +#endif diff --git a/RakNet/Sources/ReplicaManager.cpp b/RakNet/Sources/ReplicaManager.cpp new file mode 100644 index 0000000..5ffed75 --- /dev/null +++ b/RakNet/Sources/ReplicaManager.cpp @@ -0,0 +1,1285 @@ +#include "NativeFeatureIncludes.h" +#if _RAKNET_SUPPORT_ReplicaManager==1 + +#include "ReplicaManager.h" +#include "RakPeerInterface.h" +#include "GetTime.h" +#include "MessageIdentifiers.h" +#include "BitStream.h" +#include "Replica.h" +#if !defined(_PS3) && !defined(__PS3__) && !defined(SN_TARGET_PS3) +#include +#endif +#include "RakAssert.h" +#include // For my debug printfs +#include "RakAssert.h" +#include "NetworkIDManager.h" + +#ifdef _MSC_VER +#pragma warning( push ) +#endif + +int ReplicaManager::CommandStructComp( Replica* const &key, const ReplicaManager::CommandStruct &data ) +{ + if (key->GetSortPriority() < data.replica->GetSortPriority()) + return -1; + else if (key->GetSortPriority() > data.replica->GetSortPriority()) + return 1; + if (key->GetAllocationNumber() < data.replica->GetAllocationNumber()) + return -1; + if (key->GetAllocationNumber()==data.replica->GetAllocationNumber()) + return 0; + return 1; +} + +int ReplicaManager::RegisteredReplicaComp( Replica* const &key, const ReplicaManager::RegisteredReplica &data ) +{ + if (key->GetAllocationNumber() < data.replica->GetAllocationNumber()) + return -1; + if (key->GetAllocationNumber()==data.replica->GetAllocationNumber()) + return 0; + return 1; +} + +int ReplicaManager::RegisteredReplicaRefOrderComp( const unsigned int &key, const ReplicaManager::RegisteredReplica &data ) +{ + if (key < data.referenceOrder) + return -1; + if (key==data.referenceOrder) + return 0; + return 1; +} + +int ReplicaManager::RemoteObjectComp( Replica* const &key, const ReplicaManager::RemoteObject &data ) +{ + if (key->GetAllocationNumber() < data.replica->GetAllocationNumber()) + return -1; + if (key->GetAllocationNumber()==data.replica->GetAllocationNumber()) + return 0; + return 1; +} + +int ReplicaManager::ParticipantStructComp( const SystemAddress &key, ReplicaManager::ParticipantStruct * const &data ) +{ + if (key < data->systemAddress) + return -1; + if (key==data->systemAddress) + return 0; + return 1; +} + +ReplicaManager::ReplicaManager() +{ + _constructionCB=0; + _sendDownloadCompleteCB=0; + _receiveDownloadCompleteCB=0; + sendChannel=0; + autoParticipateNewConnections=false; + defaultScope=false; + autoConstructToNewParticipants=false; + autoSerializeInScope=false; + nextReferenceIndex=0; +#ifdef _DEBUG + inUpdate=false; +#endif +} +ReplicaManager::~ReplicaManager() +{ + Clear(); +} +void ReplicaManager::SetAutoParticipateNewConnections(bool autoAdd) +{ + autoParticipateNewConnections=autoAdd; +} +bool ReplicaManager::AddParticipant(SystemAddress systemAddress) +{ + RakAssert(systemAddress!=UNASSIGNED_SYSTEM_ADDRESS); + + // If this player is already in the list of participants, just return. + ParticipantStruct *participantStruct; + participantStruct=GetParticipantBySystemAddress(systemAddress); + if (participantStruct) + return false; + + // Create a new participant with this systemAddress + participantStruct = RakNet::OP_NEW( __FILE__, __LINE__ ); + participantStruct->systemAddress=systemAddress; + + // Signal that when done sending SendConstruction for each existing object, we call sendDownloadCompleteCB + participantStruct->callDownloadCompleteCB=true; + + // Add the new participant to the list of participants + participantList.Insert(systemAddress,participantStruct, true, __FILE__,__LINE__); + + /* + if (autoConstructToNewParticipants) + { + // Signal that we need to call SendConstruction for each existing object to this participant + unsigned i; + CommandStruct replicaAndCommand; + replicaAndCommand.command=REPLICA_EXPLICIT_CONSTRUCTION; + if (defaultScope==true) + replicaAndCommand.command |= REPLICA_SCOPE_TRUE; + replicaAndCommand.userFlags=0; + + // Auto replicate objects in the order they were originally referenced, rather than the random sorted order they are in now + // This way dependencies are created first. + DataStructures::OrderedList sortByRefList; + for (i=0; i < replicatedObjects.Size(); i++) + sortByRefList.Insert(replicatedObjects[i].referenceOrder, replicatedObjects[i]); + + for (i=0; i < sortByRefList.Size(); i++) + { + replicaAndCommand.replica=sortByRefList[i].replica; + participantStruct->commandList.Insert(replicaAndCommand.replica,replicaAndCommand); + } + } + */ + + + if (autoConstructToNewParticipants) + { + // Signal that we need to call SendConstruction for each existing object to this participant + unsigned i; + CommandStruct replicaAndCommand; + replicaAndCommand.command=REPLICA_EXPLICIT_CONSTRUCTION; + if (defaultScope==true) + replicaAndCommand.command |= REPLICA_SCOPE_TRUE; + replicaAndCommand.userFlags=0; + for (i=0; i < replicatedObjects.Size(); i++) + { + replicaAndCommand.replica=replicatedObjects[i].replica; + participantStruct->commandList.Insert(replicaAndCommand, __FILE__, __LINE__); + } + } + + return true; +} +bool ReplicaManager::RemoveParticipant(SystemAddress systemAddress) +{ + RakAssert(systemAddress!=UNASSIGNED_SYSTEM_ADDRESS); + + // Find this participant by systemAddress + ParticipantStruct *participantStruct; + participantStruct=GetParticipantBySystemAddress(systemAddress); + + // If found, remove and free this participant structure + if (participantStruct) + { + participantList.Remove(systemAddress); + RakNet::OP_DELETE(participantStruct, __FILE__, __LINE__); + return true; + } + + return false; +} + +void ReplicaManager::Construct(Replica *replica, bool isCopy, SystemAddress systemAddress, bool broadcast) +{ + RakAssert(replica); + + unsigned i; + ParticipantStruct *participantStruct; + CommandStruct replicaAndCommand; + unsigned index; + bool objectExists; + replicaAndCommand.replica=replica; + replicaAndCommand.userFlags=0; + + ReferencePointer(replica); + + for (i=0; i < participantList.Size(); i++) + { + participantStruct=participantList[i]; + if ((broadcast==true && systemAddress!=participantStruct->systemAddress) || + (broadcast==false && systemAddress==participantStruct->systemAddress)) + { + if (participantStruct->remoteObjectList.HasData(replica)==false) + { + index = GetCommandListReplicaIndex(participantStruct->commandList, replica, &objectExists); + // index = participantStruct->commandList.GetIndexFromKey(replica, &objectExists); + if (objectExists) + { +#ifdef _DEBUG + // Implicit is only used for objects that were not already registered. + RakAssert(isCopy==false); +#endif + participantStruct->commandList[index].command|=REPLICA_EXPLICIT_CONSTRUCTION; // Set this bit + participantStruct->commandList[index].command&=0xFF ^ REPLICA_IMPLICIT_CONSTRUCTION; // Unset this bit + + if (defaultScope==true && (participantStruct->commandList[index].command & REPLICA_SCOPE_FALSE) == 0) + participantStruct->commandList[index].command |= REPLICA_SCOPE_TRUE; + } + else + { + if (isCopy) + replicaAndCommand.command=REPLICA_IMPLICIT_CONSTRUCTION; // Set this bit + else + replicaAndCommand.command=REPLICA_EXPLICIT_CONSTRUCTION; // Set this bit + + if (defaultScope==true) + replicaAndCommand.command |= REPLICA_SCOPE_TRUE; + + participantStruct->commandList.Insert(replicaAndCommand, __FILE__, __LINE__); + } + } + } + } + + // Update immediately, otherwise if we take action on this object the first frame, the action packets will arrive before the object is created + Update(); +} +void ReplicaManager::Destruct(Replica *replica, SystemAddress systemAddress, bool broadcast) +{ + RakAssert(replica); + + bool sendTimestamp; + bool objectExists; + unsigned replicatedObjectsIndex; + replicatedObjectsIndex = replicatedObjects.GetIndexFromKey(replica, &objectExists); + if (objectExists==false) + return; + + // For each existing participant, send a packet telling them of this object destruction + RakNet::BitStream outBitstream, userDataBitStream; + unsigned i,tempIndex; + bool replicaReferenced; + ParticipantStruct *participantStruct; + replicaReferenced=false; + for (i=0; i < participantList.Size(); i++) + { + participantStruct=participantList[i]; + + if ((broadcast==true && systemAddress!=participantStruct->systemAddress) || + (broadcast==false && systemAddress==participantStruct->systemAddress)) + { + // Remove any remote object state tracking for this object, for this player + tempIndex = participantStruct->remoteObjectList.GetIndexFromKey(replica, &objectExists); + if (objectExists) + { + // Send the destruction packet immediately + if (replica->GetNetworkID()!=UNASSIGNED_NETWORK_ID && + (replicatedObjects[replicatedObjectsIndex].allowedInterfaces & REPLICA_SEND_DESTRUCTION)) + { + userDataBitStream.Reset(); + userDataBitStream.Write((MessageID)ID_REPLICA_MANAGER_DESTRUCTION); + userDataBitStream.Write(replica->GetNetworkID()); + sendTimestamp=false; + ReplicaReturnResult res = replica->SendDestruction(&userDataBitStream, participantStruct->systemAddress, &sendTimestamp); + if (res==REPLICA_PROCESSING_DONE) + { + outBitstream.Reset(); + if (sendTimestamp) + { + outBitstream.Write((MessageID)ID_TIMESTAMP); + outBitstream.Write(RakNet::GetTime()); + outBitstream.Write(&userDataBitStream); + SendUnified(&outBitstream, HIGH_PRIORITY, RELIABLE_ORDERED, sendChannel, participantStruct->systemAddress, false); + } + else + SendUnified(&userDataBitStream, HIGH_PRIORITY, RELIABLE_ORDERED, sendChannel, participantStruct->systemAddress, false); + } + } + + participantStruct->remoteObjectList.RemoveAtIndex(tempIndex); + } + + // Remove any pending commands that reference this object, for this player + tempIndex = GetCommandListReplicaIndex(participantStruct->commandList, replica, &objectExists); + // tempIndex = participantStruct->commandList.GetIndexFromKey(replica, &objectExists); + if (objectExists) + participantStruct->commandList.RemoveAtIndex(tempIndex); + } + else if (replicaReferenced==false) + { + bool objectExists; + GetCommandListReplicaIndex(participantStruct->commandList, replica, &objectExists); + // See if any commands or objects reference replica + //if (participantStruct->commandList.HasData(replica)) + if (objectExists) + replicaReferenced=true; + else if (participantStruct->remoteObjectList.HasData(replica)) + replicaReferenced=true; + } + } + + // Remove replica from the list if no commands and no remote objects reference it + if (replicaReferenced==false) + replicatedObjects.RemoveAtIndex(replicatedObjectsIndex); +} +void ReplicaManager::ReferencePointer(Replica *replica) +{ + // Start tracking this object, if we are not already + if (replicatedObjects.HasData(replica)==false) + { + RegisteredReplica replicaAndTime; + replicaAndTime.replica=replica; + replicaAndTime.lastDeserializeTrue=0; + replicaAndTime.allowedInterfaces=REPLICA_SET_ALL; + replicaAndTime.referenceOrder=nextReferenceIndex++; + replicatedObjects.Insert(replica,replicaAndTime, true, __FILE__,__LINE__); + /// Try setting the network ID manager if the user forgot + if (replica->GetNetworkIDManager()==0) + replica->SetNetworkIDManager(rakPeerInterface->GetNetworkIDManager()); + } +} +void ReplicaManager::DereferencePointer(Replica *replica) +{ + bool objectExists; + unsigned replicatedObjectsIndex; + unsigned tempIndex; + replicatedObjectsIndex = replicatedObjects.GetIndexFromKey(replica, &objectExists); + if (objectExists==false) + return; + replicatedObjects.RemoveAtIndex(replicatedObjectsIndex); + + ParticipantStruct *participantStruct; + unsigned i; + for (i=0; i < participantList.Size(); i++) + { + participantStruct=participantList[i]; + + // Remove any pending commands that reference this object for any player + tempIndex = GetCommandListReplicaIndex(participantStruct->commandList, replica, &objectExists); + // tempIndex = participantStruct->commandList.GetIndexFromKey(replica, &objectExists); + if (objectExists) + participantStruct->commandList.RemoveAtIndex(tempIndex); + + // Remove any remote object state tracking for this object for any player + tempIndex = participantStruct->remoteObjectList.GetIndexFromKey(replica, &objectExists); + if (objectExists) + participantStruct->remoteObjectList.RemoveAtIndex(tempIndex); + } +} +void ReplicaManager::SetScope(Replica *replica, bool inScope, SystemAddress systemAddress, bool broadcast) +{ + RakAssert(replica); + + // Autoreference the pointer if necessary. This way the user can call functions on an object without having to worry + // About the order of operations. + ReferencePointer(replica); + + // For each player that we want, flag to call SendScopeChange if inScope is different from what they already have + unsigned i; + ParticipantStruct *participantStruct; + bool objectExists; + unsigned index; + CommandStruct replicaAndCommand; + if (inScope) + replicaAndCommand.command=REPLICA_SCOPE_TRUE; + else + replicaAndCommand.command=REPLICA_SCOPE_FALSE; + replicaAndCommand.replica=replica; + replicaAndCommand.userFlags=0; + for (i=0; i < participantList.Size(); i++) + { + participantStruct=participantList[i]; + + if ((broadcast==true && systemAddress!=participantStruct->systemAddress) || + (broadcast==false && systemAddress==participantStruct->systemAddress)) + { + // If there is already a pending command for this object, add to it. Otherwise, add a new pending command + index = GetCommandListReplicaIndex(participantStruct->commandList, replica, &objectExists); +// index = participantStruct->commandList.GetIndexFromKey(replica, &objectExists); + if (objectExists) + { + // Update a pending command + if (inScope) + { + participantStruct->commandList[index].command&=0xFF ^ REPLICA_SCOPE_FALSE; // Unset this bit + participantStruct->commandList[index].command|=REPLICA_SCOPE_TRUE; // Set this bit + } + else + { + participantStruct->commandList[index].command&=0xFF ^ REPLICA_SCOPE_TRUE; // Unset this bit + participantStruct->commandList[index].command|=REPLICA_SCOPE_FALSE; // Set this bit + } + } + else + { + // Add a new command, since there are no pending commands for this object + participantStruct->commandList.Insert(replicaAndCommand, __FILE__, __LINE__); + } + } + } +} +void ReplicaManager::SignalSerializeNeeded(Replica *replica, SystemAddress systemAddress, bool broadcast) +{ + RakAssert(replica); + + // Autoreference the pointer if necessary. This way the user can call functions on an object without having to worry + // About the order of operations. + if (replicatedObjects.HasData(replica)==false) + ReferencePointer(replica); + + // For each player that we want, if this object exists on that system, flag to call Serialize + // (this may not necessarily happen - it depends on if the object is inScope when Update actually processes it.) + unsigned i; + ParticipantStruct *participantStruct; + bool objectExists; + unsigned index; + CommandStruct replicaAndCommand; + replicaAndCommand.command=REPLICA_SERIALIZE; + replicaAndCommand.replica=replica; + replicaAndCommand.userFlags=0; + for (i=0; i < participantList.Size(); i++) + { + participantStruct=participantList[i]; + + if ((broadcast==true && systemAddress!=participantStruct->systemAddress) || + (broadcast==false && systemAddress==participantStruct->systemAddress)) + { + // If there is already a pending command for this object, add to it. Otherwise, add a new pending command + // index = participantStruct->commandList.GetIndexFromKey(replica, &objectExists); + index = GetCommandListReplicaIndex(participantStruct->commandList, replica, &objectExists); + if (objectExists) + { + participantStruct->commandList[index].command|=REPLICA_SERIALIZE; // Set this bit + } + else + { + // Add a new command, since there are no pending commands for this object + participantStruct->commandList.Insert(replicaAndCommand, __FILE__, __LINE__); + } + } + } +} +void ReplicaManager::SetReceiveConstructionCB(ReceiveConstructionInterface *ReceiveConstructionInterface) +{ + // Just overwrite the construction callback pointer + _constructionCB=ReceiveConstructionInterface; +} +void ReplicaManager::SetDownloadCompleteCB(SendDownloadCompleteInterface *sendDownloadComplete, ReceiveDownloadCompleteInterface *receiveDownloadComplete) +{ + // Just overwrite the send and receive download complete pointers. + _sendDownloadCompleteCB=sendDownloadComplete; + _receiveDownloadCompleteCB=receiveDownloadComplete; +} +void ReplicaManager::SetSendChannel(unsigned char channel) +{ + // Change the send channel from the default of 0 + sendChannel=channel; +} +void ReplicaManager::SetAutoConstructToNewParticipants(bool autoConstruct) +{ + autoConstructToNewParticipants=autoConstruct; +} +void ReplicaManager::SetDefaultScope(bool scope) +{ + defaultScope=scope; +} +void ReplicaManager::SetAutoSerializeInScope(bool autoSerialize) +{ + autoSerializeInScope=autoSerialize; +} +void ReplicaManager::EnableReplicaInterfaces(Replica *replica, unsigned char interfaceFlags) +{ + bool objectExists; + unsigned replicatedObjectsIndex; + replicatedObjectsIndex = replicatedObjects.GetIndexFromKey(replica, &objectExists); + if (objectExists==false) + { + // Autoreference the pointer if necessary. This way the user can call functions on an object without having to worry + // About the order of operations. + ReferencePointer(replica); + replicatedObjectsIndex = replicatedObjects.GetIndexFromKey(replica, &objectExists); + } + replicatedObjects[replicatedObjectsIndex].allowedInterfaces|=interfaceFlags; +} +void ReplicaManager::DisableReplicaInterfaces(Replica *replica, unsigned char interfaceFlags) +{ + bool objectExists; + unsigned replicatedObjectsIndex; + replicatedObjectsIndex = replicatedObjects.GetIndexFromKey(replica, &objectExists); + if (objectExists==false) + { + // Autoreference the pointer if necessary. This way the user can call functions on an object without having to worry + // About the order of operations. + ReferencePointer(replica); + replicatedObjectsIndex = replicatedObjects.GetIndexFromKey(replica, &objectExists); + } + replicatedObjects[replicatedObjectsIndex].allowedInterfaces&= 0xFF ^ interfaceFlags; +} +bool ReplicaManager::IsConstructed(Replica *replica, SystemAddress systemAddress) +{ + ParticipantStruct *participantStruct = GetParticipantBySystemAddress(systemAddress); + if (participantStruct) + { + bool objectExists; + participantStruct->remoteObjectList.GetIndexFromKey(replica, &objectExists); + return objectExists; + } + return false; +} +bool ReplicaManager::IsInScope(Replica *replica, SystemAddress systemAddress) +{ + ParticipantStruct *participantStruct = GetParticipantBySystemAddress(systemAddress); + if (participantStruct) + { + bool objectExists; + unsigned remoteObjectListIndex = participantStruct->remoteObjectList.GetIndexFromKey(replica, &objectExists); + if (objectExists) + return participantStruct->remoteObjectList[remoteObjectListIndex].inScope; + } + return false; +} +unsigned ReplicaManager::GetReplicaCount(void) const +{ + return replicatedObjects.Size(); +} +Replica *ReplicaManager::GetReplicaAtIndex(unsigned index) +{ + return replicatedObjects[index].replica; +} +unsigned ReplicaManager::GetParticipantCount(void) const +{ + return participantList.Size(); +} +SystemAddress ReplicaManager::GetParticipantAtIndex(unsigned index) +{ + return participantList[index]->systemAddress; +} +bool ReplicaManager::HasParticipant(SystemAddress systemAddress) +{ + return participantList.HasData(systemAddress); +} +void ReplicaManager::SignalSerializationFlags(Replica *replica, SystemAddress systemAddress, bool broadcast, bool set, unsigned int flags) +{ + RakAssert(replica); + + // Autoreference the pointer if necessary. This way the user can call functions on an object without having to worry + // About the order of operations. + ReferencePointer(replica); + + CommandStruct replicaAndCommand; + replicaAndCommand.replica=replica; + replicaAndCommand.userFlags=flags; + replicaAndCommand.command=0; + + bool objectExists; + unsigned i, index; + ParticipantStruct *participantStruct; + for (i=0; i < participantList.Size(); i++) + { + participantStruct=participantList[i]; + + if ((broadcast==true && systemAddress!=participantStruct->systemAddress) || + (broadcast==false && systemAddress==participantStruct->systemAddress)) + { + // Set the flags in the object if the object exists + index = participantStruct->remoteObjectList.GetIndexFromKey(replica, &objectExists); + if (objectExists) + { + if (set) + participantStruct->remoteObjectList[index].userFlags|=flags; // Set these user flags + else + participantStruct->remoteObjectList[index].userFlags&=~flags; // Unset these user flags + } + else + { + // The object is not yet created. Add to the pending command, or create a new command. + // index = participantStruct->commandList.GetIndexFromKey(replica, &objectExists); + index = GetCommandListReplicaIndex(participantStruct->commandList, replica, &objectExists); + if (objectExists) + { + if (set) + participantStruct->commandList[index].userFlags|=flags; // Set these user flags + else + participantStruct->commandList[index].userFlags&=~flags; // Unset these user flags + } + else if (set) + { + // Add a new command, since there are no pending commands for this object + participantStruct->commandList.Insert(replicaAndCommand, __FILE__, __LINE__); + } + } + } + } +} +unsigned int* ReplicaManager::AccessSerializationFlags(Replica *replica, SystemAddress systemAddress) +{ + RakAssert(replica); + + // Autoreference the pointer if necessary. This way the user can call functions on an object without having to worry + // About the order of operations. + ReferencePointer(replica); + + unsigned index; + bool objectExists; + ParticipantStruct *participantStruct; + CommandStruct replicaAndCommand; + replicaAndCommand.replica=replica; + replicaAndCommand.userFlags=0; + replicaAndCommand.command=0; + + participantStruct=GetParticipantBySystemAddress(systemAddress); + if (participantStruct) + { + // Set the flags in the object if the object exists + index = participantStruct->remoteObjectList.GetIndexFromKey(replica, &objectExists); + if (objectExists) + { + return &(participantStruct->remoteObjectList[index].userFlags); + } + else + { +// index = participantStruct->commandList.GetIndexFromKey(replica, &objectExists); + index = GetCommandListReplicaIndex(participantStruct->commandList, replica, &objectExists); + if (objectExists) + { + return &(participantStruct->commandList[index].userFlags); + } + else + { + // Add a new command, since there are no pending commands for this object + //index = + participantStruct->commandList.Insert(replicaAndCommand, __FILE__, __LINE__); + // return &(participantStruct->commandList[index].userFlags); + return & (participantStruct->commandList[participantStruct->commandList.Size()-1].userFlags); + } + } + } + + // No such participant + return 0; +} + +void ReplicaManager::Clear(void) +{ + // Free all memory + unsigned i; + for (i=0; i < participantList.Size(); i++) + RakNet::OP_DELETE(participantList[i], __FILE__, __LINE__); + participantList.Clear(false, __FILE__, __LINE__); + replicatedObjects.Clear(false, __FILE__, __LINE__); + nextReferenceIndex=0; +} +void ReplicaManager::AssertReplicatedObjectsClear(void) +{ + RakAssert(replicatedObjects.Size()==0); +} +void ReplicaManager::AssertParticipantsClear(void) +{ + RakAssert(participantList.Size()==0); +} +void ReplicaManager::Update(void) +{ + if (participantList.Size()==0) + return; + + // Check for recursive calls, which is not supported and should not happen +#ifdef _DEBUG + RakAssert(inUpdate==false); + inUpdate=true; +#endif + + unsigned participantIndex, remoteObjectListIndex, replicatedObjectsIndex; + ReplicaReturnResult res; + bool sendTimestamp; + ParticipantStruct *participantStruct; + unsigned commandListIndex; + RakNet::BitStream outBitstream, userDataBitstream; + RakNetTime currentTime; + bool objectExists; + PacketPriority priority; + PacketReliability reliability; + ReceivedCommand *receivedCommand; + Replica *replica; +// unsigned int userFlags; + unsigned char command; + currentTime=0; + + // For each participant + for (participantIndex=0; participantIndex < participantList.Size(); participantIndex++) + { + participantStruct = participantList[participantIndex]; + + // Sends the download complete packet + // If callDownloadCompleteCB is true then check all the remaining objects starting at commandListIndex + // I scan every frame in case the callback returns false to delay, and after that time a new object is Replicated + if (participantStruct->callDownloadCompleteCB) + { + bool anyHasConstruction; + unsigned j; + anyHasConstruction=false; + for (j=0; j < participantStruct->commandList.Size(); j++) + { + if (participantStruct->commandList[j].command & REPLICA_EXPLICIT_CONSTRUCTION) + { + anyHasConstruction=true; + break; + } + } + // If none have REPLICA_EXPLICIT_CONSTRUCTION, send ID_REPLICA_MANAGER_DOWNLOAD_COMPLETE and set callDownloadCompleteCB false + if (anyHasConstruction==false) + { + ReplicaReturnResult sendDLComplete; + userDataBitstream.Reset(); + if (_sendDownloadCompleteCB) + sendDLComplete=_sendDownloadCompleteCB->SendDownloadComplete(&userDataBitstream, currentTime, participantStruct->systemAddress, this); // If you return false, this will be called again next update + else + sendDLComplete=REPLICA_CANCEL_PROCESS; + if (sendDLComplete==REPLICA_PROCESSING_DONE) + { + outBitstream.Reset(); + outBitstream.Write((MessageID)ID_REPLICA_MANAGER_DOWNLOAD_COMPLETE); + outBitstream.Write(&userDataBitstream, userDataBitstream.GetNumberOfBitsUsed()); + SendUnified(&outBitstream, HIGH_PRIORITY, RELIABLE_ORDERED, sendChannel, participantStruct->systemAddress, false); + participantStruct->callDownloadCompleteCB=false; + } + else if (sendDLComplete==REPLICA_CANCEL_PROCESS) + { + participantStruct->callDownloadCompleteCB=false; + } + else + { + RakAssert(sendDLComplete==REPLICA_PROCESS_LATER); + // else REPLICA_PROCESS_LATER + } + } + } + + // For each CommandStruct to send + for (commandListIndex=0; commandListIndex < participantStruct->commandList.Size(); commandListIndex++) + { + // Only call RakNet::GetTime() once because it's slow + if (currentTime==0) + currentTime=RakNet::GetTime(); + + replica=participantStruct->commandList[commandListIndex].replica; + command=participantStruct->commandList[commandListIndex].command; + // userFlags=participantStruct->commandList[commandListIndex].userFlags; + replicatedObjectsIndex=replicatedObjects.GetIndexFromKey(replica, &objectExists); +#ifdef _DEBUG + RakAssert(objectExists); +#endif + if (objectExists==false) + continue; + + // If construction is set, call SendConstruction. The only precondition is that the object was not already created, + // which was checked in ReplicaManager::Replicate + if (command & REPLICA_EXPLICIT_CONSTRUCTION) + { + if (replicatedObjects[replicatedObjectsIndex].allowedInterfaces & REPLICA_SEND_CONSTRUCTION) + { + userDataBitstream.Reset(); + sendTimestamp=false; + res=replica->SendConstruction(currentTime, participantStruct->systemAddress, + participantStruct->commandList[commandListIndex].userFlags, &userDataBitstream, &sendTimestamp); + + if (res==REPLICA_PROCESSING_DONE) + { + outBitstream.Reset(); + // If SendConstruction returns true and writes to outBitStream, do this send. Clear the construction command. Then process the next command for this CommandStruct, if any. + if (sendTimestamp) + { + outBitstream.Write((MessageID)ID_TIMESTAMP); + outBitstream.Write(currentTime); + } + outBitstream.Write((MessageID)ID_REPLICA_MANAGER_CONSTRUCTION); + // It's required to send an NetworkID if available. + // Problem: + // A->B->C + // | | + // D->E + // + // A creates. + // B->C->E->D->B will cycle forever. + // Fix is to always include an networkID. Objects are not created if that object id already is present. + if (replica->GetNetworkID()!=UNASSIGNED_NETWORK_ID) + { + outBitstream.Write(true); + outBitstream.Write(replica->GetNetworkID()); + } + else + outBitstream.Write(false); + + outBitstream.Write(&userDataBitstream, userDataBitstream.GetNumberOfBitsUsed()); + + SendUnified(&outBitstream, HIGH_PRIORITY, RELIABLE_ORDERED, sendChannel, participantStruct->systemAddress, false); + + // Turn off this bit + participantStruct->commandList[commandListIndex].command &= 0xFF ^ REPLICA_EXPLICIT_CONSTRUCTION; + + // Add the object to the participant's object list, indicating this object has been remotely created + RemoteObject remoteObject; + remoteObject.replica=replica; + //remoteObject.inScope=defaultScope; + remoteObject.inScope=false; + remoteObject.lastSendTime=0; + remoteObject.userFlags=participantStruct->commandList[commandListIndex].userFlags; + // Create an entry for this object. We do this now, even if the user might refuse the SendConstruction override, + // because that call may be delayed and other commands sent while that is pending. We always do the REPLICA_EXPLICIT_CONSTRUCTION call first. + participantStruct->remoteObjectList.Insert(remoteObject.replica,remoteObject, true, __FILE__,__LINE__); + } + else if (res==REPLICA_PROCESS_IMPLICIT) + { + // Turn off this bit + participantStruct->commandList[commandListIndex].command &= 0xFF ^ REPLICA_EXPLICIT_CONSTRUCTION; + + // Add the object to the participant's object list, indicating this object has been remotely created + RemoteObject remoteObject; + remoteObject.replica=replica; + //remoteObject.inScope=defaultScope; + remoteObject.inScope=false; + remoteObject.lastSendTime=0; + remoteObject.userFlags=participantStruct->commandList[commandListIndex].userFlags; + // Create an entry for this object. We do this now, even if the user might refuse the SendConstruction override, + // because that call may be delayed and other commands sent while that is pending. We always do the REPLICA_EXPLICIT_CONSTRUCTION call first. + participantStruct->remoteObjectList.Insert(remoteObject.replica,remoteObject, true, __FILE__,__LINE__); + } + else if (res==REPLICA_PROCESS_LATER) + { + continue; + } + else // REPLICA_CANCEL_PROCESS + { + RakAssert(res==REPLICA_CANCEL_PROCESS); + participantStruct->commandList[commandListIndex].command=0; + } + } + else + { + // Don't allow construction, or anything else for this object, as the construction send call is disallowed + participantStruct->commandList[commandListIndex].command=0; + } + } + else if (command & REPLICA_IMPLICIT_CONSTRUCTION) + { + // Turn off this bit + participantStruct->commandList[commandListIndex].command &= 0xFF ^ REPLICA_IMPLICIT_CONSTRUCTION; + + // Add the object to the participant's object list, indicating this object is assumed to be remotely created + RemoteObject remoteObject; + remoteObject.replica=replica; + //remoteObject.inScope=defaultScope; + remoteObject.inScope=false; + remoteObject.lastSendTime=0; + remoteObject.userFlags=participantStruct->commandList[commandListIndex].userFlags; + // Create an entry for this object. We do this now, even if the user might refuse the SendConstruction override, + // because that call may be delayed and other commands sent while that is pending. We always do the REPLICA_EXPLICIT_CONSTRUCTION call first. + participantStruct->remoteObjectList.Insert(remoteObject.replica,remoteObject, true, __FILE__,__LINE__); + } + + // The remaining commands, SendScopeChange and Serialize, require the object the command references exists on the remote system, so check that + remoteObjectListIndex = participantStruct->remoteObjectList.GetIndexFromKey(replica, &objectExists); + if (objectExists) + { + command = participantStruct->commandList[commandListIndex].command; + + // Process SendScopeChange. + if ((command & (REPLICA_SCOPE_TRUE | REPLICA_SCOPE_FALSE))) + { + if (replica->GetNetworkID()==UNASSIGNED_NETWORK_ID) + continue; // Not set yet so call this later. + + if (replicatedObjects[replicatedObjectsIndex].allowedInterfaces & REPLICA_SEND_SCOPE_CHANGE) + { + bool scopeTrue = (command & REPLICA_SCOPE_TRUE)!=0; + + // Only send scope changes if the requested change is different from what they already have + if (participantStruct->remoteObjectList[remoteObjectListIndex].inScope!=scopeTrue) + { + userDataBitstream.Reset(); + sendTimestamp=false; + res=replica->SendScopeChange(scopeTrue, &userDataBitstream, currentTime, participantStruct->systemAddress, &sendTimestamp); + + if (res==REPLICA_PROCESSING_DONE) + { + // If the user returns true and does write to outBitstream, do this send. Clear the scope change command. Then process the next command for this CommandStruct, if any. + outBitstream.Reset(); + if (sendTimestamp) + { + outBitstream.Write((MessageID)ID_TIMESTAMP); + outBitstream.Write(currentTime); + } + outBitstream.Write((MessageID)ID_REPLICA_MANAGER_SCOPE_CHANGE); + outBitstream.Write(replica->GetNetworkID()); + outBitstream.Write(&userDataBitstream, userDataBitstream.GetNumberOfBitsUsed()); + SendUnified(&outBitstream, HIGH_PRIORITY, RELIABLE_ORDERED, sendChannel, participantStruct->systemAddress, false); + + // Set the scope for this object and system + participantStruct->remoteObjectList[remoteObjectListIndex].inScope=scopeTrue; + + // If scope is true, turn on serialize, since you virtually always want to serialize when an object goes in scope + if (scopeTrue && autoSerializeInScope) + participantStruct->commandList[commandListIndex].command |= REPLICA_SERIALIZE; + + // Turn off these bits - Call is processed + participantStruct->commandList[commandListIndex].command &= 0xFF ^ (REPLICA_SCOPE_TRUE | REPLICA_SCOPE_FALSE); + } + else if (res==REPLICA_CANCEL_PROCESS) + { + // Turn off these bits - Call is canceled + participantStruct->commandList[commandListIndex].command &= 0xFF ^ (REPLICA_SCOPE_TRUE | REPLICA_SCOPE_FALSE); + } + else + { + // If the user returns false and the scope is currently set to false, just continue with another CommandStruct. Don't process serialization until scoping is resolved first. + if (scopeTrue==false) + continue; + + // If the user returns false and the scope is currently set to false, process the next command for this CommandStruct, if any. + } + } + } + else + { + // Turn off these bits - Call is disallowed + participantStruct->commandList[commandListIndex].command &= 0xFF ^ (REPLICA_SCOPE_TRUE | REPLICA_SCOPE_FALSE); + + // Set the scope - even if the actual send is disabled we might still be able to serialize. + participantStruct->remoteObjectList[remoteObjectListIndex].inScope=(command & REPLICA_SCOPE_TRUE)!=0; + } + } + + command = participantStruct->commandList[commandListIndex].command; + // Process Serialize + if ((command & REPLICA_SERIALIZE)) + { + if (replica->GetNetworkID()==UNASSIGNED_NETWORK_ID) + continue; // Not set yet so call this later. + + // If scope is currently false for this object in the RemoteObject list, cancel this serialize as the scope changed before the serialization went out + if (participantStruct->remoteObjectList[remoteObjectListIndex].inScope && (replicatedObjects[replicatedObjectsIndex].allowedInterfaces & REPLICA_SEND_SERIALIZE)) + { + do + { + userDataBitstream.Reset(); + priority=HIGH_PRIORITY; + reliability=RELIABLE_ORDERED; + sendTimestamp=false; + res=replica->Serialize(&sendTimestamp, &userDataBitstream, participantStruct->remoteObjectList[remoteObjectListIndex].lastSendTime, &priority, &reliability, currentTime, participantStruct->systemAddress, participantStruct->remoteObjectList[remoteObjectListIndex].userFlags); + + if (res==REPLICA_PROCESSING_DONE || res==REPLICA_PROCESS_AGAIN) + { + participantStruct->remoteObjectList[remoteObjectListIndex].lastSendTime=currentTime; + + outBitstream.Reset(); + if (sendTimestamp) + { + outBitstream.Write((MessageID)ID_TIMESTAMP); + outBitstream.Write(currentTime); + } + outBitstream.Write((MessageID)ID_REPLICA_MANAGER_SERIALIZE); + outBitstream.Write(replica->GetNetworkID()); + outBitstream.Write(&userDataBitstream, userDataBitstream.GetNumberOfBitsUsed()); + SendUnified(&outBitstream, priority, reliability, sendChannel, participantStruct->systemAddress, false); + + // Clear the serialize bit when done + if (res==REPLICA_PROCESSING_DONE) + participantStruct->commandList[commandListIndex].command &= 0xFF ^ REPLICA_SERIALIZE; + // else res==REPLICA_PROCESS_AGAIN so it will repeat the enclosing do {} while(); loop + } + else if (res==REPLICA_CANCEL_PROCESS) + { + // Clear the serialize bit + participantStruct->commandList[commandListIndex].command &= 0xFF ^ REPLICA_SERIALIZE; + } + else + { + // if the user returns REPLICA_PROCESS_LATER, just continue with another CommandStruct. + RakAssert(res==REPLICA_PROCESS_LATER); + } + } while(res==REPLICA_PROCESS_AGAIN); + } + else + { + // Cancel this serialize + participantStruct->commandList[commandListIndex].command &= 0xFF ^ REPLICA_SERIALIZE; + } + } + } + } + + // Go through the command list and delete all cleared commands, from back to front. It is more efficient to do this than to delete them as we process + commandListIndex=participantStruct->commandList.Size(); + if (commandListIndex>0) + { +#ifdef _MSC_VER +#pragma warning( disable : 4127 ) // warning C4127: conditional expression is constant +#endif + while (1) + { + if (participantStruct->commandList[commandListIndex-1].command==0) + { + // If this is the last item in the list, and it probably is, then it just changes a number rather than shifts the entire array + participantStruct->commandList.RemoveAtIndex(commandListIndex-1); + } + + if (--commandListIndex==0) + break; + } + } + + // Now process queued receives + while (participantStruct->pendingCommands.Size()) + { + receivedCommand=participantStruct->pendingCommands.Pop(); + participantStruct=GetParticipantBySystemAddress(receivedCommand->systemAddress); + if (participantStruct) + { + res=ProcessReceivedCommand(participantStruct, receivedCommand); + // Returning false means process this command again later + if (res==REPLICA_PROCESS_LATER) + { + // Push the command back in the queue + participantStruct->pendingCommands.PushAtHead(receivedCommand, 0, __FILE__,__LINE__); + + // Stop processing, because all processing is in order + break; + } + else + { + RakAssert(res==REPLICA_CANCEL_PROCESS); + } + } + + // Done with this command, so delete it + RakNet::OP_DELETE(receivedCommand->userData, __FILE__, __LINE__); + RakNet::OP_DELETE(receivedCommand, __FILE__, __LINE__); + } + } +#ifdef _DEBUG + inUpdate=false; +#endif +} +void ReplicaManager::OnClosedConnection(SystemAddress systemAddress, RakNetGUID rakNetGUID, PI2_LostConnectionReason lostConnectionReason ) +{ + (void) systemAddress; + (void) rakNetGUID; + (void) lostConnectionReason; + + RemoveParticipant(systemAddress); +} +void ReplicaManager::OnRakPeerShutdown(void) +{ + Clear(); +} +void ReplicaManager::OnNewConnection(SystemAddress systemAddress, RakNetGUID rakNetGUID, bool isIncoming) +{ + (void) systemAddress; + (void) rakNetGUID; + (void) isIncoming; + + if (autoParticipateNewConnections) + AddParticipant(systemAddress); +} +PluginReceiveResult ReplicaManager::OnReceive(Packet *packet) +{ + unsigned char packetIdentifier; + if ( ( unsigned char ) packet->data[ 0 ] == ID_TIMESTAMP ) + { + if ( packet->length > sizeof( unsigned char ) + sizeof( unsigned int ) ) + packetIdentifier = ( unsigned char ) packet->data[ sizeof( unsigned char ) + sizeof( unsigned int ) ]; + else + return RR_STOP_PROCESSING_AND_DEALLOCATE; + } + else + packetIdentifier = ( unsigned char ) packet->data[ 0 ]; + + switch (packetIdentifier) + { + case ID_REPLICA_MANAGER_DOWNLOAD_COMPLETE: + if (_receiveDownloadCompleteCB==0) + { + return RR_STOP_PROCESSING_AND_DEALLOCATE; + } + case ID_REPLICA_MANAGER_CONSTRUCTION: + case ID_REPLICA_MANAGER_DESTRUCTION: + case ID_REPLICA_MANAGER_SCOPE_CHANGE: + case ID_REPLICA_MANAGER_SERIALIZE: + { + ParticipantStruct *participantStruct; + bool hasNetworkId=false; + ReceivedCommand receivedCommand; + bool b=true; + RakNet::BitStream inBitstream(packet->data, packet->length, false); + // SetWriteOffset is used here to get around a design flaw, where I should have had the bitstream constructor take bits, rather than bytes + // It sets the actual number of bits in the packet + inBitstream.SetWriteOffset(packet->bitSize); + receivedCommand.systemAddress=packet->systemAddress; + receivedCommand.command=packetIdentifier; + + if ( ( unsigned char ) packet->data[ 0 ] == ID_TIMESTAMP ) + { + inBitstream.IgnoreBits(8); + b=inBitstream.Read(receivedCommand.u1); + } + else + receivedCommand.u1=0; + inBitstream.IgnoreBits(8); // Ignore the packet id + receivedCommand.networkID=UNASSIGNED_NETWORK_ID; + if (packetIdentifier==ID_REPLICA_MANAGER_CONSTRUCTION) // ID_REPLICA_MANAGER_CONSTRUCTION has an optional networkID + { + b=inBitstream.Read(hasNetworkId); + if (hasNetworkId) + b=inBitstream.Read(receivedCommand.networkID); + } + else if (packetIdentifier!=ID_REPLICA_MANAGER_DOWNLOAD_COMPLETE) + { + b=inBitstream.Read(receivedCommand.networkID); // Other packets always have an networkID + } + + if (b==false) + { + // Invalid packet +#ifdef _DEBUG + RakAssert(0); +#endif + return RR_STOP_PROCESSING_AND_DEALLOCATE; + } + receivedCommand.userData=&inBitstream; + participantStruct=GetParticipantBySystemAddress(receivedCommand.systemAddress); + if (participantStruct) + { + // .Size()>0 is because commands are always processed in order. If a command is delayed, no further commands are processed. + // ProcessReceivedCommand(...)==false means that the use signaled to delay a command + if (participantStruct->pendingCommands.Size()>0 || ProcessReceivedCommand(participantStruct, &receivedCommand)==REPLICA_PROCESS_LATER) + { + // Copy the data and add this to a queue that will call ProcessReceivedCommand again in Update. + + // Allocate and copy structure + ReceivedCommand *rc = RakNet::OP_NEW( __FILE__, __LINE__ ); + memcpy(rc, &receivedCommand, sizeof(ReceivedCommand)); + + // Allocate and copy inBitstream remaining data + rc->userData = RakNet::OP_NEW( __FILE__, __LINE__ ); + rc->userData->Write(&inBitstream, inBitstream.GetNumberOfBitsUsed()); + + participantStruct->pendingCommands.Push(rc, __FILE__, __LINE__ ); + } + } + + return RR_STOP_PROCESSING_AND_DEALLOCATE; + } + } + + return RR_CONTINUE_PROCESSING; +} + +ReplicaManager::ParticipantStruct* ReplicaManager::GetParticipantBySystemAddress(const SystemAddress systemAddress) const +{ + bool objectExists; + unsigned index; + index = participantList.GetIndexFromKey(systemAddress, &objectExists); + if (objectExists==false) + return 0; + return participantList[index]; +} +#ifdef _MSC_VER +#pragma warning( disable : 4701 ) // warning C4701: local variable may be used without having been initialized +#endif +ReplicaReturnResult ReplicaManager::ProcessReceivedCommand(ParticipantStruct *participantStruct, ReceivedCommand *receivedCommand) +{ + (void) participantStruct; + + // If this assert hits you didn't first call RakPeer::SetNetworkIDManager as required. + RakAssert(rakPeerInterface->GetNetworkIDManager()); + if (rakPeerInterface->GetNetworkIDManager()==0) + return REPLICA_CANCEL_PROCESS; + + Replica *replica = (Replica*) rakPeerInterface->GetNetworkIDManager()->GET_BASE_OBJECT_FROM_ID(receivedCommand->networkID); + + bool objectExists; + unsigned index=0; + ReplicaReturnResult b; + if (replica) + { + index = replicatedObjects.GetIndexFromKey(replica, &objectExists); + if (objectExists==false) + { + if (receivedCommand->command==ID_REPLICA_MANAGER_CONSTRUCTION) + { + // Object already exists with this ID, but call construction anyway +#ifdef _DEBUG + RakAssert(_constructionCB); +#endif + // Call the registered callback. If it crashes, you forgot to register the callback in SetReceiveConstructionCB + return _constructionCB->ReceiveConstruction(receivedCommand->userData, receivedCommand->u1, receivedCommand->networkID, replica, receivedCommand->systemAddress, this); + } + else + { + // This networkID is already in use but ReferencePointer was never called on it. + // RakAssert(0); + return REPLICA_CANCEL_PROCESS; + } + + } + } + + if (receivedCommand->command==ID_REPLICA_MANAGER_SERIALIZE) + { + if (replica && (replicatedObjects[index].allowedInterfaces & REPLICA_RECEIVE_SERIALIZE)) + { + b=replica->Deserialize(receivedCommand->userData, receivedCommand->u1, replicatedObjects[index].lastDeserializeTrue, receivedCommand->systemAddress); + if (b==REPLICA_PROCESSING_DONE) + replicatedObjects[index].lastDeserializeTrue=RakNet::GetTime(); + return b; + } + } + else if (receivedCommand->command==ID_REPLICA_MANAGER_CONSTRUCTION) + { + // If networkID already exists on this system, ignore the packet +#ifdef _DEBUG + RakAssert(_constructionCB); +#endif + // Call the registered callback. If it crashes, you forgot to register the callback in SetReceiveConstructionCB + return _constructionCB->ReceiveConstruction(receivedCommand->userData, receivedCommand->u1, receivedCommand->networkID, replica, receivedCommand->systemAddress, this); + } + else if (receivedCommand->command==ID_REPLICA_MANAGER_SCOPE_CHANGE) + { + if (replica && (replicatedObjects[index].allowedInterfaces & REPLICA_RECEIVE_SCOPE_CHANGE)) + { + return replica->ReceiveScopeChange(receivedCommand->userData, receivedCommand->systemAddress, receivedCommand->u1); + } + } + else if (receivedCommand->command==ID_REPLICA_MANAGER_DESTRUCTION) + { + if (replica && (replicatedObjects[index].allowedInterfaces & REPLICA_RECEIVE_DESTRUCTION)) + { + return replica->ReceiveDestruction(receivedCommand->userData, receivedCommand->systemAddress, receivedCommand->u1); + } + } + else if (receivedCommand->command==ID_REPLICA_MANAGER_DOWNLOAD_COMPLETE) + { + if (_receiveDownloadCompleteCB) + { + return _receiveDownloadCompleteCB->ReceiveDownloadComplete(receivedCommand->userData, receivedCommand->systemAddress, this); + } + } + + return REPLICA_CANCEL_PROCESS; +} + +ReplicaManager::ParticipantStruct::~ParticipantStruct() +{ + ReceivedCommand *receivedCommand; + while ( pendingCommands.Size() ) + { + receivedCommand=pendingCommands.Pop(); + RakNet::OP_DELETE(receivedCommand->userData, __FILE__, __LINE__); + RakNet::OP_DELETE(receivedCommand, __FILE__, __LINE__); + } +} + +unsigned ReplicaManager::GetCommandListReplicaIndex(const DataStructures::List &commandList, Replica *replica, bool *objectExists) const +{ + unsigned i; + for (i=0; i < commandList.Size(); i++) + { + if (commandList[i].replica==replica) + { + *objectExists=true; + return i; + } + } + *objectExists=false; + return 0; +} + +#ifdef _MSC_VER +#pragma warning( pop ) +#endif + +#endif // _RAKNET_SUPPORT_* diff --git a/RakNet/Sources/ReplicaManager.h b/RakNet/Sources/ReplicaManager.h new file mode 100644 index 0000000..e0286f3 --- /dev/null +++ b/RakNet/Sources/ReplicaManager.h @@ -0,0 +1,481 @@ +/// \file +/// \brief Contains class ReplicaManager. This system provides management for your game objects and players to make serialization, scoping, and object creation and destruction easier. +/// +/// This file is part of RakNet Copyright 2003 Jenkins Software LLC +/// +/// Usage of RakNet is subject to the appropriate license agreement. + +#include "NativeFeatureIncludes.h" +#if _RAKNET_SUPPORT_ReplicaManager==1 + +#ifndef __REPLICA_MANAGER_H +#define __REPLICA_MANAGER_H + +#include "Export.h" +#include "RakNetTypes.h" +#include "DS_OrderedList.h" +#include "PluginInterface2.h" +#include "NetworkIDObject.h" +#include "DS_Queue.h" +#include "ReplicaEnums.h" + +/// Forward declarations +namespace RakNet +{ + class BitStream; +}; +class Replica; +class ReplicaManager; + +/// \defgroup REPLICA_MANAGER_GROUP ReplicaManager +/// \brief Automatic game object replication +/// \details +/// \ingroup PLUGINS_GROUP + +/// \defgroup REPLICA_MANAGER_GROUP1 ReplicaManager +/// \brief Deprecated. First implementation of object replication +/// \details +/// \ingroup REPLICA_MANAGER_GROUP + +/// An interface for a class that handles the construction callback from the network +/// See ReplicaManager::SetReceiveConstructionCB +/// \ingroup REPLICA_MANAGER_GROUP1 +class ReceiveConstructionInterface +{ +public: + ReceiveConstructionInterface() {} + virtual ~ReceiveConstructionInterface() {} + + /// Called when a network object needs to be created by the ReplicaManager class + /// \param[in] inBitStream The bitstream that was written to in Replica::SendConstruction + /// \param[in] timestamp If in Replica::SendConstruction you set sendTimestamp to true, this is the time the packet was sent. Otherwise it is 0. + /// \param[in] networkID If the remote object had an NetworkID set by the time Replica::SendConstruction was called it is here. + /// \param[in] existingNetworkObject If networkID is already in use, existingNetworkObject is the pointer to that object. If existingReplica is non-zero, you usually shouldn't create a new object, unless you are reusing networkIDs, such as having the client and server both on the same computer + /// \param[in] senderId Which SystemAddress sent this packet. + /// \param[in] caller Which instance of ReplicaManager is calling this interface + /// \return See ReplicaReturnResult + virtual ReplicaReturnResult ReceiveConstruction(RakNet::BitStream *inBitStream, RakNetTime timestamp, NetworkID networkID, NetworkIDObject *existingObject, SystemAddress senderId, ReplicaManager *caller)=0; +}; + +/// An interface for a class that handles the call to send the download complete notification +/// See ReplicaManager::SetDownloadCompleteCB +/// \ingroup REPLICA_MANAGER_GROUP1 +class SendDownloadCompleteInterface +{ +public: + SendDownloadCompleteInterface() {} + virtual ~SendDownloadCompleteInterface() {} + + /// \param[out] outBitStream Write whatever you want to this bitstream. It will arrive in the receiveDownloadCompleteCB callback. + /// \param[in] currentTime The current time that would be returned by RakNet::GetTime(). That's a slow call I do already, so you can use the parameter instead of having to call it yourself. + /// \param[in] senderId Who we are sending to + /// \param[in] caller Which instance of ReplicaManager is calling this interface + /// \return See ReplicaReturnResult + virtual ReplicaReturnResult SendDownloadComplete(RakNet::BitStream *outBitStream, RakNetTime currentTime, SystemAddress senderId, ReplicaManager *caller)=0; +}; + +/// An interface for a class that handles the call to receive the download complete notification +/// See ReplicaManager::SetDownloadCompleteCB +/// \ingroup REPLICA_MANAGER_GROUP +class ReceiveDownloadCompleteInterface +{ +public: + ReceiveDownloadCompleteInterface() {} + virtual ~ReceiveDownloadCompleteInterface() {} + + /// \param[in] inBitStream The bitstream that was written to in SendDownloadCompleteInterface::SendDownloadComplete + /// \param[in] senderId The SystemAddress of the system that send the datagram + /// \param[in] caller Which instance of ReplicaManager is calling this interface + /// \return See ReplicaReturnResult + virtual ReplicaReturnResult ReceiveDownloadComplete(RakNet::BitStream *inBitStream, SystemAddress senderId, ReplicaManager *caller)=0; +}; + +/// \deprecated See RakNet::ReplicaManager3 + +/// ReplicaManager is a system manager for your game objects that performs the following tasks: +/// 1. Tracks all locally registered game objects and players and only performs operations to and for those objects and players +/// 2. Allows you to automatically turn off unneeded local and remote functions for your game objects, thus providing convenience and security against unauthorized sends. +/// 3. Sends notifications of existing game objects to new connections, including a download complete message. +/// 4. Sends notifications of new game objects to existing players. +/// A. Serialize and scoping calls are not sent to objects that were not notified of that object. +/// B. Notification calls can be canceled on a per-object basis. Object notification sends are tracked on a per-system per-object basis. +/// 5. Configurable per-system per-object scoping. +/// A. Scoping provides a mechanism to hide and unhide remote objects without destroying the whole object, used when when entities should not be destroyed but are currently not visible to systems. +/// B. Serialize calls are not sent to hidden objects. +/// C. Scoping calls can be canceled on a per-object basis. Scope is tracked on a per-system per-object basis. +/// 6. Replicate, SetScope, SignalSerializeNeeded, and the corresponding Replica interfaces are processed in RakPeer::Receive, rather than immediately. +/// A. This allows the ReplicaManager to reorganize function calls in order by dependency. This allows out of order calls, per-object call cancellation (which also cancels dependencies), and per-object call delays (which also delays dependencies) +/// B. For example, although SetScope and SignalSerializeNeeded have a dependency on SetNetworkID(), you can still call them in the constructor and call SetNetworkID() later, as long as it happens before calling RakPeer::Receive() +/// 7. The system is fast, uses little memory, and is intentionally hands off such that it can work with any game architecture and network topology +/// +/// What the ReplicaManager system does NOT do for you +/// 1. Actually create or destroy your game objects +/// 2. Associate object destruction events with remote system disconnects. +/// 3. Set networkIDs via SetNetworkID() on newly created objects. +/// 4. Object sub-serialization. Serialize only granular on the level of entire objects. If you want to serialize part of the object, you need to set your own flags and indicate in the BitStream which parts were sent and which not. +/// \brief A management system for your game objects and players to make serialization, scoping, and object creation and destruction easier. +/// \pre You must call RakPeer::SetNetworkIDManager to use this plugin. +/// \ingroup REPLICA_MANAGER_GROUP1 +class RAK_DLL_EXPORT ReplicaManager : public PluginInterface2 +{ +public: + // Constructor + ReplicaManager(); + + // Destructor + virtual ~ReplicaManager(); + + /// If you think all objects should have been removed, call this to assert on any that were not. + /// Useful for debugging shutdown or restarts + void AssertReplicatedObjectsClear(void); + + /// If you think all participants should have been removed, call this to assert on any that were not. + /// Useful for debugging shutdown or restarts + void AssertParticipantsClear(void); + + /// Do or don't automatically call AddParticipant when new systems connect to us. + /// Won't add automatically add connections that already exist before this was called + /// Defaults to false + /// \param[in] autoAdd True or false, to add or not + void SetAutoParticipateNewConnections(bool autoAdd); + + /// Adds a participant to the ReplicaManager system. Only these participants get packets and we only accept ReplicaManager packets from these participants. + /// This way you can have connections that have nothing to do with your game - for example remote console logins + /// \param[in] systemAddress Which player you are referring to + /// \return True on success, false on participant already exists + bool AddParticipant(SystemAddress systemAddress); + + /// Removes a participant from the data replicator system + /// This is called automatically on ID_DISCONNECTION_NOTIFICATION and ID_CONNECTION_LOST messages, as well as CloseConnection() calls. + /// \param[in] systemAddress Which player you are referring to + /// \return True on success, false on participant does not exist + bool RemoveParticipant(SystemAddress systemAddress); + + /// Construct the specified object on the specified system + /// Replica::SendConstruction will be called immediately, this is a change from before, because otherwise if you later send other packets that refer to this object, this object won't exist yet. + /// The other system will get Replica::ReceiveConstruction + /// If your system assigns NetworkIDs, do so before calling Replicate as the NetworkID is automatically included in the packet. + /// Replicate packets that are sent to systems that already have this NetworkID are ignored. + /// \note Objects which are replicated get exactly one call to SendConstruction for every player / object permutation. + /// \note To perform scoping and serialize updates on an object already created by another system, call Construct with \a isCopy true. + /// \note Setting \a isCopy true will consider the object created on that system without actually trying to create it. + /// \note If you don't need to send updates to other systems for this object, it is more efficient to use ReferencePointer instead. + /// \note In a client / server environment, be sure to call Construct() with isCopy true to let the ReplicaManager know that the server has this object. Otherwise you won't be able to send Scope or Serialize changes to the server. + /// \param[in] replica A pointer to your object + /// \param[in] isCopy True means that this is a copy of an object that already exists on the systems specified by \a systemAddress and \a broadcast. If true, we will consider these systems as having the object without sending a datagram to them. SendConstruction will NOT be called for objects which \a isCopy is true. + /// \param[in] systemAddress The participant to send the command to, or the one to exclude if broadcast is true. + /// \param[in] broadcast True to send to all. If systemAddress!=UNASSIGNED_SYSTEM_ADDRESS then this means send to all but that participant + void Construct(Replica *replica, bool isCopy, SystemAddress systemAddress, bool broadcast); + + /// Call this with your game objects to have them send Replica::SendDestruction. + /// This will be sent immediately to all participants that have this object. Those participants will get Replica::ReceiveDestruction + /// All pending calls for this object, for this player, are canceled. + /// Nothing is actually deleted - this just signals that the other system called this function. It is up to you to actually delete your object. + /// \pre Call Replicate with this object first. + /// \pre For the other system to get the network message, SetNetworkID on that object must have been called with the same value as GetNetworkID for this object. + /// \note Call Destruct before DereferencePointer if you plan on calling both, since Destruct will fail with no pointer reference. + /// \note Calling ( with systemAddress==UNASSIGNED_SYSTEM_ADDRESS and broadcast true is equivalent to calling DereferencePointer except that Destruct also sends the destruct packet. + /// \note It is important to call this before deleting your object. Otherwise this system will crash the next Update call. + /// \param[in] replica A pointer to your object + /// \param[in] systemAddress The participant to send the command to, or the one to exclude if broadcast is true. + /// \param[in] broadcast True to send to all systems that have the object. If systemAddress!=UNASSIGNED_SYSTEM_ADDRESS then this means send to all but that participant + void Destruct(Replica *replica, SystemAddress systemAddress, bool broadcast); + + /// This makes sure the object is tracked, so you can get calls on it. + /// This will automatically happen if you call Construct, SetScope, or SignalSerializeNeeded with \a replica + /// Otherwise you need to call this, or for security the system will ignore calls that reference this object, even if given a valid NetworkID + /// Duplicate calls are safe and are simply ignored. + /// Best place to put this is in the SetReceiveConstructionCB callback so that all your objects are registered. + /// \param[in] replica A pointer to your object + void ReferencePointer(Replica *replica); + + /// Call this before you delete \a replica. This locally removes all references to this pointer. + /// No messages are sent. + /// Best place to put this is in the destructor of \a replica + /// \param[in] replica A pointer to your object + void DereferencePointer(Replica *replica); + + /// Sets the scope of your object in relation to another participant. + /// Objects that are in-scope for that participant will send out Replica::Serialize calls. Otherwise Serialize calls are not sent. + /// Scoping is useful when you want to disable sends to an object temporarily, without deleting that object. + /// Calling this results in Replica::SendScopeChange being called on the local object and Replica::ReceiveScopeChange on the remote object if that object has been created on that remote system. + /// Your game should ensure that objects not in scope are hidden, but not deallocated, on the remote system. + /// Replica::SendScopeChange with \a inScope as true will automatically perform Replica::Serialize + /// \pre Call Replicate with this object first. + /// \pre For the other system to get the network message, that object must have an NetworkID (set by SetNetworkID()) the same as our object's NetworkID (returned from GetNetworkID()). + /// \note You can set the default scope with SetDefaultScope() + /// \note Individual objects can refuse to perform the SendScopeChange call by not writing to the output bitstream while returning true. + /// \param[in] replica An object previously registered with Replicate + /// \param[in] inScope in scope or not. + /// \param[in] systemAddress The participant to send the command to, or the one to exclude if broadcast is true. + /// \param[in] broadcast True to send to all. If systemAddress!=UNASSIGNED_SYSTEM_ADDRESS then this means send to all but that participant + void SetScope(Replica *replica, bool inScope, SystemAddress systemAddress, bool broadcast); + + /// Signal that data has changed and we need to call Serialize() on the \a replica object. + /// This will happen if the object has been registered, Replica::SendConstruction wrote to outBitStream and returned true, and the object is in scope for this player. + /// \pre Call Replicate with this object first. + /// \pre For the other system to get the network message, that object must have an NetworkID (set by SetNetworkID()) the same as our object's NetworkID (returned from GetNetworkID()). + /// \param[in] replica An object previously registered with Replicate + /// \param[in] systemAddress The participant to send the command to, or the one to exclude if broadcast is true. + /// \param[in] broadcast True to send to all. If systemAddress!=UNASSIGNED_SYSTEM_ADDRESS then this means send to all but that participant + void SignalSerializeNeeded(Replica *replica, SystemAddress systemAddress, bool broadcast); + + /// Required callback + /// Set your callback to parse requests to create new objects. Specifically, when Replica::SendConstruction is called and the networkID of the object is either unset or can't be found, this callback will get that call. + /// How do you know what object to create? It's up to you, but I suggest in Replica::SendConstruction you encode the class name. The best way to do this is with the StringTable class. + /// \note If you return true from NetworkIDManager::IsNetworkIDAuthority, which you should do for a server or peer, I recommend also encoding the value returned by GetNetworkID() within Replica::SendConstruction into that bitstream and reading it here. Then set that value in a call to SetNetworkID. Dereplicate, SetScope, and SignalSerializeNeeded all rely on being able to call GET_OBJECT_FROM_ID which requires that SetNetworkID be called on that object. + /// \param[in] ReceiveConstructionInterface An instance of a class that implements ReceiveConstructionInterface + void SetReceiveConstructionCB(ReceiveConstructionInterface *receiveConstructionInterface); + + /// Set your callbacks to be called when, after connecting to another system, you get all objects that system is going to send to you when it is done with the first iteration through the object list. + /// Optional if you want to send and receive the download complete notification + /// \param[in] sendDownloadComplete A class that implements the SendDownloadCompleteInterface interface. + /// \param[in] receiveDownloadComplete A class that implements the ReceiveDownloadCompleteInterface interface. + /// \sa SendDownloadCompleteInterface , ReceiveDownloadCompleteInterface + void SetDownloadCompleteCB( SendDownloadCompleteInterface *sendDownloadComplete, ReceiveDownloadCompleteInterface *receiveDownloadComplete ); + + /// This channel will be used for all RakPeer::Send calls + /// \param[in] channel The channel to use for internal RakPeer::Send calls from this system. Defaults to 0. + void SetSendChannel(unsigned char channel); + + /// This means automatically construct all known objects to all new participants + /// Has no effect on existing participants + /// Useful if your architecture always has all objects constructed on all systems all the time anyway, or if you want them to normally start constructed + /// Defaults to false. + /// \param[in] autoConstruct true or false, as desired. + void SetAutoConstructToNewParticipants(bool autoConstruct); + + /// Set the default scope for new objects to all players. Defaults to false, which means Serialize will not be called for new objects automatically. + /// If you set this to true, then new players will get existing objects, and new objects will be sent to existing players + /// This only applies to players that connect and objects that are replicated after this call. Existing object scopes are not affected. + /// Useful to set to true if you don't use scope, or if all objects normally start in scope + /// \param[in] scope The default scope to use. + void SetDefaultScope(bool scope); + + /// When an object goes in scope for a system, you normally want to serialize that object to that system. + /// Setting this flag to true will call Serialize for you automatically when SendScopeChange returns REPLICA_PROCESSING_DONE and the scopeTrue parameter is true + /// Defaults to false + /// \param[in] autoSerialize True or false as needed. + void SetAutoSerializeInScope(bool autoSerialize); + + /// Processes all pending commands and does sends as needed. + /// This is called automatically when RakPeerInterface::Receive is called. + /// Depending on where you call RakPeerInterface::Receive you may also wish to call this manually for better responsiveness. + /// For example, if you call RakPeerInterface::Receive at the start of each game tick, this means you would have to wait a render cycle, causing + /// \param[in] peer Pointer to a valid instance of RakPeerInterface used to perform sends + void Update(void); + + /// Lets you enable calling any or all of the interface functions in an instance of Replica + /// This setting is the same for all participants for this object, so if you want per-participant permissions you will need to handle that inside your implementation + /// All functions enabled by default. + /// \param[in] replica The object you are referring to + /// \param[in] interfaceFlags A bitwise-OR of REPLICA_SEND_CONSTRUCTION ... REPLICA_SET_ALL corresponding to the function of the same name + void EnableReplicaInterfaces(Replica *replica, unsigned char interfaceFlags); + + /// Lets you disable calling any or all of the interface functions in an instance of Replica + /// This setting is the same for all participants for this object, so if you want per-participant permissions you will need to handle that inside your implementation + /// All functions enabled by default. + /// \note Disabling functions is very useful for security. + /// \note For example, on the server you may wish to disable all receive functions so clients cannot change server objects. + /// \param[in] replica The object you are referring to + /// \param[in] interfaceFlags A bitwise-OR of REPLICA_SEND_CONSTRUCTION ... REPLICA_SET_ALL corresponding to the function of the same name + void DisableReplicaInterfaces(Replica *replica, unsigned char interfaceFlags); + + /// Tells us if a particular system got a SendConstruction() message from this object. e.g. does this object exist on this remote system? + /// This is set by the user when calling Replicate and sending (any) data to outBitStream in Replica::SendConstruction + /// \param[in] replica The object we are checking + /// \param[in] systemAddress The system we are checking + bool IsConstructed(Replica *replica, SystemAddress systemAddress); + + /// Tells us if a particular object is in scope for a particular system + /// This is set by the user when calling SetScope and sending (any) data to outBitstream in Replica::SendScopeChange + /// \param[in] replica The object we are checking + /// \param[in] systemAddress The system we are checking + bool IsInScope(Replica *replica, SystemAddress systemAddress); + + /// Returns how many Replica instances are registered. + /// This number goes up with each non-duplicate call to Replicate and down with each non-duplicate call to Dereplicate + /// Used for GetReplicaAtIndex if you want to perform some object on all registered Replica objects. + /// \return How many replica objects are in the list of replica objects + unsigned GetReplicaCount(void) const; + + /// Returns a previously registered Replica *, from index 0 to GetReplicaCount()-1. + /// The order that Replica * objects are returned in is arbitrary (it currently happens to be ordered by pointer address). + /// Calling Dereplicate immediately deletes the Replica * passed to it, so if you call Dereplicate while using this function + /// the array will be shifted over and the current index will now reference the next object in the array, if there was one. + /// \param[in] index An index, from 0 to GetReplicaCount()-1. + /// \return A Replica * previously passed to Construct() + Replica *GetReplicaAtIndex(unsigned index); + + /// Returns the number of unique participants added with AddParticipant + /// As these systems disconnect, they are no longer participants, so this accurately returns how many participants are using the system + /// \return The number of unique participants added with AddParticipant + unsigned GetParticipantCount(void) const; + + /// Returns a SystemAddress previously added with AddParticipant + /// \param[in] index An index, from 0 to GetParticipantCount()-1. + /// \return A SystemAddress + SystemAddress GetParticipantAtIndex(unsigned index); + + /// Returns if a participant has been added + /// \return If this participant has been added + bool HasParticipant(SystemAddress systemAddress); + + /// Each participant has a per-remote object bitfield passed to the Replica::Serialize call. + /// This function can set or unset these flags for one or more participants at the same time. + /// Flags are NOT automatically cleared on serialize. You must clear them when you want to do so. + /// \param[in] replica An object previously registered with Replicate + /// \param[in] systemAddress The participant to set the flags for + /// \param[in] broadcast True to apply to all participants. If systemAddress!=UNASSIGNED_SYSTEM_ADDRESS then this means send to all but that participant + /// \param[in] set True set the bits set in \a flags with the per-object per-system serialization flags. False to unset these bits. + /// \param[in] flags Modifier to the Per-object per-system flags sent to Replica::Serialize. See the parameter /a set + void SignalSerializationFlags(Replica *replica, SystemAddress systemAddress, bool broadcast, bool set, unsigned int flags); + + /// Each participant has a per-remote object bitfield passed to the Replica::Serialize call. + /// This function is used to read and change these flags directly for a single participant. + /// It gives more control than SignalSerializationFlags but only works for a single participant at a time. + /// \param[in] replica An object previously registered with Replicate + /// \param[in] systemAddress The participant to read/write the flags for + /// \return A pointer to the per-object per-system flags sent to Replica::Serialize. You can read or modify the flags directly with this function. This pointer is only valid until the next call to RakPeer::Receive + unsigned int* AccessSerializationFlags(Replica *replica, SystemAddress systemAddress); + + // ---------------------------- ALL INTERNAL AFTER HERE ---------------------------- + + enum + { + // Treat the object as on the remote system, and send a packet + REPLICA_EXPLICIT_CONSTRUCTION=1<<0, + // Treat the object as on the remote system, but do not send a packet. Overridden by REPLICA_EXPLICIT_CONSTRUCTION. + REPLICA_IMPLICIT_CONSTRUCTION=1<<1, + REPLICA_SCOPE_TRUE=1<<2, // Mutually exclusive REPLICA_SCOPE_FALSE + REPLICA_SCOPE_FALSE=1<<3, // Mutually exclusive REPLICA_SCOPE_TRUE + REPLICA_SERIALIZE=1<<4, + }; + + /// \internal + /// One pointer and a command to act on that pointer + struct CommandStruct + { + Replica *replica; // Pointer to an external object - not allocated here. + unsigned char command; // This is one of the enums immediately above. + unsigned int userFlags; + }; + + struct RegisteredReplica + { + Replica *replica; // Pointer to an external object - not allocated here. + RakNetTime lastDeserializeTrue; // For replicatedObjects it's the last time deserialize returned true. + unsigned char allowedInterfaces; // Replica interface flags + unsigned int referenceOrder; // The order in which we started tracking this object. Used so autoconstruction can send objects in-order + }; + + struct RemoteObject + { + Replica *replica; // Pointer to an external object - not allocated here. + bool inScope; // Is replica in scope or not? + RakNetTime lastSendTime; + unsigned int userFlags; + }; + + struct ReceivedCommand + { + SystemAddress systemAddress; + NetworkID networkID; + unsigned command; // A packetID + unsigned u1; + RakNet::BitStream *userData; + }; + + + static int RegisteredReplicaComp( Replica* const &key, const ReplicaManager::RegisteredReplica &data ); + static int RegisteredReplicaRefOrderComp( const unsigned int &key, const ReplicaManager::RegisteredReplica &data ); + static int RemoteObjectComp( Replica* const &key, const ReplicaManager::RemoteObject &data ); + static int CommandStructComp( Replica* const &key, const ReplicaManager::CommandStruct &data ); + + /// \internal + /// One remote system + struct ParticipantStruct + { + ~ParticipantStruct(); + + // The player this participant struct represents. + SystemAddress systemAddress; + + // Call sendDownloadCompleteCB when REPLICA_SEND_CONSTRUCTION is done for all objects in commandList + // This variable tracks if we did it yet or not. + bool callDownloadCompleteCB; + + // Sorted list of Replica*, sorted by pointer, along with a command to perform on that pointer. + // Ordering is just for fast lookup. + // Nothing is allocated inside this list + // DataStructures::OrderedList commandList; + // June 4, 2007 - Don't sort commands in the command list. The game replies on processing the commands in order + DataStructures::List commandList; + + // Sorted list of Replica*, sorted by pointer, along with if that object is inScope or not for this system + // Only objects that exist on the remote system are in this list, so not all objects are necessarily in this list + DataStructures::OrderedList remoteObjectList; + + // List of pending ReceivedCommand to process + DataStructures::Queue pendingCommands; + }; + + static int ParticipantStructComp( const SystemAddress &key, ReplicaManager::ParticipantStruct * const &data ); + +protected: + /// Frees all memory + void Clear(void); + // Processes a struct representing a received command + ReplicaReturnResult ProcessReceivedCommand(ParticipantStruct *participantStruct, ReceivedCommand *receivedCommand); + unsigned GetCommandListReplicaIndex(const DataStructures::List &commandList, Replica *replica, bool *objectExists) const; + + // Plugin interface functions + PluginReceiveResult OnReceive(Packet *packet); + void OnClosedConnection(SystemAddress systemAddress, RakNetGUID rakNetGUID, PI2_LostConnectionReason lostConnectionReason ); + void OnRakPeerShutdown(void); + void OnNewConnection(SystemAddress systemAddress, RakNetGUID rakNetGUID, bool isIncoming); + + /// List of objects replicated in the Replicate function. + /// Used to make sure queued actions happen on valid pointers, since objects are removed from the list in Dereplicate + /// Sorted by raw pointer address using the default sort + DataStructures::OrderedList replicatedObjects; + + /// List of participants + /// Each participant has several queues of pending commands + /// Sorted by systemAddress + /// The only complexity is that each participant also needs a list of objects that mirror the variable replicatedObjects so we know per-player if that object is in scope + DataStructures::OrderedList participantList; + + // Internal functions + ParticipantStruct* GetParticipantBySystemAddress(const SystemAddress systemAddress) const; + + // Callback pointers. + + // Required callback to handle construction calls + ReceiveConstructionInterface *_constructionCB; + + // Optional callbacks to send and receive download complete. + SendDownloadCompleteInterface *_sendDownloadCompleteCB; + ReceiveDownloadCompleteInterface *_receiveDownloadCompleteCB; + + // Channel to do send calls on. All calls are reliable ordered except for Replica::Serialize + unsigned char sendChannel; + + // Stores what you pass to SetAutoParticipateNewConnections + bool autoParticipateNewConnections; + bool autoSerializeInScope; + + bool defaultScope; + bool autoConstructToNewParticipants; + unsigned int nextReferenceIndex; + +#ifdef _DEBUG + // Check for and assert on recursive calls to update + bool inUpdate; +#endif +}; + + +#endif + +#endif // _RAKNET_SUPPORT_* diff --git a/RakNet/Sources/ReplicaManager2.cpp b/RakNet/Sources/ReplicaManager2.cpp new file mode 100644 index 0000000..90121b5 --- /dev/null +++ b/RakNet/Sources/ReplicaManager2.cpp @@ -0,0 +1,2075 @@ +#include "NativeFeatureIncludes.h" +#if _RAKNET_SUPPORT_ReplicaManager2==1 + +#include "ReplicaManager2.h" +#include "MessageIdentifiers.h" +#include "RakAssert.h" +#include "RakPeerInterface.h" +#include "NetworkIDManager.h" + +using namespace RakNet; + +unsigned char Replica2::clientSharedID=0; +Replica2* Replica2::clientPtrArray[256]; + + +#ifdef _MSC_VER +#pragma warning( push ) +#endif + +bool SerializationContext::IsSerializationCommand(SerializationType r) +{ + return r>=SEND_SERIALIZATION_GENERIC_TO_SYSTEM && r<=RELAY_SERIALIZATION_TO_SYSTEMS; +} +bool SerializationContext::IsDownloadCommand(SerializationType r) +{ + return r>=SEND_CONSTRUCTION_SERIALIZATION_AUTO_INITIAL_DOWNLOAD_TO_SYSTEM && r<=SEND_DATA_SERIALIZATION_AUTO_INITIAL_DOWNLOAD_TO_SYSTEM; +} +bool SerializationContext::IsDestructionCommand(SerializationType r) +{ + return r>=SEND_DESTRUCTION_GENERIC_TO_SYSTEM && r<=RELAY_DESTRUCTION_TO_SYSTEMS; +} +bool SerializationContext::IsConstructionCommand(SerializationType r) +{ + return r>=SEND_CONSTRUCTION_GENERIC_TO_SYSTEM && r<=SEND_CONSTRUCTION_REPLY_DENIED_TO_CLIENT; +} +bool SerializationContext::IsVisibilityCommand(SerializationType r) +{ + return r>=SEND_VISIBILITY_TRUE_TO_SYSTEM && r<=RELAY_VISIBILITY_FALSE_TO_SYSTEMS; +} +bool SerializationContext::IsVisible(SerializationType r) +{ + return r==SEND_VISIBILITY_TRUE_TO_SYSTEM || r==BROADCAST_VISIBILITY_TRUE_TO_SYSTEM || r==RELAY_VISIBILITY_TRUE_TO_SYSTEMS; +} + +int ReplicaManager2::Replica2CompByNetworkID( const NetworkID &key, RakNet::Replica2 * const &data ) +{ + if (key < data->GetNetworkID()) + return -1; + if (key == data->GetNetworkID()) + return 0; + return 1; +} + +int ReplicaManager2::Replica2ObjectComp( RakNet::Replica2 * const &key, RakNet::Replica2 * const &data ) +{ + if (key->GetAllocationNumber()GetAllocationNumber()) + return -1; + if (key->GetAllocationNumber()==data->GetAllocationNumber()) + return 0; + return 1; +} + +int ReplicaManager2::Connection_RM2CompBySystemAddress( const SystemAddress &key, RakNet::Connection_RM2 * const &data ) +{ + if (key < data->GetSystemAddress()) + return -1; + if (key == data->GetSystemAddress()) + return 0; + return 1; +} + +ReplicaManager2::ReplicaManager2() +{ + connectionFactoryInterface=0; + defaultOrderingChannel=0; + defaultPacketPriority=HIGH_PRIORITY; + defaultPacketReliablity=RELIABLE_ORDERED; + autoUpdateConstruction=true; + autoUpdateVisibility=true; + autoAddNewConnections=true; + doReplicaAutoUpdate=true; + DataStructures::OrderedList::IMPLEMENT_DEFAULT_COMPARISON(); +} +ReplicaManager2::~ReplicaManager2() +{ +} +void ReplicaManager2::SendConstruction(Replica2 *replica, BitStream *replicaData, SystemAddress recipient, RakNetTime timestamp, bool sendMessage, + DataStructures::OrderedList &exclusionList, + unsigned char localClientId, SerializationType type, + PacketPriority priority, PacketReliability reliability, char orderingChannel) +{ + if (replica==0) + return; + + // Why would you request an object you already have? + if (replica->GetNetworkID()!=UNASSIGNED_NETWORK_ID && replica->QueryIsConstructionAuthority()==false) + return; + + if (recipient==UNASSIGNED_SYSTEM_ADDRESS && connectionList.Size()==0) + return; + + RakAssert(replica->QueryConstruction(0)!=BQR_NEVER); + + bool newConnection; + Connection_RM2* connection; + + // Add to list of replicas if not already there + bool newReference; + Reference(replica, &newReference); + + RakNet::BitStream bs; + WriteHeader(&bs, ID_REPLICA_MANAGER_CONSTRUCTION, timestamp); + bs.Write((unsigned char) type); + bs.Write(replica->GetNetworkID()); + bs.Write(localClientId); + + if (recipient!=UNASSIGNED_SYSTEM_ADDRESS) + { + connection = AutoCreateConnection(recipient, &newConnection); + if (connection==0) + return; + + if (newConnection) + { + // If a new connection, the replica was constructed automatically anyway + DownloadToNewConnection(connection, timestamp, defaultPacketPriority, defaultPacketReliablity, defaultOrderingChannel); + return; + } + + if (AddToAndWriteExclusionList(recipient, &bs, exclusionList)==false) + return; + bs.AlignWriteToByteBoundary(); + bs.Write(replicaData); + + // Lookup connection by target. If does not exist, create + + AddConstructionReference(connection, replica); + if (sendMessage) + { + // Send the packet + Send(&bs, recipient, priority, reliability, orderingChannel); + + if (newReference && replica->QueryVisibility(connection)==BQR_ALWAYS) + { +// SerializationContext sc; +// sc.recipientAddress=recipient; +// sc.timestamp=timestamp; +// sc.relaySourceAddress=UNASSIGNED_SYSTEM_ADDRESS; +// sc.serializationType=SEND_SERIALIZATION_GENERIC_TO_SYSTEM; + replica->SendSerialize(recipient, SEND_SERIALIZATION_CONSTRUCTION_TO_SYSTEM); + } + } + } + else + { + DataStructures::OrderedList culledOutput; + CullByAndAddToExclusionList(connectionList, culledOutput, exclusionList); + WriteExclusionList(&bs, exclusionList); + bs.AlignWriteToByteBoundary(); + bs.Write(replicaData); + + unsigned i; + for (i=0; i < culledOutput.Size(); i++) + { + connection=culledOutput[i]; + AddConstructionReference(connection, replica); + + if (sendMessage) + Send(&bs, connection->GetSystemAddress(), priority, reliability, orderingChannel); + + if (newReference && replica->QueryIsSerializationAuthority() && replica->QueryVisibility(connection)==BQR_ALWAYS) + { +// SerializationContext sc; +// sc.recipientAddress=connection->GetSystemAddress(); +// sc.timestamp=0; +// sc.relaySourceAddress=UNASSIGNED_SYSTEM_ADDRESS; +// sc.serializationType=BROADCAST_SERIALIZATION_GENERIC_TO_SYSTEM; + replica->SendSerialize(connection->GetSystemAddress(), BROADCAST_SERIALIZATION_CONSTRUCTION_TO_SYSTEM); + } + } + } +} + +void ReplicaManager2::SendDestruction(Replica2 *replica, BitStream *replicaData, SystemAddress recipient, RakNetTime timestamp, bool sendMessage, + DataStructures::OrderedList &exclusionList, + SerializationType type, + PacketPriority priority, PacketReliability reliability, char orderingChannel) +{ + if (replica==0) + return; + + if (recipient==UNASSIGNED_SYSTEM_ADDRESS && connectionList.Size()==0) + return; + + if (replica->QueryIsDestructionAuthority()==false) + return; + + if (recipient!=UNASSIGNED_SYSTEM_ADDRESS) + { + bool newConnection; + + // Lookup connection by target. If does not exist, create + Connection_RM2* connection = AutoCreateConnection(recipient, &newConnection); + if (connection==0) + return; + + // Have this system stop referencing the replica object + connection->Deref(replica); + + if (newConnection) + { + // If a new connection, the object didn't exist anyway + DownloadToNewConnection(connection, timestamp, defaultPacketPriority, defaultPacketReliablity, defaultOrderingChannel); + return; + } + } + + if (sendMessage && replica->GetNetworkID()!=UNASSIGNED_NETWORK_ID) + { + // Send the packet + RakNet::BitStream bs; + WriteHeader(&bs, ID_REPLICA_MANAGER_DESTRUCTION, timestamp); + bs.Write((unsigned char) type); + bs.Write(replica->GetNetworkID()); + + if (recipient!=UNASSIGNED_SYSTEM_ADDRESS) + { + if (AddToAndWriteExclusionList(recipient, &bs, exclusionList)==false) + return; + bs.AlignWriteToByteBoundary(); + bs.Write(replicaData); + + Send(&bs, recipient, priority, reliability, orderingChannel); + } + else + { + DataStructures::OrderedList output, culledOutput; + GetConnectionsWithReplicaConstructed(replica, output); + CullByAndAddToExclusionList(output, culledOutput, exclusionList); + WriteExclusionList(&bs, exclusionList); + bs.AlignWriteToByteBoundary(); + bs.Write(replicaData); + + unsigned i; + for (i=0; i < output.Size(); i++) + Send(&bs, output[i]->GetSystemAddress(), priority, reliability, orderingChannel); + } + } +} + +void ReplicaManager2::SendSerialize(Replica2 *replica, BitStream *replicaData, SystemAddress recipient, RakNetTime timestamp, + DataStructures::OrderedList &exclusionList, + SerializationType type, + PacketPriority priority, PacketReliability reliability, char orderingChannel) +{ + if (replica==0) + return; + + if (recipient==UNASSIGNED_SYSTEM_ADDRESS && connectionList.Size()==0) + return; + + if (replica->GetNetworkID()==UNASSIGNED_NETWORK_ID) + return; + + if (replica->QueryIsSerializationAuthority()==false) + return; + + // Add to list of replicas if not already there + bool newReference; + Reference(replica, &newReference); + + if (newReference && replica->QueryConstruction(0)==BQR_ALWAYS) + { + replica->BroadcastConstruction(); + } + + bool newConnection; + Connection_RM2* connection; + + RakNet::BitStream bs; + WriteHeader(&bs, ID_REPLICA_MANAGER_SERIALIZE, timestamp); + bs.Write((unsigned char) type); + bs.Write(replica->GetNetworkID()); + + if (recipient!=UNASSIGNED_SYSTEM_ADDRESS) + { + connection = AutoCreateConnection(recipient, &newConnection); + if (connection==0) + return; + if (newConnection) + DownloadToNewConnection(connection, timestamp, defaultPacketPriority, defaultPacketReliablity, defaultOrderingChannel); + + if (AddToAndWriteExclusionList(recipient, &bs, exclusionList)==false) + return; + bs.AlignWriteToByteBoundary(); + bs.Write(replicaData); + + // Send the packet + Send(&bs, recipient, priority, reliability, orderingChannel); + } + else + { + DataStructures::OrderedList output, culledOutput; + GetConnectionsWithSerializeVisibility(replica, output); + + CullByAndAddToExclusionList(output, culledOutput, exclusionList); + WriteExclusionList(&bs, exclusionList); + bs.AlignWriteToByteBoundary(); + bs.Write(replicaData); + + unsigned i; + for (i=0; i < culledOutput.Size(); i++) + { + connection=culledOutput[i]; + Send(&bs, connection->GetSystemAddress(), priority, reliability, orderingChannel); + } + } +} +void ReplicaManager2::SendVisibility(Replica2 *replica, BitStream *replicaData, SystemAddress recipient, RakNetTime timestamp, + DataStructures::OrderedList &exclusionList, + SerializationType type, + PacketPriority priority, PacketReliability reliability, char orderingChannel) +{ + if (replica==0) + return; + + bool newConnection; + Connection_RM2* connection; + + if (recipient==UNASSIGNED_SYSTEM_ADDRESS && connectionList.Size()==0) + return; + + if (replica->GetNetworkID()==UNASSIGNED_NETWORK_ID) + return; + + // Add to list of replicas if not already there + bool newReference; + Reference(replica, &newReference); + + if (newReference && replica->QueryConstruction(0)==BQR_ALWAYS) + { + replica->BroadcastConstruction(); + } + + RakNet::BitStream bs; + WriteHeader(&bs, ID_REPLICA_MANAGER_SCOPE_CHANGE, timestamp); + bs.Write((unsigned char) type); + bs.Write(replica->GetNetworkID()); + + if (recipient!=UNASSIGNED_SYSTEM_ADDRESS) + { + if (AddToAndWriteExclusionList(recipient, &bs, exclusionList)==false) + return; + + connection = AutoCreateConnection(recipient, &newConnection); + if (connection==0) + return; + if (newConnection) + DownloadToNewConnection(connection, timestamp, defaultPacketPriority, defaultPacketReliablity, defaultOrderingChannel); + + bs.AlignWriteToByteBoundary(); + bs.Write(replicaData); + + if (SerializationContext::IsVisibilityCommand(type)) + { + if (SerializationContext::IsVisible(type)) + AddVisibilityReference(connection, replica); + else + RemoveVisibilityReference(connection, replica); + } + // Send the packet + Send(&bs, recipient, priority, reliability, orderingChannel); + } + else + { + DataStructures::OrderedList culledOutput; + CullByAndAddToExclusionList(connectionList, culledOutput, exclusionList); + WriteExclusionList(&bs, exclusionList); + bs.AlignWriteToByteBoundary(); + bs.Write(replicaData); + + unsigned i; + for (i=0; i < culledOutput.Size(); i++) + { + connection=culledOutput[i]; + if (SerializationContext::IsVisible(type)) + AddVisibilityReference(connection, replica); + else + RemoveVisibilityReference(connection, replica); + Send(&bs, connection->GetSystemAddress(), priority, reliability, orderingChannel); + } + } +} +void ReplicaManager2::Dereference(Replica2 *replica) +{ + unsigned i; + if (replica==0) + return; + + for (i=0; i < connectionList.Size(); i++) + { + connectionList[i]->lastConstructionList.RemoveIfExists(replica); + connectionList[i]->lastSerializationList.RemoveIfExists(replica); + } + + for (i=0; i < fullReplicaUnorderedList.Size(); i++) + { + if (fullReplicaUnorderedList[i]==replica) + { + fullReplicaUnorderedList.RemoveAtIndex(i); + break; + } + } + + fullReplicaOrderedList.RemoveIfExists(replica); + alwaysDoConstructReplicaOrderedList.RemoveIfExists(replica); + alwaysDoSerializeReplicaOrderedList.RemoveIfExists(replica); + variableConstructReplicaOrderedList.RemoveIfExists(replica); + variableSerializeReplicaOrderedList.RemoveIfExists(replica); +} +void ReplicaManager2::Update(void) +{ + unsigned i; + + if (autoUpdateConstruction || autoUpdateVisibility) + { + for (i=0; i < connectionList.Size(); i++) + { + if (autoUpdateConstruction) + connectionList[i]->SetConstructionByReplicaQuery(this); + if (autoUpdateVisibility) + connectionList[i]->SetVisibilityByReplicaQuery(this); + } + } + + if (doReplicaAutoUpdate) + { + RakNetTime currentTime = RakNet::GetTime(); + for (i=0; i < fullReplicaUnorderedList.Size(); i++) + { + fullReplicaUnorderedList[i]->ElapseAutoSerializeTimers(currentTime-lastUpdateTime,false); + } + lastUpdateTime=currentTime; + } + +} +unsigned ReplicaManager2::GetReplicaCount(void) const +{ + return fullReplicaUnorderedList.Size(); +} +Replica2 *ReplicaManager2::GetReplicaAtIndex(unsigned index) +{ + return fullReplicaUnorderedList[index]; +} +void ReplicaManager2::SetAutoAddNewConnections(bool autoDownload) +{ + autoAddNewConnections=autoDownload; +} +void ReplicaManager2::SetDoReplicaAutoSerializeUpdate(bool autoUpdate) +{ + doReplicaAutoUpdate=autoUpdate; +} +void ReplicaManager2::SetConnectionFactory(Connection_RM2Factory *factory) +{ + RakAssert(factory); + connectionFactoryInterface=factory; +} +unsigned ReplicaManager2::GetConnectionCount(void) const +{ + return connectionList.Size(); +} +Connection_RM2* ReplicaManager2::GetConnectionAtIndex(unsigned index) const +{ + return connectionList[index]; +} +Connection_RM2* ReplicaManager2::GetConnectionBySystemAddress(SystemAddress systemAddress) const +{ + bool objectExists; + unsigned index = connectionList.GetIndexFromKey(systemAddress, &objectExists); + if (objectExists) + return connectionList[index]; + return 0; +} +unsigned int ReplicaManager2::GetConnectionIndexBySystemAddress(SystemAddress systemAddress) const +{ + bool objectExists; + unsigned index = connectionList.GetIndexFromKey(systemAddress, &objectExists); + if (objectExists) + return index; + return (unsigned int) -1; +} +void ReplicaManager2::SetDefaultOrderingChannel(char def) +{ + defaultOrderingChannel=def; +} +void ReplicaManager2::SetDefaultPacketPriority(PacketPriority def) +{ + defaultPacketPriority=def; +} +void ReplicaManager2::SetDefaultPacketReliability(PacketReliability def) +{ + defaultPacketReliablity=def; +} +void ReplicaManager2::SetAutoUpdateScope(bool construction, bool visibility) +{ + autoUpdateConstruction=construction; + autoUpdateVisibility=visibility; +} +void ReplicaManager2::RecalculateVisibility(Replica2 *replica) +{ + Dereference(replica); + bool newReference; + Reference(replica, &newReference); + + if (replica->QueryConstruction(0)==BQR_NEVER && autoUpdateConstruction) + { + // Destroy on all systems + replica->SendDestruction(UNASSIGNED_SYSTEM_ADDRESS, SEND_DESTRUCTION_VISIBILITY_RECALCULATION_TO_SYSTEM); + } + if (replica->QueryConstruction(0)==BQR_ALWAYS && autoUpdateConstruction) + { + // Create on all systems + replica->SendConstruction(UNASSIGNED_SYSTEM_ADDRESS, SEND_CONSTRUCTION_VISIBILITY_RECALCULATION_TO_SYSTEM); + } + if (replica->QueryVisibility(0)==BQR_ALWAYS && autoUpdateVisibility) + { + // Set visibility and creation on all systems + replica->SendVisibility(UNASSIGNED_SYSTEM_ADDRESS, UNDEFINED_REASON); + replica->SendSerialize(UNASSIGNED_SYSTEM_ADDRESS, UNDEFINED_REASON); + } +} +void ReplicaManager2::GetConnectionsWithReplicaConstructed(Replica2 *replica, DataStructures::OrderedList &output) +{ + BooleanQueryResult res; + res = replica->QueryConstruction(0); + if (res==BQR_ALWAYS) + { + output=connectionList; + } + else if (res!=BQR_NEVER) + { + unsigned i; + for (i=0; i < connectionList.Size(); i++) + { + if (connectionList[i]->lastConstructionList.HasData(replica)) + output.Insert(connectionList[i]->GetSystemAddress(),connectionList[i], false, __FILE__,__LINE__); + } + } +} +void ReplicaManager2::GetConnectionsWithSerializeVisibility(Replica2 *replica, DataStructures::OrderedList &output) +{ + BooleanQueryResult res; + res = replica->QueryVisibility(0); + if (res==BQR_ALWAYS) + { + GetConnectionsWithReplicaConstructed(replica, output); + } + else if (res!=BQR_NEVER) + { + unsigned i; + for (i=0; i < connectionList.Size(); i++) + { + if (connectionList[i]->lastSerializationList.HasData(replica)) + output.Insert(connectionList[i]->GetSystemAddress(),connectionList[i], false, __FILE__,__LINE__); + } + } +} +RakPeerInterface *ReplicaManager2::GetRakPeer(void) const +{ + return rakPeerInterface; +} +Connection_RM2* ReplicaManager2::AutoCreateConnection(SystemAddress systemAddress, bool *newConnection) +{ + if (autoAddNewConnections) + return CreateConnectionIfDoesNotExist(systemAddress, newConnection); + else + { + bool objectExists; + unsigned index = connectionList.GetIndexFromKey(systemAddress, &objectExists); + *newConnection=false; + if (objectExists==false) + { + return 0; + } + return connectionList[index]; + } +} +Connection_RM2* ReplicaManager2::CreateConnectionIfDoesNotExist(SystemAddress systemAddress, bool *newConnection) +{ + bool objectExists; + unsigned index = connectionList.GetIndexFromKey(systemAddress, &objectExists); + if (objectExists==false) + { + // If it crashes here, you need to call SetConnection_RM2Factory + Connection_RM2 *connection = connectionFactoryInterface->AllocConnection(); + connection->SetSystemAddress(systemAddress); + connection->SetGuid(rakPeerInterface->GetGuidFromSystemAddress(systemAddress)); + connectionList.Insert(systemAddress, connection, false, __FILE__,__LINE__); + *newConnection=true; + return connection; + } + *newConnection=false; + return connectionList[index]; +} +bool ReplicaManager2::AddNewConnection(SystemAddress systemAddress) +{ + bool newConnection; + Connection_RM2* connection = CreateConnectionIfDoesNotExist(systemAddress, &newConnection); + if (newConnection) + DownloadToNewConnection(connection, 0, defaultPacketPriority, defaultPacketReliablity, defaultOrderingChannel); + return newConnection; +} +bool ReplicaManager2::RemoveConnection(SystemAddress systemAddress) +{ + unsigned int index = GetConnectionIndexBySystemAddress(systemAddress); + if (index!=(unsigned int) -1) + { + connectionFactoryInterface->DeallocConnection(connectionList[index]); + connectionList.RemoveAtIndex(index); + return true; + } + return false; +} +bool ReplicaManager2::HasConnection(SystemAddress systemAddress) +{ + unsigned int index = GetConnectionIndexBySystemAddress(systemAddress); + return index!=(unsigned int) -1; +} +void ReplicaManager2::Reference(Replica2* replica, bool *newReference) +{ + replica->SetReplicaManager(this); + + bool objectExists; + unsigned index = fullReplicaOrderedList.GetIndexFromKey(replica,&objectExists); + if (objectExists==false) + { + fullReplicaUnorderedList.Insert(replica, __FILE__, __LINE__); + fullReplicaOrderedList.InsertAtIndex(replica, index, __FILE__,__LINE__); + + BooleanQueryResult queryResult; + queryResult = replica->QueryConstruction(0); + if (queryResult==BQR_ALWAYS) + alwaysDoConstructReplicaOrderedList.Insert(replica,replica, false, __FILE__,__LINE__); + else if (queryResult!=BQR_NEVER) + variableConstructReplicaOrderedList.Insert(replica,replica, false, __FILE__,__LINE__); + queryResult = replica->QueryVisibility(0); + if (queryResult==BQR_ALWAYS) + alwaysDoSerializeReplicaOrderedList.Insert(replica,replica, false, __FILE__,__LINE__); + else if (queryResult!=BQR_NEVER) + variableSerializeReplicaOrderedList.Insert(replica,replica, false, __FILE__,__LINE__); + + if (newReference) + *newReference=true; + + return; + } + if (newReference) + *newReference=false; +} +void ReplicaManager2::AddConstructionReference(Connection_RM2* connection, Replica2* replica) +{ + if (replica->QueryIsConstructionAuthority() && replica->QueryConstruction(0)!=BQR_ALWAYS && replica->QueryConstruction(0)!=BQR_NEVER) + connection->lastConstructionList.Insert(replica, replica, false, __FILE__,__LINE__); +} +void ReplicaManager2::AddVisibilityReference(Connection_RM2* connection, Replica2* replica) +{ + if (replica->QueryIsVisibilityAuthority() && replica->QueryVisibility(0)!=BQR_ALWAYS && replica->QueryVisibility(0)!=BQR_NEVER) + connection->lastSerializationList.Insert(replica, replica, false, __FILE__,__LINE__); +} +void ReplicaManager2::RemoveVisibilityReference(Connection_RM2* connection, Replica2* replica) +{ + if (replica->QueryIsVisibilityAuthority() && replica->QueryVisibility(0)!=BQR_ALWAYS && replica->QueryVisibility(0)!=BQR_NEVER) + connection->lastSerializationList.RemoveIfExists(replica); +} +void ReplicaManager2::WriteHeader(RakNet::BitStream *bs, MessageID type, RakNetTime timestamp) +{ + if (timestamp!=0) + { + bs->Write((MessageID)ID_TIMESTAMP); + bs->Write(timestamp); + } + bs->Write(type); +} +void ReplicaManager2::OnClosedConnection(SystemAddress systemAddress, RakNetGUID rakNetGUID, PI2_LostConnectionReason lostConnectionReason ) +{ + (void) systemAddress; + (void) rakNetGUID; + (void) lostConnectionReason; + + RemoveConnection(systemAddress); +} +void ReplicaManager2::OnRakPeerShutdown(void) +{ + Clear(); +} +void ReplicaManager2::OnAttach(void) +{ + lastUpdateTime=RakNet::GetTime(); +} +void ReplicaManager2::OnNewConnection(SystemAddress systemAddress, RakNetGUID rakNetGUID, bool isIncoming) +{ + (void) systemAddress; + (void) rakNetGUID; + (void) isIncoming; + + if (autoAddNewConnections) + AddNewConnection(systemAddress); +} +PluginReceiveResult ReplicaManager2::OnReceive(Packet *packet) +{ + RakNetTime timestamp=0; + unsigned char packetIdentifier, packetDataOffset; + if ( ( unsigned char ) packet->data[ 0 ] == ID_TIMESTAMP ) + { + if ( packet->length > sizeof( unsigned char ) + sizeof( RakNetTime ) ) + { + packetIdentifier = ( unsigned char ) packet->data[ sizeof( unsigned char ) + sizeof( RakNetTime ) ]; + // Required for proper endian swapping + RakNet::BitStream tsBs(packet->data+sizeof(MessageID),packet->length-1,false); + tsBs.Read(timestamp); + packetDataOffset=sizeof( unsigned char )*2 + sizeof( RakNetTime ); + } + else + return RR_STOP_PROCESSING_AND_DEALLOCATE; + } + else + { + packetIdentifier = ( unsigned char ) packet->data[ 0 ]; + packetDataOffset=sizeof( unsigned char ); + } + + switch (packetIdentifier) + { + case ID_REPLICA_MANAGER_DOWNLOAD_STARTED: + return OnDownloadStarted(packet->data+packetDataOffset, packet->length-packetDataOffset, packet->systemAddress, timestamp); + case ID_REPLICA_MANAGER_DOWNLOAD_COMPLETE: + return OnDownloadComplete(packet->data+packetDataOffset, packet->length-packetDataOffset, packet->systemAddress, timestamp); + case ID_REPLICA_MANAGER_CONSTRUCTION: + return OnConstruction(packet->data+packetDataOffset, packet->length-packetDataOffset, packet->systemAddress, timestamp); + case ID_REPLICA_MANAGER_DESTRUCTION: + return OnDestruction(packet->data+packetDataOffset, packet->length-packetDataOffset, packet->systemAddress, timestamp); + case ID_REPLICA_MANAGER_SCOPE_CHANGE: + return OnVisibilityChange(packet->data+packetDataOffset, packet->length-packetDataOffset, packet->systemAddress, timestamp); + case ID_REPLICA_MANAGER_SERIALIZE: + return OnSerialize(packet->data+packetDataOffset, packet->length-packetDataOffset, packet->systemAddress, timestamp); + } + + return RR_CONTINUE_PROCESSING; +} +PluginReceiveResult ReplicaManager2::OnDownloadStarted(unsigned char *packetData, int packetDataLength, SystemAddress sender, RakNetTime timestamp) +{ + RakNet::BitStream incomingBitstream(packetData, packetDataLength, false); + bool newConnection; + Connection_RM2* connection = AutoCreateConnection(sender, &newConnection); + if (connection==0) + return RR_CONTINUE_PROCESSING; + SerializationType serializationType; + unsigned char c; + incomingBitstream.Read(c); + serializationType=(SerializationType) c; + incomingBitstream.AlignReadToByteBoundary(); + connection->DeserializeDownloadStarted(&incomingBitstream, sender, this, timestamp, serializationType); + + if (newConnection) + DownloadToNewConnection(connection, timestamp, defaultPacketPriority, defaultPacketReliablity, defaultOrderingChannel); + return RR_STOP_PROCESSING_AND_DEALLOCATE; +} +PluginReceiveResult ReplicaManager2::OnDownloadComplete(unsigned char *packetData, int packetDataLength, SystemAddress sender, RakNetTime timestamp) +{ + RakNet::BitStream incomingBitstream(packetData, packetDataLength, false); + bool newConnection; + Connection_RM2* connection = AutoCreateConnection(sender, &newConnection); + if (connection==0) + return RR_CONTINUE_PROCESSING; + SerializationType serializationType; + unsigned char c; + incomingBitstream.Read(c); + serializationType=(SerializationType) c; + incomingBitstream.AlignReadToByteBoundary(); + connection->DeserializeDownloadComplete(&incomingBitstream, sender, this, timestamp, serializationType); + + if (newConnection) + DownloadToNewConnection(connection, timestamp, defaultPacketPriority, defaultPacketReliablity, defaultOrderingChannel); + return RR_STOP_PROCESSING_AND_DEALLOCATE; +} + +PluginReceiveResult ReplicaManager2::OnConstruction(unsigned char *packetData, int packetDataLength, SystemAddress sender, RakNetTime timestamp) +{ + RakNet::BitStream incomingBitstream(packetData, packetDataLength, false); + bool newConnection; + Connection_RM2* connection = AutoCreateConnection(sender, &newConnection); + if (connection==0) + return RR_CONTINUE_PROCESSING; + SerializationType serializationType; + unsigned char c; + incomingBitstream.Read(c); + serializationType=(SerializationType) c; + NetworkID networkId=UNASSIGNED_NETWORK_ID; + unsigned char localClientId=255; + bool success; + incomingBitstream.Read(networkId); + success=incomingBitstream.Read(localClientId); + RakAssert(success); + + DataStructures::OrderedList exclusionList; + ReadExclusionList(&incomingBitstream, exclusionList); + exclusionList.Insert(sender,sender, false, __FILE__,__LINE__); + + Replica2* replica; + // The prefix misaligns the data from the send, which is a problem if the user uses aligned data + incomingBitstream.AlignReadToByteBoundary(); + replica = connection->ReceiveConstruct(&incomingBitstream, networkId, sender, localClientId, serializationType, this, timestamp,exclusionList); + if (replica) + { + // Register this object on this connection + AddConstructionReference(connection, replica); + } + return RR_STOP_PROCESSING_AND_DEALLOCATE; +} +PluginReceiveResult ReplicaManager2::OnDestruction(unsigned char *packetData, int packetDataLength, SystemAddress sender, RakNetTime timestamp) +{ + if (HasConnection(sender)==false) + return RR_CONTINUE_PROCESSING; + + RakNet::BitStream incomingBitstream(packetData, packetDataLength, false); + SerializationType serializationType; + unsigned char c; + incomingBitstream.Read(c); + serializationType=(SerializationType) c; + NetworkID networkId; + incomingBitstream.Read(networkId); + DataStructures::OrderedList exclusionList; + ReadExclusionList(&incomingBitstream, exclusionList); + exclusionList.Insert(sender,sender, false, __FILE__,__LINE__); + Replica2 * replica = rakPeerInterface->GetNetworkIDManager()->GET_OBJECT_FROM_ID( networkId ); + if (replica) + { + // Verify that this is a registered object, so it actually is a Replica2 + if (fullReplicaOrderedList.HasData((Replica2 *)replica)==false) + { + // This object is not registered + return RR_STOP_PROCESSING_AND_DEALLOCATE; + } + + // The prefix misaligns the data from the send, which is a problem if the user uses aligned data + incomingBitstream.AlignReadToByteBoundary(); + replica->ReceiveDestruction(sender, &incomingBitstream, serializationType, timestamp,exclusionList ); + } + // else this object is unknown + + + return RR_STOP_PROCESSING_AND_DEALLOCATE; +} +PluginReceiveResult ReplicaManager2::OnVisibilityChange(unsigned char *packetData, int packetDataLength, SystemAddress sender, RakNetTime timestamp) +{ + RakNet::BitStream incomingBitstream(packetData, packetDataLength, false); + bool newConnection; + Connection_RM2* connection = AutoCreateConnection(sender, &newConnection); + if (connection==0) + return RR_CONTINUE_PROCESSING; + SerializationType serializationType; + + unsigned char c; + incomingBitstream.Read(c); + serializationType=(SerializationType) c; + NetworkID networkId; + incomingBitstream.Read(networkId); + DataStructures::OrderedList exclusionList; + ReadExclusionList(&incomingBitstream, exclusionList); + exclusionList.Insert(sender,sender, false, __FILE__,__LINE__); + + Replica2 *replica = rakPeerInterface->GetNetworkIDManager()->GET_OBJECT_FROM_ID( networkId ); + if (replica) + { + // Verify that this is a registered object, so it actually is a Replica2 + if (fullReplicaOrderedList.HasData((Replica2 *)replica)==false) + { + RakAssert(0); + return RR_STOP_PROCESSING_AND_DEALLOCATE; + } + + // The prefix misaligns the data from the send, which is a problem if the user uses aligned data + incomingBitstream.AlignReadToByteBoundary(); + replica->ReceiveVisibility(sender, &incomingBitstream, serializationType, timestamp,exclusionList); + + AddConstructionReference(connection, replica); + + // Update the last known visibility list + if (SerializationContext::IsVisibilityCommand(serializationType)) + { + if (SerializationContext::IsVisible(serializationType)) + AddVisibilityReference(connection, replica); + else + RemoveVisibilityReference(connection, replica); + } + } + return RR_STOP_PROCESSING_AND_DEALLOCATE; +} +PluginReceiveResult ReplicaManager2::OnSerialize(unsigned char *packetData, int packetDataLength, SystemAddress sender, RakNetTime timestamp) +{ + RakNet::BitStream incomingBitstream(packetData, packetDataLength, false); + Connection_RM2* connection = GetConnectionBySystemAddress(sender); + if (connection==0) + return RR_CONTINUE_PROCESSING; + SerializationType serializationType; + unsigned char c; + incomingBitstream.Read(c); + serializationType=(SerializationType) c; + NetworkID networkId; + incomingBitstream.Read(networkId); + DataStructures::OrderedList exclusionList; + ReadExclusionList(&incomingBitstream, exclusionList); + exclusionList.Insert(sender,sender, false, __FILE__,__LINE__); + + Replica2 *replica = rakPeerInterface->GetNetworkIDManager()->GET_OBJECT_FROM_ID( networkId ); + if (replica) + { + // Verify that this is a registered object, so it actually is a Replica2 + if (fullReplicaOrderedList.HasData((Replica2 *)replica)==false) + { + RakAssert(0); + return RR_STOP_PROCESSING_AND_DEALLOCATE; + } + + exclusionList.Insert(sender,sender, false, __FILE__,__LINE__); + + // The prefix misaligns the data from the send, which is a problem if the user uses aligned data + incomingBitstream.AlignReadToByteBoundary(); + replica->ReceiveSerialize(sender, &incomingBitstream, serializationType, timestamp,exclusionList); + + AddConstructionReference(connection, replica); + } + return RR_STOP_PROCESSING_AND_DEALLOCATE; +} +bool ReplicaManager2::AddToAndWriteExclusionList(SystemAddress recipient, RakNet::BitStream *bs, DataStructures::OrderedList &exclusionList) +{ + if (exclusionList.HasData(recipient)) + return false; + exclusionList.Insert(recipient,recipient,true, __FILE__,__LINE__); + WriteExclusionList(bs,exclusionList); + return true; +} +void ReplicaManager2::WriteExclusionList(RakNet::BitStream *bs, DataStructures::OrderedList &exclusionList) +{ + bs->WriteCompressed(exclusionList.Size()); + for (unsigned exclusionListIndex=0; exclusionListIndex < exclusionList.Size(); exclusionListIndex++ ) + bs->Write(exclusionList[exclusionListIndex]); +} + +void ReplicaManager2::CullByAndAddToExclusionList( + DataStructures::OrderedList &inputList, + DataStructures::OrderedList &culledOutput, + DataStructures::OrderedList &exclusionList) +{ + Connection_RM2* connection; + unsigned i; + unsigned exclusionListIndex=0; + for (i=0; i < inputList.Size(); i++) + { + connection=inputList[i]; + while (exclusionListIndex < exclusionList.Size() && exclusionList[exclusionListIndex] < connection->GetSystemAddress()) + exclusionListIndex++; + if (exclusionListIndex < exclusionList.Size() && exclusionList[exclusionListIndex]==connection->GetSystemAddress()) + { + exclusionListIndex++; + continue; + } + culledOutput.InsertAtEnd(connection, __FILE__,__LINE__); + } + + for (i=0; i < culledOutput.Size(); i++) + exclusionList.Insert(culledOutput[i]->GetSystemAddress(),culledOutput[i]->GetSystemAddress(),true, __FILE__,__LINE__); +} +void ReplicaManager2::ReadExclusionList(RakNet::BitStream *bs, DataStructures::OrderedList &exclusionList) +{ + unsigned int exclusionListSize; + bs->ReadCompressed(exclusionListSize); + for (unsigned exclusionListIndex=0; exclusionListIndex < exclusionListSize; exclusionListIndex++) + { + SystemAddress systemToExclude; + bs->Read(systemToExclude); + exclusionList.InsertAtEnd(systemToExclude, __FILE__,__LINE__); + } +} +void ReplicaManager2::Send(RakNet::BitStream *bs, SystemAddress recipient, PacketPriority priority, PacketReliability reliability, char orderingChannel) +{ + if (priority==NUMBER_OF_PRIORITIES) + priority=defaultPacketPriority; + if (reliability==NUMBER_OF_RELIABILITIES) + reliability=defaultPacketReliablity; + if (orderingChannel==-1) + orderingChannel=defaultOrderingChannel; + SendUnified(bs, priority,reliability,orderingChannel,recipient,false); +} +void ReplicaManager2::Clear(void) +{ + fullReplicaUnorderedList.Clear(false, __FILE__, __LINE__); + fullReplicaOrderedList.Clear(false, __FILE__, __LINE__); + alwaysDoConstructReplicaOrderedList.Clear(false, __FILE__, __LINE__); + alwaysDoSerializeReplicaOrderedList.Clear(false, __FILE__, __LINE__); + variableConstructReplicaOrderedList.Clear(false, __FILE__, __LINE__); + variableSerializeReplicaOrderedList.Clear(false, __FILE__, __LINE__); + unsigned i; + for (i=0; i < connectionList.Size(); i++) + connectionFactoryInterface->DeallocConnection(connectionList[i]); + connectionList.Clear(false, __FILE__, __LINE__); +} +void ReplicaManager2::DownloadToNewConnection(Connection_RM2* connection, RakNetTime timestamp, PacketPriority priority, PacketReliability reliability, char orderingChannel) +{ + unsigned int i; + RakNet::BitStream bs, bs2; + BooleanQueryResult bqr; + SystemAddress systemAddress = connection->GetSystemAddress(); + SerializationContext serializationContext; + serializationContext.recipientAddress=systemAddress; + serializationContext.relaySourceAddress=UNASSIGNED_SYSTEM_ADDRESS; + serializationContext.serializationType=SEND_CONSTRUCTION_SERIALIZATION_AUTO_INITIAL_DOWNLOAD_TO_SYSTEM; + serializationContext.timestamp=0; + + // bs2 is so SerializeDownloadStarted can change the timestamp + bs2.AlignWriteToByteBoundary(); + connection->SerializeDownloadStarted(&bs2, this, &serializationContext); + WriteHeader(&bs, ID_REPLICA_MANAGER_DOWNLOAD_STARTED, timestamp); + bs.Write((unsigned char) SEND_CONSTRUCTION_SERIALIZATION_AUTO_INITIAL_DOWNLOAD_TO_SYSTEM); + bs.Write(&bs2); + Send(&bs, connection->GetSystemAddress(), priority, reliability, orderingChannel); + + DataStructures::List initialDownloadList; + DataStructures::List culledDownloadList; + connection->SortInitialDownload(fullReplicaUnorderedList, initialDownloadList); + + // Construct all objects before serializing them. This way the recipient will have valid NetworkID references. + // Send all objects that always exist + for (i=0; i < initialDownloadList.Size(); i++) + { + if (initialDownloadList[i]->QueryIsConstructionAuthority()) + { + bqr=initialDownloadList[i]->QueryConstruction(connection); + if (bqr==BQR_ALWAYS || bqr==BQR_YES) + { + initialDownloadList[i]->SendConstruction(systemAddress, SEND_CONSTRUCTION_SERIALIZATION_AUTO_INITIAL_DOWNLOAD_TO_SYSTEM); + culledDownloadList.Insert(initialDownloadList[i], __FILE__, __LINE__ ); + } + // Remember for this particular connection that we already sent this update to this system + if (bqr==BQR_YES) + AddConstructionReference(connection, initialDownloadList[i]); + } + } + + bool notVisible; + + // Send all objects that are always visible + for (i=0; i < culledDownloadList.Size(); i++) + { + notVisible=false; + if (culledDownloadList[i]->QueryIsVisibilityAuthority()) + { + bqr=culledDownloadList[i]->QueryVisibility(connection); + if (bqr==BQR_ALWAYS || bqr==BQR_YES) + { + culledDownloadList[i]->SendVisibility(systemAddress, SEND_VISIBILITY_AUTO_INITIAL_DOWNLOAD_TO_SYSTEM); + // Remember for this particular connection that we already sent this update to this system + if (bqr==BQR_YES) + AddVisibilityReference(connection, culledDownloadList[i]); + } + else + notVisible=true; + } + + if (culledDownloadList[i]->QueryIsSerializationAuthority() && notVisible==false) + culledDownloadList[i]->SendSerialize(systemAddress, SEND_DATA_SERIALIZATION_AUTO_INITIAL_DOWNLOAD_TO_SYSTEM); + } + + bs.Reset(); + // bs2 is so SerializeDownloadComplete can change the timestamp + bs2.AlignWriteToByteBoundary(); + connection->SerializeDownloadComplete(&bs2, this,&serializationContext); + WriteHeader(&bs, ID_REPLICA_MANAGER_DOWNLOAD_COMPLETE, timestamp); + bs.Write((unsigned char) SEND_CONSTRUCTION_SERIALIZATION_AUTO_INITIAL_DOWNLOAD_TO_SYSTEM); + bs.Write(&bs2); + Send(&bs, connection->GetSystemAddress(), priority, reliability, orderingChannel); +} +Replica2::Replica2() +{ + rm2=0; + hasClientID=false; + + DataStructures::Map::IMPLEMENT_DEFAULT_COMPARISON(); +} +Replica2::~Replica2() +{ + DereferenceFromDestruction(); +} + +void Replica2::SetReplicaManager(ReplicaManager2* rm) +{ + rm2=rm; + if (GetNetworkIDManager()==0) + SetNetworkIDManager(rm->GetRakPeer()->GetNetworkIDManager()); +} +ReplicaManager2* Replica2::GetReplicaManager(void) const +{ + return rm2; +} +bool Replica2::SerializeDestruction(RakNet::BitStream *bitStream, SerializationContext *serializationContext) +{ + (void) bitStream; + (void) serializationContext; + + return true; +} +bool Replica2::Serialize(RakNet::BitStream *bitStream, SerializationContext *serializationContext) +{ + (void) bitStream; + (void) serializationContext; + + return true; +} +bool Replica2::SerializeVisibility(RakNet::BitStream *bitStream, SerializationContext *serializationContext) +{ + (void) bitStream; + (void) serializationContext; + + return true; +} +void Replica2::DeserializeDestruction(RakNet::BitStream *bitStream, SerializationType serializationType, SystemAddress sender, RakNetTime timestamp) +{ + (void) bitStream; + (void) serializationType; + (void) sender; + (void) timestamp; + +} +void Replica2::Deserialize(RakNet::BitStream *bitStream, SerializationType serializationType, SystemAddress sender, RakNetTime timestamp) +{ + (void) bitStream; + (void) serializationType; + (void) sender; + (void) timestamp; +} +void Replica2::DeserializeVisibility(RakNet::BitStream *bitStream, SerializationType serializationType, SystemAddress sender, RakNetTime timestamp) +{ + (void) bitStream; + (void) serializationType; + (void) sender; + (void) timestamp; +} +void Replica2::SendConstruction(SystemAddress recipientAddress, SerializationType serializationType) +{ + RakNet::BitStream bs; + SerializationContext defaultContext; + + if (serializationType==UNDEFINED_REASON) + { + if (QueryIsConstructionAuthority()==false) + defaultContext.serializationType=SEND_CONSTRUCTION_REQUEST_TO_SERVER; + else + defaultContext.serializationType=SEND_CONSTRUCTION_GENERIC_TO_SYSTEM; + } + else + defaultContext.serializationType=serializationType; + + defaultContext.relaySourceAddress=UNASSIGNED_SYSTEM_ADDRESS; + defaultContext.recipientAddress=recipientAddress; + defaultContext.timestamp=0; + + unsigned char localId; + if (QueryIsConstructionAuthority()==false) + { + clientPtrArray[Replica2::clientSharedID]=this; + localId=Replica2::clientSharedID++; + } + else + localId=0; + + DataStructures::OrderedList exclusionList; + // // The prefix misaligns the data for the send, which is a problem if the user uses aligned data + bs.AlignWriteToByteBoundary(); + if (SerializeConstruction(&bs, &defaultContext)) + rm2->SendConstruction(this,&bs,recipientAddress,defaultContext.timestamp,true,exclusionList,localId,defaultContext.serializationType); +} +void Replica2::SendDestruction(SystemAddress recipientAddress, SerializationType serializationType) +{ + RakNet::BitStream bs; + SerializationContext defaultContext(serializationType, UNASSIGNED_SYSTEM_ADDRESS, recipientAddress,0); + + if (serializationType==UNDEFINED_REASON) + defaultContext.serializationType=SEND_DESTRUCTION_GENERIC_TO_SYSTEM; + + DataStructures::OrderedList exclusionList; + // // The prefix misaligns the data for the send, which is a problem if the user uses aligned data + bs.AlignWriteToByteBoundary(); + if (SerializeDestruction(&bs, &defaultContext)) + rm2->SendDestruction(this,&bs,recipientAddress,defaultContext.timestamp,true,exclusionList,defaultContext.serializationType); +} +void Replica2::SendSerialize(SystemAddress recipientAddress, SerializationType serializationType) +{ + RakNet::BitStream bs; + SerializationContext defaultContext(serializationType, UNASSIGNED_SYSTEM_ADDRESS, recipientAddress,0); + + if (serializationType==UNDEFINED_REASON) + defaultContext.serializationType=SEND_SERIALIZATION_GENERIC_TO_SYSTEM; + + // // The prefix misaligns the data for the send, which is a problem if the user uses aligned data + bs.AlignWriteToByteBoundary(); + if (Serialize(&bs, &defaultContext)) + { + DataStructures::OrderedList exclusionList; + rm2->SendSerialize(this,&bs,recipientAddress,defaultContext.timestamp,exclusionList,defaultContext.serializationType); + } +} +void Replica2::SendVisibility(SystemAddress recipientAddress, SerializationType serializationType) +{ + RakNet::BitStream bs; + SerializationContext defaultContext(serializationType, UNASSIGNED_SYSTEM_ADDRESS, recipientAddress,0); + + if (serializationType==UNDEFINED_REASON) + defaultContext.serializationType=SEND_VISIBILITY_TRUE_TO_SYSTEM; + + // // The prefix misaligns the data for the send, which is a problem if the user uses aligned data + bs.AlignWriteToByteBoundary(); + if (SerializeVisibility(&bs, &defaultContext)) + { + DataStructures::OrderedList exclusionList; + rm2->SendVisibility(this,&bs,recipientAddress,defaultContext.timestamp,exclusionList,defaultContext.serializationType); + } +} +void Replica2::BroadcastSerialize(SerializationContext *serializationContext) +{ + RakNet::BitStream bs; + SerializationContext defaultContext(BROADCAST_SERIALIZATION_GENERIC_TO_SYSTEM, UNASSIGNED_SYSTEM_ADDRESS, UNASSIGNED_SYSTEM_ADDRESS,0); + SerializationContext *usedContext; + if (serializationContext) + usedContext=serializationContext; + else + usedContext=&defaultContext; + + bool newReference; + rm2->Reference(this, &newReference); + + // If this is a new object, then before sending serialization we should send construction to all systems + if (newReference && QueryConstruction(0)==BQR_ALWAYS) + { + BroadcastConstruction(); + } + + DataStructures::OrderedList exclusionList; + for (unsigned i=0; i < rm2->GetConnectionCount(); i++) + { + usedContext->recipientAddress=rm2->GetConnectionAtIndex(i)->GetSystemAddress(); + if (usedContext->relaySourceAddress==usedContext->recipientAddress) + continue; + bs.Reset(); + // // The prefix misaligns the data for the send, which is a problem if the user uses aligned data + bs.AlignWriteToByteBoundary(); + if (Serialize(&bs, usedContext)==false) + continue; + exclusionList.Clear(false, __FILE__, __LINE__); + for (unsigned j=0; j < rm2->connectionList.Size(); j++) + { + if (rm2->connectionList[j]->GetSystemAddress()!=usedContext->recipientAddress) + exclusionList.InsertAtEnd(rm2->connectionList[j]->GetSystemAddress(), __FILE__,__LINE__); + } + rm2->SendSerialize(this,&bs,usedContext->recipientAddress,usedContext->timestamp,exclusionList,usedContext->serializationType); + } +} + +void Replica2::ReceiveSerialize(SystemAddress sender, RakNet::BitStream *serializedObject, SerializationType serializationType, RakNetTime timestamp, DataStructures::OrderedList &exclusionList ) +{ + // Force all autoserialize timers to go off early, so any variable changes from the Deserialize event do not themselves trigger an autoserialize event + ForceElapseAllAutoserializeTimers(false); + + // Deserialize the new data + Deserialize(serializedObject, serializationType, sender, timestamp); + + // Update last values for all autoserialize timers + ForceElapseAllAutoserializeTimers(true); + + SerializationContext serializationContext; + serializationContext.serializationType=RELAY_SERIALIZATION_TO_SYSTEMS; + serializationContext.timestamp=timestamp; + serializationContext.relaySourceAddress=sender; + + + BooleanQueryResult bqr; + RakNet::BitStream bs; + unsigned exclusionListIndex=0; + for (unsigned i=0; i < rm2->connectionList.Size(); i++) + { + serializationContext.recipientAddress=rm2->connectionList[i]->GetSystemAddress(); + while (exclusionListIndex < exclusionList.Size() && exclusionList[exclusionListIndex] < serializationContext.recipientAddress) + exclusionListIndex++; + if (exclusionListIndex < exclusionList.Size() && exclusionList[exclusionListIndex]==serializationContext.recipientAddress) + { + exclusionListIndex++; + continue; + } + + // Don't relay serializations if this object should not be visible + bqr=QueryVisibility(rm2->connectionList[i]); + if (bqr==BQR_NEVER || bqr==BQR_NO) + continue; + + bs.Reset(); + if (Serialize(&bs, &serializationContext)==false) + continue; + rm2->SendSerialize(this,&bs,serializationContext.recipientAddress,serializationContext.timestamp,exclusionList,serializationContext.serializationType); + } +} + +void Replica2::BroadcastConstruction(SerializationContext *serializationContext) +{ + RakNet::BitStream bs; + SerializationContext defaultContext(BROADCAST_CONSTRUCTION_GENERIC_TO_SYSTEM, UNASSIGNED_SYSTEM_ADDRESS, UNASSIGNED_SYSTEM_ADDRESS,0); + SerializationContext *usedContext; + if (serializationContext) + usedContext=serializationContext; + else + { + usedContext=&defaultContext; + if (QueryIsConstructionAuthority()==false) + defaultContext.serializationType=SEND_CONSTRUCTION_REQUEST_TO_SERVER; + } + + bool newReference; + rm2->Reference(this, &newReference); + + DataStructures::OrderedList exclusionList; + + for (unsigned i=0; i < rm2->GetConnectionCount(); i++) + { + usedContext->recipientAddress=rm2->GetConnectionAtIndex(i)->GetSystemAddress(); + if (usedContext->relaySourceAddress==usedContext->recipientAddress) + continue; + bs.Reset(); + if (SerializeConstruction(&bs, usedContext)==false) + continue; + unsigned char localId; + if (QueryIsConstructionAuthority()==false) + { + clientPtrArray[Replica2::clientSharedID]=this; + localId=Replica2::clientSharedID++; + } + else + localId=0; + exclusionList.Clear(false, __FILE__, __LINE__); + for (unsigned j=0; j < rm2->connectionList.Size(); j++) + { + if (rm2->connectionList[j]->GetSystemAddress()!=usedContext->recipientAddress) + exclusionList.InsertAtEnd(rm2->connectionList[j]->GetSystemAddress(), __FILE__,__LINE__); + } + rm2->SendConstruction(this,&bs,usedContext->recipientAddress,usedContext->timestamp,true,exclusionList, localId, usedContext->serializationType); + } + + + bool notVisible=false; + BooleanQueryResult bqr; + bqr=QueryVisibility(0); + + if (bqr==BQR_ALWAYS) + BroadcastVisibility(true); + else if (bqr==BQR_NEVER) + notVisible=true; + + if (notVisible==false) + BroadcastSerialize(); +} +Replica2 * Replica2::ReceiveConstructionReply(SystemAddress sender, BitStream *replicaData, bool constructionAllowed) +{ + (void) replicaData; + (void) sender; + + if (constructionAllowed==false) + { + //RakNet::OP_DELETE(this, __FILE__, __LINE__); + delete this; + return 0; + } + + return this; +} +void Replica2::DereferenceFromDestruction(void) +{ + if (rm2) + rm2->Dereference(this); + if (hasClientID) + clientPtrArray[clientID]=0; + ClearAutoSerializeTimers(); +} +void Replica2::BroadcastDestruction(SerializationContext *serializationContext) +{ + RakNet::BitStream bs; + SerializationContext defaultContext(BROADCAST_DESTRUCTION_GENERIC_TO_SYSTEM, UNASSIGNED_SYSTEM_ADDRESS, UNASSIGNED_SYSTEM_ADDRESS, 0); + SerializationContext *usedContext; + if (serializationContext) + usedContext=serializationContext; + else + usedContext=&defaultContext; + + DataStructures::OrderedList culledOutput; + DataStructures::OrderedList exclusionList; + rm2->CullByAndAddToExclusionList(rm2->connectionList, culledOutput, exclusionList); + + for (unsigned i=0; i < rm2->GetConnectionCount(); i++) + { + usedContext->recipientAddress=rm2->GetConnectionAtIndex(i)->GetSystemAddress(); + if (usedContext->relaySourceAddress==usedContext->recipientAddress) + continue; + bs.Reset(); + if (SerializeDestruction(&bs, usedContext)==false) + continue; + exclusionList.Clear(false, __FILE__, __LINE__); + for (unsigned j=0; j < rm2->connectionList.Size(); j++) + { + if (rm2->connectionList[j]->GetSystemAddress()!=usedContext->recipientAddress) + exclusionList.InsertAtEnd(rm2->connectionList[j]->GetSystemAddress(), __FILE__,__LINE__); + } + rm2->SendDestruction(this,&bs,usedContext->recipientAddress,usedContext->timestamp,true,exclusionList,usedContext->serializationType); + } +} +void Replica2::BroadcastVisibility(bool isVisible, SerializationContext *serializationContext) +{ + RakNet::BitStream bs; + SerializationContext defaultContext; + SerializationContext *usedContext; + + if (serializationContext) + { + usedContext=serializationContext; + } + else + { + if (isVisible) + defaultContext.serializationType=BROADCAST_VISIBILITY_TRUE_TO_SYSTEM; + else + defaultContext.serializationType=BROADCAST_VISIBILITY_FALSE_TO_SYSTEM; + defaultContext.relaySourceAddress=UNASSIGNED_SYSTEM_ADDRESS; + defaultContext.timestamp=0; + usedContext=&defaultContext; + } + + if ((QueryVisibility(0)==BQR_ALWAYS && isVisible==false) || + (QueryVisibility(0)==BQR_NEVER && isVisible==true)) + { + // This doesn't make sense + RakAssert(0); + return; + } + + bool newReference; + rm2->Reference(this, &newReference); + + // If this is a new object, then before sending visibility we should send construction to all systems + if (newReference && QueryConstruction(0)==BQR_ALWAYS) + { + BroadcastConstruction(); + } + + DataStructures::OrderedList exclusionList; + for (unsigned i=0; i < rm2->GetConnectionCount(); i++) + { + usedContext->recipientAddress=rm2->GetConnectionAtIndex(i)->GetSystemAddress(); + if (usedContext->relaySourceAddress==usedContext->recipientAddress) + continue; + bs.Reset(); + if (SerializeVisibility(&bs, usedContext)==false) + continue; + exclusionList.Clear(false, __FILE__, __LINE__); + for (unsigned j=0; j < rm2->connectionList.Size(); j++) + { + if (rm2->connectionList[j]->GetSystemAddress()!=usedContext->recipientAddress) + exclusionList.InsertAtEnd(rm2->connectionList[j]->GetSystemAddress(), __FILE__,__LINE__); + } + rm2->SendVisibility(this,&bs,usedContext->recipientAddress,usedContext->timestamp,exclusionList,usedContext->serializationType); + } + + if (newReference && QueryVisibility(0)==BQR_ALWAYS) + { + BroadcastSerialize(); + } +} + +void Replica2::ReceiveDestruction(SystemAddress sender, RakNet::BitStream *serializedObject, SerializationType serializationType, RakNetTime timestamp, DataStructures::OrderedList &exclusionList ) +{ + DeserializeDestruction(serializedObject, serializationType, sender, timestamp); + + SerializationContext serializationContext; + serializationContext.serializationType=RELAY_DESTRUCTION_TO_SYSTEMS; + serializationContext.relaySourceAddress=sender; + serializationContext.timestamp=0; + + RakNet::BitStream bs; + unsigned exclusionListIndex=0; + for (unsigned i=0; i < rm2->connectionList.Size(); i++) + { + serializationContext.recipientAddress=rm2->connectionList[i]->GetSystemAddress(); + + while (exclusionListIndex < exclusionList.Size() && exclusionList[exclusionListIndex] < serializationContext.recipientAddress) + exclusionListIndex++; + if (exclusionListIndex < exclusionList.Size() && exclusionList[exclusionListIndex]==serializationContext.recipientAddress) + { + exclusionListIndex++; + continue; + } + + bs.Reset(); + if (SerializeDestruction(&bs, &serializationContext)==false) + continue; + rm2->SendDestruction(this,&bs,serializationContext.recipientAddress,serializationContext.timestamp,true,exclusionList,serializationContext.serializationType); + } + + DeleteOnReceiveDestruction(sender, serializedObject, serializationType, timestamp, exclusionList); +} +void Replica2::DeleteOnReceiveDestruction(SystemAddress sender, RakNet::BitStream *serializedObject, SerializationType serializationType, RakNetTime timestamp, DataStructures::OrderedList &exclusionList ) +{ + (void) sender; + (void) serializedObject; + (void) serializationType; + (void) timestamp; + (void) exclusionList; + //RakNet::OP_DELETE(this, __FILE__, __LINE__); + delete this; +} +void Replica2::ReceiveVisibility(SystemAddress sender, RakNet::BitStream *serializedObject, SerializationType serializationType, RakNetTime timestamp, DataStructures::OrderedList &exclusionList) +{ + DeserializeVisibility(serializedObject, serializationType, sender, timestamp); + + SerializationContext serializationContext; + if (serializationType==SEND_VISIBILITY_TRUE_TO_SYSTEM || serializationType==BROADCAST_VISIBILITY_TRUE_TO_SYSTEM) + serializationContext.serializationType=RELAY_VISIBILITY_TRUE_TO_SYSTEMS; + else if (serializationType==SEND_VISIBILITY_FALSE_TO_SYSTEM || serializationType==BROADCAST_VISIBILITY_FALSE_TO_SYSTEM) + serializationContext.serializationType=RELAY_VISIBILITY_FALSE_TO_SYSTEMS; + else + serializationContext.serializationType=serializationType; + serializationContext.timestamp=timestamp; + serializationContext.relaySourceAddress=sender; + + RakNet::BitStream bs; + unsigned exclusionListIndex=0; + for (unsigned i=0; i < rm2->connectionList.Size(); i++) + { + serializationContext.recipientAddress=rm2->connectionList[i]->GetSystemAddress(); + + while (exclusionListIndex < exclusionList.Size() && exclusionList[exclusionListIndex] < serializationContext.recipientAddress) + exclusionListIndex++; + if (exclusionListIndex < exclusionList.Size() && exclusionList[exclusionListIndex]==serializationContext.recipientAddress) + { + exclusionListIndex++; + continue; + } + + bs.Reset(); + if (SerializeVisibility(&bs, &serializationContext)==false) + continue; + rm2->SendVisibility(this,&bs,serializationContext.recipientAddress,serializationContext.timestamp,exclusionList,serializationContext.serializationType); + } +} +BooleanQueryResult Replica2::QueryConstruction(Connection_RM2 *connection) +{ + (void) connection; + + return BQR_ALWAYS; +} +BooleanQueryResult Replica2::QueryVisibility(Connection_RM2 *connection) +{ + (void) connection; + + return BQR_ALWAYS; +} +bool Replica2::QueryIsConstructionAuthority(void) const +{ + return rm2->GetRakPeer()->GetNetworkIDManager()->IsNetworkIDAuthority(); +} +bool Replica2::QueryIsDestructionAuthority(void) const +{ + return rm2->GetRakPeer()->GetNetworkIDManager()->IsNetworkIDAuthority(); +} +bool Replica2::QueryIsVisibilityAuthority(void) const +{ + return rm2->GetRakPeer()->GetNetworkIDManager()->IsNetworkIDAuthority(); +} +bool Replica2::QueryIsSerializationAuthority(void) const +{ + return rm2->GetRakPeer()->GetNetworkIDManager()->IsNetworkIDAuthority(); +} +bool Replica2::AllowRemoteConstruction(SystemAddress sender, RakNet::BitStream *replicaData, SerializationType type, RakNetTime timestamp) +{ + (void) sender; + (void) replicaData; + (void) type; + (void) timestamp; + + return true; +} +void Replica2::ForceElapseAllAutoserializeTimers(bool resynchOnly) +{ + ElapseAutoSerializeTimers(99999999, resynchOnly); +} +void Replica2::OnConstructionComplete(RakNet::BitStream *replicaData, SystemAddress sender, SerializationType type, ReplicaManager2 *replicaManager, RakNetTime timestamp, NetworkID networkId, bool networkIDCollision) +{ + (void) replicaData; + (void) sender; + (void) type; + (void) replicaManager; + (void) timestamp; + (void) networkId; + (void) networkIDCollision; +} +void Replica2::ElapseAutoSerializeTimers(RakNetTime timeElapsed, bool resynchOnly) +{ + AutoSerializeEvent* ase; + unsigned i; + for (i=0; i < autoSerializeTimers.Size(); i++) + { + ase = autoSerializeTimers[i]; + if (ase->remainingCountdown>timeElapsed) + { + ase->remainingCountdown-=timeElapsed; + } + else + { + ase->remainingCountdown=ase->initialCountdown; + + RakNet::BitStream *lastWrite, *newWrite; + if (ase->writeToResult1) + { + newWrite=&ase->lastAutoSerializeResult1; + lastWrite=&ase->lastAutoSerializeResult2; + } + else + { + newWrite=&ase->lastAutoSerializeResult2; + lastWrite=&ase->lastAutoSerializeResult1; + } + newWrite->Reset(); + OnAutoSerializeTimerElapsed(ase->serializationType,newWrite,lastWrite,ase->remainingCountdown, resynchOnly); + ase->writeToResult1=!ase->writeToResult1; + + } + } +} +void Replica2::OnAutoSerializeTimerElapsed(SerializationType serializationType, RakNet::BitStream *output, RakNet::BitStream *lastOutput, RakNetTime lastAutoSerializeCountdown, bool resynchOnly) +{ + (void) lastAutoSerializeCountdown; + + SerializationContext context; + if (resynchOnly) + context.serializationType=AUTOSERIALIZE_RESYNCH_ONLY; + else + context.serializationType=serializationType; + context.relaySourceAddress=UNASSIGNED_SYSTEM_ADDRESS; + context.recipientAddress=UNASSIGNED_SYSTEM_ADDRESS; + context.timestamp=0; + if (Serialize(output, &context)) + { + if (resynchOnly==false && + output->GetNumberOfBitsUsed()>0 && + (output->GetNumberOfBitsUsed()!=lastOutput->GetNumberOfBitsUsed() || + memcmp(output->GetData(), lastOutput->GetData(), (size_t) output->GetNumberOfBytesUsed())!=0)) + { + BroadcastAutoSerialize(&context, output); + } + } +} +void Replica2::BroadcastAutoSerialize(SerializationContext *serializationContext, RakNet::BitStream *serializedObject) +{ + DataStructures::OrderedList exclusionList; + rm2->SendSerialize(this,serializedObject,UNASSIGNED_SYSTEM_ADDRESS, serializationContext->timestamp, exclusionList, BROADCAST_AUTO_SERIALIZE_TO_SYSTEM); +} +void Replica2::AddAutoSerializeTimer(RakNetTime interval, SerializationType serializationType, RakNetTime countdown ) +{ + if (countdown==(RakNetTime)-1) + countdown=interval; + if (autoSerializeTimers.Has(serializationType)) + { + AutoSerializeEvent *ase = autoSerializeTimers.Get(serializationType); + if (interval==0) + { + // Elapse this timer immediately, then go back to initialCountdown + ase->remainingCountdown=ase->initialCountdown; + + RakNet::BitStream *lastWrite, *newWrite; + if (ase->writeToResult1) + { + newWrite=&ase->lastAutoSerializeResult1; + lastWrite=&ase->lastAutoSerializeResult2; + } + else + { + newWrite=&ase->lastAutoSerializeResult2; + lastWrite=&ase->lastAutoSerializeResult1; + } + newWrite->Reset(); + + OnAutoSerializeTimerElapsed(serializationType,newWrite,lastWrite,ase->initialCountdown, false); + + ase->remainingCountdown=ase->initialCountdown; + } + else + { + ase->initialCountdown=interval; + ase->remainingCountdown=countdown; + } + } + else + { + AutoSerializeEvent *ase = RakNet::OP_NEW( __FILE__, __LINE__ ); + ase->serializationType=serializationType; + ase->initialCountdown=interval; + ase->remainingCountdown=countdown; + ase->writeToResult1=true; + + SerializationContext context; + context.serializationType=AUTOSERIALIZE_RESYNCH_ONLY; + context.relaySourceAddress=UNASSIGNED_SYSTEM_ADDRESS; + context.recipientAddress=UNASSIGNED_SYSTEM_ADDRESS; + context.timestamp=0; + Serialize(&ase->lastAutoSerializeResult2, &context); + + autoSerializeTimers.Set(serializationType,ase); + } +} +RakNetTime Replica2::GetTimeToNextAutoSerialize(SerializationType serializationType) +{ + if (autoSerializeTimers.Has(serializationType)) + { + AutoSerializeEvent *ase = autoSerializeTimers.Get(serializationType); + return ase->remainingCountdown; + } + return (RakNetTime)-1; +} +void Replica2::CancelAutoSerializeTimer(SerializationType serializationType) +{ + unsigned i=0; + while (i < autoSerializeTimers.Size()) + { + if (autoSerializeTimers[i]->serializationType==serializationType) + { + RakNet::OP_DELETE(autoSerializeTimers[i], __FILE__, __LINE__); + autoSerializeTimers.RemoveAtIndex(i); + } + else + i++; + } +} +void Replica2::ClearAutoSerializeTimers(void) +{ + unsigned i; + for (i=0; i < autoSerializeTimers.Size(); i++) + RakNet::OP_DELETE(autoSerializeTimers[i], __FILE__, __LINE__); + autoSerializeTimers.Clear(); +} +Connection_RM2::Connection_RM2() +{ + rakNetGuid=UNASSIGNED_RAKNET_GUID; + systemAddress=UNASSIGNED_SYSTEM_ADDRESS; +} +Connection_RM2::~Connection_RM2() +{ +} +void Connection_RM2::SetConstructionByList(DataStructures::OrderedList ¤tVisibility, ReplicaManager2 *replicaManager) +{ + (void) replicaManager; + + DataStructures::OrderedList exclusiveToCurrentConstructionList, exclusiveToLastConstructionList; + CalculateListExclusivity(currentVisibility, lastConstructionList, exclusiveToCurrentConstructionList, exclusiveToLastConstructionList); + + unsigned i; + for (i=0; i < exclusiveToCurrentConstructionList.Size(); i++) + { + // Construct + if (exclusiveToCurrentConstructionList[i]->QueryIsConstructionAuthority()) + { + exclusiveToCurrentConstructionList[i]->SendConstruction(systemAddress); + // lastConstructionList.Insert(exclusiveToCurrentConstructionList[i],exclusiveToCurrentConstructionList[i],true); + } + } + + for (i=0; i < exclusiveToLastConstructionList.Size(); i++) + { + // Destruction + if (exclusiveToLastConstructionList[i]->QueryIsDestructionAuthority()) + { + exclusiveToLastConstructionList[i]->SendDestruction(systemAddress); + lastConstructionList.RemoveIfExists(exclusiveToLastConstructionList[i]); + lastSerializationList.RemoveIfExists(exclusiveToLastConstructionList[i]); + } + } +} +void Connection_RM2::SetVisibilityByList(DataStructures::OrderedList ¤tVisibility, ReplicaManager2 *replicaManager) +{ + (void) replicaManager; + + DataStructures::OrderedList exclusiveToCurrentSerializationList, exclusiveToLastSerializationList; + CalculateListExclusivity(currentVisibility, lastSerializationList, exclusiveToCurrentSerializationList, exclusiveToLastSerializationList); + + unsigned i; + for (i=0; i < exclusiveToCurrentSerializationList.Size(); i++) + { + // In scope + if (exclusiveToCurrentSerializationList[i]->QueryIsVisibilityAuthority()) + { + exclusiveToCurrentSerializationList[i]->SendVisibility(systemAddress,SEND_VISIBILITY_TRUE_TO_SYSTEM); + exclusiveToCurrentSerializationList[i]->SendSerialize(systemAddress); + // lastSerializationList.Insert(exclusiveToCurrentSerializationList[i],exclusiveToCurrentSerializationList[i], true); + } + } + + for (i=0; i < exclusiveToLastSerializationList.Size(); i++) + { + // Out of scope + if (exclusiveToLastSerializationList[i]->QueryIsVisibilityAuthority()) + { + exclusiveToLastSerializationList[i]->SendVisibility(systemAddress,SEND_VISIBILITY_FALSE_TO_SYSTEM); + lastSerializationList.RemoveIfExists(exclusiveToLastSerializationList[i]); + } + } +} +void Connection_RM2::CalculateListExclusivity( + const DataStructures::OrderedList &listOne, + const DataStructures::OrderedList &listTwo, + DataStructures::OrderedList &exclusiveToListOne, + DataStructures::OrderedList &exclusiveToListTwo + ) const +{ + int res; + unsigned listOneIndex=0, listTwoIndex=0; + + while (listOneIndex0) + { + exclusiveToListTwo.InsertAtEnd(listTwo[listTwoIndex], __FILE__,__LINE__); + listTwoIndex++; + } + else + { + listOneIndex++; + listTwoIndex++; + } + } + + while (listOneIndex constructedObjects; + + unsigned i; + BooleanQueryResult res; + for (i=0; i < replicaManager->variableConstructReplicaOrderedList.Size(); i++) + { + if (replicaManager->variableConstructReplicaOrderedList[i]->QueryIsConstructionAuthority()) + { + res = replicaManager->variableConstructReplicaOrderedList[i]->QueryConstruction(this); + if (res==BQR_YES || res==BQR_ALWAYS) // TODO - optimize ALWAYS here + constructedObjects.InsertAtEnd(replicaManager->variableConstructReplicaOrderedList[i], __FILE__,__LINE__); + } + } + + SetConstructionByList(constructedObjects, replicaManager); +} +void Connection_RM2::SetVisibilityByReplicaQuery(ReplicaManager2 *replicaManager) +{ + DataStructures::OrderedList currentVisibility; + + unsigned i; + BooleanQueryResult res; + for (i=0; i < replicaManager->variableSerializeReplicaOrderedList.Size(); i++) + { + if (replicaManager->variableSerializeReplicaOrderedList[i]->QueryIsVisibilityAuthority()) + { + res = replicaManager->variableSerializeReplicaOrderedList[i]->QueryVisibility(this); + if (res==BQR_YES || res==BQR_ALWAYS) // TODO - optimize ALWAYS here + currentVisibility.InsertAtEnd(replicaManager->variableSerializeReplicaOrderedList[i], __FILE__,__LINE__); + } + } + + SetVisibilityByList(currentVisibility, replicaManager); +} +void Connection_RM2::SortInitialDownload( const DataStructures::List &orderedDownloadList, DataStructures::List &initialDownloadList ) { + initialDownloadList = orderedDownloadList; +} +void Connection_RM2::SerializeDownloadStarted(RakNet::BitStream *objectData, ReplicaManager2 *replicaManager, SerializationContext *serializationContext) { + (void) objectData; + (void) replicaManager; + (void) serializationContext; +} +void Connection_RM2::SerializeDownloadComplete(RakNet::BitStream *objectData, ReplicaManager2 *replicaManager, SerializationContext *serializationContext) { + (void) objectData; + (void) replicaManager; + (void) serializationContext; +} +void Connection_RM2::DeserializeDownloadStarted(RakNet::BitStream *objectData, SystemAddress sender, ReplicaManager2 *replicaManager, RakNetTime timestamp, SerializationType serializationType){ + (void) objectData; + (void) sender; + (void) replicaManager; + (void) timestamp; + (void) replicaManager; + (void) serializationType; +} +void Connection_RM2::DeserializeDownloadComplete(RakNet::BitStream *objectData, SystemAddress sender, ReplicaManager2 *replicaManager, RakNetTime timestamp, SerializationType serializationType){ + (void) objectData; + (void) sender; + (void) replicaManager; + (void) timestamp; + (void) serializationType; +} +Replica2 * Connection_RM2::ReceiveConstruct(RakNet::BitStream *replicaData, NetworkID networkId, SystemAddress sender, unsigned char localClientId, SerializationType type, + ReplicaManager2 *replicaManager, RakNetTime timestamp, + DataStructures::OrderedList &exclusionList) +{ + Replica2 *obj=0; + + bool newReference=false; + if (type==SEND_CONSTRUCTION_REPLY_ACCEPTED_TO_CLIENT || type==SEND_CONSTRUCTION_REPLY_DENIED_TO_CLIENT) + { + obj = Replica2::clientPtrArray[localClientId]; + if (obj) + { + // The prefix misaligns the data from the send, which is a problem if the user uses aligned data + replicaData->AlignReadToByteBoundary(); + obj = obj->ReceiveConstructionReply(sender, replicaData, type==SEND_CONSTRUCTION_REPLY_ACCEPTED_TO_CLIENT); + obj->SetNetworkID(networkId); + replicaManager->Reference(obj, &newReference); + replicaManager->AddConstructionReference(this,obj); + + // The object could not be serialized before because it didn't have a NetworkID. So serialize it now. + if (obj->QueryIsSerializationAuthority() && (obj->QueryVisibility(this)==BQR_ALWAYS || obj->QueryVisibility(this)==BQR_YES)) + { + SerializationContext sc; + sc.recipientAddress=UNASSIGNED_SYSTEM_ADDRESS; + sc.timestamp=timestamp; + sc.relaySourceAddress=UNASSIGNED_SYSTEM_ADDRESS; + sc.serializationType=BROADCAST_SERIALIZATION_GENERIC_TO_SYSTEM; + obj->BroadcastSerialize(&sc); + + replicaManager->AddVisibilityReference(this,obj); + } + } + } + else + { + // Create locally send back reply + bool collision = replicaManager->GetRakPeer()->GetNetworkIDManager()->GET_OBJECT_FROM_ID( networkId )!=0; + replicaData->AlignReadToByteBoundary(); + obj = Construct(replicaData, sender, type, replicaManager, timestamp, networkId, collision); + if (obj) + { + replicaManager->Reference(obj, &newReference); + + if (obj->AllowRemoteConstruction(sender, replicaData, type, timestamp)==false) + { + if (type==SEND_CONSTRUCTION_REQUEST_TO_SERVER) + obj->SendConstruction(sender, SEND_CONSTRUCTION_REPLY_DENIED_TO_CLIENT); + //RakNet::OP_DELETE(obj, __FILE__, __LINE__); + delete obj; + obj=0; + } + else + { + if (networkId!=UNASSIGNED_NETWORK_ID) + obj->SetNetworkID(networkId); + + RakNet::BitStream bs; + SerializationContext serializationContext; + serializationContext.relaySourceAddress=sender; + serializationContext.recipientAddress=UNASSIGNED_SYSTEM_ADDRESS; + + if (type==SEND_CONSTRUCTION_REQUEST_TO_SERVER) + serializationContext.serializationType=BROADCAST_CONSTRUCTION_REQUEST_ACCEPTED_TO_SYSTEM; + else + serializationContext.serializationType=BROADCAST_CONSTRUCTION_GENERIC_TO_SYSTEM; + exclusionList.Insert(sender,sender,false, __FILE__,__LINE__); + + unsigned exclusionListIndex=0; + for (unsigned i=0; i < replicaManager->connectionList.Size(); i++) + { + serializationContext.recipientAddress=replicaManager->connectionList[i]->GetSystemAddress(); + + while (exclusionListIndex < exclusionList.Size() && exclusionList[exclusionListIndex] < serializationContext.recipientAddress) + exclusionListIndex++; + if (exclusionListIndex < exclusionList.Size() && exclusionList[exclusionListIndex]==serializationContext.recipientAddress) + { + exclusionListIndex++; + continue; + } + + BooleanQueryResult queryConstruction = obj->QueryConstruction(0); + if ( (queryConstruction==BQR_ALWAYS || queryConstruction==BQR_NEVER) && + replicaManager->autoUpdateConstruction ) + { + // Relay autoUpdateConstruction is on, and the construction is not variable + bs.Reset(); + if (obj->SerializeConstruction(&bs, &serializationContext)==false) + continue; + unsigned char localId; + if (obj->QueryIsConstructionAuthority()==false) + localId=Replica2::clientSharedID++; + else + localId=0; + replicaManager->SendConstruction(obj,&bs,serializationContext.recipientAddress,serializationContext.timestamp,true,exclusionList, localId, serializationContext.serializationType); + } + } + + if (type==SEND_CONSTRUCTION_REQUEST_TO_SERVER) + { + DataStructures::OrderedList emptyList; + // // The prefix misaligns the data for the send, which is a problem if the user uses aligned data + bs.AlignWriteToByteBoundary(); + replicaManager->SendConstruction(obj, &bs, sender, timestamp, true, + emptyList, localClientId, SEND_CONSTRUCTION_REPLY_ACCEPTED_TO_CLIENT); + } + + replicaData->AlignReadToByteBoundary(); + obj->OnConstructionComplete(replicaData, sender, type, replicaManager, timestamp, networkId, collision); + } + } + } + + if (obj && newReference && obj->QueryIsSerializationAuthority() && obj->QueryVisibility(this)==BQR_ALWAYS) + { + SerializationContext sc; + sc.recipientAddress=UNASSIGNED_SYSTEM_ADDRESS; + sc.timestamp=timestamp; + sc.relaySourceAddress=UNASSIGNED_SYSTEM_ADDRESS; + sc.serializationType=BROADCAST_SERIALIZATION_GENERIC_TO_SYSTEM; + obj->BroadcastSerialize(&sc); + } + + return obj; +} +void Connection_RM2::SetSystemAddress(SystemAddress sa) +{ + systemAddress=sa; +} +SystemAddress Connection_RM2::GetSystemAddress(void) const +{ + return systemAddress; +} +void Connection_RM2::SetGuid(RakNetGUID guid) +{ + rakNetGuid=guid; +} +RakNetGUID Connection_RM2::GetGuid(void) const +{ + return rakNetGuid; +} +void Connection_RM2::Deref(Replica2* replica) +{ + lastConstructionList.RemoveIfExists(replica); + lastSerializationList.RemoveIfExists(replica); +} + +#ifdef _MSC_VER +#pragma warning( pop ) +#endif + +#endif // _RAKNET_SUPPORT_* diff --git a/RakNet/Sources/ReplicaManager2.h b/RakNet/Sources/ReplicaManager2.h new file mode 100644 index 0000000..393d1d6 --- /dev/null +++ b/RakNet/Sources/ReplicaManager2.h @@ -0,0 +1,861 @@ +/// \file +/// \brief Contains the second iteration of the ReplicaManager class. This system automatically creates and destroys objects, downloads the world to new players, manages players, and automatically serializes as needed. +/// +/// This file is part of RakNet Copyright 2003 Jenkins Software LLC +/// +/// Usage of RakNet is subject to the appropriate license agreement. + +#include "NativeFeatureIncludes.h" +#if _RAKNET_SUPPORT_ReplicaManager2==1 + +#ifndef __REPLICA_MANAGER_2_H +#define __REPLICA_MANAGER_2_H + +#include "Export.h" +#include "RakNetTypes.h" +#include "DS_Map.h" +#include "PluginInterface2.h" +#include "NetworkIDObject.h" +#include "PacketPriority.h" +#include "GetTime.h" +#include "BitStream.h" +#include "DS_Queue.h" + +namespace RakNet +{ +class BitStream; +class Replica2; +class Connection_RM2; +class Connection_RM2Factory; + +/// \defgroup REPLICA_MANAGER_2_GROUP ReplicaManager2 +/// \brief Deprecated. Second implementation of object replication +/// \details +/// \ingroup REPLICA_MANAGER_GROUP + +/// \brief These are the types of events that can cause network data to be transmitted. +/// \ingroup REPLICA_MANAGER_2_GROUP +typedef int SerializationType; +enum +{ + /// Serialization command initiated by the user + SEND_SERIALIZATION_GENERIC_TO_SYSTEM, + /// Serialization command initiated by the user + BROADCAST_SERIALIZATION_GENERIC_TO_SYSTEM, + /// Serialization command automatically called after sending construction of the object + SEND_SERIALIZATION_CONSTRUCTION_TO_SYSTEM, + /// Serialization command automatically called after sending construction of the object + BROADCAST_SERIALIZATION_CONSTRUCTION_TO_SYSTEM, + /// Automatic serialization of data, based on Replica2::AddAutoSerializeTimer + SEND_AUTO_SERIALIZE_TO_SYSTEM, + /// Automatic serialization of data, based on Replica2::AddAutoSerializeTimer + BROADCAST_AUTO_SERIALIZE_TO_SYSTEM, + /// Received a serialization command, relaying to systems other than the sender + RELAY_SERIALIZATION_TO_SYSTEMS, + + /// If SetAutoAddNewConnections is true, this is the command sent when sending all game objects to new connections automatically + SEND_CONSTRUCTION_SERIALIZATION_AUTO_INITIAL_DOWNLOAD_TO_SYSTEM, + // Automatically sent message indicating if the replica is visible or not to a new connection + SEND_VISIBILITY_AUTO_INITIAL_DOWNLOAD_TO_SYSTEM, + /// The data portion of the game download, preceeded by SEND_CONSTRUCTION_SERIALIZATION_AUTO_INITIAL_DOWNLOAD_TO_SYSTEM + SEND_DATA_SERIALIZATION_AUTO_INITIAL_DOWNLOAD_TO_SYSTEM, + + /// Default reason to send a destruction command + SEND_DESTRUCTION_GENERIC_TO_SYSTEM, + /// Triggered by ReplicaManager2::RecalculateVisibility - A replica is now never constructed, so needs to be destroyed + SEND_DESTRUCTION_VISIBILITY_RECALCULATION_TO_SYSTEM, + /// Triggered by Replica2::BroadcastDestruction + BROADCAST_DESTRUCTION_GENERIC_TO_SYSTEM, + /// Received destruction message, relaying to other systems + RELAY_DESTRUCTION_TO_SYSTEMS, + + /// Default reason to send a construction command + SEND_CONSTRUCTION_GENERIC_TO_SYSTEM, + /// Triggered by ReplicaManager2::RecalculateVisibility - A replica is now always constructed, so needs to be created + SEND_CONSTRUCTION_VISIBILITY_RECALCULATION_TO_SYSTEM, + /// Triggered by Replica2::BroadcastConstruction() + BROADCAST_CONSTRUCTION_GENERIC_TO_SYSTEM, + /// Replica2::QueryIsConstructionAuthority()==false yet we called ReplicaManager2::SendConstruction() + SEND_CONSTRUCTION_REQUEST_TO_SERVER, + /// A non-authority object was created by a client, accepted, and is now relayed to all other connected systems + BROADCAST_CONSTRUCTION_REQUEST_ACCEPTED_TO_SYSTEM, + /// A non-authority object was created by a client, accepted + SEND_CONSTRUCTION_REPLY_ACCEPTED_TO_CLIENT, + /// A non-authority object was created by a client, denied + SEND_CONSTRUCTION_REPLY_DENIED_TO_CLIENT, + + /// An object is visible + SEND_VISIBILITY_TRUE_TO_SYSTEM, + /// An object is visible + BROADCAST_VISIBILITY_TRUE_TO_SYSTEM, + /// An object is not visible + SEND_VISIBILITY_FALSE_TO_SYSTEM, + /// An object is not visible + BROADCAST_VISIBILITY_FALSE_TO_SYSTEM, + /// An object is visible, and we are telling other systems about this + RELAY_VISIBILITY_TRUE_TO_SYSTEMS, + /// An object is visible, and we are telling other systems about this + RELAY_VISIBILITY_FALSE_TO_SYSTEMS, + + /// Calling Replica2::Serialize() for the purpose of reading memory to compare against later. This read will not be transmitted + AUTOSERIALIZE_RESYNCH_ONLY, + /// Calling Replica2::Serialize() to compare against a prior call. The serialization may be transmitted + AUTOSERIALIZE_DEFAULT, + + /// Start your own reasons one unit past this enum + UNDEFINED_REASON, +}; + +/// \deprecated See RakNet::ReplicaManager3 + +/// \brief A management system for your game objects and players to make serialization, scoping, and object creation and destruction easier. +/// \details Quick start: +///
    +///
  1. Create a class that derives from Connection_RM2, implementing the Construct() function. Construct() is a factory function that should return instances of your game objects, given a user-defined identifier. +///
  2. Create a class that derives from Connection_RM2Factory, implementing AllocConnection() and DeallocConnection() to return instances of the class from step 1. +///
  3. Attach ReplicaManager2 as a plugin +///
  4. Call ReplicaManager2::SetConnectionFactory with an instance of the class from step 2. +///
  5. For each of your game classes that use this system, derive from Replica2 and implement SerializeConstruction(), Serialize(), Deserialize(). The output of SerializeConstruction() is sent to Connection_RM2::Construct() +///
  6. When these classes are allocated, call Replica2::SetReplicaManager() with the instance of ReplicaManager2 class created in step 3 (this could be done automatically in the constructor) +///
  7. Creation: Use Replica2::SendConstruction() to create the object remotely, Replica2::SendDestruction() to delete the object remotely. +///
  8. Scoping: Override Replica2::QueryVisibility() and Replica2::QueryConstruction() to return BQR_YES or BQR_NO if an object should be visible and in scope to a given connection. Defaults to BQR_ALWAYS +///
  9. Automatic serialization: Call Replica2::AddAutoSerializeTimer() to automatically call Replica2::Serialize() at intervals, compare this to the last value, and broadcast out the object when the serialized variables change. +/// <\OL> +/// +/// \pre Call RakPeer::SetNetworkIDManager() +/// \pre This system is a server or peer: Call NetworkIDManager::SetIsNetworkIDAuthority(true). +/// \pre This system is a client: Call NetworkIDManager::SetIsNetworkIDAuthority(false). +/// \pre If peer to peer, NETWORK_ID_SUPPORTS_PEER_TO_PEER should be defined in RakNetDefines.h +/// \ingroup REPLICA_MANAGER_2_GROUP +class RAK_DLL_EXPORT ReplicaManager2 : public PluginInterface2 +{ +public: + // Constructor + ReplicaManager2(); + + // Destructor + virtual ~ReplicaManager2(); + + /// Sets the factory class used to allocate connection objects + /// \param[in] factory A pointer to an instance of a class that derives from Connection_RM2Factory. This pointer it saved and not copied, so the object should remain in memory. + void SetConnectionFactory(Connection_RM2Factory *factory); + + /// \param[in] Default ordering channel to use when passing -1 to a function that takes orderingChannel as a parameter + void SetDefaultOrderingChannel(char def); + + /// \param[in] Default packet priority to use when passing NUMBER_OF_PRIORITIES to a function that takes priority as a parameter + void SetDefaultPacketPriority(PacketPriority def); + + /// \param[in] Default packet reliability to use when passing NUMBER_OF_RELIABILITIES to a function that takes reliability as a parameter + void SetDefaultPacketReliability(PacketReliability def); + + /// Auto scope will track the prior construction and serialization visibility of each registered Replica2 class, for each connection. + /// Per-tick, as the visibility or construction status of a replica changes, it will be constructed, destroyed, or the visibility will change as appropriate. + /// \param[in] construction If true, Connection_RM2::SetConstructionByReplicaQuery will be called once per PluginInterface::Update tick. This will call Replica2::QueryConstruction to return if an object should be exist on a particular connection + /// \param[in] visibility If true, Connection_RM2::SetConstructionSerializationByReplicaQuery will be called once per PluginInterface::Update tick. This will call Replica2::QuerySerialization to return if an object should be visible to a particular connection or not. + void SetAutoUpdateScope(bool construction, bool visibility); + + /// Autoadd will cause a Connection_RM2 instance to be allocated for every connection. + /// Defaults to true. Set this to false if you have connections which do not participate in the game (master server, etc). + /// \param[in] autoAdd If true, all incoming connections are added as ReplicaManager2 connections. + void SetAutoAddNewConnections(bool autoAdd); + + /// If SetAutoAddNewConnections() is false, you need to add connections manually + /// connections are also created implicitly if needed + /// \param[in] systemAddress The address of the new system + /// \return false if the connection already exists + bool AddNewConnection(SystemAddress systemAddress); + + /// Remove an existing connection. Also done automatically on ID_DISCONNECTION_NOTIFICATION and ID_CONNECTION_LOST + /// \param[in] systemAddress The address of the system to remove the connection for + /// \return false if the connection does not exist + bool RemoveConnection(SystemAddress systemAddress); + + /// Is this connection registered with the system? + /// \param[in] systemAddress The address of the system to check + /// \return true if this address is registered, false otherwise + bool HasConnection(SystemAddress systemAddress); + + /// If true, autoserialize timers added with Replica2::AddAutoSerializeTimer() will automatically decrement. + /// If false, you must call Replica2::ElapseAutoSerializeTimers() manually. + /// Defaults to true + /// \param[in] autoUpdate True to automatically call ElapseAutoSerializeTimers(). Set to false if you need to control these timers. + void SetDoReplicaAutoSerializeUpdate(bool autoUpdate); + + /// Sends a construction command to one or more systems, which will be relayed throughout the network. + /// Recipient(s) will allocate the connection via Connection_RM2Factory::AllocConnection() if it does not already exist. + /// Will trigger a call on the remote system(s) to Connection_RM2::Construct() + /// \note If using peer-to-peer, NETWORK_ID_SUPPORTS_PEER_TO_PEER should be defined in RakNetDefines.h. + /// \note This is a low level function. Beginners may wish to use Replica2::SendConstruction() or Replica2::BroadcastConstruction(). You can also override Replica2::QueryConstruction() + /// \param[in] replica The class to construct remotely + /// \param[in] replicaData User-defined serialized data representing how to construct the class. Could be the name of the class, a unique identifier, or other methods + /// \param[in] recipient Which system to send to. Use UNASSIGNED_SYSTEM_ADDRESS to send to all previously created connections. Connection_RM2Factory::AllocConnection will be called if this connection has not been previously used. + /// \param[in] timestamp Timestamp to send with the message. Use 0 to not send a timestamp if you don't need it. + /// \param[in] sendMessage True to actually send a network message. False to only register that the object exists on the remote system, useful for objects created outside ReplicaManager2, or objects that already existed in the world. + /// \param[in] exclusionList Which systems to not send to. This list is carried with the messsage, and appended to at each node in the connection graph. This is used to prevent infinite cyclical sends. + /// \param[in] localClientId If replica->QueryIsConstructionAuthority()==false, this number will be sent with SEND_CONSTRUCTION_REQUEST_TO_SERVER to the \a recipient. SEND_CONSTRUCTION_REPLY_ACCEPTED_TO_CLIENT or SEND_CONSTRUCTION_REPLY_DENIED_TO_CLIENT will be returned, and this number will be used to look up the local object in Replica2::clientPtrArray + /// \param[in] type What kind of serialization operation this is. Use one of the pre-defined types, or create your own. This will be returned in \a type in Connection_RM2::Construct() + /// \param[in] priority PacketPriority to send with. Use NUMBER_OF_PRIORITIES to use the default defined by SetDefaultPacketPriority(). + /// \param[in] reliability PacketReliability to send with. Use NUMBER_OF_RELIABILITIES to use the default defined by SetDefaultPacketReliability(); + /// \param[in] orderingChannel ordering channel to send on. Use -1 to use the default defined by SetDefaultOrderingChannel() + /// \pre Call SetConnectionFactory() with a derived instance of Connection_RM2Factory. + void SendConstruction(Replica2 *replica, BitStream *replicaData, SystemAddress recipient, RakNetTime timestamp, bool sendMessage, + DataStructures::OrderedList &exclusionList, + unsigned char localClientId, SerializationType type=SEND_CONSTRUCTION_GENERIC_TO_SYSTEM, + PacketPriority priority=NUMBER_OF_PRIORITIES, PacketReliability reliability=NUMBER_OF_RELIABILITIES, char orderingChannel=-1); + + /// Sends a destruction command to one or more systems, which will be relayed throughout the network. + /// Recipient(s) will allocate the connection via Connection_RM2Factory::AllocConnection() if it does not already exist. + /// Will trigger a call on the remote system(s) to Replica2::ReceiveDestruction() which in turn calls Replica2::DeserializeDestruction() with the value passed in \a replicaData + /// Note: This is a low level function. Beginners may wish to use Replica2::SendDestruction() or Replica2::BroadcastDestruction(). + /// \param[in] replica The class to destroy remotely + /// \param[in] replicaData User-defined serialized data. Passed to Replica2::ReceiveDestruction() + /// \param[in] recipient Which system to send to. Use UNASSIGNED_SYSTEM_ADDRESS to send to all previously created connections. Connection_RM2Factory::AllocConnection will be called if this connection has not been previously used. + /// \param[in] timestamp Timestamp to send with the message. Use 0 to not send a timestamp if you don't need it. + /// \param[in] sendMessage True to actually send a network message. False to only register that the object no longer exists on the remote system. + /// \param[in] exclusionList Which systems to not send to. This list is carried with the messsage, and appended to at each node in the connection graph. This is used to prevent infinite cyclical sends. + /// \param[in] type What kind of serialization operation this is. Use one of the pre-defined types, or create your own. This will be returned in \a type in Connection_RM2::Construct() + /// \param[in] priority PacketPriority to send with. Use NUMBER_OF_PRIORITIES to use the default defined by SetDefaultPacketPriority(). + /// \param[in] reliability PacketReliability to send with. Use NUMBER_OF_RELIABILITIES to use the default defined by SetDefaultPacketReliability(); + /// \param[in] orderingChannel ordering channel to send on. Use -1 to use the default defined by SetDefaultOrderingChannel() + /// \pre Replica::QueryIsDestructionAuthority() must return true + /// \pre Call SetConnectionFactory() with a derived instance of Connection_RM2Factory. + void SendDestruction(Replica2 *replica, BitStream *replicaData, SystemAddress recipient, RakNetTime timestamp, bool sendMessage, + DataStructures::OrderedList &exclusionList, + SerializationType type=SEND_DESTRUCTION_GENERIC_TO_SYSTEM, + PacketPriority priority=NUMBER_OF_PRIORITIES, PacketReliability reliability=NUMBER_OF_RELIABILITIES, char orderingChannel=-1); + + /// Sends a serialized object to one or more systems, which will be relayed throughout the network. + /// Recipient(s) will allocate the connection via Connection_RM2Factory::AllocConnection() if it does not already exist. + /// Will trigger a call on the remote system(s) to Replica2::ReceiveSerialization() which in turn calls Replica2::Deserialize() with the value passed in \a replicaData + /// Note: This is a low level function. Beginners may wish to use Replica2::SendSerialize() or Replica2::BroadcastSerialize(). + /// \param[in] replica The class to serialize + /// \param[in] replicaData User-defined serialized data. Passed to Replica2::ReceiveSerialization() + /// \param[in] recipient Which system to send to. Use UNASSIGNED_SYSTEM_ADDRESS to send to all previously created connections. Connection_RM2Factory::AllocConnection will be called if this connection has not been previously used. + /// \param[in] timestamp Timestamp to send with the message. Use 0 to not send a timestamp if you don't need it. + /// \param[in] exclusionList Which systems to not send to. This list is carried with the messsage, and appended to at each node in the connection graph. This is used to prevent infinite cyclical sends. + /// \param[in] type What kind of serialization operation this is. Use one of the pre-defined types, or create your own. This will be returned in \a type in Connection_RM2::Construct() + /// \param[in] priority PacketPriority to send with. Use NUMBER_OF_PRIORITIES to use the default defined by SetDefaultPacketPriority(). + /// \param[in] reliability PacketReliability to send with. Use NUMBER_OF_RELIABILITIES to use the default defined by SetDefaultPacketReliability(); + /// \param[in] orderingChannel ordering channel to send on. Use -1 to use the default defined by SetDefaultOrderingChannel() + /// \pre Replica::QueryIsSerializationAuthority() must return true + /// \pre Call SetConnectionFactory() with a derived instance of Connection_RM2Factory. + void SendSerialize(Replica2 *replica, BitStream *replicaData, SystemAddress recipient, RakNetTime timestamp, + DataStructures::OrderedList &exclusionList, + SerializationType type=SEND_SERIALIZATION_GENERIC_TO_SYSTEM, + PacketPriority priority=NUMBER_OF_PRIORITIES, PacketReliability reliability=NUMBER_OF_RELIABILITIES, char orderingChannel=-1); + + /// Sets the visibility status of an object. which will be relayed throughout the network. + /// Objects that are not visible should be hidden in the game world, and will not send AutoSerialize updates + /// Recipient(s) will allocate the connection via Connection_RM2Factory::AllocConnection() if it does not already exist. + /// Will trigger a call on the remote system(s) to Connection_RM2::ReceiveVisibility() + /// Note: This is a low level function. Beginners may wish to use Connection_RM2::SendVisibility() or override Replica2::QueryVisibility() + /// \param[in] objectList The objects to send to the system. + /// \param[in] replicaData User-defined serialized data. Read in Connection_RM2::ReceiveVisibility() + /// \param[in] recipient Which system to send to. Use UNASSIGNED_SYSTEM_ADDRESS to send to all previously created connections. Connection_RM2Factory::AllocConnection will be called if this connection has not been previously used. + /// \param[in] timestamp Timestamp to send with the message. Use 0 to not send a timestamp if you don't need it. + /// \param[in] sendMessage True to actually send a network message. False to only register that the objects exist on the remote system + /// \param[in] type What kind of serialization operation this is. Use one of the pre-defined types, or create your own. This will be returned in \a type in Connection_RM2::Construct() + /// \param[in] priority PacketPriority to send with. Use NUMBER_OF_PRIORITIES to use the default defined by SetDefaultPacketPriority(). + /// \param[in] reliability PacketReliability to send with. Use NUMBER_OF_RELIABILITIES to use the default defined by SetDefaultPacketReliability(); + /// \param[in] orderingChannel ordering channel to send on. Use -1 to use the default defined by SetDefaultOrderingChannel() + /// \pre Replica::QueryIsConstructionAuthority() must return true + /// \pre Call SetConnectionFactory() with a derived instance of Connection_RM2Factory. + void SendVisibility(Replica2 *replica, BitStream *replicaData, SystemAddress recipient, RakNetTime timestamp, + DataStructures::OrderedList &exclusionList, + SerializationType type=SEND_VISIBILITY_TRUE_TO_SYSTEM, + PacketPriority priority=NUMBER_OF_PRIORITIES, PacketReliability reliability=NUMBER_OF_RELIABILITIES, char orderingChannel=-1); + + /// Returns how many Replica2 instances are registered. + /// Replica2 instances are automatically registered when used, and unregistered when calling Deref (which is automatically done in the destructor). + /// Used for GetReplicaAtIndex if you want to perform some object on all registered Replica objects. + /// \return How many replica objects are in the list of replica objects + unsigned GetReplicaCount(void) const; + + /// Returns a previously registered Replica2 *, from index 0 to GetReplicaCount()-1. + /// Replica2* objects are returned in the order they were registered. + /// \param[in] index An index, from 0 to GetReplicaCount()-1. + /// \return A Replica2 pointer + Replica2 *GetReplicaAtIndex(unsigned index); + + /// Returns the number of registered connections. + /// Connections are registered implicitly when used. + /// Connections are unregistered on disconnect. + /// \return The number of registered connections + unsigned GetConnectionCount(void) const; + + /// Returns a connection pointer previously implicitly added. + /// \param[in] index An index, from 0 to GetConnectionCount()-1. + /// \return A Connection_RM2 pointer + Connection_RM2* GetConnectionAtIndex(unsigned index) const; + + /// Returns a connection pointer previously implicitly added. + /// \param[in] systemAddress The system address of the connection to return + /// \return A Connection_RM2 pointer + Connection_RM2* GetConnectionBySystemAddress(SystemAddress systemAddress) const; + + /// Returns the index of a connection, by SystemAddress + /// \param[in] systemAddress The system address of the connection index to return + /// \return The connection index, or -1 if no such connection + unsigned int GetConnectionIndexBySystemAddress(SystemAddress systemAddress) const; + + /// Call this when Replica2::QueryVisibility() or Replica2::QueryConstructionVisibility() changes from BQR_ALWAYS or BQR_NEVER to BQR_YES or BQR_NO + /// Otherwise these two conditions are assumed to never change + /// \param[in] Which replica to update + void RecalculateVisibility(Replica2 *replica); + + /// \internal + static int Replica2ObjectComp( RakNet::Replica2 * const &key, RakNet::Replica2 * const &data ); + /// \internal + static int Replica2CompByNetworkID( const NetworkID &key, RakNet::Replica2 * const &data ); + /// \internal + static int Connection_RM2CompBySystemAddress( const SystemAddress &key, RakNet::Connection_RM2 * const &data ); + + /// Given a replica instance, return all connections that are believed to have this replica instantiated. + /// \param[in] replica Which replica is being refered to + /// \param[out] output List of connections, ordered by system address + void GetConnectionsWithReplicaConstructed(Replica2 *replica, DataStructures::OrderedList &output); + + /// Given a replica instance, return all connections that are believed to have this replica visible + /// \param[in] replica Which replica is being refered to + /// \param[out] output List of connections, ordered by system address + void GetConnectionsWithSerializeVisibility(Replica2 *replica, DataStructures::OrderedList &output); + + /// Gets the instance of RakPeerInterface that this plugin was attached to + /// \return The instance of RakPeerInterface that this plugin was attached to + RakPeerInterface *GetRakPeer(void) const; + + /// Internally starts tracking this replica + /// \internal + void Reference(Replica2* replica, bool *newReference); + + /// Stops tracking this replica. Call before deleting the Replica. Done automatically in ~Replica() + /// \internal + void Dereference(Replica2 *replica); + +protected: + // Plugin interface functions + void OnAttach(void); + PluginReceiveResult OnReceive(Packet *packet); + void OnClosedConnection(SystemAddress systemAddress, RakNetGUID rakNetGUID, PI2_LostConnectionReason lostConnectionReason ); + void OnRakPeerShutdown(void); + void Update(void); + virtual void OnNewConnection(SystemAddress systemAddress, RakNetGUID rakNetGUID, bool isIncoming); + + PluginReceiveResult OnDownloadComplete(unsigned char *packetData, int packetDataLength, SystemAddress sender, RakNetTime timestamp); + PluginReceiveResult OnDownloadStarted(unsigned char *packetData, int packetDataLength, SystemAddress sender, RakNetTime timestamp); + PluginReceiveResult OnConstruction(unsigned char *packetData, int packetDataLength, SystemAddress sender, RakNetTime timestamp); + PluginReceiveResult OnDestruction(unsigned char *packetData, int packetDataLength, SystemAddress sender, RakNetTime timestamp); + PluginReceiveResult OnVisibilityChange(unsigned char *packetData, int packetDataLength, SystemAddress sender, RakNetTime timestamp); + PluginReceiveResult OnSerialize(unsigned char *packetData, int packetDataLength, SystemAddress sender, RakNetTime timestamp); + + bool AddToAndWriteExclusionList(SystemAddress recipient, RakNet::BitStream *bs, DataStructures::OrderedList &exclusionList); + void WriteExclusionList(RakNet::BitStream *bs, DataStructures::OrderedList &exclusionList); + void CullByAndAddToExclusionList( + DataStructures::OrderedList &inputList, + DataStructures::OrderedList &culledOutput, + DataStructures::OrderedList &exclusionList); + void ReadExclusionList(RakNet::BitStream *bs, DataStructures::OrderedList &exclusionList); + + void Send(RakNet::BitStream *bs, SystemAddress recipient, PacketPriority priority, PacketReliability reliability, char orderingChannel); + void Clear(void); + void DownloadToNewConnection(Connection_RM2* connection, RakNetTime timestamp, PacketPriority priority, PacketReliability reliability, char orderingChannel); + + Connection_RM2* CreateConnectionIfDoesNotExist(SystemAddress systemAddress, bool *newConnection); + Connection_RM2* AutoCreateConnection(SystemAddress systemAddress, bool *newConnection); + void AddConstructionReference(Connection_RM2* connection, Replica2* replica); + void AddVisibilityReference(Connection_RM2* connection, Replica2* replica); + void RemoveVisibilityReference(Connection_RM2* connection, Replica2* replica); + void WriteHeader(RakNet::BitStream *bs, MessageID type, RakNetTime timestamp); + + friend class Connection_RM2; + friend class Replica2; + + Connection_RM2Factory *connectionFactoryInterface; + bool autoUpdateConstruction, autoUpdateVisibility; + + char defaultOrderingChannel; + PacketPriority defaultPacketPriority; + PacketReliability defaultPacketReliablity; + bool autoAddNewConnections; + bool doReplicaAutoUpdate; + RakNetTime lastUpdateTime; + + DataStructures::List fullReplicaUnorderedList; + DataStructures::OrderedList fullReplicaOrderedList; + DataStructures::OrderedList alwaysDoConstructReplicaOrderedList; + DataStructures::OrderedList alwaysDoSerializeReplicaOrderedList; + // Should only be in this list if QueryIsServer() is true + DataStructures::OrderedList variableConstructReplicaOrderedList; + DataStructures::OrderedList variableSerializeReplicaOrderedList; + + DataStructures::OrderedList connectionList; +}; + +/// \brief The result of various scope and construction queries +/// \ingroup REPLICA_MANAGER_2_GROUP +enum RAK_DLL_EXPORT BooleanQueryResult +{ + /// The query is always true, for all systems. Certain optimizations are performed here, but you should not later return any other value without first calling ReplicaManager2::RecalculateVisibility + BQR_ALWAYS, + + /// True + BQR_YES, + + /// False + BQR_NO, + + /// The query is never true, for all systems. Certain optimizations are performed here, but you should not later return any other value without first calling ReplicaManager2::RecalculateVisibility + BQR_NEVER +}; + +/// \brief Contextual information about serialization, passed to some functions in Replica2 +/// \ingroup REPLICA_MANAGER_2_GROUP +struct RAK_DLL_EXPORT SerializationContext +{ + SerializationContext() {} + ~SerializationContext() {} + SerializationContext(SerializationType st, SystemAddress relay, SystemAddress recipient, RakNetTime _timestamp) {serializationType=st; relaySourceAddress=relay; recipientAddress=recipient; timestamp=_timestamp;} + + /// The system that sent the message to us. + SystemAddress relaySourceAddress; + + /// The system that we are sending to. + SystemAddress recipientAddress; + + /// Timestamp to send with the message. 0 means undefined. Set to non-zero to actually transmit using ID_TIMESTAMP + RakNetTime timestamp; + + /// What type of serialization was performed + SerializationType serializationType; + + /// General category of serialization + static bool IsSerializationCommand(SerializationType r); + /// General category of serialization + static bool IsDownloadCommand(SerializationType r); + /// General category of serialization + static bool IsDestructionCommand(SerializationType r); + /// General category of serialization + static bool IsConstructionCommand(SerializationType r); + /// General category of serialization + static bool IsVisibilityCommand(SerializationType r); + /// General category of serialization + static bool IsVisible(SerializationType r); +}; + + +/// \brief Base class for game objects that use the ReplicaManager2 system +/// \details All game objects that want to use the ReplicaManager2 functionality must inherit from Replica2.
    +/// Generally you will want to implement at a minimum Serialize(), Deserialize(), and SerializeConstruction() +/// \ingroup REPLICA_MANAGER_2_GROUP +class RAK_DLL_EXPORT Replica2 : public NetworkIDObject +{ +public: + // Constructor + Replica2(); + + // Destructor + virtual ~Replica2(); + + /// Sets the replica manager to use with this Replica. + /// Will also set the NetworkIDManager associated with RakPeerInterface::SetNetworkIDManager() + /// Call this before using any of the functions below! + /// \param[in] rm A pointer to your instance of ReplicaManager 2 + void SetReplicaManager(ReplicaManager2* rm); + + /// Returns what was passed to SetReplicaManager(), or 0 if no value ever passed + /// \return Registered instance of ReplicaManager2 + ReplicaManager2* GetReplicaManager(void) const; + + /// Construct this object on other systems + /// Triggers a call to SerializeConstruction() + /// \note If using peer-to-peer, NETWORK_ID_SUPPORTS_PEER_TO_PEER should be defined in RakNetDefines.h + /// \param[in] recipientAddress Which system to send to + /// \param[in] serializationType What type of command this is. Use UNDEFINED_REASON to have a type chosen automatically + virtual void SendConstruction(SystemAddress recipientAddress, SerializationType serializationType=UNDEFINED_REASON); + + /// Destroy this object on other systems + /// Triggers a call to SerializeDestruction() + /// \param[in] recipientAddress Which system to send to + /// \param[in] serializationType What type of command this is. Use UNDEFINED_REASON to have a type chosen automatically + virtual void SendDestruction(SystemAddress recipientAddress, SerializationType serializationType=UNDEFINED_REASON); + + /// Serialize this object to another system + /// Triggers a call to Serialize() + /// \param[in] recipientAddress Which system to send to + /// \param[in] serializationType What type of command this is. Use UNDEFINED_REASON to have a type chosen automatically + virtual void SendSerialize(SystemAddress recipientAddress, SerializationType serializationType=UNDEFINED_REASON); + + /// Update the visibility of this object on another system + /// Triggers a call to SerializeVisibility() + /// \param[in] recipientAddress Which system to send to + /// \param[in] serializationType What type of command this is. Use UNDEFINED_REASON to have a type chosen automatically + virtual void SendVisibility(SystemAddress recipientAddress, SerializationType serializationType=UNDEFINED_REASON); + + /// Construct this object on other systems + /// \param[in] serializationContext Which system to send to, an input timestamp, and the SerializationType. 0 to use defaults, no timestamp. + virtual void BroadcastConstruction(SerializationContext *serializationContext=0); + + /// Serialize this object to all current connections + /// Triggers a call to SerializeConstruction() for each connection (you can serialize differently per connection). + /// \param[in] serializationContext Which system to send to, an input timestamp, and the SerializationType. 0 to use defaults, no timestamp. + virtual void BroadcastSerialize(SerializationContext *serializationContext=0); + + /// Destroy this object on all current connections + /// Triggers a call to SerializeDestruction() for each connection (you can serialize differently per connection). + /// \param[in] serializationContext Which system to send to, an input timestamp, and the SerializationType. 0 to use defaults, no timestamp. + virtual void BroadcastDestruction(SerializationContext *serializationContext=0); + + /// Update the visibility state of this object on all other systems + /// Use SEND_VISIBILITY_TRUE_TO_SYSTEM or SEND_VISIBILITY_FALSE_TO_SYSTEM in \a serializationContext::serializationType + /// Triggers a call to SerializeVisibility() for each connection (you can serialize differently per connection). + /// \param[in] serializationContext Which system to send to, an input timestamp, and the SerializationType. 0 to use defaults, no timestamp, true visibility + virtual void BroadcastVisibility(bool isVisible, SerializationContext *serializationContext=0); + + /// CALLBACK: + /// Override in order to write to \a bitStream data identifying this class for the class factory. Will be received by Connection_RM2::Construct() to create an instance of this class. + /// \param[out] bitStream Data used to identify this class, along with any data you also want to send when constructing the class + /// \param[in] serializationContext serializationType passed to Replica2::SendConstruction(), along with destination system, and a timestamp you can write to. + /// \return Return false to cancel the construction, true to process + virtual bool SerializeConstruction(RakNet::BitStream *bitStream, SerializationContext *serializationContext)=0; + + /// CALLBACK: + /// Override in order to write to \a bitStream data to send along with destruction requests. Will be received by DeserializeDestruction() + /// \param[out] bitStream Data to send + /// \param[in] serializationContext Describes which system we are sending to, and a timestamp as an out parameter + /// \return Return false to cancel the operation, true to process + virtual bool SerializeDestruction(RakNet::BitStream *bitStream, SerializationContext *serializationContext); + + /// CALLBACK: + /// Override in order to write to \a bitStream data to send as regular class serialization, for normal per-tick data. Will be received by Deserialize() + /// \param[out] bitStream Data to send + /// \param[in] serializationContext Describes which system we are sending to, and a timestamp as an out parameter + /// \return Return false to cancel the operation, true to process + virtual bool Serialize(RakNet::BitStream *bitStream, SerializationContext *serializationContext); + + /// CALLBACK: + /// Override in order to write to \a bitStream data to send along with visibility changes. Will be received by DeserializeVisibility() + /// \param[out] bitStream Data to send + /// \param[in] serializationContext Describes which system we are sending to, and a timestamp as an out parameter + /// \return Return false to cancel the operation, true to process + virtual bool SerializeVisibility(RakNet::BitStream *bitStream, SerializationContext *serializationContext); + + /// CALLBACK: + /// Receives data written by SerializeDestruction() + /// \param[in] bitStream Data sent + /// \param[in] serializationType SerializationContext::serializationType + /// \param[in] sender Which system sent this message to us + /// \param[in] timestamp If a timestamp was written, will be whatever was written adjusted to the local system time. 0 if not used. + virtual void DeserializeDestruction(RakNet::BitStream *bitStream, SerializationType serializationType, SystemAddress sender, RakNetTime timestamp); + + /// CALLBACK: + /// Receives data written by Serialize() + /// \param[in] bitStream Data sent + /// \param[in] serializationType SerializationContext::serializationType + /// \param[in] sender Which system sent this message to us + /// \param[in] timestamp If a timestamp was written, will be whatever was written adjusted to the local system time. 0 if not used. + virtual void Deserialize(RakNet::BitStream *bitStream, SerializationType serializationType, SystemAddress sender, RakNetTime timestamp); + + /// CALLBACK: + /// Receives data written by SerializeVisibility() + /// \param[in] bitStream Data sent + /// \param[in] serializationType SerializationContext::serializationType + /// \param[in] sender Which system sent this message to us + /// \param[in] timestamp If a timestamp was written, will be whatever was written adjusted to the local system time. 0 if not used. + virtual void DeserializeVisibility(RakNet::BitStream *bitStream, SerializationType serializationType, SystemAddress sender, RakNetTime timestamp); + + /// CALLBACK: + /// For a given connection, should this object exist? + /// Checked every Update cycle if ReplicaManager2::SetAutoUpdateScope() parameter \a construction is true + /// Defaults to BQR_ALWAYS + /// \note This query is NOT used for ReplicaManager2::BroadcastConstruction() or SendConstruction(), which forces the operation to occur. If you DO want to use the query, use ReplicaManager2::Reference() and the next time RakPeer::Receive() is called it will occur. + /// \param[in] connection Which connection we are referring to. 0 means unknown, in which case the system is checking for BQR_ALWAYS or BQR_NEVER as an optimization. + /// \return BQR_NO and the object will be destroyed. BQR_YES and the object will be created. BQR_ALWAYS is YES for all connections, and is optimized to only be checked once. + virtual BooleanQueryResult QueryConstruction(Connection_RM2 *connection); + + /// CALLBACK: + /// For a given connection, should this object be visible (updatable?) + /// Checked every Update cycle if ReplicaManager2::SetAutoUpdateScope() parameter \a serializationVisiblity is true + /// Defaults to BQR_ALWAYS + /// \note This query is NOT used for ReplicaManager2::BroadcastVisibility() or SendVisibility(), which forces the operation to occur. If you DO want to use the query, use ReplicaManager2::Reference() and the next time RakPeer::Receive() is called it will occur. + /// \param[in] connection Which connection we are referring to. 0 means unknown, in which case the system is checking for BQR_ALWAYS or BQR_NEVER as an optimization. + /// \return BQR_NO or BQR_YES and as this value changes per connection, you will get a call to DeserializeVisibility(). + virtual BooleanQueryResult QueryVisibility(Connection_RM2 *connection); + + /// CALLBACK: + /// Does this system have control over construction of this object? + /// While not strictly required, it is best to have this consistently return true for only one system. Otherwise systems may fight and override each other. + /// Defaults to NetworkIDManager::IsNetworkIDAuthority(); + /// \return True if an authority over this operation, for this object instance + virtual bool QueryIsConstructionAuthority(void) const; + + /// CALLBACK: + /// Does this system have control over deletion of this object? + /// While not strictly required, it is best to have this consistently return true for only one system. Otherwise systems may fight and override each other. + /// Defaults to NetworkIDManager::IsNetworkIDAuthority(); + /// \return True if an authority over this operation, for this object instance + virtual bool QueryIsDestructionAuthority(void) const; + + /// CALLBACK: + /// Does this system have control over visibility of this object? + /// While not strictly required, it is best to have this consistently return true for only one system. Otherwise systems may fight and override each other. + /// Defaults to NetworkIDManager::IsNetworkIDAuthority(); + /// \return True if an authority over this operation, for this object instance + virtual bool QueryIsVisibilityAuthority(void) const; + + /// CALLBACK: + /// Does this system have control over serialization of object members of this object? + /// It is reasonable to have this be true for more than one system, but you would want to serialize different variables so those systems do not conflict. + /// Defaults to NetworkIDManager::IsNetworkIDAuthority(); + /// \return True if an authority over this operation, for this object instance + virtual bool QueryIsSerializationAuthority(void) const; + + /// CALLBACK: + /// If QueryIsConstructionAuthority() is false for a remote system, should that system be able to create this kind of object? + /// \param[in] sender Which system sent this message to us? Also happens to be the system that is requesting to create an object + /// \param[in] replicaData Construction data used to create this object + /// \param[in] type Which type of serialization operation was performed + /// \param[in] timestamp Written timestamp with the packet. 0 if not used. + /// \return True to allow remote construction of this object. If true, we will reply with SEND_CONSTRUCTION_REPLY_ACCEPTED_TO_CLIENT and the network ID will be set on the requester. + virtual bool AllowRemoteConstruction(SystemAddress sender, RakNet::BitStream *replicaData, SerializationType type, RakNetTime timestamp); + + /// Adds a timer that will elapse every \a countdown milliseconds, calling Serialize with AUTOSERIALIZE_DEFAULT or whatever value was passed to \a serializationType + /// Every time this timer elapses, the value returned from Serialize() will be compared to the last value returned by Serialize(). + /// If different, SendSerialize() will be called automatically. + /// It is possible to create your own AUTOSERIALIZE enumerations and thus control which parts of the object is serialized + /// Use CancelAutoSerializeTimer() or ClearAutoSerializeTimers() to stop the timer. + /// If this timer already exists, it will simply override the existing countdown + /// This timer will automatically repeat every \a countdown milliseconds + /// \note The same data is sent to all participants when the autoserialize timer completes. If the data sent depends on the system to be sent to, do not use autoserialize. This is an optimization to save memory. + /// \param[in] interval Time in milliseconds between autoserialize ticks. Use 0 to process immediately, and every tick + /// \param[in] serializationType User-defined identifier for what type of serialization operation to perform. Returned in Deserialize() as the \a serializationType parameter. + /// \param[in] countdown Amount of time before doing the next autoserialize. Defaults to interval + virtual void AddAutoSerializeTimer(RakNetTime interval, SerializationType serializationType=AUTOSERIALIZE_DEFAULT, RakNetTime countdown=(RakNetTime)-1 ); + + /// Elapse time for all timers added with AddAutoSerializeTimer() + /// Only necessary to call this if you call Replica2::SetDoReplicaAutoSerializeUpdate(false) (which defaults to true) + /// \param[in] timeElapsed How many milliseconds have elapsed since the last call + /// \param[in] resynchOnly True to only update what was considered the last send, without actually doing a send. + virtual void ElapseAutoSerializeTimers(RakNetTime timeElapsed, bool resynchOnly); + + /// Returns how many milliseconds are remaining until the next autoserialize update + /// \param[in] serializationType User-defined identifier for what type of serialization operation to perform. Returned in Deserialize() as the \a serializationType parameter. + /// \return How many milliseconds are remaining until the next autoserialize update. Returns -1 if no such autoserialization timer is in place. + RakNetTime GetTimeToNextAutoSerialize(SerializationType serializationType=AUTOSERIALIZE_DEFAULT); + + /// Do the actual send call when needed to support autoSerialize + /// If you want to do different types of send calls (UNRELIABLE for example) override this function. + /// \param[in] serializationContext Describes the recipient, sender. serializationContext::timestamp is an [out] parameter which if you write to, will be send along with the message + /// \param[in] serializedObject Data to pass to ReplicaManager2::SendSerialize() + virtual void BroadcastAutoSerialize(SerializationContext *serializationContext, RakNet::BitStream *serializedObject); + + /// Stop calling an autoSerialize timer previously setup with AddAutoSerializeTimer() + /// \param[in] serializationType Corresponding value passed to serializationType + virtual void CancelAutoSerializeTimer(SerializationType serializationType=AUTOSERIALIZE_DEFAULT); + + /// Remove and deallocate all previously added autoSerialize timers + virtual void ClearAutoSerializeTimers(void); + + /// A timer has elapsed. Compare the last value sent to the current value, and if different, send the new value + /// \internal + virtual void OnAutoSerializeTimerElapsed(SerializationType serializationType, RakNet::BitStream *output, RakNet::BitStream *lastOutput, RakNetTime lastAutoSerializeCountdown, bool resynchOnly); + + /// Immediately elapse all autoserialize timers + /// Used internally when a Deserialize() event occurs, so that the deserialize does not trigger an autoserialize itself + /// \internal + /// \param[in] resynchOnly If true, do not send a Serialize() message if the data has changed + virtual void ForceElapseAllAutoserializeTimers(bool resynchOnly); + + /// A call to Connection_RM2 Construct() has completed and the object is now internally referenced + /// \param[in] replicaData Whatever was written \a bitStream in Replica2::SerializeConstruction() + /// \param[in] type Whatever was written \a serializationType in Replica2::SerializeConstruction() + /// \param[in] replicaManager ReplicaManager2 instance that created this class. + /// \param[in] timestamp timestamp sent with Replica2::SerializeConstruction(), 0 for none. + /// \param[in] networkId NetworkID that will be assigned automatically to the new object after this function returns + /// \param[in] networkIDCollision True if the network ID that should be assigned to this object is already in use. Usuallly this is because the object already exists, and you should just read your data and return 0. + virtual void OnConstructionComplete(RakNet::BitStream *replicaData, SystemAddress sender, SerializationType type, ReplicaManager2 *replicaManager, RakNetTime timestamp, NetworkID networkId, bool networkIDCollision); + +protected: + + virtual void ReceiveSerialize(SystemAddress sender, RakNet::BitStream *serializedObject, SerializationType serializationType, RakNetTime timestamp, DataStructures::OrderedList &exclusionList ); + virtual void ReceiveDestruction(SystemAddress sender, RakNet::BitStream *serializedObject, SerializationType serializationType, RakNetTime timestamp, DataStructures::OrderedList &exclusionList ); + virtual void DeleteOnReceiveDestruction(SystemAddress sender, RakNet::BitStream *serializedObject, SerializationType serializationType, RakNetTime timestamp, DataStructures::OrderedList &exclusionList ); + virtual void ReceiveVisibility(SystemAddress sender, RakNet::BitStream *serializedObject, SerializationType serializationType, RakNetTime timestamp, DataStructures::OrderedList &exclusionList); + virtual Replica2 * ReceiveConstructionReply(SystemAddress sender, BitStream *replicaData, bool constructionAllowed); + void DereferenceFromDestruction(void); + + friend class ReplicaManager2; + friend class Connection_RM2; + + static unsigned char clientSharedID; + static Replica2* clientPtrArray[256]; + + bool hasClientID; + unsigned char clientID; + ReplicaManager2 *rm2; + + struct AutoSerializeEvent + { + SerializationType serializationType; + RakNetTime initialCountdown; + RakNetTime remainingCountdown; + bool writeToResult1; + RakNet::BitStream lastAutoSerializeResult1; + RakNet::BitStream lastAutoSerializeResult2; + }; + + DataStructures::Map autoSerializeTimers; + +}; + +/// \brief Implement this factory class to return instances of your Connection_RM2 derived object. This is used as a class factory and exposes functionality related to the connection and the system +/// \ingroup REPLICA_MANAGER_2_GROUP +class RAK_DLL_EXPORT Connection_RM2Factory +{ +public: + Connection_RM2Factory() {} + virtual ~Connection_RM2Factory() {} + virtual Connection_RM2* AllocConnection(void) const=0; + virtual void DeallocConnection(Connection_RM2* s) const=0; +}; + +/// \brief This class represents a connection between two instances of ReplicaManager2 +/// \details Represents a connection. Allocated by user supplied factory interface Connection_RM2Factory.
    +/// Implicitly created as needed.
    +/// Generally you will want to implement at a minimum the Construct() function, used as a factory function to create your game objects. +/// \ingroup REPLICA_MANAGER_2_GROUP +class RAK_DLL_EXPORT Connection_RM2 +{ +public: + // Constructor + Connection_RM2(); + + // Destructor + virtual ~Connection_RM2(); + + /// Factory function, used to create instances of your game objects + /// Encoding is entirely up to you. \a replicaData will hold whatever was written \a bitStream in Replica2::SerializeConstruction() + /// One efficient way to do it is to use StringTable.h. This allows you to send predetermined strings over the network at a cost of 9 bits, up to 65536 strings + /// \note The object is not yet referenced by ReplicaManager2 in this callback. Use Replica2::OnConstructionComplete() to perform functionality such as AutoSerialize() + /// \param[in] replicaData Whatever was written \a bitStream in Replica2::SerializeConstruction() + /// \param[in] type Whatever was written \a serializationType in Replica2::SerializeConstruction() + /// \param[in] replicaManager ReplicaManager2 instance that created this class. + /// \param[in] timestamp timestamp sent with Replica2::SerializeConstruction(), 0 for none. + /// \param[in] networkId NetworkID that will be assigned automatically to the new object after this function returns + /// \param[in] networkIDCollision True if the network ID that should be assigned to this object is already in use. Usuallly this is because the object already exists, and you should just read your data and return 0. + /// \return Return 0 to signal that construction failed or was refused for this object. Otherwise return the object that was created. A reference will be held to this object, and SetNetworkID() and SetReplicaManager() will be called automatically. + virtual Replica2* Construct(RakNet::BitStream *replicaData, SystemAddress sender, SerializationType type, ReplicaManager2 *replicaManager, RakNetTime timestamp, NetworkID networkId, bool networkIDCollision)=0; + + /// CALLBACK: + /// Called before a download is sent to a new connection, called after ID_REPLICA_MANAGER_DOWNLOAD_STARTED is sent. + /// Gives you control over the list of objects to be downloaded. For greater control, you can override ReplicaManager2::DownloadToNewConnection + /// Defaults to send everything in the default order + /// \param[in] fullReplicaUnorderedList The list of all known objects in the order they were originally known about by the system (the first time used by any function) + /// \param[out] orderedDownloadList An empty list. Copy fullReplicaUnorderedList to this list to send everything. Leave elements out to not send them. Add them to the list in a different order to send them in that order. + virtual void SortInitialDownload( const DataStructures::List &orderedDownloadList, DataStructures::List &initialDownloadList ); + + + /// CALLBACK: + /// Called before a download is sent to a new connection + /// \param[out] objectData What data you want to send to DeSerializeDownloadStarted() + /// \param[in] replicaManager Which replica manager to use to perform the send + /// \param[in/out] serializationContext Target recipient, optional timestamp, type of command + virtual void SerializeDownloadStarted(RakNet::BitStream *objectData, ReplicaManager2 *replicaManager, SerializationContext *serializationContext); + + /// CALLBACK: + /// Called after a download is sent to a new connection + /// \param[out] objectData What data you want to send to DeSerializeDownloadComplete() + /// \param[in] replicaManager Which replica manager to use to perform the send + /// \param[in/out] serializationContext Target recipient, optional timestamp, type of command + virtual void SerializeDownloadComplete(RakNet::BitStream *objectData, ReplicaManager2 *replicaManager, SerializationContext *serializationContext); + + /// CALLBACK: + /// A new connection was added. All objects that are constructed and visible for this system will arrive immediately after this message. + /// Write data to \a objectData by deriving from SerializeDownloadStarted() + /// \note Only called if SetAutoUpdateScope is called with serializationVisiblity or construction true. (This is the default) + /// \param[in] objectData objectData Data written through SerializeDownloadStarted() + /// \param[in] replicaManager Which replica manager to use to perform the send + /// \param[in] timestamp timestamp sent, 0 for none + /// \param[in] serializationType Type of command + virtual void DeserializeDownloadStarted(RakNet::BitStream *objectData, SystemAddress sender, ReplicaManager2 *replicaManager, RakNetTime timestamp, SerializationType serializationType); + + /// CALLBACK: + /// A new connection was added. All objects that are constructed and visible for this system have now arrived. + /// Write data to \a objectData by deriving from SerializeDownloadComplete + /// \note Only called if SetAutoUpdateScope is called with serializationVisiblity or construction true. (This is the default) + /// \param[in] objectData objectData Data written through SerializeDownloadComplete() + /// \param[in] replicaManager Which replica manager to use to perform the send + /// \param[in] timestamp timestamp sent, 0 for none + /// \param[in] serializationType Type of command + virtual void DeserializeDownloadComplete(RakNet::BitStream *objectData, SystemAddress sender, ReplicaManager2 *replicaManager, RakNetTime timestamp, SerializationType serializationType); + + /// Given a list of objects, compare it against lastConstructionList. + /// BroadcastConstruct() is called for objects that only exist in the new list. + /// BroadcastDestruct() is called for objects that only exist in the old list. + /// This is used by SetConstructionByReplicaQuery() for all Replica2 that do not return BQR_ALWAYS from Replica2::QueryConstruction() + /// If you want to pass your own, more efficient list to check against, call ReplicaManager2::SetAutoUpdateScope with construction=false and call this function yourself when desired + /// \param[in] List of all objects that do not return BQR_ALWAYS from Replica2::QueryConstruction() that should currently be created on this system + /// \param[in] replicaManager Which replica manager to use to perform the send + virtual void SetConstructionByList(DataStructures::OrderedList ¤tVisibility, ReplicaManager2 *replicaManager); + + /// Given a list of objects, compare it against lastSerializationList. + /// Replica2::BroadcastVisibility(true) is called for objects that only exist in the new list. + /// Replica2::BroadcastVisibility(false) is called for objects that only exist in the old list. + /// This is used by SetVisibilityByReplicaQuery() for all Replica2 that do not return BQR_ALWAYS from Replica2::QueryVisibility() + /// If you want to pass your own, more efficient list to check against, call ReplicaManager2::SetAutoUpdateScope with construction=false and call this function yourself when desired + /// \param[in] List of all objects that do not return BQR_ALWAYS from Replica2::QueryConstruction() that should currently be created on this system + /// \param[in] replicaManager Which replica manager to use to perform the send + virtual void SetVisibilityByList(DataStructures::OrderedList ¤tVisibility, ReplicaManager2 *replicaManager); + + /// Go through all registered Replica2 objects that do not return BQR_ALWAYS from Replica2::QueryConstruction() + /// For each of these objects that return BQR_YES, pass them to currentVisibility in SetConstructionByList() + /// Automatically called every tick if ReplicaManager2::SetAutoUpdateScope with construction=true is called (which is the default) + /// \param[in] replicaManager Which replica manager to use to perform the send + virtual void SetConstructionByReplicaQuery(ReplicaManager2 *replicaManager); + + /// Go through all registered Replica2 objects that do not return BQR_ALWAYS from Replica2::QueryVisibility() + /// For each of these objects that return BQR_YES, pass them to currentVisibility in SetVisibilityByList() + /// Automatically called every tick if ReplicaManager2::SetAutoUpdateScope with construction=true is called (which is the default) + /// \param[in] replicaManager Which replica manager to use to perform the send + virtual void SetVisibilityByReplicaQuery(ReplicaManager2 *replicaManager); + + /// Set the system address to use with this class instance. This is set internally when the object is created + void SetSystemAddress(SystemAddress sa); + + /// Get the system address associated with this class instance. + SystemAddress GetSystemAddress(void) const; + + /// Set the guid to use with this class instance. This is set internally when the object is created + void SetGuid(RakNetGUID guid); + + /// Get the guid associated with this class instance. + RakNetGUID GetGuid(void) const; + +protected: + void Deref(Replica2* replica); + + void CalculateListExclusivity( + const DataStructures::OrderedList &listOne, + const DataStructures::OrderedList &listTwo, + DataStructures::OrderedList &exclusiveToListOne, + DataStructures::OrderedList &exclusiveToListTwo + ) const; + + virtual Replica2 * ReceiveConstruct(RakNet::BitStream *replicaData, NetworkID networkId, SystemAddress sender, unsigned char localClientId, SerializationType type, + ReplicaManager2 *replicaManager, RakNetTime timestamp, DataStructures::OrderedList &exclusionList); + + friend class ReplicaManager2; + + // Address of this participant + SystemAddress systemAddress; + RakNetGUID rakNetGuid; + + DataStructures::OrderedList lastConstructionList; + DataStructures::OrderedList lastSerializationList; +}; + +} + +#endif + +#endif // _RAKNET_SUPPORT_* diff --git a/RakNet/Sources/ReplicaManager3.cpp b/RakNet/Sources/ReplicaManager3.cpp new file mode 100644 index 0000000..4510048 --- /dev/null +++ b/RakNet/Sources/ReplicaManager3.cpp @@ -0,0 +1,2093 @@ +#include "NativeFeatureIncludes.h" +#if _RAKNET_SUPPORT_ReplicaManager3==1 + +#include "ReplicaManager3.h" +#include "GetTime.h" +#include "MessageIdentifiers.h" +#include "RakPeerInterface.h" +#include "NetworkIDManager.h" + +using namespace RakNet; + +DEFINE_MULTILIST_PTR_TO_MEMBER_COMPARISONS(LastSerializationResult,Replica3*,replica); + +// -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + +bool PRO::operator==( const PRO& right ) const +{ + return priority == right.priority && reliability == right.reliability && orderingChannel == right.orderingChannel && sendReceipt == right.sendReceipt; +} + +bool PRO::operator!=( const PRO& right ) const +{ + return priority != right.priority || reliability != right.reliability || orderingChannel != right.orderingChannel || sendReceipt != right.sendReceipt; +} + + +// -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + +LastSerializationResult::LastSerializationResult() +{ + replica=0; + lastSerializationResultBS=0; +} +LastSerializationResult::~LastSerializationResult() +{ + if (lastSerializationResultBS) + RakNet::OP_DELETE(lastSerializationResultBS,__FILE__,__LINE__); +} +void LastSerializationResult::AllocBS(void) +{ + if (lastSerializationResultBS==0) + { + lastSerializationResultBS=RakNet::OP_NEW(__FILE__,__LINE__); + } +} +// -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + +ReplicaManager3::ReplicaManager3() +{ + defaultSendParameters.orderingChannel=0; + defaultSendParameters.priority=HIGH_PRIORITY; + defaultSendParameters.reliability=RELIABLE_ORDERED; + defaultSendParameters.sendReceipt=0; + autoSerializeInterval=30; + lastAutoSerializeOccurance=0; + worldId=0; + autoCreateConnections=true; + autoDestroyConnections=true; + networkIDManager=0; +} + +// -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + +ReplicaManager3::~ReplicaManager3() +{ + Clear(); +} + +// -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + +void ReplicaManager3::SetAutoManageConnections(bool autoCreate, bool autoDestroy) +{ + autoCreateConnections=autoCreate; + autoDestroyConnections=autoDestroy; +} + +// -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + +bool ReplicaManager3::PushConnection(RakNet::Connection_RM3 *newConnection) +{ + if (newConnection==0) + return false; + if (GetConnectionByGUID(newConnection->GetRakNetGUID())) + return false; + DataStructures::DefaultIndexType index = connectionList.GetInsertionIndex(newConnection); + if (index!=(DataStructures::DefaultIndexType)-1) + { + connectionList.InsertAtIndex(newConnection,index,__FILE__,__LINE__); + + // Send message to validate the connection + newConnection->SendValidation(rakPeerInterface, worldId); + + Connection_RM3::ConstructionMode constructionMode = newConnection->QueryConstructionMode(); + if (constructionMode==Connection_RM3::QUERY_REPLICA_FOR_CONSTRUCTION || constructionMode==Connection_RM3::QUERY_REPLICA_FOR_CONSTRUCTION_AND_DESTRUCTION) + { + DataStructures::DefaultIndexType pushIdx; + for (pushIdx=0; pushIdx < userReplicaList.GetSize(); pushIdx++) + newConnection->OnLocalReference(userReplicaList[pushIdx], this); + } + } + return true; +} + + +// -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + +RakNet::Connection_RM3 * ReplicaManager3::PopConnection(DataStructures::DefaultIndexType index) +{ + DataStructures::Multilist replicaList; + DataStructures::Multilist destructionList; + DataStructures::Multilist broadcastList; + RakNet::Connection_RM3 *connection; + DataStructures::DefaultIndexType index2; + RM3ActionOnPopConnection action; + + connection=connectionList[index]; + + RakNetGUID guid = connection->GetRakNetGUID(); + GetReplicasCreatedByGuid(guid, replicaList); + + for (index2=0; index2 < replicaList.GetSize(); index2++) + { + action = replicaList[index2]->QueryActionOnPopConnection(connection); + replicaList[index2]->OnPoppedConnection(connection); + if (action==RM3AOPC_DELETE_REPLICA) + { + destructionList.Push( replicaList[index2], __FILE__, __LINE__ ); + } + else if (action==RM3AOPC_DELETE_REPLICA_AND_BROADCAST_DESTRUCTION) + { + destructionList.Push( replicaList[index2], __FILE__, __LINE__ ); + + broadcastList.Push( replicaList[index2], __FILE__, __LINE__ ); + } + } + + BroadcastDestructionList(broadcastList, connection->GetSystemAddress()); + for (index2=0; index2 < destructionList.GetSize(); index2++) + { + destructionList[index2]->PreDestruction(connection); + destructionList[index2]->DeallocReplica(connection); + } + + connectionList.RemoveAtIndex(index,__FILE__,__LINE__); + return connection; +} + +// -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + +RakNet::Connection_RM3 * ReplicaManager3::PopConnection(RakNetGUID guid) +{ + DataStructures::DefaultIndexType index; + + for (index=0; index < connectionList.GetSize(); index++) + { + if (connectionList[index]->GetRakNetGUID()==guid) + { + return PopConnection(index); + } + } + return 0; +} + +// -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + +void ReplicaManager3::Reference(RakNet::Replica3 *replica3) +{ + DataStructures::DefaultIndexType index = ReferenceInternal(replica3); + + if (index!=(DataStructures::DefaultIndexType)-1) + { + DataStructures::DefaultIndexType pushIdx; + for (pushIdx=0; pushIdx < connectionList.GetSize(); pushIdx++) + { + Connection_RM3::ConstructionMode constructionMode = connectionList[pushIdx]->QueryConstructionMode(); + if (constructionMode==Connection_RM3::QUERY_REPLICA_FOR_CONSTRUCTION || constructionMode==Connection_RM3::QUERY_REPLICA_FOR_CONSTRUCTION_AND_DESTRUCTION) + { + connectionList[pushIdx]->OnLocalReference(replica3, this); + } + } + } +} + +// -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + +DataStructures::DefaultIndexType ReplicaManager3::ReferenceInternal(RakNet::Replica3 *replica3) +{ + DataStructures::DefaultIndexType index; + index = userReplicaList.GetInsertionIndex(replica3); + if (index!=(DataStructures::DefaultIndexType)-1) + { + if (networkIDManager==0) + networkIDManager=rakPeerInterface->GetNetworkIDManager(); + RakAssert(networkIDManager); + replica3->SetNetworkIDManager(networkIDManager); + if (replica3->creatingSystemGUID==UNASSIGNED_RAKNET_GUID) + replica3->creatingSystemGUID=rakPeerInterface->GetGuidFromSystemAddress(UNASSIGNED_SYSTEM_ADDRESS); + replica3->replicaManager=this; + userReplicaList.InsertAtIndex(replica3,index,__FILE__,__LINE__); + } + return index; +} + +// -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + +void ReplicaManager3::Dereference(RakNet::Replica3 *replica3) +{ + DataStructures::DefaultIndexType index, index2; + for (index=0; index < userReplicaList.GetSize(); index++) + { + if (userReplicaList[index]==replica3) + { + userReplicaList.RemoveAtIndex(index,__FILE__,__LINE__); + break; + } + } + + // Remove from all connections + for (index2=0; index2 < connectionList.GetSize(); index2++) + { + connectionList[index2]->OnDereference(replica3, this); + } +} + +// -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + +void ReplicaManager3::DereferenceList(DataStructures::Multilist &replicaListIn) +{ + DataStructures::DefaultIndexType index; + for (index=0; index < userReplicaList.GetSize(); index++) + Dereference(replicaListIn[index]); +} + + +// -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + +void ReplicaManager3::GetReplicasCreatedByMe(DataStructures::Multilist &replicaListOut) +{ + RakNetGUID myGuid = rakPeerInterface->GetGuidFromSystemAddress(UNASSIGNED_SYSTEM_ADDRESS); + GetReplicasCreatedByGuid(rakPeerInterface->GetGuidFromSystemAddress(UNASSIGNED_SYSTEM_ADDRESS), replicaListOut); +} + +// -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + +void ReplicaManager3::GetReferencedReplicaList(DataStructures::Multilist &replicaListOut) +{ + replicaListOut=userReplicaList; +} +// -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + +void ReplicaManager3::GetReplicasCreatedByGuid(RakNetGUID guid, DataStructures::Multilist &replicaListOut) +{ + replicaListOut.Clear(false,__FILE__,__LINE__); + DataStructures::DefaultIndexType index; + for (index=0; index < userReplicaList.GetSize(); index++) + { + if (userReplicaList[index]->creatingSystemGUID==guid) + replicaListOut.Push(userReplicaList[index],__FILE__,__LINE__); + } +} + + +// -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + +unsigned ReplicaManager3::GetReplicaCount(void) const +{ + return userReplicaList.GetSize(); +} + +// -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + +Replica3 *ReplicaManager3::GetReplicaAtIndex(unsigned index) +{ + return userReplicaList[index]; +} + +// -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + +DataStructures::DefaultIndexType ReplicaManager3::GetConnectionCount(void) const +{ + return connectionList.GetSize(); +} + +// -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + +Connection_RM3* ReplicaManager3::GetConnectionAtIndex(unsigned index) const +{ + return connectionList[index]; +} + +// -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + +Connection_RM3* ReplicaManager3::GetConnectionBySystemAddress(SystemAddress sa) const +{ + DataStructures::DefaultIndexType index; + for (index=0; index < connectionList.GetSize(); index++) + { + if (connectionList[index]->GetSystemAddress()==sa) + { + return connectionList[index]; + } + } + return 0; +} + +// -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + +Connection_RM3* ReplicaManager3::GetConnectionByGUID(RakNetGUID guid) const +{ + DataStructures::DefaultIndexType index; + for (index=0; index < connectionList.GetSize(); index++) + { + if (connectionList[index]->GetRakNetGUID()==guid) + { + return connectionList[index]; + } + } + return 0; +} + +// -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + +void ReplicaManager3::SetDefaultOrderingChannel(char def) +{ + defaultSendParameters.orderingChannel=def; +} + +// -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + +void ReplicaManager3::SetDefaultPacketPriority(PacketPriority def) +{ + defaultSendParameters.priority=def; +} + +// -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + +void ReplicaManager3::SetDefaultPacketReliability(PacketReliability def) +{ + defaultSendParameters.reliability=def; +} + +// -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + +void ReplicaManager3::SetAutoSerializeInterval(RakNetTime intervalMS) +{ + autoSerializeInterval=intervalMS; +} + +// -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + +void ReplicaManager3::GetConnectionsThatHaveReplicaConstructed(Replica3 *replica, DataStructures::Multilist &connectionsThatHaveConstructedThisReplica) +{ + connectionsThatHaveConstructedThisReplica.Clear(false,__FILE__,__LINE__); + DataStructures::DefaultIndexType index; + for (index=0; index < connectionList.GetSize(); index++) + { + if (connectionList[index]->HasReplicaConstructed(replica)) + connectionsThatHaveConstructedThisReplica.Push(connectionList[index],__FILE__,__LINE__); + } +} + +// -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + +void ReplicaManager3::Clear(void) +{ + if (autoDestroyConnections) + { + for (DataStructures::DefaultIndexType i=0; i < connectionList.GetSize(); i++) + DeallocConnection(connectionList[i]); + } + connectionList.Clear(true,__FILE__,__LINE__); + userReplicaList.Clear(true,__FILE__,__LINE__); +} + +// -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + +PRO ReplicaManager3::GetDefaultSendParameters(void) const +{ + return defaultSendParameters; +} + +// -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + +void ReplicaManager3::SetWorldID(unsigned char id) +{ + worldId=id; +} + +// -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + +unsigned char ReplicaManager3::GetWorldID(void) const +{ + return worldId; +} + +// -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + +NetworkIDManager *ReplicaManager3::GetNetworkIDManager(void) const +{ + return networkIDManager; +} + +// -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + +void ReplicaManager3::SetNetworkIDManager(NetworkIDManager *_networkIDManager) +{ + networkIDManager=_networkIDManager; + if (networkIDManager) + networkIDManager->SetGuid(rakPeerInterface->GetGuidFromSystemAddress(UNASSIGNED_SYSTEM_ADDRESS)); +} + +// -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + +PluginReceiveResult ReplicaManager3::OnReceive(Packet *packet) +{ + if (packet->length<2) + return RR_CONTINUE_PROCESSING; + + unsigned char incomingWorldId; + + RakNetTime timestamp=0; + unsigned char packetIdentifier, packetDataOffset; + if ( ( unsigned char ) packet->data[ 0 ] == ID_TIMESTAMP ) + { + if ( packet->length > sizeof( unsigned char ) + sizeof( RakNetTime ) ) + { + packetIdentifier = ( unsigned char ) packet->data[ sizeof( unsigned char ) + sizeof( RakNetTime ) ]; + // Required for proper endian swapping + RakNet::BitStream tsBs(packet->data+sizeof(MessageID),packet->length-1,false); + tsBs.Read(timestamp); + incomingWorldId=packet->data[sizeof( unsigned char )*2 + sizeof( RakNetTime )]; + packetDataOffset=sizeof( unsigned char )*3 + sizeof( RakNetTime ); + } + else + return RR_STOP_PROCESSING_AND_DEALLOCATE; + } + else + { + packetIdentifier = ( unsigned char ) packet->data[ 0 ]; + incomingWorldId=packet->data[sizeof( unsigned char )]; + packetDataOffset=sizeof( unsigned char )*2; + } + + switch (packetIdentifier) + { + case ID_REPLICA_MANAGER_CONSTRUCTION: + if (incomingWorldId!=worldId) + return RR_CONTINUE_PROCESSING; + OnConstruction(packet->data, packet->length, packet->guid, packetDataOffset); + break; + case ID_REPLICA_MANAGER_SERIALIZE: + if (incomingWorldId!=worldId) + return RR_CONTINUE_PROCESSING; + OnSerialize(packet->data, packet->length, packet->guid, timestamp, packetDataOffset); + break; + case ID_REPLICA_MANAGER_3_LOCAL_CONSTRUCTION_REJECTED: + if (incomingWorldId!=worldId) + return RR_CONTINUE_PROCESSING; + OnLocalConstructionRejected(packet->data, packet->length, packet->guid, packetDataOffset); + break; + case ID_REPLICA_MANAGER_3_LOCAL_CONSTRUCTION_ACCEPTED: + if (incomingWorldId!=worldId) + return RR_CONTINUE_PROCESSING; + OnLocalConstructionAccepted(packet->data, packet->length, packet->guid, packetDataOffset); + break; + case ID_REPLICA_MANAGER_DOWNLOAD_STARTED: + if (incomingWorldId!=worldId) + return RR_CONTINUE_PROCESSING; + OnDownloadStarted(packet->data, packet->length, packet->guid, packetDataOffset); + break; + case ID_REPLICA_MANAGER_DOWNLOAD_COMPLETE: + if (incomingWorldId!=worldId) + return RR_CONTINUE_PROCESSING; + OnDownloadComplete(packet->data, packet->length, packet->guid, packetDataOffset); + break; + case ID_REPLICA_MANAGER_3_SERIALIZE_CONSTRUCTION_EXISTING: + if (incomingWorldId!=worldId) + return RR_CONTINUE_PROCESSING; + OnConstructionExisting(packet->data, packet->length, packet->guid, packetDataOffset); + break; + case ID_REPLICA_MANAGER_SCOPE_CHANGE: + { + if (incomingWorldId!=worldId) + return RR_CONTINUE_PROCESSING; + + Connection_RM3 *connection = GetConnectionByGUID(packet->guid); + if (connection && connection->isValidated==false) + { + // This connection is now confirmed bidirectional + connection->isValidated=true; + // Reply back on validation + connection->SendValidation(rakPeerInterface,worldId); + } + } + } + + return RR_CONTINUE_PROCESSING; +} + +// -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + +void Connection_RM3::AutoConstructByQuery(ReplicaManager3 *replicaManager3) +{ + ValidateLists(replicaManager3); + + ConstructionMode constructionMode = QueryConstructionMode(); + + DataStructures::DefaultIndexType index; + RM3ConstructionState constructionState; + LastSerializationResult *lsr; + index=0; + + constructedReplicasCulled.Clear(false,__FILE__,__LINE__); + destroyedReplicasCulled.Clear(false,__FILE__,__LINE__); + + if (constructionMode==QUERY_REPLICA_FOR_CONSTRUCTION || constructionMode==QUERY_REPLICA_FOR_CONSTRUCTION_AND_DESTRUCTION) + { + while (index < queryToConstructReplicaList.GetSize()) + { + lsr=queryToConstructReplicaList[index]; + constructionState=lsr->replica->QueryConstruction(this, replicaManager3); + if (constructionState==RM3CS_ALREADY_EXISTS_REMOTELY) + { + OnReplicaAlreadyExists(index, replicaManager3); + + // Serialize construction data to this connection + RakNet::BitStream bsOut; + bsOut.Write((MessageID)ID_REPLICA_MANAGER_3_SERIALIZE_CONSTRUCTION_EXISTING); + bsOut.Write(replicaManager3->GetWorldID()); + NetworkID networkId; + networkId=lsr->replica->GetNetworkID(); + bsOut.Write(networkId); + BitSize_t bitsWritten = bsOut.GetNumberOfBitsUsed(); + lsr->replica->SerializeConstructionExisting(&bsOut, this); + if (bsOut.GetNumberOfBitsUsed()!=bitsWritten) + replicaManager3->SendUnified(&bsOut,HIGH_PRIORITY,RELIABLE_ORDERED,0,GetSystemAddress(), false); + + // Serialize first serialization to this connection. + // This is done here, as it isn't done in PushConstruction + SerializeParameters sp; + RakNet::BitStream emptyBs; + for (index=0; index < RM3_NUM_OUTPUT_BITSTREAM_CHANNELS; index++) + { + sp.lastSentBitstream[index]=&emptyBs; + sp.pro[index]=replicaManager3->GetDefaultSendParameters(); + } + sp.bitsWrittenSoFar=0; + sp.destinationConnection=this; + sp.messageTimestamp=0; + sp.whenLastSerialized=0; + + RakNet::Replica3 *replica = lsr->replica; + + RM3SerializationResult res = replica->Serialize(&sp); + if (res!=RM3SR_NEVER_SERIALIZE_FOR_THIS_CONNECTION && + res!=RM3SR_DO_NOT_SERIALIZE && + res!=RM3SR_SERIALIZED_UNIQUELY) + { + bool allIndices[RM3_NUM_OUTPUT_BITSTREAM_CHANNELS]; + for (int z=0; z < RM3_NUM_OUTPUT_BITSTREAM_CHANNELS; z++) + { + sp.bitsWrittenSoFar+=sp.outputBitstream[z].GetNumberOfBitsUsed(); + allIndices[z]=true; + } + if (SendSerialize(replica, allIndices, sp.outputBitstream, sp.messageTimestamp, sp.pro, replicaManager3->GetRakPeerInterface(), replicaManager3->GetWorldID())==SSICR_SENT_DATA) + lsr->replica->whenLastSerialized=RakNet::GetTime(); + } + } + else if (constructionState==RM3CS_SEND_CONSTRUCTION) + { + OnConstructToThisConnection(index, replicaManager3); + constructedReplicasCulled.Push(lsr->replica,lsr->replica,__FILE__,__LINE__); + } + else if (constructionState==RM3CS_NEVER_CONSTRUCT) + { + OnNeverConstruct(index, replicaManager3); + } + else// if (constructionState==RM3CS_NO_ACTION) + { + // Do nothing + index++; + } + } + + if (constructionMode==QUERY_REPLICA_FOR_CONSTRUCTION_AND_DESTRUCTION) + { + RM3DestructionState destructionState; + index=0; + while (index < queryToDestructReplicaList.GetSize()) + { + lsr=queryToDestructReplicaList[index]; + destructionState=lsr->replica->QueryDestruction(this, replicaManager3); + if (destructionState==RM3DS_SEND_DESTRUCTION) + { + OnSendDestructionFromQuery(index, replicaManager3); + destroyedReplicasCulled.Push(lsr->replica,lsr->replica,__FILE__,__LINE__); + } + else if (destructionState==RM3DS_DO_NOT_QUERY_DESTRUCTION) + { + OnDoNotQueryDestruction(index, replicaManager3); + } + else// if (destructionState==RM3CS_NO_ACTION) + { + // Do nothing + index++; + } + } + } + } + else if (constructionMode==QUERY_CONNECTION_FOR_REPLICA_LIST) + { + QueryReplicaList(constructedReplicasCulled,destroyedReplicasCulled); + + DataStructures::DefaultIndexType idx1, idx2; +#ifdef _DEBUG + // The user should not construct a replica that already exists + for (idx2=0; idx2 < constructedReplicasCulled.GetSize(); idx2++) + { + RakAssert(constructedReplicaList.GetIndexOf(constructedReplicasCulled[idx2])==(DataStructures::DefaultIndexType)-1); + } + +#endif + + // Create new + for (idx2=0; idx2 < constructedReplicasCulled.GetSize(); idx2++) + OnConstructToThisConnection(constructedReplicasCulled[idx2], replicaManager3); + + bool exists; + for (idx2=0; idx2 < destroyedReplicasCulled.GetSize(); idx2++) + { + exists=false; + idx1=constructedReplicaList.GetIndexOf(destroyedReplicasCulled[idx2]); + RakAssert(idx1!=(DataStructures::DefaultIndexType)-1); + if (idx1!=(DataStructures::DefaultIndexType)-1) + { + OnSendDestructionFromQuery(idx1,replicaManager3); + } + + // If this assert hits, the user tried to destroy a replica that doesn't exist on the remote system + RakAssert(exists); + } + } + + SendConstruction(constructedReplicasCulled,destroyedReplicasCulled,replicaManager3->defaultSendParameters,replicaManager3->rakPeerInterface,replicaManager3->worldId); + +} +void ReplicaManager3::Update(void) +{ + DataStructures::DefaultIndexType index,index2; + + for (index=0; index < connectionList.GetSize(); index++) + { + if (connectionList[index]->isValidated==false) + continue; + connectionList[index]->AutoConstructByQuery(this); + } + + if (autoSerializeInterval>0) + { + RakNetTime time = RakNet::GetTime(); + + if (time - lastAutoSerializeOccurance > autoSerializeInterval) + { + for (index=0; index < userReplicaList.GetSize(); index++) + { + userReplicaList[index]->forceSendUntilNextUpdate=false; + userReplicaList[index]->OnUserReplicaPreSerializeTick(); + } + + + DataStructures::DefaultIndexType index; + SerializeParameters sp; + sp.curTime=time; + Connection_RM3 *connection; + SendSerializeIfChangedResult ssicr; + sp.messageTimestamp=0; + for (int i=0; i < RM3_NUM_OUTPUT_BITSTREAM_CHANNELS; i++) + sp.pro[i]=defaultSendParameters; + index2=0; + for (index=0; index < connectionList.GetSize(); index++) + { + connection = connectionList[index]; + sp.bitsWrittenSoFar=0; + index2=0; + while (index2 < connection->queryToSerializeReplicaList.GetSize()) + { + sp.destinationConnection=connection; + sp.whenLastSerialized=connection->queryToSerializeReplicaList[index2]->replica->whenLastSerialized; + ssicr=connection->SendSerializeIfChanged(index2, &sp, GetRakPeerInterface(), GetWorldID(), this); + if (ssicr==SSICR_SENT_DATA) + { + connection->queryToSerializeReplicaList[index2]->replica->whenLastSerialized=time; + index2++; + } + else if (ssicr==SSICR_NEVER_SERIALIZE) + { + // Removed from the middle of the list + } + else + index2++; + } + } + + lastAutoSerializeOccurance=time; + } + } +} + +// -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + +void ReplicaManager3::OnClosedConnection(SystemAddress systemAddress, RakNetGUID rakNetGUID, PI2_LostConnectionReason lostConnectionReason ) +{ + (void) lostConnectionReason; + (void) systemAddress; + if (autoDestroyConnections) + { + Connection_RM3 *connection = PopConnection(rakNetGUID); + if (connection) + DeallocConnection(connection); + } +} + +// -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + +void ReplicaManager3::OnNewConnection(SystemAddress systemAddress, RakNetGUID rakNetGUID, bool isIncoming) +{ + (void) isIncoming; + if (autoCreateConnections) + { + Connection_RM3 *connection = AllocConnection(systemAddress, rakNetGUID); + if (connection) + PushConnection(connection); + } +} + +// -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + +void ReplicaManager3::OnRakPeerShutdown(void) +{ + if (autoDestroyConnections) + { + while (connectionList.GetSize()) + { + Connection_RM3 *connection = PopConnection(connectionList.GetSize()-1); + if (connection) + DeallocConnection(connection); + } + } + + Clear(); +} + +// -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + +void ReplicaManager3::OnConstructionExisting(unsigned char *packetData, int packetDataLength, RakNetGUID senderGuid, unsigned char packetDataOffset) +{ + Connection_RM3 *connection = GetConnectionByGUID(senderGuid); + if (connection==0) + { + // Almost certainly a bug + RakAssert("Got OnConstruction but no connection yet" && 0); + return; + } + + RakNet::BitStream bsIn(packetData,packetDataLength,false); + bsIn.IgnoreBytes(packetDataOffset); + + if (networkIDManager==0) + networkIDManager=rakPeerInterface->GetNetworkIDManager(); + RakAssert(networkIDManager); + + NetworkID networkId; + bsIn.Read(networkId); + Replica3* existingReplica = networkIDManager->GET_OBJECT_FROM_ID(networkId); + if (existingReplica) + { + existingReplica->DeserializeConstructionExisting(&bsIn, connection); + } +} + +// -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + +void ReplicaManager3::OnConstruction(unsigned char *packetData, int packetDataLength, RakNetGUID senderGuid, unsigned char packetDataOffset) +{ + Connection_RM3 *connection = GetConnectionByGUID(senderGuid); + if (connection==0) + { + // Almost certainly a bug + RakAssert("Got OnConstruction but no connection yet" && 0); + return; + } + + RakNet::BitStream bsIn(packetData,packetDataLength,false); + bsIn.IgnoreBytes(packetDataOffset); + DataStructures::DefaultIndexType objectListSize, index, index2; + BitSize_t bitOffset; + Replica3 *replica; + uint32_t allocationNumber=0; + NetworkID networkId; + RakNetGUID creatingSystemGuid; + + if (networkIDManager==0) + networkIDManager=rakPeerInterface->GetNetworkIDManager(); + RakAssert(networkIDManager); + + bsIn.Read(objectListSize); + for (index=0; index < objectListSize; index++) + { + bsIn.Read(bitOffset); + bsIn.Read(networkId); + Replica3* existingReplica = networkIDManager->GET_OBJECT_FROM_ID(networkId); + if (existingReplica) + { + existingReplica->replicaManager=this; + + // Network ID already in use + connection->OnDownloadExisting(existingReplica, this); + + bsIn.SetReadOffset(bitOffset); + continue; + } + + replica = connection->AllocReplica(&bsIn, this); + if (replica==0) + { + bsIn.SetReadOffset(bitOffset); + continue; + } + + replica->SetNetworkIDManager(networkIDManager); + + if (networkId==UNASSIGNED_NETWORK_ID) + { + if (networkIDManager->IsNetworkIDAuthority()==false) + { + // Can't assign network ID + replica->replicaManager=0; + replica->DeallocReplica(connection); + bsIn.SetReadOffset(bitOffset); + continue; + } + + bsIn.Read(allocationNumber); + } + else + { + + replica->SetNetworkID(networkId); + } + + replica->replicaManager=this; + bsIn.Read(creatingSystemGuid); + replica->creatingSystemGUID=creatingSystemGuid; + + if (!replica->QueryRemoteConstruction(connection) || + !replica->DeserializeConstruction(&bsIn, connection)) + { + // Overtake this message to mean construction rejected + if (networkId==UNASSIGNED_NETWORK_ID) + { + RakNet::BitStream bsOut; + bsOut.Write((MessageID)ID_REPLICA_MANAGER_3_LOCAL_CONSTRUCTION_REJECTED); + bsOut.Write(worldId); + bsOut.Write(allocationNumber); + replica->SerializeConstructionRequestRejected(&bsOut,connection); + rakPeerInterface->Send(&bsOut, defaultSendParameters.priority, defaultSendParameters.reliability, defaultSendParameters.orderingChannel, connection->GetSystemAddress(), false, defaultSendParameters.sendReceipt); + } + + replica->replicaManager=0; + replica->DeallocReplica(connection); + bsIn.SetReadOffset(bitOffset); + continue; + } + + bsIn.SetReadOffset(bitOffset); + replica->PostDeserializeConstruction(connection); + + if (networkId==UNASSIGNED_NETWORK_ID) + { + // Overtake this message to mean construction accepted + RakNet::BitStream bsOut; + bsOut.Write((MessageID)ID_REPLICA_MANAGER_3_LOCAL_CONSTRUCTION_ACCEPTED); + bsOut.Write(worldId); + bsOut.Write(allocationNumber); + bsOut.Write(replica->GetNetworkID()); + replica->SerializeConstructionRequestAccepted(&bsOut,connection); + rakPeerInterface->Send(&bsOut, defaultSendParameters.priority, defaultSendParameters.reliability, defaultSendParameters.orderingChannel, connection->GetSystemAddress(), false, defaultSendParameters.sendReceipt); + } + bsIn.AlignReadToByteBoundary(); + + // Register the replica + ReferenceInternal(replica); + + // Tell the connection(s) that this object exists since they just sent it to us + connection->OnDownloadFromThisSystem(replica, this); + + for (index2=0; index2 < connectionList.GetSize(); index2++) + { + if (connectionList[index2]!=connection) + connectionList[index2]->OnDownloadFromOtherSystem(replica, this); + } + } + + // Destructions + bool b = bsIn.Read(objectListSize); + RakAssert(b); + for (index=0; index < objectListSize; index++) + { + bsIn.Read(networkId); + bsIn.Read(bitOffset); + replica = networkIDManager->GET_OBJECT_FROM_ID(networkId); + if (replica==0) + { + // Unknown object + bsIn.SetReadOffset(bitOffset); + continue; + } + bsIn.Read(replica->deletingSystemGUID); + if (replica->DeserializeDestruction(&bsIn,connection)) + { + // Make sure it wasn't deleted in DeserializeDestruction + if (networkIDManager->GET_OBJECT_FROM_ID(networkId)) + { + replica->PreDestruction(connection); + + // Forward deletion by remote system + BroadcastDestruction(replica,connection->GetSystemAddress()); + Dereference(replica); + replica->replicaManager=0; // Prevent BroadcastDestruction from being called again + replica->DeallocReplica(connection); + } + } + else + { + replica->PreDestruction(connection); + connection->OnDereference(replica, this); + } + + bsIn.AlignReadToByteBoundary(); + } +} + +// -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + +void ReplicaManager3::OnSerialize(unsigned char *packetData, int packetDataLength, RakNetGUID senderGuid, RakNetTime timestamp, unsigned char packetDataOffset) +{ + Connection_RM3 *connection = GetConnectionByGUID(senderGuid); + if (connection==0) + return; + if (networkIDManager==0) + networkIDManager=rakPeerInterface->GetNetworkIDManager(); + RakAssert(networkIDManager); + RakNet::BitStream bsIn(packetData,packetDataLength,false); + bsIn.IgnoreBytes(packetDataOffset); + + struct DeserializeParameters ds; + ds.timeStamp=timestamp; + ds.sourceConnection=connection; + + Replica3 *replica; + NetworkID networkId; + BitSize_t bitsUsed; + bsIn.Read(networkId); + replica = networkIDManager->GET_OBJECT_FROM_ID(networkId); + if (replica) + { + for (int z=0; z < RM3_NUM_OUTPUT_BITSTREAM_CHANNELS; z++) + { + bsIn.Read(ds.bitstreamWrittenTo[z]); + if (ds.bitstreamWrittenTo[z]) + { + bsIn.ReadCompressed(bitsUsed); + bsIn.AlignReadToByteBoundary(); + bsIn.Read(ds.serializationBitstream[z], bitsUsed); + } + } + replica->Deserialize(&ds); + } +} +// -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + +void ReplicaManager3::OnDownloadStarted(unsigned char *packetData, int packetDataLength, RakNetGUID senderGuid, unsigned char packetDataOffset) +{ + Connection_RM3 *connection = GetConnectionByGUID(senderGuid); + if (connection==0) + return; + RakNet::BitStream bsIn(packetData,packetDataLength,false); + bsIn.IgnoreBytes(packetDataOffset); + connection->DeserializeOnDownloadStarted(&bsIn); +} + +// -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + +void ReplicaManager3::OnDownloadComplete(unsigned char *packetData, int packetDataLength, RakNetGUID senderGuid, unsigned char packetDataOffset) +{ + Connection_RM3 *connection = GetConnectionByGUID(senderGuid); + if (connection==0) + return; + RakNet::BitStream bsIn(packetData,packetDataLength,false); + bsIn.IgnoreBytes(packetDataOffset); + connection->DeserializeOnDownloadComplete(&bsIn); +} + +// -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + +void ReplicaManager3::OnLocalConstructionRejected(unsigned char *packetData, int packetDataLength, RakNetGUID senderGuid, unsigned char packetDataOffset) +{ + Connection_RM3 *connection = GetConnectionByGUID(senderGuid); + if (connection==0) + return; + RakNet::BitStream bsIn(packetData,packetDataLength,false); + bsIn.IgnoreBytes(packetDataOffset); + uint32_t allocationNumber; + bsIn.Read(allocationNumber); + DataStructures::DefaultIndexType index; + for (index=0; index < userReplicaList.GetSize(); index++) + { + if (userReplicaList[index]->GetAllocationNumber()==allocationNumber) + { + userReplicaList[index]->DeserializeConstructionRequestRejected(&bsIn,connection); + break; + } + } + +} + +// -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + +void ReplicaManager3::OnLocalConstructionAccepted(unsigned char *packetData, int packetDataLength, RakNetGUID senderGuid, unsigned char packetDataOffset) +{ + Connection_RM3 *connection = GetConnectionByGUID(senderGuid); + if (connection==0) + return; + RakNet::BitStream bsIn(packetData,packetDataLength,false); + bsIn.IgnoreBytes(packetDataOffset); + uint32_t allocationNumber; + bsIn.Read(allocationNumber); + NetworkID assignedNetworkId; + bsIn.Read(assignedNetworkId); + DataStructures::DefaultIndexType index; + DataStructures::DefaultIndexType index2; + Replica3 *replica; + SerializeParameters sp; + sp.whenLastSerialized=0; + RakNet::BitStream emptyBs; + for (index=0; index < RM3_NUM_OUTPUT_BITSTREAM_CHANNELS; index++) + sp.lastSentBitstream[index]=&emptyBs; + RM3SerializationResult res; + for (index=0; index < userReplicaList.GetSize(); index++) + { + if (userReplicaList[index]->GetAllocationNumber()==allocationNumber) + { + replica=userReplicaList[index]; + index2=connection->constructedReplicaList.GetIndexOf(replica); + if (index2!=(DataStructures::DefaultIndexType)-1) + { + LastSerializationResult *lsr = connection->constructedReplicaList[index2]; + + replica->SetNetworkID(assignedNetworkId); + replica->DeserializeConstructionRequestAccepted(&bsIn,connection); + sp.destinationConnection=connection; + + // Immediately serialize + res = replica->Serialize(&sp); + if (res!=RM3SR_NEVER_SERIALIZE_FOR_THIS_CONNECTION && + res!=RM3SR_DO_NOT_SERIALIZE && + res!=RM3SR_SERIALIZED_UNIQUELY) + { + sp.destinationConnection=connection; + sp.messageTimestamp=0; + for (int i=0; i < RM3_NUM_OUTPUT_BITSTREAM_CHANNELS; i++) + sp.pro[i]=defaultSendParameters; + bool allIndices[RM3_NUM_OUTPUT_BITSTREAM_CHANNELS]; + for (int z=0; z < RM3_NUM_OUTPUT_BITSTREAM_CHANNELS; z++) + { + allIndices[z]=true; + } + if (connection->SendSerialize(replica, allIndices, sp.outputBitstream, sp.messageTimestamp, sp.pro, rakPeerInterface, worldId)==SSICR_SENT_DATA) + lsr->replica->whenLastSerialized=RakNet::GetTime(); + } + + // Start serialization queries + connection->queryToSerializeReplicaList.Push(lsr,lsr->replica,__FILE__,__LINE__); + + return; + } + } + } +} + +// -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + +Replica3* ReplicaManager3::GetReplicaByNetworkID(NetworkID networkId) +{ + DataStructures::DefaultIndexType i; + for (i=0; i < userReplicaList.GetSize(); i++) + { + if (userReplicaList[i]->GetNetworkID()==networkId) + return userReplicaList[i]; + } + return 0; +} + + +// -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + + +void ReplicaManager3::BroadcastDestructionList(DataStructures::Multilist &replicaList, SystemAddress exclusionAddress) +{ + RakNet::BitStream bsOut; + DataStructures::DefaultIndexType i,j; + + for (i=0; i < replicaList.GetSize(); i++) + { + + if (replicaList[i]->deletingSystemGUID==UNASSIGNED_RAKNET_GUID) + replicaList[i]->deletingSystemGUID=GetRakPeerInterface()->GetGuidFromSystemAddress(UNASSIGNED_SYSTEM_ADDRESS); + } + + for (j=0; j < connectionList.GetSize(); j++) + { + if (connectionList[j]->GetSystemAddress()==exclusionAddress) + continue; + + bsOut.Reset(); + bsOut.Write((MessageID)ID_REPLICA_MANAGER_CONSTRUCTION); + bsOut.Write(worldId); + DataStructures::DefaultIndexType cnt=0; + bsOut.Write(cnt); // No construction + cnt=replicaList.GetSize(); + bsOut.Write(cnt); + + for (i=0; i < replicaList.GetSize(); i++) + { + if (connectionList[j]->HasReplicaConstructed(replicaList[i])==false) + continue; + + NetworkID networkId; + networkId=replicaList[i]->GetNetworkID(); + bsOut.Write(networkId); + BitSize_t offsetStart, offsetEnd; + offsetStart=bsOut.GetWriteOffset(); + bsOut.Write(offsetStart); + bsOut.Write(replicaList[i]->deletingSystemGUID); + replicaList[i]->SerializeDestruction(&bsOut, connectionList[j]); + bsOut.AlignWriteToByteBoundary(); + offsetEnd=bsOut.GetWriteOffset(); + bsOut.SetWriteOffset(offsetStart); + bsOut.Write(offsetEnd); + bsOut.SetWriteOffset(offsetEnd); + } + + rakPeerInterface->Send(&bsOut,defaultSendParameters.priority,defaultSendParameters.reliability,defaultSendParameters.orderingChannel,connectionList[j]->GetSystemAddress(),false, defaultSendParameters.sendReceipt); + } +} + +// -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + + +void ReplicaManager3::BroadcastDestruction(Replica3 *replica, SystemAddress exclusionAddress) +{ + DataStructures::Multilist replicaList; + replicaList.Push(replica, __FILE__, __LINE__ ); + BroadcastDestructionList(replicaList,exclusionAddress); +} + +// -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + + +// -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + +Connection_RM3::Connection_RM3(SystemAddress _systemAddress, RakNetGUID _guid) +: systemAddress(_systemAddress), guid(_guid) +{ + isValidated=false; + isFirstConstruction=true; +} + +// -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + +Connection_RM3::~Connection_RM3() +{ + constructedReplicaList.ClearPointers(true,__FILE__,__LINE__); + queryToConstructReplicaList.ClearPointers(true,__FILE__,__LINE__); +} + +// -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + +void Connection_RM3::GetConstructedReplicas(DataStructures::Multilist &objectsTheyDoHave) +{ + objectsTheyDoHave.Clear(true,__FILE__,__LINE__); + for (DataStructures::DefaultIndexType idx=0; idx < constructedReplicaList.GetSize(); idx++) + { + objectsTheyDoHave.Push(constructedReplicaList[idx]->replica, __FILE__, __LINE__ ); + } + objectsTheyDoHave.TagSorted(); +} + +// -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + +bool Connection_RM3::HasReplicaConstructed(RakNet::Replica3 *replica) +{ + return constructedReplicaList.GetIndexOf(replica)!=(DataStructures::DefaultIndexType)-1; +} + +// -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +void Connection_RM3::SendSerializeHeader(RakNet::Replica3 *replica, RakNetTime timestamp, RakNet::BitStream *bs, unsigned char worldId) +{ + bs->Reset(); + + if (timestamp!=0) + { + bs->Write((MessageID)ID_TIMESTAMP); + bs->Write(timestamp); + } + bs->Write((MessageID)ID_REPLICA_MANAGER_SERIALIZE); + bs->Write(worldId); + bs->Write(replica->GetNetworkID()); +} +// -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +SendSerializeIfChangedResult Connection_RM3::SendSerialize(RakNet::Replica3 *replica, bool indicesToSend[RM3_NUM_OUTPUT_BITSTREAM_CHANNELS], RakNet::BitStream serializationData[RM3_NUM_OUTPUT_BITSTREAM_CHANNELS], RakNetTime timestamp, PRO sendParameters[RM3_NUM_OUTPUT_BITSTREAM_CHANNELS], RakPeerInterface *rakPeer, unsigned char worldId) +{ + bool channelHasData; + BitSize_t sum=0; + for (int z=0; z < RM3_NUM_OUTPUT_BITSTREAM_CHANNELS; z++) + { + if (indicesToSend[z]) + sum+=serializationData[z].GetNumberOfBitsUsed(); + } + if (sum==0) + return SSICR_DID_NOT_SEND_DATA; + + RakAssert(replica->GetNetworkID()!=UNASSIGNED_NETWORK_ID); + + RakNet::BitStream out; + BitSize_t bitsUsed; + + int channelIndex; + PRO lastPro=sendParameters[0]; + + for (channelIndex=0; channelIndex < RM3_NUM_OUTPUT_BITSTREAM_CHANNELS; channelIndex++) + { + if (channelIndex==0) + { + SendSerializeHeader(replica, timestamp, &out, worldId); + } + else if (lastPro!=sendParameters[channelIndex]) + { + // Write out remainder + for (int channelIndex2=channelIndex; channelIndex2 < RM3_NUM_OUTPUT_BITSTREAM_CHANNELS; channelIndex2++) + out.Write(false); + + // Send remainder + replica->OnSerializeTransmission(&out, systemAddress); + rakPeer->Send(&out,lastPro.priority,lastPro.reliability,lastPro.orderingChannel,systemAddress,false,lastPro.sendReceipt); + + // Restart stream + SendSerializeHeader(replica, timestamp, &out, worldId); + + for (int channelIndex2=0; channelIndex2 < channelIndex; channelIndex2++) + out.Write(false); + lastPro=sendParameters[channelIndex]; + } + + bitsUsed=serializationData[channelIndex].GetNumberOfBitsUsed(); + channelHasData = indicesToSend[channelIndex]==true && bitsUsed>0; + out.Write(channelHasData); + if (channelHasData) + { + out.WriteCompressed(bitsUsed); + out.AlignWriteToByteBoundary(); + out.Write(serializationData[channelIndex]); + // Crap, forgot this line, was a huge bug in that I'd only send to the first 3 systems + serializationData[channelIndex].ResetReadPointer(); + } + } + replica->OnSerializeTransmission(&out, systemAddress); + rakPeer->Send(&out,lastPro.priority,lastPro.reliability,lastPro.orderingChannel,systemAddress,false,lastPro.sendReceipt); + return SSICR_SENT_DATA; +} + +// -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + +SendSerializeIfChangedResult Connection_RM3::SendSerializeIfChanged(DataStructures::DefaultIndexType queryToSerializeIndex, SerializeParameters *sp, RakPeerInterface *rakPeer, unsigned char worldId, ReplicaManager3 *replicaManager) +{ + RakNet::Replica3 *replica = queryToSerializeReplicaList[queryToSerializeIndex]->replica; + + if (replica->GetNetworkID()==UNASSIGNED_NETWORK_ID) + return SSICR_DID_NOT_SEND_DATA; + + RM3QuerySerializationResult rm3qsr = replica->QuerySerialization(this); + if (rm3qsr==RM3QSR_NEVER_CALL_SERIALIZE) + { + // Never again for this connection and replica pair + OnNeverSerialize(queryToSerializeIndex, replicaManager); + return SSICR_NEVER_SERIALIZE; + } + + if (rm3qsr==RM3QSR_DO_NOT_CALL_SERIALIZE) + return SSICR_DID_NOT_SEND_DATA; + + if (replica->forceSendUntilNextUpdate) + { + for (int z=0; z < RM3_NUM_OUTPUT_BITSTREAM_CHANNELS; z++) + { + if (replica->lastSentSerialization.indicesToSend[z]) + sp->bitsWrittenSoFar+=replica->lastSentSerialization.bitStream[z].GetNumberOfBitsUsed(); + } + return SendSerialize(replica, replica->lastSentSerialization.indicesToSend, replica->lastSentSerialization.bitStream, sp->messageTimestamp, sp->pro, rakPeer, worldId); + } + + for (int i=0; i < RM3_NUM_OUTPUT_BITSTREAM_CHANNELS; i++) + { + sp->outputBitstream[i].Reset(); + if (queryToSerializeReplicaList[queryToSerializeIndex]->lastSerializationResultBS) + sp->lastSentBitstream[i]=&queryToSerializeReplicaList[queryToSerializeIndex]->lastSerializationResultBS->bitStream[i]; + else + sp->lastSentBitstream[i]=&replica->lastSentSerialization.bitStream[i]; + } + + RM3SerializationResult serializationResult = replica->Serialize(sp); + + if (serializationResult==RM3SR_NEVER_SERIALIZE_FOR_THIS_CONNECTION) + { + // Never again for this connection and replica pair + OnNeverSerialize(queryToSerializeIndex, replicaManager); + return SSICR_NEVER_SERIALIZE; + } + + if (serializationResult==RM3SR_DO_NOT_SERIALIZE) + { + // Don't serialize this tick only + return SSICR_DID_NOT_SEND_DATA; + } + + // This is necessary in case the user in the Serialize() function for some reason read the bitstream they also wrote + // WIthout this code, the Write calls to another bitstream would not write the entire bitstream + BitSize_t sum=0; + for (int z=0; z < RM3_NUM_OUTPUT_BITSTREAM_CHANNELS; z++) + { + sp->outputBitstream[z].ResetReadPointer(); + sum+=sp->outputBitstream[z].GetNumberOfBitsUsed(); + } + + if (sum==0) + { + // Don't serialize this tick only + return SSICR_DID_NOT_SEND_DATA; + } + + if (serializationResult==RM3SR_SERIALIZED_ALWAYS) + { + bool allIndices[RM3_NUM_OUTPUT_BITSTREAM_CHANNELS]; + for (int z=0; z < RM3_NUM_OUTPUT_BITSTREAM_CHANNELS; z++) + { + sp->bitsWrittenSoFar+=sp->outputBitstream[z].GetNumberOfBitsUsed(); + allIndices[z]=true; + + queryToSerializeReplicaList[queryToSerializeIndex]->AllocBS(); + queryToSerializeReplicaList[queryToSerializeIndex]->lastSerializationResultBS->bitStream[z].Reset(); + queryToSerializeReplicaList[queryToSerializeIndex]->lastSerializationResultBS->bitStream[z].Write(&sp->outputBitstream[z]); + sp->outputBitstream[z].ResetReadPointer(); + } + return SendSerialize(replica, allIndices, sp->outputBitstream, sp->messageTimestamp, sp->pro, rakPeer, worldId); + } + + if (serializationResult==RM3SR_SERIALIZED_ALWAYS_IDENTICALLY) + { + for (int z=0; z < RM3_NUM_OUTPUT_BITSTREAM_CHANNELS; z++) + { + replica->lastSentSerialization.indicesToSend[z]=sp->outputBitstream[z].GetNumberOfBitsUsed()>0; + sp->bitsWrittenSoFar+=sp->outputBitstream[z].GetNumberOfBitsUsed(); + replica->lastSentSerialization.bitStream[z].Reset(); + replica->lastSentSerialization.bitStream[z].Write(&sp->outputBitstream[z]); + sp->outputBitstream[z].ResetReadPointer(); + replica->forceSendUntilNextUpdate=true; + } + return SendSerialize(replica, replica->lastSentSerialization.indicesToSend, sp->outputBitstream, sp->messageTimestamp, sp->pro, rakPeer, worldId); + } + + bool indicesToSend[RM3_NUM_OUTPUT_BITSTREAM_CHANNELS]; + if (serializationResult==RM3SR_BROADCAST_IDENTICALLY || serializationResult==RM3SR_BROADCAST_IDENTICALLY_FORCE_SERIALIZATION) + { + for (int z=0; z < RM3_NUM_OUTPUT_BITSTREAM_CHANNELS; z++) + { + if (sp->outputBitstream[z].GetNumberOfBitsUsed() > 0 && + (serializationResult==RM3SR_BROADCAST_IDENTICALLY_FORCE_SERIALIZATION || + ((sp->outputBitstream[z].GetNumberOfBitsUsed()!=replica->lastSentSerialization.bitStream[z].GetNumberOfBitsUsed() || + memcmp(sp->outputBitstream[z].GetData(), replica->lastSentSerialization.bitStream[z].GetData(), sp->outputBitstream[z].GetNumberOfBytesUsed())!=0)))) + { + indicesToSend[z]=true; + replica->lastSentSerialization.indicesToSend[z]=true; + sp->bitsWrittenSoFar+=sp->outputBitstream[z].GetNumberOfBitsUsed(); + replica->lastSentSerialization.bitStream[z].Reset(); + replica->lastSentSerialization.bitStream[z].Write(&sp->outputBitstream[z]); + sp->outputBitstream[z].ResetReadPointer(); + replica->forceSendUntilNextUpdate=true; + } + else + { + indicesToSend[z]=false; + replica->lastSentSerialization.indicesToSend[z]=false; + } + } + } + else + { + queryToSerializeReplicaList[queryToSerializeIndex]->AllocBS(); + + // RM3SR_SERIALIZED_UNIQUELY + for (int z=0; z < RM3_NUM_OUTPUT_BITSTREAM_CHANNELS; z++) + { + if (sp->outputBitstream[z].GetNumberOfBitsUsed() > 0 && + (sp->outputBitstream[z].GetNumberOfBitsUsed()!=queryToSerializeReplicaList[queryToSerializeIndex]->lastSerializationResultBS->bitStream[z].GetNumberOfBitsUsed() || + memcmp(sp->outputBitstream[z].GetData(), queryToSerializeReplicaList[queryToSerializeIndex]->lastSerializationResultBS->bitStream[z].GetData(), sp->outputBitstream[z].GetNumberOfBytesUsed())!=0) + ) + { + indicesToSend[z]=true; + sp->bitsWrittenSoFar+=sp->outputBitstream[z].GetNumberOfBitsUsed(); + queryToSerializeReplicaList[queryToSerializeIndex]->lastSerializationResultBS->bitStream[z].Reset(); + queryToSerializeReplicaList[queryToSerializeIndex]->lastSerializationResultBS->bitStream[z].Write(&sp->outputBitstream[z]); + sp->outputBitstream[z].ResetReadPointer(); + } + else + { + indicesToSend[z]=false; + } + } + } + + + if (serializationResult==RM3SR_BROADCAST_IDENTICALLY || serializationResult==RM3SR_BROADCAST_IDENTICALLY_FORCE_SERIALIZATION) + replica->forceSendUntilNextUpdate=true; + + // Send out the data + return SendSerialize(replica, indicesToSend, sp->outputBitstream, sp->messageTimestamp, sp->pro, rakPeer, worldId); +} + +// -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +void Connection_RM3::OnLocalReference(Replica3* replica3, ReplicaManager3 *replicaManager) +{ + ConstructionMode constructionMode = QueryConstructionMode(); + RakAssert(constructionMode==QUERY_REPLICA_FOR_CONSTRUCTION || constructionMode==QUERY_REPLICA_FOR_CONSTRUCTION_AND_DESTRUCTION) + (void) replicaManager; + + LastSerializationResult* lsr=RakNet::OP_NEW(__FILE__,__LINE__); + lsr->replica=replica3; + queryToConstructReplicaList.Push(lsr,replica3,__FILE__,__LINE__); +} + +// -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + +void Connection_RM3::OnDereference(Replica3* replica3, ReplicaManager3 *replicaManager) +{ + ValidateLists(replicaManager); + + LastSerializationResult* lsr=0; + DataStructures::DefaultIndexType idx; + idx=constructedReplicaList.GetIndexOf(replica3); + if (idx!=(DataStructures::DefaultIndexType)-1) + { + lsr=constructedReplicaList[idx]; + constructedReplicaList.RemoveAtIndex(idx,__FILE__,__LINE__); + } + + for (idx=0; idx < queryToConstructReplicaList.GetSize(); idx++) + { + if (queryToConstructReplicaList[idx]->replica==replica3) + { + lsr=queryToConstructReplicaList[idx]; + queryToConstructReplicaList.RemoveAtIndex(idx,__FILE__,__LINE__); + break; + } + } + + for (idx=0; idx < queryToSerializeReplicaList.GetSize(); idx++) + { + if (queryToSerializeReplicaList[idx]->replica==replica3) + { + lsr=queryToSerializeReplicaList[idx]; + queryToSerializeReplicaList.RemoveAtIndex(idx,__FILE__,__LINE__); + break; + } + } + + for (idx=0; idx < queryToDestructReplicaList.GetSize(); idx++) + { + if (queryToDestructReplicaList[idx]->replica==replica3) + { + lsr=queryToDestructReplicaList[idx]; + queryToDestructReplicaList.RemoveAtIndex(idx,__FILE__,__LINE__); + break; + } + } + + ValidateLists(replicaManager); + + if (lsr) + RakNet::OP_DELETE(lsr,__FILE__,__LINE__); + + ValidateLists(replicaManager); +} + +// -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + +void Connection_RM3::OnDownloadFromThisSystem(Replica3* replica3, ReplicaManager3 *replicaManager) +{ + ValidateLists(replicaManager); + LastSerializationResult* lsr=RakNet::OP_NEW(__FILE__,__LINE__); + lsr->replica=replica3; + + ConstructionMode constructionMode = QueryConstructionMode(); + if (constructionMode==QUERY_REPLICA_FOR_CONSTRUCTION || constructionMode==QUERY_REPLICA_FOR_CONSTRUCTION_AND_DESTRUCTION) + { + queryToConstructReplicaList.RemoveAtKey(replica3,false,__FILE__,__LINE__); + queryToDestructReplicaList.Push(lsr,replica3,__FILE__,__LINE__); + } + + constructedReplicaList.Push(lsr,replica3,__FILE__,__LINE__); + //assert(queryToSerializeReplicaList.GetIndexOf(replica3)==(DataStructures::DefaultIndexType)-1); + queryToSerializeReplicaList.Push(lsr,replica3,__FILE__,__LINE__); + + ValidateLists(replicaManager); +} + +// -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + +void Connection_RM3::OnDownloadFromOtherSystem(Replica3* replica3, ReplicaManager3 *replicaManager) +{ + ConstructionMode constructionMode = QueryConstructionMode(); + if (constructionMode==QUERY_REPLICA_FOR_CONSTRUCTION || constructionMode==QUERY_REPLICA_FOR_CONSTRUCTION_AND_DESTRUCTION) + { + if (queryToConstructReplicaList.GetIndexOf(replica3)==(DataStructures::DefaultIndexType)-1) + OnLocalReference(replica3, replicaManager); + } +} + +// -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + +void Connection_RM3::OnNeverConstruct(DataStructures::DefaultIndexType queryToConstructIdx, ReplicaManager3 *replicaManager) +{ + ConstructionMode constructionMode = QueryConstructionMode(); + RakAssert(constructionMode==QUERY_REPLICA_FOR_CONSTRUCTION || constructionMode==QUERY_REPLICA_FOR_CONSTRUCTION_AND_DESTRUCTION) + + ValidateLists(replicaManager); + LastSerializationResult* lsr = queryToConstructReplicaList[queryToConstructIdx]; + queryToConstructReplicaList.RemoveAtIndex(queryToConstructIdx,__FILE__,__LINE__); + RakNet::OP_DELETE(lsr,__FILE__,__LINE__); + ValidateLists(replicaManager); +} + +// -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + +void Connection_RM3::OnConstructToThisConnection(DataStructures::DefaultIndexType queryToConstructIdx, ReplicaManager3 *replicaManager) +{ + ConstructionMode constructionMode = QueryConstructionMode(); + RakAssert(constructionMode==QUERY_REPLICA_FOR_CONSTRUCTION || constructionMode==QUERY_REPLICA_FOR_CONSTRUCTION_AND_DESTRUCTION) + + ValidateLists(replicaManager); + LastSerializationResult* lsr = queryToConstructReplicaList[queryToConstructIdx]; + queryToConstructReplicaList.RemoveAtIndex(queryToConstructIdx,__FILE__,__LINE__); + //assert(constructedReplicaList.GetIndexOf(lsr->replica)==(DataStructures::DefaultIndexType)-1); + constructedReplicaList.Push(lsr,lsr->replica,__FILE__,__LINE__); + //assert(queryToDestructReplicaList.GetIndexOf(lsr->replica)==(DataStructures::DefaultIndexType)-1); + queryToDestructReplicaList.Push(lsr,lsr->replica,__FILE__,__LINE__); + //assert(queryToSerializeReplicaList.GetIndexOf(lsr->replica)==(DataStructures::DefaultIndexType)-1); + if (lsr->replica->GetNetworkID()!=UNASSIGNED_NETWORK_ID) + queryToSerializeReplicaList.Push(lsr,lsr->replica,__FILE__,__LINE__); + ValidateLists(replicaManager); +} + +// -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + +void Connection_RM3::OnConstructToThisConnection(Replica3 *replica, ReplicaManager3 *replicaManager) +{ + RakAssert(QueryConstructionMode()==QUERY_CONNECTION_FOR_REPLICA_LIST); + (void) replicaManager; + + LastSerializationResult* lsr=RakNet::OP_NEW(__FILE__,__LINE__); + lsr->replica=replica; + constructedReplicaList.Push(lsr,replica,__FILE__,__LINE__); + if (lsr->replica->GetNetworkID()!=UNASSIGNED_NETWORK_ID) + queryToSerializeReplicaList.Push(lsr,lsr->replica,__FILE__,__LINE__); +} + +// -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + +void Connection_RM3::OnNeverSerialize(DataStructures::DefaultIndexType queryToSerializeIndex, ReplicaManager3 *replicaManager) +{ + ValidateLists(replicaManager); + queryToSerializeReplicaList.RemoveAtIndex(queryToSerializeIndex,__FILE__,__LINE__); + ValidateLists(replicaManager); +} + +// -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + +void Connection_RM3::OnReplicaAlreadyExists(DataStructures::DefaultIndexType queryToConstructIdx, ReplicaManager3 *replicaManager) +{ + ConstructionMode constructionMode = QueryConstructionMode(); + RakAssert(constructionMode==QUERY_REPLICA_FOR_CONSTRUCTION || constructionMode==QUERY_REPLICA_FOR_CONSTRUCTION_AND_DESTRUCTION) + + ValidateLists(replicaManager); + LastSerializationResult* lsr = queryToConstructReplicaList[queryToConstructIdx]; + queryToConstructReplicaList.RemoveAtIndex(queryToConstructIdx,__FILE__,__LINE__); + //assert(constructedReplicaList.GetIndexOf(lsr->replica)==(DataStructures::DefaultIndexType)-1); + constructedReplicaList.Push(lsr,lsr->replica,__FILE__,__LINE__); + //assert(queryToDestructReplicaList.GetIndexOf(lsr->replica)==(DataStructures::DefaultIndexType)-1); + queryToDestructReplicaList.Push(lsr,lsr->replica,__FILE__,__LINE__); + //assert(queryToSerializeReplicaList.GetIndexOf(lsr->replica)==(DataStructures::DefaultIndexType)-1); + queryToSerializeReplicaList.Push(lsr,lsr->replica,__FILE__,__LINE__); + ValidateLists(replicaManager); +} + +// -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + +void Connection_RM3::OnDownloadExisting(Replica3* replica3, ReplicaManager3 *replicaManager) +{ + ValidateLists(replicaManager); + + ConstructionMode constructionMode = QueryConstructionMode(); + if (constructionMode==QUERY_REPLICA_FOR_CONSTRUCTION || constructionMode==QUERY_REPLICA_FOR_CONSTRUCTION_AND_DESTRUCTION) + { + DataStructures::DefaultIndexType idx; + for (idx=0; idx < queryToConstructReplicaList.GetSize(); idx++) + { + if (queryToConstructReplicaList[idx]->replica==replica3) + { + OnConstructToThisConnection(idx, replicaManager); + return; + } + } + } + else + { + OnConstructToThisConnection(replica3, replicaManager); + } +} +// -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + +void Connection_RM3::OnSendDestructionFromQuery(DataStructures::DefaultIndexType queryToDestructIdx, ReplicaManager3 *replicaManager) +{ + ConstructionMode constructionMode = QueryConstructionMode(); + RakAssert(constructionMode==QUERY_REPLICA_FOR_CONSTRUCTION || constructionMode==QUERY_REPLICA_FOR_CONSTRUCTION_AND_DESTRUCTION) + + ValidateLists(replicaManager); + LastSerializationResult* lsr = queryToDestructReplicaList[queryToDestructIdx]; + queryToDestructReplicaList.RemoveAtIndex(queryToDestructIdx,__FILE__,__LINE__); + queryToSerializeReplicaList.RemoveAtKey(lsr->replica,false,__FILE__,__LINE__); + constructedReplicaList.RemoveAtKey(lsr->replica,true,__FILE__,__LINE__); + //assert(queryToConstructReplicaList.GetIndexOf(lsr->replica)==(DataStructures::DefaultIndexType)-1); + queryToConstructReplicaList.Push(lsr,lsr->replica,__FILE__,__LINE__); + ValidateLists(replicaManager); +} + +// -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + +void Connection_RM3::OnDoNotQueryDestruction(DataStructures::DefaultIndexType queryToDestructIdx, ReplicaManager3 *replicaManager) +{ + ValidateLists(replicaManager); + queryToDestructReplicaList.RemoveAtIndex(queryToDestructIdx,__FILE__,__LINE__); + ValidateLists(replicaManager); +} + +// -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + +void Connection_RM3::ValidateLists(ReplicaManager3 *replicaManager) const +{ + (void) replicaManager; + /* +#ifdef _DEBUG + // Each object should exist only once in either constructedReplicaList or queryToConstructReplicaList + // replicaPointer from LastSerializationResult should be same among all lists + DataStructures::DefaultIndexType idx, idx2; + for (idx=0; idx < constructedReplicaList.GetSize(); idx++) + { + idx2=queryToConstructReplicaList.GetIndexOf(constructedReplicaList[idx]->replica); + if (idx2!=(DataStructures::DefaultIndexType)-1) + { + int a=5; + assert(a==0); + int *b=0; + *b=5; + } + } + + for (idx=0; idx < queryToConstructReplicaList.GetSize(); idx++) + { + idx2=constructedReplicaList.GetIndexOf(queryToConstructReplicaList[idx]->replica); + if (idx2!=(DataStructures::DefaultIndexType)-1) + { + int a=5; + assert(a==0); + int *b=0; + *b=5; + } + } + + LastSerializationResult *lsr, *lsr2; + for (idx=0; idx < constructedReplicaList.GetSize(); idx++) + { + lsr=constructedReplicaList[idx]; + + idx2=queryToSerializeReplicaList.GetIndexOf(lsr->replica); + if (idx2!=(DataStructures::DefaultIndexType)-1) + { + lsr2=queryToSerializeReplicaList[idx2]; + if (lsr2!=lsr) + { + int a=5; + assert(a==0); + int *b=0; + *b=5; + } + } + + idx2=queryToDestructReplicaList.GetIndexOf(lsr->replica); + if (idx2!=(DataStructures::DefaultIndexType)-1) + { + lsr2=queryToDestructReplicaList[idx2]; + if (lsr2!=lsr) + { + int a=5; + assert(a==0); + int *b=0; + *b=5; + } + } + } + for (idx=0; idx < queryToConstructReplicaList.GetSize(); idx++) + { + lsr=queryToConstructReplicaList[idx]; + + idx2=queryToSerializeReplicaList.GetIndexOf(lsr->replica); + if (idx2!=(DataStructures::DefaultIndexType)-1) + { + lsr2=queryToSerializeReplicaList[idx2]; + if (lsr2!=lsr) + { + int a=5; + assert(a==0); + int *b=0; + *b=5; + } + } + + idx2=queryToDestructReplicaList.GetIndexOf(lsr->replica); + if (idx2!=(DataStructures::DefaultIndexType)-1) + { + lsr2=queryToDestructReplicaList[idx2]; + if (lsr2!=lsr) + { + int a=5; + assert(a==0); + int *b=0; + *b=5; + } + } + } + + // Verify pointer integrity + for (idx=0; idx < constructedReplicaList.GetSize(); idx++) + { + if (constructedReplicaList[idx]->replica->replicaManager!=replicaManager) + { + int a=5; + assert(a==0); + int *b=0; + *b=5; + } + } + + // Verify pointer integrity + for (idx=0; idx < queryToConstructReplicaList.GetSize(); idx++) + { + if (queryToConstructReplicaList[idx]->replica->replicaManager!=replicaManager) + { + int a=5; + assert(a==0); + int *b=0; + *b=5; + } + } +#endif + */ +} +// -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + +void Connection_RM3::SendConstruction(DataStructures::Multilist &newObjects, DataStructures::Multilist &deletedObjects, PRO sendParameters, RakPeerInterface *rakPeer, unsigned char worldId) +{ + if (newObjects.GetSize()==0 && deletedObjects.GetSize()==0) + return; + + // All construction and destruction takes place in the same network message + // Otherwise, if objects rely on each other being created the same tick to be valid, this won't always be true + // DataStructures::Multilist serializedObjects; + BitSize_t offsetStart, offsetEnd; + DataStructures::DefaultIndexType newListIndex, oldListIndex; + RakNet::BitStream bsOut; + NetworkID networkId; + if (isFirstConstruction) + { + bsOut.Write((MessageID)ID_REPLICA_MANAGER_DOWNLOAD_STARTED); + bsOut.Write(worldId); + SerializeOnDownloadStarted(&bsOut); + rakPeer->Send(&bsOut,sendParameters.priority,sendParameters.reliability,sendParameters.orderingChannel,systemAddress,false,sendParameters.sendReceipt); + } + + + // LastSerializationResult* lsr; + bsOut.Reset(); + bsOut.Write((MessageID)ID_REPLICA_MANAGER_CONSTRUCTION); + bsOut.Write(worldId); + bsOut.Write(newObjects.GetSize()); + // Construction + for (newListIndex=0; newListIndex < newObjects.GetSize(); newListIndex++) + { + offsetStart=bsOut.GetWriteOffset(); + bsOut.Write(offsetStart); + networkId=newObjects[newListIndex]->GetNetworkID(); + bsOut.Write(networkId); + newObjects[newListIndex]->WriteAllocationID(&bsOut); + if (networkId==UNASSIGNED_NETWORK_ID) + bsOut.Write(newObjects[newListIndex]->GetAllocationNumber()); + bsOut.Write(newObjects[newListIndex]->creatingSystemGUID); + newObjects[newListIndex]->SerializeConstruction(&bsOut, this); + bsOut.AlignWriteToByteBoundary(); + offsetEnd=bsOut.GetWriteOffset(); + bsOut.SetWriteOffset(offsetStart); + bsOut.Write(offsetEnd); + bsOut.SetWriteOffset(offsetEnd); + // lsr = Reference(newObjects[newListIndex],false); + // serializedObjects.Push(newObjects[newListIndex]); + } + + // Destruction + DataStructures::DefaultIndexType listSize=deletedObjects.GetSize(); + bsOut.Write(listSize); + for (oldListIndex=0; oldListIndex < deletedObjects.GetSize(); oldListIndex++) + { + networkId=deletedObjects[oldListIndex]->GetNetworkID(); + bsOut.Write(networkId); + offsetStart=bsOut.GetWriteOffset(); + bsOut.Write(offsetStart); + deletedObjects[oldListIndex]->deletingSystemGUID=rakPeer->GetGuidFromSystemAddress(UNASSIGNED_SYSTEM_ADDRESS); + bsOut.Write(deletedObjects[oldListIndex]->deletingSystemGUID); + deletedObjects[oldListIndex]->SerializeDestruction(&bsOut, this); + bsOut.AlignWriteToByteBoundary(); + offsetEnd=bsOut.GetWriteOffset(); + bsOut.SetWriteOffset(offsetStart); + bsOut.Write(offsetEnd); + bsOut.SetWriteOffset(offsetEnd); + } + rakPeer->Send(&bsOut,sendParameters.priority,sendParameters.reliability,sendParameters.orderingChannel,systemAddress,false,sendParameters.sendReceipt); + + // Initial Download serialize to a new system + // Immediately send serialize after construction if the replica object already has saved data + // If the object was serialized identically, and does not change later on, then the new connection never gets the data + SerializeParameters sp; + sp.whenLastSerialized=0; + RakNet::BitStream emptyBs; + for (int index=0; index < RM3_NUM_OUTPUT_BITSTREAM_CHANNELS; index++) + { + sp.lastSentBitstream[index]=&emptyBs; + sp.pro[index]=sendParameters; + } + + sp.bitsWrittenSoFar=0; + RakNetTime t = RakNet::GetTime(); + for (newListIndex=0; newListIndex < newObjects.GetSize(); newListIndex++) + { + sp.destinationConnection=this; + sp.messageTimestamp=0; + RakNet::Replica3 *replica = newObjects[newListIndex]; + // 8/22/09 Forgot ResetWritePointer + for (int z=0; z < RM3_NUM_OUTPUT_BITSTREAM_CHANNELS; z++) + { + sp.outputBitstream[z].ResetWritePointer(); + } + + RM3SerializationResult res = replica->Serialize(&sp); + if (replica->GetNetworkID()!=UNASSIGNED_NETWORK_ID) + { + if (res!=RM3SR_NEVER_SERIALIZE_FOR_THIS_CONNECTION && + res!=RM3SR_DO_NOT_SERIALIZE && + res!=RM3SR_SERIALIZED_UNIQUELY) + { + bool allIndices[RM3_NUM_OUTPUT_BITSTREAM_CHANNELS]; + for (int z=0; z < RM3_NUM_OUTPUT_BITSTREAM_CHANNELS; z++) + { + sp.bitsWrittenSoFar+=sp.outputBitstream[z].GetNumberOfBitsUsed(); + allIndices[z]=true; + } + SendSerialize(replica, allIndices, sp.outputBitstream, sp.messageTimestamp, sp.pro, rakPeer, worldId); + newObjects[newListIndex]->whenLastSerialized=t; + + } + } + // else wait for construction request accepted before serializing + } + + if (isFirstConstruction) + { + bsOut.Reset(); + bsOut.Write((MessageID)ID_REPLICA_MANAGER_DOWNLOAD_COMPLETE); + bsOut.Write(worldId); + SerializeOnDownloadComplete(&bsOut); + rakPeer->Send(&bsOut,sendParameters.priority,sendParameters.reliability,sendParameters.orderingChannel,systemAddress,false,sendParameters.sendReceipt); + } + + isFirstConstruction=false; + +} + +// -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + +void Connection_RM3::SendValidation(RakPeerInterface *rakPeer, unsigned char worldId) +{ + // Hijack to mean sendValidation + RakNet::BitStream bsOut; + bsOut.Write((MessageID)ID_REPLICA_MANAGER_SCOPE_CHANGE); + bsOut.Write(worldId); + rakPeer->Send(&bsOut,HIGH_PRIORITY,RELIABLE_ORDERED,0,systemAddress,false); +} + +// -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + +Replica3::Replica3() +{ + creatingSystemGUID=UNASSIGNED_RAKNET_GUID; + deletingSystemGUID=UNASSIGNED_RAKNET_GUID; + replicaManager=0; + forceSendUntilNextUpdate=false; + whenLastSerialized=0; +} + +// -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + +Replica3::~Replica3() +{ + if (replicaManager) + { + replicaManager->Dereference(this); + } +} + +// -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + +void Replica3::BroadcastDestruction(void) +{ + if (replicaManager) + replicaManager->BroadcastDestruction(this,UNASSIGNED_SYSTEM_ADDRESS); +} + +// -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + +RakNetGUID Replica3::GetCreatingSystemGUID(void) const +{ + return creatingSystemGUID; +} + +// -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + +RM3ConstructionState Replica3::QueryConstruction_ClientConstruction(RakNet::Connection_RM3 *destinationConnection) +{ + (void) destinationConnection; + if (creatingSystemGUID==replicaManager->GetRakPeerInterface()->GetGuidFromSystemAddress(UNASSIGNED_SYSTEM_ADDRESS)) + return RM3CS_SEND_CONSTRUCTION; + // Send back to the owner client too, because they couldn't assign the network ID + if (networkIDManager->IsNetworkIDAuthority()) + return RM3CS_SEND_CONSTRUCTION; + return RM3CS_NEVER_CONSTRUCT; +} + +// -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + +bool Replica3::QueryRemoteConstruction_ClientConstruction(RakNet::Connection_RM3 *sourceConnection) +{ + (void) sourceConnection; + + // OK to create + return true; +} + +// -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + +RM3ConstructionState Replica3::QueryConstruction_ServerConstruction(RakNet::Connection_RM3 *destinationConnection) +{ + (void) destinationConnection; + + if (networkIDManager->IsNetworkIDAuthority()) + return RM3CS_SEND_CONSTRUCTION; + return RM3CS_NEVER_CONSTRUCT; +} + +// -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + +bool Replica3::QueryRemoteConstruction_ServerConstruction(RakNet::Connection_RM3 *sourceConnection) +{ + (void) sourceConnection; + if (networkIDManager->IsNetworkIDAuthority()) + return false; + return true; +} + +// -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + +RM3ConstructionState Replica3::QueryConstruction_PeerToPeer(RakNet::Connection_RM3 *destinationConnection) +{ + (void) destinationConnection; + + // We send to all, others do nothing + if (creatingSystemGUID==replicaManager->GetRakPeerInterface()->GetGuidFromSystemAddress(UNASSIGNED_SYSTEM_ADDRESS)) + return RM3CS_SEND_CONSTRUCTION; + return RM3CS_NEVER_CONSTRUCT; +} + +// -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + +bool Replica3::QueryRemoteConstruction_PeerToPeer(RakNet::Connection_RM3 *sourceConnection) +{ + (void) sourceConnection; + + return true; +} + +// -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + +RM3QuerySerializationResult Replica3::QuerySerialization_ClientSerializable(RakNet::Connection_RM3 *destinationConnection) +{ + // Owner client sends to all + if (creatingSystemGUID==replicaManager->GetRakPeerInterface()->GetGuidFromSystemAddress(UNASSIGNED_SYSTEM_ADDRESS)) + return RM3QSR_CALL_SERIALIZE; + // Server sends to all but owner client + if (networkIDManager->IsNetworkIDAuthority() && destinationConnection->GetRakNetGUID()!=creatingSystemGUID) + return RM3QSR_CALL_SERIALIZE; + // Remote clients do not send + return RM3QSR_NEVER_CALL_SERIALIZE; +} + +// -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + +RM3QuerySerializationResult Replica3::QuerySerialization_ServerSerializable(RakNet::Connection_RM3 *destinationConnection) +{ + (void) destinationConnection; + // Server sends to all + if (networkIDManager->IsNetworkIDAuthority()) + return RM3QSR_CALL_SERIALIZE; + + // Clients do not send + return RM3QSR_NEVER_CALL_SERIALIZE; +} + +// -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + +RM3QuerySerializationResult Replica3::QuerySerialization_PeerToPeer(RakNet::Connection_RM3 *destinationConnection) +{ + (void) destinationConnection; + + // Owner peer sends to all + if (creatingSystemGUID==replicaManager->GetRakPeerInterface()->GetGuidFromSystemAddress(UNASSIGNED_SYSTEM_ADDRESS)) + return RM3QSR_CALL_SERIALIZE; + + // Remote peers do not send + return RM3QSR_NEVER_CALL_SERIALIZE; +} + +// -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + +RM3ActionOnPopConnection Replica3::QueryActionOnPopConnection_Client(RakNet::Connection_RM3 *droppedConnection) const +{ + (void) droppedConnection; + return RM3AOPC_DELETE_REPLICA; +} + +// -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + +RM3ActionOnPopConnection Replica3::QueryActionOnPopConnection_Server(RakNet::Connection_RM3 *droppedConnection) const +{ + (void) droppedConnection; + return RM3AOPC_DELETE_REPLICA_AND_BROADCAST_DESTRUCTION; +} + +// -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + +RM3ActionOnPopConnection Replica3::QueryActionOnPopConnection_PeerToPeer(RakNet::Connection_RM3 *droppedConnection) const +{ + (void) droppedConnection; + return RM3AOPC_DELETE_REPLICA; +} + +// -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + +#endif // _RAKNET_SUPPORT_* diff --git a/RakNet/Sources/ReplicaManager3.h b/RakNet/Sources/ReplicaManager3.h new file mode 100644 index 0000000..2d98185 --- /dev/null +++ b/RakNet/Sources/ReplicaManager3.h @@ -0,0 +1,920 @@ +/// \file +/// \brief Contains the third iteration of the ReplicaManager class. +/// +/// This file is part of RakNet Copyright 2003 Jenkins Software LLC +/// +/// Usage of RakNet is subject to the appropriate license agreement. + +#include "NativeFeatureIncludes.h" +#if _RAKNET_SUPPORT_ReplicaManager3==1 + +#ifndef __REPLICA_MANAGER_3 +#define __REPLICA_MANAGER_3 + +#include "DS_Multilist.h" +#include "RakNetTypes.h" +#include "RakNetTime.h" +#include "BitStream.h" +#include "PacketPriority.h" +#include "PluginInterface2.h" +#include "NetworkIDObject.h" + +/// \defgroup REPLICA_MANAGER_GROUP3 ReplicaManager3 +/// \brief Third implementation of object replication +/// \details +/// \ingroup REPLICA_MANAGER_GROUP + +namespace RakNet +{ +class Connection_RM3; +class Replica3; + + +/// \internal +/// \ingroup REPLICA_MANAGER_GROUP3 +struct PRO +{ + /// Passed to RakPeerInterface::Send(). Defaults to ReplicaManager3::SetDefaultPacketPriority(). + PacketPriority priority; + + /// Passed to RakPeerInterface::Send(). Defaults to ReplicaManager3::SetDefaultPacketReliability(). + PacketReliability reliability; + + /// Passed to RakPeerInterface::Send(). Defaults to ReplicaManager3::SetDefaultOrderingChannel(). + char orderingChannel; + + /// Passed to RakPeerInterface::Send(). Defaults to 0. + uint32_t sendReceipt; + + bool operator==( const PRO& right ) const; + bool operator!=( const PRO& right ) const; +}; + + +/// \brief System to help automate game object construction, destruction, and serialization +/// \details ReplicaManager3 tracks your game objects and automates the networking for replicating them across the network
    +/// As objects are created, destroyed, or serialized differently, those changes are pushed out to other systems.
    +/// To use:
    +///
      +///
    1. Derive from Connection_RM3 and implement Connection_RM3::AllocReplica(). This is a factory function where given a user-supplied identifier for a class (such as name) return an instance of that class. Should be able to return any networked object in your game. +///
    2. Derive from ReplicaManager3 and implement AllocConnection() and DeallocConnection() to return the class you created in step 1. +///
    3. Derive your networked game objects from Replica3. All pure virtuals have to be implemented, however defaults are provided for Replica3::QueryConstruction(), Replica3::QueryRemoteConstruction(), and Replica3::QuerySerialization() depending on your network architecture. +///
    4. When a new game object is created on the local system, pass it to ReplicaManager3::Reference(). +///
    5. When a game object is destroyed on the local system, and you want other systems to know about it, call Replica3::BroadcastDestruction() +///
    +///
    +/// At this point, all new connections will automatically download, get construction messages, get destruction messages, and update serialization automatically. +/// \ingroup REPLICA_MANAGER_GROUP3 +class RAK_DLL_EXPORT ReplicaManager3 : public PluginInterface2 +{ +public: + ReplicaManager3(); + virtual ~ReplicaManager3(); + + /// \brief Implement to return a game specific derivation of Connection_RM3 + /// \details The connection object represents a remote system connected to you that is using the ReplicaManager3 system.
    + /// It has functions to perform operations per-connection.
    + /// AllocConnection() and DeallocConnection() are factory functions to create and destroy instances of the connection object.
    + /// It is used if autoCreate is true via SetAutoManageConnections() (true by default). Otherwise, the function is not called, and you will have to call PushConnection() manually
    + /// \note If you do not want a new network connection to immediately download game objects, SetAutoManageConnections() and PushConnection() are how you do this. + /// \sa SetAutoManageConnections() + /// \param[in] systemAddress Address of the system you are adding + /// \param[in] rakNetGUID GUID of the system you are adding. See Packet::rakNetGUID or RakPeerInterface::GetGUIDFromSystemAddress() + /// \return The new connection instance. + virtual Connection_RM3* AllocConnection(SystemAddress systemAddress, RakNetGUID rakNetGUID) const=0; + + /// \brief Implement to destroy a class instanced returned by AllocConnection() + /// \details Most likely just implement as {delete connection;}
    + /// It is used if autoDestroy is true via SetAutoManageConnections() (true by default). Otherwise, the function is not called and you would then be responsible for deleting your own connection objects. + /// \param[in] connection The pointer instance to delete + virtual void DeallocConnection(Connection_RM3 *connection) const=0; + + /// \brief Enable or disable automatically assigning connections to new instances of Connection_RM3 + /// \details ReplicaManager3 can automatically create and/or destroy Connection_RM3 as systems connect or disconnect from RakPeerInterface.
    + /// By default this is on, to make the system easier to learn and setup.
    + /// If you don't want all connections to take part in the game, or you want to delay when a connection downloads the game, set \a autoCreate to false.
    + /// If you want to delay deleting a connection that has dropped, set \a autoDestroy to false. If you do this, then you must call PopConnection() to remove that connection from being internally tracked. You'll also have to delete the connection instance on your own.
    + /// \param[in] autoCreate Automatically call ReplicaManager3::AllocConnection() for each new connection. Defaults to true. + /// \param[in] autoDestroy Automatically call ReplicaManager3::DeallocConnection() for each dropped connection. Defaults to true. + void SetAutoManageConnections(bool autoCreate, bool autoDestroy); + + /// \brief Track a new Connection_RM3 instance + /// \details If \a autoCreate is false for SetAutoManageConnections(), then you need this function to add new instances of Connection_RM3 yourself.
    + /// You don't need to track this pointer yourself, you can get it with GetConnectionAtIndex(), GetConnectionByGUID(), or GetConnectionBySystemAddress().
    + /// \param[in] newConnection The new connection instance to track. + bool PushConnection(RakNet::Connection_RM3 *newConnection); + + /// \brief Stop tracking a connection + /// \details On call, for each replica returned by GetReplicasCreatedByGuid(), QueryActionOnPopConnection() will be called. Depending on the return value, this may delete the corresponding replica.
    + /// If autoDestroy is true in the call to SetAutoManageConnections() (true by default) then this is called automatically when the connection is lost. In that case, the returned connection instance is deleted.
    + /// \param[in] guid of the connection to get. Passed to ReplicaManager3::AllocConnection() originally. + RakNet::Connection_RM3 * PopConnection(RakNetGUID guid); + + /// \brief Adds a replicated object to the system. + /// \details Anytime you create a new object that derives from Replica3, and you want ReplicaManager3 to use it, pass it to Reference().
    + /// Remote systems already connected will potentially download this object the next time ReplicaManager3::Update() is called, which happens every time you call RakPeerInterface::Receive().
    + /// \param[in] replica3 The object to start tracking + void Reference(RakNet::Replica3 *replica3); + + /// \brief Removes a replicated object from the system. + /// \details The object is not deallocated, it is up to the caller to do so.
    + /// This is called automatically from the destructor of Replica3, so you don't need to call it manually unless you want to stop tracking an object before it is destroyed. + /// \param[in] replica3 The object to stop tracking + void Dereference(RakNet::Replica3 *replica3); + + /// \brief Removes multiple replicated objects from the system. + /// \details Same as Dereference(), but for a list of objects.
    + /// Useful with the lists returned by GetReplicasCreatedByGuid(), GetReplicasCreatedByMe(), or GetReferencedReplicaList().
    + /// \param[in] replicaListIn List of objects + void DereferenceList(DataStructures::Multilist &replicaListIn); + + /// \brief Returns all objects originally created by a particular system + /// \details Originally created is defined as the value of Replica3::creatingSystemGUID, which is automatically assigned in ReplicaManager3::Reference().
    + /// You do not have to be directly connected to that system to get the objects originally created by that system.
    + /// \param[in] guid GUID of the system we are referring to. Originally passed as the \a guid parameter to ReplicaManager3::AllocConnection() + /// \param[out] List of Replica3 instances to be returned + void GetReplicasCreatedByGuid(RakNetGUID guid, DataStructures::Multilist &replicaListOut); + + /// \brief Returns all objects originally created by your system + /// \details Calls GetReplicasCreatedByGuid() for your own system guid. + /// \param[out] List of Replica3 instances to be returned + void GetReplicasCreatedByMe(DataStructures::Multilist &replicaListOut); + + /// \brief Returns the entire list of Replicas that we know about. + /// \details This is all Replica3 instances passed to Reference, as well as instances we downloaded and created via Connection_RM3::AllocReference() + /// \param[out] List of Replica3 instances to be returned + void GetReferencedReplicaList(DataStructures::Multilist &replicaListOut); + + /// \brief Returns the number of replicas known about + /// \details Returns the size of the list that would be returned by GetReferencedReplicaList() + /// \return How many replica objects are in the list of replica objects + unsigned GetReplicaCount(void) const; + + /// \brief Returns a replica by index + /// \details Returns one of the items in the list that would be returned by GetReferencedReplicaList() + /// \param[in] index An index, from 0 to GetReplicaCount()-1. + /// \return A Replica3 instance + Replica3 *GetReplicaAtIndex(unsigned index); + + /// \brief Returns the number of connections + /// \details Returns the number of connections added with ReplicaManager3::PushConnection(), minus the number removed with ReplicaManager3::PopConnection() + /// \return The number of registered connections + DataStructures::DefaultIndexType GetConnectionCount(void) const; + + /// \brief Returns a connection pointer previously added with PushConnection() + /// \param[in] index An index, from 0 to GetConnectionCount()-1. + /// \return A Connection_RM3 pointer + Connection_RM3* GetConnectionAtIndex(unsigned index) const; + + /// \brief Returns a connection pointer previously added with PushConnection() + /// \param[in] sa The system address of the connection to return + /// \return A Connection_RM3 pointer, or 0 if not found + Connection_RM3* GetConnectionBySystemAddress(SystemAddress sa) const; + + /// \brief Returns a connection pointer previously added with PushConnection.() + /// \param[in] guid The guid of the connection to return + /// \return A Connection_RM3 pointer, or 0 if not found + Connection_RM3* GetConnectionByGUID(RakNetGUID guid) const; + + /// \param[in] Default ordering channel to use for object creation, destruction, and serializations + void SetDefaultOrderingChannel(char def); + + /// \param[in] Default packet priority to use for object creation, destruction, and serializations + void SetDefaultPacketPriority(PacketPriority def); + + /// \param[in] Default packet reliability to use for object creation, destruction, and serializations + void SetDefaultPacketReliability(PacketReliability def); + + /// \details Every \a intervalMS milliseconds, Connection_RM3::OnAutoserializeInterval() will be called.
    + /// Defaults to 30.
    + /// Pass with 0 to disable.
    + /// If you want to control the update interval with more granularity, use the return values from Replica3::Serialize().
    + /// \param[in] intervalMS How frequently to autoserialize all objects. This controls the maximum number of game object updates per second. + void SetAutoSerializeInterval(RakNetTime intervalMS); + + /// \brief Return the connections that we think have an instance of the specified Replica3 instance + /// \details This can be wrong, for example if that system locally deleted the outside the scope of ReplicaManager3, if QueryRemoteConstruction() returned false, or if DeserializeConstruction() returned false. + /// \param[in] replica The replica to check against. + /// \param[out] connectionsThatHaveConstructedThisReplica Populated with connection instances that we believe have \a replica allocated + void GetConnectionsThatHaveReplicaConstructed(Replica3 *replica, DataStructures::Multilist &connectionsThatHaveConstructedThisReplica); + + /// \brief Defines the unique instance of ReplicaManager3 if multiple instances are on the same instance of RakPeerInterface + /// \details ReplicaManager3 supports multiple instances of itself attached to the same instance of rakPeer, in case your game has multiple worlds.
    + /// Call SetWorldID with a different number for each instance.
    + /// The default worldID is 0.
    + /// To use multiple worlds, you will also need to call ReplicaManager3::SetNetworkIDManager() to have a different NetworkIDManager instance per world + void SetWorldID(unsigned char id); + + /// \return Whatever was passed to SetWorldID(), or 0 if it was never called. + unsigned char GetWorldID(void) const; + + /// \details Sets the networkIDManager instance that this plugin relys upon.
    + /// Uses whatever instance is attached to RakPeerInterface if unset.
    + /// To support multiple worlds, you should set it to a different manager for each instance of the plugin + /// \param[in] _networkIDManager The externally allocated NetworkIDManager instance for this plugin to use. + void SetNetworkIDManager(NetworkIDManager *_networkIDManager); + + /// Returns what was passed to SetNetworkIDManager(), or the instance on RakPeerInterface if unset. + NetworkIDManager *GetNetworkIDManager(void) const; + + /// \details Send a network command to destroy one or more Replica3 instances + /// Usually you won't need this, but use Replica3::BroadcastDestruction() instead. + /// The objects are unaffected locally + /// \param[in] replicaList List of Replica3 objects to tell other systems to destroy. + /// \param[in] exclusionAddress Which system to not send to. UNASSIGNED_SYSTEM_ADDRESS to send to all. + void BroadcastDestructionList(DataStructures::Multilist &replicaList, SystemAddress exclusionAddress); + + /// \internal + /// \details Tell other systems that have this replica to destroy this replica.
    + /// You shouldn't need to call this, as it happens in the Replica3 destructor + void BroadcastDestruction(Replica3 *replica, SystemAddress exclusionAddress); + + /// \internal + /// \details Frees internal lists.
    + /// Externally allocated pointers are not deallocated + void Clear(void); + + /// \internal + PRO GetDefaultSendParameters(void) const; + +protected: + virtual PluginReceiveResult OnReceive(Packet *packet); + virtual void Update(void); + virtual void OnClosedConnection(SystemAddress systemAddress, RakNetGUID rakNetGUID, PI2_LostConnectionReason lostConnectionReason ); + virtual void OnNewConnection(SystemAddress systemAddress, RakNetGUID rakNetGUID, bool isIncoming); + virtual void OnRakPeerShutdown(void); + + void OnConstructionExisting(unsigned char *packetData, int packetDataLength, RakNetGUID senderGuid, unsigned char packetDataOffset); + void OnConstruction(unsigned char *packetData, int packetDataLength, RakNetGUID senderGuid, unsigned char packetDataOffset); + void OnSerialize(unsigned char *packetData, int packetDataLength, RakNetGUID senderGuid, RakNetTime timestamp, unsigned char packetDataOffset); + void OnDownloadStarted(unsigned char *packetData, int packetDataLength, RakNetGUID senderGuid, unsigned char packetDataOffset); + void OnDownloadComplete(unsigned char *packetData, int packetDataLength, RakNetGUID senderGuid, unsigned char packetDataOffset); + void OnLocalConstructionRejected(unsigned char *packetData, int packetDataLength, RakNetGUID senderGuid, unsigned char packetDataOffset); + void OnLocalConstructionAccepted(unsigned char *packetData, int packetDataLength, RakNetGUID senderGuid, unsigned char packetDataOffset); + + RakNet::Connection_RM3 * PopConnection(DataStructures::DefaultIndexType index); + Replica3* GetReplicaByNetworkID(NetworkID networkId); + DataStructures::DefaultIndexType ReferenceInternal(RakNet::Replica3 *replica3); + + DataStructures::Multilist connectionList; + DataStructures::Multilist userReplicaList; + + PRO defaultSendParameters; + RakNetTime autoSerializeInterval; + RakNetTime lastAutoSerializeOccurance; + unsigned char worldId; + NetworkIDManager *networkIDManager; + bool autoCreateConnections, autoDestroyConnections; + + friend class Connection_RM3; +}; + +static const int RM3_NUM_OUTPUT_BITSTREAM_CHANNELS=8; + +/// \ingroup REPLICA_MANAGER_GROUP3 +struct LastSerializationResultBS +{ + RakNet::BitStream bitStream[RM3_NUM_OUTPUT_BITSTREAM_CHANNELS]; + bool indicesToSend[RM3_NUM_OUTPUT_BITSTREAM_CHANNELS]; +}; + +/// Represents the serialized data for an object the last time it was sent. Used by Connection_RM3::OnAutoserializeInterval() and Connection_RM3::SendSerializeIfChanged() +/// \ingroup REPLICA_MANAGER_GROUP3 +struct LastSerializationResult +{ + LastSerializationResult(); + ~LastSerializationResult(); + + /// The replica instance we serialized + RakNet::Replica3 *replica; + //bool neverSerialize; +// bool isConstructed; + + void AllocBS(void); + LastSerializationResultBS* lastSerializationResultBS; +}; + +/// Parameters passed to Replica3::Serialize() +/// \ingroup REPLICA_MANAGER_GROUP3 +struct SerializeParameters +{ + /// Write your output for serialization here + /// If nothing is written, the serialization will not occur + /// Write to any or all of the NUM_OUTPUT_BITSTREAM_CHANNELS channels available. Channels can hold independent data + RakNet::BitStream outputBitstream[RM3_NUM_OUTPUT_BITSTREAM_CHANNELS]; + + /// Last bitstream we sent for this replica to this system. + /// Read, but DO NOT MODIFY + RakNet::BitStream* lastSentBitstream[RM3_NUM_OUTPUT_BITSTREAM_CHANNELS]; + + /// Set to non-zero to transmit a timestamp with this message. + /// Defaults to 0 + RakNetTime messageTimestamp; + + /// Passed to RakPeerInterface::Send(). Defaults to ReplicaManager3::SetDefaultPacketPriority(). + /// Passed to RakPeerInterface::Send(). Defaults to ReplicaManager3::SetDefaultPacketReliability(). + /// Passed to RakPeerInterface::Send(). Defaults to ReplicaManager3::SetDefaultOrderingChannel(). + PRO pro[RM3_NUM_OUTPUT_BITSTREAM_CHANNELS]; + + /// Passed to RakPeerInterface::Send(). + RakNet::Connection_RM3 *destinationConnection; + + /// For prior serializations this tick, for the same connection, how many bits have we written so far? + /// Use this to limit how many objects you send to update per-tick if desired + BitSize_t bitsWrittenSoFar; + + /// When this object was last serialized to the connection + /// 0 means never + RakNetTime whenLastSerialized; + + /// Current time, in milliseconds. + /// curTime - whenLastSerialized is how long it has been since this object was last sent + RakNetTime curTime; +}; + +/// \ingroup REPLICA_MANAGER_GROUP3 +struct DeserializeParameters +{ + RakNet::BitStream serializationBitstream[RM3_NUM_OUTPUT_BITSTREAM_CHANNELS]; + bool bitstreamWrittenTo[RM3_NUM_OUTPUT_BITSTREAM_CHANNELS]; + RakNetTime timeStamp; + RakNet::Connection_RM3 *sourceConnection; +}; + +/// \ingroup REPLICA_MANAGER_GROUP3 +enum SendSerializeIfChangedResult +{ + SSICR_SENT_DATA, + SSICR_DID_NOT_SEND_DATA, + SSICR_NEVER_SERIALIZE, +}; + +/// \brief Each remote system is represented by Connection_RM3. Used to allocate Replica3 and track which instances have been allocated +/// \details Important function: AllocReplica() - must be overridden to create an object given an identifier for that object, which you define for all objects in your game +/// \ingroup REPLICA_MANAGER_GROUP3 +class RAK_DLL_EXPORT Connection_RM3 +{ +public: + Connection_RM3(SystemAddress _systemAddress, RakNetGUID _guid); + virtual ~Connection_RM3(); + + /// \brief Class factory to create a Replica3 instance, given a user-defined identifier + /// \details Identifier is returned by Replica3::WriteAllocationID() for what type of class to create.
    + /// This is called when you download a replica from another system.
    + /// See Replica3::Dealloc for the corresponding destruction message.
    + /// Return 0 if unable to create the intended object. Note, in that case the other system will still think we have the object and will try to serialize object updates to us. Generally, you should not send objects the other system cannot create.
    + /// \sa Replica3::WriteAllocationID(). + /// Sample implementation:
    + /// {RakNet::RakString typeName; allocationIdBitstream->Read(typeName); if (typeName=="Soldier") return new Soldier; return 0;}
    + /// \param[in] allocationIdBitstream user-defined bitstream uniquely identifying a game object type + /// \param[in] replicaManager3 Instance of ReplicaManager3 that controls this connection + /// \return The new replica instance + virtual Replica3 *AllocReplica(RakNet::BitStream *allocationIdBitstream, ReplicaManager3 *replicaManager3)=0; + + /// \brief Get list of all replicas that are constructed for this connection + /// \param[out] objectsTheyDoHave Destination list. Returned in sorted ascending order, sorted on the value of the Replica3 pointer. + virtual void GetConstructedReplicas(DataStructures::Multilist &objectsTheyDoHave); + + /// Returns true if we think this remote connection has this replica constructed + /// \param[in] replica3 Which replica we are querying + /// \return True if constructed, false othewise + bool HasReplicaConstructed(RakNet::Replica3 *replica); + + /// When a new connection connects, before sending any objects, SerializeOnDownloadStarted() is called + /// \param[out] bitStream Passed to DeserializeOnDownloadStarted() + virtual void SerializeOnDownloadStarted(RakNet::BitStream *bitStream) {(void) bitStream;} + + /// Receives whatever was written in SerializeOnDownloadStarted() + /// \param[in] bitStream Written in SerializeOnDownloadStarted() + virtual void DeserializeOnDownloadStarted(RakNet::BitStream *bitStream) {(void) bitStream;} + + /// When a new connection connects, after constructing and serialization all objects, SerializeOnDownloadComplete() is called + /// \param[out] bitStream Passed to DeserializeOnDownloadComplete() + virtual void SerializeOnDownloadComplete(RakNet::BitStream *bitStream) {(void) bitStream;} + + /// Receives whatever was written in DeserializeOnDownloadComplete() + /// \param[in] bitStream Written in SerializeOnDownloadComplete() + virtual void DeserializeOnDownloadComplete(RakNet::BitStream *bitStream) {(void) bitStream;} + + /// \return The system address passed to the constructor of this object + SystemAddress GetSystemAddress(void) const {return systemAddress;} + + /// \return Returns the RakNetGUID passed to the constructor of this object + RakNetGUID GetRakNetGUID(void) const {return guid;} + + /// List of enumerations for how to get the list of valid objects for other systems + enum ConstructionMode + { + /// For every object that does not exist on the remote system, call Replica3::QueryConstruction() every tick. + /// Do not call Replica3::QueryDestruction() + /// Do not call Connection_RM3::QueryReplicaList() + QUERY_REPLICA_FOR_CONSTRUCTION, + + /// For every object that does not exist on the remote system, call Replica3::QueryConstruction() every tick. Based on the call, the object may be sent to the other system. + /// For every object that does exist on the remote system, call Replica3::QueryDestruction() every tick. Based on the call, the object may be deleted on the other system. + /// Do not call Connection_RM3::QueryReplicaList() + QUERY_REPLICA_FOR_CONSTRUCTION_AND_DESTRUCTION, + + /// Do not call Replica3::QueryConstruction() or Replica3::QueryDestruction() + /// Call Connection_RM3::QueryReplicaList() to determine which objects exist on remote systems + /// This can be faster than QUERY_REPLICA_FOR_CONSTRUCTION and QUERY_REPLICA_FOR_CONSTRUCTION_AND_DESTRUCTION for large worlds + /// See GridSectorizer.h under /Source for code that can help with this + QUERY_CONNECTION_FOR_REPLICA_LIST + }; + + /// \brief Queries how to get the list of objects that exist on remote systems + /// \details The default of calling QueryConstruction for every known object is easy to use, but not efficient, especially for large worlds where many objects are outside of the player's circle of influence.
    + /// QueryDestruction is also not necessarily useful or efficient, as object destruction tends to happen in known cases, and can be accomplished by calling Replica3::BroadcastDestruction() + /// QueryConstructionMode() allows you to specify more efficient algorithms than the default when overriden. + /// \return How to get the list of objects that exist on the remote system. You should always return the same value for a given connection + virtual ConstructionMode QueryConstructionMode(void) const {return QUERY_REPLICA_FOR_CONSTRUCTION_AND_DESTRUCTION;} + + /// \brief Callback used when QueryConstructionMode() returns QUERY_CONNECTION_FOR_REPLICA_LIST + /// \details This advantage of this callback is if that there are many objects that a particular connection does not have, then we do not have to iterate through those + /// objects calling QueryConstruction() for each of them.
    + ///
    + /// The following code uses a sorted merge sort to quickly find new and deleted objects, given a list of objects we know should exist.
    + ///
    + /// DataStructures::Multilist objectsTheyShouldHave; // You have to fill in this list
    + /// DataStructures::Multilist objectsTheyCurrentlyHave,objectsTheyStillHave,existingReplicasToDestro,newReplicasToCreatey;
    + /// GetConstructedReplicas(objectsTheyCurrentlyHave);
    + /// DataStructures::Multilist::FindIntersection(objectsTheyCurrentlyHave, objectsTheyShouldHave, objectsTheyStillHave, existingReplicasToDestroy, newReplicasToCreate);
    + ///
    + /// See GridSectorizer in the Source directory as a method to find all objects within a certain radius in a fast way.
    + ///
    + /// \param[out] newReplicasToCreate Anything in this list will be created on the remote system + /// \param[out] existingReplicasToDestroy Anything in this list will be destroyed on the remote system + virtual void QueryReplicaList( + DataStructures::Multilist newReplicasToCreate, + DataStructures::Multilist existingReplicasToDestroy) {} + + /// \internal This is used internally - however, you can also call it manually to send a data update for a remote replica.
    + /// \brief Sends over a serialization update for \a replica.
    + /// NetworkID::GetNetworkID() is written automatically, serializationData is the object data.
    + /// \param[in] replica Which replica to serialize + /// \param[in] serializationData Serialized object data + /// \param[in] timestamp 0 means no timestamp. Otherwise message is prepended with ID_TIMESTAMP + /// \param[in] sendParameters Parameters on how to send + /// \param[in] rakPeer Instance of RakPeerInterface to send on + /// \param[in] worldId Which world, see ReplicaManager3::SetWorldID() + virtual SendSerializeIfChangedResult SendSerialize(RakNet::Replica3 *replica, bool indicesToSend[RM3_NUM_OUTPUT_BITSTREAM_CHANNELS], RakNet::BitStream serializationData[RM3_NUM_OUTPUT_BITSTREAM_CHANNELS], RakNetTime timestamp, PRO sendParameters[RM3_NUM_OUTPUT_BITSTREAM_CHANNELS], RakPeerInterface *rakPeer, unsigned char worldId); + + /// \internal + /// \details Calls Connection_RM3::SendSerialize() if Replica3::Serialize() returns a different result than what is contained in \a lastSerializationResult.
    + /// Used by autoserialization in Connection_RM3::OnAutoserializeInterval() + /// \param[in] queryToSerializeIndex Index into queryToSerializeReplicaList for whichever replica this is + /// \param[in] sp Controlling parameters over the serialization + /// \param[in] rakPeer Instance of RakPeerInterface to send on + /// \param[in] worldId Which world, see ReplicaManager3::SetWorldID() + virtual SendSerializeIfChangedResult SendSerializeIfChanged(DataStructures::DefaultIndexType queryToSerializeIndex, SerializeParameters *sp, RakPeerInterface *rakPeer, unsigned char worldId, ReplicaManager3 *replicaManager); + + /// \internal + /// \brief Given a list of objects that were created and destroyed, serialize and send them to another system. + /// \param[in] newObjects Objects to serialize construction + /// \param[in] deletedObjects Objects to serialize destruction + /// \param[in] sendParameters Controlling parameters over the serialization + /// \param[in] rakPeer Instance of RakPeerInterface to send on + /// \param[in] worldId Which world, see ReplicaManager3::SetWorldID() + virtual void SendConstruction(DataStructures::Multilist &newObjects, DataStructures::Multilist &deletedObjects, PRO sendParameters, RakPeerInterface *rakPeer, unsigned char worldId); + + /// \internal + /// Remove from \a newObjectsIn objects that already exist and save to \a newObjectsOut + /// Remove from \a deletedObjectsIn objects that do not exist, and save to \a deletedObjectsOut + void CullUniqueNewAndDeletedObjects(DataStructures::Multilist &newObjectsIn, + DataStructures::Multilist &deletedObjectsIn, + DataStructures::Multilist &newObjectsOut, + DataStructures::Multilist &deletedObjectsOut); + + /// \internal + void SendValidation(RakPeerInterface *rakPeer, unsigned char worldId); + + /// \internal + void AutoConstructByQuery(ReplicaManager3 *replicaManager3); + + + // Internal - does the other system have this connection too? Validated means we can now use it + bool isValidated; + // Internal - Used to see if we should send download started + bool isFirstConstruction; + +protected: + + SystemAddress systemAddress; + RakNetGUID guid; + + /* + Operations: + + Locally reference a new replica: + Add to queryToConstructReplicaList for all objects + + Add all objects to queryToConstructReplicaList + + Download: + Add to constructedReplicaList for connection that send the object to us + Add to queryToSerializeReplicaList for connection that send the object to us + Add to queryToConstructReplicaList for all other connections + + Never construct for this connection: + Remove from queryToConstructReplicaList + + Construct to this connection + Remove from queryToConstructReplicaList + Add to constructedReplicaList for this connection + Add to queryToSerializeReplicaList for this connection + + Serialize: + Iterate through queryToSerializeReplicaList + + Never serialize for this connection + Remove from queryToSerializeReplicaList + + Reference (this system has this object already) + Remove from queryToConstructReplicaList + Add to constructedReplicaList for this connection + Add to queryToSerializeReplicaList for this connection + + Downloaded an existing object + if replica is in queryToConstructReplicaList, OnConstructToThisConnection() + else ignore + + Send destruction from query + Remove from queryToDestructReplicaList + Remove from queryToSerializeReplicaList + Remove from constructedReplicaList + Add to queryToConstructReplicaList + + Do not query destruction again + Remove from queryToDestructReplicaList + */ + void OnLocalReference(Replica3* replica3, ReplicaManager3 *replicaManager); + void OnDereference(Replica3* replica3, ReplicaManager3 *replicaManager); + void OnDownloadFromThisSystem(Replica3* replica3, ReplicaManager3 *replicaManager); + void OnDownloadFromOtherSystem(Replica3* replica3, ReplicaManager3 *replicaManager); + void OnNeverConstruct(DataStructures::DefaultIndexType queryToConstructIdx, ReplicaManager3 *replicaManager); + void OnConstructToThisConnection(DataStructures::DefaultIndexType queryToConstructIdx, ReplicaManager3 *replicaManager); + void OnConstructToThisConnection(Replica3 *replica, ReplicaManager3 *replicaManager); + void OnNeverSerialize(DataStructures::DefaultIndexType queryToSerializeIndex, ReplicaManager3 *replicaManager); + void OnReplicaAlreadyExists(DataStructures::DefaultIndexType queryToConstructIdx, ReplicaManager3 *replicaManager); + void OnDownloadExisting(Replica3* replica3, ReplicaManager3 *replicaManager); + void OnSendDestructionFromQuery(DataStructures::DefaultIndexType queryToDestructIdx, ReplicaManager3 *replicaManager); + void OnDoNotQueryDestruction(DataStructures::DefaultIndexType queryToDestructIdx, ReplicaManager3 *replicaManager); + void ValidateLists(ReplicaManager3 *replicaManager) const; + void SendSerializeHeader(RakNet::Replica3 *replica, RakNetTime timestamp, RakNet::BitStream *bs, unsigned char worldId); + + // The list of objects that our local system and this remote system both have + // Either we sent this object to them, or they sent this object to us + // A given Replica can be either in queryToConstructReplicaList or constructedReplicaList but not both at the same time + DataStructures::Multilist constructedReplicaList; + + // Objects that we have, but this system does not, and we will query each tick to see if it should be sent to them + // If we do send it to them, the replica is moved to constructedReplicaList + // A given Replica can be either in queryToConstructReplicaList or constructedReplicaList but not both at the same time + DataStructures::Multilist queryToConstructReplicaList; + + // Objects that this system has constructed are added at the same time to queryToSerializeReplicaList + // This list is used to serialize all objects that this system has to this connection + DataStructures::Multilist queryToSerializeReplicaList; + + // Objects that are constructed on this system are also queried if they should be destroyed to this system + DataStructures::Multilist queryToDestructReplicaList; + + // Working lists + DataStructures::Multilist constructedReplicasCulled, destroyedReplicasCulled; + + friend class ReplicaManager3; +private: + Connection_RM3() {}; + + ConstructionMode constructionMode; +}; + +/// \brief Return codes for Connection_RM3::GetConstructionState() and Replica3::QueryConstruction() +/// \details Indicates what state the object should be in for the remote system +/// \ingroup REPLICA_MANAGER_GROUP3 +enum RM3ConstructionState +{ + /// This object should exist on the remote system. Send a construction message if necessary + /// If the NetworkID is already in use, it will not do anything + /// If it is not in use, it will create the object, and then call DeserializeConstruction + RM3CS_SEND_CONSTRUCTION, + + /// This object should exist on the remote system. + /// The other system already has the object, and the object will never be deleted. + /// This is true of objects that are loaded with the level, for example. + /// Treat it as if it existed, without sending a construction message. + /// Will call SerializeConstructionExisting() to the object on the remote system + RM3CS_ALREADY_EXISTS_REMOTELY, + + /// This object will never be sent to this system + RM3CS_NEVER_CONSTRUCT, + + /// Don't do anything this tick. Will query again next tick + RM3CS_NO_ACTION, +}; + +/// If this object already exists for this system, should it be removed? +/// \ingroup REPLICA_MANAGER_GROUP3 +enum RM3DestructionState +{ + /// This object should not exist on the remote system. Send a destruction message if necessary. + RM3DS_SEND_DESTRUCTION, + + /// This object will never be destroyed by a per-tick query. Don't call again + RM3DS_DO_NOT_QUERY_DESTRUCTION, + + /// Don't do anything this tick. Will query again next tick + RM3DS_NO_ACTION, +}; + +/// Return codes when constructing an object +/// \ingroup REPLICA_MANAGER_GROUP3 +enum RM3SerializationResult +{ + /// This object serializes identically no matter who we send to + /// We also send it to every connection (broadcast). + /// Efficient for memory, speed, and bandwidth but only if the object is always broadcast identically. + RM3SR_BROADCAST_IDENTICALLY, + + /// Same as RM3SR_BROADCAST_IDENTICALLY, but assume the object needs to be serialized, do not check with a memcmp + /// Assume the object changed, and serialize it + /// Use this if you know exactly when your object needs to change. Can be faster than RM3SR_BROADCAST_IDENTICALLY. + /// An example of this is if every member variable has an accessor, changing a member sets a flag, and you check that flag in Replica3::QuerySerialization() + /// The opposite of this is RM3SR_DO_NOT_SERIALIZE, in case the object did not change + RM3SR_BROADCAST_IDENTICALLY_FORCE_SERIALIZATION, + + /// Either this object serializes differently depending on who we send to or we send it to some systems and not others. + /// Inefficient for memory and speed, but efficient for bandwidth + /// However, if you don't know what to return, return this + RM3SR_SERIALIZED_UNIQUELY, + + /// Do not compare against last sent value. Just send even if the data is the same as the last tick + /// If the data is always changing anyway, or you want to send unreliably, this is a good method of serialization + /// Can send unique data per connection if desired. If same data is sent to all connections, use RM3SR_SERIALIZED_ALWAYS_IDENTICALLY for even better performance + /// Efficient for memory and speed, but not necessarily bandwidth + RM3SR_SERIALIZED_ALWAYS, + + /// Even faster than RM3SR_SERIALIZED_ALWAYS + /// Serialize() will only be called for the first system. The remaining systems will get the same data as the first system. + RM3SR_SERIALIZED_ALWAYS_IDENTICALLY, + + /// Do not serialize this object this tick, for this connection. Will query again next autoserialize timer + RM3SR_DO_NOT_SERIALIZE, + + /// Never serialize this object for this connection + /// Useful for objects that are downloaded, and never change again + /// Efficient + RM3SR_NEVER_SERIALIZE_FOR_THIS_CONNECTION, +}; + +/// First pass at topology to see if an object should be serialized +/// \ingroup REPLICA_MANAGER_GROUP3 +enum RM3QuerySerializationResult +{ + /// Call Serialize() to see if this object should be serializable for this connection + RM3QSR_CALL_SERIALIZE, + /// Do not call Serialize() this tick to see if this object should be serializable for this connection + RM3QSR_DO_NOT_CALL_SERIALIZE, + /// Never call Serialize() for this object and connection. This system will not serialize this object for this topology + RM3QSR_NEVER_CALL_SERIALIZE, +}; + +/// \ingroup REPLICA_MANAGER_GROUP3 +enum RM3ActionOnPopConnection +{ + RM3AOPC_DO_NOTHING, + RM3AOPC_DELETE_REPLICA, + RM3AOPC_DELETE_REPLICA_AND_BROADCAST_DESTRUCTION, +}; + + +/// \brief Base class for your replicated objects for the ReplicaManager3 system. +/// \details To use, derive your class, or a member of your class, from Replica3.
    +/// \ingroup REPLICA_MANAGER_GROUP3 +class RAK_DLL_EXPORT Replica3 : public NetworkIDObject +{ +public: + Replica3(); + + /// Before deleting a local instance of Replica3, call Replica3::BroadcastDestruction() for the deletion notification to go out on the network. + /// It is not necessary to call ReplicaManager3::Dereference(), as this happens automatically in the destructor + virtual ~Replica3(); + + /// \brief Write a unique identifer that can be read on a remote system to create an object of this same class. + /// \details The value written to \a allocationIdBitstream will be passed to Connection_RM3::AllocReplica().
    + /// Sample implementation:
    + /// {allocationIdBitstream->Write(RakNet::RakString("Soldier");}
    + /// \param[out] allocationIdBitstream Bitstream for the user to write to, to identify this class + virtual void WriteAllocationID(RakNet::BitStream *allocationIdBitstream) const=0; + + /// \brief Ask if this object, which does not exist on \a destinationConnection should (now) be sent to that system. + /// \details If ReplicaManager3::QueryConstructionMode() returns QUERY_CONNECTION_FOR_REPLICA_LIST or QUERY_REPLICA_FOR_CONSTRUCTION_AND_DESTRUCTION (default), + /// then QueyrConstruction() is called once per tick from ReplicaManager3::Update() to determine if an object should exist on a given system.
    + /// Based on the return value, a network message may be sent to the other system to create the object.
    + /// If QueryConstructionMode() is overriden to return QUERY_CONNECTION_FOR_REPLICA_LIST, this function is unused.
    + /// \note Defaults are provided: QueryConstruction_PeerToPeer(), QueryConstruction_ServerConstruction(), QueryConstruction_ClientConstruction(). Return one of these functions for a working default for the relevant topology. + /// \param[in] destinationConnection Which system we will send to + /// \param[in] replicaManager3 Plugin instance for this Replica3 + /// \return What action to take + virtual RM3ConstructionState QueryConstruction(RakNet::Connection_RM3 *destinationConnection, ReplicaManager3 *replicaManager3)=0; + + /// \brief Ask if this object, which does exist on \a destinationConnection should be removed from the remote system + /// \details If ReplicaManager3::QueryConstructionMode() returns QUERY_REPLICA_FOR_CONSTRUCTION_AND_DESTRUCTION (default), + /// then QueryDestruction() is called once per tick from ReplicaManager3::Update() to determine if an object that exists on a remote system should be destroyed for a given system.
    + /// Based on the return value, a network message may be sent to the other system to destroy the object.
    + /// Note that you can also destroy objects with BroadcastDestruction(), so this function is not useful unless you plan to delete objects for only a particular connection.
    + /// If QueryConstructionMode() is overriden to return QUERY_CONNECTION_FOR_REPLICA_LIST, this function is unused.
    + /// \param[in] destinationConnection Which system we will send to + /// \param[in] replicaManager3 Plugin instance for this Replica3 + /// \return What action to take. Only RM3CS_SEND_DESTRUCTION does anything at this time. + virtual RM3DestructionState QueryDestruction(RakNet::Connection_RM3 *destinationConnection, ReplicaManager3 *replicaManager3) {(void) destinationConnection; (void) replicaManager3; return RM3DS_DO_NOT_QUERY_DESTRUCTION;} + + /// \brief We're about to call DeserializeConstruction() on this Replica3. If QueryRemoteConstruction() returns false, this object is deleted instead. + /// \details By default, QueryRemoteConstruction_ServerConstruction() does not allow clients to create objects. The client will get Replica3::DeserializeConstructionRequestRejected().
    + /// If you want the client to be able to potentially create objects for client/server, override accordingly.
    + /// Other variants of QueryRemoteConstruction_* just return true. + /// \note Defaults are provided: QueryRemoteConstruction_PeerToPeer(), QueryRemoteConstruction_ServerConstruction(), QueryRemoteConstruction_ClientConstruction(). Return one of these functions for a working default for the relevant topology. + /// \param[in] sourceConnection Which system sent us the object creation request message. + /// \return True to allow the object to pass onto DeserializeConstruction() (where it may also be rejected), false to immediately reject the remote construction request + virtual bool QueryRemoteConstruction(RakNet::Connection_RM3 *sourceConnection)=0; + + /// \brief Write data to be sent only when the object is constructed on a remote system. + /// \details SerializeConstruction is used to write out data that you need to create this object in the context of your game, such as health, score, name. Use it for data you only need to send when the object is created.
    + /// After SerializeConstruction() is called, Serialize() will be called immediately thereafter. However, they are sent in different messages, so Serialize() may arrive a later frame than SerializeConstruction() + /// For that reason, the object should be valid after a call to DeserializeConstruction() for at least a short time.
    + /// \note The object's NetworkID and allocation id are handled by the system automatically, you do not need to write these values to \a constructionBitstream + /// \param[out] constructionBitstream Destination bitstream to write your data to + /// \param[in] destinationConnection System that will receive this network message. + virtual void SerializeConstruction(RakNet::BitStream *constructionBitstream, RakNet::Connection_RM3 *destinationConnection)=0; + + /// \brief Read data written by Replica3::SerializeConstruction() + /// \details Reads whatever data was written to \a constructionBitstream in Replica3::SerializeConstruction() + /// \param[out] constructionBitstream Bitstream written to in Replica3::SerializeConstruction() + /// \param[in] sourceConnection System that sent us this network message. + /// \return true to accept construction of the object. false to reject, in which case the object will be deleted via Replica3::DeallocReplica() + virtual bool DeserializeConstruction(RakNet::BitStream *constructionBitstream, RakNet::Connection_RM3 *sourceConnection)=0; + + /// Same as SerializeConstruction(), but for an object that already exists on the remote system. + /// Used if you return RM3CS_ALREADY_EXISTS_REMOTELY from QueryConstruction + virtual void SerializeConstructionExisting(RakNet::BitStream *constructionBitstream, RakNet::Connection_RM3 *destinationConnection) {(void) constructionBitstream; (void) destinationConnection;}; + + /// Same as DeserializeConstruction(), but for an object that already exists on the remote system. + /// Used if you return RM3CS_ALREADY_EXISTS_REMOTELY from QueryConstruction + virtual void DeserializeConstructionExisting(RakNet::BitStream *constructionBitstream, RakNet::Connection_RM3 *sourceConnection) {(void) constructionBitstream; (void) sourceConnection;}; + + /// \brief Write extra data to send with the object deletion event, if desired + /// \details Replica3::SerializeDestruction() will be called to write any object destruction specific data you want to send with this event. + /// \a destructionBitstream can be read in DeserializeDestruction() + /// \param[out] destructionBitstream Bitstream for you to write to + /// \param[in] destinationConnection System that will receive this network message. + virtual void SerializeDestruction(RakNet::BitStream *destructionBitstream, RakNet::Connection_RM3 *destinationConnection)=0; + + /// \brief Read data written by Replica3::SerializeDestruction() + /// \details Return true to delete the object. BroadcastDestruction() will be called automatically, followed by ReplicaManager3::Dereference.
    + /// Return false to not delete it. If you delete it at a later point, you are responsible for calling BroadcastDestruction() yourself. + virtual bool DeserializeDestruction(RakNet::BitStream *destructionBitstream, RakNet::Connection_RM3 *sourceConnection)=0; + + /// \brief The system is asking what to do with this replica when the connection is dropped + /// \details Return QueryActionOnPopConnection_Client, QueryActionOnPopConnection_Server, or QueryActionOnPopConnection_PeerToPeer + virtual RM3ActionOnPopConnection QueryActionOnPopConnection(RakNet::Connection_RM3 *droppedConnection) const=0; + + /// Notification called for each of our replicas when a connection is popped + void OnPoppedConnection(RakNet::Connection_RM3 *droppedConnection) {(void) droppedConnection;} + + /// \brief Override with {delete this;} + /// \details + ///
      + ///
    1. Got a remote message to delete this object which passed DeserializeDestruction(), OR + ///
    2. ReplicaManager3::SetAutoManageConnections() was called autoDestroy true (which is the default setting), and a remote system that owns this object disconnected) OR + /// <\OL> + ///
      + /// Override with {delete this;} to actually delete the object (and any other processing you wish).
      + /// If you don't want to delete the object, just do nothing, however, the system will not know this so you are responsible for deleting it yoruself later.
      + /// destructionBitstream may be 0 if the object was deleted locally + virtual void DeallocReplica(RakNet::Connection_RM3 *sourceConnection)=0; + + /// \brief Implement with QuerySerialization_ClientSerializable(), QuerySerialization_ServerSerializable(), or QuerySerialization_PeerToPeer() + /// \details QuerySerialization() is a first pass query to check if a given object should serializable to a given system. The intent is that the user implements with one of the defaults for client, server, or peer to peer.
      + /// Without this function, a careless implementation would serialize an object anytime it changed to all systems. This would give you feedback loops as the sender gets the same message back from the recipient it just sent to.
      + /// If more than one system can serialize the same object then you will need to override to return true, and control the serialization result from Replica3::Serialize(). Be careful not to send back the same data to the system that just sent to you! + /// \return True to allow calling Replica3::Serialize() for this connection, false to not call. + virtual RM3QuerySerializationResult QuerySerialization(RakNet::Connection_RM3 *destinationConnection)=0; + + /// \brief Called for each replica owned by the user, once per Serialization tick, before Serialize() is called. + /// If you want to do some kind of operation on the Replica objects that you own, just before Serialization(), then overload this function + virtual void OnUserReplicaPreSerializeTick(void) {} + + /// \brief Serialize our class to a bitstream + /// \details User should implement this function to write the contents of this class to SerializationParamters::serializationBitstream.
      + /// If data only needs to be written once, you can write it to SerializeConstruction() instead for efficiency.
      + /// Transmitted over the network if it changed from the last time we called Serialize().
      + /// Called every time the time interval to ReplicaManager3::SetAutoSerializeInterval() elapses and ReplicaManager3::Update is subsequently called. + /// \param[in/out] serializeParameters Parameters controlling the serialization, including destination bitstream to write to + /// \return Whether to serialize, and if so, how to optimize the results + virtual RM3SerializationResult Serialize(RakNet::SerializeParameters *serializeParameters)=0; + + /// \brief Called when the class is actually transmitted via Serialize() + /// \details Use to track how much bandwidth this class it taking + virtual void OnSerializeTransmission(RakNet::BitStream *bitStream, SystemAddress systemAddress) {(void) bitStream; (void) systemAddress; } + + /// \brief Read what was written in Serialize() + /// \details Reads the contents of the class from SerializationParamters::serializationBitstream.
      + /// Called whenever Serialize() is called with different data from the last send. + /// \param[in] serializationBitstream Bitstream passed to Serialize() + /// \param[in] timeStamp 0 if unused, else contains the time the message originated on the remote system + /// \param[in] sourceConnection Which system sent to us + virtual void Deserialize(RakNet::DeserializeParameters *deserializeParameters)=0; + + /// \brief Write data for when an object creation request is accepted + /// \details If a system creates an object and NetworkIDManager::IsNetworkIDAuthority() returns false, then the object cannot locally assign NetworkID, which means that the object cannot be used over the network.
      + /// The object will call SerializeConstruction() and sent over the network with a temporary id.
      + /// When the object is created by a system where NetworkIDManager::IsNetworkIDAuthority() returns true, SerializeConstructionRequestAccepted() will be called with the opportunity to write additional data if desired.
      + /// The sender will then receive serializationBitstream in DeserializeConstructionRequestAccepted(), after the NetworkID has been assigned.
      + /// This is not pure virtual, because it is not often used and is not necessary for the system to work. + /// \param[out] serializationBitstream Destination bitstream to write to + /// \param[in] requestingConnection Which system sent to us + virtual void SerializeConstructionRequestAccepted(RakNet::BitStream *serializationBitstream, RakNet::Connection_RM3 *requestingConnection) {(void) serializationBitstream; (void) requestingConnection;} + + /// Receive the result of SerializeConstructionRequestAccepted + /// \param[in] serializationBitstream Source bitstream to read from + /// \param[in] acceptingConnection Which system sent to us + virtual void DeserializeConstructionRequestAccepted(RakNet::BitStream *serializationBitstream, RakNet::Connection_RM3 *acceptingConnection) {(void) serializationBitstream; (void) acceptingConnection;} + + /// Same as SerializeConstructionRequestAccepted(), but the client construction request was rejected + /// \param[out] serializationBitstream Destination bitstream to write to + /// \param[in] requestingConnection Which system sent to us + virtual void SerializeConstructionRequestRejected(RakNet::BitStream *serializationBitstream, RakNet::Connection_RM3 *requestingConnection) {(void) serializationBitstream; (void) requestingConnection;} + + /// Receive the result of DeserializeConstructionRequestRejected + /// \param[in] serializationBitstream Source bitstream to read from + /// \param[in] requestingConnection Which system sent to us + virtual void DeserializeConstructionRequestRejected(RakNet::BitStream *serializationBitstream, RakNet::Connection_RM3 *rejectingConnection) {(void) serializationBitstream; (void) rejectingConnection;} + + /// Called after DeserializeConstruction completes for the object successfully.
      + /// Override to trigger some sort of event when you know the object has completed deserialization. + /// \param[in] sourceConnection System that sent us this network message. + virtual void PostDeserializeConstruction(RakNet::Connection_RM3 *sourceConnection) {(void) sourceConnection;} + + /// Called after DeserializeDestruction completes for the object successfully, but obviously before the object is deleted.
      + /// Override to trigger some sort of event when you know the object has completed destruction. + /// \param[in] sourceConnection System that sent us this network message. + virtual void PreDestruction(RakNet::Connection_RM3 *sourceConnection) {(void) sourceConnection;} + + /// creatingSystemGUID is set the first time Reference() is called, or if we get the object from another system + /// \return System that originally created this object + RakNetGUID GetCreatingSystemGUID(void) const; + + /// Call to send a network message to delete this object on other systems.
      + /// Call it before deleting the object + virtual void BroadcastDestruction(void); + + /// Default call for QueryConstruction(). Allow clients to create this object + virtual RM3ConstructionState QueryConstruction_ClientConstruction(RakNet::Connection_RM3 *destinationConnection); + /// Default call for QueryConstruction(). Allow clients to create this object + virtual bool QueryRemoteConstruction_ClientConstruction(RakNet::Connection_RM3 *sourceConnection); + + /// Default call for QueryConstruction(). Allow the server to create this object, but not the client. + virtual RM3ConstructionState QueryConstruction_ServerConstruction(RakNet::Connection_RM3 *destinationConnection); + /// Default call for QueryConstruction(). Allow the server to create this object, but not the client. + virtual bool QueryRemoteConstruction_ServerConstruction(RakNet::Connection_RM3 *sourceConnection); + + /// Default call for QueryConstruction(). Peer to peer - creating system sends the object to all other systems. No relaying. + virtual RM3ConstructionState QueryConstruction_PeerToPeer(RakNet::Connection_RM3 *destinationConnection); + /// Default call for QueryConstruction(). Peer to peer - creating system sends the object to all other systems. No relaying. + virtual bool QueryRemoteConstruction_PeerToPeer(RakNet::Connection_RM3 *sourceConnection); + + /// Default call for QuerySerialization(). Use if the values you are serializing are generated by the client that owns the object. The serialization will be relayed through the server to the other clients. + virtual RakNet::RM3QuerySerializationResult QuerySerialization_ClientSerializable(RakNet::Connection_RM3 *destinationConnection); + /// Default call for QuerySerialization(). Use if the values you are serializing are generated only by the server. The serialization will be sent to all clients, but the clients will not send back to the server. + virtual RakNet::RM3QuerySerializationResult QuerySerialization_ServerSerializable(RakNet::Connection_RM3 *destinationConnection); + /// Default call for QuerySerialization(). Use if the values you are serializing are on a peer to peer network. The peer that owns the object will send to all. Remote peers will not send. + virtual RakNet::RM3QuerySerializationResult QuerySerialization_PeerToPeer(RakNet::Connection_RM3 *destinationConnection); + + /// Default: If we are a client, and the connection is lost, delete the server's objects + virtual RM3ActionOnPopConnection QueryActionOnPopConnection_Client(RakNet::Connection_RM3 *droppedConnection) const; + /// Default: If we are a client, and the connection is lost, delete the client's objects and broadcast the destruction + virtual RM3ActionOnPopConnection QueryActionOnPopConnection_Server(RakNet::Connection_RM3 *droppedConnection) const; + /// Default: If we are a peer, and the connection is lost, delete the peer's objects + virtual RM3ActionOnPopConnection QueryActionOnPopConnection_PeerToPeer(RakNet::Connection_RM3 *droppedConnection) const; + + /// GUID of the system that first called Reference() on this object. + /// Transmitted automatically when the object is constructed + RakNetGUID creatingSystemGUID; + /// GUID of the system that caused the item to send a deletion command over the network + RakNetGUID deletingSystemGUID; + + /// \internal + /// ReplicaManager3 plugin associated with this object + ReplicaManager3 *replicaManager; + + LastSerializationResultBS lastSentSerialization; + RakNetTime whenLastSerialized; + bool forceSendUntilNextUpdate; +}; + +} // namespace RakNet + + +#endif + +#endif // _RAKNET_SUPPORT_* diff --git a/RakNet/Sources/Rijndael-Boxes.h b/RakNet/Sources/Rijndael-Boxes.h new file mode 100644 index 0000000..c49c2eb --- /dev/null +++ b/RakNet/Sources/Rijndael-Boxes.h @@ -0,0 +1,949 @@ +word8 Logtable[256] = { + 0, 0, 25, 1, 50, 2, 26, 198, 75, 199, 27, 104, 51, 238, 223, 3, +100, 4, 224, 14, 52, 141, 129, 239, 76, 113, 8, 200, 248, 105, 28, 193, +125, 194, 29, 181, 249, 185, 39, 106, 77, 228, 166, 114, 154, 201, 9, 120, +101, 47, 138, 5, 33, 15, 225, 36, 18, 240, 130, 69, 53, 147, 218, 142, +150, 143, 219, 189, 54, 208, 206, 148, 19, 92, 210, 241, 64, 70, 131, 56, +102, 221, 253, 48, 191, 6, 139, 98, 179, 37, 226, 152, 34, 136, 145, 16, +126, 110, 72, 195, 163, 182, 30, 66, 58, 107, 40, 84, 250, 133, 61, 186, + 43, 121, 10, 21, 155, 159, 94, 202, 78, 212, 172, 229, 243, 115, 167, 87, +175, 88, 168, 80, 244, 234, 214, 116, 79, 174, 233, 213, 231, 230, 173, 232, + 44, 215, 117, 122, 235, 22, 11, 245, 89, 203, 95, 176, 156, 169, 81, 160, +127, 12, 246, 111, 23, 196, 73, 236, 216, 67, 31, 45, 164, 118, 123, 183, +204, 187, 62, 90, 251, 96, 177, 134, 59, 82, 161, 108, 170, 85, 41, 157, +151, 178, 135, 144, 97, 190, 220, 252, 188, 149, 207, 205, 55, 63, 91, 209, + 83, 57, 132, 60, 65, 162, 109, 71, 20, 42, 158, 93, 86, 242, 211, 171, + 68, 17, 146, 217, 35, 32, 46, 137, 180, 124, 184, 38, 119, 153, 227, 165, +103, 74, 237, 222, 197, 49, 254, 24, 13, 99, 140, 128, 192, 247, 112, 7 +}; + +word8 Alogtable[256] = { + 1, 3, 5, 15, 17, 51, 85, 255, 26, 46, 114, 150, 161, 248, 19, 53, + 95, 225, 56, 72, 216, 115, 149, 164, 247, 2, 6, 10, 30, 34, 102, 170, +229, 52, 92, 228, 55, 89, 235, 38, 106, 190, 217, 112, 144, 171, 230, 49, + 83, 245, 4, 12, 20, 60, 68, 204, 79, 209, 104, 184, 211, 110, 178, 205, + 76, 212, 103, 169, 224, 59, 77, 215, 98, 166, 241, 8, 24, 40, 120, 136, +131, 158, 185, 208, 107, 189, 220, 127, 129, 152, 179, 206, 73, 219, 118, 154, +181, 196, 87, 249, 16, 48, 80, 240, 11, 29, 39, 105, 187, 214, 97, 163, +254, 25, 43, 125, 135, 146, 173, 236, 47, 113, 147, 174, 233, 32, 96, 160, +251, 22, 58, 78, 210, 109, 183, 194, 93, 231, 50, 86, 250, 21, 63, 65, +195, 94, 226, 61, 71, 201, 64, 192, 91, 237, 44, 116, 156, 191, 218, 117, +159, 186, 213, 100, 172, 239, 42, 126, 130, 157, 188, 223, 122, 142, 137, 128, +155, 182, 193, 88, 232, 35, 101, 175, 234, 37, 111, 177, 200, 67, 197, 84, +252, 31, 33, 99, 165, 244, 7, 9, 27, 45, 119, 153, 176, 203, 70, 202, + 69, 207, 74, 222, 121, 139, 134, 145, 168, 227, 62, 66, 198, 81, 243, 14, + 18, 54, 90, 238, 41, 123, 141, 140, 143, 138, 133, 148, 167, 242, 13, 23, + 57, 75, 221, 124, 132, 151, 162, 253, 28, 36, 108, 180, 199, 82, 246, 1 +}; + +word8 S[256] = { + 99, 124, 119, 123, 242, 107, 111, 197, 48, 1, 103, 43, 254, 215, 171, 118, +202, 130, 201, 125, 250, 89, 71, 240, 173, 212, 162, 175, 156, 164, 114, 192, +183, 253, 147, 38, 54, 63, 247, 204, 52, 165, 229, 241, 113, 216, 49, 21, + 4, 199, 35, 195, 24, 150, 5, 154, 7, 18, 128, 226, 235, 39, 178, 117, + 9, 131, 44, 26, 27, 110, 90, 160, 82, 59, 214, 179, 41, 227, 47, 132, + 83, 209, 0, 237, 32, 252, 177, 91, 106, 203, 190, 57, 74, 76, 88, 207, +208, 239, 170, 251, 67, 77, 51, 133, 69, 249, 2, 127, 80, 60, 159, 168, + 81, 163, 64, 143, 146, 157, 56, 245, 188, 182, 218, 33, 16, 255, 243, 210, +205, 12, 19, 236, 95, 151, 68, 23, 196, 167, 126, 61, 100, 93, 25, 115, + 96, 129, 79, 220, 34, 42, 144, 136, 70, 238, 184, 20, 222, 94, 11, 219, +224, 50, 58, 10, 73, 6, 36, 92, 194, 211, 172, 98, 145, 149, 228, 121, +231, 200, 55, 109, 141, 213, 78, 169, 108, 86, 244, 234, 101, 122, 174, 8, +186, 120, 37, 46, 28, 166, 180, 198, 232, 221, 116, 31, 75, 189, 139, 138, +112, 62, 181, 102, 72, 3, 246, 14, 97, 53, 87, 185, 134, 193, 29, 158, +225, 248, 152, 17, 105, 217, 142, 148, 155, 30, 135, 233, 206, 85, 40, 223, +140, 161, 137, 13, 191, 230, 66, 104, 65, 153, 45, 15, 176, 84, 187, 22 +}; + +word8 Si[256] = { + 82, 9, 106, 213, 48, 54, 165, 56, 191, 64, 163, 158, 129, 243, 215, 251, +124, 227, 57, 130, 155, 47, 255, 135, 52, 142, 67, 68, 196, 222, 233, 203, + 84, 123, 148, 50, 166, 194, 35, 61, 238, 76, 149, 11, 66, 250, 195, 78, + 8, 46, 161, 102, 40, 217, 36, 178, 118, 91, 162, 73, 109, 139, 209, 37, +114, 248, 246, 100, 134, 104, 152, 22, 212, 164, 92, 204, 93, 101, 182, 146, +108, 112, 72, 80, 253, 237, 185, 218, 94, 21, 70, 87, 167, 141, 157, 132, +144, 216, 171, 0, 140, 188, 211, 10, 247, 228, 88, 5, 184, 179, 69, 6, +208, 44, 30, 143, 202, 63, 15, 2, 193, 175, 189, 3, 1, 19, 138, 107, + 58, 145, 17, 65, 79, 103, 220, 234, 151, 242, 207, 206, 240, 180, 230, 115, +150, 172, 116, 34, 231, 173, 53, 133, 226, 249, 55, 232, 28, 117, 223, 110, + 71, 241, 26, 113, 29, 41, 197, 137, 111, 183, 98, 14, 170, 24, 190, 27, +252, 86, 62, 75, 198, 210, 121, 32, 154, 219, 192, 254, 120, 205, 90, 244, + 31, 221, 168, 51, 136, 7, 199, 49, 177, 18, 16, 89, 39, 128, 236, 95, + 96, 81, 127, 169, 25, 181, 74, 13, 45, 229, 122, 159, 147, 201, 156, 239, +160, 224, 59, 77, 174, 42, 245, 176, 200, 235, 187, 60, 131, 83, 153, 97, + 23, 43, 4, 126, 186, 119, 214, 38, 225, 105, 20, 99, 85, 33, 12, 125 +}; + +word8 T1[256][4] = { +{0xc6,0x63,0x63,0xa5}, {0xf8,0x7c,0x7c,0x84}, {0xee,0x77,0x77,0x99}, {0xf6,0x7b,0x7b,0x8d}, +{0xff,0xf2,0xf2,0x0d}, {0xd6,0x6b,0x6b,0xbd}, {0xde,0x6f,0x6f,0xb1}, {0x91,0xc5,0xc5,0x54}, +{0x60,0x30,0x30,0x50}, {0x02,0x01,0x01,0x03}, {0xce,0x67,0x67,0xa9}, {0x56,0x2b,0x2b,0x7d}, +{0xe7,0xfe,0xfe,0x19}, {0xb5,0xd7,0xd7,0x62}, {0x4d,0xab,0xab,0xe6}, {0xec,0x76,0x76,0x9a}, +{0x8f,0xca,0xca,0x45}, {0x1f,0x82,0x82,0x9d}, {0x89,0xc9,0xc9,0x40}, {0xfa,0x7d,0x7d,0x87}, +{0xef,0xfa,0xfa,0x15}, {0xb2,0x59,0x59,0xeb}, {0x8e,0x47,0x47,0xc9}, {0xfb,0xf0,0xf0,0x0b}, +{0x41,0xad,0xad,0xec}, {0xb3,0xd4,0xd4,0x67}, {0x5f,0xa2,0xa2,0xfd}, {0x45,0xaf,0xaf,0xea}, +{0x23,0x9c,0x9c,0xbf}, {0x53,0xa4,0xa4,0xf7}, {0xe4,0x72,0x72,0x96}, {0x9b,0xc0,0xc0,0x5b}, +{0x75,0xb7,0xb7,0xc2}, {0xe1,0xfd,0xfd,0x1c}, {0x3d,0x93,0x93,0xae}, {0x4c,0x26,0x26,0x6a}, +{0x6c,0x36,0x36,0x5a}, {0x7e,0x3f,0x3f,0x41}, {0xf5,0xf7,0xf7,0x02}, {0x83,0xcc,0xcc,0x4f}, +{0x68,0x34,0x34,0x5c}, {0x51,0xa5,0xa5,0xf4}, {0xd1,0xe5,0xe5,0x34}, {0xf9,0xf1,0xf1,0x08}, +{0xe2,0x71,0x71,0x93}, {0xab,0xd8,0xd8,0x73}, {0x62,0x31,0x31,0x53}, {0x2a,0x15,0x15,0x3f}, +{0x08,0x04,0x04,0x0c}, {0x95,0xc7,0xc7,0x52}, {0x46,0x23,0x23,0x65}, {0x9d,0xc3,0xc3,0x5e}, +{0x30,0x18,0x18,0x28}, {0x37,0x96,0x96,0xa1}, {0x0a,0x05,0x05,0x0f}, {0x2f,0x9a,0x9a,0xb5}, +{0x0e,0x07,0x07,0x09}, {0x24,0x12,0x12,0x36}, {0x1b,0x80,0x80,0x9b}, {0xdf,0xe2,0xe2,0x3d}, +{0xcd,0xeb,0xeb,0x26}, {0x4e,0x27,0x27,0x69}, {0x7f,0xb2,0xb2,0xcd}, {0xea,0x75,0x75,0x9f}, +{0x12,0x09,0x09,0x1b}, {0x1d,0x83,0x83,0x9e}, {0x58,0x2c,0x2c,0x74}, {0x34,0x1a,0x1a,0x2e}, +{0x36,0x1b,0x1b,0x2d}, {0xdc,0x6e,0x6e,0xb2}, {0xb4,0x5a,0x5a,0xee}, {0x5b,0xa0,0xa0,0xfb}, +{0xa4,0x52,0x52,0xf6}, {0x76,0x3b,0x3b,0x4d}, {0xb7,0xd6,0xd6,0x61}, {0x7d,0xb3,0xb3,0xce}, +{0x52,0x29,0x29,0x7b}, {0xdd,0xe3,0xe3,0x3e}, {0x5e,0x2f,0x2f,0x71}, {0x13,0x84,0x84,0x97}, +{0xa6,0x53,0x53,0xf5}, {0xb9,0xd1,0xd1,0x68}, {0x00,0x00,0x00,0x00}, {0xc1,0xed,0xed,0x2c}, +{0x40,0x20,0x20,0x60}, {0xe3,0xfc,0xfc,0x1f}, {0x79,0xb1,0xb1,0xc8}, {0xb6,0x5b,0x5b,0xed}, +{0xd4,0x6a,0x6a,0xbe}, {0x8d,0xcb,0xcb,0x46}, {0x67,0xbe,0xbe,0xd9}, {0x72,0x39,0x39,0x4b}, +{0x94,0x4a,0x4a,0xde}, {0x98,0x4c,0x4c,0xd4}, {0xb0,0x58,0x58,0xe8}, {0x85,0xcf,0xcf,0x4a}, +{0xbb,0xd0,0xd0,0x6b}, {0xc5,0xef,0xef,0x2a}, {0x4f,0xaa,0xaa,0xe5}, {0xed,0xfb,0xfb,0x16}, +{0x86,0x43,0x43,0xc5}, {0x9a,0x4d,0x4d,0xd7}, {0x66,0x33,0x33,0x55}, {0x11,0x85,0x85,0x94}, +{0x8a,0x45,0x45,0xcf}, {0xe9,0xf9,0xf9,0x10}, {0x04,0x02,0x02,0x06}, {0xfe,0x7f,0x7f,0x81}, +{0xa0,0x50,0x50,0xf0}, {0x78,0x3c,0x3c,0x44}, {0x25,0x9f,0x9f,0xba}, {0x4b,0xa8,0xa8,0xe3}, +{0xa2,0x51,0x51,0xf3}, {0x5d,0xa3,0xa3,0xfe}, {0x80,0x40,0x40,0xc0}, {0x05,0x8f,0x8f,0x8a}, +{0x3f,0x92,0x92,0xad}, {0x21,0x9d,0x9d,0xbc}, {0x70,0x38,0x38,0x48}, {0xf1,0xf5,0xf5,0x04}, +{0x63,0xbc,0xbc,0xdf}, {0x77,0xb6,0xb6,0xc1}, {0xaf,0xda,0xda,0x75}, {0x42,0x21,0x21,0x63}, +{0x20,0x10,0x10,0x30}, {0xe5,0xff,0xff,0x1a}, {0xfd,0xf3,0xf3,0x0e}, {0xbf,0xd2,0xd2,0x6d}, +{0x81,0xcd,0xcd,0x4c}, {0x18,0x0c,0x0c,0x14}, {0x26,0x13,0x13,0x35}, {0xc3,0xec,0xec,0x2f}, +{0xbe,0x5f,0x5f,0xe1}, {0x35,0x97,0x97,0xa2}, {0x88,0x44,0x44,0xcc}, {0x2e,0x17,0x17,0x39}, +{0x93,0xc4,0xc4,0x57}, {0x55,0xa7,0xa7,0xf2}, {0xfc,0x7e,0x7e,0x82}, {0x7a,0x3d,0x3d,0x47}, +{0xc8,0x64,0x64,0xac}, {0xba,0x5d,0x5d,0xe7}, {0x32,0x19,0x19,0x2b}, {0xe6,0x73,0x73,0x95}, +{0xc0,0x60,0x60,0xa0}, {0x19,0x81,0x81,0x98}, {0x9e,0x4f,0x4f,0xd1}, {0xa3,0xdc,0xdc,0x7f}, +{0x44,0x22,0x22,0x66}, {0x54,0x2a,0x2a,0x7e}, {0x3b,0x90,0x90,0xab}, {0x0b,0x88,0x88,0x83}, +{0x8c,0x46,0x46,0xca}, {0xc7,0xee,0xee,0x29}, {0x6b,0xb8,0xb8,0xd3}, {0x28,0x14,0x14,0x3c}, +{0xa7,0xde,0xde,0x79}, {0xbc,0x5e,0x5e,0xe2}, {0x16,0x0b,0x0b,0x1d}, {0xad,0xdb,0xdb,0x76}, +{0xdb,0xe0,0xe0,0x3b}, {0x64,0x32,0x32,0x56}, {0x74,0x3a,0x3a,0x4e}, {0x14,0x0a,0x0a,0x1e}, +{0x92,0x49,0x49,0xdb}, {0x0c,0x06,0x06,0x0a}, {0x48,0x24,0x24,0x6c}, {0xb8,0x5c,0x5c,0xe4}, +{0x9f,0xc2,0xc2,0x5d}, {0xbd,0xd3,0xd3,0x6e}, {0x43,0xac,0xac,0xef}, {0xc4,0x62,0x62,0xa6}, +{0x39,0x91,0x91,0xa8}, {0x31,0x95,0x95,0xa4}, {0xd3,0xe4,0xe4,0x37}, {0xf2,0x79,0x79,0x8b}, +{0xd5,0xe7,0xe7,0x32}, {0x8b,0xc8,0xc8,0x43}, {0x6e,0x37,0x37,0x59}, {0xda,0x6d,0x6d,0xb7}, +{0x01,0x8d,0x8d,0x8c}, {0xb1,0xd5,0xd5,0x64}, {0x9c,0x4e,0x4e,0xd2}, {0x49,0xa9,0xa9,0xe0}, +{0xd8,0x6c,0x6c,0xb4}, {0xac,0x56,0x56,0xfa}, {0xf3,0xf4,0xf4,0x07}, {0xcf,0xea,0xea,0x25}, +{0xca,0x65,0x65,0xaf}, {0xf4,0x7a,0x7a,0x8e}, {0x47,0xae,0xae,0xe9}, {0x10,0x08,0x08,0x18}, +{0x6f,0xba,0xba,0xd5}, {0xf0,0x78,0x78,0x88}, {0x4a,0x25,0x25,0x6f}, {0x5c,0x2e,0x2e,0x72}, +{0x38,0x1c,0x1c,0x24}, {0x57,0xa6,0xa6,0xf1}, {0x73,0xb4,0xb4,0xc7}, {0x97,0xc6,0xc6,0x51}, +{0xcb,0xe8,0xe8,0x23}, {0xa1,0xdd,0xdd,0x7c}, {0xe8,0x74,0x74,0x9c}, {0x3e,0x1f,0x1f,0x21}, +{0x96,0x4b,0x4b,0xdd}, {0x61,0xbd,0xbd,0xdc}, {0x0d,0x8b,0x8b,0x86}, {0x0f,0x8a,0x8a,0x85}, +{0xe0,0x70,0x70,0x90}, {0x7c,0x3e,0x3e,0x42}, {0x71,0xb5,0xb5,0xc4}, {0xcc,0x66,0x66,0xaa}, +{0x90,0x48,0x48,0xd8}, {0x06,0x03,0x03,0x05}, {0xf7,0xf6,0xf6,0x01}, {0x1c,0x0e,0x0e,0x12}, +{0xc2,0x61,0x61,0xa3}, {0x6a,0x35,0x35,0x5f}, {0xae,0x57,0x57,0xf9}, {0x69,0xb9,0xb9,0xd0}, +{0x17,0x86,0x86,0x91}, {0x99,0xc1,0xc1,0x58}, {0x3a,0x1d,0x1d,0x27}, {0x27,0x9e,0x9e,0xb9}, +{0xd9,0xe1,0xe1,0x38}, {0xeb,0xf8,0xf8,0x13}, {0x2b,0x98,0x98,0xb3}, {0x22,0x11,0x11,0x33}, +{0xd2,0x69,0x69,0xbb}, {0xa9,0xd9,0xd9,0x70}, {0x07,0x8e,0x8e,0x89}, {0x33,0x94,0x94,0xa7}, +{0x2d,0x9b,0x9b,0xb6}, {0x3c,0x1e,0x1e,0x22}, {0x15,0x87,0x87,0x92}, {0xc9,0xe9,0xe9,0x20}, +{0x87,0xce,0xce,0x49}, {0xaa,0x55,0x55,0xff}, {0x50,0x28,0x28,0x78}, {0xa5,0xdf,0xdf,0x7a}, +{0x03,0x8c,0x8c,0x8f}, {0x59,0xa1,0xa1,0xf8}, {0x09,0x89,0x89,0x80}, {0x1a,0x0d,0x0d,0x17}, +{0x65,0xbf,0xbf,0xda}, {0xd7,0xe6,0xe6,0x31}, {0x84,0x42,0x42,0xc6}, {0xd0,0x68,0x68,0xb8}, +{0x82,0x41,0x41,0xc3}, {0x29,0x99,0x99,0xb0}, {0x5a,0x2d,0x2d,0x77}, {0x1e,0x0f,0x0f,0x11}, +{0x7b,0xb0,0xb0,0xcb}, {0xa8,0x54,0x54,0xfc}, {0x6d,0xbb,0xbb,0xd6}, {0x2c,0x16,0x16,0x3a} +}; + +word8 T2[256][4] = { +{0xa5,0xc6,0x63,0x63}, {0x84,0xf8,0x7c,0x7c}, {0x99,0xee,0x77,0x77}, {0x8d,0xf6,0x7b,0x7b}, +{0x0d,0xff,0xf2,0xf2}, {0xbd,0xd6,0x6b,0x6b}, {0xb1,0xde,0x6f,0x6f}, {0x54,0x91,0xc5,0xc5}, +{0x50,0x60,0x30,0x30}, {0x03,0x02,0x01,0x01}, {0xa9,0xce,0x67,0x67}, {0x7d,0x56,0x2b,0x2b}, +{0x19,0xe7,0xfe,0xfe}, {0x62,0xb5,0xd7,0xd7}, {0xe6,0x4d,0xab,0xab}, {0x9a,0xec,0x76,0x76}, +{0x45,0x8f,0xca,0xca}, {0x9d,0x1f,0x82,0x82}, {0x40,0x89,0xc9,0xc9}, {0x87,0xfa,0x7d,0x7d}, +{0x15,0xef,0xfa,0xfa}, {0xeb,0xb2,0x59,0x59}, {0xc9,0x8e,0x47,0x47}, {0x0b,0xfb,0xf0,0xf0}, +{0xec,0x41,0xad,0xad}, {0x67,0xb3,0xd4,0xd4}, {0xfd,0x5f,0xa2,0xa2}, {0xea,0x45,0xaf,0xaf}, +{0xbf,0x23,0x9c,0x9c}, {0xf7,0x53,0xa4,0xa4}, {0x96,0xe4,0x72,0x72}, {0x5b,0x9b,0xc0,0xc0}, +{0xc2,0x75,0xb7,0xb7}, {0x1c,0xe1,0xfd,0xfd}, {0xae,0x3d,0x93,0x93}, {0x6a,0x4c,0x26,0x26}, +{0x5a,0x6c,0x36,0x36}, {0x41,0x7e,0x3f,0x3f}, {0x02,0xf5,0xf7,0xf7}, {0x4f,0x83,0xcc,0xcc}, +{0x5c,0x68,0x34,0x34}, {0xf4,0x51,0xa5,0xa5}, {0x34,0xd1,0xe5,0xe5}, {0x08,0xf9,0xf1,0xf1}, +{0x93,0xe2,0x71,0x71}, {0x73,0xab,0xd8,0xd8}, {0x53,0x62,0x31,0x31}, {0x3f,0x2a,0x15,0x15}, +{0x0c,0x08,0x04,0x04}, {0x52,0x95,0xc7,0xc7}, {0x65,0x46,0x23,0x23}, {0x5e,0x9d,0xc3,0xc3}, +{0x28,0x30,0x18,0x18}, {0xa1,0x37,0x96,0x96}, {0x0f,0x0a,0x05,0x05}, {0xb5,0x2f,0x9a,0x9a}, +{0x09,0x0e,0x07,0x07}, {0x36,0x24,0x12,0x12}, {0x9b,0x1b,0x80,0x80}, {0x3d,0xdf,0xe2,0xe2}, +{0x26,0xcd,0xeb,0xeb}, {0x69,0x4e,0x27,0x27}, {0xcd,0x7f,0xb2,0xb2}, {0x9f,0xea,0x75,0x75}, +{0x1b,0x12,0x09,0x09}, {0x9e,0x1d,0x83,0x83}, {0x74,0x58,0x2c,0x2c}, {0x2e,0x34,0x1a,0x1a}, +{0x2d,0x36,0x1b,0x1b}, {0xb2,0xdc,0x6e,0x6e}, {0xee,0xb4,0x5a,0x5a}, {0xfb,0x5b,0xa0,0xa0}, +{0xf6,0xa4,0x52,0x52}, {0x4d,0x76,0x3b,0x3b}, {0x61,0xb7,0xd6,0xd6}, {0xce,0x7d,0xb3,0xb3}, +{0x7b,0x52,0x29,0x29}, {0x3e,0xdd,0xe3,0xe3}, {0x71,0x5e,0x2f,0x2f}, {0x97,0x13,0x84,0x84}, +{0xf5,0xa6,0x53,0x53}, {0x68,0xb9,0xd1,0xd1}, {0x00,0x00,0x00,0x00}, {0x2c,0xc1,0xed,0xed}, +{0x60,0x40,0x20,0x20}, {0x1f,0xe3,0xfc,0xfc}, {0xc8,0x79,0xb1,0xb1}, {0xed,0xb6,0x5b,0x5b}, +{0xbe,0xd4,0x6a,0x6a}, {0x46,0x8d,0xcb,0xcb}, {0xd9,0x67,0xbe,0xbe}, {0x4b,0x72,0x39,0x39}, +{0xde,0x94,0x4a,0x4a}, {0xd4,0x98,0x4c,0x4c}, {0xe8,0xb0,0x58,0x58}, {0x4a,0x85,0xcf,0xcf}, +{0x6b,0xbb,0xd0,0xd0}, {0x2a,0xc5,0xef,0xef}, {0xe5,0x4f,0xaa,0xaa}, {0x16,0xed,0xfb,0xfb}, +{0xc5,0x86,0x43,0x43}, {0xd7,0x9a,0x4d,0x4d}, {0x55,0x66,0x33,0x33}, {0x94,0x11,0x85,0x85}, +{0xcf,0x8a,0x45,0x45}, {0x10,0xe9,0xf9,0xf9}, {0x06,0x04,0x02,0x02}, {0x81,0xfe,0x7f,0x7f}, +{0xf0,0xa0,0x50,0x50}, {0x44,0x78,0x3c,0x3c}, {0xba,0x25,0x9f,0x9f}, {0xe3,0x4b,0xa8,0xa8}, +{0xf3,0xa2,0x51,0x51}, {0xfe,0x5d,0xa3,0xa3}, {0xc0,0x80,0x40,0x40}, {0x8a,0x05,0x8f,0x8f}, +{0xad,0x3f,0x92,0x92}, {0xbc,0x21,0x9d,0x9d}, {0x48,0x70,0x38,0x38}, {0x04,0xf1,0xf5,0xf5}, +{0xdf,0x63,0xbc,0xbc}, {0xc1,0x77,0xb6,0xb6}, {0x75,0xaf,0xda,0xda}, {0x63,0x42,0x21,0x21}, +{0x30,0x20,0x10,0x10}, {0x1a,0xe5,0xff,0xff}, {0x0e,0xfd,0xf3,0xf3}, {0x6d,0xbf,0xd2,0xd2}, +{0x4c,0x81,0xcd,0xcd}, {0x14,0x18,0x0c,0x0c}, {0x35,0x26,0x13,0x13}, {0x2f,0xc3,0xec,0xec}, +{0xe1,0xbe,0x5f,0x5f}, {0xa2,0x35,0x97,0x97}, {0xcc,0x88,0x44,0x44}, {0x39,0x2e,0x17,0x17}, +{0x57,0x93,0xc4,0xc4}, {0xf2,0x55,0xa7,0xa7}, {0x82,0xfc,0x7e,0x7e}, {0x47,0x7a,0x3d,0x3d}, +{0xac,0xc8,0x64,0x64}, {0xe7,0xba,0x5d,0x5d}, {0x2b,0x32,0x19,0x19}, {0x95,0xe6,0x73,0x73}, +{0xa0,0xc0,0x60,0x60}, {0x98,0x19,0x81,0x81}, {0xd1,0x9e,0x4f,0x4f}, {0x7f,0xa3,0xdc,0xdc}, +{0x66,0x44,0x22,0x22}, {0x7e,0x54,0x2a,0x2a}, {0xab,0x3b,0x90,0x90}, {0x83,0x0b,0x88,0x88}, +{0xca,0x8c,0x46,0x46}, {0x29,0xc7,0xee,0xee}, {0xd3,0x6b,0xb8,0xb8}, {0x3c,0x28,0x14,0x14}, +{0x79,0xa7,0xde,0xde}, {0xe2,0xbc,0x5e,0x5e}, {0x1d,0x16,0x0b,0x0b}, {0x76,0xad,0xdb,0xdb}, +{0x3b,0xdb,0xe0,0xe0}, {0x56,0x64,0x32,0x32}, {0x4e,0x74,0x3a,0x3a}, {0x1e,0x14,0x0a,0x0a}, +{0xdb,0x92,0x49,0x49}, {0x0a,0x0c,0x06,0x06}, {0x6c,0x48,0x24,0x24}, {0xe4,0xb8,0x5c,0x5c}, +{0x5d,0x9f,0xc2,0xc2}, {0x6e,0xbd,0xd3,0xd3}, {0xef,0x43,0xac,0xac}, {0xa6,0xc4,0x62,0x62}, +{0xa8,0x39,0x91,0x91}, {0xa4,0x31,0x95,0x95}, {0x37,0xd3,0xe4,0xe4}, {0x8b,0xf2,0x79,0x79}, +{0x32,0xd5,0xe7,0xe7}, {0x43,0x8b,0xc8,0xc8}, {0x59,0x6e,0x37,0x37}, {0xb7,0xda,0x6d,0x6d}, +{0x8c,0x01,0x8d,0x8d}, {0x64,0xb1,0xd5,0xd5}, {0xd2,0x9c,0x4e,0x4e}, {0xe0,0x49,0xa9,0xa9}, +{0xb4,0xd8,0x6c,0x6c}, {0xfa,0xac,0x56,0x56}, {0x07,0xf3,0xf4,0xf4}, {0x25,0xcf,0xea,0xea}, +{0xaf,0xca,0x65,0x65}, {0x8e,0xf4,0x7a,0x7a}, {0xe9,0x47,0xae,0xae}, {0x18,0x10,0x08,0x08}, +{0xd5,0x6f,0xba,0xba}, {0x88,0xf0,0x78,0x78}, {0x6f,0x4a,0x25,0x25}, {0x72,0x5c,0x2e,0x2e}, +{0x24,0x38,0x1c,0x1c}, {0xf1,0x57,0xa6,0xa6}, {0xc7,0x73,0xb4,0xb4}, {0x51,0x97,0xc6,0xc6}, +{0x23,0xcb,0xe8,0xe8}, {0x7c,0xa1,0xdd,0xdd}, {0x9c,0xe8,0x74,0x74}, {0x21,0x3e,0x1f,0x1f}, +{0xdd,0x96,0x4b,0x4b}, {0xdc,0x61,0xbd,0xbd}, {0x86,0x0d,0x8b,0x8b}, {0x85,0x0f,0x8a,0x8a}, +{0x90,0xe0,0x70,0x70}, {0x42,0x7c,0x3e,0x3e}, {0xc4,0x71,0xb5,0xb5}, {0xaa,0xcc,0x66,0x66}, +{0xd8,0x90,0x48,0x48}, {0x05,0x06,0x03,0x03}, {0x01,0xf7,0xf6,0xf6}, {0x12,0x1c,0x0e,0x0e}, +{0xa3,0xc2,0x61,0x61}, {0x5f,0x6a,0x35,0x35}, {0xf9,0xae,0x57,0x57}, {0xd0,0x69,0xb9,0xb9}, +{0x91,0x17,0x86,0x86}, {0x58,0x99,0xc1,0xc1}, {0x27,0x3a,0x1d,0x1d}, {0xb9,0x27,0x9e,0x9e}, +{0x38,0xd9,0xe1,0xe1}, {0x13,0xeb,0xf8,0xf8}, {0xb3,0x2b,0x98,0x98}, {0x33,0x22,0x11,0x11}, +{0xbb,0xd2,0x69,0x69}, {0x70,0xa9,0xd9,0xd9}, {0x89,0x07,0x8e,0x8e}, {0xa7,0x33,0x94,0x94}, +{0xb6,0x2d,0x9b,0x9b}, {0x22,0x3c,0x1e,0x1e}, {0x92,0x15,0x87,0x87}, {0x20,0xc9,0xe9,0xe9}, +{0x49,0x87,0xce,0xce}, {0xff,0xaa,0x55,0x55}, {0x78,0x50,0x28,0x28}, {0x7a,0xa5,0xdf,0xdf}, +{0x8f,0x03,0x8c,0x8c}, {0xf8,0x59,0xa1,0xa1}, {0x80,0x09,0x89,0x89}, {0x17,0x1a,0x0d,0x0d}, +{0xda,0x65,0xbf,0xbf}, {0x31,0xd7,0xe6,0xe6}, {0xc6,0x84,0x42,0x42}, {0xb8,0xd0,0x68,0x68}, +{0xc3,0x82,0x41,0x41}, {0xb0,0x29,0x99,0x99}, {0x77,0x5a,0x2d,0x2d}, {0x11,0x1e,0x0f,0x0f}, +{0xcb,0x7b,0xb0,0xb0}, {0xfc,0xa8,0x54,0x54}, {0xd6,0x6d,0xbb,0xbb}, {0x3a,0x2c,0x16,0x16} +}; + +word8 T3[256][4] = { +{0x63,0xa5,0xc6,0x63}, {0x7c,0x84,0xf8,0x7c}, {0x77,0x99,0xee,0x77}, {0x7b,0x8d,0xf6,0x7b}, +{0xf2,0x0d,0xff,0xf2}, {0x6b,0xbd,0xd6,0x6b}, {0x6f,0xb1,0xde,0x6f}, {0xc5,0x54,0x91,0xc5}, +{0x30,0x50,0x60,0x30}, {0x01,0x03,0x02,0x01}, {0x67,0xa9,0xce,0x67}, {0x2b,0x7d,0x56,0x2b}, +{0xfe,0x19,0xe7,0xfe}, {0xd7,0x62,0xb5,0xd7}, {0xab,0xe6,0x4d,0xab}, {0x76,0x9a,0xec,0x76}, +{0xca,0x45,0x8f,0xca}, {0x82,0x9d,0x1f,0x82}, {0xc9,0x40,0x89,0xc9}, {0x7d,0x87,0xfa,0x7d}, +{0xfa,0x15,0xef,0xfa}, {0x59,0xeb,0xb2,0x59}, {0x47,0xc9,0x8e,0x47}, {0xf0,0x0b,0xfb,0xf0}, +{0xad,0xec,0x41,0xad}, {0xd4,0x67,0xb3,0xd4}, {0xa2,0xfd,0x5f,0xa2}, {0xaf,0xea,0x45,0xaf}, +{0x9c,0xbf,0x23,0x9c}, {0xa4,0xf7,0x53,0xa4}, {0x72,0x96,0xe4,0x72}, {0xc0,0x5b,0x9b,0xc0}, +{0xb7,0xc2,0x75,0xb7}, {0xfd,0x1c,0xe1,0xfd}, {0x93,0xae,0x3d,0x93}, {0x26,0x6a,0x4c,0x26}, +{0x36,0x5a,0x6c,0x36}, {0x3f,0x41,0x7e,0x3f}, {0xf7,0x02,0xf5,0xf7}, {0xcc,0x4f,0x83,0xcc}, +{0x34,0x5c,0x68,0x34}, {0xa5,0xf4,0x51,0xa5}, {0xe5,0x34,0xd1,0xe5}, {0xf1,0x08,0xf9,0xf1}, +{0x71,0x93,0xe2,0x71}, {0xd8,0x73,0xab,0xd8}, {0x31,0x53,0x62,0x31}, {0x15,0x3f,0x2a,0x15}, +{0x04,0x0c,0x08,0x04}, {0xc7,0x52,0x95,0xc7}, {0x23,0x65,0x46,0x23}, {0xc3,0x5e,0x9d,0xc3}, +{0x18,0x28,0x30,0x18}, {0x96,0xa1,0x37,0x96}, {0x05,0x0f,0x0a,0x05}, {0x9a,0xb5,0x2f,0x9a}, +{0x07,0x09,0x0e,0x07}, {0x12,0x36,0x24,0x12}, {0x80,0x9b,0x1b,0x80}, {0xe2,0x3d,0xdf,0xe2}, +{0xeb,0x26,0xcd,0xeb}, {0x27,0x69,0x4e,0x27}, {0xb2,0xcd,0x7f,0xb2}, {0x75,0x9f,0xea,0x75}, +{0x09,0x1b,0x12,0x09}, {0x83,0x9e,0x1d,0x83}, {0x2c,0x74,0x58,0x2c}, {0x1a,0x2e,0x34,0x1a}, +{0x1b,0x2d,0x36,0x1b}, {0x6e,0xb2,0xdc,0x6e}, {0x5a,0xee,0xb4,0x5a}, {0xa0,0xfb,0x5b,0xa0}, +{0x52,0xf6,0xa4,0x52}, {0x3b,0x4d,0x76,0x3b}, {0xd6,0x61,0xb7,0xd6}, {0xb3,0xce,0x7d,0xb3}, +{0x29,0x7b,0x52,0x29}, {0xe3,0x3e,0xdd,0xe3}, {0x2f,0x71,0x5e,0x2f}, {0x84,0x97,0x13,0x84}, +{0x53,0xf5,0xa6,0x53}, {0xd1,0x68,0xb9,0xd1}, {0x00,0x00,0x00,0x00}, {0xed,0x2c,0xc1,0xed}, +{0x20,0x60,0x40,0x20}, {0xfc,0x1f,0xe3,0xfc}, {0xb1,0xc8,0x79,0xb1}, {0x5b,0xed,0xb6,0x5b}, +{0x6a,0xbe,0xd4,0x6a}, {0xcb,0x46,0x8d,0xcb}, {0xbe,0xd9,0x67,0xbe}, {0x39,0x4b,0x72,0x39}, +{0x4a,0xde,0x94,0x4a}, {0x4c,0xd4,0x98,0x4c}, {0x58,0xe8,0xb0,0x58}, {0xcf,0x4a,0x85,0xcf}, +{0xd0,0x6b,0xbb,0xd0}, {0xef,0x2a,0xc5,0xef}, {0xaa,0xe5,0x4f,0xaa}, {0xfb,0x16,0xed,0xfb}, +{0x43,0xc5,0x86,0x43}, {0x4d,0xd7,0x9a,0x4d}, {0x33,0x55,0x66,0x33}, {0x85,0x94,0x11,0x85}, +{0x45,0xcf,0x8a,0x45}, {0xf9,0x10,0xe9,0xf9}, {0x02,0x06,0x04,0x02}, {0x7f,0x81,0xfe,0x7f}, +{0x50,0xf0,0xa0,0x50}, {0x3c,0x44,0x78,0x3c}, {0x9f,0xba,0x25,0x9f}, {0xa8,0xe3,0x4b,0xa8}, +{0x51,0xf3,0xa2,0x51}, {0xa3,0xfe,0x5d,0xa3}, {0x40,0xc0,0x80,0x40}, {0x8f,0x8a,0x05,0x8f}, +{0x92,0xad,0x3f,0x92}, {0x9d,0xbc,0x21,0x9d}, {0x38,0x48,0x70,0x38}, {0xf5,0x04,0xf1,0xf5}, +{0xbc,0xdf,0x63,0xbc}, {0xb6,0xc1,0x77,0xb6}, {0xda,0x75,0xaf,0xda}, {0x21,0x63,0x42,0x21}, +{0x10,0x30,0x20,0x10}, {0xff,0x1a,0xe5,0xff}, {0xf3,0x0e,0xfd,0xf3}, {0xd2,0x6d,0xbf,0xd2}, +{0xcd,0x4c,0x81,0xcd}, {0x0c,0x14,0x18,0x0c}, {0x13,0x35,0x26,0x13}, {0xec,0x2f,0xc3,0xec}, +{0x5f,0xe1,0xbe,0x5f}, {0x97,0xa2,0x35,0x97}, {0x44,0xcc,0x88,0x44}, {0x17,0x39,0x2e,0x17}, +{0xc4,0x57,0x93,0xc4}, {0xa7,0xf2,0x55,0xa7}, {0x7e,0x82,0xfc,0x7e}, {0x3d,0x47,0x7a,0x3d}, +{0x64,0xac,0xc8,0x64}, {0x5d,0xe7,0xba,0x5d}, {0x19,0x2b,0x32,0x19}, {0x73,0x95,0xe6,0x73}, +{0x60,0xa0,0xc0,0x60}, {0x81,0x98,0x19,0x81}, {0x4f,0xd1,0x9e,0x4f}, {0xdc,0x7f,0xa3,0xdc}, +{0x22,0x66,0x44,0x22}, {0x2a,0x7e,0x54,0x2a}, {0x90,0xab,0x3b,0x90}, {0x88,0x83,0x0b,0x88}, +{0x46,0xca,0x8c,0x46}, {0xee,0x29,0xc7,0xee}, {0xb8,0xd3,0x6b,0xb8}, {0x14,0x3c,0x28,0x14}, +{0xde,0x79,0xa7,0xde}, {0x5e,0xe2,0xbc,0x5e}, {0x0b,0x1d,0x16,0x0b}, {0xdb,0x76,0xad,0xdb}, +{0xe0,0x3b,0xdb,0xe0}, {0x32,0x56,0x64,0x32}, {0x3a,0x4e,0x74,0x3a}, {0x0a,0x1e,0x14,0x0a}, +{0x49,0xdb,0x92,0x49}, {0x06,0x0a,0x0c,0x06}, {0x24,0x6c,0x48,0x24}, {0x5c,0xe4,0xb8,0x5c}, +{0xc2,0x5d,0x9f,0xc2}, {0xd3,0x6e,0xbd,0xd3}, {0xac,0xef,0x43,0xac}, {0x62,0xa6,0xc4,0x62}, +{0x91,0xa8,0x39,0x91}, {0x95,0xa4,0x31,0x95}, {0xe4,0x37,0xd3,0xe4}, {0x79,0x8b,0xf2,0x79}, +{0xe7,0x32,0xd5,0xe7}, {0xc8,0x43,0x8b,0xc8}, {0x37,0x59,0x6e,0x37}, {0x6d,0xb7,0xda,0x6d}, +{0x8d,0x8c,0x01,0x8d}, {0xd5,0x64,0xb1,0xd5}, {0x4e,0xd2,0x9c,0x4e}, {0xa9,0xe0,0x49,0xa9}, +{0x6c,0xb4,0xd8,0x6c}, {0x56,0xfa,0xac,0x56}, {0xf4,0x07,0xf3,0xf4}, {0xea,0x25,0xcf,0xea}, +{0x65,0xaf,0xca,0x65}, {0x7a,0x8e,0xf4,0x7a}, {0xae,0xe9,0x47,0xae}, {0x08,0x18,0x10,0x08}, +{0xba,0xd5,0x6f,0xba}, {0x78,0x88,0xf0,0x78}, {0x25,0x6f,0x4a,0x25}, {0x2e,0x72,0x5c,0x2e}, +{0x1c,0x24,0x38,0x1c}, {0xa6,0xf1,0x57,0xa6}, {0xb4,0xc7,0x73,0xb4}, {0xc6,0x51,0x97,0xc6}, +{0xe8,0x23,0xcb,0xe8}, {0xdd,0x7c,0xa1,0xdd}, {0x74,0x9c,0xe8,0x74}, {0x1f,0x21,0x3e,0x1f}, +{0x4b,0xdd,0x96,0x4b}, {0xbd,0xdc,0x61,0xbd}, {0x8b,0x86,0x0d,0x8b}, {0x8a,0x85,0x0f,0x8a}, +{0x70,0x90,0xe0,0x70}, {0x3e,0x42,0x7c,0x3e}, {0xb5,0xc4,0x71,0xb5}, {0x66,0xaa,0xcc,0x66}, +{0x48,0xd8,0x90,0x48}, {0x03,0x05,0x06,0x03}, {0xf6,0x01,0xf7,0xf6}, {0x0e,0x12,0x1c,0x0e}, +{0x61,0xa3,0xc2,0x61}, {0x35,0x5f,0x6a,0x35}, {0x57,0xf9,0xae,0x57}, {0xb9,0xd0,0x69,0xb9}, +{0x86,0x91,0x17,0x86}, {0xc1,0x58,0x99,0xc1}, {0x1d,0x27,0x3a,0x1d}, {0x9e,0xb9,0x27,0x9e}, +{0xe1,0x38,0xd9,0xe1}, {0xf8,0x13,0xeb,0xf8}, {0x98,0xb3,0x2b,0x98}, {0x11,0x33,0x22,0x11}, +{0x69,0xbb,0xd2,0x69}, {0xd9,0x70,0xa9,0xd9}, {0x8e,0x89,0x07,0x8e}, {0x94,0xa7,0x33,0x94}, +{0x9b,0xb6,0x2d,0x9b}, {0x1e,0x22,0x3c,0x1e}, {0x87,0x92,0x15,0x87}, {0xe9,0x20,0xc9,0xe9}, +{0xce,0x49,0x87,0xce}, {0x55,0xff,0xaa,0x55}, {0x28,0x78,0x50,0x28}, {0xdf,0x7a,0xa5,0xdf}, +{0x8c,0x8f,0x03,0x8c}, {0xa1,0xf8,0x59,0xa1}, {0x89,0x80,0x09,0x89}, {0x0d,0x17,0x1a,0x0d}, +{0xbf,0xda,0x65,0xbf}, {0xe6,0x31,0xd7,0xe6}, {0x42,0xc6,0x84,0x42}, {0x68,0xb8,0xd0,0x68}, +{0x41,0xc3,0x82,0x41}, {0x99,0xb0,0x29,0x99}, {0x2d,0x77,0x5a,0x2d}, {0x0f,0x11,0x1e,0x0f}, +{0xb0,0xcb,0x7b,0xb0}, {0x54,0xfc,0xa8,0x54}, {0xbb,0xd6,0x6d,0xbb}, {0x16,0x3a,0x2c,0x16} +}; + +word8 T4[256][4] = { +{0x63,0x63,0xa5,0xc6}, {0x7c,0x7c,0x84,0xf8}, {0x77,0x77,0x99,0xee}, {0x7b,0x7b,0x8d,0xf6}, +{0xf2,0xf2,0x0d,0xff}, {0x6b,0x6b,0xbd,0xd6}, {0x6f,0x6f,0xb1,0xde}, {0xc5,0xc5,0x54,0x91}, +{0x30,0x30,0x50,0x60}, {0x01,0x01,0x03,0x02}, {0x67,0x67,0xa9,0xce}, {0x2b,0x2b,0x7d,0x56}, +{0xfe,0xfe,0x19,0xe7}, {0xd7,0xd7,0x62,0xb5}, {0xab,0xab,0xe6,0x4d}, {0x76,0x76,0x9a,0xec}, +{0xca,0xca,0x45,0x8f}, {0x82,0x82,0x9d,0x1f}, {0xc9,0xc9,0x40,0x89}, {0x7d,0x7d,0x87,0xfa}, +{0xfa,0xfa,0x15,0xef}, {0x59,0x59,0xeb,0xb2}, {0x47,0x47,0xc9,0x8e}, {0xf0,0xf0,0x0b,0xfb}, +{0xad,0xad,0xec,0x41}, {0xd4,0xd4,0x67,0xb3}, {0xa2,0xa2,0xfd,0x5f}, {0xaf,0xaf,0xea,0x45}, +{0x9c,0x9c,0xbf,0x23}, {0xa4,0xa4,0xf7,0x53}, {0x72,0x72,0x96,0xe4}, {0xc0,0xc0,0x5b,0x9b}, +{0xb7,0xb7,0xc2,0x75}, {0xfd,0xfd,0x1c,0xe1}, {0x93,0x93,0xae,0x3d}, {0x26,0x26,0x6a,0x4c}, +{0x36,0x36,0x5a,0x6c}, {0x3f,0x3f,0x41,0x7e}, {0xf7,0xf7,0x02,0xf5}, {0xcc,0xcc,0x4f,0x83}, +{0x34,0x34,0x5c,0x68}, {0xa5,0xa5,0xf4,0x51}, {0xe5,0xe5,0x34,0xd1}, {0xf1,0xf1,0x08,0xf9}, +{0x71,0x71,0x93,0xe2}, {0xd8,0xd8,0x73,0xab}, {0x31,0x31,0x53,0x62}, {0x15,0x15,0x3f,0x2a}, +{0x04,0x04,0x0c,0x08}, {0xc7,0xc7,0x52,0x95}, {0x23,0x23,0x65,0x46}, {0xc3,0xc3,0x5e,0x9d}, +{0x18,0x18,0x28,0x30}, {0x96,0x96,0xa1,0x37}, {0x05,0x05,0x0f,0x0a}, {0x9a,0x9a,0xb5,0x2f}, +{0x07,0x07,0x09,0x0e}, {0x12,0x12,0x36,0x24}, {0x80,0x80,0x9b,0x1b}, {0xe2,0xe2,0x3d,0xdf}, +{0xeb,0xeb,0x26,0xcd}, {0x27,0x27,0x69,0x4e}, {0xb2,0xb2,0xcd,0x7f}, {0x75,0x75,0x9f,0xea}, +{0x09,0x09,0x1b,0x12}, {0x83,0x83,0x9e,0x1d}, {0x2c,0x2c,0x74,0x58}, {0x1a,0x1a,0x2e,0x34}, +{0x1b,0x1b,0x2d,0x36}, {0x6e,0x6e,0xb2,0xdc}, {0x5a,0x5a,0xee,0xb4}, {0xa0,0xa0,0xfb,0x5b}, +{0x52,0x52,0xf6,0xa4}, {0x3b,0x3b,0x4d,0x76}, {0xd6,0xd6,0x61,0xb7}, {0xb3,0xb3,0xce,0x7d}, +{0x29,0x29,0x7b,0x52}, {0xe3,0xe3,0x3e,0xdd}, {0x2f,0x2f,0x71,0x5e}, {0x84,0x84,0x97,0x13}, +{0x53,0x53,0xf5,0xa6}, {0xd1,0xd1,0x68,0xb9}, {0x00,0x00,0x00,0x00}, {0xed,0xed,0x2c,0xc1}, +{0x20,0x20,0x60,0x40}, {0xfc,0xfc,0x1f,0xe3}, {0xb1,0xb1,0xc8,0x79}, {0x5b,0x5b,0xed,0xb6}, +{0x6a,0x6a,0xbe,0xd4}, {0xcb,0xcb,0x46,0x8d}, {0xbe,0xbe,0xd9,0x67}, {0x39,0x39,0x4b,0x72}, +{0x4a,0x4a,0xde,0x94}, {0x4c,0x4c,0xd4,0x98}, {0x58,0x58,0xe8,0xb0}, {0xcf,0xcf,0x4a,0x85}, +{0xd0,0xd0,0x6b,0xbb}, {0xef,0xef,0x2a,0xc5}, {0xaa,0xaa,0xe5,0x4f}, {0xfb,0xfb,0x16,0xed}, +{0x43,0x43,0xc5,0x86}, {0x4d,0x4d,0xd7,0x9a}, {0x33,0x33,0x55,0x66}, {0x85,0x85,0x94,0x11}, +{0x45,0x45,0xcf,0x8a}, {0xf9,0xf9,0x10,0xe9}, {0x02,0x02,0x06,0x04}, {0x7f,0x7f,0x81,0xfe}, +{0x50,0x50,0xf0,0xa0}, {0x3c,0x3c,0x44,0x78}, {0x9f,0x9f,0xba,0x25}, {0xa8,0xa8,0xe3,0x4b}, +{0x51,0x51,0xf3,0xa2}, {0xa3,0xa3,0xfe,0x5d}, {0x40,0x40,0xc0,0x80}, {0x8f,0x8f,0x8a,0x05}, +{0x92,0x92,0xad,0x3f}, {0x9d,0x9d,0xbc,0x21}, {0x38,0x38,0x48,0x70}, {0xf5,0xf5,0x04,0xf1}, +{0xbc,0xbc,0xdf,0x63}, {0xb6,0xb6,0xc1,0x77}, {0xda,0xda,0x75,0xaf}, {0x21,0x21,0x63,0x42}, +{0x10,0x10,0x30,0x20}, {0xff,0xff,0x1a,0xe5}, {0xf3,0xf3,0x0e,0xfd}, {0xd2,0xd2,0x6d,0xbf}, +{0xcd,0xcd,0x4c,0x81}, {0x0c,0x0c,0x14,0x18}, {0x13,0x13,0x35,0x26}, {0xec,0xec,0x2f,0xc3}, +{0x5f,0x5f,0xe1,0xbe}, {0x97,0x97,0xa2,0x35}, {0x44,0x44,0xcc,0x88}, {0x17,0x17,0x39,0x2e}, +{0xc4,0xc4,0x57,0x93}, {0xa7,0xa7,0xf2,0x55}, {0x7e,0x7e,0x82,0xfc}, {0x3d,0x3d,0x47,0x7a}, +{0x64,0x64,0xac,0xc8}, {0x5d,0x5d,0xe7,0xba}, {0x19,0x19,0x2b,0x32}, {0x73,0x73,0x95,0xe6}, +{0x60,0x60,0xa0,0xc0}, {0x81,0x81,0x98,0x19}, {0x4f,0x4f,0xd1,0x9e}, {0xdc,0xdc,0x7f,0xa3}, +{0x22,0x22,0x66,0x44}, {0x2a,0x2a,0x7e,0x54}, {0x90,0x90,0xab,0x3b}, {0x88,0x88,0x83,0x0b}, +{0x46,0x46,0xca,0x8c}, {0xee,0xee,0x29,0xc7}, {0xb8,0xb8,0xd3,0x6b}, {0x14,0x14,0x3c,0x28}, +{0xde,0xde,0x79,0xa7}, {0x5e,0x5e,0xe2,0xbc}, {0x0b,0x0b,0x1d,0x16}, {0xdb,0xdb,0x76,0xad}, +{0xe0,0xe0,0x3b,0xdb}, {0x32,0x32,0x56,0x64}, {0x3a,0x3a,0x4e,0x74}, {0x0a,0x0a,0x1e,0x14}, +{0x49,0x49,0xdb,0x92}, {0x06,0x06,0x0a,0x0c}, {0x24,0x24,0x6c,0x48}, {0x5c,0x5c,0xe4,0xb8}, +{0xc2,0xc2,0x5d,0x9f}, {0xd3,0xd3,0x6e,0xbd}, {0xac,0xac,0xef,0x43}, {0x62,0x62,0xa6,0xc4}, +{0x91,0x91,0xa8,0x39}, {0x95,0x95,0xa4,0x31}, {0xe4,0xe4,0x37,0xd3}, {0x79,0x79,0x8b,0xf2}, +{0xe7,0xe7,0x32,0xd5}, {0xc8,0xc8,0x43,0x8b}, {0x37,0x37,0x59,0x6e}, {0x6d,0x6d,0xb7,0xda}, +{0x8d,0x8d,0x8c,0x01}, {0xd5,0xd5,0x64,0xb1}, {0x4e,0x4e,0xd2,0x9c}, {0xa9,0xa9,0xe0,0x49}, +{0x6c,0x6c,0xb4,0xd8}, {0x56,0x56,0xfa,0xac}, {0xf4,0xf4,0x07,0xf3}, {0xea,0xea,0x25,0xcf}, +{0x65,0x65,0xaf,0xca}, {0x7a,0x7a,0x8e,0xf4}, {0xae,0xae,0xe9,0x47}, {0x08,0x08,0x18,0x10}, +{0xba,0xba,0xd5,0x6f}, {0x78,0x78,0x88,0xf0}, {0x25,0x25,0x6f,0x4a}, {0x2e,0x2e,0x72,0x5c}, +{0x1c,0x1c,0x24,0x38}, {0xa6,0xa6,0xf1,0x57}, {0xb4,0xb4,0xc7,0x73}, {0xc6,0xc6,0x51,0x97}, +{0xe8,0xe8,0x23,0xcb}, {0xdd,0xdd,0x7c,0xa1}, {0x74,0x74,0x9c,0xe8}, {0x1f,0x1f,0x21,0x3e}, +{0x4b,0x4b,0xdd,0x96}, {0xbd,0xbd,0xdc,0x61}, {0x8b,0x8b,0x86,0x0d}, {0x8a,0x8a,0x85,0x0f}, +{0x70,0x70,0x90,0xe0}, {0x3e,0x3e,0x42,0x7c}, {0xb5,0xb5,0xc4,0x71}, {0x66,0x66,0xaa,0xcc}, +{0x48,0x48,0xd8,0x90}, {0x03,0x03,0x05,0x06}, {0xf6,0xf6,0x01,0xf7}, {0x0e,0x0e,0x12,0x1c}, +{0x61,0x61,0xa3,0xc2}, {0x35,0x35,0x5f,0x6a}, {0x57,0x57,0xf9,0xae}, {0xb9,0xb9,0xd0,0x69}, +{0x86,0x86,0x91,0x17}, {0xc1,0xc1,0x58,0x99}, {0x1d,0x1d,0x27,0x3a}, {0x9e,0x9e,0xb9,0x27}, +{0xe1,0xe1,0x38,0xd9}, {0xf8,0xf8,0x13,0xeb}, {0x98,0x98,0xb3,0x2b}, {0x11,0x11,0x33,0x22}, +{0x69,0x69,0xbb,0xd2}, {0xd9,0xd9,0x70,0xa9}, {0x8e,0x8e,0x89,0x07}, {0x94,0x94,0xa7,0x33}, +{0x9b,0x9b,0xb6,0x2d}, {0x1e,0x1e,0x22,0x3c}, {0x87,0x87,0x92,0x15}, {0xe9,0xe9,0x20,0xc9}, +{0xce,0xce,0x49,0x87}, {0x55,0x55,0xff,0xaa}, {0x28,0x28,0x78,0x50}, {0xdf,0xdf,0x7a,0xa5}, +{0x8c,0x8c,0x8f,0x03}, {0xa1,0xa1,0xf8,0x59}, {0x89,0x89,0x80,0x09}, {0x0d,0x0d,0x17,0x1a}, +{0xbf,0xbf,0xda,0x65}, {0xe6,0xe6,0x31,0xd7}, {0x42,0x42,0xc6,0x84}, {0x68,0x68,0xb8,0xd0}, +{0x41,0x41,0xc3,0x82}, {0x99,0x99,0xb0,0x29}, {0x2d,0x2d,0x77,0x5a}, {0x0f,0x0f,0x11,0x1e}, +{0xb0,0xb0,0xcb,0x7b}, {0x54,0x54,0xfc,0xa8}, {0xbb,0xbb,0xd6,0x6d}, {0x16,0x16,0x3a,0x2c} +}; + +word8 T5[256][4] = { +{0x51,0xf4,0xa7,0x50}, {0x7e,0x41,0x65,0x53}, {0x1a,0x17,0xa4,0xc3}, {0x3a,0x27,0x5e,0x96}, +{0x3b,0xab,0x6b,0xcb}, {0x1f,0x9d,0x45,0xf1}, {0xac,0xfa,0x58,0xab}, {0x4b,0xe3,0x03,0x93}, +{0x20,0x30,0xfa,0x55}, {0xad,0x76,0x6d,0xf6}, {0x88,0xcc,0x76,0x91}, {0xf5,0x02,0x4c,0x25}, +{0x4f,0xe5,0xd7,0xfc}, {0xc5,0x2a,0xcb,0xd7}, {0x26,0x35,0x44,0x80}, {0xb5,0x62,0xa3,0x8f}, +{0xde,0xb1,0x5a,0x49}, {0x25,0xba,0x1b,0x67}, {0x45,0xea,0x0e,0x98}, {0x5d,0xfe,0xc0,0xe1}, +{0xc3,0x2f,0x75,0x02}, {0x81,0x4c,0xf0,0x12}, {0x8d,0x46,0x97,0xa3}, {0x6b,0xd3,0xf9,0xc6}, +{0x03,0x8f,0x5f,0xe7}, {0x15,0x92,0x9c,0x95}, {0xbf,0x6d,0x7a,0xeb}, {0x95,0x52,0x59,0xda}, +{0xd4,0xbe,0x83,0x2d}, {0x58,0x74,0x21,0xd3}, {0x49,0xe0,0x69,0x29}, {0x8e,0xc9,0xc8,0x44}, +{0x75,0xc2,0x89,0x6a}, {0xf4,0x8e,0x79,0x78}, {0x99,0x58,0x3e,0x6b}, {0x27,0xb9,0x71,0xdd}, +{0xbe,0xe1,0x4f,0xb6}, {0xf0,0x88,0xad,0x17}, {0xc9,0x20,0xac,0x66}, {0x7d,0xce,0x3a,0xb4}, +{0x63,0xdf,0x4a,0x18}, {0xe5,0x1a,0x31,0x82}, {0x97,0x51,0x33,0x60}, {0x62,0x53,0x7f,0x45}, +{0xb1,0x64,0x77,0xe0}, {0xbb,0x6b,0xae,0x84}, {0xfe,0x81,0xa0,0x1c}, {0xf9,0x08,0x2b,0x94}, +{0x70,0x48,0x68,0x58}, {0x8f,0x45,0xfd,0x19}, {0x94,0xde,0x6c,0x87}, {0x52,0x7b,0xf8,0xb7}, +{0xab,0x73,0xd3,0x23}, {0x72,0x4b,0x02,0xe2}, {0xe3,0x1f,0x8f,0x57}, {0x66,0x55,0xab,0x2a}, +{0xb2,0xeb,0x28,0x07}, {0x2f,0xb5,0xc2,0x03}, {0x86,0xc5,0x7b,0x9a}, {0xd3,0x37,0x08,0xa5}, +{0x30,0x28,0x87,0xf2}, {0x23,0xbf,0xa5,0xb2}, {0x02,0x03,0x6a,0xba}, {0xed,0x16,0x82,0x5c}, +{0x8a,0xcf,0x1c,0x2b}, {0xa7,0x79,0xb4,0x92}, {0xf3,0x07,0xf2,0xf0}, {0x4e,0x69,0xe2,0xa1}, +{0x65,0xda,0xf4,0xcd}, {0x06,0x05,0xbe,0xd5}, {0xd1,0x34,0x62,0x1f}, {0xc4,0xa6,0xfe,0x8a}, +{0x34,0x2e,0x53,0x9d}, {0xa2,0xf3,0x55,0xa0}, {0x05,0x8a,0xe1,0x32}, {0xa4,0xf6,0xeb,0x75}, +{0x0b,0x83,0xec,0x39}, {0x40,0x60,0xef,0xaa}, {0x5e,0x71,0x9f,0x06}, {0xbd,0x6e,0x10,0x51}, +{0x3e,0x21,0x8a,0xf9}, {0x96,0xdd,0x06,0x3d}, {0xdd,0x3e,0x05,0xae}, {0x4d,0xe6,0xbd,0x46}, +{0x91,0x54,0x8d,0xb5}, {0x71,0xc4,0x5d,0x05}, {0x04,0x06,0xd4,0x6f}, {0x60,0x50,0x15,0xff}, +{0x19,0x98,0xfb,0x24}, {0xd6,0xbd,0xe9,0x97}, {0x89,0x40,0x43,0xcc}, {0x67,0xd9,0x9e,0x77}, +{0xb0,0xe8,0x42,0xbd}, {0x07,0x89,0x8b,0x88}, {0xe7,0x19,0x5b,0x38}, {0x79,0xc8,0xee,0xdb}, +{0xa1,0x7c,0x0a,0x47}, {0x7c,0x42,0x0f,0xe9}, {0xf8,0x84,0x1e,0xc9}, {0x00,0x00,0x00,0x00}, +{0x09,0x80,0x86,0x83}, {0x32,0x2b,0xed,0x48}, {0x1e,0x11,0x70,0xac}, {0x6c,0x5a,0x72,0x4e}, +{0xfd,0x0e,0xff,0xfb}, {0x0f,0x85,0x38,0x56}, {0x3d,0xae,0xd5,0x1e}, {0x36,0x2d,0x39,0x27}, +{0x0a,0x0f,0xd9,0x64}, {0x68,0x5c,0xa6,0x21}, {0x9b,0x5b,0x54,0xd1}, {0x24,0x36,0x2e,0x3a}, +{0x0c,0x0a,0x67,0xb1}, {0x93,0x57,0xe7,0x0f}, {0xb4,0xee,0x96,0xd2}, {0x1b,0x9b,0x91,0x9e}, +{0x80,0xc0,0xc5,0x4f}, {0x61,0xdc,0x20,0xa2}, {0x5a,0x77,0x4b,0x69}, {0x1c,0x12,0x1a,0x16}, +{0xe2,0x93,0xba,0x0a}, {0xc0,0xa0,0x2a,0xe5}, {0x3c,0x22,0xe0,0x43}, {0x12,0x1b,0x17,0x1d}, +{0x0e,0x09,0x0d,0x0b}, {0xf2,0x8b,0xc7,0xad}, {0x2d,0xb6,0xa8,0xb9}, {0x14,0x1e,0xa9,0xc8}, +{0x57,0xf1,0x19,0x85}, {0xaf,0x75,0x07,0x4c}, {0xee,0x99,0xdd,0xbb}, {0xa3,0x7f,0x60,0xfd}, +{0xf7,0x01,0x26,0x9f}, {0x5c,0x72,0xf5,0xbc}, {0x44,0x66,0x3b,0xc5}, {0x5b,0xfb,0x7e,0x34}, +{0x8b,0x43,0x29,0x76}, {0xcb,0x23,0xc6,0xdc}, {0xb6,0xed,0xfc,0x68}, {0xb8,0xe4,0xf1,0x63}, +{0xd7,0x31,0xdc,0xca}, {0x42,0x63,0x85,0x10}, {0x13,0x97,0x22,0x40}, {0x84,0xc6,0x11,0x20}, +{0x85,0x4a,0x24,0x7d}, {0xd2,0xbb,0x3d,0xf8}, {0xae,0xf9,0x32,0x11}, {0xc7,0x29,0xa1,0x6d}, +{0x1d,0x9e,0x2f,0x4b}, {0xdc,0xb2,0x30,0xf3}, {0x0d,0x86,0x52,0xec}, {0x77,0xc1,0xe3,0xd0}, +{0x2b,0xb3,0x16,0x6c}, {0xa9,0x70,0xb9,0x99}, {0x11,0x94,0x48,0xfa}, {0x47,0xe9,0x64,0x22}, +{0xa8,0xfc,0x8c,0xc4}, {0xa0,0xf0,0x3f,0x1a}, {0x56,0x7d,0x2c,0xd8}, {0x22,0x33,0x90,0xef}, +{0x87,0x49,0x4e,0xc7}, {0xd9,0x38,0xd1,0xc1}, {0x8c,0xca,0xa2,0xfe}, {0x98,0xd4,0x0b,0x36}, +{0xa6,0xf5,0x81,0xcf}, {0xa5,0x7a,0xde,0x28}, {0xda,0xb7,0x8e,0x26}, {0x3f,0xad,0xbf,0xa4}, +{0x2c,0x3a,0x9d,0xe4}, {0x50,0x78,0x92,0x0d}, {0x6a,0x5f,0xcc,0x9b}, {0x54,0x7e,0x46,0x62}, +{0xf6,0x8d,0x13,0xc2}, {0x90,0xd8,0xb8,0xe8}, {0x2e,0x39,0xf7,0x5e}, {0x82,0xc3,0xaf,0xf5}, +{0x9f,0x5d,0x80,0xbe}, {0x69,0xd0,0x93,0x7c}, {0x6f,0xd5,0x2d,0xa9}, {0xcf,0x25,0x12,0xb3}, +{0xc8,0xac,0x99,0x3b}, {0x10,0x18,0x7d,0xa7}, {0xe8,0x9c,0x63,0x6e}, {0xdb,0x3b,0xbb,0x7b}, +{0xcd,0x26,0x78,0x09}, {0x6e,0x59,0x18,0xf4}, {0xec,0x9a,0xb7,0x01}, {0x83,0x4f,0x9a,0xa8}, +{0xe6,0x95,0x6e,0x65}, {0xaa,0xff,0xe6,0x7e}, {0x21,0xbc,0xcf,0x08}, {0xef,0x15,0xe8,0xe6}, +{0xba,0xe7,0x9b,0xd9}, {0x4a,0x6f,0x36,0xce}, {0xea,0x9f,0x09,0xd4}, {0x29,0xb0,0x7c,0xd6}, +{0x31,0xa4,0xb2,0xaf}, {0x2a,0x3f,0x23,0x31}, {0xc6,0xa5,0x94,0x30}, {0x35,0xa2,0x66,0xc0}, +{0x74,0x4e,0xbc,0x37}, {0xfc,0x82,0xca,0xa6}, {0xe0,0x90,0xd0,0xb0}, {0x33,0xa7,0xd8,0x15}, +{0xf1,0x04,0x98,0x4a}, {0x41,0xec,0xda,0xf7}, {0x7f,0xcd,0x50,0x0e}, {0x17,0x91,0xf6,0x2f}, +{0x76,0x4d,0xd6,0x8d}, {0x43,0xef,0xb0,0x4d}, {0xcc,0xaa,0x4d,0x54}, {0xe4,0x96,0x04,0xdf}, +{0x9e,0xd1,0xb5,0xe3}, {0x4c,0x6a,0x88,0x1b}, {0xc1,0x2c,0x1f,0xb8}, {0x46,0x65,0x51,0x7f}, +{0x9d,0x5e,0xea,0x04}, {0x01,0x8c,0x35,0x5d}, {0xfa,0x87,0x74,0x73}, {0xfb,0x0b,0x41,0x2e}, +{0xb3,0x67,0x1d,0x5a}, {0x92,0xdb,0xd2,0x52}, {0xe9,0x10,0x56,0x33}, {0x6d,0xd6,0x47,0x13}, +{0x9a,0xd7,0x61,0x8c}, {0x37,0xa1,0x0c,0x7a}, {0x59,0xf8,0x14,0x8e}, {0xeb,0x13,0x3c,0x89}, +{0xce,0xa9,0x27,0xee}, {0xb7,0x61,0xc9,0x35}, {0xe1,0x1c,0xe5,0xed}, {0x7a,0x47,0xb1,0x3c}, +{0x9c,0xd2,0xdf,0x59}, {0x55,0xf2,0x73,0x3f}, {0x18,0x14,0xce,0x79}, {0x73,0xc7,0x37,0xbf}, +{0x53,0xf7,0xcd,0xea}, {0x5f,0xfd,0xaa,0x5b}, {0xdf,0x3d,0x6f,0x14}, {0x78,0x44,0xdb,0x86}, +{0xca,0xaf,0xf3,0x81}, {0xb9,0x68,0xc4,0x3e}, {0x38,0x24,0x34,0x2c}, {0xc2,0xa3,0x40,0x5f}, +{0x16,0x1d,0xc3,0x72}, {0xbc,0xe2,0x25,0x0c}, {0x28,0x3c,0x49,0x8b}, {0xff,0x0d,0x95,0x41}, +{0x39,0xa8,0x01,0x71}, {0x08,0x0c,0xb3,0xde}, {0xd8,0xb4,0xe4,0x9c}, {0x64,0x56,0xc1,0x90}, +{0x7b,0xcb,0x84,0x61}, {0xd5,0x32,0xb6,0x70}, {0x48,0x6c,0x5c,0x74}, {0xd0,0xb8,0x57,0x42} +}; + +word8 T6[256][4] = { +{0x50,0x51,0xf4,0xa7}, {0x53,0x7e,0x41,0x65}, {0xc3,0x1a,0x17,0xa4}, {0x96,0x3a,0x27,0x5e}, +{0xcb,0x3b,0xab,0x6b}, {0xf1,0x1f,0x9d,0x45}, {0xab,0xac,0xfa,0x58}, {0x93,0x4b,0xe3,0x03}, +{0x55,0x20,0x30,0xfa}, {0xf6,0xad,0x76,0x6d}, {0x91,0x88,0xcc,0x76}, {0x25,0xf5,0x02,0x4c}, +{0xfc,0x4f,0xe5,0xd7}, {0xd7,0xc5,0x2a,0xcb}, {0x80,0x26,0x35,0x44}, {0x8f,0xb5,0x62,0xa3}, +{0x49,0xde,0xb1,0x5a}, {0x67,0x25,0xba,0x1b}, {0x98,0x45,0xea,0x0e}, {0xe1,0x5d,0xfe,0xc0}, +{0x02,0xc3,0x2f,0x75}, {0x12,0x81,0x4c,0xf0}, {0xa3,0x8d,0x46,0x97}, {0xc6,0x6b,0xd3,0xf9}, +{0xe7,0x03,0x8f,0x5f}, {0x95,0x15,0x92,0x9c}, {0xeb,0xbf,0x6d,0x7a}, {0xda,0x95,0x52,0x59}, +{0x2d,0xd4,0xbe,0x83}, {0xd3,0x58,0x74,0x21}, {0x29,0x49,0xe0,0x69}, {0x44,0x8e,0xc9,0xc8}, +{0x6a,0x75,0xc2,0x89}, {0x78,0xf4,0x8e,0x79}, {0x6b,0x99,0x58,0x3e}, {0xdd,0x27,0xb9,0x71}, +{0xb6,0xbe,0xe1,0x4f}, {0x17,0xf0,0x88,0xad}, {0x66,0xc9,0x20,0xac}, {0xb4,0x7d,0xce,0x3a}, +{0x18,0x63,0xdf,0x4a}, {0x82,0xe5,0x1a,0x31}, {0x60,0x97,0x51,0x33}, {0x45,0x62,0x53,0x7f}, +{0xe0,0xb1,0x64,0x77}, {0x84,0xbb,0x6b,0xae}, {0x1c,0xfe,0x81,0xa0}, {0x94,0xf9,0x08,0x2b}, +{0x58,0x70,0x48,0x68}, {0x19,0x8f,0x45,0xfd}, {0x87,0x94,0xde,0x6c}, {0xb7,0x52,0x7b,0xf8}, +{0x23,0xab,0x73,0xd3}, {0xe2,0x72,0x4b,0x02}, {0x57,0xe3,0x1f,0x8f}, {0x2a,0x66,0x55,0xab}, +{0x07,0xb2,0xeb,0x28}, {0x03,0x2f,0xb5,0xc2}, {0x9a,0x86,0xc5,0x7b}, {0xa5,0xd3,0x37,0x08}, +{0xf2,0x30,0x28,0x87}, {0xb2,0x23,0xbf,0xa5}, {0xba,0x02,0x03,0x6a}, {0x5c,0xed,0x16,0x82}, +{0x2b,0x8a,0xcf,0x1c}, {0x92,0xa7,0x79,0xb4}, {0xf0,0xf3,0x07,0xf2}, {0xa1,0x4e,0x69,0xe2}, +{0xcd,0x65,0xda,0xf4}, {0xd5,0x06,0x05,0xbe}, {0x1f,0xd1,0x34,0x62}, {0x8a,0xc4,0xa6,0xfe}, +{0x9d,0x34,0x2e,0x53}, {0xa0,0xa2,0xf3,0x55}, {0x32,0x05,0x8a,0xe1}, {0x75,0xa4,0xf6,0xeb}, +{0x39,0x0b,0x83,0xec}, {0xaa,0x40,0x60,0xef}, {0x06,0x5e,0x71,0x9f}, {0x51,0xbd,0x6e,0x10}, +{0xf9,0x3e,0x21,0x8a}, {0x3d,0x96,0xdd,0x06}, {0xae,0xdd,0x3e,0x05}, {0x46,0x4d,0xe6,0xbd}, +{0xb5,0x91,0x54,0x8d}, {0x05,0x71,0xc4,0x5d}, {0x6f,0x04,0x06,0xd4}, {0xff,0x60,0x50,0x15}, +{0x24,0x19,0x98,0xfb}, {0x97,0xd6,0xbd,0xe9}, {0xcc,0x89,0x40,0x43}, {0x77,0x67,0xd9,0x9e}, +{0xbd,0xb0,0xe8,0x42}, {0x88,0x07,0x89,0x8b}, {0x38,0xe7,0x19,0x5b}, {0xdb,0x79,0xc8,0xee}, +{0x47,0xa1,0x7c,0x0a}, {0xe9,0x7c,0x42,0x0f}, {0xc9,0xf8,0x84,0x1e}, {0x00,0x00,0x00,0x00}, +{0x83,0x09,0x80,0x86}, {0x48,0x32,0x2b,0xed}, {0xac,0x1e,0x11,0x70}, {0x4e,0x6c,0x5a,0x72}, +{0xfb,0xfd,0x0e,0xff}, {0x56,0x0f,0x85,0x38}, {0x1e,0x3d,0xae,0xd5}, {0x27,0x36,0x2d,0x39}, +{0x64,0x0a,0x0f,0xd9}, {0x21,0x68,0x5c,0xa6}, {0xd1,0x9b,0x5b,0x54}, {0x3a,0x24,0x36,0x2e}, +{0xb1,0x0c,0x0a,0x67}, {0x0f,0x93,0x57,0xe7}, {0xd2,0xb4,0xee,0x96}, {0x9e,0x1b,0x9b,0x91}, +{0x4f,0x80,0xc0,0xc5}, {0xa2,0x61,0xdc,0x20}, {0x69,0x5a,0x77,0x4b}, {0x16,0x1c,0x12,0x1a}, +{0x0a,0xe2,0x93,0xba}, {0xe5,0xc0,0xa0,0x2a}, {0x43,0x3c,0x22,0xe0}, {0x1d,0x12,0x1b,0x17}, +{0x0b,0x0e,0x09,0x0d}, {0xad,0xf2,0x8b,0xc7}, {0xb9,0x2d,0xb6,0xa8}, {0xc8,0x14,0x1e,0xa9}, +{0x85,0x57,0xf1,0x19}, {0x4c,0xaf,0x75,0x07}, {0xbb,0xee,0x99,0xdd}, {0xfd,0xa3,0x7f,0x60}, +{0x9f,0xf7,0x01,0x26}, {0xbc,0x5c,0x72,0xf5}, {0xc5,0x44,0x66,0x3b}, {0x34,0x5b,0xfb,0x7e}, +{0x76,0x8b,0x43,0x29}, {0xdc,0xcb,0x23,0xc6}, {0x68,0xb6,0xed,0xfc}, {0x63,0xb8,0xe4,0xf1}, +{0xca,0xd7,0x31,0xdc}, {0x10,0x42,0x63,0x85}, {0x40,0x13,0x97,0x22}, {0x20,0x84,0xc6,0x11}, +{0x7d,0x85,0x4a,0x24}, {0xf8,0xd2,0xbb,0x3d}, {0x11,0xae,0xf9,0x32}, {0x6d,0xc7,0x29,0xa1}, +{0x4b,0x1d,0x9e,0x2f}, {0xf3,0xdc,0xb2,0x30}, {0xec,0x0d,0x86,0x52}, {0xd0,0x77,0xc1,0xe3}, +{0x6c,0x2b,0xb3,0x16}, {0x99,0xa9,0x70,0xb9}, {0xfa,0x11,0x94,0x48}, {0x22,0x47,0xe9,0x64}, +{0xc4,0xa8,0xfc,0x8c}, {0x1a,0xa0,0xf0,0x3f}, {0xd8,0x56,0x7d,0x2c}, {0xef,0x22,0x33,0x90}, +{0xc7,0x87,0x49,0x4e}, {0xc1,0xd9,0x38,0xd1}, {0xfe,0x8c,0xca,0xa2}, {0x36,0x98,0xd4,0x0b}, +{0xcf,0xa6,0xf5,0x81}, {0x28,0xa5,0x7a,0xde}, {0x26,0xda,0xb7,0x8e}, {0xa4,0x3f,0xad,0xbf}, +{0xe4,0x2c,0x3a,0x9d}, {0x0d,0x50,0x78,0x92}, {0x9b,0x6a,0x5f,0xcc}, {0x62,0x54,0x7e,0x46}, +{0xc2,0xf6,0x8d,0x13}, {0xe8,0x90,0xd8,0xb8}, {0x5e,0x2e,0x39,0xf7}, {0xf5,0x82,0xc3,0xaf}, +{0xbe,0x9f,0x5d,0x80}, {0x7c,0x69,0xd0,0x93}, {0xa9,0x6f,0xd5,0x2d}, {0xb3,0xcf,0x25,0x12}, +{0x3b,0xc8,0xac,0x99}, {0xa7,0x10,0x18,0x7d}, {0x6e,0xe8,0x9c,0x63}, {0x7b,0xdb,0x3b,0xbb}, +{0x09,0xcd,0x26,0x78}, {0xf4,0x6e,0x59,0x18}, {0x01,0xec,0x9a,0xb7}, {0xa8,0x83,0x4f,0x9a}, +{0x65,0xe6,0x95,0x6e}, {0x7e,0xaa,0xff,0xe6}, {0x08,0x21,0xbc,0xcf}, {0xe6,0xef,0x15,0xe8}, +{0xd9,0xba,0xe7,0x9b}, {0xce,0x4a,0x6f,0x36}, {0xd4,0xea,0x9f,0x09}, {0xd6,0x29,0xb0,0x7c}, +{0xaf,0x31,0xa4,0xb2}, {0x31,0x2a,0x3f,0x23}, {0x30,0xc6,0xa5,0x94}, {0xc0,0x35,0xa2,0x66}, +{0x37,0x74,0x4e,0xbc}, {0xa6,0xfc,0x82,0xca}, {0xb0,0xe0,0x90,0xd0}, {0x15,0x33,0xa7,0xd8}, +{0x4a,0xf1,0x04,0x98}, {0xf7,0x41,0xec,0xda}, {0x0e,0x7f,0xcd,0x50}, {0x2f,0x17,0x91,0xf6}, +{0x8d,0x76,0x4d,0xd6}, {0x4d,0x43,0xef,0xb0}, {0x54,0xcc,0xaa,0x4d}, {0xdf,0xe4,0x96,0x04}, +{0xe3,0x9e,0xd1,0xb5}, {0x1b,0x4c,0x6a,0x88}, {0xb8,0xc1,0x2c,0x1f}, {0x7f,0x46,0x65,0x51}, +{0x04,0x9d,0x5e,0xea}, {0x5d,0x01,0x8c,0x35}, {0x73,0xfa,0x87,0x74}, {0x2e,0xfb,0x0b,0x41}, +{0x5a,0xb3,0x67,0x1d}, {0x52,0x92,0xdb,0xd2}, {0x33,0xe9,0x10,0x56}, {0x13,0x6d,0xd6,0x47}, +{0x8c,0x9a,0xd7,0x61}, {0x7a,0x37,0xa1,0x0c}, {0x8e,0x59,0xf8,0x14}, {0x89,0xeb,0x13,0x3c}, +{0xee,0xce,0xa9,0x27}, {0x35,0xb7,0x61,0xc9}, {0xed,0xe1,0x1c,0xe5}, {0x3c,0x7a,0x47,0xb1}, +{0x59,0x9c,0xd2,0xdf}, {0x3f,0x55,0xf2,0x73}, {0x79,0x18,0x14,0xce}, {0xbf,0x73,0xc7,0x37}, +{0xea,0x53,0xf7,0xcd}, {0x5b,0x5f,0xfd,0xaa}, {0x14,0xdf,0x3d,0x6f}, {0x86,0x78,0x44,0xdb}, +{0x81,0xca,0xaf,0xf3}, {0x3e,0xb9,0x68,0xc4}, {0x2c,0x38,0x24,0x34}, {0x5f,0xc2,0xa3,0x40}, +{0x72,0x16,0x1d,0xc3}, {0x0c,0xbc,0xe2,0x25}, {0x8b,0x28,0x3c,0x49}, {0x41,0xff,0x0d,0x95}, +{0x71,0x39,0xa8,0x01}, {0xde,0x08,0x0c,0xb3}, {0x9c,0xd8,0xb4,0xe4}, {0x90,0x64,0x56,0xc1}, +{0x61,0x7b,0xcb,0x84}, {0x70,0xd5,0x32,0xb6}, {0x74,0x48,0x6c,0x5c}, {0x42,0xd0,0xb8,0x57} +}; + +word8 T7[256][4] = { +{0xa7,0x50,0x51,0xf4}, {0x65,0x53,0x7e,0x41}, {0xa4,0xc3,0x1a,0x17}, {0x5e,0x96,0x3a,0x27}, +{0x6b,0xcb,0x3b,0xab}, {0x45,0xf1,0x1f,0x9d}, {0x58,0xab,0xac,0xfa}, {0x03,0x93,0x4b,0xe3}, +{0xfa,0x55,0x20,0x30}, {0x6d,0xf6,0xad,0x76}, {0x76,0x91,0x88,0xcc}, {0x4c,0x25,0xf5,0x02}, +{0xd7,0xfc,0x4f,0xe5}, {0xcb,0xd7,0xc5,0x2a}, {0x44,0x80,0x26,0x35}, {0xa3,0x8f,0xb5,0x62}, +{0x5a,0x49,0xde,0xb1}, {0x1b,0x67,0x25,0xba}, {0x0e,0x98,0x45,0xea}, {0xc0,0xe1,0x5d,0xfe}, +{0x75,0x02,0xc3,0x2f}, {0xf0,0x12,0x81,0x4c}, {0x97,0xa3,0x8d,0x46}, {0xf9,0xc6,0x6b,0xd3}, +{0x5f,0xe7,0x03,0x8f}, {0x9c,0x95,0x15,0x92}, {0x7a,0xeb,0xbf,0x6d}, {0x59,0xda,0x95,0x52}, +{0x83,0x2d,0xd4,0xbe}, {0x21,0xd3,0x58,0x74}, {0x69,0x29,0x49,0xe0}, {0xc8,0x44,0x8e,0xc9}, +{0x89,0x6a,0x75,0xc2}, {0x79,0x78,0xf4,0x8e}, {0x3e,0x6b,0x99,0x58}, {0x71,0xdd,0x27,0xb9}, +{0x4f,0xb6,0xbe,0xe1}, {0xad,0x17,0xf0,0x88}, {0xac,0x66,0xc9,0x20}, {0x3a,0xb4,0x7d,0xce}, +{0x4a,0x18,0x63,0xdf}, {0x31,0x82,0xe5,0x1a}, {0x33,0x60,0x97,0x51}, {0x7f,0x45,0x62,0x53}, +{0x77,0xe0,0xb1,0x64}, {0xae,0x84,0xbb,0x6b}, {0xa0,0x1c,0xfe,0x81}, {0x2b,0x94,0xf9,0x08}, +{0x68,0x58,0x70,0x48}, {0xfd,0x19,0x8f,0x45}, {0x6c,0x87,0x94,0xde}, {0xf8,0xb7,0x52,0x7b}, +{0xd3,0x23,0xab,0x73}, {0x02,0xe2,0x72,0x4b}, {0x8f,0x57,0xe3,0x1f}, {0xab,0x2a,0x66,0x55}, +{0x28,0x07,0xb2,0xeb}, {0xc2,0x03,0x2f,0xb5}, {0x7b,0x9a,0x86,0xc5}, {0x08,0xa5,0xd3,0x37}, +{0x87,0xf2,0x30,0x28}, {0xa5,0xb2,0x23,0xbf}, {0x6a,0xba,0x02,0x03}, {0x82,0x5c,0xed,0x16}, +{0x1c,0x2b,0x8a,0xcf}, {0xb4,0x92,0xa7,0x79}, {0xf2,0xf0,0xf3,0x07}, {0xe2,0xa1,0x4e,0x69}, +{0xf4,0xcd,0x65,0xda}, {0xbe,0xd5,0x06,0x05}, {0x62,0x1f,0xd1,0x34}, {0xfe,0x8a,0xc4,0xa6}, +{0x53,0x9d,0x34,0x2e}, {0x55,0xa0,0xa2,0xf3}, {0xe1,0x32,0x05,0x8a}, {0xeb,0x75,0xa4,0xf6}, +{0xec,0x39,0x0b,0x83}, {0xef,0xaa,0x40,0x60}, {0x9f,0x06,0x5e,0x71}, {0x10,0x51,0xbd,0x6e}, +{0x8a,0xf9,0x3e,0x21}, {0x06,0x3d,0x96,0xdd}, {0x05,0xae,0xdd,0x3e}, {0xbd,0x46,0x4d,0xe6}, +{0x8d,0xb5,0x91,0x54}, {0x5d,0x05,0x71,0xc4}, {0xd4,0x6f,0x04,0x06}, {0x15,0xff,0x60,0x50}, +{0xfb,0x24,0x19,0x98}, {0xe9,0x97,0xd6,0xbd}, {0x43,0xcc,0x89,0x40}, {0x9e,0x77,0x67,0xd9}, +{0x42,0xbd,0xb0,0xe8}, {0x8b,0x88,0x07,0x89}, {0x5b,0x38,0xe7,0x19}, {0xee,0xdb,0x79,0xc8}, +{0x0a,0x47,0xa1,0x7c}, {0x0f,0xe9,0x7c,0x42}, {0x1e,0xc9,0xf8,0x84}, {0x00,0x00,0x00,0x00}, +{0x86,0x83,0x09,0x80}, {0xed,0x48,0x32,0x2b}, {0x70,0xac,0x1e,0x11}, {0x72,0x4e,0x6c,0x5a}, +{0xff,0xfb,0xfd,0x0e}, {0x38,0x56,0x0f,0x85}, {0xd5,0x1e,0x3d,0xae}, {0x39,0x27,0x36,0x2d}, +{0xd9,0x64,0x0a,0x0f}, {0xa6,0x21,0x68,0x5c}, {0x54,0xd1,0x9b,0x5b}, {0x2e,0x3a,0x24,0x36}, +{0x67,0xb1,0x0c,0x0a}, {0xe7,0x0f,0x93,0x57}, {0x96,0xd2,0xb4,0xee}, {0x91,0x9e,0x1b,0x9b}, +{0xc5,0x4f,0x80,0xc0}, {0x20,0xa2,0x61,0xdc}, {0x4b,0x69,0x5a,0x77}, {0x1a,0x16,0x1c,0x12}, +{0xba,0x0a,0xe2,0x93}, {0x2a,0xe5,0xc0,0xa0}, {0xe0,0x43,0x3c,0x22}, {0x17,0x1d,0x12,0x1b}, +{0x0d,0x0b,0x0e,0x09}, {0xc7,0xad,0xf2,0x8b}, {0xa8,0xb9,0x2d,0xb6}, {0xa9,0xc8,0x14,0x1e}, +{0x19,0x85,0x57,0xf1}, {0x07,0x4c,0xaf,0x75}, {0xdd,0xbb,0xee,0x99}, {0x60,0xfd,0xa3,0x7f}, +{0x26,0x9f,0xf7,0x01}, {0xf5,0xbc,0x5c,0x72}, {0x3b,0xc5,0x44,0x66}, {0x7e,0x34,0x5b,0xfb}, +{0x29,0x76,0x8b,0x43}, {0xc6,0xdc,0xcb,0x23}, {0xfc,0x68,0xb6,0xed}, {0xf1,0x63,0xb8,0xe4}, +{0xdc,0xca,0xd7,0x31}, {0x85,0x10,0x42,0x63}, {0x22,0x40,0x13,0x97}, {0x11,0x20,0x84,0xc6}, +{0x24,0x7d,0x85,0x4a}, {0x3d,0xf8,0xd2,0xbb}, {0x32,0x11,0xae,0xf9}, {0xa1,0x6d,0xc7,0x29}, +{0x2f,0x4b,0x1d,0x9e}, {0x30,0xf3,0xdc,0xb2}, {0x52,0xec,0x0d,0x86}, {0xe3,0xd0,0x77,0xc1}, +{0x16,0x6c,0x2b,0xb3}, {0xb9,0x99,0xa9,0x70}, {0x48,0xfa,0x11,0x94}, {0x64,0x22,0x47,0xe9}, +{0x8c,0xc4,0xa8,0xfc}, {0x3f,0x1a,0xa0,0xf0}, {0x2c,0xd8,0x56,0x7d}, {0x90,0xef,0x22,0x33}, +{0x4e,0xc7,0x87,0x49}, {0xd1,0xc1,0xd9,0x38}, {0xa2,0xfe,0x8c,0xca}, {0x0b,0x36,0x98,0xd4}, +{0x81,0xcf,0xa6,0xf5}, {0xde,0x28,0xa5,0x7a}, {0x8e,0x26,0xda,0xb7}, {0xbf,0xa4,0x3f,0xad}, +{0x9d,0xe4,0x2c,0x3a}, {0x92,0x0d,0x50,0x78}, {0xcc,0x9b,0x6a,0x5f}, {0x46,0x62,0x54,0x7e}, +{0x13,0xc2,0xf6,0x8d}, {0xb8,0xe8,0x90,0xd8}, {0xf7,0x5e,0x2e,0x39}, {0xaf,0xf5,0x82,0xc3}, +{0x80,0xbe,0x9f,0x5d}, {0x93,0x7c,0x69,0xd0}, {0x2d,0xa9,0x6f,0xd5}, {0x12,0xb3,0xcf,0x25}, +{0x99,0x3b,0xc8,0xac}, {0x7d,0xa7,0x10,0x18}, {0x63,0x6e,0xe8,0x9c}, {0xbb,0x7b,0xdb,0x3b}, +{0x78,0x09,0xcd,0x26}, {0x18,0xf4,0x6e,0x59}, {0xb7,0x01,0xec,0x9a}, {0x9a,0xa8,0x83,0x4f}, +{0x6e,0x65,0xe6,0x95}, {0xe6,0x7e,0xaa,0xff}, {0xcf,0x08,0x21,0xbc}, {0xe8,0xe6,0xef,0x15}, +{0x9b,0xd9,0xba,0xe7}, {0x36,0xce,0x4a,0x6f}, {0x09,0xd4,0xea,0x9f}, {0x7c,0xd6,0x29,0xb0}, +{0xb2,0xaf,0x31,0xa4}, {0x23,0x31,0x2a,0x3f}, {0x94,0x30,0xc6,0xa5}, {0x66,0xc0,0x35,0xa2}, +{0xbc,0x37,0x74,0x4e}, {0xca,0xa6,0xfc,0x82}, {0xd0,0xb0,0xe0,0x90}, {0xd8,0x15,0x33,0xa7}, +{0x98,0x4a,0xf1,0x04}, {0xda,0xf7,0x41,0xec}, {0x50,0x0e,0x7f,0xcd}, {0xf6,0x2f,0x17,0x91}, +{0xd6,0x8d,0x76,0x4d}, {0xb0,0x4d,0x43,0xef}, {0x4d,0x54,0xcc,0xaa}, {0x04,0xdf,0xe4,0x96}, +{0xb5,0xe3,0x9e,0xd1}, {0x88,0x1b,0x4c,0x6a}, {0x1f,0xb8,0xc1,0x2c}, {0x51,0x7f,0x46,0x65}, +{0xea,0x04,0x9d,0x5e}, {0x35,0x5d,0x01,0x8c}, {0x74,0x73,0xfa,0x87}, {0x41,0x2e,0xfb,0x0b}, +{0x1d,0x5a,0xb3,0x67}, {0xd2,0x52,0x92,0xdb}, {0x56,0x33,0xe9,0x10}, {0x47,0x13,0x6d,0xd6}, +{0x61,0x8c,0x9a,0xd7}, {0x0c,0x7a,0x37,0xa1}, {0x14,0x8e,0x59,0xf8}, {0x3c,0x89,0xeb,0x13}, +{0x27,0xee,0xce,0xa9}, {0xc9,0x35,0xb7,0x61}, {0xe5,0xed,0xe1,0x1c}, {0xb1,0x3c,0x7a,0x47}, +{0xdf,0x59,0x9c,0xd2}, {0x73,0x3f,0x55,0xf2}, {0xce,0x79,0x18,0x14}, {0x37,0xbf,0x73,0xc7}, +{0xcd,0xea,0x53,0xf7}, {0xaa,0x5b,0x5f,0xfd}, {0x6f,0x14,0xdf,0x3d}, {0xdb,0x86,0x78,0x44}, +{0xf3,0x81,0xca,0xaf}, {0xc4,0x3e,0xb9,0x68}, {0x34,0x2c,0x38,0x24}, {0x40,0x5f,0xc2,0xa3}, +{0xc3,0x72,0x16,0x1d}, {0x25,0x0c,0xbc,0xe2}, {0x49,0x8b,0x28,0x3c}, {0x95,0x41,0xff,0x0d}, +{0x01,0x71,0x39,0xa8}, {0xb3,0xde,0x08,0x0c}, {0xe4,0x9c,0xd8,0xb4}, {0xc1,0x90,0x64,0x56}, +{0x84,0x61,0x7b,0xcb}, {0xb6,0x70,0xd5,0x32}, {0x5c,0x74,0x48,0x6c}, {0x57,0x42,0xd0,0xb8} +}; + +word8 T8[256][4] = { +{0xf4,0xa7,0x50,0x51}, {0x41,0x65,0x53,0x7e}, {0x17,0xa4,0xc3,0x1a}, {0x27,0x5e,0x96,0x3a}, +{0xab,0x6b,0xcb,0x3b}, {0x9d,0x45,0xf1,0x1f}, {0xfa,0x58,0xab,0xac}, {0xe3,0x03,0x93,0x4b}, +{0x30,0xfa,0x55,0x20}, {0x76,0x6d,0xf6,0xad}, {0xcc,0x76,0x91,0x88}, {0x02,0x4c,0x25,0xf5}, +{0xe5,0xd7,0xfc,0x4f}, {0x2a,0xcb,0xd7,0xc5}, {0x35,0x44,0x80,0x26}, {0x62,0xa3,0x8f,0xb5}, +{0xb1,0x5a,0x49,0xde}, {0xba,0x1b,0x67,0x25}, {0xea,0x0e,0x98,0x45}, {0xfe,0xc0,0xe1,0x5d}, +{0x2f,0x75,0x02,0xc3}, {0x4c,0xf0,0x12,0x81}, {0x46,0x97,0xa3,0x8d}, {0xd3,0xf9,0xc6,0x6b}, +{0x8f,0x5f,0xe7,0x03}, {0x92,0x9c,0x95,0x15}, {0x6d,0x7a,0xeb,0xbf}, {0x52,0x59,0xda,0x95}, +{0xbe,0x83,0x2d,0xd4}, {0x74,0x21,0xd3,0x58}, {0xe0,0x69,0x29,0x49}, {0xc9,0xc8,0x44,0x8e}, +{0xc2,0x89,0x6a,0x75}, {0x8e,0x79,0x78,0xf4}, {0x58,0x3e,0x6b,0x99}, {0xb9,0x71,0xdd,0x27}, +{0xe1,0x4f,0xb6,0xbe}, {0x88,0xad,0x17,0xf0}, {0x20,0xac,0x66,0xc9}, {0xce,0x3a,0xb4,0x7d}, +{0xdf,0x4a,0x18,0x63}, {0x1a,0x31,0x82,0xe5}, {0x51,0x33,0x60,0x97}, {0x53,0x7f,0x45,0x62}, +{0x64,0x77,0xe0,0xb1}, {0x6b,0xae,0x84,0xbb}, {0x81,0xa0,0x1c,0xfe}, {0x08,0x2b,0x94,0xf9}, +{0x48,0x68,0x58,0x70}, {0x45,0xfd,0x19,0x8f}, {0xde,0x6c,0x87,0x94}, {0x7b,0xf8,0xb7,0x52}, +{0x73,0xd3,0x23,0xab}, {0x4b,0x02,0xe2,0x72}, {0x1f,0x8f,0x57,0xe3}, {0x55,0xab,0x2a,0x66}, +{0xeb,0x28,0x07,0xb2}, {0xb5,0xc2,0x03,0x2f}, {0xc5,0x7b,0x9a,0x86}, {0x37,0x08,0xa5,0xd3}, +{0x28,0x87,0xf2,0x30}, {0xbf,0xa5,0xb2,0x23}, {0x03,0x6a,0xba,0x02}, {0x16,0x82,0x5c,0xed}, +{0xcf,0x1c,0x2b,0x8a}, {0x79,0xb4,0x92,0xa7}, {0x07,0xf2,0xf0,0xf3}, {0x69,0xe2,0xa1,0x4e}, +{0xda,0xf4,0xcd,0x65}, {0x05,0xbe,0xd5,0x06}, {0x34,0x62,0x1f,0xd1}, {0xa6,0xfe,0x8a,0xc4}, +{0x2e,0x53,0x9d,0x34}, {0xf3,0x55,0xa0,0xa2}, {0x8a,0xe1,0x32,0x05}, {0xf6,0xeb,0x75,0xa4}, +{0x83,0xec,0x39,0x0b}, {0x60,0xef,0xaa,0x40}, {0x71,0x9f,0x06,0x5e}, {0x6e,0x10,0x51,0xbd}, +{0x21,0x8a,0xf9,0x3e}, {0xdd,0x06,0x3d,0x96}, {0x3e,0x05,0xae,0xdd}, {0xe6,0xbd,0x46,0x4d}, +{0x54,0x8d,0xb5,0x91}, {0xc4,0x5d,0x05,0x71}, {0x06,0xd4,0x6f,0x04}, {0x50,0x15,0xff,0x60}, +{0x98,0xfb,0x24,0x19}, {0xbd,0xe9,0x97,0xd6}, {0x40,0x43,0xcc,0x89}, {0xd9,0x9e,0x77,0x67}, +{0xe8,0x42,0xbd,0xb0}, {0x89,0x8b,0x88,0x07}, {0x19,0x5b,0x38,0xe7}, {0xc8,0xee,0xdb,0x79}, +{0x7c,0x0a,0x47,0xa1}, {0x42,0x0f,0xe9,0x7c}, {0x84,0x1e,0xc9,0xf8}, {0x00,0x00,0x00,0x00}, +{0x80,0x86,0x83,0x09}, {0x2b,0xed,0x48,0x32}, {0x11,0x70,0xac,0x1e}, {0x5a,0x72,0x4e,0x6c}, +{0x0e,0xff,0xfb,0xfd}, {0x85,0x38,0x56,0x0f}, {0xae,0xd5,0x1e,0x3d}, {0x2d,0x39,0x27,0x36}, +{0x0f,0xd9,0x64,0x0a}, {0x5c,0xa6,0x21,0x68}, {0x5b,0x54,0xd1,0x9b}, {0x36,0x2e,0x3a,0x24}, +{0x0a,0x67,0xb1,0x0c}, {0x57,0xe7,0x0f,0x93}, {0xee,0x96,0xd2,0xb4}, {0x9b,0x91,0x9e,0x1b}, +{0xc0,0xc5,0x4f,0x80}, {0xdc,0x20,0xa2,0x61}, {0x77,0x4b,0x69,0x5a}, {0x12,0x1a,0x16,0x1c}, +{0x93,0xba,0x0a,0xe2}, {0xa0,0x2a,0xe5,0xc0}, {0x22,0xe0,0x43,0x3c}, {0x1b,0x17,0x1d,0x12}, +{0x09,0x0d,0x0b,0x0e}, {0x8b,0xc7,0xad,0xf2}, {0xb6,0xa8,0xb9,0x2d}, {0x1e,0xa9,0xc8,0x14}, +{0xf1,0x19,0x85,0x57}, {0x75,0x07,0x4c,0xaf}, {0x99,0xdd,0xbb,0xee}, {0x7f,0x60,0xfd,0xa3}, +{0x01,0x26,0x9f,0xf7}, {0x72,0xf5,0xbc,0x5c}, {0x66,0x3b,0xc5,0x44}, {0xfb,0x7e,0x34,0x5b}, +{0x43,0x29,0x76,0x8b}, {0x23,0xc6,0xdc,0xcb}, {0xed,0xfc,0x68,0xb6}, {0xe4,0xf1,0x63,0xb8}, +{0x31,0xdc,0xca,0xd7}, {0x63,0x85,0x10,0x42}, {0x97,0x22,0x40,0x13}, {0xc6,0x11,0x20,0x84}, +{0x4a,0x24,0x7d,0x85}, {0xbb,0x3d,0xf8,0xd2}, {0xf9,0x32,0x11,0xae}, {0x29,0xa1,0x6d,0xc7}, +{0x9e,0x2f,0x4b,0x1d}, {0xb2,0x30,0xf3,0xdc}, {0x86,0x52,0xec,0x0d}, {0xc1,0xe3,0xd0,0x77}, +{0xb3,0x16,0x6c,0x2b}, {0x70,0xb9,0x99,0xa9}, {0x94,0x48,0xfa,0x11}, {0xe9,0x64,0x22,0x47}, +{0xfc,0x8c,0xc4,0xa8}, {0xf0,0x3f,0x1a,0xa0}, {0x7d,0x2c,0xd8,0x56}, {0x33,0x90,0xef,0x22}, +{0x49,0x4e,0xc7,0x87}, {0x38,0xd1,0xc1,0xd9}, {0xca,0xa2,0xfe,0x8c}, {0xd4,0x0b,0x36,0x98}, +{0xf5,0x81,0xcf,0xa6}, {0x7a,0xde,0x28,0xa5}, {0xb7,0x8e,0x26,0xda}, {0xad,0xbf,0xa4,0x3f}, +{0x3a,0x9d,0xe4,0x2c}, {0x78,0x92,0x0d,0x50}, {0x5f,0xcc,0x9b,0x6a}, {0x7e,0x46,0x62,0x54}, +{0x8d,0x13,0xc2,0xf6}, {0xd8,0xb8,0xe8,0x90}, {0x39,0xf7,0x5e,0x2e}, {0xc3,0xaf,0xf5,0x82}, +{0x5d,0x80,0xbe,0x9f}, {0xd0,0x93,0x7c,0x69}, {0xd5,0x2d,0xa9,0x6f}, {0x25,0x12,0xb3,0xcf}, +{0xac,0x99,0x3b,0xc8}, {0x18,0x7d,0xa7,0x10}, {0x9c,0x63,0x6e,0xe8}, {0x3b,0xbb,0x7b,0xdb}, +{0x26,0x78,0x09,0xcd}, {0x59,0x18,0xf4,0x6e}, {0x9a,0xb7,0x01,0xec}, {0x4f,0x9a,0xa8,0x83}, +{0x95,0x6e,0x65,0xe6}, {0xff,0xe6,0x7e,0xaa}, {0xbc,0xcf,0x08,0x21}, {0x15,0xe8,0xe6,0xef}, +{0xe7,0x9b,0xd9,0xba}, {0x6f,0x36,0xce,0x4a}, {0x9f,0x09,0xd4,0xea}, {0xb0,0x7c,0xd6,0x29}, +{0xa4,0xb2,0xaf,0x31}, {0x3f,0x23,0x31,0x2a}, {0xa5,0x94,0x30,0xc6}, {0xa2,0x66,0xc0,0x35}, +{0x4e,0xbc,0x37,0x74}, {0x82,0xca,0xa6,0xfc}, {0x90,0xd0,0xb0,0xe0}, {0xa7,0xd8,0x15,0x33}, +{0x04,0x98,0x4a,0xf1}, {0xec,0xda,0xf7,0x41}, {0xcd,0x50,0x0e,0x7f}, {0x91,0xf6,0x2f,0x17}, +{0x4d,0xd6,0x8d,0x76}, {0xef,0xb0,0x4d,0x43}, {0xaa,0x4d,0x54,0xcc}, {0x96,0x04,0xdf,0xe4}, +{0xd1,0xb5,0xe3,0x9e}, {0x6a,0x88,0x1b,0x4c}, {0x2c,0x1f,0xb8,0xc1}, {0x65,0x51,0x7f,0x46}, +{0x5e,0xea,0x04,0x9d}, {0x8c,0x35,0x5d,0x01}, {0x87,0x74,0x73,0xfa}, {0x0b,0x41,0x2e,0xfb}, +{0x67,0x1d,0x5a,0xb3}, {0xdb,0xd2,0x52,0x92}, {0x10,0x56,0x33,0xe9}, {0xd6,0x47,0x13,0x6d}, +{0xd7,0x61,0x8c,0x9a}, {0xa1,0x0c,0x7a,0x37}, {0xf8,0x14,0x8e,0x59}, {0x13,0x3c,0x89,0xeb}, +{0xa9,0x27,0xee,0xce}, {0x61,0xc9,0x35,0xb7}, {0x1c,0xe5,0xed,0xe1}, {0x47,0xb1,0x3c,0x7a}, +{0xd2,0xdf,0x59,0x9c}, {0xf2,0x73,0x3f,0x55}, {0x14,0xce,0x79,0x18}, {0xc7,0x37,0xbf,0x73}, +{0xf7,0xcd,0xea,0x53}, {0xfd,0xaa,0x5b,0x5f}, {0x3d,0x6f,0x14,0xdf}, {0x44,0xdb,0x86,0x78}, +{0xaf,0xf3,0x81,0xca}, {0x68,0xc4,0x3e,0xb9}, {0x24,0x34,0x2c,0x38}, {0xa3,0x40,0x5f,0xc2}, +{0x1d,0xc3,0x72,0x16}, {0xe2,0x25,0x0c,0xbc}, {0x3c,0x49,0x8b,0x28}, {0x0d,0x95,0x41,0xff}, +{0xa8,0x01,0x71,0x39}, {0x0c,0xb3,0xde,0x08}, {0xb4,0xe4,0x9c,0xd8}, {0x56,0xc1,0x90,0x64}, +{0xcb,0x84,0x61,0x7b}, {0x32,0xb6,0x70,0xd5}, {0x6c,0x5c,0x74,0x48}, {0xb8,0x57,0x42,0xd0} +}; + +word8 S5[256] = { +0x52,0x09,0x6a,0xd5, +0x30,0x36,0xa5,0x38, +0xbf,0x40,0xa3,0x9e, +0x81,0xf3,0xd7,0xfb, +0x7c,0xe3,0x39,0x82, +0x9b,0x2f,0xff,0x87, +0x34,0x8e,0x43,0x44, +0xc4,0xde,0xe9,0xcb, +0x54,0x7b,0x94,0x32, +0xa6,0xc2,0x23,0x3d, +0xee,0x4c,0x95,0x0b, +0x42,0xfa,0xc3,0x4e, +0x08,0x2e,0xa1,0x66, +0x28,0xd9,0x24,0xb2, +0x76,0x5b,0xa2,0x49, +0x6d,0x8b,0xd1,0x25, +0x72,0xf8,0xf6,0x64, +0x86,0x68,0x98,0x16, +0xd4,0xa4,0x5c,0xcc, +0x5d,0x65,0xb6,0x92, +0x6c,0x70,0x48,0x50, +0xfd,0xed,0xb9,0xda, +0x5e,0x15,0x46,0x57, +0xa7,0x8d,0x9d,0x84, +0x90,0xd8,0xab,0x00, +0x8c,0xbc,0xd3,0x0a, +0xf7,0xe4,0x58,0x05, +0xb8,0xb3,0x45,0x06, +0xd0,0x2c,0x1e,0x8f, +0xca,0x3f,0x0f,0x02, +0xc1,0xaf,0xbd,0x03, +0x01,0x13,0x8a,0x6b, +0x3a,0x91,0x11,0x41, +0x4f,0x67,0xdc,0xea, +0x97,0xf2,0xcf,0xce, +0xf0,0xb4,0xe6,0x73, +0x96,0xac,0x74,0x22, +0xe7,0xad,0x35,0x85, +0xe2,0xf9,0x37,0xe8, +0x1c,0x75,0xdf,0x6e, +0x47,0xf1,0x1a,0x71, +0x1d,0x29,0xc5,0x89, +0x6f,0xb7,0x62,0x0e, +0xaa,0x18,0xbe,0x1b, +0xfc,0x56,0x3e,0x4b, +0xc6,0xd2,0x79,0x20, +0x9a,0xdb,0xc0,0xfe, +0x78,0xcd,0x5a,0xf4, +0x1f,0xdd,0xa8,0x33, +0x88,0x07,0xc7,0x31, +0xb1,0x12,0x10,0x59, +0x27,0x80,0xec,0x5f, +0x60,0x51,0x7f,0xa9, +0x19,0xb5,0x4a,0x0d, +0x2d,0xe5,0x7a,0x9f, +0x93,0xc9,0x9c,0xef, +0xa0,0xe0,0x3b,0x4d, +0xae,0x2a,0xf5,0xb0, +0xc8,0xeb,0xbb,0x3c, +0x83,0x53,0x99,0x61, +0x17,0x2b,0x04,0x7e, +0xba,0x77,0xd6,0x26, +0xe1,0x69,0x14,0x63, +0x55,0x21,0x0c,0x7d +}; + +word8 U1[256][4] = { +{0x00,0x00,0x00,0x00}, {0x0e,0x09,0x0d,0x0b}, {0x1c,0x12,0x1a,0x16}, {0x12,0x1b,0x17,0x1d}, +{0x38,0x24,0x34,0x2c}, {0x36,0x2d,0x39,0x27}, {0x24,0x36,0x2e,0x3a}, {0x2a,0x3f,0x23,0x31}, +{0x70,0x48,0x68,0x58}, {0x7e,0x41,0x65,0x53}, {0x6c,0x5a,0x72,0x4e}, {0x62,0x53,0x7f,0x45}, +{0x48,0x6c,0x5c,0x74}, {0x46,0x65,0x51,0x7f}, {0x54,0x7e,0x46,0x62}, {0x5a,0x77,0x4b,0x69}, +{0xe0,0x90,0xd0,0xb0}, {0xee,0x99,0xdd,0xbb}, {0xfc,0x82,0xca,0xa6}, {0xf2,0x8b,0xc7,0xad}, +{0xd8,0xb4,0xe4,0x9c}, {0xd6,0xbd,0xe9,0x97}, {0xc4,0xa6,0xfe,0x8a}, {0xca,0xaf,0xf3,0x81}, +{0x90,0xd8,0xb8,0xe8}, {0x9e,0xd1,0xb5,0xe3}, {0x8c,0xca,0xa2,0xfe}, {0x82,0xc3,0xaf,0xf5}, +{0xa8,0xfc,0x8c,0xc4}, {0xa6,0xf5,0x81,0xcf}, {0xb4,0xee,0x96,0xd2}, {0xba,0xe7,0x9b,0xd9}, +{0xdb,0x3b,0xbb,0x7b}, {0xd5,0x32,0xb6,0x70}, {0xc7,0x29,0xa1,0x6d}, {0xc9,0x20,0xac,0x66}, +{0xe3,0x1f,0x8f,0x57}, {0xed,0x16,0x82,0x5c}, {0xff,0x0d,0x95,0x41}, {0xf1,0x04,0x98,0x4a}, +{0xab,0x73,0xd3,0x23}, {0xa5,0x7a,0xde,0x28}, {0xb7,0x61,0xc9,0x35}, {0xb9,0x68,0xc4,0x3e}, +{0x93,0x57,0xe7,0x0f}, {0x9d,0x5e,0xea,0x04}, {0x8f,0x45,0xfd,0x19}, {0x81,0x4c,0xf0,0x12}, +{0x3b,0xab,0x6b,0xcb}, {0x35,0xa2,0x66,0xc0}, {0x27,0xb9,0x71,0xdd}, {0x29,0xb0,0x7c,0xd6}, +{0x03,0x8f,0x5f,0xe7}, {0x0d,0x86,0x52,0xec}, {0x1f,0x9d,0x45,0xf1}, {0x11,0x94,0x48,0xfa}, +{0x4b,0xe3,0x03,0x93}, {0x45,0xea,0x0e,0x98}, {0x57,0xf1,0x19,0x85}, {0x59,0xf8,0x14,0x8e}, +{0x73,0xc7,0x37,0xbf}, {0x7d,0xce,0x3a,0xb4}, {0x6f,0xd5,0x2d,0xa9}, {0x61,0xdc,0x20,0xa2}, +{0xad,0x76,0x6d,0xf6}, {0xa3,0x7f,0x60,0xfd}, {0xb1,0x64,0x77,0xe0}, {0xbf,0x6d,0x7a,0xeb}, +{0x95,0x52,0x59,0xda}, {0x9b,0x5b,0x54,0xd1}, {0x89,0x40,0x43,0xcc}, {0x87,0x49,0x4e,0xc7}, +{0xdd,0x3e,0x05,0xae}, {0xd3,0x37,0x08,0xa5}, {0xc1,0x2c,0x1f,0xb8}, {0xcf,0x25,0x12,0xb3}, +{0xe5,0x1a,0x31,0x82}, {0xeb,0x13,0x3c,0x89}, {0xf9,0x08,0x2b,0x94}, {0xf7,0x01,0x26,0x9f}, +{0x4d,0xe6,0xbd,0x46}, {0x43,0xef,0xb0,0x4d}, {0x51,0xf4,0xa7,0x50}, {0x5f,0xfd,0xaa,0x5b}, +{0x75,0xc2,0x89,0x6a}, {0x7b,0xcb,0x84,0x61}, {0x69,0xd0,0x93,0x7c}, {0x67,0xd9,0x9e,0x77}, +{0x3d,0xae,0xd5,0x1e}, {0x33,0xa7,0xd8,0x15}, {0x21,0xbc,0xcf,0x08}, {0x2f,0xb5,0xc2,0x03}, +{0x05,0x8a,0xe1,0x32}, {0x0b,0x83,0xec,0x39}, {0x19,0x98,0xfb,0x24}, {0x17,0x91,0xf6,0x2f}, +{0x76,0x4d,0xd6,0x8d}, {0x78,0x44,0xdb,0x86}, {0x6a,0x5f,0xcc,0x9b}, {0x64,0x56,0xc1,0x90}, +{0x4e,0x69,0xe2,0xa1}, {0x40,0x60,0xef,0xaa}, {0x52,0x7b,0xf8,0xb7}, {0x5c,0x72,0xf5,0xbc}, +{0x06,0x05,0xbe,0xd5}, {0x08,0x0c,0xb3,0xde}, {0x1a,0x17,0xa4,0xc3}, {0x14,0x1e,0xa9,0xc8}, +{0x3e,0x21,0x8a,0xf9}, {0x30,0x28,0x87,0xf2}, {0x22,0x33,0x90,0xef}, {0x2c,0x3a,0x9d,0xe4}, +{0x96,0xdd,0x06,0x3d}, {0x98,0xd4,0x0b,0x36}, {0x8a,0xcf,0x1c,0x2b}, {0x84,0xc6,0x11,0x20}, +{0xae,0xf9,0x32,0x11}, {0xa0,0xf0,0x3f,0x1a}, {0xb2,0xeb,0x28,0x07}, {0xbc,0xe2,0x25,0x0c}, +{0xe6,0x95,0x6e,0x65}, {0xe8,0x9c,0x63,0x6e}, {0xfa,0x87,0x74,0x73}, {0xf4,0x8e,0x79,0x78}, +{0xde,0xb1,0x5a,0x49}, {0xd0,0xb8,0x57,0x42}, {0xc2,0xa3,0x40,0x5f}, {0xcc,0xaa,0x4d,0x54}, +{0x41,0xec,0xda,0xf7}, {0x4f,0xe5,0xd7,0xfc}, {0x5d,0xfe,0xc0,0xe1}, {0x53,0xf7,0xcd,0xea}, +{0x79,0xc8,0xee,0xdb}, {0x77,0xc1,0xe3,0xd0}, {0x65,0xda,0xf4,0xcd}, {0x6b,0xd3,0xf9,0xc6}, +{0x31,0xa4,0xb2,0xaf}, {0x3f,0xad,0xbf,0xa4}, {0x2d,0xb6,0xa8,0xb9}, {0x23,0xbf,0xa5,0xb2}, +{0x09,0x80,0x86,0x83}, {0x07,0x89,0x8b,0x88}, {0x15,0x92,0x9c,0x95}, {0x1b,0x9b,0x91,0x9e}, +{0xa1,0x7c,0x0a,0x47}, {0xaf,0x75,0x07,0x4c}, {0xbd,0x6e,0x10,0x51}, {0xb3,0x67,0x1d,0x5a}, +{0x99,0x58,0x3e,0x6b}, {0x97,0x51,0x33,0x60}, {0x85,0x4a,0x24,0x7d}, {0x8b,0x43,0x29,0x76}, +{0xd1,0x34,0x62,0x1f}, {0xdf,0x3d,0x6f,0x14}, {0xcd,0x26,0x78,0x09}, {0xc3,0x2f,0x75,0x02}, +{0xe9,0x10,0x56,0x33}, {0xe7,0x19,0x5b,0x38}, {0xf5,0x02,0x4c,0x25}, {0xfb,0x0b,0x41,0x2e}, +{0x9a,0xd7,0x61,0x8c}, {0x94,0xde,0x6c,0x87}, {0x86,0xc5,0x7b,0x9a}, {0x88,0xcc,0x76,0x91}, +{0xa2,0xf3,0x55,0xa0}, {0xac,0xfa,0x58,0xab}, {0xbe,0xe1,0x4f,0xb6}, {0xb0,0xe8,0x42,0xbd}, +{0xea,0x9f,0x09,0xd4}, {0xe4,0x96,0x04,0xdf}, {0xf6,0x8d,0x13,0xc2}, {0xf8,0x84,0x1e,0xc9}, +{0xd2,0xbb,0x3d,0xf8}, {0xdc,0xb2,0x30,0xf3}, {0xce,0xa9,0x27,0xee}, {0xc0,0xa0,0x2a,0xe5}, +{0x7a,0x47,0xb1,0x3c}, {0x74,0x4e,0xbc,0x37}, {0x66,0x55,0xab,0x2a}, {0x68,0x5c,0xa6,0x21}, +{0x42,0x63,0x85,0x10}, {0x4c,0x6a,0x88,0x1b}, {0x5e,0x71,0x9f,0x06}, {0x50,0x78,0x92,0x0d}, +{0x0a,0x0f,0xd9,0x64}, {0x04,0x06,0xd4,0x6f}, {0x16,0x1d,0xc3,0x72}, {0x18,0x14,0xce,0x79}, +{0x32,0x2b,0xed,0x48}, {0x3c,0x22,0xe0,0x43}, {0x2e,0x39,0xf7,0x5e}, {0x20,0x30,0xfa,0x55}, +{0xec,0x9a,0xb7,0x01}, {0xe2,0x93,0xba,0x0a}, {0xf0,0x88,0xad,0x17}, {0xfe,0x81,0xa0,0x1c}, +{0xd4,0xbe,0x83,0x2d}, {0xda,0xb7,0x8e,0x26}, {0xc8,0xac,0x99,0x3b}, {0xc6,0xa5,0x94,0x30}, +{0x9c,0xd2,0xdf,0x59}, {0x92,0xdb,0xd2,0x52}, {0x80,0xc0,0xc5,0x4f}, {0x8e,0xc9,0xc8,0x44}, +{0xa4,0xf6,0xeb,0x75}, {0xaa,0xff,0xe6,0x7e}, {0xb8,0xe4,0xf1,0x63}, {0xb6,0xed,0xfc,0x68}, +{0x0c,0x0a,0x67,0xb1}, {0x02,0x03,0x6a,0xba}, {0x10,0x18,0x7d,0xa7}, {0x1e,0x11,0x70,0xac}, +{0x34,0x2e,0x53,0x9d}, {0x3a,0x27,0x5e,0x96}, {0x28,0x3c,0x49,0x8b}, {0x26,0x35,0x44,0x80}, +{0x7c,0x42,0x0f,0xe9}, {0x72,0x4b,0x02,0xe2}, {0x60,0x50,0x15,0xff}, {0x6e,0x59,0x18,0xf4}, +{0x44,0x66,0x3b,0xc5}, {0x4a,0x6f,0x36,0xce}, {0x58,0x74,0x21,0xd3}, {0x56,0x7d,0x2c,0xd8}, +{0x37,0xa1,0x0c,0x7a}, {0x39,0xa8,0x01,0x71}, {0x2b,0xb3,0x16,0x6c}, {0x25,0xba,0x1b,0x67}, +{0x0f,0x85,0x38,0x56}, {0x01,0x8c,0x35,0x5d}, {0x13,0x97,0x22,0x40}, {0x1d,0x9e,0x2f,0x4b}, +{0x47,0xe9,0x64,0x22}, {0x49,0xe0,0x69,0x29}, {0x5b,0xfb,0x7e,0x34}, {0x55,0xf2,0x73,0x3f}, +{0x7f,0xcd,0x50,0x0e}, {0x71,0xc4,0x5d,0x05}, {0x63,0xdf,0x4a,0x18}, {0x6d,0xd6,0x47,0x13}, +{0xd7,0x31,0xdc,0xca}, {0xd9,0x38,0xd1,0xc1}, {0xcb,0x23,0xc6,0xdc}, {0xc5,0x2a,0xcb,0xd7}, +{0xef,0x15,0xe8,0xe6}, {0xe1,0x1c,0xe5,0xed}, {0xf3,0x07,0xf2,0xf0}, {0xfd,0x0e,0xff,0xfb}, +{0xa7,0x79,0xb4,0x92}, {0xa9,0x70,0xb9,0x99}, {0xbb,0x6b,0xae,0x84}, {0xb5,0x62,0xa3,0x8f}, +{0x9f,0x5d,0x80,0xbe}, {0x91,0x54,0x8d,0xb5}, {0x83,0x4f,0x9a,0xa8}, {0x8d,0x46,0x97,0xa3} +}; + +word8 U2[256][4] = { +{0x00,0x00,0x00,0x00}, {0x0b,0x0e,0x09,0x0d}, {0x16,0x1c,0x12,0x1a}, {0x1d,0x12,0x1b,0x17}, +{0x2c,0x38,0x24,0x34}, {0x27,0x36,0x2d,0x39}, {0x3a,0x24,0x36,0x2e}, {0x31,0x2a,0x3f,0x23}, +{0x58,0x70,0x48,0x68}, {0x53,0x7e,0x41,0x65}, {0x4e,0x6c,0x5a,0x72}, {0x45,0x62,0x53,0x7f}, +{0x74,0x48,0x6c,0x5c}, {0x7f,0x46,0x65,0x51}, {0x62,0x54,0x7e,0x46}, {0x69,0x5a,0x77,0x4b}, +{0xb0,0xe0,0x90,0xd0}, {0xbb,0xee,0x99,0xdd}, {0xa6,0xfc,0x82,0xca}, {0xad,0xf2,0x8b,0xc7}, +{0x9c,0xd8,0xb4,0xe4}, {0x97,0xd6,0xbd,0xe9}, {0x8a,0xc4,0xa6,0xfe}, {0x81,0xca,0xaf,0xf3}, +{0xe8,0x90,0xd8,0xb8}, {0xe3,0x9e,0xd1,0xb5}, {0xfe,0x8c,0xca,0xa2}, {0xf5,0x82,0xc3,0xaf}, +{0xc4,0xa8,0xfc,0x8c}, {0xcf,0xa6,0xf5,0x81}, {0xd2,0xb4,0xee,0x96}, {0xd9,0xba,0xe7,0x9b}, +{0x7b,0xdb,0x3b,0xbb}, {0x70,0xd5,0x32,0xb6}, {0x6d,0xc7,0x29,0xa1}, {0x66,0xc9,0x20,0xac}, +{0x57,0xe3,0x1f,0x8f}, {0x5c,0xed,0x16,0x82}, {0x41,0xff,0x0d,0x95}, {0x4a,0xf1,0x04,0x98}, +{0x23,0xab,0x73,0xd3}, {0x28,0xa5,0x7a,0xde}, {0x35,0xb7,0x61,0xc9}, {0x3e,0xb9,0x68,0xc4}, +{0x0f,0x93,0x57,0xe7}, {0x04,0x9d,0x5e,0xea}, {0x19,0x8f,0x45,0xfd}, {0x12,0x81,0x4c,0xf0}, +{0xcb,0x3b,0xab,0x6b}, {0xc0,0x35,0xa2,0x66}, {0xdd,0x27,0xb9,0x71}, {0xd6,0x29,0xb0,0x7c}, +{0xe7,0x03,0x8f,0x5f}, {0xec,0x0d,0x86,0x52}, {0xf1,0x1f,0x9d,0x45}, {0xfa,0x11,0x94,0x48}, +{0x93,0x4b,0xe3,0x03}, {0x98,0x45,0xea,0x0e}, {0x85,0x57,0xf1,0x19}, {0x8e,0x59,0xf8,0x14}, +{0xbf,0x73,0xc7,0x37}, {0xb4,0x7d,0xce,0x3a}, {0xa9,0x6f,0xd5,0x2d}, {0xa2,0x61,0xdc,0x20}, +{0xf6,0xad,0x76,0x6d}, {0xfd,0xa3,0x7f,0x60}, {0xe0,0xb1,0x64,0x77}, {0xeb,0xbf,0x6d,0x7a}, +{0xda,0x95,0x52,0x59}, {0xd1,0x9b,0x5b,0x54}, {0xcc,0x89,0x40,0x43}, {0xc7,0x87,0x49,0x4e}, +{0xae,0xdd,0x3e,0x05}, {0xa5,0xd3,0x37,0x08}, {0xb8,0xc1,0x2c,0x1f}, {0xb3,0xcf,0x25,0x12}, +{0x82,0xe5,0x1a,0x31}, {0x89,0xeb,0x13,0x3c}, {0x94,0xf9,0x08,0x2b}, {0x9f,0xf7,0x01,0x26}, +{0x46,0x4d,0xe6,0xbd}, {0x4d,0x43,0xef,0xb0}, {0x50,0x51,0xf4,0xa7}, {0x5b,0x5f,0xfd,0xaa}, +{0x6a,0x75,0xc2,0x89}, {0x61,0x7b,0xcb,0x84}, {0x7c,0x69,0xd0,0x93}, {0x77,0x67,0xd9,0x9e}, +{0x1e,0x3d,0xae,0xd5}, {0x15,0x33,0xa7,0xd8}, {0x08,0x21,0xbc,0xcf}, {0x03,0x2f,0xb5,0xc2}, +{0x32,0x05,0x8a,0xe1}, {0x39,0x0b,0x83,0xec}, {0x24,0x19,0x98,0xfb}, {0x2f,0x17,0x91,0xf6}, +{0x8d,0x76,0x4d,0xd6}, {0x86,0x78,0x44,0xdb}, {0x9b,0x6a,0x5f,0xcc}, {0x90,0x64,0x56,0xc1}, +{0xa1,0x4e,0x69,0xe2}, {0xaa,0x40,0x60,0xef}, {0xb7,0x52,0x7b,0xf8}, {0xbc,0x5c,0x72,0xf5}, +{0xd5,0x06,0x05,0xbe}, {0xde,0x08,0x0c,0xb3}, {0xc3,0x1a,0x17,0xa4}, {0xc8,0x14,0x1e,0xa9}, +{0xf9,0x3e,0x21,0x8a}, {0xf2,0x30,0x28,0x87}, {0xef,0x22,0x33,0x90}, {0xe4,0x2c,0x3a,0x9d}, +{0x3d,0x96,0xdd,0x06}, {0x36,0x98,0xd4,0x0b}, {0x2b,0x8a,0xcf,0x1c}, {0x20,0x84,0xc6,0x11}, +{0x11,0xae,0xf9,0x32}, {0x1a,0xa0,0xf0,0x3f}, {0x07,0xb2,0xeb,0x28}, {0x0c,0xbc,0xe2,0x25}, +{0x65,0xe6,0x95,0x6e}, {0x6e,0xe8,0x9c,0x63}, {0x73,0xfa,0x87,0x74}, {0x78,0xf4,0x8e,0x79}, +{0x49,0xde,0xb1,0x5a}, {0x42,0xd0,0xb8,0x57}, {0x5f,0xc2,0xa3,0x40}, {0x54,0xcc,0xaa,0x4d}, +{0xf7,0x41,0xec,0xda}, {0xfc,0x4f,0xe5,0xd7}, {0xe1,0x5d,0xfe,0xc0}, {0xea,0x53,0xf7,0xcd}, +{0xdb,0x79,0xc8,0xee}, {0xd0,0x77,0xc1,0xe3}, {0xcd,0x65,0xda,0xf4}, {0xc6,0x6b,0xd3,0xf9}, +{0xaf,0x31,0xa4,0xb2}, {0xa4,0x3f,0xad,0xbf}, {0xb9,0x2d,0xb6,0xa8}, {0xb2,0x23,0xbf,0xa5}, +{0x83,0x09,0x80,0x86}, {0x88,0x07,0x89,0x8b}, {0x95,0x15,0x92,0x9c}, {0x9e,0x1b,0x9b,0x91}, +{0x47,0xa1,0x7c,0x0a}, {0x4c,0xaf,0x75,0x07}, {0x51,0xbd,0x6e,0x10}, {0x5a,0xb3,0x67,0x1d}, +{0x6b,0x99,0x58,0x3e}, {0x60,0x97,0x51,0x33}, {0x7d,0x85,0x4a,0x24}, {0x76,0x8b,0x43,0x29}, +{0x1f,0xd1,0x34,0x62}, {0x14,0xdf,0x3d,0x6f}, {0x09,0xcd,0x26,0x78}, {0x02,0xc3,0x2f,0x75}, +{0x33,0xe9,0x10,0x56}, {0x38,0xe7,0x19,0x5b}, {0x25,0xf5,0x02,0x4c}, {0x2e,0xfb,0x0b,0x41}, +{0x8c,0x9a,0xd7,0x61}, {0x87,0x94,0xde,0x6c}, {0x9a,0x86,0xc5,0x7b}, {0x91,0x88,0xcc,0x76}, +{0xa0,0xa2,0xf3,0x55}, {0xab,0xac,0xfa,0x58}, {0xb6,0xbe,0xe1,0x4f}, {0xbd,0xb0,0xe8,0x42}, +{0xd4,0xea,0x9f,0x09}, {0xdf,0xe4,0x96,0x04}, {0xc2,0xf6,0x8d,0x13}, {0xc9,0xf8,0x84,0x1e}, +{0xf8,0xd2,0xbb,0x3d}, {0xf3,0xdc,0xb2,0x30}, {0xee,0xce,0xa9,0x27}, {0xe5,0xc0,0xa0,0x2a}, +{0x3c,0x7a,0x47,0xb1}, {0x37,0x74,0x4e,0xbc}, {0x2a,0x66,0x55,0xab}, {0x21,0x68,0x5c,0xa6}, +{0x10,0x42,0x63,0x85}, {0x1b,0x4c,0x6a,0x88}, {0x06,0x5e,0x71,0x9f}, {0x0d,0x50,0x78,0x92}, +{0x64,0x0a,0x0f,0xd9}, {0x6f,0x04,0x06,0xd4}, {0x72,0x16,0x1d,0xc3}, {0x79,0x18,0x14,0xce}, +{0x48,0x32,0x2b,0xed}, {0x43,0x3c,0x22,0xe0}, {0x5e,0x2e,0x39,0xf7}, {0x55,0x20,0x30,0xfa}, +{0x01,0xec,0x9a,0xb7}, {0x0a,0xe2,0x93,0xba}, {0x17,0xf0,0x88,0xad}, {0x1c,0xfe,0x81,0xa0}, +{0x2d,0xd4,0xbe,0x83}, {0x26,0xda,0xb7,0x8e}, {0x3b,0xc8,0xac,0x99}, {0x30,0xc6,0xa5,0x94}, +{0x59,0x9c,0xd2,0xdf}, {0x52,0x92,0xdb,0xd2}, {0x4f,0x80,0xc0,0xc5}, {0x44,0x8e,0xc9,0xc8}, +{0x75,0xa4,0xf6,0xeb}, {0x7e,0xaa,0xff,0xe6}, {0x63,0xb8,0xe4,0xf1}, {0x68,0xb6,0xed,0xfc}, +{0xb1,0x0c,0x0a,0x67}, {0xba,0x02,0x03,0x6a}, {0xa7,0x10,0x18,0x7d}, {0xac,0x1e,0x11,0x70}, +{0x9d,0x34,0x2e,0x53}, {0x96,0x3a,0x27,0x5e}, {0x8b,0x28,0x3c,0x49}, {0x80,0x26,0x35,0x44}, +{0xe9,0x7c,0x42,0x0f}, {0xe2,0x72,0x4b,0x02}, {0xff,0x60,0x50,0x15}, {0xf4,0x6e,0x59,0x18}, +{0xc5,0x44,0x66,0x3b}, {0xce,0x4a,0x6f,0x36}, {0xd3,0x58,0x74,0x21}, {0xd8,0x56,0x7d,0x2c}, +{0x7a,0x37,0xa1,0x0c}, {0x71,0x39,0xa8,0x01}, {0x6c,0x2b,0xb3,0x16}, {0x67,0x25,0xba,0x1b}, +{0x56,0x0f,0x85,0x38}, {0x5d,0x01,0x8c,0x35}, {0x40,0x13,0x97,0x22}, {0x4b,0x1d,0x9e,0x2f}, +{0x22,0x47,0xe9,0x64}, {0x29,0x49,0xe0,0x69}, {0x34,0x5b,0xfb,0x7e}, {0x3f,0x55,0xf2,0x73}, +{0x0e,0x7f,0xcd,0x50}, {0x05,0x71,0xc4,0x5d}, {0x18,0x63,0xdf,0x4a}, {0x13,0x6d,0xd6,0x47}, +{0xca,0xd7,0x31,0xdc}, {0xc1,0xd9,0x38,0xd1}, {0xdc,0xcb,0x23,0xc6}, {0xd7,0xc5,0x2a,0xcb}, +{0xe6,0xef,0x15,0xe8}, {0xed,0xe1,0x1c,0xe5}, {0xf0,0xf3,0x07,0xf2}, {0xfb,0xfd,0x0e,0xff}, +{0x92,0xa7,0x79,0xb4}, {0x99,0xa9,0x70,0xb9}, {0x84,0xbb,0x6b,0xae}, {0x8f,0xb5,0x62,0xa3}, +{0xbe,0x9f,0x5d,0x80}, {0xb5,0x91,0x54,0x8d}, {0xa8,0x83,0x4f,0x9a}, {0xa3,0x8d,0x46,0x97} +}; + +word8 U3[256][4] = { +{0x00,0x00,0x00,0x00}, {0x0d,0x0b,0x0e,0x09}, {0x1a,0x16,0x1c,0x12}, {0x17,0x1d,0x12,0x1b}, +{0x34,0x2c,0x38,0x24}, {0x39,0x27,0x36,0x2d}, {0x2e,0x3a,0x24,0x36}, {0x23,0x31,0x2a,0x3f}, +{0x68,0x58,0x70,0x48}, {0x65,0x53,0x7e,0x41}, {0x72,0x4e,0x6c,0x5a}, {0x7f,0x45,0x62,0x53}, +{0x5c,0x74,0x48,0x6c}, {0x51,0x7f,0x46,0x65}, {0x46,0x62,0x54,0x7e}, {0x4b,0x69,0x5a,0x77}, +{0xd0,0xb0,0xe0,0x90}, {0xdd,0xbb,0xee,0x99}, {0xca,0xa6,0xfc,0x82}, {0xc7,0xad,0xf2,0x8b}, +{0xe4,0x9c,0xd8,0xb4}, {0xe9,0x97,0xd6,0xbd}, {0xfe,0x8a,0xc4,0xa6}, {0xf3,0x81,0xca,0xaf}, +{0xb8,0xe8,0x90,0xd8}, {0xb5,0xe3,0x9e,0xd1}, {0xa2,0xfe,0x8c,0xca}, {0xaf,0xf5,0x82,0xc3}, +{0x8c,0xc4,0xa8,0xfc}, {0x81,0xcf,0xa6,0xf5}, {0x96,0xd2,0xb4,0xee}, {0x9b,0xd9,0xba,0xe7}, +{0xbb,0x7b,0xdb,0x3b}, {0xb6,0x70,0xd5,0x32}, {0xa1,0x6d,0xc7,0x29}, {0xac,0x66,0xc9,0x20}, +{0x8f,0x57,0xe3,0x1f}, {0x82,0x5c,0xed,0x16}, {0x95,0x41,0xff,0x0d}, {0x98,0x4a,0xf1,0x04}, +{0xd3,0x23,0xab,0x73}, {0xde,0x28,0xa5,0x7a}, {0xc9,0x35,0xb7,0x61}, {0xc4,0x3e,0xb9,0x68}, +{0xe7,0x0f,0x93,0x57}, {0xea,0x04,0x9d,0x5e}, {0xfd,0x19,0x8f,0x45}, {0xf0,0x12,0x81,0x4c}, +{0x6b,0xcb,0x3b,0xab}, {0x66,0xc0,0x35,0xa2}, {0x71,0xdd,0x27,0xb9}, {0x7c,0xd6,0x29,0xb0}, +{0x5f,0xe7,0x03,0x8f}, {0x52,0xec,0x0d,0x86}, {0x45,0xf1,0x1f,0x9d}, {0x48,0xfa,0x11,0x94}, +{0x03,0x93,0x4b,0xe3}, {0x0e,0x98,0x45,0xea}, {0x19,0x85,0x57,0xf1}, {0x14,0x8e,0x59,0xf8}, +{0x37,0xbf,0x73,0xc7}, {0x3a,0xb4,0x7d,0xce}, {0x2d,0xa9,0x6f,0xd5}, {0x20,0xa2,0x61,0xdc}, +{0x6d,0xf6,0xad,0x76}, {0x60,0xfd,0xa3,0x7f}, {0x77,0xe0,0xb1,0x64}, {0x7a,0xeb,0xbf,0x6d}, +{0x59,0xda,0x95,0x52}, {0x54,0xd1,0x9b,0x5b}, {0x43,0xcc,0x89,0x40}, {0x4e,0xc7,0x87,0x49}, +{0x05,0xae,0xdd,0x3e}, {0x08,0xa5,0xd3,0x37}, {0x1f,0xb8,0xc1,0x2c}, {0x12,0xb3,0xcf,0x25}, +{0x31,0x82,0xe5,0x1a}, {0x3c,0x89,0xeb,0x13}, {0x2b,0x94,0xf9,0x08}, {0x26,0x9f,0xf7,0x01}, +{0xbd,0x46,0x4d,0xe6}, {0xb0,0x4d,0x43,0xef}, {0xa7,0x50,0x51,0xf4}, {0xaa,0x5b,0x5f,0xfd}, +{0x89,0x6a,0x75,0xc2}, {0x84,0x61,0x7b,0xcb}, {0x93,0x7c,0x69,0xd0}, {0x9e,0x77,0x67,0xd9}, +{0xd5,0x1e,0x3d,0xae}, {0xd8,0x15,0x33,0xa7}, {0xcf,0x08,0x21,0xbc}, {0xc2,0x03,0x2f,0xb5}, +{0xe1,0x32,0x05,0x8a}, {0xec,0x39,0x0b,0x83}, {0xfb,0x24,0x19,0x98}, {0xf6,0x2f,0x17,0x91}, +{0xd6,0x8d,0x76,0x4d}, {0xdb,0x86,0x78,0x44}, {0xcc,0x9b,0x6a,0x5f}, {0xc1,0x90,0x64,0x56}, +{0xe2,0xa1,0x4e,0x69}, {0xef,0xaa,0x40,0x60}, {0xf8,0xb7,0x52,0x7b}, {0xf5,0xbc,0x5c,0x72}, +{0xbe,0xd5,0x06,0x05}, {0xb3,0xde,0x08,0x0c}, {0xa4,0xc3,0x1a,0x17}, {0xa9,0xc8,0x14,0x1e}, +{0x8a,0xf9,0x3e,0x21}, {0x87,0xf2,0x30,0x28}, {0x90,0xef,0x22,0x33}, {0x9d,0xe4,0x2c,0x3a}, +{0x06,0x3d,0x96,0xdd}, {0x0b,0x36,0x98,0xd4}, {0x1c,0x2b,0x8a,0xcf}, {0x11,0x20,0x84,0xc6}, +{0x32,0x11,0xae,0xf9}, {0x3f,0x1a,0xa0,0xf0}, {0x28,0x07,0xb2,0xeb}, {0x25,0x0c,0xbc,0xe2}, +{0x6e,0x65,0xe6,0x95}, {0x63,0x6e,0xe8,0x9c}, {0x74,0x73,0xfa,0x87}, {0x79,0x78,0xf4,0x8e}, +{0x5a,0x49,0xde,0xb1}, {0x57,0x42,0xd0,0xb8}, {0x40,0x5f,0xc2,0xa3}, {0x4d,0x54,0xcc,0xaa}, +{0xda,0xf7,0x41,0xec}, {0xd7,0xfc,0x4f,0xe5}, {0xc0,0xe1,0x5d,0xfe}, {0xcd,0xea,0x53,0xf7}, +{0xee,0xdb,0x79,0xc8}, {0xe3,0xd0,0x77,0xc1}, {0xf4,0xcd,0x65,0xda}, {0xf9,0xc6,0x6b,0xd3}, +{0xb2,0xaf,0x31,0xa4}, {0xbf,0xa4,0x3f,0xad}, {0xa8,0xb9,0x2d,0xb6}, {0xa5,0xb2,0x23,0xbf}, +{0x86,0x83,0x09,0x80}, {0x8b,0x88,0x07,0x89}, {0x9c,0x95,0x15,0x92}, {0x91,0x9e,0x1b,0x9b}, +{0x0a,0x47,0xa1,0x7c}, {0x07,0x4c,0xaf,0x75}, {0x10,0x51,0xbd,0x6e}, {0x1d,0x5a,0xb3,0x67}, +{0x3e,0x6b,0x99,0x58}, {0x33,0x60,0x97,0x51}, {0x24,0x7d,0x85,0x4a}, {0x29,0x76,0x8b,0x43}, +{0x62,0x1f,0xd1,0x34}, {0x6f,0x14,0xdf,0x3d}, {0x78,0x09,0xcd,0x26}, {0x75,0x02,0xc3,0x2f}, +{0x56,0x33,0xe9,0x10}, {0x5b,0x38,0xe7,0x19}, {0x4c,0x25,0xf5,0x02}, {0x41,0x2e,0xfb,0x0b}, +{0x61,0x8c,0x9a,0xd7}, {0x6c,0x87,0x94,0xde}, {0x7b,0x9a,0x86,0xc5}, {0x76,0x91,0x88,0xcc}, +{0x55,0xa0,0xa2,0xf3}, {0x58,0xab,0xac,0xfa}, {0x4f,0xb6,0xbe,0xe1}, {0x42,0xbd,0xb0,0xe8}, +{0x09,0xd4,0xea,0x9f}, {0x04,0xdf,0xe4,0x96}, {0x13,0xc2,0xf6,0x8d}, {0x1e,0xc9,0xf8,0x84}, +{0x3d,0xf8,0xd2,0xbb}, {0x30,0xf3,0xdc,0xb2}, {0x27,0xee,0xce,0xa9}, {0x2a,0xe5,0xc0,0xa0}, +{0xb1,0x3c,0x7a,0x47}, {0xbc,0x37,0x74,0x4e}, {0xab,0x2a,0x66,0x55}, {0xa6,0x21,0x68,0x5c}, +{0x85,0x10,0x42,0x63}, {0x88,0x1b,0x4c,0x6a}, {0x9f,0x06,0x5e,0x71}, {0x92,0x0d,0x50,0x78}, +{0xd9,0x64,0x0a,0x0f}, {0xd4,0x6f,0x04,0x06}, {0xc3,0x72,0x16,0x1d}, {0xce,0x79,0x18,0x14}, +{0xed,0x48,0x32,0x2b}, {0xe0,0x43,0x3c,0x22}, {0xf7,0x5e,0x2e,0x39}, {0xfa,0x55,0x20,0x30}, +{0xb7,0x01,0xec,0x9a}, {0xba,0x0a,0xe2,0x93}, {0xad,0x17,0xf0,0x88}, {0xa0,0x1c,0xfe,0x81}, +{0x83,0x2d,0xd4,0xbe}, {0x8e,0x26,0xda,0xb7}, {0x99,0x3b,0xc8,0xac}, {0x94,0x30,0xc6,0xa5}, +{0xdf,0x59,0x9c,0xd2}, {0xd2,0x52,0x92,0xdb}, {0xc5,0x4f,0x80,0xc0}, {0xc8,0x44,0x8e,0xc9}, +{0xeb,0x75,0xa4,0xf6}, {0xe6,0x7e,0xaa,0xff}, {0xf1,0x63,0xb8,0xe4}, {0xfc,0x68,0xb6,0xed}, +{0x67,0xb1,0x0c,0x0a}, {0x6a,0xba,0x02,0x03}, {0x7d,0xa7,0x10,0x18}, {0x70,0xac,0x1e,0x11}, +{0x53,0x9d,0x34,0x2e}, {0x5e,0x96,0x3a,0x27}, {0x49,0x8b,0x28,0x3c}, {0x44,0x80,0x26,0x35}, +{0x0f,0xe9,0x7c,0x42}, {0x02,0xe2,0x72,0x4b}, {0x15,0xff,0x60,0x50}, {0x18,0xf4,0x6e,0x59}, +{0x3b,0xc5,0x44,0x66}, {0x36,0xce,0x4a,0x6f}, {0x21,0xd3,0x58,0x74}, {0x2c,0xd8,0x56,0x7d}, +{0x0c,0x7a,0x37,0xa1}, {0x01,0x71,0x39,0xa8}, {0x16,0x6c,0x2b,0xb3}, {0x1b,0x67,0x25,0xba}, +{0x38,0x56,0x0f,0x85}, {0x35,0x5d,0x01,0x8c}, {0x22,0x40,0x13,0x97}, {0x2f,0x4b,0x1d,0x9e}, +{0x64,0x22,0x47,0xe9}, {0x69,0x29,0x49,0xe0}, {0x7e,0x34,0x5b,0xfb}, {0x73,0x3f,0x55,0xf2}, +{0x50,0x0e,0x7f,0xcd}, {0x5d,0x05,0x71,0xc4}, {0x4a,0x18,0x63,0xdf}, {0x47,0x13,0x6d,0xd6}, +{0xdc,0xca,0xd7,0x31}, {0xd1,0xc1,0xd9,0x38}, {0xc6,0xdc,0xcb,0x23}, {0xcb,0xd7,0xc5,0x2a}, +{0xe8,0xe6,0xef,0x15}, {0xe5,0xed,0xe1,0x1c}, {0xf2,0xf0,0xf3,0x07}, {0xff,0xfb,0xfd,0x0e}, +{0xb4,0x92,0xa7,0x79}, {0xb9,0x99,0xa9,0x70}, {0xae,0x84,0xbb,0x6b}, {0xa3,0x8f,0xb5,0x62}, +{0x80,0xbe,0x9f,0x5d}, {0x8d,0xb5,0x91,0x54}, {0x9a,0xa8,0x83,0x4f}, {0x97,0xa3,0x8d,0x46} +}; + +word8 U4[256][4] = { +{0x00,0x00,0x00,0x00}, {0x09,0x0d,0x0b,0x0e}, {0x12,0x1a,0x16,0x1c}, {0x1b,0x17,0x1d,0x12}, +{0x24,0x34,0x2c,0x38}, {0x2d,0x39,0x27,0x36}, {0x36,0x2e,0x3a,0x24}, {0x3f,0x23,0x31,0x2a}, +{0x48,0x68,0x58,0x70}, {0x41,0x65,0x53,0x7e}, {0x5a,0x72,0x4e,0x6c}, {0x53,0x7f,0x45,0x62}, +{0x6c,0x5c,0x74,0x48}, {0x65,0x51,0x7f,0x46}, {0x7e,0x46,0x62,0x54}, {0x77,0x4b,0x69,0x5a}, +{0x90,0xd0,0xb0,0xe0}, {0x99,0xdd,0xbb,0xee}, {0x82,0xca,0xa6,0xfc}, {0x8b,0xc7,0xad,0xf2}, +{0xb4,0xe4,0x9c,0xd8}, {0xbd,0xe9,0x97,0xd6}, {0xa6,0xfe,0x8a,0xc4}, {0xaf,0xf3,0x81,0xca}, +{0xd8,0xb8,0xe8,0x90}, {0xd1,0xb5,0xe3,0x9e}, {0xca,0xa2,0xfe,0x8c}, {0xc3,0xaf,0xf5,0x82}, +{0xfc,0x8c,0xc4,0xa8}, {0xf5,0x81,0xcf,0xa6}, {0xee,0x96,0xd2,0xb4}, {0xe7,0x9b,0xd9,0xba}, +{0x3b,0xbb,0x7b,0xdb}, {0x32,0xb6,0x70,0xd5}, {0x29,0xa1,0x6d,0xc7}, {0x20,0xac,0x66,0xc9}, +{0x1f,0x8f,0x57,0xe3}, {0x16,0x82,0x5c,0xed}, {0x0d,0x95,0x41,0xff}, {0x04,0x98,0x4a,0xf1}, +{0x73,0xd3,0x23,0xab}, {0x7a,0xde,0x28,0xa5}, {0x61,0xc9,0x35,0xb7}, {0x68,0xc4,0x3e,0xb9}, +{0x57,0xe7,0x0f,0x93}, {0x5e,0xea,0x04,0x9d}, {0x45,0xfd,0x19,0x8f}, {0x4c,0xf0,0x12,0x81}, +{0xab,0x6b,0xcb,0x3b}, {0xa2,0x66,0xc0,0x35}, {0xb9,0x71,0xdd,0x27}, {0xb0,0x7c,0xd6,0x29}, +{0x8f,0x5f,0xe7,0x03}, {0x86,0x52,0xec,0x0d}, {0x9d,0x45,0xf1,0x1f}, {0x94,0x48,0xfa,0x11}, +{0xe3,0x03,0x93,0x4b}, {0xea,0x0e,0x98,0x45}, {0xf1,0x19,0x85,0x57}, {0xf8,0x14,0x8e,0x59}, +{0xc7,0x37,0xbf,0x73}, {0xce,0x3a,0xb4,0x7d}, {0xd5,0x2d,0xa9,0x6f}, {0xdc,0x20,0xa2,0x61}, +{0x76,0x6d,0xf6,0xad}, {0x7f,0x60,0xfd,0xa3}, {0x64,0x77,0xe0,0xb1}, {0x6d,0x7a,0xeb,0xbf}, +{0x52,0x59,0xda,0x95}, {0x5b,0x54,0xd1,0x9b}, {0x40,0x43,0xcc,0x89}, {0x49,0x4e,0xc7,0x87}, +{0x3e,0x05,0xae,0xdd}, {0x37,0x08,0xa5,0xd3}, {0x2c,0x1f,0xb8,0xc1}, {0x25,0x12,0xb3,0xcf}, +{0x1a,0x31,0x82,0xe5}, {0x13,0x3c,0x89,0xeb}, {0x08,0x2b,0x94,0xf9}, {0x01,0x26,0x9f,0xf7}, +{0xe6,0xbd,0x46,0x4d}, {0xef,0xb0,0x4d,0x43}, {0xf4,0xa7,0x50,0x51}, {0xfd,0xaa,0x5b,0x5f}, +{0xc2,0x89,0x6a,0x75}, {0xcb,0x84,0x61,0x7b}, {0xd0,0x93,0x7c,0x69}, {0xd9,0x9e,0x77,0x67}, +{0xae,0xd5,0x1e,0x3d}, {0xa7,0xd8,0x15,0x33}, {0xbc,0xcf,0x08,0x21}, {0xb5,0xc2,0x03,0x2f}, +{0x8a,0xe1,0x32,0x05}, {0x83,0xec,0x39,0x0b}, {0x98,0xfb,0x24,0x19}, {0x91,0xf6,0x2f,0x17}, +{0x4d,0xd6,0x8d,0x76}, {0x44,0xdb,0x86,0x78}, {0x5f,0xcc,0x9b,0x6a}, {0x56,0xc1,0x90,0x64}, +{0x69,0xe2,0xa1,0x4e}, {0x60,0xef,0xaa,0x40}, {0x7b,0xf8,0xb7,0x52}, {0x72,0xf5,0xbc,0x5c}, +{0x05,0xbe,0xd5,0x06}, {0x0c,0xb3,0xde,0x08}, {0x17,0xa4,0xc3,0x1a}, {0x1e,0xa9,0xc8,0x14}, +{0x21,0x8a,0xf9,0x3e}, {0x28,0x87,0xf2,0x30}, {0x33,0x90,0xef,0x22}, {0x3a,0x9d,0xe4,0x2c}, +{0xdd,0x06,0x3d,0x96}, {0xd4,0x0b,0x36,0x98}, {0xcf,0x1c,0x2b,0x8a}, {0xc6,0x11,0x20,0x84}, +{0xf9,0x32,0x11,0xae}, {0xf0,0x3f,0x1a,0xa0}, {0xeb,0x28,0x07,0xb2}, {0xe2,0x25,0x0c,0xbc}, +{0x95,0x6e,0x65,0xe6}, {0x9c,0x63,0x6e,0xe8}, {0x87,0x74,0x73,0xfa}, {0x8e,0x79,0x78,0xf4}, +{0xb1,0x5a,0x49,0xde}, {0xb8,0x57,0x42,0xd0}, {0xa3,0x40,0x5f,0xc2}, {0xaa,0x4d,0x54,0xcc}, +{0xec,0xda,0xf7,0x41}, {0xe5,0xd7,0xfc,0x4f}, {0xfe,0xc0,0xe1,0x5d}, {0xf7,0xcd,0xea,0x53}, +{0xc8,0xee,0xdb,0x79}, {0xc1,0xe3,0xd0,0x77}, {0xda,0xf4,0xcd,0x65}, {0xd3,0xf9,0xc6,0x6b}, +{0xa4,0xb2,0xaf,0x31}, {0xad,0xbf,0xa4,0x3f}, {0xb6,0xa8,0xb9,0x2d}, {0xbf,0xa5,0xb2,0x23}, +{0x80,0x86,0x83,0x09}, {0x89,0x8b,0x88,0x07}, {0x92,0x9c,0x95,0x15}, {0x9b,0x91,0x9e,0x1b}, +{0x7c,0x0a,0x47,0xa1}, {0x75,0x07,0x4c,0xaf}, {0x6e,0x10,0x51,0xbd}, {0x67,0x1d,0x5a,0xb3}, +{0x58,0x3e,0x6b,0x99}, {0x51,0x33,0x60,0x97}, {0x4a,0x24,0x7d,0x85}, {0x43,0x29,0x76,0x8b}, +{0x34,0x62,0x1f,0xd1}, {0x3d,0x6f,0x14,0xdf}, {0x26,0x78,0x09,0xcd}, {0x2f,0x75,0x02,0xc3}, +{0x10,0x56,0x33,0xe9}, {0x19,0x5b,0x38,0xe7}, {0x02,0x4c,0x25,0xf5}, {0x0b,0x41,0x2e,0xfb}, +{0xd7,0x61,0x8c,0x9a}, {0xde,0x6c,0x87,0x94}, {0xc5,0x7b,0x9a,0x86}, {0xcc,0x76,0x91,0x88}, +{0xf3,0x55,0xa0,0xa2}, {0xfa,0x58,0xab,0xac}, {0xe1,0x4f,0xb6,0xbe}, {0xe8,0x42,0xbd,0xb0}, +{0x9f,0x09,0xd4,0xea}, {0x96,0x04,0xdf,0xe4}, {0x8d,0x13,0xc2,0xf6}, {0x84,0x1e,0xc9,0xf8}, +{0xbb,0x3d,0xf8,0xd2}, {0xb2,0x30,0xf3,0xdc}, {0xa9,0x27,0xee,0xce}, {0xa0,0x2a,0xe5,0xc0}, +{0x47,0xb1,0x3c,0x7a}, {0x4e,0xbc,0x37,0x74}, {0x55,0xab,0x2a,0x66}, {0x5c,0xa6,0x21,0x68}, +{0x63,0x85,0x10,0x42}, {0x6a,0x88,0x1b,0x4c}, {0x71,0x9f,0x06,0x5e}, {0x78,0x92,0x0d,0x50}, +{0x0f,0xd9,0x64,0x0a}, {0x06,0xd4,0x6f,0x04}, {0x1d,0xc3,0x72,0x16}, {0x14,0xce,0x79,0x18}, +{0x2b,0xed,0x48,0x32}, {0x22,0xe0,0x43,0x3c}, {0x39,0xf7,0x5e,0x2e}, {0x30,0xfa,0x55,0x20}, +{0x9a,0xb7,0x01,0xec}, {0x93,0xba,0x0a,0xe2}, {0x88,0xad,0x17,0xf0}, {0x81,0xa0,0x1c,0xfe}, +{0xbe,0x83,0x2d,0xd4}, {0xb7,0x8e,0x26,0xda}, {0xac,0x99,0x3b,0xc8}, {0xa5,0x94,0x30,0xc6}, +{0xd2,0xdf,0x59,0x9c}, {0xdb,0xd2,0x52,0x92}, {0xc0,0xc5,0x4f,0x80}, {0xc9,0xc8,0x44,0x8e}, +{0xf6,0xeb,0x75,0xa4}, {0xff,0xe6,0x7e,0xaa}, {0xe4,0xf1,0x63,0xb8}, {0xed,0xfc,0x68,0xb6}, +{0x0a,0x67,0xb1,0x0c}, {0x03,0x6a,0xba,0x02}, {0x18,0x7d,0xa7,0x10}, {0x11,0x70,0xac,0x1e}, +{0x2e,0x53,0x9d,0x34}, {0x27,0x5e,0x96,0x3a}, {0x3c,0x49,0x8b,0x28}, {0x35,0x44,0x80,0x26}, +{0x42,0x0f,0xe9,0x7c}, {0x4b,0x02,0xe2,0x72}, {0x50,0x15,0xff,0x60}, {0x59,0x18,0xf4,0x6e}, +{0x66,0x3b,0xc5,0x44}, {0x6f,0x36,0xce,0x4a}, {0x74,0x21,0xd3,0x58}, {0x7d,0x2c,0xd8,0x56}, +{0xa1,0x0c,0x7a,0x37}, {0xa8,0x01,0x71,0x39}, {0xb3,0x16,0x6c,0x2b}, {0xba,0x1b,0x67,0x25}, +{0x85,0x38,0x56,0x0f}, {0x8c,0x35,0x5d,0x01}, {0x97,0x22,0x40,0x13}, {0x9e,0x2f,0x4b,0x1d}, +{0xe9,0x64,0x22,0x47}, {0xe0,0x69,0x29,0x49}, {0xfb,0x7e,0x34,0x5b}, {0xf2,0x73,0x3f,0x55}, +{0xcd,0x50,0x0e,0x7f}, {0xc4,0x5d,0x05,0x71}, {0xdf,0x4a,0x18,0x63}, {0xd6,0x47,0x13,0x6d}, +{0x31,0xdc,0xca,0xd7}, {0x38,0xd1,0xc1,0xd9}, {0x23,0xc6,0xdc,0xcb}, {0x2a,0xcb,0xd7,0xc5}, +{0x15,0xe8,0xe6,0xef}, {0x1c,0xe5,0xed,0xe1}, {0x07,0xf2,0xf0,0xf3}, {0x0e,0xff,0xfb,0xfd}, +{0x79,0xb4,0x92,0xa7}, {0x70,0xb9,0x99,0xa9}, {0x6b,0xae,0x84,0xbb}, {0x62,0xa3,0x8f,0xb5}, +{0x5d,0x80,0xbe,0x9f}, {0x54,0x8d,0xb5,0x91}, {0x4f,0x9a,0xa8,0x83}, {0x46,0x97,0xa3,0x8d} +}; + +word32 rcon[30] = { + 0x01,0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x1b, 0x36, 0x6c, 0xd8, 0xab, 0x4d, 0x9a, 0x2f, 0x5e, 0xbc, 0x63, 0xc6, 0x97, 0x35, 0x6a, 0xd4, 0xb3, 0x7d, 0xfa, 0xef, 0xc5, 0x91 }; diff --git a/RakNet/Sources/Rijndael.h b/RakNet/Sources/Rijndael.h new file mode 100644 index 0000000..b984d64 --- /dev/null +++ b/RakNet/Sources/Rijndael.h @@ -0,0 +1,125 @@ +/// \file +/// \brief \b [Internal] AES encoding / decoding +/// rijndael-alg-fst.h v2.0 August '99 +/// Optimised ANSI C code +/// taken from the 'aescrypt' project: www.sf.net/projects/aescrypt +/// See LICENSE-EST for the license applicable to this file + + +/// \note Although the routines claim to support 192 and 256 bit blocks, +/// don't take your chances - stick to the 128 bit (16 byte) blocks unless +/// you've run tests to prove that 192 and 256 are correctly supported. +/// - Cirilo + + +#include + +#ifndef __RIJNDAEL_ALG_H +#define __RIJNDAEL_ALG_H + +#define MAXKC (256/32) +#define MAXROUNDS 14 + +typedef unsigned char word8; +typedef unsigned short word16; +typedef unsigned int word32; + +int rijndaelKeySched (word8 k[MAXKC][4], int keyBits, + word8 rk[MAXROUNDS+1][4][4]); +int rijndaelKeyEnctoDec (int keyBits, word8 W[MAXROUNDS+1][4][4]); +int rijndaelEncrypt (word8 a[16], word8 b[16], + word8 rk[MAXROUNDS+1][4][4]); +int rijndaelEncryptRound (word8 a[4][4], + word8 rk[MAXROUNDS+1][4][4], int rounds); +int rijndaelDecrypt (word8 a[16], word8 b[16], + word8 rk[MAXROUNDS+1][4][4]); +int rijndaelDecryptRound (word8 a[4][4], + word8 rk[MAXROUNDS+1][4][4], int rounds); + +#endif + +// End of algorithm headers. begin the AES API header defs + + +#ifndef __RIJNDAEL_API_H +#define __RIJNDAEL_API_H + +// rijndael-api-fst.h v2.0 August '99 +// Optimised ANSI C code + + +// Defines: +// Add any additional defines you need + + +#define DIR_ENCRYPT 0 // Are we encrpyting? +#define DIR_DECRYPT 1 // Are we decrpyting? +#define MODE_ECB 1 // Are we ciphering in ECB mode? +#define MODE_CBC 2 // Are we ciphering in CBC mode? +#define MODE_CFB1 3 // Are we ciphering in 1-bit CFB mode? +#ifndef TRUE +#define TRUE 1 +#endif +#ifndef FALSE +#define FALSE 0 +#endif +#define BITSPERBLOCK 128 // Default number of bits in a cipher block + +// Error Codes - CHANGE POSSIBLE: inclusion of additional error codes +#define BAD_KEY_DIR -1 // Key direction is invalid, e.g., unknown value +#define BAD_KEY_MAT -2 // Key material not of correct length +#define BAD_KEY_INSTANCE -3 // Key passed is not valid +#define BAD_CIPHER_MODE -4 // Params struct passed to cipherInit invalid +#define BAD_CIPHER_STATE -5 // Cipher in wrong state (e.g., not initialized) +#define BAD_BLOCK_LENGTH -6 +#define BAD_CIPHER_INSTANCE -7 + + +// CHANGE POSSIBLE: inclusion of algorithm specific defines +// 14.Dec.2005 Cirilo: keys are now unsigned char rather than hex (ASCII) +#define MAX_KEY_SIZE 32 // # of unsigned char's needed to represent a key +#define MAX_IV_SIZE 16 // # bytes needed to represent an IV + +// Typedefs: +// Typedef'ed data storage elements. Add any algorithm specific parameters at the bottom of the structs as appropriate. + +typedef unsigned char BYTE; + +// The structure for key information +typedef struct { + BYTE direction; /// Key used for encrypting or decrypting? + int keyLen; /// Length of the key + char keyMaterial[MAX_KEY_SIZE+1]; /// Raw key data in ASCII, e.g., user input or KAT values + /// The following parameters are algorithm dependent, replace or add as necessary + int blockLen; /// block length + word8 keySched[MAXROUNDS+1][4][4]; /// key schedule + } keyInstance; + +// The structure for cipher information +typedef struct { // changed order of the components + BYTE mode; /// MODE_ECB, MODE_CBC, or MODE_CFB1 + BYTE IV[MAX_IV_SIZE]; /// A possible Initialization Vector for ciphering + // Add any algorithm specific parameters needed here + int blockLen; /// Sample: Handles non-128 bit block sizes (if available) + } cipherInstance; + + +// Function protoypes +// CHANGED: makeKey(): parameter blockLen added this parameter is absolutely necessary if you want to +// setup the round keys in a variable block length setting +// cipherInit(): parameter blockLen added (for obvious reasons) + +int makeKey(keyInstance *key, BYTE direction, int keyLen, char *keyMaterial); + +int cipherInit(cipherInstance *cipher, BYTE mode, char *IV); + +int blockEncrypt(cipherInstance *cipher, keyInstance *key, BYTE *input, + int inputLen, BYTE *outBuffer); + +int blockDecrypt(cipherInstance *cipher, keyInstance *key, BYTE *input, + int inputLen, BYTE *outBuffer); +int cipherUpdateRounds(cipherInstance *cipher, keyInstance *key, BYTE *input, + int inputLen, BYTE *outBuffer, int Rounds); + + +#endif // __RIJNDAEL_API_H diff --git a/RakNet/Sources/Router.cpp b/RakNet/Sources/Router.cpp new file mode 100644 index 0000000..9c9c1a4 --- /dev/null +++ b/RakNet/Sources/Router.cpp @@ -0,0 +1,317 @@ +#include "NativeFeatureIncludes.h" +#if _RAKNET_SUPPORT_Router==1 + +#include "Router.h" +#include "BitStream.h" +#include "RakPeerInterface.h" +#include "MessageIdentifiers.h" +#include "RakAssert.h" + +//#define _DO_PRINTF + +#ifdef _DO_PRINTF +#include +#endif + +#ifdef _MSC_VER +#pragma warning( push ) +#endif + +Router::Router() +{ + graph=0; + restrictByType=false; + DataStructures::OrderedList::IMPLEMENT_DEFAULT_COMPARISON(); +} +Router::~Router() +{ +} +void Router::SetRestrictRoutingByType(bool restrict__) +{ + restrictByType=restrict__; +} +void Router::AddAllowedType(unsigned char messageId) +{ + if (allowedTypes.HasData(messageId)==false) + allowedTypes.Insert(messageId,messageId, true, __FILE__,__LINE__); +} +void Router::RemoveAllowedType(unsigned char messageId) +{ + if (allowedTypes.HasData(messageId)==true) + allowedTypes.Remove(messageId); +} +void Router::SetConnectionGraph(DataStructures::WeightedGraph *connectionGraph) +{ + graph=connectionGraph; +} +bool Router::Send( const char *data, BitSize_t bitLength, PacketPriority priority, PacketReliability reliability, char orderingChannel, SystemAddress systemAddress ) +{ + if (systemAddress!=UNASSIGNED_SYSTEM_ADDRESS) + { + RakAssert(data); + RakAssert(bitLength); + // Prevent recursion in case a routing call itself calls the router + if (bitLength>=8 && data[0]==ID_ROUTE_AND_MULTICAST) + return false; + + SystemAddressList systemAddressList; + systemAddressList.AddSystem(systemAddress); + return Send((char*)data, bitLength, priority, reliability, orderingChannel, &systemAddressList); + } + return false; +} +bool Router::Send( char *data, BitSize_t bitLength, PacketPriority priority, PacketReliability reliability, char orderingChannel, SystemAddressList *recipients ) +{ + RakAssert(data); + RakAssert(bitLength); + if (recipients->GetList()->Size()==0) + return false; + if (bitLength==0) + return false; + DataStructures::Tree tree; + SystemAddress root; + root = rakPeerInterface->GetExternalID(rakPeerInterface->GetSystemAddressFromIndex(0)); + if (root==UNASSIGNED_SYSTEM_ADDRESS) + return false; + DataStructures::List recipientList; + unsigned i; + for (i=0; i < recipients->Size(); i++) + recipientList.Insert(ConnectionGraph::SystemAddressAndGroupId(recipients->GetList()->operator [](i),0, UNASSIGNED_RAKNET_GUID), __FILE__, __LINE__); + if (graph->GetSpanningTree(tree, &recipientList, ConnectionGraph::SystemAddressAndGroupId(root,0,UNASSIGNED_RAKNET_GUID), 65535)==false) + return false; + + RakNet::BitStream out; + + // Write timestamp first, if the user had a timestamp + if (data[0]==ID_TIMESTAMP && bitLength >= BYTES_TO_BITS(sizeof(MessageID)+sizeof(RakNetTime))) + { + out.Write(data, sizeof(MessageID)+sizeof(RakNetTime)); + data+=sizeof(MessageID)+sizeof(RakNetTime); + bitLength-=BYTES_TO_BITS(sizeof(MessageID)+sizeof(RakNetTime)); + } + + SendTree(priority, reliability, orderingChannel, &tree, data, bitLength, &out, recipients); + return true; +} +void Router::SendTree(PacketPriority priority, PacketReliability reliability, char orderingChannel, DataStructures::Tree *tree, const char *data, BitSize_t bitLength, RakNet::BitStream *out, SystemAddressList *recipients) +{ + BitSize_t outputOffset; + + // Write routing identifer + out->Write((MessageID)ID_ROUTE_AND_MULTICAST); + + // Write the send parameters + out->WriteCompressed((unsigned char)priority); + out->WriteCompressed((unsigned char)reliability); + out->WriteCompressed((unsigned char)orderingChannel); + + // Write the user payload length + out->Write((unsigned int)bitLength); +// out->PrintBits(); +// payload->PrintBits(); + + out->AlignWriteToByteBoundary(); +// payload->AlignReadToByteBoundary(); +// out->Write(payload, payload->GetNumberOfUnreadBits()); +// out->PrintBits(); + if ((bitLength % 8)==0) + out->Write(data, BITS_TO_BYTES(bitLength)); + else + out->WriteBits((const unsigned char*)data, bitLength, false); + + // Save where to start writing per-system data + outputOffset=out->GetWriteOffset(); + + // Write every child of the root of the tree (SystemAddress, isRecipient, branch) + unsigned i; + for (i=0; i < tree->children.Size(); i++) + { + // Start writing at the per-system data byte + out->SetWriteOffset(outputOffset); + + // Write our external IP to designate the sender + out->Write(rakPeerInterface->GetExternalID(tree->children[i]->data.systemAddress)); + + // Serialize the tree + SerializePreorder(tree->children[i], out, recipients); + + // Send to the first hop +#ifdef _DO_PRINTF + RAKNET_DEBUG_PRINTF("%i sending to %i\n", rakPeerInterface->GetExternalID(tree->children[i]->data.systemAddress).port, tree->children[i]->data.systemAddress.port); +#endif + SendUnified(out, priority, reliability, orderingChannel, tree->children[i]->data.systemAddress, false); + } +} +PluginReceiveResult Router::OnReceive(Packet *packet) +{ + if (packet->data[0]==ID_ROUTE_AND_MULTICAST || + (packet->length>5 && packet->data[0]==ID_TIMESTAMP && packet->data[5]==ID_ROUTE_AND_MULTICAST)) + { +#ifdef _DO_PRINTF + RAKNET_DEBUG_PRINTF("%i got routed message from %i\n", peer->GetExternalID(packet->systemAddress).port, packet->systemAddress.port); +#endif + RakNetTime timestamp; + PacketPriority priority; + PacketReliability reliability; + unsigned char orderingChannel; + SystemAddress originalSender; + RakNet::BitStream out; + BitSize_t outStartingOffset; + unsigned int payloadBitLength; + unsigned payloadWriteByteOffset; + RakNet::BitStream incomingBitstream(packet->data, packet->length, false); + incomingBitstream.IgnoreBits(8); + + if (packet->data[0]==ID_TIMESTAMP) + { + incomingBitstream.Read(timestamp); + out.Write((MessageID)ID_TIMESTAMP); + out.Write(timestamp); + incomingBitstream.IgnoreBits(8); + } + + // Read the send parameters + unsigned char c; + incomingBitstream.ReadCompressed(c); + priority=(PacketPriority)c; + incomingBitstream.ReadCompressed(c); + reliability=(PacketReliability)c; + incomingBitstream.ReadCompressed(orderingChannel); + incomingBitstream.Read(payloadBitLength); + + out.Write((MessageID)ID_ROUTE_AND_MULTICAST); + out.WriteCompressed((unsigned char)priority); + out.WriteCompressed((unsigned char)reliability); + out.WriteCompressed((unsigned char)orderingChannel); + out.Write(payloadBitLength); + out.AlignWriteToByteBoundary(); + incomingBitstream.AlignReadToByteBoundary(); + payloadWriteByteOffset=(unsigned int) BITS_TO_BYTES(out.GetWriteOffset()); + out.Write(&incomingBitstream, payloadBitLength); // This write also does a read on incomingBitStream + + if (restrictByType) + { + RakNet::BitStream t(out.GetData()+payloadWriteByteOffset, sizeof(unsigned char), false); + MessageID messageID; + t.Read(messageID); + if (allowedTypes.HasData(messageID)==false) + return RR_STOP_PROCESSING_AND_DEALLOCATE; // Don't route restricted types + } + + incomingBitstream.Read(originalSender); + out.Write(originalSender); + outStartingOffset=out.GetWriteOffset(); + + // Deserialize the root + bool hasData=false; + SystemAddress recipient; + unsigned short numberOfChildren; + incomingBitstream.Read(hasData); + incomingBitstream.Read(recipient); // This should be our own address + if (incomingBitstream.ReadCompressed(numberOfChildren)==false) + { +#ifdef _DEBUG + RakAssert(0); +#endif + return RR_STOP_PROCESSING_AND_DEALLOCATE; + } + + unsigned childIndex; + bool childHasData=false; + SystemAddress childRecipient; + unsigned short childNumberOfChildren; + SystemAddress immediateRecipient; + immediateRecipient=UNASSIGNED_SYSTEM_ADDRESS; + int pendingNodeCount=0; + + for (childIndex=0; childIndex < numberOfChildren; childIndex++) + { + while (pendingNodeCount!=-1) + { + // Copy out the serialized subtree for this child + incomingBitstream.Read(childHasData); + incomingBitstream.Read(childRecipient); + if (!incomingBitstream.ReadCompressed(childNumberOfChildren)) + return RR_STOP_PROCESSING_AND_DEALLOCATE; + if (immediateRecipient==UNASSIGNED_SYSTEM_ADDRESS) + { + immediateRecipient=childRecipient; + } + + pendingNodeCount+=childNumberOfChildren-1; + + out.Write(childHasData); + out.Write(childRecipient); + out.WriteCompressed(childNumberOfChildren); + } + +#ifdef _DO_PRINTF + RAKNET_DEBUG_PRINTF("%i routing to %i\n", peer->GetExternalID(packet->systemAddress).port, immediateRecipient.port); +#endif + + // Send what we got so far + SendUnified(&out, priority, reliability, orderingChannel, immediateRecipient, false); + + // Restart writing the per recipient data + out.SetWriteOffset(outStartingOffset); + + // Reread the top level node + immediateRecipient=UNASSIGNED_SYSTEM_ADDRESS; + + pendingNodeCount=0; + + } + + // Write the user payload to the packet struct if this is a destination and change the sender and return true + if (hasData) + { +#ifdef _DO_PRINTF + RAKNET_DEBUG_PRINTF("%i returning payload to user\n", peer->GetExternalID(packet->systemAddress).port); +#endif + + if (packet->data[0]==ID_TIMESTAMP ) + { + memcpy( packet->data + sizeof(RakNetTime)+sizeof(unsigned char), out.GetData()+payloadWriteByteOffset, BITS_TO_BYTES(payloadBitLength) ); + packet->bitSize=BYTES_TO_BITS(sizeof(RakNetTime)+sizeof(unsigned char))+payloadBitLength; + } + else + { + memcpy( packet->data, out.GetData()+payloadWriteByteOffset, BITS_TO_BYTES(payloadBitLength) ); + packet->bitSize=payloadBitLength; + } + packet->length=(unsigned int) BITS_TO_BYTES(packet->bitSize); + packet->systemAddress.systemIndex=(SystemIndex)-1; + packet->systemAddress=originalSender; + + return RR_CONTINUE_PROCESSING; + } + + // Absorb + return RR_STOP_PROCESSING_AND_DEALLOCATE; + } + + return RR_CONTINUE_PROCESSING; +} +void Router::OnAttach(void) +{ + rakPeerInterface->SetRouterInterface(this); +} +void Router::OnDetach(void) +{ + rakPeerInterface->RemoveRouterInterface(this); +} +void Router::SerializePreorder(DataStructures::Tree *tree, RakNet::BitStream *out, SystemAddressList *recipients) const +{ + unsigned i; + out->Write((bool) (recipients->GetList()->GetIndexOf(tree->data.systemAddress)!=MAX_UNSIGNED_LONG)); + out->Write(tree->data.systemAddress); + out->WriteCompressed((unsigned short) tree->children.Size()); + for (i=0; i < tree->children.Size(); i++) + SerializePreorder(tree->children[i], out, recipients); +} + +#ifdef _MSC_VER +#pragma warning( pop ) +#endif + +#endif // _RAKNET_SUPPORT_* diff --git a/RakNet/Sources/Router.h b/RakNet/Sources/Router.h new file mode 100644 index 0000000..c1e53fe --- /dev/null +++ b/RakNet/Sources/Router.h @@ -0,0 +1,94 @@ +/// \file +/// \brief Router plugin. Allows you to send to systems you are not directly connected to, and to route those messages +/// +/// This file is part of RakNet Copyright 2003 Jenkins Software LLC +/// +/// Usage of RakNet is subject to the appropriate license agreement. + + +#include "NativeFeatureIncludes.h" +#if _RAKNET_SUPPORT_Router==1 + + +#ifndef __ROUTER_PLUGIN_H +#define __ROUTER_PLUGIN_H + +class RakPeerInterface; +#include "RakNetTypes.h" +#include "PluginInterface2.h" +#include "DS_OrderedList.h" +#include "DS_WeightedGraph.h" +#include "PacketPriority.h" +#include "SystemAddressList.h" +#include "RouterInterface.h" +#include "Export.h" +#include "ConnectionGraph.h" + +/// \defgroup ROUTER_GROUP Router +/// \brief Deprecated. Doesn't work well in practice. Use it at your own risk +/// \details +/// \ingroup PLUGINS_GROUP + +/// \ingroup ROUTER_GROUP +/// \brief Used to route messages between peers +/// \details +/// \deprecated +class RAK_DLL_EXPORT Router : public PluginInterface2 , public RouterInterface +{ +public: + Router(); + virtual ~Router(); + + // -------------------------------------------------------------------------------------------- + // User functions + // -------------------------------------------------------------------------------------------- + /// We can restrict what kind of messages are routed by this plugin. + /// This is useful for security, since you usually want to restrict what kind of messages you have to worry about from (as an example) other + /// clients in a client / server system + /// \param[in] restrict True to restrict what messages will be routed. False to not do so (default). + void SetRestrictRoutingByType(bool restrict__); + + /// If types are restricted, this adds an allowed message type to be routed + /// \param[in] messageId The type to not allow routing of. + void AddAllowedType(unsigned char messageId); + + /// Removes a restricted type previously added with AddRestrictedType + /// \param[in] messageId The type to no longer restrict routing of. + void RemoveAllowedType(unsigned char messageId); + + /// Set the connection graph, which is a weighted graph of the topology of the network. You can easily get this from the + /// ConnectionGraph plugin. See the 'router' sample for usage. + /// This is necessary if you want to send (not necessary just to route). + /// \param[in] connectionGraph A weighted graph representing the topology of the network. + void SetConnectionGraph(DataStructures::WeightedGraph *connectionGraph); + + /// Sends a bitstream to one or more systems. If multiple systems are specified, the message will be multicasted using a minimum spanning tree + /// \pre You just have called SetConnectionGraph with a valid graph representing the network topology + /// \note Single target sends from RakPeer with this plugin installed will also be routed. Sends from other plugins will also be routed as long as this plugin is attached first. + /// \param[in] data The data to send + /// \param[in] bitLength How many bits long data is + /// \param[in] priority What priority level to send on. + /// \param[in] reliability How reliability to send this data + /// \param[in] orderingChannel When using ordered or sequenced packets, what channel to order these on.- Packets are only ordered relative to other packets on the same stream + /// \param[in] recipients A list of recipients to send to. To send to one recipient, just pass a SystemAddress + /// \return True on success, false mostly if the connection graph cannot find the destination. + bool Send( char *data, BitSize_t bitLength, PacketPriority priority, PacketReliability reliability, char orderingChannel, SystemAddressList *recipients ); + bool Send( const char *data, BitSize_t bitLength, PacketPriority priority, PacketReliability reliability, char orderingChannel, SystemAddress systemAddress ); + + // -------------------------------------------------------------------------------------------- + // Packet handling functions + // -------------------------------------------------------------------------------------------- + virtual void OnAttach(void); + virtual void OnDetach(void); + virtual PluginReceiveResult OnReceive(Packet *packet); +protected: + void SendTree(PacketPriority priority, PacketReliability reliability, char orderingChannel, DataStructures::Tree *tree, const char *data, BitSize_t bitLength, RakNet::BitStream *out, SystemAddressList *recipients); + void SerializePreorder(DataStructures::Tree *tree, RakNet::BitStream *out, SystemAddressList *recipients) const; + DataStructures::WeightedGraph *graph; + bool restrictByType; + DataStructures::OrderedList allowedTypes; +}; + +#endif + +#endif // _RAKNET_SUPPORT_* diff --git a/RakNet/Sources/Router2.cpp b/RakNet/Sources/Router2.cpp new file mode 100644 index 0000000..4eba1cd --- /dev/null +++ b/RakNet/Sources/Router2.cpp @@ -0,0 +1,851 @@ +#include "NativeFeatureIncludes.h" +#if _RAKNET_SUPPORT_Router2==1 + +#include "Router2.h" +#include "RakPeerInterface.h" +#include "BitStream.h" +#include "RakNetTime.h" +#include "GetTime.h" +#include "DS_OrderedList.h" +#include "SocketLayer.h" + +using namespace RakNet; + +enum Router2MessageIdentifiers +{ + ID_ROUTER_2_QUERY_FORWARDING, + ID_ROUTER_2_REPLY_FORWARDING, + ID_ROUTER_2_REQUEST_FORWARDING, + ID_ROUTER_2_INCREASE_TIMEOUT, +}; +Router2::ConnnectRequest::ConnnectRequest() +{ + +} +Router2::ConnnectRequest::~ConnnectRequest() +{ + +} +Router2::Router2() +{ + udpForwarder=0; + maximumForwardingRequests=0; +} +Router2::~Router2() +{ + ClearAll(); + + if (udpForwarder) + { + udpForwarder->Shutdown(); + RakNet::OP_DELETE(udpForwarder,__FILE__,__LINE__); + } +} +void Router2::ClearMinipunches(void) +{ + miniPunchesInProgress.Clear(false,__FILE__,__LINE__); +} +void Router2::ClearConnectionRequests(void) +{ + for (unsigned int i=0; i < connectionRequests.Size(); i++) + { + RakNet::OP_DELETE(connectionRequests[i],__FILE__,__LINE__); + } + connectionRequests.Clear(false,__FILE__,__LINE__); +} +bool Router2::ConnectInternal(RakNetGUID endpointGuid, bool returnConnectionLostOnFailure) +{ + int largestPing = GetLargestPingAmongConnectedSystems(); + if (largestPing<0) + { + printf("Router2 failed at %s:%i\n", __FILE__, __LINE__); + + // Not connected to anyone + return false; + } + + // ALready in progress? + if (GetConnectionRequestIndex(endpointGuid)!=(unsigned int)-1) + { + printf("Router2 failed at %s:%i\n", __FILE__, __LINE__); + + return false; + } + + // StoreRequest(endpointGuid, Largest(ping*2), systemsSentTo). Set state REQUEST_STATE_QUERY_FORWARDING + Router2::ConnnectRequest *cr = RakNet::OP_NEW(__FILE__,__LINE__); + DataStructures::List addresses; + DataStructures::List guids; + rakPeerInterface->GetSystemList(addresses, guids); + cr->requestState=R2RS_REQUEST_STATE_QUERY_FORWARDING; + cr->pingTimeout=RakNet::GetTimeMS()+largestPing*2+1000; + cr->endpointGuid=endpointGuid; + cr->returnConnectionLostOnFailure=returnConnectionLostOnFailure; + for (unsigned int i=0; i < guids.Size(); i++) + { + ConnectionRequestSystem crs; + if (guids[i]!=endpointGuid) + { + crs.guid=guids[i]; + crs.pingToEndpoint=-1; + cr->connectionRequestSystems.Push(crs,__FILE__,__LINE__); + + // Broadcast(ID_ROUTER_2_QUERY_FORWARDING, endpointGuid); + RakNet::BitStream bsOut; + bsOut.Write((MessageID)ID_ROUTER_2_INTERNAL); + bsOut.Write((unsigned char) ID_ROUTER_2_QUERY_FORWARDING); + bsOut.Write(endpointGuid); + rakPeerInterface->Send(&bsOut,MEDIUM_PRIORITY,RELIABLE_ORDERED,0,crs.guid,false); + } + } + connectionRequests.Push(cr,__FILE__,__LINE__); + + return true; +} +void Router2::EstablishRouting(RakNetGUID endpointGuid) +{ + // if (alreadyConnected to endpointGuid) Return false + if (rakPeerInterface->IsConnected(endpointGuid,false,false)==true) + { + printf("Router2 failed at %s:%i (already connected)\n", __FILE__, __LINE__); + return; + } + + ConnectInternal(endpointGuid,false); +} +void Router2::SetMaximumForwardingRequests(int max) +{ + if (max>0 && maximumForwardingRequests<=0) + { + udpForwarder = RakNet::OP_NEW(__FILE__,__LINE__); + udpForwarder->Startup(); + } + else if (max<=0 && maximumForwardingRequests>0) + { + udpForwarder->Shutdown(); + RakNet::OP_DELETE(udpForwarder,__FILE__,__LINE__); + udpForwarder=0; + } + + maximumForwardingRequests=max; +} +PluginReceiveResult Router2::OnReceive(Packet *packet) +{ + SystemAddress sa; + RakNet::BitStream bs(packet->data,packet->length,false); + if (packet->data[0]==ID_ROUTER_2_INTERNAL) + { + switch (packet->data[1]) + { + case ID_ROUTER_2_QUERY_FORWARDING: + { + OnQueryForwarding(packet); + return RR_STOP_PROCESSING_AND_DEALLOCATE; + } + case ID_ROUTER_2_REPLY_FORWARDING: + { + OnQueryForwardingReply(packet); + return RR_STOP_PROCESSING_AND_DEALLOCATE; + } + case ID_ROUTER_2_REQUEST_FORWARDING: + { + OnRequestForwarding(packet); + return RR_STOP_PROCESSING_AND_DEALLOCATE; + } + case ID_ROUTER_2_INCREASE_TIMEOUT: + { + /// The routed system wants more time to stay alive on no communication, in case the router drops or crashes + rakPeerInterface->SetTimeoutTime(rakPeerInterface->GetTimeoutTime(packet->systemAddress)+10000, packet->systemAddress); + return RR_STOP_PROCESSING_AND_DEALLOCATE; + } + } + } + else if (packet->data[0]==ID_OUT_OF_BAND_INTERNAL && packet->length>=2) + { + switch (packet->data[1]) + { + case ID_ROUTER_2_REPLY_TO_SENDER_PORT: + { + RakNet::BitStream bsOut; + bsOut.Write(packet->guid); + SendOOBFromRakNetPort(ID_ROUTER_2_MINI_PUNCH_REPLY, &bsOut, packet->systemAddress); + return RR_STOP_PROCESSING_AND_DEALLOCATE; + } + case ID_ROUTER_2_REPLY_TO_SPECIFIED_PORT: + { + RakNet::BitStream bsOut; + bsOut.Write(packet->guid); + bs.IgnoreBytes(2); + sa.binaryAddress=packet->systemAddress.binaryAddress; + bs.Read(sa.port); + RakAssert(sa.port!=0); + SendOOBFromRakNetPort(ID_ROUTER_2_MINI_PUNCH_REPLY, &bsOut, sa); + return RR_STOP_PROCESSING_AND_DEALLOCATE; + } + case ID_ROUTER_2_MINI_PUNCH_REPLY: + OnMiniPunchReply(packet); + return RR_STOP_PROCESSING_AND_DEALLOCATE; + case ID_ROUTER_2_MINI_PUNCH_REPLY_BOUNCE: + OnMiniPunchReplyBounce(packet); + return RR_STOP_PROCESSING_AND_DEALLOCATE; + case ID_ROUTER_2_REROUTE: + { + OnReroute(packet); + return RR_STOP_PROCESSING_AND_DEALLOCATE; + } + } + } + else if (packet->data[0]==ID_CONNECTION_ATTEMPT_FAILED) + { + unsigned int forwardedConnectionIndex=0; + while (forwardedConnectionIndexsystemAddress) + { + packet->guid=forwardedConnectionList[forwardedConnectionIndex].endpointGuid; + forwardedConnectionList.RemoveAtIndexFast(forwardedConnectionIndex); + } + else + forwardedConnectionIndex++; + } + } + else if (packet->data[0]==ID_ROUTER_2_FORWARDING_ESTABLISHED) + { + if (OnForwardingSuccess(packet)==false) + return RR_STOP_PROCESSING_AND_DEALLOCATE; + } + else if (packet->data[0]==ID_CONNECTION_REQUEST_ACCEPTED) + { + unsigned int forwardingIndex; + for (forwardingIndex=0; forwardingIndex < forwardedConnectionList.Size(); forwardingIndex++) + { + if (forwardedConnectionList[forwardingIndex].endpointGuid==packet->guid) + break; + } + + if (forwardingIndexSend(&bsOut,HIGH_PRIORITY,RELIABLE,0,packet->guid,false); + } + } + + return RR_CONTINUE_PROCESSING; +} +void Router2::Update(void) +{ + RakNetTimeMS curTime = RakNet::GetTimeMS(); + unsigned int connectionRequestIndex=0; + while (connectionRequestIndex < connectionRequests.Size()) + { + ConnnectRequest* connectionRequest = connectionRequests[connectionRequestIndex]; + // pingTimeout is only used with R2RS_REQUEST_STATE_QUERY_FORWARDING + if (connectionRequest->requestState==R2RS_REQUEST_STATE_QUERY_FORWARDING && + connectionRequest->pingTimeout < curTime) + { + bool anyRemoved=false; + unsigned int connectionRequestGuidIndex=0; + while (connectionRequestGuidIndex < connectionRequest->connectionRequestSystems.Size()) + { + if (connectionRequest->connectionRequestSystems[connectionRequestGuidIndex].pingToEndpoint<0) + { + anyRemoved=true; + connectionRequest->connectionRequestSystems.RemoveAtIndexFast(connectionRequestGuidIndex); + } + else + { + connectionRequestGuidIndex++; + } + } + + if (anyRemoved) + { + if (UpdateForwarding(connectionRequestIndex)==false) + { + RemoveConnectionRequest(connectionRequestIndex); + } + else + { + connectionRequestIndex++; + } + } + else + { + connectionRequestIndex++; + } + } + else + { + connectionRequestIndex++; + } + } + + unsigned int i=0; + while (i < miniPunchesInProgress.Size()) + { + if (miniPunchesInProgress[i].timeoutminiPunchesInProgress[i].nextAction) + { + miniPunchesInProgress[i].nextAction=curTime+100; + SendOOBMessages(&miniPunchesInProgress[i]); + } + else + i++; + } + +} +void Router2::OnClosedConnection(SystemAddress systemAddress, RakNetGUID rakNetGUID, PI2_LostConnectionReason lostConnectionReason ) +{ + (void) lostConnectionReason; + (void) systemAddress; + + + unsigned int forwardedConnectionIndex=0; + while (forwardedConnectionIndexGetGuidIndex(rakNetGUID); + if (connectionRequestGuidIndex!=(unsigned int)-1) + { + connectionRequests[connectionRequestIndex]->connectionRequestSystems.RemoveAtIndexFast(connectionRequestGuidIndex); + if (UpdateForwarding(connectionRequestIndex)==false) + { + RemoveConnectionRequest(connectionRequestIndex); + } + else + { + connectionRequestIndex++; + } + } + else + { + connectionRequestIndex++; + } + } + + + unsigned int i=0; + while (i < miniPunchesInProgress.Size()) + { + if (miniPunchesInProgress[i].sourceGuid==rakNetGUID || miniPunchesInProgress[i].endpointGuid==rakNetGUID) + { + if (miniPunchesInProgress[i].sourceGuid!=rakNetGUID) + ReturnFailureOnCannotForward(miniPunchesInProgress[i].sourceGuid, miniPunchesInProgress[i].endpointGuid); + miniPunchesInProgress.RemoveAtIndexFast(i); + } + else + i++; + } +} +void Router2::OnFailedConnectionAttempt(SystemAddress systemAddress, PI2_FailedConnectionAttemptReason failedConnectionAttemptReason) +{ + (void) failedConnectionAttemptReason; + (void) systemAddress; +} +void Router2::OnNewConnection(SystemAddress systemAddress, RakNetGUID rakNetGUID, bool isIncoming) +{ + (void) isIncoming; + + unsigned int forwardedConnectionIndex=0; + while (forwardedConnectionIndexSetTimeoutTime(1000000, systemAddress); + forwardedConnectionIndex++; + } +} +void Router2::OnRakPeerShutdown(void) +{ + ClearAll(); +} +bool Router2::UpdateForwarding(unsigned int connectionRequestIndex) +{ + ConnnectRequest* connectionRequest = connectionRequests[connectionRequestIndex]; + if (connectionRequest->connectionRequestSystems.Size()==0) + { + printf("Router2 failed at %s:%i\n", __FILE__, __LINE__); + if (connectionRequest->returnConnectionLostOnFailure) + ReturnToUser(ID_CONNECTION_LOST, connectionRequest->endpointGuid, UNASSIGNED_SYSTEM_ADDRESS); + else + ReturnToUser(ID_ROUTER_2_FORWARDING_NO_PATH, connectionRequest->endpointGuid, UNASSIGNED_SYSTEM_ADDRESS); + + for (unsigned int forwardedConnectionIndex=0; forwardedConnectionIndex < forwardedConnectionList.Size(); forwardedConnectionIndex++) + { + if (forwardedConnectionList[forwardedConnectionIndex].endpointGuid==connectionRequest->endpointGuid) + { + forwardedConnectionList.RemoveAtIndexFast(forwardedConnectionIndex); + break; + } + } + + return false; + } + + if (connectionRequest->requestState==R2RS_REQUEST_STATE_QUERY_FORWARDING) + { + for (unsigned int i=0; i < connectionRequest->connectionRequestSystems.Size(); i++) + { + if (connectionRequest->connectionRequestSystems[i].pingToEndpoint<0) + return true; + } + + RequestForwarding(connectionRequestIndex); + } +// else if (connectionRequest->requestState==REQUEST_STATE_REQUEST_FORWARDING) +// { +// RequestForwarding(connectionRequestIndex); +// } + + return true; +} +void Router2::RemoveConnectionRequest(unsigned int connectionRequestIndex) +{ + RakNet::OP_DELETE(connectionRequests[connectionRequestIndex],__FILE__,__LINE__); + connectionRequests.RemoveAtIndexFast(connectionRequestIndex); +} +int ConnectionRequestSystemComp( const Router2::ConnectionRequestSystem & key, const Router2::ConnectionRequestSystem &data ) +{ + if (key.pingToEndpoint * (key.usedForwardingEntries+1) < data.pingToEndpoint * (data.usedForwardingEntries+1)) + return -1; + if (key.pingToEndpoint * (key.usedForwardingEntries+1) == data.pingToEndpoint * (data.usedForwardingEntries+1)) + return 1; + if (key.guid < data.guid) + return -1; + if (key.guid > data.guid) + return -1; + return 0; +} +void Router2::RequestForwarding(unsigned int connectionRequestIndex) +{ + ConnnectRequest* connectionRequest = connectionRequests[connectionRequestIndex]; + RakAssert(connectionRequest->requestState==R2RS_REQUEST_STATE_QUERY_FORWARDING); + connectionRequest->requestState=REQUEST_STATE_REQUEST_FORWARDING; + + if (connectionRequest->GetGuidIndex(connectionRequest->lastRequestedForwardingSystem)!=(unsigned int)-1) + { + printf("Router2 failed at %s:%i\n", __FILE__, __LINE__); + return; + } + + // Prioritize systems to request forwarding + DataStructures::OrderedList commandList; + unsigned int connectionRequestGuidIndex; + for (connectionRequestGuidIndex=0; connectionRequestGuidIndex < connectionRequest->connectionRequestSystems.Size(); connectionRequestGuidIndex++) + { + RakAssert(connectionRequest->connectionRequestSystems[connectionRequestGuidIndex].pingToEndpoint>=0); + commandList.Insert(connectionRequest->connectionRequestSystems[connectionRequestGuidIndex], + connectionRequest->connectionRequestSystems[connectionRequestGuidIndex], + true, + __FILE__, + __LINE__); + } + + connectionRequest->lastRequestedForwardingSystem=commandList[0].guid; + + RakNet::BitStream bsOut; + bsOut.Write((MessageID)ID_ROUTER_2_INTERNAL); + bsOut.Write((unsigned char) ID_ROUTER_2_REQUEST_FORWARDING); + bsOut.Write(connectionRequest->endpointGuid); + rakPeerInterface->Send(&bsOut,MEDIUM_PRIORITY,RELIABLE_ORDERED,0,connectionRequest->lastRequestedForwardingSystem,false); +} +void Router2::SendFailureOnCannotForward(RakNetGUID sourceGuid, RakNetGUID endpointGuid) +{ + RakNet::BitStream bsOut; + bsOut.Write((MessageID)ID_ROUTER_2_INTERNAL); + bsOut.Write((unsigned char) ID_ROUTER_2_REPLY_FORWARDING); + bsOut.Write(endpointGuid); + bsOut.Write(false); + rakPeerInterface->Send(&bsOut,MEDIUM_PRIORITY,RELIABLE_ORDERED,0,sourceGuid,false); +} +int Router2::ReturnFailureOnCannotForward(RakNetGUID sourceGuid, RakNetGUID endpointGuid) +{ + // If the number of systems we are currently forwarding>=maxForwarding, return ID_ROUTER_2_REPLY_FORWARDING,endpointGuid,false + if (udpForwarder==0 || udpForwarder->GetUsedForwardEntries()/2>maximumForwardingRequests) + { + printf("Router2 failed at %s:%i\n", __FILE__, __LINE__); + SendFailureOnCannotForward(sourceGuid,endpointGuid); + return -1; + } + + int pingToEndpoint; + pingToEndpoint = rakPeerInterface->GetAveragePing(endpointGuid); + if (pingToEndpoint==-1) + { + printf("Router2 failed at %s:%i\n", __FILE__, __LINE__); + SendFailureOnCannotForward(sourceGuid,endpointGuid); + return -1; + } + return pingToEndpoint; +} +void Router2::OnQueryForwarding(Packet *packet) +{ + RakNet::BitStream bs(packet->data, packet->length, false); + bs.IgnoreBytes(sizeof(MessageID) + sizeof(unsigned char)); + RakNetGUID endpointGuid; + // Read endpointGuid + bs.Read(endpointGuid); + + int pingToEndpoint = ReturnFailureOnCannotForward(packet->guid, endpointGuid); + if (pingToEndpoint==-1) + { + printf("Router2 failed at %s:%i\n", __FILE__, __LINE__); + return; + } + + // If we are connected to endpointGuid, reply ID_ROUTER_2_REPLY_FORWARDING,endpointGuid,true,ping,numCurrentlyForwarding + RakNet::BitStream bsOut; + bsOut.Write((MessageID)ID_ROUTER_2_INTERNAL); + bsOut.Write((unsigned char) ID_ROUTER_2_REPLY_FORWARDING); + bsOut.Write(endpointGuid); + bsOut.Write(true); + bsOut.Write((unsigned short) pingToEndpoint); + bsOut.Write((unsigned short) udpForwarder->GetUsedForwardEntries()/2); + rakPeerInterface->Send(&bsOut,MEDIUM_PRIORITY,RELIABLE_ORDERED,0,packet->guid,false); +} +void Router2::OnQueryForwardingReply(Packet *packet) +{ + RakNet::BitStream bs(packet->data, packet->length, false); + bs.IgnoreBytes(sizeof(MessageID) + sizeof(unsigned char)); + RakNetGUID endpointGuid; + bs.Read(endpointGuid); + // Find endpointGuid among stored requests + + unsigned int connectionRequestIndex = GetConnectionRequestIndex(endpointGuid); + if (connectionRequestIndex==(unsigned int)-1) + { + printf("Router2 failed at %s:%i\n", __FILE__, __LINE__); + return; + } + unsigned int connectionRequestGuidIndex = connectionRequests[connectionRequestIndex]->GetGuidIndex(packet->guid); + if (connectionRequestGuidIndex==(unsigned int)-1) + { + printf("Router2 failed at %s:%i\n", __FILE__, __LINE__); + return; + } + + bool canForward; + bs.Read(canForward); + if (canForward) + { + unsigned short pingToEndpoint; + unsigned short usedEntries; + bs.Read(pingToEndpoint); + bs.Read(usedEntries); + connectionRequests[connectionRequestIndex]->connectionRequestSystems[connectionRequestGuidIndex].usedForwardingEntries=usedEntries; + connectionRequests[connectionRequestIndex]->connectionRequestSystems[connectionRequestGuidIndex].pingToEndpoint=rakPeerInterface->GetAveragePing(packet->guid)+pingToEndpoint; + } + else + { + connectionRequests[connectionRequestIndex]->connectionRequestSystems.RemoveAtIndex(connectionRequestGuidIndex); + } + + if (UpdateForwarding(connectionRequestIndex)==false) + { + RemoveConnectionRequest(connectionRequestIndex); + } +} +void Router2::SendForwardingSuccess(RakNetGUID sourceGuid, RakNetGUID endpointGuid, unsigned short sourceToDstPort) +{ + RakNet::BitStream bsOut; + bsOut.Write((MessageID) ID_ROUTER_2_FORWARDING_ESTABLISHED); + bsOut.Write(endpointGuid); + bsOut.Write(sourceToDstPort); + rakPeerInterface->Send(&bsOut,MEDIUM_PRIORITY,RELIABLE_ORDERED,0,sourceGuid,false); +} +void Router2::SendOOBFromRakNetPort(OutOfBandIdentifiers oob, BitStream *extraData, SystemAddress sa) +{ + RakNet::BitStream oobBs; + oobBs.Write((unsigned char)oob); + if (extraData) + { + extraData->ResetReadPointer(); + oobBs.Write(*extraData); + } + char ipAddressString[32]; + sa.ToString(false, ipAddressString); + rakPeerInterface->SendOutOfBand((const char*) ipAddressString,sa.port,(MessageID) ID_OUT_OF_BAND_INTERNAL,(const char*) oobBs.GetData(),oobBs.GetNumberOfBytesUsed()); +} +void Router2::SendOOBFromSpecifiedSocket(OutOfBandIdentifiers oob, SystemAddress sa, SOCKET socket) +{ + RakNet::BitStream bs; + rakPeerInterface->WriteOutOfBandHeader(&bs, ID_OUT_OF_BAND_INTERNAL); + bs.Write((unsigned char) oob); + SocketLayer::Instance()->SendTo_PC( socket, (const char*) bs.GetData(), bs.GetNumberOfBytesUsed(), sa.binaryAddress, sa.port ); +} +void Router2::SendOOBMessages(Router2::MiniPunchRequest *mpr) +{ + // Mini NAT punch + // Send from srcToDestPort to packet->systemAddress (source). If the message arrives, the remote system should reply. + SendOOBFromSpecifiedSocket(ID_ROUTER_2_REPLY_TO_SENDER_PORT, mpr->sourceAddress, mpr->destToSourceSocket); + + // Send from destToSourcePort to endpointSystemAddress (destination). If the message arrives, the remote system should reply. + SendOOBFromSpecifiedSocket(ID_ROUTER_2_REPLY_TO_SENDER_PORT, mpr->endpointAddress, mpr->srcToDestSocket); + + // Tell source to send to srcToDestPort + RakNet::BitStream extraData; + extraData.Write(mpr->srcToDestPort); + RakAssert(mpr->srcToDestPort!=0); + SendOOBFromRakNetPort(ID_ROUTER_2_REPLY_TO_SPECIFIED_PORT, &extraData, mpr->sourceAddress); + + // Tell destination to send to destToSourcePort + extraData.Reset(); + extraData.Write(mpr->destToSourcePort); + RakAssert(mpr->destToSourcePort); + SendOOBFromRakNetPort(ID_ROUTER_2_REPLY_TO_SPECIFIED_PORT, &extraData, mpr->endpointAddress); +} +void Router2::OnReroute(Packet *packet) +{ + RakNet::BitStream bs(packet->data, packet->length, false); + bs.IgnoreBytes(sizeof(MessageID) + sizeof(unsigned char)); + RakNetGUID sourceGuid; + bs.Read(sourceGuid); + + char address[64]; + char ip[64]; + sourceGuid.ToString(address); + packet->systemAddress.ToString(true,ip); + printf("Rerouting source guid %s to address %s\n", address, ip); + + rakPeerInterface->ChangeSystemAddress(sourceGuid,packet->systemAddress); +} +void Router2::OnRequestForwarding(Packet *packet) +{ + RakNet::BitStream bs(packet->data, packet->length, false); + bs.IgnoreBytes(sizeof(MessageID) + sizeof(unsigned char)); + RakNetGUID endpointGuid; + bs.Read(endpointGuid); + + int pingToEndpoint = ReturnFailureOnCannotForward(packet->guid, endpointGuid); + if (pingToEndpoint==-1) + { + printf("Router2 failed at %s:%i\n", __FILE__, __LINE__); + return; + } + + unsigned short srcToDestPort; + unsigned short destToSourcePort; + SOCKET srcToDestSocket; + SOCKET destToSourceSocket; + SystemAddress endpointSystemAddress = rakPeerInterface->GetSystemAddressFromGuid(endpointGuid); + UDPForwarderResult result = udpForwarder->StartForwarding( + packet->systemAddress, endpointSystemAddress, 10000, 0, + &srcToDestPort, &destToSourcePort, &srcToDestSocket, &destToSourceSocket); + + printf("srcToDestPort=%i destToSourcePort=%i\n", srcToDestPort, destToSourcePort); + + if (result==UDPFORWARDER_FORWARDING_ALREADY_EXISTS) + { + SendForwardingSuccess(packet->guid, endpointGuid, srcToDestPort); + } + else if (result==UDPFORWARDER_NO_SOCKETS || result==UDPFORWARDER_INVALID_PARAMETERS) + { + printf("Router2 failed at %s:%i\n", __FILE__, __LINE__); + RakAssert(result!=UDPFORWARDER_INVALID_PARAMETERS); + SendFailureOnCannotForward(packet->guid, endpointGuid); + } + else + { + + // Store the punch request + MiniPunchRequest miniPunchRequest; + miniPunchRequest.endpointAddress=endpointSystemAddress; + miniPunchRequest.endpointGuid=endpointGuid; + miniPunchRequest.gotReplyFromEndpoint=false; + miniPunchRequest.gotReplyFromSource=false; + miniPunchRequest.sourceGuid=packet->guid; + miniPunchRequest.sourceAddress=packet->systemAddress; + miniPunchRequest.srcToDestPort=srcToDestPort; + miniPunchRequest.destToSourcePort=destToSourcePort; + miniPunchRequest.srcToDestSocket=srcToDestSocket; + miniPunchRequest.destToSourceSocket=destToSourceSocket; + int ping1 = rakPeerInterface->GetAveragePing(packet->guid); + int ping2 = rakPeerInterface->GetAveragePing(endpointGuid); + if (ping1>ping2) + miniPunchRequest.timeout=RakNet::GetTimeMS()+ping1*8+300; + else + miniPunchRequest.timeout=RakNet::GetTimeMS()+ping2*8+300; + miniPunchRequest.nextAction=RakNet::GetTimeMS()+100; + SendOOBMessages(&miniPunchRequest); + miniPunchesInProgress.Push(miniPunchRequest,__FILE__,__LINE__); + } +} +void Router2::OnMiniPunchReplyBounce(Packet *packet) +{ + // Find stored punch request + unsigned int i=0; + while (i < miniPunchesInProgress.Size()) + { + if (miniPunchesInProgress[i].sourceGuid==packet->guid || miniPunchesInProgress[i].endpointGuid==packet->guid) + { + if (miniPunchesInProgress[i].sourceGuid==packet->guid) + miniPunchesInProgress[i].gotReplyFromSource=true; + if (miniPunchesInProgress[i].endpointGuid==packet->guid) + miniPunchesInProgress[i].gotReplyFromEndpoint=true; + if (miniPunchesInProgress[i].gotReplyFromEndpoint==true && + miniPunchesInProgress[i].gotReplyFromSource==true) + { + RakNet::BitStream bs; + rakPeerInterface->WriteOutOfBandHeader(&bs, ID_OUT_OF_BAND_INTERNAL); + bs.Write((unsigned char) ID_ROUTER_2_REROUTE); + bs.Write(miniPunchesInProgress[i].sourceGuid); + SocketLayer::Instance()->SendTo_PC( miniPunchesInProgress[i].srcToDestSocket, (const char*) bs.GetData(), bs.GetNumberOfBytesUsed(), miniPunchesInProgress[i].endpointAddress.binaryAddress, miniPunchesInProgress[i].endpointAddress.port ); + + SendForwardingSuccess(miniPunchesInProgress[i].sourceGuid, miniPunchesInProgress[i].endpointGuid, miniPunchesInProgress[i].srcToDestPort); + miniPunchesInProgress.RemoveAtIndexFast(i); + } + else + { + i++; + } + } + else + i++; + } +} +void Router2::OnMiniPunchReply(Packet *packet) +{ + RakNet::BitStream bs(packet->data, packet->length, false); + bs.IgnoreBytes(sizeof(MessageID) + sizeof(unsigned char)); + RakNetGUID routerGuid; + bs.Read(routerGuid); + SendOOBFromRakNetPort(ID_ROUTER_2_MINI_PUNCH_REPLY_BOUNCE, 0, rakPeerInterface->GetSystemAddressFromGuid(routerGuid)); +} +bool Router2::OnForwardingSuccess(Packet *packet) +{ + RakNet::BitStream bs(packet->data, packet->length, false); + bs.IgnoreBytes(sizeof(MessageID)); + RakNetGUID endpointGuid; + bs.Read(endpointGuid); + unsigned short sourceToDestPort; + bs.Read(sourceToDestPort); + + unsigned int forwardingIndex; + for (forwardingIndex=0; forwardingIndex < forwardedConnectionList.Size(); forwardingIndex++) + { + if (forwardedConnectionList[forwardingIndex].endpointGuid==endpointGuid) + break; + } + + if (forwardingIndexsystemAddress; + intermediaryAddress.port=sourceToDestPort; + rakPeerInterface->ChangeSystemAddress(endpointGuid, intermediaryAddress); + + packet->data[0]=ID_ROUTER_2_REROUTED; + + return true; // Return packet to user + } + else + { + // removeFrom connectionRequests; + unsigned int connectionRequestIndex = GetConnectionRequestIndex(endpointGuid); + + ForwardedConnection fc; + fc.endpointGuid=endpointGuid; + fc.intermediaryAddress=packet->systemAddress; + fc.intermediaryAddress.port=sourceToDestPort; + fc.intermediaryGuid=packet->guid; + fc.returnConnectionLostOnFailure=connectionRequests[connectionRequestIndex]->returnConnectionLostOnFailure; + // add to forwarding list + forwardedConnectionList.Push(fc,__FILE__,__LINE__); + + connectionRequests.RemoveAtIndexFast(connectionRequestIndex); + } + return true; // Return packet to user +} +int Router2::GetLargestPingAmongConnectedSystems(void) const +{ + int avePing; + int largestPing=-1; + unsigned short maxPeers = rakPeerInterface->GetMaximumNumberOfPeers(); + if (maxPeers==0) + return 9999; + unsigned short index; + for (index=0; index < rakPeerInterface->GetMaximumNumberOfPeers(); index++) + { + RakNetGUID g = rakPeerInterface->GetGUIDFromIndex(index); + if (g!=UNASSIGNED_RAKNET_GUID) + { + avePing=rakPeerInterface->GetAveragePing(rakPeerInterface->GetGUIDFromIndex(index)); + if (avePing>largestPing) + largestPing=avePing; + } + } + return largestPing; +} + +unsigned int Router2::GetConnectionRequestIndex(RakNetGUID endpointGuid) +{ + unsigned int i; + for (i=0; i < connectionRequests.Size(); i++) + { + if (connectionRequests[i]->endpointGuid==endpointGuid) + return i; + } + return (unsigned int) -1; +} +unsigned int Router2::ConnnectRequest::GetGuidIndex(RakNetGUID guid) +{ + unsigned int i; + for (i=0; i < connectionRequestSystems.Size(); i++) + { + if (connectionRequestSystems[i].guid==guid) + return i; + } + return (unsigned int) -1; +} +void Router2::ReturnToUser(MessageID messageId, RakNetGUID endpointGuid, SystemAddress systemAddress) +{ + Packet *p = rakPeerInterface->AllocatePacket(sizeof(MessageID)+sizeof(unsigned char)); + p->data[0]=messageId; + p->systemAddress=systemAddress; + p->systemAddress.systemIndex=(SystemIndex)-1; + p->guid=endpointGuid; + rakPeerInterface->PushBackPacket(p, true); +} +void Router2::ClearForwardedConnections(void) +{ + forwardedConnectionList.Clear(false,__FILE__,__LINE__); +} +void Router2::ClearAll(void) +{ + ClearConnectionRequests(); + ClearMinipunches(); + ClearForwardedConnections(); +} + +#endif // _RAKNET_SUPPORT_* diff --git a/RakNet/Sources/Router2.h b/RakNet/Sources/Router2.h new file mode 100644 index 0000000..0f16738 --- /dev/null +++ b/RakNet/Sources/Router2.h @@ -0,0 +1,165 @@ +/// \file +/// \brief Router2 plugin. Allows you to connect to a system by routing packets through another system that is connected to both you and the destination. Useful for getting around NATs. +/// +/// This file is part of RakNet Copyright 2003 Jenkins Software LLC +/// +/// Usage of RakNet is subject to the appropriate license agreement. + +#include "NativeFeatureIncludes.h" +#if _RAKNET_SUPPORT_Router2==1 + +#ifndef __ROUTER_2_PLUGIN_H +#define __ROUTER_2_PLUGIN_H + +class RakPeerInterface; +#include "RakNetTypes.h" +#include "PluginInterface2.h" +#include "PacketPriority.h" +#include "Export.h" +#include "UDPForwarder.h" +#include "MessageIdentifiers.h" + +namespace RakNet +{ + +/// \defgroup ROUTER_2_GROUP Router2 +/// \brief Part of the NAT punchthrough solution, allowing you to connect to systems by routing through a shared connection. +/// \details +/// \ingroup PLUGINS_GROUP + +/// \ingroup ROUTER_2_GROUP +/// \brief Class interface for the Router2 system +/// \details +class RAK_DLL_EXPORT Router2 : public PluginInterface2 +{ +public: + Router2(); + virtual ~Router2(); + + /// \brief Query all connected systems to connect through them to a third system. + /// System will return ID_ROUTER_2_FORWARDING_NO_PATH if unable to connect. + /// Else you will get ID_ROUTER_2_FORWARDING_ESTABLISHED + /// + /// On ID_ROUTER_2_FORWARDING_ESTABLISHED, EstablishRouting 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->EstablishRouting(ipAddressString, sourceToDestPort, 0,0); + /// + /// \note The SystemAddress for a connection should not be used - always use RakNetGuid as the address can change at any time. + /// When the address changes, you will get ID_ROUTER_2_REROUTED + void EstablishRouting(RakNetGUID endpointGuid); + + /// Set the maximum number of bidirectional connections this system will support + /// Defaults to 0 + void SetMaximumForwardingRequests(int max); + + // -------------------------------------------------------------------------------------------- + // Packet handling functions + // -------------------------------------------------------------------------------------------- + virtual PluginReceiveResult OnReceive(Packet *packet); + virtual void Update(void); + virtual void OnClosedConnection(SystemAddress systemAddress, RakNetGUID rakNetGUID, PI2_LostConnectionReason lostConnectionReason ); + virtual void OnFailedConnectionAttempt(SystemAddress systemAddress, PI2_FailedConnectionAttemptReason failedConnectionAttemptReason); + virtual void OnNewConnection(SystemAddress systemAddress, RakNetGUID rakNetGUID, bool isIncoming); + virtual void OnRakPeerShutdown(void); + + + enum Router2RequestStates + { + R2RS_REQUEST_STATE_QUERY_FORWARDING, + REQUEST_STATE_REQUEST_FORWARDING, + }; + + struct ConnectionRequestSystem + { + RakNetGUID guid; + int pingToEndpoint; + unsigned short usedForwardingEntries; + }; + + struct ConnnectRequest + { + ConnnectRequest(); + ~ConnnectRequest(); + + DataStructures::List connectionRequestSystems; + Router2RequestStates requestState; + RakNetTimeMS pingTimeout; + RakNetGUID endpointGuid; + RakNetGUID lastRequestedForwardingSystem; + bool returnConnectionLostOnFailure; + unsigned int GetGuidIndex(RakNetGUID guid); + }; + + unsigned int GetConnectionRequestIndex(RakNetGUID endpointGuid); + + struct MiniPunchRequest + { + RakNetGUID endpointGuid; + SystemAddress endpointAddress; + bool gotReplyFromEndpoint; + RakNetGUID sourceGuid; + SystemAddress sourceAddress; + bool gotReplyFromSource; + RakNetTimeMS timeout; + RakNetTimeMS nextAction; + unsigned short srcToDestPort; + unsigned short destToSourcePort; + SOCKET srcToDestSocket; + SOCKET destToSourceSocket; + }; + + struct ForwardedConnection + { + RakNetGUID endpointGuid; + RakNetGUID intermediaryGuid; + SystemAddress intermediaryAddress; + bool returnConnectionLostOnFailure; + }; + +protected: + + bool UpdateForwarding(unsigned int connectionRequestIndex); + void RemoveConnectionRequest(unsigned int connectionRequestIndex); + void RequestForwarding(unsigned int connectionRequestIndex); + void OnQueryForwarding(Packet *packet); + void OnQueryForwardingReply(Packet *packet); + void OnRequestForwarding(Packet *packet); + void OnReroute(Packet *packet); + void OnMiniPunchReply(Packet *packet); + void OnMiniPunchReplyBounce(Packet *packet); + bool OnForwardingSuccess(Packet *packet); + int GetLargestPingAmongConnectedSystems(void) const; + void ReturnToUser(MessageID messageId, RakNetGUID endpointGuid, SystemAddress systemAddress); + bool ConnectInternal(RakNetGUID endpointGuid, bool returnConnectionLostOnFailure); + + UDPForwarder *udpForwarder; + int maximumForwardingRequests; + DataStructures::List connectionRequests; + DataStructures::List miniPunchesInProgress; + DataStructures::List forwardedConnectionList; + + void ClearConnectionRequests(void); + void ClearMinipunches(void); + void ClearForwardedConnections(void); + void ClearAll(void); + int ReturnFailureOnCannotForward(RakNetGUID sourceGuid, RakNetGUID endpointGuid); + void SendFailureOnCannotForward(RakNetGUID sourceGuid, RakNetGUID endpointGuid); + void SendForwardingSuccess(RakNetGUID sourceGuid, RakNetGUID endpointGuid, unsigned short sourceToDstPort); + void SendOOBFromRakNetPort(OutOfBandIdentifiers oob, BitStream *extraData, SystemAddress sa); + void SendOOBFromSpecifiedSocket(OutOfBandIdentifiers oob, SystemAddress sa, SOCKET socket); + void SendOOBMessages(MiniPunchRequest *mpr); +}; + +} + +#endif + +#endif // _RAKNET_SUPPORT_* diff --git a/RakNet/Sources/RouterInterface.h b/RakNet/Sources/RouterInterface.h new file mode 100644 index 0000000..6496633 --- /dev/null +++ b/RakNet/Sources/RouterInterface.h @@ -0,0 +1,17 @@ +#ifndef __ROUTER_INTERFACE_H +#define __ROUTER_INTERFACE_H + +#include "Export.h" +#include "RakNetTypes.h" + +/// On failed directed sends, RakNet can call an alternative send function to use. +class RAK_DLL_EXPORT RouterInterface +{ +public: + RouterInterface() {} + virtual ~RouterInterface() {} + + virtual bool Send( const char *data, BitSize_t bitLength, PacketPriority priority, PacketReliability reliability, char orderingChannel, SystemAddress systemAddress )=0; +}; + +#endif diff --git a/RakNet/Sources/SHA1.cpp b/RakNet/Sources/SHA1.cpp new file mode 100644 index 0000000..fbfe7d0 --- /dev/null +++ b/RakNet/Sources/SHA1.cpp @@ -0,0 +1,326 @@ +/** +* @brief SHA-1 Hash key computation +* +* 100% free public domain implementation of the SHA-1 +* algorithm by Dominik Reichl +* +* +* === Test Vectors (from FIPS PUB 180-1) === +* +* "abc" +* A9993E36 4706816A BA3E2571 7850C26C 9CD0D89D +* +* "abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq" +* 84983E44 1C3BD26E BAAE4AA1 F95129E5 E54670F1 +* +* A million repetitions of "a" +* 34AA973C D4C4DAA4 F61EEB2B DBAD2731 6534016F +*/ + + +#include "SHA1.h" +#include + + +CSHA1::CSHA1() +{ + Reset(); +} + +CSHA1::~CSHA1() +{ + Reset(); +} + + +void CSHA1::Reset() +{ + // SHA1 initialization constants + m_state[ 0 ] = 0x67452301; + m_state[ 1 ] = 0xEFCDAB89; + m_state[ 2 ] = 0x98BADCFE; + m_state[ 3 ] = 0x10325476; + m_state[ 4 ] = 0xC3D2E1F0; + + m_count[ 0 ] = 0; + m_count[ 1 ] = 0; +} + +void CSHA1::Transform( unsigned int state[ 5 ], unsigned char buffer[ 64 ] ) +{ + unsigned int a = 0, b = 0, c = 0, d = 0, e = 0; + + SHA1_WORKSPACE_BLOCK* block; + // static unsigned char workspace[64]; + block = ( SHA1_WORKSPACE_BLOCK * ) workspace; + memcpy( block, buffer, 64 ); + + // Copy state[] to working vars + a = state[ 0 ]; + b = state[ 1 ]; + c = state[ 2 ]; + d = state[ 3 ]; + e = state[ 4 ]; + + // 4 rounds of 20 operations each. Loop unrolled. + R0( a, b, c, d, e, 0 ); + R0( e, a, b, c, d, 1 ); + R0( d, e, a, b, c, 2 ); + R0( c, d, e, a, b, 3 ); + R0( b, c, d, e, a, 4 ); + R0( a, b, c, d, e, 5 ); + R0( e, a, b, c, d, 6 ); + R0( d, e, a, b, c, 7 ); + R0( c, d, e, a, b, 8 ); + R0( b, c, d, e, a, 9 ); + R0( a, b, c, d, e, 10 ); + R0( e, a, b, c, d, 11 ); + R0( d, e, a, b, c, 12 ); + R0( c, d, e, a, b, 13 ); + R0( b, c, d, e, a, 14 ); + R0( a, b, c, d, e, 15 ); + R1( e, a, b, c, d, 16 ); + R1( d, e, a, b, c, 17 ); + R1( c, d, e, a, b, 18 ); + R1( b, c, d, e, a, 19 ); + R2( a, b, c, d, e, 20 ); + R2( e, a, b, c, d, 21 ); + R2( d, e, a, b, c, 22 ); + R2( c, d, e, a, b, 23 ); + R2( b, c, d, e, a, 24 ); + R2( a, b, c, d, e, 25 ); + R2( e, a, b, c, d, 26 ); + R2( d, e, a, b, c, 27 ); + R2( c, d, e, a, b, 28 ); + R2( b, c, d, e, a, 29 ); + R2( a, b, c, d, e, 30 ); + R2( e, a, b, c, d, 31 ); + R2( d, e, a, b, c, 32 ); + R2( c, d, e, a, b, 33 ); + R2( b, c, d, e, a, 34 ); + R2( a, b, c, d, e, 35 ); + R2( e, a, b, c, d, 36 ); + R2( d, e, a, b, c, 37 ); + R2( c, d, e, a, b, 38 ); + R2( b, c, d, e, a, 39 ); + R3( a, b, c, d, e, 40 ); + R3( e, a, b, c, d, 41 ); + R3( d, e, a, b, c, 42 ); + R3( c, d, e, a, b, 43 ); + R3( b, c, d, e, a, 44 ); + R3( a, b, c, d, e, 45 ); + R3( e, a, b, c, d, 46 ); + R3( d, e, a, b, c, 47 ); + R3( c, d, e, a, b, 48 ); + R3( b, c, d, e, a, 49 ); + R3( a, b, c, d, e, 50 ); + R3( e, a, b, c, d, 51 ); + R3( d, e, a, b, c, 52 ); + R3( c, d, e, a, b, 53 ); + R3( b, c, d, e, a, 54 ); + R3( a, b, c, d, e, 55 ); + R3( e, a, b, c, d, 56 ); + R3( d, e, a, b, c, 57 ); + R3( c, d, e, a, b, 58 ); + R3( b, c, d, e, a, 59 ); + R4( a, b, c, d, e, 60 ); + R4( e, a, b, c, d, 61 ); + R4( d, e, a, b, c, 62 ); + R4( c, d, e, a, b, 63 ); + R4( b, c, d, e, a, 64 ); + R4( a, b, c, d, e, 65 ); + R4( e, a, b, c, d, 66 ); + R4( d, e, a, b, c, 67 ); + R4( c, d, e, a, b, 68 ); + R4( b, c, d, e, a, 69 ); + R4( a, b, c, d, e, 70 ); + R4( e, a, b, c, d, 71 ); + R4( d, e, a, b, c, 72 ); + R4( c, d, e, a, b, 73 ); + R4( b, c, d, e, a, 74 ); + R4( a, b, c, d, e, 75 ); + R4( e, a, b, c, d, 76 ); + R4( d, e, a, b, c, 77 ); + R4( c, d, e, a, b, 78 ); + R4( b, c, d, e, a, 79 ); + + // Add the working vars back into state[] + state[ 0 ] += a; + state[ 1 ] += b; + state[ 2 ] += c; + state[ 3 ] += d; + state[ 4 ] += e; + + // Wipe variables + a = 0; + b = 0; + c = 0; + d = 0; + e = 0; +} + +// Use this function to hash in binary data and strings +void CSHA1::Update( unsigned char* data, unsigned int len ) +{ + unsigned int i = 0, j = 0; + + j = ( m_count[ 0 ] >> 3 ) & 63; + + if ( ( m_count[ 0 ] += len << 3 ) < ( len << 3 ) ) + m_count[ 1 ] ++; + + m_count[ 1 ] += ( len >> 29 ); + + if ( ( j + len ) > 63 ) + { + memcpy( &m_buffer[ j ], data, ( i = 64 - j ) ); + Transform( m_state, m_buffer ); + + for ( ; i + 63 < len; i += 64 ) + { + Transform( m_state, &data[ i ] ); + } + + j = 0; + } + + else + i = 0; + + memcpy( &m_buffer[ j ], &data[ i ], len - i ); +} + +// Hash in file contents +bool CSHA1::HashFile( char *szFileName ) +{ + unsigned long ulFileSize = 0, ulRest = 0, ulBlocks = 0; + unsigned long i = 0; + unsigned char uData[ MAX_FILE_READ_BUFFER ]; + FILE *fIn = NULL; + + if ( ( fIn = fopen( szFileName, "rb" ) ) == NULL ) + return ( false ); + + fseek( fIn, 0, SEEK_END ); + + ulFileSize = ftell( fIn ); + + fseek( fIn, 0, SEEK_SET ); + + // This is faster + div_t temp; + + temp = div( ulFileSize, MAX_FILE_READ_BUFFER ); + + ulRest = temp.rem; + + ulBlocks = temp.quot; + + // ulRest = ulFileSize % MAX_FILE_READ_BUFFER; + // ulBlocks = ulFileSize / MAX_FILE_READ_BUFFER; + + for ( i = 0; i < ulBlocks; i++ ) + { + fread( uData, 1, MAX_FILE_READ_BUFFER, fIn ); + Update( uData, MAX_FILE_READ_BUFFER ); + } + + if ( ulRest != 0 ) + { + fread( uData, 1, ulRest, fIn ); + Update( uData, ulRest ); + } + + fclose( fIn ); + fIn = NULL; + + return ( true ); +} + +void CSHA1::Final() +{ + unsigned int i = 0; + unsigned char finalcount[ 8 ] = + { + 0, 0, 0, 0, 0, 0, 0, 0 + }; + + for ( i = 0; i < 8; i++ ) + finalcount[ i ] = (unsigned char) ( ( m_count[ ( i >= 4 ? 0 : 1 ) ] + >> ( ( 3 - ( i & 3 ) ) * 8 ) ) & 255 ); // Endian independent + + Update( ( unsigned char * ) "\200", 1 ); + + while ( ( m_count[ 0 ] & 504 ) != 448 ) + Update( ( unsigned char * ) "\0", 1 ); + + Update( finalcount, 8 ); // Cause a SHA1Transform() + + for ( i = 0; i < 20; i++ ) + { + m_digest[ i ] = (unsigned char) ( ( m_state[ i >> 2 ] >> ( ( 3 - ( i & 3 ) ) * 8 ) ) & 255 ); + } + + // Wipe variables for security reasons + i = 0; + +// j = 0; + + memset( m_buffer, 0, 64 ); + + memset( m_state, 0, 20 ); + + memset( m_count, 0, 8 ); + + memset( finalcount, 0, 8 ); + + Transform( m_state, m_buffer ); +} + +// Get the final hash as a pre-formatted string +void CSHA1::ReportHash( char *szReport, unsigned char uReportType ) +{ + unsigned char i = 0; + char szTemp[ 4 ]; + + if ( uReportType == REPORT_HEX ) + { + sprintf( szTemp, "%02X", m_digest[ 0 ] ); + strcat( szReport, szTemp ); + + for ( i = 1; i < 20; i++ ) + { + sprintf( szTemp, " %02X", m_digest[ i ] ); + strcat( szReport, szTemp ); + } + } + + else + if ( uReportType == REPORT_DIGIT ) + { + sprintf( szTemp, "%u", m_digest[ 0 ] ); + strcat( szReport, szTemp ); + + for ( i = 1; i < 20; i++ ) + { + sprintf( szTemp, " %u", m_digest[ i ] ); + strcat( szReport, szTemp ); + } + } + + else + strcpy( szReport, "Error: Unknown report type!" ); +} + +// Get the raw message digest +void CSHA1::GetHash( unsigned char *uDest ) +{ + memcpy( uDest, m_digest, 20 ); +} + +// Get the raw message digest +// Added by Kevin to be quicker +unsigned char * CSHA1::GetHash( void ) const +{ + return ( unsigned char * ) m_digest; +} diff --git a/RakNet/Sources/SHA1.h b/RakNet/Sources/SHA1.h new file mode 100644 index 0000000..25ddacb --- /dev/null +++ b/RakNet/Sources/SHA1.h @@ -0,0 +1,87 @@ +/// \brief \b [Internal] SHA-1 computation class +/// +/// 100% free public domain implementation of the SHA-1 +/// algorithm by Dominik Reichl +/// +/// +/// === Test Vectors (from FIPS PUB 180-1) === +/// +/// "abc" +/// A9993E36 4706816A BA3E2571 7850C26C 9CD0D89D +/// +/// "abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq" +/// 84983E44 1C3BD26E BAAE4AA1 F95129E5 E54670F1 +/// +/// A million repetitions of "a" +/// 34AA973C D4C4DAA4 F61EEB2B DBAD2731 6534016F + +#ifndef ___SHA1_H___ +#define ___SHA1_H___ + +#include "RakMemoryOverride.h" +#include // Needed for file access +#if !defined(_PS3) && !defined(__PS3__) && !defined(SN_TARGET_PS3) +#include // Needed for memset and memcpy +#endif +#include // Needed for strcat and strcpy +#include "Export.h" + +#define MAX_FILE_READ_BUFFER 8000 + +#define SHA1_LENGTH 20 + +class RAK_DLL_EXPORT CSHA1 +{ + +public: + // Rotate x bits to the left + #define ROL32(value, bits) (((value)<<(bits))|((value)>>(32-(bits)))) + +#ifdef LITTLE_ENDIAN +#define SHABLK0(i) (block->l[i] = (ROL32(block->l[i],24) & 0xFF00FF00) \ + | (ROL32(block->l[i],8) & 0x00FF00FF)) +#else +#define SHABLK0(i) (block->l[i]) +#endif + +#define SHABLK(i) (block->l[i&15] = ROL32(block->l[(i+13)&15] ^ block->l[(i+8)&15] \ + ^ block->l[(i+2)&15] ^ block->l[i&15],1)) + + // SHA-1 rounds +#define R0(v,w,x,y,z,i) { z+=((w&(x^y))^y)+SHABLK0(i)+0x5A827999+ROL32(v,5); w=ROL32(w,30); } +#define R1(v,w,x,y,z,i) { z+=((w&(x^y))^y)+SHABLK(i)+0x5A827999+ROL32(v,5); w=ROL32(w,30); } +#define R2(v,w,x,y,z,i) { z+=(w^x^y)+SHABLK(i)+0x6ED9EBA1+ROL32(v,5); w=ROL32(w,30); } +#define R3(v,w,x,y,z,i) { z+=(((w|x)&y)|(w&x))+SHABLK(i)+0x8F1BBCDC+ROL32(v,5); w=ROL32(w,30); } +#define R4(v,w,x,y,z,i) { z+=(w^x^y)+SHABLK(i)+0xCA62C1D6+ROL32(v,5); w=ROL32(w,30); } + + typedef union { + unsigned char c[ 64 ]; + unsigned int l[ 16 ]; + } SHA1_WORKSPACE_BLOCK; + /* Two different formats for ReportHash(...) + */ + enum { REPORT_HEX = 0, + REPORT_DIGIT = 1}; + + CSHA1(); + virtual ~CSHA1(); + + unsigned int m_state[ 5 ]; + unsigned int m_count[ 2 ]; + unsigned char m_buffer[ 64 ]; + unsigned char m_digest[ 20 ]; + void Reset(); + void Update( unsigned char* data, unsigned int len ); + bool HashFile( char *szFileName ); + void Final(); + void ReportHash( char *szReport, unsigned char uReportType = REPORT_HEX ); + void GetHash( unsigned char *uDest ); + unsigned char * GetHash( void ) const; + +private: + void Transform( unsigned int state[ 5 ], unsigned char buffer[ 64 ] ); + unsigned char workspace[ 64 ]; +}; + +#endif // ___SHA1_H___ + diff --git a/RakNet/Sources/SendToThread.cpp b/RakNet/Sources/SendToThread.cpp new file mode 100644 index 0000000..28be2a5 --- /dev/null +++ b/RakNet/Sources/SendToThread.cpp @@ -0,0 +1,66 @@ +#include "SendToThread.h" +#ifdef USE_THREADED_SEND +#include "RakThread.h" +#include "CCRakNetUDT.h" +#include "InternalPacket.h" +#include "GetTime.h" + +int SendToThread::refCount=0; +DataStructures::ThreadsafeAllocatingQueue SendToThread::objectQueue; +ThreadPool SendToThread::threadPool; + +SendToThread::SendToThreadBlock* SendToWorkerThread(SendToThread::SendToThreadBlock* input, bool *returnOutput, void* perThreadData) +{ + (void) perThreadData; + *returnOutput=false; + RakNetTimeUS *mostRecentTime=(RakNetTimeUS *)input->data; + *mostRecentTime=RakNet::GetTimeUS(); + SocketLayer::Instance()->SendTo(input->s, input->data, input->dataWriteOffset, input->binaryAddress, input->port, input->remotePortRakNetWasStartedOn_PS3); + SendToThread::objectQueue.Push(input); + return 0; +} +SendToThread::SendToThread() +{ +} +SendToThread::~SendToThread() +{ + +} +void SendToThread::AddRef(void) +{ + if (++refCount==1) + { + threadPool.StartThreads(1,0); + } +} +void SendToThread::Deref(void) +{ + if (refCount>0) + { + if (--refCount==0) + { + threadPool.StopThreads(); + RakAssert(threadPool.NumThreadsWorking()==0); + + unsigned i; + SendToThreadBlock* info; + for (i=0; i < threadPool.InputSize(); i++) + { + info = threadPool.GetInputAtIndex(i); + objectQueue.Push(info); + } + threadPool.ClearInput(); + objectQueue.Clear(__FILE__, __LINE__); + } + } +} +SendToThread::SendToThreadBlock* SendToThread::AllocateBlock(void) +{ + return objectQueue.PopOrAllocate(); +} +void SendToThread::ProcessBlock(SendToThread::SendToThreadBlock* threadedSend) +{ + RakAssert(threadedSend->dataWriteOffset>0 && threadedSend->dataWriteOffset<=MAXIMUM_MTU_SIZE-UDP_HEADER_SIZE); + threadPool.AddInput(SendToWorkerThread,threadedSend); +} +#endif diff --git a/RakNet/Sources/SendToThread.h b/RakNet/Sources/SendToThread.h new file mode 100644 index 0000000..2a39336 --- /dev/null +++ b/RakNet/Sources/SendToThread.h @@ -0,0 +1,43 @@ +#ifndef __SENDTO_THREAD +#define __SENDTO_THREAD + +#include "RakNetDefines.h" + +#ifdef USE_THREADED_SEND + +#include "InternalPacket.h" +#include "SocketLayer.h" +#include "DS_ThreadsafeAllocatingQueue.h" +#include "ThreadPool.h" + +class SendToThread +{ +public: + SendToThread(); + ~SendToThread(); + + struct SendToThreadBlock + { + SOCKET s; + unsigned int binaryAddress; + unsigned short port; + unsigned short remotePortRakNetWasStartedOn_PS3; + char data[MAXIMUM_MTU_SIZE]; + unsigned short dataWriteOffset; + }; + + static SendToThreadBlock* AllocateBlock(void); + static void ProcessBlock(SendToThreadBlock* threadedSend); + + static void AddRef(void); + static void Deref(void); + static DataStructures::ThreadsafeAllocatingQueue objectQueue; +protected: + static int refCount; + static ThreadPool threadPool; + +}; + +#endif + +#endif diff --git a/RakNet/Sources/SignaledEvent.cpp b/RakNet/Sources/SignaledEvent.cpp new file mode 100644 index 0000000..cb42c58 --- /dev/null +++ b/RakNet/Sources/SignaledEvent.cpp @@ -0,0 +1,145 @@ +#include "SignaledEvent.h" +#include "RakAssert.h" +#include "RakSleep.h" + +#if defined(__GNUC__) +#include +#include +#endif + +SignaledEvent::SignaledEvent() +{ +#ifdef _WIN32 + eventList=INVALID_HANDLE_VALUE; +#else + isSignaled=false; +#endif +} +SignaledEvent::~SignaledEvent() +{ + // Intentionally do not close event, so it doesn't close twice on linux +} + +void SignaledEvent::InitEvent(void) +{ +#ifdef _WIN32 + eventList=CreateEvent(0, false, false, 0); +#else + +#if !defined(ANDROID) + pthread_condattr_init( &condAttr ); + pthread_cond_init(&eventList, &condAttr); +#else + pthread_cond_init(&eventList, 0); +#endif + pthread_mutexattr_init( &mutexAttr ); + pthread_mutex_init(&hMutex, &mutexAttr); +#endif +} + +void SignaledEvent::CloseEvent(void) +{ +#ifdef _WIN32 + if (eventList!=INVALID_HANDLE_VALUE) + { + CloseHandle(eventList); + eventList=INVALID_HANDLE_VALUE; + } +#else + pthread_cond_destroy(&eventList); + pthread_mutex_destroy(&hMutex); +#if !defined(ANDROID) + pthread_condattr_destroy( &condAttr ); +#endif + pthread_mutexattr_destroy( &mutexAttr ); +#endif +} + +void SignaledEvent::SetEvent(void) +{ +#ifdef _WIN32 + ::SetEvent(eventList); +#else + // Different from SetEvent which stays signaled. + // We have to record manually that the event was signaled + isSignaledMutex.Lock(); + isSignaled=true; + isSignaledMutex.Unlock(); + + // Unblock waiting threads + pthread_cond_broadcast(&eventList); +#endif +} + +void SignaledEvent::WaitOnEvent(int timeoutMs) +{ +#ifdef _WIN32 +// WaitForMultipleObjects( +// 2, +// eventList, +// false, +// timeoutMs); + WaitForSingleObject(eventList,timeoutMs); +#else + + // If was previously set signaled, just unset and return + isSignaledMutex.Lock(); + if (isSignaled==true) + { + isSignaled=false; + isSignaledMutex.Unlock(); + return; + } + isSignaledMutex.Unlock(); + + + struct timespec ts; + + // Else wait for SetEvent to be called +#if defined(_PS3) || defined(__PS3__) || defined(SN_TARGET_PS3) + + #else + int rc; + struct timeval tp; + rc = gettimeofday(&tp, NULL); + ts.tv_sec = tp.tv_sec; + ts.tv_nsec = tp.tv_usec * 1000; + #endif + + while (timeoutMs > 30) + { + // Wait 30 milliseconds for the signal, then check again. + // This is in case we missed the signal between the top of this function and pthread_cond_timedwait, or after the end of the loop and pthread_cond_timedwait + ts.tv_nsec += 30*1000000; + if (ts.tv_nsec >= 1000000000) + { + ts.tv_nsec -= 1000000000; + ts.tv_sec++; + } + pthread_cond_timedwait(&eventList, &hMutex, &ts); + timeoutMs-=30; + + isSignaledMutex.Lock(); + if (isSignaled==true) + { + isSignaled=false; + isSignaledMutex.Unlock(); + return; + } + isSignaledMutex.Unlock(); + } + + // Wait the remaining time, and turn off the signal in case it was set + ts.tv_nsec += timeoutMs*1000000; + if (ts.tv_nsec >= 1000000000) + { + ts.tv_nsec -= 1000000000; + ts.tv_sec++; + } + pthread_cond_timedwait(&eventList, &hMutex, &ts); + + isSignaledMutex.Lock(); + isSignaled=false; + isSignaledMutex.Unlock(); +#endif +} diff --git a/RakNet/Sources/SignaledEvent.h b/RakNet/Sources/SignaledEvent.h new file mode 100644 index 0000000..964ec83 --- /dev/null +++ b/RakNet/Sources/SignaledEvent.h @@ -0,0 +1,45 @@ +#ifndef __SIGNALED_EVENT_H +#define __SIGNALED_EVENT_H + +#if defined(_XBOX) || defined(X360) + +#elif defined(_WIN32) +#include +#else + #include + #include + #include "SimpleMutex.h" + #if defined(_PS3) || defined(__PS3__) || defined(SN_TARGET_PS3) + + #endif +#endif + +#include "Export.h" + +class RAK_DLL_EXPORT SignaledEvent +{ +public: + SignaledEvent(); + ~SignaledEvent(); + + void InitEvent(void); + void CloseEvent(void); + void SetEvent(void); + void WaitOnEvent(int timeoutMs); + +protected: +#ifdef _WIN32 + HANDLE eventList; +#else + SimpleMutex isSignaledMutex; + bool isSignaled; +#if !defined(ANDROID) + pthread_condattr_t condAttr; +#endif + pthread_cond_t eventList; + pthread_mutex_t hMutex; + pthread_mutexattr_t mutexAttr; +#endif +}; + +#endif diff --git a/RakNet/Sources/SimpleMutex.cpp b/RakNet/Sources/SimpleMutex.cpp new file mode 100644 index 0000000..3b10bdf --- /dev/null +++ b/RakNet/Sources/SimpleMutex.cpp @@ -0,0 +1,104 @@ +/// \file +/// +/// This file is part of RakNet Copyright 2003 Jenkins Software LLC +/// +/// Usage of RakNet is subject to the appropriate license agreement. + + +#include "SimpleMutex.h" +#include "RakAssert.h" + +SimpleMutex::SimpleMutex() : isInitialized(false) +{ +} + +SimpleMutex::~SimpleMutex() +{ + if (isInitialized==false) + return; +#ifdef _WIN32 + // CloseHandle(hMutex); + DeleteCriticalSection(&criticalSection); +#else + pthread_mutex_destroy(&hMutex); +#endif +} + +#ifdef _WIN32 +#ifdef _DEBUG +#include +#endif +#endif + +void SimpleMutex::Lock(void) +{ + // Initialize in the Lock() call in case this object was created globally during initialization + if (isInitialized==false) + Init(); + +#ifdef _WIN32 + /* + DWORD d = WaitForSingleObject(hMutex, INFINITE); + #ifdef _DEBUG + if (d==WAIT_FAILED) + { + LPVOID messageBuffer; + FormatMessage( + FORMAT_MESSAGE_ALLOCATE_BUFFER | + FORMAT_MESSAGE_FROM_SYSTEM | + FORMAT_MESSAGE_IGNORE_INSERTS, + NULL, + GetLastError(), + MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), // Default language + (LPTSTR) &messageBuffer, + 0, + NULL + ); + // Process any inserts in messageBuffer. + // ... + // Display the string. + //MessageBox( NULL, (LPCTSTR)messageBuffer, "Error", MB_OK | MB_ICONINFORMATION ); + RAKNET_DEBUG_PRINTF("SimpleMutex error: %s", messageBuffer); + // Free the buffer. + LocalFree( messageBuffer ); + + } + + RakAssert(d==WAIT_OBJECT_0); + */ + EnterCriticalSection(&criticalSection); + +#else + int error = pthread_mutex_lock(&hMutex); + (void) error; + RakAssert(error==0); +#endif +} + +void SimpleMutex::Unlock(void) +{ + if (isInitialized==false) + return; +#ifdef _WIN32 + // ReleaseMutex(hMutex); + LeaveCriticalSection(&criticalSection); +#else + int error = pthread_mutex_unlock(&hMutex); + (void) error; + RakAssert(error==0); +#endif +} + +void SimpleMutex::Init(void) +{ +#ifdef _WIN32 + // hMutex = CreateMutex(NULL, FALSE, 0); + // RakAssert(hMutex); + InitializeCriticalSection(&criticalSection); +#else + int error = pthread_mutex_init(&hMutex, 0); + (void) error; + RakAssert(error==0); +#endif + isInitialized=true; +} diff --git a/RakNet/Sources/SimpleMutex.h b/RakNet/Sources/SimpleMutex.h new file mode 100644 index 0000000..3a37244 --- /dev/null +++ b/RakNet/Sources/SimpleMutex.h @@ -0,0 +1,52 @@ +/// \file +/// \brief \b [Internal] Encapsulates a mutex +/// +/// This file is part of RakNet Copyright 2003 Jenkins Software LLC +/// +/// Usage of RakNet is subject to the appropriate license agreement. + + +#ifndef __SIMPLE_MUTEX_H +#define __SIMPLE_MUTEX_H + +#include "RakMemoryOverride.h" +#if defined(_XBOX) || defined(X360) + +#elif defined(_WIN32) +#include "WindowsIncludes.h" +#else +#include +#include +#endif +#include "Export.h" +/// \brief An easy to use mutex. +/// +/// I wrote this because the version that comes with Windows is too complicated and requires too much code to use. +/// @remark Previously I used this everywhere, and in fact for a year or two RakNet was totally threadsafe. While doing profiling, I saw that this function was incredibly slow compared to the blazing performance of everything else, so switched to single producer / consumer everywhere. Now the user thread of RakNet is not threadsafe, but it's 100X faster than before. +class RAK_DLL_EXPORT SimpleMutex +{ +public: + + // Constructor + SimpleMutex(); + + // Destructor + ~SimpleMutex(); + + // Locks the mutex. Slow! + void Lock(void); + + // Unlocks the mutex. + void Unlock(void); +private: + void Init(void); + #ifdef _WIN32 + CRITICAL_SECTION criticalSection; /// Docs say this is faster than a mutex for single process access + #else + pthread_mutex_t hMutex; + #endif + bool isInitialized; +}; + +#endif + diff --git a/RakNet/Sources/SimpleTCPServer.h b/RakNet/Sources/SimpleTCPServer.h new file mode 100644 index 0000000..1181cd0 --- /dev/null +++ b/RakNet/Sources/SimpleTCPServer.h @@ -0,0 +1 @@ +// Eraseme \ No newline at end of file diff --git a/RakNet/Sources/SingleProducerConsumer.h b/RakNet/Sources/SingleProducerConsumer.h new file mode 100644 index 0000000..ddf078b --- /dev/null +++ b/RakNet/Sources/SingleProducerConsumer.h @@ -0,0 +1,259 @@ +/// \file +/// \brief \b [Internal] Passes queued data between threads using a circular buffer with read and write pointers +/// +/// This file is part of RakNet Copyright 2003 Jenkins Software LLC +/// +/// Usage of RakNet is subject to the appropriate license agreement. + + +#ifndef __SINGLE_PRODUCER_CONSUMER_H +#define __SINGLE_PRODUCER_CONSUMER_H + +#include "RakAssert.h" + +static const int MINIMUM_LIST_SIZE=8; + +#include "RakMemoryOverride.h" +#include "Export.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 +{ + /// \brief A single producer consumer implementation without critical sections. + template + class RAK_DLL_EXPORT SingleProducerConsumer + { + public: + // Constructor + SingleProducerConsumer(); + + // Destructor + ~SingleProducerConsumer(); + + /// WriteLock must be immediately followed by WriteUnlock. These two functions must be called in the same thread. + /// \return A pointer to a block of data you can write to. + SingleProducerConsumerType* WriteLock(void); + + /// Call if you don't want to write to a block of data from WriteLock() after all. + /// Cancelling locks cancels all locks back up to the data passed. So if you lock twice and cancel using the first lock, the second lock is ignored + /// \param[in] cancelToLocation Which WriteLock() to cancel. + void CancelWriteLock(SingleProducerConsumerType* cancelToLocation); + + /// Call when you are done writing to a block of memory returned by WriteLock() + void WriteUnlock(void); + + /// ReadLock must be immediately followed by ReadUnlock. These two functions must be called in the same thread. + /// \retval 0 No data is availble to read + /// \retval Non-zero The data previously written to, in another thread, by WriteLock followed by WriteUnlock. + SingleProducerConsumerType* ReadLock(void); + + // Cancelling locks cancels all locks back up to the data passed. So if you lock twice and cancel using the first lock, the second lock is ignored + /// param[in] Which ReadLock() to cancel. + void CancelReadLock(SingleProducerConsumerType* cancelToLocation); + + /// Signals that we are done reading the the data from the least recent call of ReadLock. + /// At this point that pointer is no longer valid, and should no longer be read. + void ReadUnlock(void); + + /// Clear is not thread-safe and none of the lock or unlock functions should be called while it is running. + void Clear(void); + + /// This function will estimate how many elements are waiting to be read. It's threadsafe enough that the value returned is stable, but not threadsafe enough to give accurate results. + /// \return An ESTIMATE of how many data elements are waiting to be read + int Size(void) const; + + /// Make sure that the pointer we done reading for the call to ReadUnlock is the right pointer. + /// param[in] A previous pointer returned by ReadLock() + bool CheckReadUnlockOrder(const SingleProducerConsumerType* data) const; + + /// Returns if ReadUnlock was called before ReadLock + /// \return If the read is locked + bool ReadIsLocked(void) const; + + private: + struct DataPlusPtr + { + DataPlusPtr () {readyToRead=false;} + SingleProducerConsumerType object; + + // Ready to read is so we can use an equality boolean comparison, in case the writePointer var is trashed while context switching. + volatile bool readyToRead; + volatile DataPlusPtr *next; + }; + volatile DataPlusPtr *readAheadPointer; + volatile DataPlusPtr *writeAheadPointer; + volatile DataPlusPtr *readPointer; + volatile DataPlusPtr *writePointer; + unsigned readCount, writeCount; + }; + + template + SingleProducerConsumer::SingleProducerConsumer() + { + // Preallocate + readPointer = RakNet::OP_NEW( __FILE__, __LINE__ ); + writePointer=readPointer; + readPointer->next = RakNet::OP_NEW( __FILE__, __LINE__ ); + int listSize; +#ifdef _DEBUG + RakAssert(MINIMUM_LIST_SIZE>=3); +#endif + for (listSize=2; listSize < MINIMUM_LIST_SIZE; listSize++) + { + readPointer=readPointer->next; + readPointer->next = RakNet::OP_NEW( __FILE__, __LINE__ ); + } + readPointer->next->next=writePointer; // last to next = start + readPointer=writePointer; + readAheadPointer=readPointer; + writeAheadPointer=writePointer; + readCount=writeCount=0; + } + + template + SingleProducerConsumer::~SingleProducerConsumer() + { + volatile DataPlusPtr *next; + readPointer=writeAheadPointer->next; + while (readPointer!=writeAheadPointer) + { + next=readPointer->next; + RakNet::OP_DELETE((char*) readPointer, __FILE__, __LINE__); + readPointer=next; + } + RakNet::OP_DELETE((char*) readPointer, __FILE__, __LINE__); + } + + template + SingleProducerConsumerType* SingleProducerConsumer::WriteLock( void ) + { + if (writeAheadPointer->next==readPointer || + writeAheadPointer->next->readyToRead==true) + { + volatile DataPlusPtr *originalNext=writeAheadPointer->next; + writeAheadPointer->next=RakNet::OP_NEW(__FILE__, __LINE__); + RakAssert(writeAheadPointer->next); + writeAheadPointer->next->next=originalNext; + } + + volatile DataPlusPtr *last; + last=writeAheadPointer; + writeAheadPointer=writeAheadPointer->next; + + return (SingleProducerConsumerType*) last; + } + + template + void SingleProducerConsumer::CancelWriteLock( SingleProducerConsumerType* cancelToLocation ) + { + writeAheadPointer=(DataPlusPtr *)cancelToLocation; + } + + template + void SingleProducerConsumer::WriteUnlock( void ) + { + // DataPlusPtr *dataContainer = (DataPlusPtr *)structure; + +#ifdef _DEBUG + RakAssert(writePointer->next!=readPointer); + RakAssert(writePointer!=writeAheadPointer); +#endif + + writeCount++; + // User is done with the data, allow send by updating the write pointer + writePointer->readyToRead=true; + writePointer=writePointer->next; + } + + template + SingleProducerConsumerType* SingleProducerConsumer::ReadLock( void ) + { + if (readAheadPointer==writePointer || + readAheadPointer->readyToRead==false) + { + return 0; + } + + volatile DataPlusPtr *last; + last=readAheadPointer; + readAheadPointer=readAheadPointer->next; + return (SingleProducerConsumerType*)last; + } + + template + void SingleProducerConsumer::CancelReadLock( SingleProducerConsumerType* cancelToLocation ) + { +#ifdef _DEBUG + RakAssert(readPointer!=writePointer); +#endif + readAheadPointer=(DataPlusPtr *)cancelToLocation; + } + + template + void SingleProducerConsumer::ReadUnlock( void ) + { +#ifdef _DEBUG + RakAssert(readAheadPointer!=readPointer); // If hits, then called ReadUnlock before ReadLock + RakAssert(readPointer!=writePointer); // If hits, then called ReadUnlock when Read returns 0 +#endif + readCount++; + + // Allow writes to this memory block + readPointer->readyToRead=false; + readPointer=readPointer->next; + } + + template + void SingleProducerConsumer::Clear( void ) + { + // Shrink the list down to MINIMUM_LIST_SIZE elements + volatile DataPlusPtr *next; + writePointer=readPointer->next; + + int listSize=1; + next=readPointer->next; + while (next!=readPointer) + { + listSize++; + next=next->next; + } + + while (listSize-- > MINIMUM_LIST_SIZE) + { + next=writePointer->next; +#ifdef _DEBUG + RakAssert(writePointer!=readPointer); +#endif + RakNet::OP_DELETE((char*) writePointer, __FILE__, __LINE__); + writePointer=next; + } + + readPointer->next=writePointer; + writePointer=readPointer; + readAheadPointer=readPointer; + writeAheadPointer=writePointer; + readCount=writeCount=0; + } + + template + int SingleProducerConsumer::Size( void ) const + { + return writeCount-readCount; + } + + template + bool SingleProducerConsumer::CheckReadUnlockOrder(const SingleProducerConsumerType* data) const + { + return const_cast(&readPointer->object) == data; + } + + + template + bool SingleProducerConsumer::ReadIsLocked(void) const + { + return readAheadPointer!=readPointer; + } +} + +#endif diff --git a/RakNet/Sources/SocketIncludes.h b/RakNet/Sources/SocketIncludes.h new file mode 100644 index 0000000..fa61d29 --- /dev/null +++ b/RakNet/Sources/SocketIncludes.h @@ -0,0 +1,26 @@ +// All this crap just to include type SOCKET + +#if defined(_XBOX) || defined(X360) + +#elif defined(_PS3) || defined(__PS3__) || defined(SN_TARGET_PS3) + +#elif defined(_WIN32) +typedef int socklen_t; +// 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 +#else +#define closesocket close +#include +#include +#include +#include +#include +#include +#include +#include +#define INVALID_SOCKET -1 +//#include "RakMemoryOverride.h" +/// Unix/Linux uses ints for sockets +typedef int SOCKET; +#endif diff --git a/RakNet/Sources/SocketLayer.cpp b/RakNet/Sources/SocketLayer.cpp new file mode 100644 index 0000000..e5002c9 --- /dev/null +++ b/RakNet/Sources/SocketLayer.cpp @@ -0,0 +1,1243 @@ +/// \file +/// \brief SocketLayer class implementation +/// +/// This file is part of RakNet Copyright 2003 Jenkins Software LLC +/// +/// Usage of RakNet is subject to the appropriate license agreement. + + +#include "SocketLayer.h" +#include "RakAssert.h" +#include "RakNetTypes.h" +#include "CCRakNetUDT.h" +#include "GetTime.h" + +#ifdef _WIN32 +#elif !defined(_PS3) && !defined(__PS3__) && !defined(SN_TARGET_PS3) +#include // memcpy +#include +#include +#include +#include // error numbers +#include // RAKNET_DEBUG_PRINTF +#if !defined(ANDROID) +#include +#endif +#include +#include +#include +#include +#include + +#endif + +#if defined(_PS3) || defined(__PS3__) || defined(SN_TARGET_PS3) + +#endif + +#if defined(_XBOX) || defined(X360) + +#elif defined(_WIN32) +#include "WSAStartupSingleton.h" +#include // 'IP_DONTFRAGMENT' 'IP_TTL' +#elif defined(_PS3) || defined(__PS3__) || defined(SN_TARGET_PS3) + +#else +#define closesocket close +#include +#endif + +#include "RakSleep.h" +#include + +#include "ExtendedOverlappedPool.h" + +#ifdef _MSC_VER +#pragma warning( push ) +#endif + +SocketLayer SocketLayer::I; + +extern void ProcessNetworkPacket( const SystemAddress systemAddress, const char *data, const int length, RakPeer *rakPeer, RakNetSmartPtr rakNetSocket, RakNetTimeUS timeRead ); +extern void ProcessNetworkPacket( const SystemAddress systemAddress, const char *data, const int length, RakPeer *rakPeer, RakNetTimeUS timeRead ); +extern void ProcessPortUnreachable( const unsigned int binaryAddress, const unsigned short port, RakPeer *rakPeer ); + +#ifdef _DEBUG +#include +#endif + +SocketLayer::SocketLayer() +{ +#ifdef _WIN32 + WSAStartupSingleton::AddRef(); +#endif + slo=0; +} + +SocketLayer::~SocketLayer() +{ +#ifdef _WIN32 + WSAStartupSingleton::Deref(); +#endif +} + +SOCKET SocketLayer::Connect( SOCKET writeSocket, unsigned int binaryAddress, unsigned short port ) +{ + RakAssert( writeSocket != (SOCKET) -1 ); + sockaddr_in connectSocketAddress; + + connectSocketAddress.sin_family = AF_INET; + connectSocketAddress.sin_port = htons( port ); + connectSocketAddress.sin_addr.s_addr = binaryAddress; + + if ( connect( writeSocket, ( struct sockaddr * ) & connectSocketAddress, sizeof( struct sockaddr ) ) != 0 ) + { +#if defined(_WIN32) && !defined(_XBOX) && defined(_DEBUG) && !defined(X360) + DWORD dwIOError = GetLastError(); + LPVOID messageBuffer; + FormatMessage( FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, + NULL, dwIOError, MAKELANGID( LANG_NEUTRAL, SUBLANG_DEFAULT ), // Default language + ( LPTSTR ) &messageBuffer, 0, NULL ); + // something has gone wrong here... + RAKNET_DEBUG_PRINTF( "WSAConnect failed:Error code - %d\n%s", dwIOError, messageBuffer ); + //Free the buffer. + LocalFree( messageBuffer ); +#endif + } + + return writeSocket; +} +bool SocketLayer::IsPortInUse(unsigned short port, const char *hostAddress) +{ + SOCKET listenSocket; + sockaddr_in listenerSocketAddress; + // Listen on our designated Port# + listenerSocketAddress.sin_port = htons( port ); + listenSocket = socket( AF_INET, SOCK_DGRAM, 0 ); + if ( listenSocket == (SOCKET) -1 ) + return true; + // bind our name to the socket + // Fill in the rest of the address structure + listenerSocketAddress.sin_family = AF_INET; + if ( hostAddress && hostAddress[0] ) + listenerSocketAddress.sin_addr.s_addr = inet_addr( hostAddress ); + else + listenerSocketAddress.sin_addr.s_addr = INADDR_ANY; + int ret = bind( listenSocket, ( struct sockaddr * ) & listenerSocketAddress, sizeof( listenerSocketAddress ) ); + closesocket(listenSocket); + +#if defined(_PS3) || defined(__PS3__) || defined(SN_TARGET_PS3) + +#else + #if defined(_DEBUG) + if (ret == -1) + perror("Failed to bind to address:"); + #endif + return ret <= -1; +#endif +} +void SocketLayer::SetDoNotFragment( SOCKET listenSocket, int opt ) +{ + +#if defined(IP_DONTFRAGMENT ) + +#if defined(_WIN32) && !defined(_XBOX) && defined(_DEBUG) && !defined(X360) + // If this assert hit you improperly linked against WSock32.h + RakAssert(IP_DONTFRAGMENT==14); +#endif + + if ( setsockopt( listenSocket, IPPROTO_IP, IP_DONTFRAGMENT, ( char * ) & opt, sizeof ( opt ) ) == -1 ) + { +#if defined(_WIN32) && defined(_DEBUG) + DWORD dwIOError = GetLastError(); + LPVOID messageBuffer; + FormatMessage( FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, + NULL, dwIOError, MAKELANGID( LANG_NEUTRAL, SUBLANG_DEFAULT ), // Default language + ( LPTSTR ) & messageBuffer, 0, NULL ); + RAKNET_DEBUG_PRINTF( "setsockopt(IP_DONTFRAGMENT) failed:Error code - %d\n%s", dwIOError, messageBuffer ); + LocalFree( messageBuffer ); +#endif + } +#endif + +} + +void SocketLayer::SetNonBlocking( SOCKET listenSocket) +{ +#ifdef _WIN32 + unsigned long nonBlocking = 1; + ioctlsocket( listenSocket, FIONBIO, &nonBlocking ); +#elif defined(_PS3) || defined(__PS3__) || defined(SN_TARGET_PS3) + +#else + int flags = fcntl(listenSocket, F_GETFL, 0); + fcntl(listenSocket, F_SETFL, flags | O_NONBLOCK); +#endif +} + +void SocketLayer::SetSocketOptions( SOCKET listenSocket) +{ + int sock_opt = 1; + // // On Vista, can get WSAEACCESS (10013) + /* + if ( setsockopt( listenSocket, SOL_SOCKET, SO_REUSEADDR, ( char * ) & sock_opt, sizeof ( sock_opt ) ) == -1 ) + { + #if defined(_WIN32) && !defined(_XBOX) && defined(_DEBUG) && !defined(X360) + DWORD dwIOError = GetLastError(); + LPVOID messageBuffer; + FormatMessage( FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, + NULL, dwIOError, MAKELANGID( LANG_NEUTRAL, SUBLANG_DEFAULT ), // Default language + ( LPTSTR ) & messageBuffer, 0, NULL ); + // something has gone wrong here... + RAKNET_DEBUG_PRINTF( "setsockopt(SO_REUSEADDR) failed:Error code - %d\n%s", dwIOError, messageBuffer ); + //Free the buffer. + LocalFree( messageBuffer ); + #endif + } + */ + + // This doubles the max throughput rate + sock_opt=1024*256; + setsockopt(listenSocket, SOL_SOCKET, SO_RCVBUF, ( char * ) & sock_opt, sizeof ( sock_opt ) ); + + // Immediate hard close. Don't linger the socket, or recreating the socket quickly on Vista fails. + sock_opt=0; + setsockopt(listenSocket, SOL_SOCKET, SO_LINGER, ( char * ) & sock_opt, sizeof ( sock_opt ) ); + +#if !defined(_PS3) && !defined(__PS3__) && !defined(SN_TARGET_PS3) + // This doesn't make much difference: 10% maybe + // Not supported on console 2 + sock_opt=1024*16; + setsockopt(listenSocket, SOL_SOCKET, SO_SNDBUF, ( char * ) & sock_opt, sizeof ( sock_opt ) ); +#endif + + /* + #ifdef _WIN32 + unsigned long nonblocking = 1; + ioctlsocket( listenSocket, FIONBIO, &nonblocking ); + #elif defined(_PS3) || defined(__PS3__) || defined(SN_TARGET_PS3) + + #else + fcntl( listenSocket, F_SETFL, O_NONBLOCK ); + #endif + */ + + // Set broadcast capable + sock_opt=1; + if ( setsockopt( listenSocket, SOL_SOCKET, SO_BROADCAST, ( char * ) & sock_opt, sizeof( sock_opt ) ) == -1 ) + { +#if defined(_WIN32) && defined(_DEBUG) + DWORD dwIOError = GetLastError(); + // On Vista, can get WSAEACCESS (10013) + // See http://support.microsoft.com/kb/819124 + // http://blogs.msdn.com/wndp/archive/2007/03/19/winsock-so-exclusiveaddruse-on-vista.aspx + // http://msdn.microsoft.com/en-us/library/ms740621(VS.85).aspx +#if !defined(_XBOX) && !defined(X360) + LPVOID messageBuffer; + FormatMessage( FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, + NULL, dwIOError, MAKELANGID( LANG_NEUTRAL, SUBLANG_DEFAULT ), // Default language + ( LPTSTR ) & messageBuffer, 0, NULL ); + // something has gone wrong here... + RAKNET_DEBUG_PRINTF( "setsockopt(SO_BROADCAST) failed:Error code - %d\n%s", dwIOError, messageBuffer ); + //Free the buffer. + LocalFree( messageBuffer ); +#endif +#endif + + } +} +SOCKET SocketLayer::CreateBoundSocket_PS3Lobby( unsigned short port, bool blockingSocket, const char *forceHostAddress ) +{ + (void) port; + (void) blockingSocket; + (void) forceHostAddress; + +#if defined(_PS3) || defined(__PS3__) || defined(SN_TARGET_PS3) + +#else + return 0; +#endif +} +SOCKET SocketLayer::CreateBoundSocket( unsigned short port, bool blockingSocket, const char *forceHostAddress, unsigned int sleepOn10048 ) +{ + (void) blockingSocket; + + int ret; + SOCKET listenSocket; + sockaddr_in listenerSocketAddress; + // Listen on our designated Port# + listenerSocketAddress.sin_port = htons( port ); +#if (defined(_XBOX) || defined(_X360)) && defined(RAKNET_USE_VDP) + listenSocket = socket( AF_INET, SOCK_DGRAM, IPPROTO_VDP ); +#else + listenSocket = socket( AF_INET, SOCK_DGRAM, 0 ); +#endif + + if ( listenSocket == (SOCKET) -1 ) + { +#if defined(_WIN32) && !defined(_XBOX) && defined(_DEBUG) + DWORD dwIOError = GetLastError(); + LPVOID messageBuffer; + FormatMessage( FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, + NULL, dwIOError, MAKELANGID( LANG_NEUTRAL, SUBLANG_DEFAULT ), // Default language + ( LPTSTR ) & messageBuffer, 0, NULL ); + // something has gone wrong here... + RAKNET_DEBUG_PRINTF( "socket(...) failed:Error code - %d\n%s", dwIOError, messageBuffer ); + //Free the buffer. + LocalFree( messageBuffer ); +#endif + + return (SOCKET) -1; + } + + SetSocketOptions(listenSocket); + + // Fill in the rest of the address structure + listenerSocketAddress.sin_family = AF_INET; + + if (forceHostAddress && forceHostAddress[0]) + { +// printf("Force binding %s:%i\n", forceHostAddress, port); + listenerSocketAddress.sin_addr.s_addr = inet_addr( forceHostAddress ); + } + else + { +// printf("Binding any on port %i\n", port); + listenerSocketAddress.sin_addr.s_addr = INADDR_ANY; + } + + // bind our name to the socket + ret = bind( listenSocket, ( struct sockaddr * ) & listenerSocketAddress, sizeof( listenerSocketAddress ) ); + + if ( ret <= -1 ) + { +#if defined(_WIN32) && !defined(_XBOX) && !defined(X360) + DWORD dwIOError = GetLastError(); + if (dwIOError==10048) + { + if (sleepOn10048==0) + return (SOCKET) -1; + // Vista has a bug where it returns WSAEADDRINUSE (10048) if you create, shutdown, then rebind the socket port unless you wait a while first. + // Wait, then rebind + RakSleep(100); + + closesocket(listenSocket); + listenerSocketAddress.sin_port = htons( port ); + listenSocket = socket( AF_INET, SOCK_DGRAM, 0 ); + if ( listenSocket == (SOCKET) -1 ) + return false; + SetSocketOptions(listenSocket); + + // Fill in the rest of the address structure + listenerSocketAddress.sin_family = AF_INET; + if (forceHostAddress && forceHostAddress[0]) + listenerSocketAddress.sin_addr.s_addr = inet_addr( forceHostAddress ); + else + listenerSocketAddress.sin_addr.s_addr = INADDR_ANY; + + // bind our name to the socket + ret = bind( listenSocket, ( struct sockaddr * ) & listenerSocketAddress, sizeof( listenerSocketAddress ) ); + + if ( ret >= 0 ) + return listenSocket; + } + dwIOError = GetLastError(); + LPVOID messageBuffer; + FormatMessage( FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, + NULL, dwIOError, MAKELANGID( LANG_NEUTRAL, SUBLANG_DEFAULT ), // Default language + ( LPTSTR ) & messageBuffer, 0, NULL ); + // something has gone wrong here... + RAKNET_DEBUG_PRINTF( "bind(...) failed:Error code - %d\n%s", (unsigned int) dwIOError, (char*) messageBuffer ); + //Free the buffer. + LocalFree( messageBuffer ); +#elif (defined(__GNUC__) || defined(__GCCXML__) || defined(_PS3) || defined(__PS3__) || defined(SN_TARGET_PS3)) && !defined(__WIN32) + switch (ret) + { + case EBADF: + RAKNET_DEBUG_PRINTF("bind(): sockfd is not a valid descriptor.\n"); break; +#if !defined(_PS3) && !defined(__PS3__) && !defined(SN_TARGET_PS3) + case ENOTSOCK: + RAKNET_DEBUG_PRINTF("bind(): Argument is a descriptor for a file, not a socket.\n"); break; +#endif + case EINVAL: + RAKNET_DEBUG_PRINTF("bind(): The addrlen is wrong, or the socket was not in the AF_UNIX family.\n"); break; + case EROFS: + RAKNET_DEBUG_PRINTF("bind(): The socket inode would reside on a read-only file system.\n"); break; + case EFAULT: + RAKNET_DEBUG_PRINTF("bind(): my_addr points outside the user's accessible address space.\n"); break; + case ENAMETOOLONG: + RAKNET_DEBUG_PRINTF("bind(): my_addr is too long.\n"); break; + case ENOENT: + RAKNET_DEBUG_PRINTF("bind(): The file does not exist.\n"); break; + case ENOMEM: + RAKNET_DEBUG_PRINTF("bind(): Insufficient kernel memory was available.\n"); break; + case ENOTDIR: + RAKNET_DEBUG_PRINTF("bind(): A component of the path prefix is not a directory.\n"); break; + case EACCES: + RAKNET_DEBUG_PRINTF("bind(): Search permission is denied on a component of the path prefix.\n"); break; +#if !defined(_PS3) && !defined(__PS3__) && !defined(SN_TARGET_PS3) + case ELOOP: + RAKNET_DEBUG_PRINTF("bind(): Too many symbolic links were encountered in resolving my_addr.\n"); break; +#endif + default: + RAKNET_DEBUG_PRINTF("Unknown bind() error %i.\n", ret); break; + } +#endif + + return (SOCKET) -1; + } + + return listenSocket; +} + +const char* SocketLayer::DomainNameToIP( const char *domainName ) +{ + struct in_addr addr; + +#if defined(_XBOX) || defined(X360) + +#else + struct hostent * phe = gethostbyname( domainName ); + + if ( phe == 0 || phe->h_addr_list[ 0 ] == 0 ) + { + //cerr << "Yow! Bad host lookup." << endl; + return 0; + } + + if (phe->h_addr_list[ 0 ]==0) + return 0; + + memcpy( &addr, phe->h_addr_list[ 0 ], sizeof( struct in_addr ) ); + return inet_ntoa( addr ); +#endif + +// return ""; +} + + +void SocketLayer::Write( const SOCKET writeSocket, const char* data, const int length ) +{ +#ifdef _DEBUG + RakAssert( writeSocket != (SOCKET) -1 ); +#endif + + send( writeSocket, data, length, 0 ); +} +int SocketLayer::RecvFrom( const SOCKET s, RakPeer *rakPeer, int *errorCode, RakNetSmartPtr rakNetSocket, unsigned short remotePortRakNetWasStartedOn_PS3 ) +{ + int len=0; +#if (defined(_XBOX) || defined(_X360)) && defined(RAKNET_USE_VDP) + char dataAndVoice[ MAXIMUM_MTU_SIZE*2 ]; + char *data=&dataAndVoice[sizeof(unsigned short)]; // 2 bytes in +#else + char data[ MAXIMUM_MTU_SIZE ]; +#endif + + if (slo) + { + SystemAddress sender; + len = slo->RakNetRecvFrom(s,rakPeer,data,&sender,true); + if (len>0) + { + ProcessNetworkPacket( sender, data, len, rakPeer, rakNetSocket, RakNet::GetTimeUS() ); + return 1; + } + } + + if ( s == (SOCKET) -1 ) + { + *errorCode = -1; + return -1; + } + +#if defined (_WIN32) || !defined(MSG_DONTWAIT) + const int flag=0; +#else + const int flag=MSG_DONTWAIT; +#endif + + sockaddr_in sa; + socklen_t len2; + unsigned short portnum=0; + if (remotePortRakNetWasStartedOn_PS3!=0) + { +#if defined(_PS3) || defined(__PS3__) || defined(SN_TARGET_PS3) + +#endif + } + else + { + len2 = sizeof( sa ); + sa.sin_family = AF_INET; + sa.sin_port=0; + +#if (defined(_XBOX) || defined(_X360)) && defined(RAKNET_USE_VDP) + + /* + DWORD zero=0; + WSABUF wsaBuf; + DWORD lenDword=0; + wsaBuf.buf=dataAndVoice; + wsaBuf.len=sizeof(dataAndVoice); + int result = WSARecvFrom( s, + &wsaBuf, + 1, + &lenDword, + &zero, + ( sockaddr* ) & sa, ( socklen_t* ) & len2, + 0,0 ); + len=lenDword; + */ + + len = recvfrom( s, dataAndVoice, sizeof(dataAndVoice), flag, ( sockaddr* ) & sa, ( socklen_t* ) & len2 ); + if (len>2) + { + // Skip first two bytes + len-=2; + } +#else + len = recvfrom( s, data, MAXIMUM_MTU_SIZE, flag, ( sockaddr* ) & sa, ( socklen_t* ) & len2 ); +#endif + + portnum = ntohs( sa.sin_port ); + } + + if ( len == 0 ) + { +#ifdef _DEBUG + RAKNET_DEBUG_PRINTF( "Error: recvfrom returned 0 on a connectionless blocking call\non port %i. This is a bug with Zone Alarm. Please turn off Zone Alarm.\n", portnum ); + RakAssert( 0 ); +#endif + + // 4/13/09 Changed from returning -1 to 0, to prevent attackers from sending 0 byte messages to shutdown the server + *errorCode = 0; + return 0; + } + + if ( len > 0 ) + { + ProcessNetworkPacket( SystemAddress(sa.sin_addr.s_addr, portnum), data, len, rakPeer, rakNetSocket, RakNet::GetTimeUS() ); + + return 1; + } + else + { + *errorCode = 0; + + +#if defined(_WIN32) && defined(_DEBUG) + + DWORD dwIOError = WSAGetLastError(); + + if ( dwIOError == WSAEWOULDBLOCK ) + { + return SOCKET_ERROR; + } + if ( dwIOError == WSAECONNRESET ) + { +#if defined(_DEBUG) +// RAKNET_DEBUG_PRINTF( "A previous send operation resulted in an ICMP Port Unreachable message.\n" ); +#endif + + + unsigned short portnum=0; + ProcessPortUnreachable(sa.sin_addr.s_addr, portnum, rakPeer); + // *errorCode = dwIOError; + return -1; + } + else + { +#if defined(_DEBUG) && !defined(_XBOX) && !defined(X360) + if ( dwIOError != WSAEINTR && dwIOError != WSAETIMEDOUT) + { + LPVOID messageBuffer; + FormatMessage( FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, + NULL, dwIOError, MAKELANGID( LANG_NEUTRAL, SUBLANG_DEFAULT ), // Default language + ( LPTSTR ) & messageBuffer, 0, NULL ); + // something has gone wrong here... + RAKNET_DEBUG_PRINTF( "recvfrom failed:Error code - %d\n%s", dwIOError, messageBuffer ); + + //Free the buffer. + LocalFree( messageBuffer ); + } +#endif + } +#endif + } + + return 0; // no data +} +void SocketLayer::RecvFromBlocking( const SOCKET s, RakPeer *rakPeer, unsigned short remotePortRakNetWasStartedOn_PS3, char *dataOut, int *bytesReadOut, SystemAddress *systemAddressOut, RakNetTimeUS *timeRead ) +{ + (void) rakPeer; + // Randomly crashes, slo is 0, yet gets inside loop. + /* + if (SocketLayer::Instance()->slo) + { + SystemAddress sender; + *bytesReadOut = SocketLayer::Instance()->slo->RakNetRecvFrom(s,rakPeer,dataOut,systemAddressOut,false); + if (*bytesReadOut>0) + { + *timeRead=RakNet::GetTimeUS(); + return; + } + else if (*bytesReadOut==0) + { + return; + } + // Negative, process as normal + } + */ + + + sockaddr* sockAddrPtr; + socklen_t sockLen; + socklen_t* socketlenPtr=(socklen_t*) &sockLen; + sockaddr_in sa; + char *dataOutModified; + int dataOutSize; + const int flag=0; + + (void) remotePortRakNetWasStartedOn_PS3; + +#if defined(_PS3) || defined(__PS3__) || defined(SN_TARGET_PS3) + +#endif + { + sockLen=sizeof(sa); + sa.sin_family = AF_INET; + sa.sin_port=0; + sockAddrPtr=(sockaddr*) &sa; + } + +#if (defined(_XBOX) || defined(_X360)) && defined(RAKNET_USE_VDP) + dataOutModified=dataOut+sizeof(uint16_t); + dataOutSize=MAXIMUM_MTU_SIZE*2; +#else + dataOutModified=dataOut; + dataOutSize=MAXIMUM_MTU_SIZE; +#endif + *bytesReadOut = recvfrom( s, dataOutModified, dataOutSize, flag, sockAddrPtr, socketlenPtr ); + if (*bytesReadOut<=0) + { + /* +#if defined(_WIN32) && !defined(_XBOX) && !defined(X360) && defined(_DEBUG) + DWORD dwIOError = GetLastError(); + LPVOID messageBuffer; + FormatMessage( FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, + NULL, dwIOError, MAKELANGID( LANG_NEUTRAL, SUBLANG_DEFAULT ), // Default language + ( LPTSTR ) & messageBuffer, 0, NULL ); + // something has gone wrong here... + RAKNET_DEBUG_PRINTF( "sendto failed:Error code - %d\n%s", dwIOError, messageBuffer ); + + //Free the buffer. + LocalFree( messageBuffer ); +#endif + */ + return; + } + *timeRead=RakNet::GetTimeUS(); + +#if defined(_PS3) || defined(__PS3__) || defined(SN_TARGET_PS3) + +#endif + { + systemAddressOut->port=ntohs( sa.sin_port ); + systemAddressOut->binaryAddress=sa.sin_addr.s_addr; + } +} + +void SocketLayer::RawRecvFromNonBlocking( const SOCKET s, unsigned short remotePortRakNetWasStartedOn_PS3, char *dataOut, int *bytesReadOut, SystemAddress *systemAddressOut, RakNetTimeUS *timeRead ) +{ + + sockaddr* sockAddrPtr; + socklen_t sockLen; + socklen_t* socketlenPtr=(socklen_t*) &sockLen; + sockaddr_in sa; + char *dataOutModified; + int dataOutSize; + const int flag=0; + + (void) remotePortRakNetWasStartedOn_PS3; + +// This is the wrong place for this - call on the socket before calling the function +// #if defined(_WIN32) +// u_long val = 1; +// ioctlsocket (s,FIONBIO,&val);//non block +// #else +// int flags = fcntl(s, F_GETFL, 0); +// fcntl(s, F_SETFL, flags | O_NONBLOCK); +// #endif + +#if defined(_PS3) || defined(__PS3__) || defined(SN_TARGET_PS3) + +#endif + { + sockLen=sizeof(sa); + sa.sin_family = AF_INET; + sa.sin_port=0; + sockAddrPtr=(sockaddr*) &sa; + } + +#if (defined(_XBOX) || defined(_X360)) && defined(RAKNET_USE_VDP) + dataOutModified=dataOut+sizeof(uint16_t); + dataOutSize=MAXIMUM_MTU_SIZE*2; +#else + dataOutModified=dataOut; + dataOutSize=MAXIMUM_MTU_SIZE; +#endif + + *bytesReadOut = recvfrom( s, dataOutModified, dataOutSize, flag, sockAddrPtr, socketlenPtr ); + if (*bytesReadOut<=0) + { + return; + } + *timeRead=RakNet::GetTimeUS(); + +#if defined(_PS3) || defined(__PS3__) || defined(SN_TARGET_PS3) + +#endif + { + systemAddressOut->port=ntohs( sa.sin_port ); + systemAddressOut->binaryAddress=sa.sin_addr.s_addr; + } +} + +int SocketLayer::SendTo_PS3Lobby( SOCKET s, const char *data, int length, unsigned int binaryAddress, unsigned short port, unsigned short remotePortRakNetWasStartedOn_PS3 ) +{ + (void) s; + (void) data; + (void) length; + (void) binaryAddress; + (void) port; + (void) remotePortRakNetWasStartedOn_PS3; + + int len=0; +#if defined(_PS3) || defined(__PS3__) || defined(SN_TARGET_PS3) + +#endif + return len; +} +int SocketLayer::SendTo_360( SOCKET s, const char *data, int length, const char *voiceData, int voiceLength, unsigned int binaryAddress, unsigned short port ) +{ + (void) s; + (void) data; + (void) length; + (void) voiceData; + (void) voiceLength; + (void) binaryAddress; + (void) port; + + int len=0; +#if (defined(_XBOX) || defined(_X360)) && defined(RAKNET_USE_VDP) + unsigned short payloadLength=length; + WSABUF buffers[3]; + buffers[0].buf=(char*) &payloadLength; + buffers[0].len=sizeof(payloadLength); + buffers[1].buf=(char*) data; + buffers[1].len=length; + buffers[2].buf=(char*) voiceData; + buffers[2].len=voiceLength; + DWORD size = buffers[0].len + buffers[1].len + buffers[2].len; + + sockaddr_in sa; + sa.sin_port = htons( port ); // User port + sa.sin_addr.s_addr = binaryAddress; + sa.sin_family = AF_INET; + + int result = WSASendTo(s, (LPWSABUF)buffers, 3, &size, 0, ( const sockaddr* ) & sa, sizeof( sa ), NULL, NULL); + if (result==-1) + { + DWORD dwIOError = GetLastError(); + int a=5; + } + len=size; + +#endif + return len; +} +int SocketLayer::SendTo_PC( SOCKET s, const char *data, int length, unsigned int binaryAddress, unsigned short port ) +{ + sockaddr_in sa; + sa.sin_port = htons( port ); // User port + sa.sin_addr.s_addr = binaryAddress; + sa.sin_family = AF_INET; + int len=0; + do + { + len = sendto( s, data, length, 0, ( const sockaddr* ) & sa, sizeof( sa ) ); + if (len<0) + { + +#if defined(_WIN32) && !defined(_XBOX) && defined(_DEBUG) && !defined(X360) + DWORD dwIOError = GetLastError(); + if (dwIOError!= 10040 && dwIOError != WSAEADDRNOTAVAIL) + { + LPVOID messageBuffer; + FormatMessage( FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, + NULL, dwIOError, MAKELANGID( LANG_NEUTRAL, SUBLANG_DEFAULT ), // Default language + ( LPTSTR ) &messageBuffer, 0, NULL ); + // something has gone wrong here... + RAKNET_DEBUG_PRINTF( "SendTo_PC failed:Error code - %d\n%s", dwIOError, messageBuffer ); + //Free the buffer. + LocalFree( messageBuffer ); + } + else + { + // buffer size exceeded + return -10040; + } +#endif + + printf("sendto failed with code %i for char %i and length %i.\n", len, data[0], length); + } + #if defined(_DEBUG) + printf("Sent %d bytes to port %d\n", len, port); + #endif + } + while ( len == 0 ); + return len; +} + +#ifdef _MSC_VER +#pragma warning( disable : 4702 ) // warning C4702: unreachable code +#endif +int SocketLayer::SendTo( SOCKET s, const char *data, int length, unsigned int binaryAddress, unsigned short port, unsigned short remotePortRakNetWasStartedOn_PS3 ) +{ + RakAssert(length<=MAXIMUM_MTU_SIZE-UDP_HEADER_SIZE); + RakAssert(port!=0); + if (slo) + { + SystemAddress sa(binaryAddress,port); + return slo->RakNetSendTo(s,data,length,sa); + } + + if ( s == (SOCKET) -1 ) + { + return -1; + } + + int len=0; + + if (remotePortRakNetWasStartedOn_PS3!=0) + { + len = SendTo_PS3Lobby(s,data,length,binaryAddress,port, remotePortRakNetWasStartedOn_PS3); + } + else + { + +#if (defined(_XBOX) || defined(_X360)) && defined(RAKNET_USE_VDP) + len = SendTo_360(s,data,length,0,0,binaryAddress,port); +#else + len = SendTo_PC(s,data,length,binaryAddress,port); +#endif + } + + if ( len != -1 ) + return 0; + +#if defined(_WIN32) && !defined(_WIN32_WCE) + + DWORD dwIOError = WSAGetLastError(); + + if ( dwIOError == WSAECONNRESET ) + { +#if defined(_DEBUG) + RAKNET_DEBUG_PRINTF( "A previous send operation resulted in an ICMP Port Unreachable message.\n" ); +#endif + + } + else if ( dwIOError != WSAEWOULDBLOCK && dwIOError != WSAEADDRNOTAVAIL) + { +#if defined(_WIN32) && !defined(_XBOX) && !defined(X360) && defined(_DEBUG) + LPVOID messageBuffer; + FormatMessage( FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, + NULL, dwIOError, MAKELANGID( LANG_NEUTRAL, SUBLANG_DEFAULT ), // Default language + ( LPTSTR ) & messageBuffer, 0, NULL ); + // something has gone wrong here... + RAKNET_DEBUG_PRINTF( "sendto failed:Error code - %d\n%s", dwIOError, messageBuffer ); + + //Free the buffer. + LocalFree( messageBuffer ); +#endif + + } + + return dwIOError; +#endif + + return 1; // error +} +int SocketLayer::SendTo( SOCKET s, const char *data, int length, const char ip[ 16 ], unsigned short port, unsigned short remotePortRakNetWasStartedOn_PS3 ) +{ + unsigned int binaryAddress; + binaryAddress = inet_addr( ip ); + return SendTo( s, data, length, binaryAddress, port,remotePortRakNetWasStartedOn_PS3 ); +} +int SocketLayer::SendToTTL( SOCKET s, const char *data, int length, const char ip[ 16 ], unsigned short port, int ttl ) +{ + unsigned int binaryAddress; + binaryAddress = inet_addr( ip ); + SystemAddress sa(binaryAddress,port); + + if (slo) + return slo->RakNetSendTo(s,data,length,sa); + +#if !defined(_XBOX) && !defined(X360) + int oldTTL; + socklen_t opLen=sizeof(oldTTL); + // Get the current TTL + if (getsockopt(s, IPPROTO_IP, IP_TTL, ( char * ) & oldTTL, &opLen ) == -1) + { +#if defined(_WIN32) && defined(_DEBUG) + DWORD dwIOError = GetLastError(); + LPVOID messageBuffer; + FormatMessage( FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, + NULL, dwIOError, MAKELANGID( LANG_NEUTRAL, SUBLANG_DEFAULT ), // Default language + ( LPTSTR ) & messageBuffer, 0, NULL ); + // something has gone wrong here... + RAKNET_DEBUG_PRINTF( "getsockopt(IPPROTO_IP,IP_TTL) failed:Error code - %d\n%s", dwIOError, messageBuffer ); + //Free the buffer. + LocalFree( messageBuffer ); +#endif + } + + // Set to TTL + int newTTL=ttl; + if (setsockopt(s, IPPROTO_IP, IP_TTL, ( char * ) & newTTL, sizeof ( newTTL ) ) == -1) + { + +#if defined(_WIN32) && defined(_DEBUG) + DWORD dwIOError = GetLastError(); + LPVOID messageBuffer; + FormatMessage( FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, + NULL, dwIOError, MAKELANGID( LANG_NEUTRAL, SUBLANG_DEFAULT ), // Default language + ( LPTSTR ) & messageBuffer, 0, NULL ); + // something has gone wrong here... + RAKNET_DEBUG_PRINTF( "setsockopt(IPPROTO_IP,IP_TTL) failed:Error code - %d\n%s", dwIOError, messageBuffer ); + //Free the buffer. + LocalFree( messageBuffer ); +#endif + } + + // Send + int res = SendTo(s,data,length,ip,port,false); + + // Restore the old TTL + setsockopt(s, IPPROTO_IP, IP_TTL, ( char * ) & oldTTL, opLen ); + + return res; +#else + return 0; +#endif +} + + +RakNet::RakString SocketLayer::GetSubNetForSocketAndIp(SOCKET inSock, RakNet::RakString inIpString) +{ + RakNet::RakString netMaskString; + RakNet::RakString ipString; + +#if defined(_XBOX) || defined(X360) + +#elif defined(_PS3) || defined(__PS3__) || defined(SN_TARGET_PS3) + +#elif defined(_WIN32) + INTERFACE_INFO InterfaceList[20]; + unsigned long nBytesReturned; + if (WSAIoctl(inSock, SIO_GET_INTERFACE_LIST, 0, 0, &InterfaceList, + sizeof(InterfaceList), &nBytesReturned, 0, 0) == SOCKET_ERROR) { + return ""; + } + + int nNumInterfaces = nBytesReturned / sizeof(INTERFACE_INFO); + + for (int i = 0; i < nNumInterfaces; ++i) + { + sockaddr_in *pAddress; + pAddress = (sockaddr_in *) & (InterfaceList[i].iiAddress); + ipString=inet_ntoa(pAddress->sin_addr); + + if (inIpString==ipString) + { + pAddress = (sockaddr_in *) & (InterfaceList[i].iiNetmask); + netMaskString=inet_ntoa(pAddress->sin_addr); + return netMaskString; + } + } + return ""; +#else + + int fd,fd2; + fd2 = socket(AF_INET, SOCK_DGRAM, 0); + + if(fd2 < 0) + { + return ""; + } + + struct ifconf ifc; + char buf[1999]; + ifc.ifc_len = sizeof(buf); + ifc.ifc_buf = buf; + if(ioctl(fd2, SIOCGIFCONF, &ifc) < 0) + { + return ""; + } + + struct ifreq *ifr; + ifr = ifc.ifc_req; + int intNum = ifc.ifc_len / sizeof(struct ifreq); + for(int i = 0; i < intNum; i++) + { + ipString=inet_ntoa(((struct sockaddr_in *)&ifr[i].ifr_addr)->sin_addr); + + if (inIpString==ipString) + { + struct ifreq ifr2; + fd = socket(AF_INET, SOCK_DGRAM, 0); + if(fd < 0) + { + return ""; + } + ifr2.ifr_addr.sa_family = AF_INET; + + strncpy(ifr2.ifr_name, ifr[i].ifr_name, IFNAMSIZ-1); + + ioctl(fd, SIOCGIFNETMASK, &ifr2); + + close(fd); + close(fd2); + netMaskString=inet_ntoa(((struct sockaddr_in *)&ifr2.ifr_addr)->sin_addr); + + return netMaskString; + } + } + + close(fd2); + return ""; + +#endif + +} +#if defined(_PS3) || defined(__PS3__) || defined(SN_TARGET_PS3) + +#elif defined(_WIN32) +void GetMyIP_Win32( char ipList[ MAXIMUM_NUMBER_OF_INTERNAL_IDS ][ 16 ], unsigned int binaryAddresses[MAXIMUM_NUMBER_OF_INTERNAL_IDS] ) +{ + char ac[ 80 ]; + if ( gethostname( ac, sizeof( ac ) ) == -1 ) + { + DWORD dwIOError = GetLastError(); + LPVOID messageBuffer; + FormatMessage( FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, + NULL, dwIOError, MAKELANGID( LANG_NEUTRAL, SUBLANG_DEFAULT ), // Default language + ( LPTSTR ) & messageBuffer, 0, NULL ); + // something has gone wrong here... + RAKNET_DEBUG_PRINTF( "gethostname failed:Error code - %d\n%s", dwIOError, messageBuffer ); + //Free the buffer. + LocalFree( messageBuffer ); + return ; + } + + struct hostent *phe = gethostbyname( ac ); + + if ( phe == 0 ) + { + DWORD dwIOError = GetLastError(); + LPVOID messageBuffer; + FormatMessage( FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, + NULL, dwIOError, MAKELANGID( LANG_NEUTRAL, SUBLANG_DEFAULT ), // Default language + ( LPTSTR ) & messageBuffer, 0, NULL ); + // something has gone wrong here... + RAKNET_DEBUG_PRINTF( "gethostbyname failed:Error code - %d\n%s", dwIOError, messageBuffer ); + + //Free the buffer. + LocalFree( messageBuffer ); + return ; + } + + struct in_addr addr[ MAXIMUM_NUMBER_OF_INTERNAL_IDS ]; + int idx; + for ( idx = 0; idx < MAXIMUM_NUMBER_OF_INTERNAL_IDS; ++idx ) + { + if (phe->h_addr_list[ idx ] == 0) + break; + + memcpy( &addr[idx], phe->h_addr_list[ idx ], sizeof( struct in_addr ) ); + binaryAddresses[idx]=addr[idx].S_un.S_addr; + strcpy( ipList[ idx ], inet_ntoa( addr[idx] ) ); + + } + + for ( ; idx < MAXIMUM_NUMBER_OF_INTERNAL_IDS; ++idx ) + { + ipList[idx][0]=0; + } +} +#elif defined(ANDROID) +void GetMyIP_Linux( char ipList[ MAXIMUM_NUMBER_OF_INTERNAL_IDS ][ 16 ], unsigned int binaryAddresses[MAXIMUM_NUMBER_OF_INTERNAL_IDS] ) +{ + struct ifreq ifreqs[MAXIMUM_NUMBER_OF_INTERNAL_IDS]; + struct ifconf ifconf; + struct in_addr bin_addr; + + memset(&ifconf,0,sizeof(ifconf)); + ifconf.ifc_buf = (char*) (ifreqs); + ifconf.ifc_len = sizeof(ifreqs); + + { // get list of interfaces + int sock = socket(AF_INET,SOCK_STREAM,0); + if (sock < 0) + { + perror("Error creating socket"); + return; + } + if ((ioctl(sock, SIOCGIFCONF, (char*) &ifconf )) < 0 ) + { + perror("Error returned from ioctl(SIOGIFCONF)"); + ifconf.ifc_len = 0; + } + close(sock); + } + + int idx = 0; + int iface_count = ifconf.ifc_len/sizeof(struct ifreq); + printf("Interfaces (%d):\n", iface_count); + for( ; idx < iface_count; idx++) + { + char ip_addr[ 16/*INET_ADDRSTRLEN */]; + struct sockaddr_in *b = (struct sockaddr_in *) &(ifreqs[idx].ifr_addr); + const char* host = inet_ntop(AF_INET, & b->sin_addr, ip_addr, sizeof ip_addr); + strcpy( ipList[idx], host ); + if (inet_aton(host, &bin_addr) == 0) + { + perror("inet_aton error"); + continue; + } + else + { + binaryAddresses[idx] = bin_addr.s_addr; + } + printf("\t%-10s\t%s (%08x)\n", ifreqs[idx].ifr_name, ipList[idx], binaryAddresses[idx]); + } + for ( ; idx < MAXIMUM_NUMBER_OF_INTERNAL_IDS; ++idx ) + { + ipList[idx][0]=0; + } +} +#elif !defined(_XBOX) && !defined(X360) +void GetMyIP_Linux( char ipList[ MAXIMUM_NUMBER_OF_INTERNAL_IDS ][ 16 ], unsigned int binaryAddresses[MAXIMUM_NUMBER_OF_INTERNAL_IDS] ) +{ + struct ifaddrs *ifaddr, *ifa; + int family, s; + char host[NI_MAXHOST]; + struct in_addr linux_in_addr; + + if (getifaddrs(&ifaddr) == -1) { + printf( "Error getting interface list\n"); + } + + int idx = 0; + for (ifa = ifaddr; ifa != NULL; ifa = ifa->ifa_next) { + if (!ifa->ifa_addr) continue; + family = ifa->ifa_addr->sa_family; + + if (family == AF_INET) { + s = getnameinfo(ifa->ifa_addr, sizeof(struct sockaddr_in), host, NI_MAXHOST, NULL, 0, NI_NUMERICHOST); + if (s != 0) { + printf ("getnameinfo() failed: %s\n", gai_strerror(s)); + } + printf ("IP address: %s\n", host); + strcpy( ipList[ idx ], host ); + if (inet_aton(host, &linux_in_addr) == 0) { + perror("inet_aton"); + } + else { + binaryAddresses[idx]=linux_in_addr.s_addr; + } + idx++; + } + } + + for ( ; idx < MAXIMUM_NUMBER_OF_INTERNAL_IDS; ++idx ) + { + ipList[idx][0]=0; + } + + freeifaddrs(ifaddr); +} +#endif + +#if !defined(_XBOX) && !defined(X360) +void SocketLayer::GetMyIP( char ipList[ MAXIMUM_NUMBER_OF_INTERNAL_IDS ][ 16 ], unsigned int binaryAddresses[MAXIMUM_NUMBER_OF_INTERNAL_IDS] ) +{ +#if defined(_PS3) || defined(__PS3__) || defined(SN_TARGET_PS3) + +#elif defined(_WIN32) + GetMyIP_Win32(ipList, binaryAddresses); +#else + GetMyIP_Linux(ipList, binaryAddresses); +#endif +} +#endif + +unsigned short SocketLayer::GetLocalPort ( SOCKET s ) +{ + sockaddr_in sa; + socklen_t len = sizeof(sa); + if (getsockname(s, (sockaddr*)&sa, &len)!=0) + { +#if defined(_WIN32) && !defined(_XBOX) && !defined(X360) && defined(_DEBUG) + DWORD dwIOError = GetLastError(); + LPVOID messageBuffer; + FormatMessage( FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, + NULL, dwIOError, MAKELANGID( LANG_NEUTRAL, SUBLANG_DEFAULT ), // Default language + ( LPTSTR ) & messageBuffer, 0, NULL ); + // something has gone wrong here... + RAKNET_DEBUG_PRINTF( "getsockname failed:Error code - %d\n%s", dwIOError, messageBuffer ); + + //Free the buffer. + LocalFree( messageBuffer ); +#endif + return 0; + } + return ntohs(sa.sin_port); +} + +SystemAddress SocketLayer::GetSystemAddress ( SOCKET s ) +{ + sockaddr_in sa; + socklen_t len = sizeof(sa); + if (getsockname(s, (sockaddr*)&sa, &len)!=0) + { +#if defined(_WIN32) && !defined(_XBOX) && !defined(X360) && defined(_DEBUG) + DWORD dwIOError = GetLastError(); + LPVOID messageBuffer; + FormatMessage( FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, + NULL, dwIOError, MAKELANGID( LANG_NEUTRAL, SUBLANG_DEFAULT ), // Default language + ( LPTSTR ) & messageBuffer, 0, NULL ); + // something has gone wrong here... + RAKNET_DEBUG_PRINTF( "getsockname failed:Error code - %d\n%s", dwIOError, messageBuffer ); + + //Free the buffer. + LocalFree( messageBuffer ); +#endif + return UNASSIGNED_SYSTEM_ADDRESS; + } + + SystemAddress out; + out.port=ntohs(sa.sin_port); + out.binaryAddress=sa.sin_addr.s_addr; + return out; +} + +void SocketLayer::SetSocketLayerOverride(SocketLayerOverride *_slo) +{ + slo=_slo; +} + +#ifdef _MSC_VER +#pragma warning( pop ) +#endif diff --git a/RakNet/Sources/SocketLayer.h b/RakNet/Sources/SocketLayer.h new file mode 100644 index 0000000..5242b02 --- /dev/null +++ b/RakNet/Sources/SocketLayer.h @@ -0,0 +1,185 @@ +/// \file +/// \brief SocketLayer class implementation +/// +/// This file is part of RakNet Copyright 2003 Jenkins Software LLC +/// +/// Usage of RakNet is subject to the appropriate license agreement. + + + +#ifndef __SOCKET_LAYER_H +#define __SOCKET_LAYER_H + +#include "RakMemoryOverride.h" +#include "SocketIncludes.h" +#include "RakNetTypes.h" +#include "RakNetSmartPtr.h" +#include "RakNetSocket.h" +#include "Export.h" +#include "MTUSize.h" +#include "RakString.h" + +//#include "ClientContextStruct.h" + +class RakPeer; + +class RAK_DLL_EXPORT SocketLayerOverride +{ +public: + SocketLayerOverride() {} + virtual ~SocketLayerOverride() {} + + /// Called when SendTo would otherwise occur. + virtual int RakNetSendTo( SOCKET s, const char *data, int length, SystemAddress systemAddress )=0; + + /// Called when RecvFrom would otherwise occur. Return number of bytes read. Write data into dataOut + // Return -1 to use RakNet's normal recvfrom, 0 to abort RakNet's normal recvfrom, and positive to return data + virtual int RakNetRecvFrom( const SOCKET sIn, RakPeer *rakPeerIn, char dataOut[ MAXIMUM_MTU_SIZE ], SystemAddress *senderOut, bool calledFromMainThread )=0; +}; + + +// A platform independent implementation of Berkeley sockets, with settings used by RakNet +class SocketLayer +{ + +public: + + /// Default Constructor + SocketLayer(); + + // Destructor + ~SocketLayer(); + + // Get the singleton instance of the Socket Layer. + /// \return unique instance + static inline SocketLayer* Instance() + { + return & I; + } + + // Connect a socket to a remote host. + /// \param[in] writeSocket The local socket. + /// \param[in] binaryAddress The address of the remote host. + /// \param[in] port the remote port. + /// \return A new socket used for communication. + SOCKET Connect( SOCKET writeSocket, unsigned int binaryAddress, unsigned short port ); + + /// Creates a bound socket to listen for incoming connections on the specified port + /// \param[in] port the port number + /// \param[in] blockingSocket + /// \return A new socket used for accepting clients + SOCKET CreateBoundSocket( unsigned short port, bool blockingSocket, const char *forceHostAddress, unsigned int sleepOn10048 ); + SOCKET CreateBoundSocket_PS3Lobby( unsigned short port, bool blockingSocket, const char *forceHostAddress ); + + /// Returns if this specified port is in use, for UDP + /// \param[in] port the port number + /// \return If this port is already in use + static bool IsPortInUse(unsigned short port, const char *hostAddress=0); + + const char* DomainNameToIP( const char *domainName ); + + /// Start an asynchronous read using the specified socket. The callback will use the specified SystemAddress (associated with this socket) and call either the client or the server callback (one or + /// the other should be 0) + /// \note Was used for deprecated IO completion ports. + bool AssociateSocketWithCompletionPortAndRead( SOCKET readSocket, unsigned int binaryAddress, unsigned short port, RakPeer* rakPeer ); + + /// Write \a data of length \a length to \a writeSocket + /// \param[in] writeSocket The socket to write to + /// \param[in] data The data to write + /// \param[in] length The length of \a data + void Write( const SOCKET writeSocket, const char* data, const int length ); + + /// Read data from a socket + /// \param[in] s the socket + /// \param[in] rakPeer The instance of rakPeer containing the recvFrom C callback + /// \param[in] errorCode An error code if an error occured . + /// \param[in] connectionSocketIndex Which of the sockets in RakPeer we are using + /// \return Returns true if you successfully read data, false on error. + int RecvFrom( const SOCKET s, RakPeer *rakPeer, int *errorCode, RakNetSmartPtr rakNetSocket, unsigned short remotePortRakNetWasStartedOn_PS3 ); + // Newer version, for reads from a thread + static void RecvFromBlocking( const SOCKET s, RakPeer *rakPeer, unsigned short remotePortRakNetWasStartedOn_PS3, char *dataOut, int *bytesReadOut, SystemAddress *systemAddressOut, RakNetTimeUS *timeRead ); + + /// Read raw unprocessed data from the socket + /// \param[in] s the socket + /// \param[in] remotePortRakNetWasStartedOn_PS3 was started on the PS3? + /// \param[out] dataOut The data read + /// \param[out] bytesReadOut Number of bytes read, -1 if nothing was read + /// \param[out] systemAddressOut Who is sending the packet + /// \param[out] timeRead Time that thre read occured + void RawRecvFromNonBlocking( const SOCKET s, unsigned short remotePortRakNetWasStartedOn_PS3, char *dataOut, int *bytesReadOut, SystemAddress *systemAddressOut, RakNetTimeUS *timeRead ); + + + /// Given a socket and IP, retrieves the subnet mask, on linux the socket is unused + /// \param[in] inSock the socket + /// \param[in] inIpString The ip of the interface you wish to retrieve the subnet mask from + /// \return Returns the ip dotted subnet mask if successful, otherwise returns empty string ("") + RakNet::RakString GetSubNetForSocketAndIp(SOCKET inSock, RakNet::RakString inIpString); + + + /// Sets the socket flags to nonblocking + /// \param[in] listenSocket the socket to set + void SetNonBlocking( SOCKET listenSocket); + +#if !defined(_XBOX) && !defined(_X360) + /// Retrieve all local IP address in a string format. + /// \param[in] s The socket whose port we are referring to + /// \param[in] ipList An array of ip address in dotted notation. + void GetMyIP( char ipList[ MAXIMUM_NUMBER_OF_INTERNAL_IDS ][ 16 ], unsigned int binaryAddresses[MAXIMUM_NUMBER_OF_INTERNAL_IDS] ); +#endif + + /// Call sendto (UDP obviously) + /// \param[in] s the socket + /// \param[in] data The byte buffer to send + /// \param[in] length The length of the \a data in bytes + /// \param[in] ip The address of the remote host in dotted notation. + /// \param[in] port The port number to send to. + /// \return 0 on success, nonzero on failure. + int SendTo( SOCKET s, const char *data, int length, const char ip[ 16 ], unsigned short port, unsigned short remotePortRakNetWasStartedOn_PS3 ); + + /// Call sendto (UDP obviously) + /// It won't reach the recipient, except on a LAN + /// However, this is good for opening routers / firewalls + /// \param[in] s the socket + /// \param[in] data The byte buffer to send + /// \param[in] length The length of the \a data in bytes + /// \param[in] ip The address of the remote host in dotted notation. + /// \param[in] port The port number to send to. + /// \param[in] ttl Max hops of datagram + /// \return 0 on success, nonzero on failure. + int SendToTTL( SOCKET s, const char *data, int length, const char ip[ 16 ], unsigned short port, int ttl ); + + /// Call sendto (UDP obviously) + /// \param[in] s the socket + /// \param[in] data The byte buffer to send + /// \param[in] length The length of the \a data in bytes + /// \param[in] binaryAddress The address of the remote host in binary format. + /// \param[in] port The port number to send to. + /// \return 0 on success, nonzero on failure. + int SendTo( SOCKET s, const char *data, int length, unsigned int binaryAddress, unsigned short port, unsigned short remotePortRakNetWasStartedOn_PS3 ); + + /// Returns the local port, useful when passing 0 as the startup port. + /// \param[in] s The socket whose port we are referring to + /// \return The local port + static unsigned short GetLocalPort ( SOCKET s ); + + static SystemAddress GetSystemAddress ( SOCKET s ); + + void SetSocketLayerOverride(SocketLayerOverride *_slo); + SocketLayerOverride* GetSocketLayerOverride(void) const {return slo;} + + int SendTo_PS3Lobby( SOCKET s, const char *data, int length, unsigned int binaryAddress, unsigned short port, unsigned short remotePortRakNetWasStartedOn_PS3 ); + int SendTo_360( SOCKET s, const char *data, int length, const char *voiceData, int voiceLength, unsigned int binaryAddress, unsigned short port ); + int SendTo_PC( SOCKET s, const char *data, int length, unsigned int binaryAddress, unsigned short port ); + + + static void SetDoNotFragment( SOCKET listenSocket, int opt ); +private: + + + static SocketLayer I; + void SetSocketOptions( SOCKET listenSocket); + SocketLayerOverride *slo; +}; + +#endif + diff --git a/RakNet/Sources/StringCompressor.cpp b/RakNet/Sources/StringCompressor.cpp new file mode 100644 index 0000000..b9dadd3 --- /dev/null +++ b/RakNet/Sources/StringCompressor.cpp @@ -0,0 +1,499 @@ +/// \file +/// +/// This file is part of RakNet Copyright 2003 Jenkins Software LLC +/// +/// Usage of RakNet is subject to the appropriate license agreement. + + +#include "StringCompressor.h" +#include "DS_HuffmanEncodingTree.h" +#include "BitStream.h" +#include "RakString.h" +#include "RakAssert.h" +#include +#if !defined(_PS3) && !defined(__PS3__) && !defined(SN_TARGET_PS3) +#include +#endif +#if defined(_PS3) || defined(__PS3__) || defined(SN_TARGET_PS3) + +#endif + +using namespace RakNet; + +StringCompressor* StringCompressor::instance=0; +int StringCompressor::referenceCount=0; + +void StringCompressor::AddReference(void) +{ + if (++referenceCount==1) + { + instance = RakNet::OP_NEW( __FILE__, __LINE__ ); + } +} +void StringCompressor::RemoveReference(void) +{ + RakAssert(referenceCount > 0); + + if (referenceCount > 0) + { + if (--referenceCount==0) + { + RakNet::OP_DELETE(instance, __FILE__, __LINE__); + instance=0; + } + } +} + +StringCompressor* StringCompressor::Instance(void) +{ + return instance; +} + +unsigned int englishCharacterFrequencies[ 256 ] = +{ + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 722, + 0, + 0, + 2, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 11084, + 58, + 63, + 1, + 0, + 31, + 0, + 317, + 64, + 64, + 44, + 0, + 695, + 62, + 980, + 266, + 69, + 67, + 56, + 7, + 73, + 3, + 14, + 2, + 69, + 1, + 167, + 9, + 1, + 2, + 25, + 94, + 0, + 195, + 139, + 34, + 96, + 48, + 103, + 56, + 125, + 653, + 21, + 5, + 23, + 64, + 85, + 44, + 34, + 7, + 92, + 76, + 147, + 12, + 14, + 57, + 15, + 39, + 15, + 1, + 1, + 1, + 2, + 3, + 0, + 3611, + 845, + 1077, + 1884, + 5870, + 841, + 1057, + 2501, + 3212, + 164, + 531, + 2019, + 1330, + 3056, + 4037, + 848, + 47, + 2586, + 2919, + 4771, + 1707, + 535, + 1106, + 152, + 1243, + 100, + 0, + 2, + 0, + 10, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0 +}; + +StringCompressor::StringCompressor() +{ + DataStructures::Map::IMPLEMENT_DEFAULT_COMPARISON(); + + // Make a default tree immediately, since this is used for RPC possibly from multiple threads at the same time + HuffmanEncodingTree *huffmanEncodingTree = RakNet::OP_NEW( __FILE__, __LINE__ ); + huffmanEncodingTree->GenerateFromFrequencyTable( englishCharacterFrequencies ); + + huffmanEncodingTrees.Set(0, huffmanEncodingTree); +} +void StringCompressor::GenerateTreeFromStrings( unsigned char *input, unsigned inputLength, int languageID ) +{ + HuffmanEncodingTree *huffmanEncodingTree; + if (huffmanEncodingTrees.Has(languageID)) + { + huffmanEncodingTree = huffmanEncodingTrees.Get(languageID); + RakNet::OP_DELETE(huffmanEncodingTree, __FILE__, __LINE__); + } + + unsigned index; + unsigned int frequencyTable[ 256 ]; + + if ( inputLength == 0 ) + return ; + + // Zero out the frequency table + memset( frequencyTable, 0, sizeof( frequencyTable ) ); + + // Generate the frequency table from the strings + for ( index = 0; index < inputLength; index++ ) + frequencyTable[ input[ index ] ] ++; + + // Build the tree + huffmanEncodingTree = RakNet::OP_NEW( __FILE__, __LINE__ ); + huffmanEncodingTree->GenerateFromFrequencyTable( frequencyTable ); + huffmanEncodingTrees.Set(languageID, huffmanEncodingTree); +} + +StringCompressor::~StringCompressor() +{ + for (unsigned i=0; i < huffmanEncodingTrees.Size(); i++) + RakNet::OP_DELETE(huffmanEncodingTrees[i], __FILE__, __LINE__); +} + +void StringCompressor::EncodeString( const char *input, int maxCharsToWrite, RakNet::BitStream *output, int languageID ) +{ + HuffmanEncodingTree *huffmanEncodingTree; + if (huffmanEncodingTrees.Has(languageID)==false) + return; + huffmanEncodingTree=huffmanEncodingTrees.Get(languageID); + + if ( input == 0 ) + { + output->WriteCompressed( (unsigned int) 0 ); + return ; + } + + RakNet::BitStream encodedBitStream; + + unsigned int stringBitLength; + + int charsToWrite; + + if ( maxCharsToWrite<=0 || ( int ) strlen( input ) < maxCharsToWrite ) + charsToWrite = ( int ) strlen( input ); + else + charsToWrite = maxCharsToWrite - 1; + + huffmanEncodingTree->EncodeArray( ( unsigned char* ) input, charsToWrite, &encodedBitStream ); + + stringBitLength = (unsigned int) encodedBitStream.GetNumberOfBitsUsed(); + + output->WriteCompressed( stringBitLength ); + + output->WriteBits( encodedBitStream.GetData(), stringBitLength ); +} + +bool StringCompressor::DecodeString( char *output, int maxCharsToWrite, RakNet::BitStream *input, int languageID ) +{ + HuffmanEncodingTree *huffmanEncodingTree; + if (huffmanEncodingTrees.Has(languageID)==false) + return false; + if (maxCharsToWrite<=0) + return false; + huffmanEncodingTree=huffmanEncodingTrees.Get(languageID); + + unsigned int stringBitLength; + int bytesInStream; + + output[ 0 ] = 0; + + if ( input->ReadCompressed( stringBitLength ) == false ) + return false; + + if ( (unsigned) input->GetNumberOfUnreadBits() < stringBitLength ) + return false; + + bytesInStream = huffmanEncodingTree->DecodeArray( input, stringBitLength, maxCharsToWrite, ( unsigned char* ) output ); + + if ( bytesInStream < maxCharsToWrite ) + output[ bytesInStream ] = 0; + else + output[ maxCharsToWrite - 1 ] = 0; + + return true; +} +#ifdef _CSTRING_COMPRESSOR +void StringCompressor::EncodeString( const CString &input, int maxCharsToWrite, RakNet::BitStream *output ) +{ + LPTSTR p = input; + EncodeString(p, maxCharsToWrite*sizeof(TCHAR), output, languageID); +} +bool StringCompressor::DecodeString( CString &output, int maxCharsToWrite, RakNet::BitStream *input, int languageID ) +{ + LPSTR p = output.GetBuffer(maxCharsToWrite*sizeof(TCHAR)); + DecodeString(p,maxCharsToWrite*sizeof(TCHAR), input, languageID); + output.ReleaseBuffer(0) + +} +#endif +#ifdef _STD_STRING_COMPRESSOR +void StringCompressor::EncodeString( const std::string &input, int maxCharsToWrite, RakNet::BitStream *output, int languageID ) +{ + EncodeString(input.c_str(), maxCharsToWrite, output, languageID); +} +bool StringCompressor::DecodeString( std::string *output, int maxCharsToWrite, RakNet::BitStream *input, int languageID ) +{ + if (maxCharsToWrite <= 0) + { + output->clear(); + return true; + } + + char *destinationBlock; + bool out; + +#if !defined(_XBOX) && !defined(_X360) + if (maxCharsToWrite < MAX_ALLOCA_STACK_ALLOCATION) + { + destinationBlock = (char*) alloca(maxCharsToWrite); + out=DecodeString(destinationBlock, maxCharsToWrite, input, languageID); + *output=destinationBlock; + } + else +#endif + { + destinationBlock = (char*) rakMalloc_Ex( maxCharsToWrite, __FILE__, __LINE__ ); + out=DecodeString(destinationBlock, maxCharsToWrite, input, languageID); + *output=destinationBlock; + rakFree_Ex(destinationBlock, __FILE__, __LINE__ ); + } + + return out; +} +#endif +void StringCompressor::EncodeString( const RakString *input, int maxCharsToWrite, RakNet::BitStream *output, int languageID ) +{ + EncodeString(input->C_String(), maxCharsToWrite, output, languageID); +} +bool StringCompressor::DecodeString( RakString *output, int maxCharsToWrite, RakNet::BitStream *input, int languageID ) +{ + if (maxCharsToWrite <= 0) + { + output->Clear(); + return true; + } + + char *destinationBlock; + bool out; + +#if !defined(_XBOX) && !defined(_X360) + if (maxCharsToWrite < MAX_ALLOCA_STACK_ALLOCATION) + { + destinationBlock = (char*) alloca(maxCharsToWrite); + out=DecodeString(destinationBlock, maxCharsToWrite, input, languageID); + *output=destinationBlock; + } + else +#endif + { + destinationBlock = (char*) rakMalloc_Ex( maxCharsToWrite, __FILE__, __LINE__ ); + out=DecodeString(destinationBlock, maxCharsToWrite, input, languageID); + *output=destinationBlock; + rakFree_Ex(destinationBlock, __FILE__, __LINE__ ); + } + + return out; +} diff --git a/RakNet/Sources/StringCompressor.h b/RakNet/Sources/StringCompressor.h new file mode 100644 index 0000000..014309f --- /dev/null +++ b/RakNet/Sources/StringCompressor.h @@ -0,0 +1,102 @@ +/// \file +/// \brief \b Compresses/Decompresses ASCII strings and writes/reads them to BitStream class instances. You can use this to easily serialize and deserialize your own strings. +/// +/// This file is part of RakNet Copyright 2003 Jenkins Software LLC +/// +/// Usage of RakNet is subject to the appropriate license agreement. + + +#ifndef __STRING_COMPRESSOR_H +#define __STRING_COMPRESSOR_H + +#include "Export.h" +#include "DS_Map.h" +#include "RakMemoryOverride.h" + + +//#include + +/// Forward declaration +namespace RakNet +{ + class BitStream; + class RakString; +}; + +class HuffmanEncodingTree; + +/// \brief Writes and reads strings to and from bitstreams. +/// +/// Only works with ASCII strings. The default compression is for English. +/// You can call GenerateTreeFromStrings to compress and decompress other languages efficiently as well. +class RAK_DLL_EXPORT StringCompressor +{ +public: + + // Destructor + ~StringCompressor(); + + /// static function because only static functions can access static members + /// The RakPeer constructor adds a reference to this class, so don't call this until an instance of RakPeer exists, or unless you call AddReference yourself. + /// \return the unique instance of the StringCompressor + static StringCompressor* Instance(void); + + /// Given an array of strings, such as a chat log, generate the optimal encoding tree for it. + /// This function is optional and if it is not called a default tree will be used instead. + /// \param[in] input An array of bytes which should point to text. + /// \param[in] inputLength Length of \a input + /// \param[in] languageID An identifier for the language / string table to generate the tree for. English is automatically created with ID 0 in the constructor. + void GenerateTreeFromStrings( unsigned char *input, unsigned inputLength, int languageID ); + + /// Writes input to output, compressed. Takes care of the null terminator for you. + /// \param[in] input Pointer to an ASCII string + /// \param[in] maxCharsToWrite The max number of bytes to write of \a input. Use 0 to mean no limit. + /// \param[out] output The bitstream to write the compressed string to + /// \param[in] languageID Which language to use + void EncodeString( const char *input, int maxCharsToWrite, RakNet::BitStream *output, int languageID=0 ); + + /// Writes input to output, uncompressed. Takes care of the null terminator for you. + /// \param[out] output A block of bytes to receive the output + /// \param[in] maxCharsToWrite Size, in bytes, of \a output . A NULL terminator will always be appended to the output string. If the maxCharsToWrite is not large enough, the string will be truncated. + /// \param[in] input The bitstream containing the compressed string + /// \param[in] languageID Which language to use + bool DecodeString( char *output, int maxCharsToWrite, RakNet::BitStream *input, int languageID=0 ); + +#ifdef _CSTRING_COMPRESSOR + void EncodeString( const CString &input, int maxCharsToWrite, RakNet::BitStream *output, int languageID=0 ); + bool DecodeString( CString &output, int maxCharsToWrite, RakNet::BitStream *input, int languageID=0 ); +#endif + +#ifdef _STD_STRING_COMPRESSOR + void EncodeString( const std::string &input, int maxCharsToWrite, RakNet::BitStream *output, int languageID=0 ); + bool DecodeString( std::string *output, int maxCharsToWrite, RakNet::BitStream *input, int languageID=0 ); +#endif + + void EncodeString( const RakNet::RakString *input, int maxCharsToWrite, RakNet::BitStream *output, int languageID=0 ); + bool DecodeString( RakNet::RakString *output, int maxCharsToWrite, RakNet::BitStream *input, int languageID=0 ); + + /// Used so I can allocate and deallocate this singleton at runtime + static void AddReference(void); + + /// Used so I can allocate and deallocate this singleton at runtime + static void RemoveReference(void); + + /// Private Constructor + StringCompressor(); + +private: + + /// Singleton instance + static StringCompressor *instance; + + /// Pointer to the huffman encoding trees. + DataStructures::Map huffmanEncodingTrees; + + static int referenceCount; +}; + +/// Define to more easily reference the string compressor instance. +/// The RakPeer constructor adds a reference to this class, so don't call this until an instance of RakPeer exists, or unless you call AddReference yourself. +#define stringCompressor StringCompressor::Instance() + +#endif diff --git a/RakNet/Sources/StringTable.cpp b/RakNet/Sources/StringTable.cpp new file mode 100644 index 0000000..7e2c003 --- /dev/null +++ b/RakNet/Sources/StringTable.cpp @@ -0,0 +1,144 @@ +#include "StringTable.h" +#include +#include "RakAssert.h" +#include +#include "BitStream.h" +#include "StringCompressor.h" +using namespace RakNet; + +StringTable* StringTable::instance=0; +int StringTable::referenceCount=0; + + +int StrAndBoolComp( char *const &key, const StrAndBool &data ) +{ + return strcmp(key,(const char*)data.str); +} + +StringTable::StringTable() +{ + +} + +StringTable::~StringTable() +{ + unsigned i; + for (i=0; i < orderedStringList.Size(); i++) + { + if (orderedStringList[i].b) + rakFree_Ex(orderedStringList[i].str, __FILE__, __LINE__ ); + } +} + +void StringTable::AddReference(void) +{ + if (++referenceCount==1) + { + instance = RakNet::OP_NEW( __FILE__, __LINE__ ); + } +} +void StringTable::RemoveReference(void) +{ + RakAssert(referenceCount > 0); + + if (referenceCount > 0) + { + if (--referenceCount==0) + { + RakNet::OP_DELETE(instance, __FILE__, __LINE__); + instance=0; + } + } +} + +StringTable* StringTable::Instance(void) +{ + return instance; +} + +void StringTable::AddString(const char *str, bool copyString) +{ + StrAndBool sab; + sab.b=copyString; + if (copyString) + { + sab.str = (char*) rakMalloc_Ex( strlen(str)+1, __FILE__, __LINE__ ); + strcpy(sab.str, str); + } + else + { + sab.str=(char*)str; + } + + // If it asserts inside here you are adding duplicate strings. + if (orderedStringList.Insert(sab.str,sab, true, __FILE__,__LINE__)!=(unsigned)-1) + { + if (copyString) + RakNet::OP_DELETE(sab.str, __FILE__, __LINE__); + } + + // If this assert hits you need to increase the range of StringTableType + RakAssert(orderedStringList.Size() < (StringTableType)-1); + +} +void StringTable::EncodeString( const char *input, int maxCharsToWrite, RakNet::BitStream *output ) +{ + unsigned index; + bool objectExists; + // This is fast because the list is kept ordered. + index=orderedStringList.GetIndexFromKey((char*)input, &objectExists); + if (objectExists) + { + output->Write(true); + output->Write((StringTableType)index); + } + else + { + LogStringNotFound(input); + output->Write(false); + stringCompressor->EncodeString(input, maxCharsToWrite, output); + } +} + +bool StringTable::DecodeString( char *output, int maxCharsToWrite, RakNet::BitStream *input ) +{ + bool hasIndex; + RakAssert(maxCharsToWrite>0); + + if (maxCharsToWrite==0) + return false; + if (!input->Read(hasIndex)) + return false; + if (hasIndex==false) + { + stringCompressor->DecodeString(output, maxCharsToWrite, input); + } + else + { + StringTableType index; + if (!input->Read(index)) + return false; + if (index >= orderedStringList.Size()) + { +#ifdef _DEBUG + // Critical error - got a string index out of range, which means AddString was called more times on the remote system than on this system. + // All systems must call AddString the same number of types, with the same strings in the same order. + RakAssert(0); +#endif + return false; + } + + strncpy(output, orderedStringList[index].str, maxCharsToWrite); + output[maxCharsToWrite-1]=0; + } + + return true; +} +void StringTable::LogStringNotFound(const char *strName) +{ + (void) strName; + +#ifdef _DEBUG + RAKNET_DEBUG_PRINTF("Efficiency Warning! Unregistered String %s sent to StringTable.\n", strName); +#endif +} diff --git a/RakNet/Sources/StringTable.h b/RakNet/Sources/StringTable.h new file mode 100644 index 0000000..d3fb04d --- /dev/null +++ b/RakNet/Sources/StringTable.h @@ -0,0 +1,95 @@ +/// \file +/// \brief A simple class to encode and decode known strings based on a lookup table. Similar to the StringCompressor class. +/// +/// This file is part of RakNet Copyright 2003 Jenkins Software LLC +/// +/// Usage of RakNet is subject to the appropriate license agreement. + + +#ifndef __STRING_TABLE_H +#define __STRING_TABLE_H + +#include "DS_OrderedList.h" +#include "Export.h" +#include "RakMemoryOverride.h" + +/// Forward declaration +namespace RakNet +{ + class BitStream; +}; + +/// StringTableType should be the smallest type possible, or else it defeats the purpose of the StringTable class, which is to save bandwidth. +typedef unsigned char StringTableType; + +/// The string plus a bool telling us if this string was copied or not. +struct StrAndBool +{ + char *str; + bool b; +}; +int RAK_DLL_EXPORT StrAndBoolComp( char *const &key, const StrAndBool &data ); + +namespace RakNet +{ + /// \details This is an even more efficient alternative to StringCompressor in that it writes a single byte from a lookup table and only does compression.
      + /// if the string does not already exist in the table.
      + /// All string tables must match on all systems - hence you must add all the strings in the same order on all systems.
      + /// Furthermore, this must be done before sending packets that use this class, since the strings are ordered for fast lookup. Adding after that time would mess up all the indices so don't do it.
      + /// Don't use this class to write strings which were not previously registered with AddString, since you just waste bandwidth then. Use StringCompressor instead. + /// \brief Writes a string index, instead of the whole string + class RAK_DLL_EXPORT StringTable + { + public: + + // Destructor + ~StringTable(); + + /// static function because only static functions can access static members + /// The RakPeer constructor adds a reference to this class, so don't call this until an instance of RakPeer exists, or unless you call AddReference yourself. + /// \return the unique instance of the StringTable + static StringTable* Instance(void); + + /// Add a string to the string table. + /// \param[in] str The string to add to the string table + /// \param[in] copyString true to make a copy of the passed string (takes more memory), false to not do so (if your string is in static memory). + void AddString(const char *str, bool copyString); + + /// Writes input to output, compressed. Takes care of the null terminator for you. + /// Relies on the StringCompressor class, which is automatically reference counted in the constructor and destructor in RakPeer. You can call the reference counting functions yourself if you wish too. + /// \param[in] input Pointer to an ASCII string + /// \param[in] maxCharsToWrite The size of \a input + /// \param[out] output The bitstream to write the compressed string to + void EncodeString( const char *input, int maxCharsToWrite, RakNet::BitStream *output ); + + /// Writes input to output, uncompressed. Takes care of the null terminator for you. + /// Relies on the StringCompressor class, which is automatically reference counted in the constructor and destructor in RakPeer. You can call the reference counting functions yourself if you wish too. + /// \param[out] output A block of bytes to receive the output + /// \param[in] maxCharsToWrite Size, in bytes, of \a output . A NULL terminator will always be appended to the output string. If the maxCharsToWrite is not large enough, the string will be truncated. + /// \param[in] input The bitstream containing the compressed string + bool DecodeString( char *output, int maxCharsToWrite, RakNet::BitStream *input ); + + /// Used so I can allocate and deallocate this singleton at runtime + static void AddReference(void); + + /// Used so I can allocate and deallocate this singleton at runtime + static void RemoveReference(void); + + /// Private Constructor + StringTable(); + + protected: + /// Called when you mess up and send a string using this class that was not registered with AddString + /// \param[in] maxCharsToWrite Size, in bytes, of \a output . A NULL terminator will always be appended to the output string. If the maxCharsToWrite is not large enough, the string will be truncated. + void LogStringNotFound(const char *strName); + + /// Singleton instance + static StringTable *instance; + static int referenceCount; + + DataStructures::OrderedList orderedStringList; + }; +} + + +#endif diff --git a/RakNet/Sources/SuperFastHash.cpp b/RakNet/Sources/SuperFastHash.cpp new file mode 100644 index 0000000..1166740 --- /dev/null +++ b/RakNet/Sources/SuperFastHash.cpp @@ -0,0 +1,120 @@ +#include "SuperFastHash.h" +#include "NativeTypes.h" +#include + +#if !defined(_WIN32) +#include +#endif + +#undef get16bits +#if (defined(__GNUC__) && defined(__i386__)) || defined(__WATCOMC__) \ + || defined(_MSC_VER) || defined (__BORLANDC__) || defined (__TURBOC__) +#define get16bits(d) (*((const uint16_t *) (d))) +#endif + +#if !defined (get16bits) +#define get16bits(d) ((((uint32_t)(((const uint8_t *)(d))[1])) << 8)\ + +(uint32_t)(((const uint8_t *)(d))[0]) ) +#endif + +static const int INCREMENTAL_READ_BLOCK=65536; + +unsigned int SuperFastHash (const char * data, int length) +{ + // All this is necessary or the hash does not match SuperFastHashIncremental + int bytesRemaining=length; + unsigned int lastHash = length; + int offset=0; + while (bytesRemaining>=INCREMENTAL_READ_BLOCK) + { + lastHash=SuperFastHashIncremental (data+offset, INCREMENTAL_READ_BLOCK, lastHash ); + bytesRemaining-=INCREMENTAL_READ_BLOCK; + offset+=INCREMENTAL_READ_BLOCK; + } + if (bytesRemaining>0) + { + lastHash=SuperFastHashIncremental (data+offset, bytesRemaining, lastHash ); + } + return lastHash; + +// return SuperFastHashIncremental(data,len,len); +} +unsigned int SuperFastHashIncremental (const char * data, int len, unsigned int lastHash ) +{ + uint32_t hash = (uint32_t) lastHash; + uint32_t tmp; + int rem; + + if (len <= 0 || data == NULL) return 0; + + rem = len & 3; + len >>= 2; + + /* Main loop */ + for (;len > 0; len--) { + hash += get16bits (data); + tmp = (get16bits (data+2) << 11) ^ hash; + hash = (hash << 16) ^ tmp; + data += 2*sizeof (uint16_t); + hash += hash >> 11; + } + + /* Handle end cases */ + switch (rem) { + case 3: hash += get16bits (data); + hash ^= hash << 16; + hash ^= data[sizeof (uint16_t)] << 18; + hash += hash >> 11; + break; + case 2: hash += get16bits (data); + hash ^= hash << 11; + hash += hash >> 17; + break; + case 1: hash += *data; + hash ^= hash << 10; + hash += hash >> 1; + } + + /* Force "avalanching" of final 127 bits */ + hash ^= hash << 3; + hash += hash >> 5; + hash ^= hash << 4; + hash += hash >> 17; + hash ^= hash << 25; + hash += hash >> 6; + + return (unsigned int) hash; + +} + +unsigned int SuperFastHashFile (const char * filename) +{ + FILE *fp = fopen(filename, "rb"); + if (fp==0) + return 0; + unsigned int hash = SuperFastHashFilePtr(fp); + fclose(fp); + return hash; +} + +unsigned int SuperFastHashFilePtr (FILE *fp) +{ + fseek(fp, 0, SEEK_END); + int length = ftell(fp); + fseek(fp, 0, SEEK_SET); + int bytesRemaining=length; + unsigned int lastHash = length; + char readBlock[INCREMENTAL_READ_BLOCK]; + while (bytesRemaining>=(int) sizeof(readBlock)) + { + fread(readBlock, sizeof(readBlock), 1, fp); + lastHash=SuperFastHashIncremental (readBlock, (int) sizeof(readBlock), lastHash ); + bytesRemaining-=(int) sizeof(readBlock); + } + if (bytesRemaining>0) + { + fread(readBlock, bytesRemaining, 1, fp); + lastHash=SuperFastHashIncremental (readBlock, bytesRemaining, lastHash ); + } + return lastHash; +} diff --git a/RakNet/Sources/SuperFastHash.h b/RakNet/Sources/SuperFastHash.h new file mode 100644 index 0000000..5cd9ecb --- /dev/null +++ b/RakNet/Sources/SuperFastHash.h @@ -0,0 +1,16 @@ +#ifndef __SUPER_FAST_HASH_H +#define __SUPER_FAST_HASH_H + +#include + +// From http://www.azillionmonkeys.com/qed/hash.html +// Author of main code is Paul Hsieh +// I just added some convenience functions +// Also note http://burtleburtle.net/bob/hash/doobs.html, which shows that this is 20% faster than the one on that page but has more collisions + +unsigned int SuperFastHash (const char * data, int length); +unsigned int SuperFastHashIncremental (const char * data, int len, unsigned int lastHash ); +unsigned int SuperFastHashFile (const char * filename); +unsigned int SuperFastHashFilePtr (FILE *fp); + +#endif diff --git a/RakNet/Sources/SystemAddressList.cpp b/RakNet/Sources/SystemAddressList.cpp new file mode 100644 index 0000000..f1af8b7 --- /dev/null +++ b/RakNet/Sources/SystemAddressList.cpp @@ -0,0 +1,131 @@ +#include "SystemAddressList.h" +#include "Rand.h" +#include "RakAssert.h" +#include "BitStream.h" +#include + +SystemAddressList::SystemAddressList() +{ + +} +SystemAddressList::SystemAddressList(SystemAddress system) +{ + systemList.Insert(system, __FILE__, __LINE__); +} +void SystemAddressList::AddSystem(SystemAddress system) +{ + systemList.Insert(system, __FILE__, __LINE__); +} +void SystemAddressList::RandomizeOrder(void) +{ + unsigned index, size, randIndex; + SystemAddress temp; + size = systemList.Size(); + for (index=0; index < size; index++) + { + randIndex=index + (randomMT() % (size-index)); + if (randIndex!=index) + { + temp=systemList[index]; + systemList[index]=systemList[randIndex]; + systemList[randIndex]=temp; + } + } +} +void SystemAddressList::Serialize(RakNet::BitStream *out) +{ + out->Write((unsigned short) systemList.Size()); + unsigned index; + for (index=0; index < systemList.Size(); index++) + out->Write(systemList[index]); +} +bool SystemAddressList::Deserialize(RakNet::BitStream *in) +{ + unsigned short systemListSize; + SystemAddress systemAddress; + unsigned index; + if (in->Read(systemListSize)==false) + { + RakAssert(0); + return false; + } + systemList.Clear(false, __FILE__, __LINE__); + for (index=0; index < systemListSize; index++) + { + if (in->Read(systemAddress)==false) + { + RakAssert(0); + systemList.Clear(false, __FILE__, __LINE__); + return false; + } + systemList.Insert(systemAddress, __FILE__, __LINE__); + + } + return true; +} +void SystemAddressList::RemoveSystem(SystemAddress system) +{ + unsigned i; + for (i=0; i < systemList.Size(); i++) + { + if (systemList[i]==system) + { + systemList.RemoveAtIndex(i); + return; + } + } +} +DataStructures::List * SystemAddressList::GetList(void) +{ + return &systemList; +} +bool SystemAddressList::Save(const char *filename) +{ + RakNet::BitStream temp; + Serialize(&temp); + FILE *fp = fopen(filename, "wb"); + if (fp) + { + fwrite(temp.GetData(), (size_t) temp.GetNumberOfBytesUsed(), 1, fp); + fclose(fp); + return true; + } + return false; +} +bool SystemAddressList::Load(const char *filename) +{ + FILE *fp = NULL; + unsigned long fileSize; + + if ( ( fp = fopen( filename, "rb" ) ) == 0 ) + return false; + + fseek( fp, 0, SEEK_END ); + fileSize = ftell( fp ); + fseek( fp, 0, SEEK_SET ); + if (fileSize==0) + { + fclose(fp); + return false; + } + unsigned char *filedata = (unsigned char*) rakMalloc_Ex( fileSize, __FILE__, __LINE__ ); + fread(filedata, fileSize, 1, fp); + fclose(fp); + + RakNet::BitStream bs(filedata, fileSize, false); + Deserialize(&bs); + rakFree_Ex(filedata, __FILE__, __LINE__ ); + return true; +} +unsigned SystemAddressList::Size(void) const +{ + return systemList.Size(); +} +SystemAddress& SystemAddressList::operator[] ( const unsigned int position ) const +{ + return systemList[position]; +} +void SystemAddressList::Clear(void) +{ + systemList.Clear(false, __FILE__, __LINE__); +} diff --git a/RakNet/Sources/SystemAddressList.h b/RakNet/Sources/SystemAddressList.h new file mode 100644 index 0000000..a7f4326 --- /dev/null +++ b/RakNet/Sources/SystemAddressList.h @@ -0,0 +1,37 @@ +/// \file +/// \brief Just a class to hold a list of systems +/// +/// This file is part of RakNet Copyright 2003 Jenkins Software LLC +/// +/// Usage of RakNet is subject to the appropriate license agreement. + + +#ifndef __SYSTEM_ID_LIST_H +#define __SYSTEM_ID_LIST_H + +#include "RakNetTypes.h" +#include "DS_OrderedList.h" + +class SystemAddressList +{ +public: + SystemAddressList(); + SystemAddressList(SystemAddress system); + void AddSystem(SystemAddress system); + void RandomizeOrder(void); + void Serialize(RakNet::BitStream *out); + bool Deserialize(RakNet::BitStream *in); + bool Save(const char *filename); + bool Load(const char *filename); + void RemoveSystem(SystemAddress system); + unsigned Size(void) const; + SystemAddress& operator[] ( const unsigned int position ) const; + void Clear(void); + + DataStructures::List * GetList(void); + +protected: + DataStructures::List systemList; +}; + +#endif diff --git a/RakNet/Sources/TCPInterface.cpp b/RakNet/Sources/TCPInterface.cpp new file mode 100644 index 0000000..1032f19 --- /dev/null +++ b/RakNet/Sources/TCPInterface.cpp @@ -0,0 +1,982 @@ +#include "NativeFeatureIncludes.h" +#if _RAKNET_SUPPORT_TCPInterface==1 + +/// \file +/// \brief A simple TCP based server allowing sends and receives. Can be connected to by a telnet client. +/// +/// This file is part of RakNet Copyright 2003 Jenkins Software LLC +/// +/// Usage of RakNet is subject to the appropriate license agreement. + + +#include "TCPInterface.h" +#ifdef _WIN32 +typedef int socklen_t; +#else +#include +#include +#include +#endif +#include +#include "RakAssert.h" +#include +#include "RakAssert.h" +#include "RakSleep.h" +#include "StringCompressor.h" +#include "StringTable.h" + +#ifdef _DO_PRINTF +#endif + +#ifdef _WIN32 +#include "WSAStartupSingleton.h" +#endif + +RAK_THREAD_DECLARATION(UpdateTCPInterfaceLoop); +RAK_THREAD_DECLARATION(ConnectionAttemptLoop); + +#ifdef _MSC_VER +#pragma warning( push ) +#endif + +TCPInterface::TCPInterface() +{ + isStarted=false; + threadRunning=false; + listenSocket=(SOCKET) -1; + remoteClients=0; + remoteClientsLength=0; + + StringCompressor::AddReference(); + RakNet::StringTable::AddReference(); + +#if defined(OPEN_SSL_CLIENT_SUPPORT) + ctx=0; + meth=0; +#endif + +#ifdef _WIN32 + WSAStartupSingleton::AddRef(); +#endif +} +TCPInterface::~TCPInterface() +{ + Stop(); +#ifdef _WIN32 + WSAStartupSingleton::Deref(); +#endif + + RakNet::OP_DELETE_ARRAY(remoteClients,__FILE__,__LINE__); + + StringCompressor::RemoveReference(); + RakNet::StringTable::RemoveReference(); +} +bool TCPInterface::Start(unsigned short port, unsigned short maxIncomingConnections, unsigned short maxConnections, int _threadPriority) +{ + if (isStarted) + return false; + + threadPriority=_threadPriority; + + if (threadPriority==-99999) + { +#if defined(_XBOX) || defined(X360) + +#elif defined(_WIN32) + threadPriority=0; +#elif defined(_PS3) || defined(__PS3__) || defined(SN_TARGET_PS3) + +#else + threadPriority=1000; +#endif + } + + isStarted=true; + if (maxConnections==0) + maxConnections=maxIncomingConnections; + if (maxConnections==0) + maxConnections=1; + remoteClientsLength=maxConnections; + remoteClients=RakNet::OP_NEW_ARRAY(maxConnections,__FILE__,__LINE__); + + if (maxIncomingConnections>0) + { + listenSocket = socket(AF_INET, SOCK_STREAM, 0); + if ((int)listenSocket ==-1) + return false; + + struct sockaddr_in serverAddress; + serverAddress.sin_family = AF_INET; + serverAddress.sin_addr.s_addr = htonl(INADDR_ANY); + serverAddress.sin_port = htons(port); + + if (bind(listenSocket,(struct sockaddr *) &serverAddress,sizeof(serverAddress)) < 0) + return false; + + listen(listenSocket, maxIncomingConnections); + } + + // Start the update thread + int errorCode = RakNet::RakThread::Create(UpdateTCPInterfaceLoop, this, threadPriority); + if (errorCode!=0) + return false; + + while (threadRunning==false) + RakSleep(0); + + return true; +} +void TCPInterface::Stop(void) +{ + if (isStarted==false) + return; + + unsigned i; +#if defined(OPEN_SSL_CLIENT_SUPPORT) + for (i=0; i < remoteClientsLength; i++) + remoteClients[i].DisconnectSSL(); +#endif + + isStarted=false; + + if (listenSocket!=(SOCKET) -1) + { +#ifdef _WIN32 + shutdown(listenSocket, SD_BOTH); +#else + shutdown(listenSocket, SHUT_RDWR); +#endif + closesocket(listenSocket); + listenSocket=(SOCKET) -1; + } + + // Abort waiting connect calls + blockingSocketListMutex.Lock(); + for (i=0; i < blockingSocketList.Size(); i++) + { + closesocket(blockingSocketList[i]); + } + blockingSocketListMutex.Unlock(); + + // Wait for the thread to stop + while ( threadRunning ) + RakSleep(15); + + RakSleep(100); + + // Stuff from here on to the end of the function is not threadsafe + for (i=0; i < (unsigned int) remoteClientsLength; i++) + { + closesocket(remoteClients[i].socket); +#if defined(OPEN_SSL_CLIENT_SUPPORT) + remoteClients[i].FreeSSL(); +#endif + } + remoteClientsLength=0; + RakNet::OP_DELETE_ARRAY(remoteClients,__FILE__,__LINE__); + remoteClients=0; + + incomingMessages.Clear(__FILE__, __LINE__); + newIncomingConnections.Clear(__FILE__, __LINE__); + newRemoteClients.Clear(__FILE__, __LINE__); + lostConnections.Clear(__FILE__, __LINE__); + requestedCloseConnections.Clear(__FILE__, __LINE__); + failedConnectionAttempts.Clear(__FILE__, __LINE__); + completedConnectionAttempts.Clear(__FILE__, __LINE__); + failedConnectionAttempts.Clear(__FILE__, __LINE__); + for (i=0; i < headPush.Size(); i++) + DeallocatePacket(headPush[i]); + headPush.Clear(__FILE__, __LINE__); + for (i=0; i < tailPush.Size(); i++) + DeallocatePacket(tailPush[i]); + tailPush.Clear(__FILE__, __LINE__); + +#if defined(OPEN_SSL_CLIENT_SUPPORT) + SSL_CTX_free (ctx); + startSSL.Clear(__FILE__, __LINE__); + activeSSLConnections.Clear(false, __FILE__, __LINE__); +#endif +} +SystemAddress TCPInterface::Connect(const char* host, unsigned short remotePort, bool block) +{ + if (threadRunning==false) + return UNASSIGNED_SYSTEM_ADDRESS; + + int newRemoteClientIndex=-1; + for (newRemoteClientIndex=0; newRemoteClientIndex < remoteClientsLength; newRemoteClientIndex++) + { + remoteClients[newRemoteClientIndex].isActiveMutex.Lock(); + if (remoteClients[newRemoteClientIndex].isActive==false) + { + remoteClients[newRemoteClientIndex].SetActive(true); + remoteClients[newRemoteClientIndex].isActiveMutex.Unlock(); + break; + } + remoteClients[newRemoteClientIndex].isActiveMutex.Unlock(); + } + if (newRemoteClientIndex==-1) + return UNASSIGNED_SYSTEM_ADDRESS; + + if (block) + { + SystemAddress systemAddress; + systemAddress.binaryAddress=inet_addr(host); + systemAddress.port=remotePort; + systemAddress.systemIndex=(SystemIndex) newRemoteClientIndex; + + SOCKET sockfd = SocketConnect(host, remotePort); + if (sockfd==(SOCKET)-1) + { + remoteClients[newRemoteClientIndex].isActiveMutex.Lock(); + remoteClients[newRemoteClientIndex].SetActive(false); + remoteClients[newRemoteClientIndex].isActiveMutex.Unlock(); + + failedConnectionAttemptMutex.Lock(); + failedConnectionAttempts.Push(systemAddress, __FILE__, __LINE__ ); + failedConnectionAttemptMutex.Unlock(); + + return UNASSIGNED_SYSTEM_ADDRESS; + } + + remoteClients[newRemoteClientIndex].socket=sockfd; + remoteClients[newRemoteClientIndex].systemAddress=systemAddress; + + completedConnectionAttemptMutex.Lock(); + completedConnectionAttempts.Push(remoteClients[newRemoteClientIndex].systemAddress, __FILE__, __LINE__ ); + completedConnectionAttemptMutex.Unlock(); + + return remoteClients[newRemoteClientIndex].systemAddress; + } + else + { + ThisPtrPlusSysAddr *s = RakNet::OP_NEW( __FILE__, __LINE__ ); + s->systemAddress.SetBinaryAddress(host); + s->systemAddress.port=remotePort; + s->systemAddress.systemIndex=(SystemIndex) newRemoteClientIndex; + s->tcpInterface=this; + + // Start the connection thread + int errorCode = RakNet::RakThread::Create(ConnectionAttemptLoop, s, threadPriority); + if (errorCode!=0) + { + RakNet::OP_DELETE(s, __FILE__, __LINE__); + failedConnectionAttempts.Push(s->systemAddress, __FILE__, __LINE__ ); + } + return UNASSIGNED_SYSTEM_ADDRESS; + } +} +#if defined(OPEN_SSL_CLIENT_SUPPORT) +void TCPInterface::StartSSLClient(SystemAddress systemAddress) +{ + if (ctx==0) + { + SSLeay_add_ssl_algorithms(); + meth = SSLv2_client_method(); + SSL_load_error_strings(); + ctx = SSL_CTX_new (meth); + RakAssert(ctx!=0); + } + + SystemAddress *id = startSSL.Allocate( __FILE__, __LINE__ ); + *id=systemAddress; + startSSL.Push(id); + unsigned index = activeSSLConnections.GetIndexOf(systemAddress); + if (index==(unsigned)-1) + activeSSLConnections.Insert(systemAddress,__FILE__,__LINE__); +} +bool TCPInterface::IsSSLActive(SystemAddress systemAddress) +{ + return activeSSLConnections.GetIndexOf(systemAddress)!=-1; +} +#endif +void TCPInterface::Send( const char *data, unsigned length, SystemAddress systemAddress, bool broadcast ) +{ + SendList( &data, &length, 1, systemAddress,broadcast ); +} +bool TCPInterface::SendList( const char **data, const unsigned int *lengths, const int numParameters, SystemAddress systemAddress, bool broadcast ) +{ + if (isStarted==false) + return false; + if (data==0) + return false; + if (systemAddress==UNASSIGNED_SYSTEM_ADDRESS && broadcast==false) + return false; + unsigned int totalLength=0; + int i; + for (i=0; i < numParameters; i++) + { + if (lengths[i]>0) + totalLength+=lengths[i]; + } + if (totalLength==0) + return false; + + if (broadcast) + { + // Send to all, possible exception system + for (i=0; i < remoteClientsLength; i++) + { + if (remoteClients[i].systemAddress!=systemAddress) + { + remoteClients[i].SendOrBuffer(data, lengths, numParameters); + } + } + } + else + { + // Send to this player + if (systemAddress.systemIndexdeleteData) + { + rakFree_Ex(packet->data, __FILE__, __LINE__ ); + incomingMessages.Deallocate(packet, __FILE__,__LINE__); + } + else + { + // Came from userspace AllocatePacket + rakFree_Ex(packet->data, __FILE__, __LINE__ ); + RakNet::OP_DELETE(packet, __FILE__, __LINE__); + } +} +Packet* TCPInterface::AllocatePacket(unsigned dataSize) +{ + Packet*p = RakNet::OP_NEW(__FILE__,__LINE__); + p->data=(unsigned char*) rakMalloc_Ex(dataSize,__FILE__,__LINE__); + p->length=dataSize; + p->bitSize=BYTES_TO_BITS(dataSize); + p->deleteData=false; + p->guid=UNASSIGNED_RAKNET_GUID; + p->systemAddress=UNASSIGNED_SYSTEM_ADDRESS; + p->systemAddress.systemIndex=(SystemIndex)-1; + return p; +} +void TCPInterface::PushBackPacket( Packet *packet, bool pushAtHead ) +{ + if (pushAtHead) + headPush.Push(packet, __FILE__, __LINE__ ); + else + tailPush.Push(packet, __FILE__, __LINE__ ); +} +SystemAddress TCPInterface::HasCompletedConnectionAttempt(void) +{ + SystemAddress sysAddr=UNASSIGNED_SYSTEM_ADDRESS; + completedConnectionAttemptMutex.Lock(); + if (completedConnectionAttempts.IsEmpty()==false) + sysAddr=completedConnectionAttempts.Pop(); + completedConnectionAttemptMutex.Unlock(); + return sysAddr; +} +SystemAddress TCPInterface::HasFailedConnectionAttempt(void) +{ + SystemAddress sysAddr=UNASSIGNED_SYSTEM_ADDRESS; + failedConnectionAttemptMutex.Lock(); + if (failedConnectionAttempts.IsEmpty()==false) + sysAddr=failedConnectionAttempts.Pop(); + failedConnectionAttemptMutex.Unlock(); + return sysAddr; +} +SystemAddress TCPInterface::HasNewIncomingConnection(void) +{ + SystemAddress *out, out2; + out = newIncomingConnections.PopInaccurate(); + if (out) + { + out2=*out; + newIncomingConnections.Deallocate(out, __FILE__,__LINE__); + return *out; + } + else + { + return UNASSIGNED_SYSTEM_ADDRESS; + } +} +SystemAddress TCPInterface::HasLostConnection(void) +{ + SystemAddress *out, out2; + out = lostConnections.PopInaccurate(); + if (out) + { + out2=*out; + lostConnections.Deallocate(out, __FILE__,__LINE__); + return *out; + } + else + { + return UNASSIGNED_SYSTEM_ADDRESS; + } +} +void TCPInterface::GetConnectionList( SystemAddress *remoteSystems, unsigned short *numberOfSystems ) const +{ + unsigned short systemCount=0; + unsigned short maxToWrite=*numberOfSystems; + for (int i=0; i < remoteClientsLength; i++) + { + if (remoteClients[i].isActive) + { + if (systemCount < maxToWrite) + remoteSystems[systemCount]=remoteClients[i].systemAddress; + systemCount++; + } + } + *numberOfSystems=systemCount; +} +unsigned short TCPInterface::GetConnectionCount(void) const +{ + unsigned short systemCount=0; + for (int i=0; i < remoteClientsLength; i++) + { + if (remoteClients[i].isActive) + systemCount++; + } + return systemCount; +} + +unsigned int TCPInterface::GetOutgoingDataBufferSize(SystemAddress systemAddress) const +{ + unsigned bytesWritten=0; + if (systemAddress.systemIndexh_addr, server->h_length); +#else + serverAddress.sin_addr.s_addr = inet_addr( host ); +#endif + + blockingSocketListMutex.Lock(); + blockingSocketList.Insert(sockfd, __FILE__, __LINE__); + blockingSocketListMutex.Unlock(); + + // This is blocking + int connectResult = connect( sockfd, ( struct sockaddr * ) &serverAddress, sizeof( struct sockaddr ) ); + + unsigned sockfdIndex; + blockingSocketListMutex.Lock(); + sockfdIndex=blockingSocketList.GetIndexOf(sockfd); + if (sockfdIndex!=(unsigned)-1) + blockingSocketList.RemoveAtIndexFast(sockfdIndex); + blockingSocketListMutex.Unlock(); + + if (connectResult==-1) + { + closesocket(sockfd); + return (SOCKET) -1; + } + + return sockfd; +} +RAK_THREAD_DECLARATION(ConnectionAttemptLoop) +{ + TCPInterface::ThisPtrPlusSysAddr *s = (TCPInterface::ThisPtrPlusSysAddr *) arguments; + SystemAddress systemAddress = s->systemAddress; + TCPInterface *tcpInterface = s->tcpInterface; + int newRemoteClientIndex=systemAddress.systemIndex; + RakNet::OP_DELETE(s, __FILE__, __LINE__); + + char str1[64]; + systemAddress.ToString(false, str1); + SOCKET sockfd = tcpInterface->SocketConnect(str1, systemAddress.port); + if (sockfd==(SOCKET)-1) + { + tcpInterface->remoteClients[newRemoteClientIndex].isActiveMutex.Lock(); + tcpInterface->remoteClients[newRemoteClientIndex].SetActive(false); + tcpInterface->remoteClients[newRemoteClientIndex].isActiveMutex.Unlock(); + + tcpInterface->failedConnectionAttemptMutex.Lock(); + tcpInterface->failedConnectionAttempts.Push(systemAddress, __FILE__, __LINE__ ); + tcpInterface->failedConnectionAttemptMutex.Unlock(); + return 0; + } + + tcpInterface->remoteClients[newRemoteClientIndex].socket=sockfd; + tcpInterface->remoteClients[newRemoteClientIndex].systemAddress=systemAddress; + + // Notify user that the connection attempt has completed. + if (tcpInterface->threadRunning) + { + tcpInterface->completedConnectionAttemptMutex.Lock(); + tcpInterface->completedConnectionAttempts.Push(systemAddress, __FILE__, __LINE__ ); + tcpInterface->completedConnectionAttemptMutex.Unlock(); + } + + return 0; +} + +RAK_THREAD_DECLARATION(UpdateTCPInterfaceLoop) +{ + TCPInterface * sts = ( TCPInterface * ) arguments; +// const int BUFF_SIZE=8096; + const int BUFF_SIZE=1048576; + //char data[ BUFF_SIZE ]; + char * data = (char*) rakMalloc_Ex(BUFF_SIZE,__FILE__,__LINE__); + Packet *incomingMessage; + fd_set readFD, exceptionFD, writeFD; + sts->threadRunning=true; + + sockaddr_in sockAddr; + int sockAddrSize = sizeof(sockAddr); + + int len; + SOCKET newSock; + timeval tv; + int selectResult; + tv.tv_sec=0; + tv.tv_usec=50000; + + while (sts->isStarted) + { +#if defined(OPEN_SSL_CLIENT_SUPPORT) + SystemAddress *sslSystemAddress; + sslSystemAddress = sts->startSSL.PopInaccurate(); + if (sslSystemAddress) + { + if (sslSystemAddress->systemIndex>=0 && + sslSystemAddress->systemIndexremoteClientsLength && + sts->remoteClients[sslSystemAddress->systemIndex].systemAddress==*sslSystemAddress) + { + sts->remoteClients[sslSystemAddress->systemIndex].InitSSL(sts->ctx,sts->meth); + } + else + { + for (int i=0; i < sts->remoteClientsLength; i++) + { + sts->remoteClients[i].isActiveMutex.Lock(); + if (sts->remoteClients[i].isActive && sts->remoteClients[i].systemAddress==*sslSystemAddress) + { + if (sts->remoteClients[i].ssl==0) + sts->remoteClients[i].InitSSL(sts->ctx,sts->meth); + } + sts->remoteClients[i].isActiveMutex.Unlock(); + } + } + sts->startSSL.Deallocate(sslSystemAddress,__FILE__,__LINE__); + } +#endif + + + SOCKET largestDescriptor=0; // see select()'s first parameter's documentation under linux + + + // Linux' select() implementation changes the timeout + tv.tv_sec=0; + tv.tv_usec=500000; + + while (1) + { + // Reset readFD, writeFD, and exceptionFD since select seems to clear it + FD_ZERO(&readFD); + FD_ZERO(&exceptionFD); + FD_ZERO(&writeFD); +#ifdef _MSC_VER +#pragma warning( disable : 4127 ) // warning C4127: conditional expression is constant +#endif + largestDescriptor=0; + if (sts->listenSocket!=(SOCKET) -1) + { + FD_SET(sts->listenSocket, &readFD); + FD_SET(sts->listenSocket, &exceptionFD); + largestDescriptor = sts->listenSocket; // @see largestDescriptor def + } + + unsigned i; + for (i=0; i < (unsigned int) sts->remoteClientsLength; i++) + { + sts->remoteClients[i].isActiveMutex.Lock(); + if (sts->remoteClients[i].isActive && sts->remoteClients[i].socket!=INVALID_SOCKET) + { + FD_SET(sts->remoteClients[i].socket, &readFD); + FD_SET(sts->remoteClients[i].socket, &exceptionFD); + if (sts->remoteClients[i].outgoingData.GetBytesWritten()>0) + FD_SET(sts->remoteClients[i].socket, &writeFD); + if(sts->remoteClients[i].socket > largestDescriptor) // @see largestDescriptorDef + largestDescriptor = sts->remoteClients[i].socket; + } + sts->remoteClients[i].isActiveMutex.Unlock(); + } + +#ifdef _MSC_VER +#pragma warning( disable : 4244 ) // warning C4127: conditional expression is constant +#endif +#if defined(_PS3) || defined(__PS3__) || defined(SN_TARGET_PS3) + +#else + selectResult=(int) select(largestDescriptor+1, &readFD, &writeFD, &exceptionFD, &tv); +#endif + + if (selectResult<=0) + break; + + if (sts->listenSocket!=(SOCKET) -1 && FD_ISSET(sts->listenSocket, &readFD)) + { + newSock = accept(sts->listenSocket, (sockaddr*)&sockAddr, (socklen_t*)&sockAddrSize); + + if (newSock != (SOCKET) -1) + { + int newRemoteClientIndex=-1; + for (newRemoteClientIndex=0; newRemoteClientIndex < sts->remoteClientsLength; newRemoteClientIndex++) + { + sts->remoteClients[newRemoteClientIndex].isActiveMutex.Lock(); + if (sts->remoteClients[newRemoteClientIndex].isActive==false) + { + sts->remoteClients[newRemoteClientIndex].socket=newSock; + sts->remoteClients[newRemoteClientIndex].systemAddress.binaryAddress=sockAddr.sin_addr.s_addr; + sts->remoteClients[newRemoteClientIndex].systemAddress.port=ntohs( sockAddr.sin_port); + sts->remoteClients[newRemoteClientIndex].systemAddress.systemIndex=newRemoteClientIndex; + + sts->remoteClients[newRemoteClientIndex].SetActive(true); + sts->remoteClients[newRemoteClientIndex].isActiveMutex.Unlock(); + + + SystemAddress *newConnectionSystemAddress=sts->newIncomingConnections.Allocate( __FILE__, __LINE__ ); + *newConnectionSystemAddress=sts->remoteClients[newRemoteClientIndex].systemAddress; + sts->newIncomingConnections.Push(newConnectionSystemAddress); + + break; + } + sts->remoteClients[newRemoteClientIndex].isActiveMutex.Unlock(); + } + if (newRemoteClientIndex==-1) + { + closesocket(sts->listenSocket); + } + } + else + { +#ifdef _DO_PRINTF + RAKNET_DEBUG_PRINTF("Error: connection failed\n"); +#endif + } + } + else if (sts->listenSocket!=(SOCKET) -1 && FD_ISSET(sts->listenSocket, &exceptionFD)) + { +#ifdef _DO_PRINTF + int err; + int errlen = sizeof(err); + getsockopt(sts->listenSocket, SOL_SOCKET, SO_ERROR,(char*)&err, &errlen); + RAKNET_DEBUG_PRINTF("Socket error %s on listening socket\n", err); +#endif + } + + { + i=0; + while (i < (unsigned int) sts->remoteClientsLength) + { + if (sts->remoteClients[i].isActive==false) + { + i++; + continue; + } + + if (FD_ISSET(sts->remoteClients[i].socket, &exceptionFD)) + { +#ifdef _DO_PRINTF + if (sts->listenSocket!=-1) + { + int err; + int errlen = sizeof(err); + getsockopt(sts->listenSocket, SOL_SOCKET, SO_ERROR,(char*)&err, &errlen); + in_addr in; + in.s_addr = sts->remoteClients[i].systemAddress.binaryAddress; + RAKNET_DEBUG_PRINTF("Socket error %i on %s:%i\n", err,inet_ntoa( in ), sts->remoteClients[i].systemAddress.port ); + } + +#endif + // Connection lost abruptly + SystemAddress *lostConnectionSystemAddress=sts->lostConnections.Allocate( __FILE__, __LINE__ ); + *lostConnectionSystemAddress=sts->remoteClients[i].systemAddress; + sts->lostConnections.Push(lostConnectionSystemAddress); + sts->remoteClients[i].SetActive(false); + } + else + { + if (FD_ISSET(sts->remoteClients[i].socket, &readFD)) + { + // if recv returns 0 this was a graceful close + len = sts->remoteClients[i].Recv(data,BUFF_SIZE); + if (len>0) + { + incomingMessage=sts->incomingMessages.Allocate( __FILE__, __LINE__ ); + incomingMessage->data = (unsigned char*) rakMalloc_Ex( len+1, __FILE__, __LINE__ ); + memcpy(incomingMessage->data, data, len); + incomingMessage->data[len]=0; // Null terminate this so we can print it out as regular strings. This is different from RakNet which does not do this. +// printf("RECV: %s\n",incomingMessage->data); + incomingMessage->length=len; + incomingMessage->deleteData=true; // actually means came from SPSC, rather than AllocatePacket + incomingMessage->systemAddress=sts->remoteClients[i].systemAddress; + sts->incomingMessages.Push(incomingMessage); + } + else + { + // Connection lost gracefully + SystemAddress *lostConnectionSystemAddress=sts->lostConnections.Allocate( __FILE__, __LINE__ ); + *lostConnectionSystemAddress=sts->remoteClients[i].systemAddress; + sts->lostConnections.Push(lostConnectionSystemAddress); + sts->remoteClients[i].SetActive(false); + continue; + } + } + if (FD_ISSET(sts->remoteClients[i].socket, &writeFD)) + { + RemoteClient *rc = &sts->remoteClients[i]; + unsigned int bytesInBuffer; + int bytesAvailable; + int bytesSent; + rc->outgoingDataMutex.Lock(); + bytesInBuffer=rc->outgoingData.GetBytesWritten(); + if (bytesInBuffer>0) + { + unsigned int contiguousLength; + char* contiguousBytesPointer = rc->outgoingData.PeekContiguousBytes(&contiguousLength); + if (contiguousLength < BUFF_SIZE && contiguousLength BUFF_SIZE) + bytesAvailable=BUFF_SIZE; + else + bytesAvailable=bytesInBuffer; + rc->outgoingData.ReadBytes(data,bytesAvailable,true); + bytesSent=rc->Send(data,bytesAvailable); + } + else + { + bytesSent=rc->Send(contiguousBytesPointer,contiguousLength); + } + + rc->outgoingData.IncrementReadOffset(bytesSent); + bytesInBuffer=rc->outgoingData.GetBytesWritten(); + } + rc->outgoingDataMutex.Unlock(); + } + + i++; // Nothing deleted so increment the index + } + } + } + } + + // Sleep 0 on Linux monopolizes the CPU + RakSleep(30); + } + sts->threadRunning=false; + + rakFree_Ex(data,__FILE__,__LINE__); + + return 0; +} +void RemoteClient::SetActive(bool a) +{ + isActive=a; + Reset(); + if (isActive==false && socket!=INVALID_SOCKET) + { + closesocket(socket); + socket=INVALID_SOCKET; + } +} +void RemoteClient::SendOrBuffer(const char **data, const unsigned int *lengths, const int numParameters) +{ + // True can save memory and buffer copies, but gives worse performance overall + // Do not use true for the XBOX, as it just locks up + const bool ALLOW_SEND_FROM_USER_THREAD=false; + + int parameterIndex; + if (isActive==false) + return; + parameterIndex=0; + for (; parameterIndex < numParameters; parameterIndex++) + { + outgoingDataMutex.Lock(); + if (ALLOW_SEND_FROM_USER_THREAD && outgoingData.GetBytesWritten()==0) + { + outgoingDataMutex.Unlock(); + int bytesSent = Send(data[parameterIndex],lengths[parameterIndex]); + if (bytesSent<(int) lengths[parameterIndex]) + { + // Push remainder + outgoingDataMutex.Lock(); + outgoingData.WriteBytes(data[parameterIndex]+bytesSent,lengths[parameterIndex]-bytesSent,__FILE__,__LINE__); + outgoingDataMutex.Unlock(); + } + } + else + { + outgoingData.WriteBytes(data[parameterIndex],lengths[parameterIndex],__FILE__,__LINE__); + outgoingDataMutex.Unlock(); + } + } +} +#if defined(OPEN_SSL_CLIENT_SUPPORT) +void RemoteClient::InitSSL(SSL_CTX* ctx, SSL_METHOD *meth) +{ + (void) meth; + + ssl = SSL_new (ctx); + RakAssert(ssl); + SSL_set_fd (ssl, socket); + SSL_connect (ssl); +} +void RemoteClient::DisconnectSSL(void) +{ + if (ssl) + SSL_shutdown (ssl); /* send SSL/TLS close_notify */ +} +void RemoteClient::FreeSSL(void) +{ + if (ssl) + SSL_free (ssl); +} +int RemoteClient::Send(const char *data, unsigned int length) +{ + int err; + if (ssl) + { + err = SSL_write (ssl, data, length); + RakAssert(err>0); + return 0; + } + else + return send(socket, data, length, 0); +} +int RemoteClient::Recv(char *data, const int dataSize) +{ + if (ssl) + return SSL_read (ssl, data, dataSize); + else + return recv(socket, data, dataSize, 0); +} +#else +int RemoteClient::Send(const char *data, unsigned int length) +{ + return send(socket, data, length, 0); +} +int RemoteClient::Recv(char *data, const int dataSize) +{ + return recv(socket, data, dataSize, 0); +} +#endif + +#ifdef _MSC_VER +#pragma warning( pop ) +#endif + +#endif // _RAKNET_SUPPORT_* diff --git a/RakNet/Sources/TCPInterface.h b/RakNet/Sources/TCPInterface.h new file mode 100644 index 0000000..c089d9b --- /dev/null +++ b/RakNet/Sources/TCPInterface.h @@ -0,0 +1,210 @@ +/// \file +/// \brief A simple TCP based server allowing sends and receives. Can be connected by any TCP client, including telnet. +/// +/// This file is part of RakNet Copyright 2003 Jenkins Software LLC +/// +/// Usage of RakNet is subject to the appropriate license agreement. + +#include "NativeFeatureIncludes.h" +#if _RAKNET_SUPPORT_TCPInterface==1 + +#ifndef __SIMPLE_TCP_SERVER +#define __SIMPLE_TCP_SERVER + +#include "RakMemoryOverride.h" +#include "DS_List.h" +#include "RakNetTypes.h" +#include "Export.h" +#include "RakThread.h" +#include "DS_Queue.h" +#include "SimpleMutex.h" +#include "RakNetDefines.h" +#include "SocketIncludes.h" +#include "DS_ByteQueue.h" +#include "DS_ThreadsafeAllocatingQueue.h" + +struct RemoteClient; + +#if defined(OPEN_SSL_CLIENT_SUPPORT) +#include +#include +#include +#include +#include +#endif + +/// \internal +/// \brief As the name says, a simple multithreaded TCP server. Used by TelnetTransport +class RAK_DLL_EXPORT TCPInterface +{ +public: + TCPInterface(); + virtual ~TCPInterface(); + + /// Starts the TCP server on the indicated port + /// \param[in] threadPriority Passed to thread creation routine. Use THREAD_PRIORITY_NORMAL for Windows. WARNING!!! On Linux, 0 means highest priority! You MUST set this to something valid based on the values used by your other threads + bool Start(unsigned short port, unsigned short maxIncomingConnections, unsigned short maxConnections=0, int _threadPriority=-99999); + + /// Stops the TCP server + void Stop(void); + + /// Connect to the specified host on the specified port + SystemAddress Connect(const char* host, unsigned short remotePort, bool block=true); + +#if defined(OPEN_SSL_CLIENT_SUPPORT) + /// Start SSL on an existing connection, notified with HasCompletedConnectionAttempt + void StartSSLClient(SystemAddress systemAddress); + + /// Was SSL started on this socket? + bool IsSSLActive(SystemAddress systemAddress); +#endif + + /// Sends a byte stream + void Send( const char *data, unsigned int length, SystemAddress systemAddress, bool broadcast ); + + // Sends a concatenated list of byte streams + bool SendList( const char **data, const unsigned int *lengths, const int numParameters, SystemAddress systemAddress, bool broadcast ); + + // Get how many bytes are waiting to be sent. If too many, you may want to skip sending + unsigned int GetOutgoingDataBufferSize(SystemAddress systemAddress) const; + + /// Returns data received + Packet* Receive( void ); + + /// Disconnects a player/address + void CloseConnection( SystemAddress systemAddress ); + + /// Deallocates a packet returned by Receive + void DeallocatePacket( Packet *packet ); + + /// Fills the array remoteSystems with the SystemAddress of all the systems we are connected to + /// \param[out] remoteSystems An array of SystemAddress structures to be filled with the SystemAddresss of the systems we are connected to. Pass 0 to remoteSystems to only get the number of systems we are connected to + /// \param[in, out] numberOfSystems As input, the size of remoteSystems array. As output, the number of elements put into the array + void GetConnectionList( SystemAddress *remoteSystems, unsigned short *numberOfSystems ) const; + + /// Returns just the number of connections we have + unsigned short GetConnectionCount(void) const; + + /// Has a previous call to connect succeeded? + /// \return UNASSIGNED_SYSTEM_ADDRESS = no. Anything else means yes. + SystemAddress HasCompletedConnectionAttempt(void); + + /// Has a previous call to connect failed? + /// \return UNASSIGNED_SYSTEM_ADDRESS = no. Anything else means yes. + SystemAddress HasFailedConnectionAttempt(void); + + /// Queued events of new incoming connections + SystemAddress HasNewIncomingConnection(void); + + /// Queued events of lost connections + SystemAddress HasLostConnection(void); + + /// Return an allocated but empty packet, for custom use + Packet* AllocatePacket(unsigned dataSize); + + // Push a packet back to the queue + virtual void PushBackPacket( Packet *packet, bool pushAtHead ); +protected: + + bool isStarted, threadRunning; + SOCKET listenSocket; + + DataStructures::Queue headPush, tailPush; + RemoteClient* remoteClients; + int remoteClientsLength; + + // Assuming remoteClients is only used by one thread! + // DataStructures::List remoteClients; + // Use this thread-safe queue to add to remoteClients + // DataStructures::Queue remoteClientsInsertionQueue; + // SimpleMutex remoteClientsInsertionQueueMutex; + + /* + struct OutgoingMessage + { + unsigned char* data; + SystemAddress systemAddress; + bool broadcast; + unsigned int length; + }; + */ +// DataStructures::SingleProducerConsumer outgoingMessages; +// DataStructures::SingleProducerConsumer incomingMessages; +// DataStructures::SingleProducerConsumer newIncomingConnections, lostConnections, requestedCloseConnections; +// DataStructures::SingleProducerConsumer newRemoteClients; +// DataStructures::ThreadsafeAllocatingQueue outgoingMessages; + DataStructures::ThreadsafeAllocatingQueue incomingMessages; + DataStructures::ThreadsafeAllocatingQueue newIncomingConnections, lostConnections, requestedCloseConnections; + DataStructures::ThreadsafeAllocatingQueue newRemoteClients; + SimpleMutex completedConnectionAttemptMutex, failedConnectionAttemptMutex; + DataStructures::Queue completedConnectionAttempts, failedConnectionAttempts; + + int threadPriority; + + DataStructures::List blockingSocketList; + SimpleMutex blockingSocketListMutex; + + friend RAK_THREAD_DECLARATION(UpdateTCPInterfaceLoop); + friend RAK_THREAD_DECLARATION(ConnectionAttemptLoop); + +// void DeleteRemoteClient(RemoteClient *remoteClient, fd_set *exceptionFD); +// void InsertRemoteClient(RemoteClient* remoteClient); + SOCKET SocketConnect(const char* host, unsigned short remotePort); + + struct ThisPtrPlusSysAddr + { + TCPInterface *tcpInterface; + SystemAddress systemAddress; + bool useSSL; + }; + +#if defined(OPEN_SSL_CLIENT_SUPPORT) + SSL_CTX* ctx; + SSL_METHOD *meth; + DataStructures::ThreadsafeAllocatingQueue startSSL; + DataStructures::List activeSSLConnections; +#endif +}; + +/// Stores information about a remote client. +struct RemoteClient +{ + RemoteClient() { +#if defined(OPEN_SSL_CLIENT_SUPPORT) + ssl=0; +#endif + isActive=false; + socket=INVALID_SOCKET; + } + SOCKET socket; + SystemAddress systemAddress; + DataStructures::ByteQueue outgoingData; + bool isActive; + SimpleMutex outgoingDataMutex; + SimpleMutex isActiveMutex; + +#if defined(OPEN_SSL_CLIENT_SUPPORT) + SSL* ssl; + void InitSSL(SSL_CTX* ctx, SSL_METHOD *meth); + void DisconnectSSL(void); + void FreeSSL(void); + int Send(const char *data, unsigned int length); + int Recv(char *data, const int dataSize); +#else + int Send(const char *data, unsigned int length); + int Recv(char *data, const int dataSize); +#endif + void Reset(void) + { + outgoingDataMutex.Lock(); + outgoingData.Clear(__FILE__,__LINE__); + outgoingDataMutex.Unlock(); + } + void SetActive(bool a); + void SendOrBuffer(const char **data, const unsigned int *lengths, const int numParameters); +}; + +#endif + +#endif // _RAKNET_SUPPORT_* + diff --git a/RakNet/Sources/TableSerializer.cpp b/RakNet/Sources/TableSerializer.cpp new file mode 100644 index 0000000..24cce00 --- /dev/null +++ b/RakNet/Sources/TableSerializer.cpp @@ -0,0 +1,318 @@ +#include "TableSerializer.h" +#include "DS_Table.h" +#include "BitStream.h" +#include "StringCompressor.h" +#include "RakAssert.h" + +void TableSerializer::SerializeTable(DataStructures::Table *in, RakNet::BitStream *out) +{ + DataStructures::Page *cur = in->GetRows().GetListHead(); + const DataStructures::List &columns=in->GetColumns(); + SerializeColumns(in, out); + out->Write((unsigned)in->GetRows().Size()); + unsigned rowIndex; + while (cur) + { + for (rowIndex=0; rowIndex < (unsigned)cur->size; rowIndex++) + { + SerializeRow(cur->data[rowIndex], cur->keys[rowIndex], columns, out); + } + cur=cur->next; + } +} +void TableSerializer::SerializeColumns(DataStructures::Table *in, RakNet::BitStream *out) +{ + const DataStructures::List &columns=in->GetColumns(); + out->Write((unsigned)columns.Size()); + unsigned i; + for (i=0; iEncodeString(columns[i].columnName, _TABLE_MAX_COLUMN_NAME_LENGTH, out); + out->Write((unsigned char)columns[i].columnType); + } +} +void TableSerializer::SerializeColumns(DataStructures::Table *in, RakNet::BitStream *out, DataStructures::List &skipColumnIndices) +{ + const DataStructures::List &columns=in->GetColumns(); + out->Write((unsigned)columns.Size()-skipColumnIndices.Size()); + unsigned i; + for (i=0; iEncodeString(columns[i].columnName, _TABLE_MAX_COLUMN_NAME_LENGTH, out); + out->Write((unsigned char)columns[i].columnType); + } + } +} +bool TableSerializer::DeserializeTable(unsigned char *serializedTable, unsigned int dataLength, DataStructures::Table *out) +{ + RakNet::BitStream in((unsigned char*) serializedTable, dataLength, false); + return DeserializeTable(&in, out); +} +bool TableSerializer::DeserializeTable(RakNet::BitStream *in, DataStructures::Table *out) +{ + unsigned rowSize; + DeserializeColumns(in,out); + if (in->Read(rowSize)==false || rowSize>100000) + { + RakAssert(0); + return false; // Hacker crash prevention + } + + unsigned rowIndex; + for (rowIndex=0; rowIndex < rowSize; rowIndex++) + { + if (DeserializeRow(in, out)==false) + return false; + } + return true; +} +bool TableSerializer::DeserializeColumns(RakNet::BitStream *in, DataStructures::Table *out) +{ + unsigned columnSize; + unsigned char columnType; + char columnName[_TABLE_MAX_COLUMN_NAME_LENGTH]; + if (in->Read(columnSize)==false || columnSize > 10000) + return false; // Hacker crash prevention + + out->Clear(); + unsigned i; + for (i=0; iDecodeString(columnName, 32, in); + in->Read(columnType); + out->AddColumn(columnName, (DataStructures::Table::ColumnType)columnType); + } + return true; +} +void TableSerializer::SerializeRow(DataStructures::Table::Row *in, unsigned keyIn, const DataStructures::List &columns, RakNet::BitStream *out) +{ + unsigned cellIndex; + out->Write(keyIn); + unsigned int columnsSize = columns.Size(); + out->Write(columnsSize); + for (cellIndex=0; cellIndexWrite(cellIndex); + SerializeCell(out, in->cells[cellIndex], columns[cellIndex].columnType); + } +} +void TableSerializer::SerializeRow(DataStructures::Table::Row *in, unsigned keyIn, const DataStructures::List &columns, RakNet::BitStream *out, DataStructures::List &skipColumnIndices) +{ + unsigned cellIndex; + out->Write(keyIn); + unsigned int numEntries=0; + for (cellIndex=0; cellIndexWrite(numEntries); + + for (cellIndex=0; cellIndexWrite(cellIndex); + SerializeCell(out, in->cells[cellIndex], columns[cellIndex].columnType); + } + } +} +bool TableSerializer::DeserializeRow(RakNet::BitStream *in, DataStructures::Table *out) +{ + const DataStructures::List &columns=out->GetColumns(); + unsigned numEntries; + DataStructures::Table::Row *row; + unsigned key; + if (in->Read(key)==false) + return false; + row=out->AddRow(key); + unsigned int cnt; + in->Read(numEntries); + for (cnt=0; cntRead(cellIndex); + if (DeserializeCell(in, row->cells[cellIndex], columns[cellIndex].columnType)==false) + { + out->RemoveRow(key); + return false; + } + } + return true; +} +void TableSerializer::SerializeCell(RakNet::BitStream *out, DataStructures::Table::Cell *cell, DataStructures::Table::ColumnType columnType) +{ + out->Write(cell->isEmpty); + if (cell->isEmpty==false) + { + if (columnType==DataStructures::Table::NUMERIC) + { + out->Write(cell->i); + } + else if (columnType==DataStructures::Table::STRING) + { + stringCompressor->EncodeString(cell->c, 65535, out); + } + else if (columnType==DataStructures::Table::POINTER) + { + out->Write(cell->ptr); + } + else + { + // Binary + RakAssert(columnType==DataStructures::Table::BINARY); + RakAssert(cell->i>0); + unsigned binaryLength; + binaryLength=(unsigned)cell->i; + out->Write(binaryLength); + out->WriteAlignedBytes((const unsigned char*) cell->c, (const unsigned int) cell->i); + } + } +} +bool TableSerializer::DeserializeCell(RakNet::BitStream *in, DataStructures::Table::Cell *cell, DataStructures::Table::ColumnType columnType) +{ + bool isEmpty; + double value; + void *ptr; + char tempString[65535]; + cell->Clear(); + + if (in->Read(isEmpty)==false) + return false; + if (isEmpty==false) + { + if (columnType==DataStructures::Table::NUMERIC) + { + if (in->Read(value)==false) + return false; + cell->Set(value); + } + else if (columnType==DataStructures::Table::STRING) + { + if (stringCompressor->DecodeString(tempString, 65535, in)==false) + return false; + cell->Set(tempString); + } + else if (columnType==DataStructures::Table::POINTER) + { + if (in->Read(ptr)==false) + return false; + cell->SetPtr(ptr); + } + else + { + unsigned binaryLength; + // Binary + RakAssert(columnType==DataStructures::Table::BINARY); + if (in->Read(binaryLength)==false || binaryLength > 10000000) + return false; // Sanity check to max binary cell of 10 megabytes + in->AlignReadToByteBoundary(); + if (BITS_TO_BYTES(in->GetNumberOfUnreadBits())<(BitSize_t)binaryLength) + return false; + cell->Set((char*) in->GetData()+BITS_TO_BYTES(in->GetReadOffset()), (int) binaryLength); + in->IgnoreBits(BYTES_TO_BITS((int) binaryLength)); + } + } + return true; +} +void TableSerializer::SerializeFilterQuery(RakNet::BitStream *in, DataStructures::Table::FilterQuery *query) +{ + stringCompressor->EncodeString(query->columnName,_TABLE_MAX_COLUMN_NAME_LENGTH,in,0); + in->WriteCompressed(query->columnIndex); + in->Write((unsigned char) query->operation); + in->Write(query->cellValue->isEmpty); + if (query->cellValue->isEmpty==false) + { + in->Write(query->cellValue->i); + in->WriteAlignedBytesSafe((const char*)query->cellValue->c,(const unsigned int)query->cellValue->i,10000000); // Sanity check to max binary cell of 10 megabytes + in->Write(query->cellValue->ptr); + + } +} +bool TableSerializer::DeserializeFilterQuery(RakNet::BitStream *out, DataStructures::Table::FilterQuery *query) +{ + bool b; + RakAssert(query->cellValue); + stringCompressor->DecodeString(query->columnName,_TABLE_MAX_COLUMN_NAME_LENGTH,out,0); + out->ReadCompressed(query->columnIndex); + unsigned char op; + out->Read(op); + query->operation=(DataStructures::Table::FilterQueryType) op; + query->cellValue->Clear(); + b=out->Read(query->cellValue->isEmpty); + if (query->cellValue->isEmpty==false) + { + // HACK - cellValue->i is used for integer, character, and binary data. However, for character and binary c will be 0. So use that to determine if the data was integer or not. + out->Read(query->cellValue->i); + unsigned int inputLength; + out->ReadAlignedBytesSafeAlloc(&query->cellValue->c,inputLength,10000000); // Sanity check to max binary cell of 10 megabytes + if (query->cellValue->c) + query->cellValue->i=inputLength; + b=out->Read(query->cellValue->ptr); + } + return b; +} +void TableSerializer::SerializeFilterQueryList(RakNet::BitStream *in, DataStructures::Table::FilterQuery *query, unsigned int numQueries, unsigned int maxQueries) +{ + (void) maxQueries; + in->Write((bool)(query && numQueries>0)); + if (query==0 || numQueries<=0) + return; + + RakAssert(numQueries<=maxQueries); + in->WriteCompressed(numQueries); + unsigned i; + for (i=0; i < numQueries; i++) + { + SerializeFilterQuery(in, query); + } +} +bool TableSerializer::DeserializeFilterQueryList(RakNet::BitStream *out, DataStructures::Table::FilterQuery **query, unsigned int *numQueries, unsigned int maxQueries, int allocateExtraQueries) +{ + bool b, anyQueries=false; + out->Read(anyQueries); + if (anyQueries==false) + { + if (allocateExtraQueries<=0) + *query=0; + else + *query=new DataStructures::Table::FilterQuery[allocateExtraQueries]; + + *numQueries=0; + return true; + } + b=out->ReadCompressed(*numQueries); + if (*numQueries>maxQueries) + { + RakAssert(0); + *numQueries=maxQueries; + } + if (*numQueries==0) + return b; + + *query=new DataStructures::Table::FilterQuery[*numQueries+allocateExtraQueries]; + DataStructures::Table::FilterQuery *queryPtr = *query; + + unsigned i; + for (i=0; i < *numQueries; i++) + { + queryPtr[i].cellValue=new DataStructures::Table::Cell; + b=DeserializeFilterQuery(out, queryPtr+i); + } + + return b; +} +void TableSerializer::DeallocateQueryList(DataStructures::Table::FilterQuery *query, unsigned int numQueries) +{ + if (query==0 || numQueries==0) + return; + + unsigned i; + for (i=0; i < numQueries; i++) + RakNet::OP_DELETE(query[i].cellValue, __FILE__, __LINE__); + RakNet::OP_DELETE_ARRAY(query, __FILE__, __LINE__); +} diff --git a/RakNet/Sources/TableSerializer.h b/RakNet/Sources/TableSerializer.h new file mode 100644 index 0000000..91320d9 --- /dev/null +++ b/RakNet/Sources/TableSerializer.h @@ -0,0 +1,203 @@ +#ifndef __TABLE_SERIALIZER_H +#define __TABLE_SERIALIZER_H + +#include "RakMemoryOverride.h" +#include "DS_Table.h" +#include "Export.h" + +namespace RakNet +{ + class BitStream; +} + +class RAK_DLL_EXPORT TableSerializer +{ +public: + static void SerializeTable(DataStructures::Table *in, RakNet::BitStream *out); + static bool DeserializeTable(unsigned char *serializedTable, unsigned int dataLength, DataStructures::Table *out); + static bool DeserializeTable(RakNet::BitStream *in, DataStructures::Table *out); + static void SerializeColumns(DataStructures::Table *in, RakNet::BitStream *out); + static void SerializeColumns(DataStructures::Table *in, RakNet::BitStream *out, DataStructures::List &skipColumnIndices); + static bool DeserializeColumns(RakNet::BitStream *in, DataStructures::Table *out); + static void SerializeRow(DataStructures::Table::Row *in, unsigned keyIn, const DataStructures::List &columns, RakNet::BitStream *out); + static void SerializeRow(DataStructures::Table::Row *in, unsigned keyIn, const DataStructures::List &columns, RakNet::BitStream *out, DataStructures::List &skipColumnIndices); + static bool DeserializeRow(RakNet::BitStream *in, DataStructures::Table *out); + static void SerializeCell(RakNet::BitStream *out, DataStructures::Table::Cell *cell, DataStructures::Table::ColumnType columnType); + static bool DeserializeCell(RakNet::BitStream *in, DataStructures::Table::Cell *cell, DataStructures::Table::ColumnType columnType); + static void SerializeFilterQuery(RakNet::BitStream *in, DataStructures::Table::FilterQuery *query); + // Note that this allocates query->cell->c! + static bool DeserializeFilterQuery(RakNet::BitStream *out, DataStructures::Table::FilterQuery *query); + static void SerializeFilterQueryList(RakNet::BitStream *in, DataStructures::Table::FilterQuery *query, unsigned int numQueries, unsigned int maxQueries); + // Note that this allocates queries, cells, and query->cell->c!. Use DeallocateQueryList to free. + static bool DeserializeFilterQueryList(RakNet::BitStream *out, DataStructures::Table::FilterQuery **query, unsigned int *numQueries, unsigned int maxQueries, int allocateExtraQueries=0); + static void DeallocateQueryList(DataStructures::Table::FilterQuery *query, unsigned int numQueries); +}; + +#endif + +// Test code for the table +/* +#include "LightweightDatabaseServer.h" +#include "LightweightDatabaseClient.h" +#include "TableSerializer.h" +#include "BitStream.h" +#include "StringCompressor.h" +#include "DS_Table.h" +void main(void) +{ + DataStructures::Table table; + DataStructures::Table::Row *row; + unsigned int dummydata=12345; + + // Add columns Name (string), IP (binary), score (int), and players (int). + table.AddColumn("Name", DataStructures::Table::STRING); + table.AddColumn("IP", DataStructures::Table::BINARY); + table.AddColumn("Score", DataStructures::Table::NUMERIC); + table.AddColumn("Players", DataStructures::Table::NUMERIC); + table.AddColumn("Empty Test Column", DataStructures::Table::STRING); + RakAssert(table.GetColumnCount()==5); + row=table.AddRow(0); + RakAssert(row); + row->UpdateCell(0,"Kevin Jenkins"); + row->UpdateCell(1,sizeof(dummydata), (char*)&dummydata); + row->UpdateCell(2,5); + row->UpdateCell(3,10); + //row->UpdateCell(4,"should be unique"); + + row=table.AddRow(1); + row->UpdateCell(0,"Kevin Jenkins"); + row->UpdateCell(1,sizeof(dummydata), (char*)&dummydata); + row->UpdateCell(2,5); + row->UpdateCell(3,15); + + row=table.AddRow(2); + row->UpdateCell(0,"Kevin Jenkins"); + row->UpdateCell(1,sizeof(dummydata), (char*)&dummydata); + row->UpdateCell(2,5); + row->UpdateCell(3,20); + + row=table.AddRow(3); + RakAssert(row); + row->UpdateCell(0,"Kevin Jenkins"); + row->UpdateCell(1,sizeof(dummydata), (char*)&dummydata); + row->UpdateCell(2,15); + row->UpdateCell(3,5); + row->UpdateCell(4,"col index 4"); + + row=table.AddRow(4); + RakAssert(row); + row->UpdateCell(0,"Kevin Jenkins"); + row->UpdateCell(1,sizeof(dummydata), (char*)&dummydata); + //row->UpdateCell(2,25); + row->UpdateCell(3,30); + //row->UpdateCell(4,"should be unique"); + + row=table.AddRow(5); + RakAssert(row); + row->UpdateCell(0,"Kevin Jenkins"); + row->UpdateCell(1,sizeof(dummydata), (char*)&dummydata); + //row->UpdateCell(2,25); + row->UpdateCell(3,5); + //row->UpdateCell(4,"should be unique"); + + row=table.AddRow(6); + RakAssert(row); + row->UpdateCell(0,"Kevin Jenkins"); + row->UpdateCell(1,sizeof(dummydata), (char*)&dummydata); + row->UpdateCell(2,35); + //row->UpdateCell(3,40); + //row->UpdateCell(4,"should be unique"); + + row=table.AddRow(7); + RakAssert(row); + row->UpdateCell(0,"Bob Jenkins"); + + row=table.AddRow(8); + RakAssert(row); + row->UpdateCell(0,"Zack Jenkins"); + + // Test multi-column sorting + DataStructures::Table::Row *rows[30]; + DataStructures::Table::SortQuery queries[4]; + queries[0].columnIndex=0; + queries[0].operation=DataStructures::Table::QS_INCREASING_ORDER; + queries[1].columnIndex=1; + queries[1].operation=DataStructures::Table::QS_INCREASING_ORDER; + queries[2].columnIndex=2; + queries[2].operation=DataStructures::Table::QS_INCREASING_ORDER; + queries[3].columnIndex=3; + queries[3].operation=DataStructures::Table::QS_DECREASING_ORDER; + table.SortTable(queries, 4, rows); + unsigned i; + char out[256]; + RAKNET_DEBUG_PRINTF("Sort: Ascending except for column index 3\n"); + for (i=0; i < table.GetRowCount(); i++) + { + table.PrintRow(out,256,',',true, rows[i]); + RAKNET_DEBUG_PRINTF("%s\n", out); + } + + // Test query: + // Don't return column 3, and swap columns 0 and 2 + unsigned columnsToReturn[4]; + columnsToReturn[0]=2; + columnsToReturn[1]=1; + columnsToReturn[2]=0; + columnsToReturn[3]=4; + DataStructures::Table resultsTable; + table.QueryTable(columnsToReturn,4,0,0,&resultsTable); + RAKNET_DEBUG_PRINTF("Query: Don't return column 3, and swap columns 0 and 2:\n"); + for (i=0; i < resultsTable.GetRowCount(); i++) + { + resultsTable.PrintRow(out,256,',',true, resultsTable.GetRowByIndex(i)); + RAKNET_DEBUG_PRINTF("%s\n", out); + } + + // Test filter: + // Only return rows with column index 4 empty + DataStructures::Table::FilterQuery inclusionFilters[3]; + inclusionFilters[0].columnIndex=4; + inclusionFilters[0].operation=DataStructures::Table::QF_IS_EMPTY; + // inclusionFilters[0].cellValue; // Unused for IS_EMPTY + table.QueryTable(0,0,inclusionFilters,1,&resultsTable); + RAKNET_DEBUG_PRINTF("Filter: Only return rows with column index 4 empty:\n"); + for (i=0; i < resultsTable.GetRowCount(); i++) + { + resultsTable.PrintRow(out,256,',',true, resultsTable.GetRowByIndex(i)); + RAKNET_DEBUG_PRINTF("%s\n", out); + } + + // Column 5 empty and column 0 == Kevin Jenkins + inclusionFilters[0].columnIndex=4; + inclusionFilters[0].operation=DataStructures::Table::QF_IS_EMPTY; + inclusionFilters[1].columnIndex=0; + inclusionFilters[1].operation=DataStructures::Table::QF_EQUAL; + inclusionFilters[1].cellValue.Set("Kevin Jenkins"); + table.QueryTable(0,0,inclusionFilters,2,&resultsTable); + RAKNET_DEBUG_PRINTF("Filter: Column 5 empty and column 0 == Kevin Jenkins:\n"); + for (i=0; i < resultsTable.GetRowCount(); i++) + { + resultsTable.PrintRow(out,256,',',true, resultsTable.GetRowByIndex(i)); + RAKNET_DEBUG_PRINTF("%s\n", out); + } + + RakNet::BitStream bs; + RAKNET_DEBUG_PRINTF("PreSerialize:\n"); + for (i=0; i < table.GetRowCount(); i++) + { + table.PrintRow(out,256,',',true, table.GetRowByIndex(i)); + RAKNET_DEBUG_PRINTF("%s\n", out); + } + StringCompressor::AddReference(); + TableSerializer::Serialize(&table, &bs); + TableSerializer::Deserialize(&bs, &table); + StringCompressor::RemoveReference(); + RAKNET_DEBUG_PRINTF("PostDeserialize:\n"); + for (i=0; i < table.GetRowCount(); i++) + { + table.PrintRow(out,256,',',true, table.GetRowByIndex(i)); + RAKNET_DEBUG_PRINTF("%s\n", out); + } + int a=5; +} +*/ diff --git a/RakNet/Sources/TeamBalancer.cpp b/RakNet/Sources/TeamBalancer.cpp new file mode 100644 index 0000000..791ab21 --- /dev/null +++ b/RakNet/Sources/TeamBalancer.cpp @@ -0,0 +1,764 @@ +#include "NativeFeatureIncludes.h" +#if _RAKNET_SUPPORT_TeamBalancer==1 + +#include "TeamBalancer.h" +#include "BitStream.h" +#include "MessageIdentifiers.h" +#include "RakPeerInterface.h" +#include "Rand.h" + +using namespace RakNet; + +enum TeamBalancerOperations +{ + ID_STATUS_UPDATE_TO_NEW_HOST, + ID_CANCEL_TEAM_REQUEST, + ID_REQUEST_ANY_TEAM, + ID_REQUEST_SPECIFIC_TEAM +}; + +TeamBalancer::TeamBalancer() +{ + hostGuid=UNASSIGNED_RAKNET_GUID; + currentTeam=UNASSIGNED_TEAM_ID; + requestedTeam=UNASSIGNED_TEAM_ID; + defaultAssigmentAlgorithm=SMALLEST_TEAM; + forceTeamsToBeEven=false; + lockTeams=false; + expectingToReceiveTeamNumber=false; + allowHostMigration=true; +} +TeamBalancer::~TeamBalancer() +{ + +} +void TeamBalancer::SetHostGuid(RakNetGUID _hostGuid) +{ + // If host guid did not change, return. + if (hostGuid==_hostGuid) + return; + + hostGuid=_hostGuid; + + // If we never requested a team anyway, return + if (expectingToReceiveTeamNumber==false && currentTeam==UNASSIGNED_TEAM_ID) + return; + + // Send current team, and currently requested team (if any) to new(?) host + BitStream bsOut; + bsOut.Write((MessageID)ID_TEAM_BALANCER_INTERNAL); + bsOut.Write((MessageID)ID_STATUS_UPDATE_TO_NEW_HOST); + bsOut.Write(currentTeam); + bsOut.Write(requestedTeam); + rakPeerInterface->Send(&bsOut,HIGH_PRIORITY,RELIABLE_ORDERED,0,_hostGuid,false); +} +void TeamBalancer::SetTeamSizeLimits(const DataStructures::List &_teamLimits) +{ + // Just update the internal list. Currently active teams are not affected + teamLimits=_teamLimits; + + teamMemberCounts.Clear(true,__FILE__,__LINE__); + if (_teamLimits.Size()>0) + teamMemberCounts.Replace(0,0,_teamLimits.Size()-1,__FILE__,__LINE__); +} +void TeamBalancer::SetTeamSizeLimits(unsigned short *values, int valuesLength) +{ + RakAssert(valuesLength>0); + teamMemberCounts.Clear(true,__FILE__,__LINE__); + for (int i=0; i < valuesLength; i++) + teamMemberCounts.Push(values[i],__FILE__,__LINE__); +} +void TeamBalancer::SetDefaultAssignmentAlgorithm(DefaultAssigmentAlgorithm daa) +{ + // Just update the default. Currently active teams are not affected. + defaultAssigmentAlgorithm=daa; +} +void TeamBalancer::SetForceEvenTeams(bool force) +{ + // Set flag to indicate that teams should be even. + forceTeamsToBeEven=force; + + // If teams are locked, just return. + if (lockTeams==true) + return; + + if (forceTeamsToBeEven==true) + { + // Run the even team algorithm + EvenTeams(); + } +} +void TeamBalancer::SetLockTeams(bool lock) +{ + if (lock==lockTeams) + return; + + // Set flag to indicate that teams can no longer be changed. + lockTeams=lock; + + // If lock is false, and teams were set to be forced as even, then run through the even team algorithm + if (lockTeams==false) + { + // Process even swaps + TeamId i,j; + for (i=0; i < teamMembers.Size(); i++) + { + if (teamMembers[i].requestedTeam!=UNASSIGNED_TEAM_ID) + { + for (j=i+1; j < teamMembers.Size(); j++) + { + if (teamMembers[j].requestedTeam==teamMembers[i].currentTeam && + teamMembers[i].requestedTeam==teamMembers[j].currentTeam) + { + SwapTeamMembersByRequest(i,j); + NotifyTeamAssigment(i); + NotifyTeamAssigment(j); + } + } + } + } + + if (forceTeamsToBeEven==true) + { + EvenTeams(); + } + else + { + // Process requested team changes + // Process movement while not full + for (i=0; i < teamMembers.Size(); i++) + { + TeamId requestedTeam = teamMembers[i].requestedTeam; + if (requestedTeam!=UNASSIGNED_TEAM_ID) + { + if (teamMemberCounts[requestedTeam]Send(&bsOut,HIGH_PRIORITY,RELIABLE_ORDERED,0,hostGuid,false); + + if (desiredTeam!=UNASSIGNED_TEAM_ID) + expectingToReceiveTeamNumber=true; + + return true; +} +void TeamBalancer::CancelRequestSpecificTeam(void) +{ + // Clear out that we have requested a team. + requestedTeam=UNASSIGNED_TEAM_ID; + + // Send packet to the host to remove our request flag. + BitStream bsOut; + bsOut.Write((MessageID)ID_TEAM_BALANCER_INTERNAL); + bsOut.Write((MessageID)ID_CANCEL_TEAM_REQUEST); + rakPeerInterface->Send(&bsOut,HIGH_PRIORITY,RELIABLE_ORDERED,0,hostGuid,false); + + expectingToReceiveTeamNumber=false; +} +void TeamBalancer::RequestAnyTeam(void) +{ + // If we currently have a team, just return (does nothing) + if (GetMyTeam()!=UNASSIGNED_TEAM_ID) + return; + + // Else send to the current host that we need a team. + BitStream bsOut; + bsOut.Write((MessageID)ID_TEAM_BALANCER_INTERNAL); + bsOut.Write((MessageID)ID_REQUEST_ANY_TEAM); + rakPeerInterface->Send(&bsOut,HIGH_PRIORITY,RELIABLE_ORDERED,0,hostGuid,false); + + expectingToReceiveTeamNumber=true; +} +TeamId TeamBalancer::GetMyTeam(void) const +{ + // Return team returned by last ID_TEAM_BALANCER_TEAM_ASSIGNED packet + + return currentTeam; +} +PluginReceiveResult TeamBalancer::OnReceive(Packet *packet) +{ + switch (packet->data[0]) + { + case ID_TEAM_BALANCER_INTERNAL: + { + if (packet->length>=2) + { + switch (packet->data[1]) + { + case ID_STATUS_UPDATE_TO_NEW_HOST: + OnStatusUpdateToNewHost(packet); + break; + case ID_CANCEL_TEAM_REQUEST: + OnCancelTeamRequest(packet); + break; + case ID_REQUEST_ANY_TEAM: + OnRequestAnyTeam(packet); + break; + case ID_REQUEST_SPECIFIC_TEAM: + OnRequestSpecificTeam(packet); + break; + } + } + } + return RR_STOP_PROCESSING_AND_DEALLOCATE; + + case ID_TEAM_BALANCER_TEAM_ASSIGNED: + { + return OnTeamAssigned(packet); + } + + case ID_TEAM_BALANCER_REQUESTED_TEAM_CHANGE_PENDING: + { + return OnRequestedTeamChangePending(packet); + } + + case ID_TEAM_BALANCER_TEAMS_LOCKED: + { + return OnTeamsLocked(packet); + } + } + + // Got RequestSpecificTeam + // If teams are locked + // - If this user already has a team, return ID_TEAM_BALANCER_TEAMS_LOCKED + // - This user does not already have a team. Assign a team as if the user called RequestAnyTeam(), with a preference for the requested team. Return ID_TEAM_BALANCER_TEAM_ASSIGNED once the team has been assigned. + // If teams are not locked + // - If even team balancing is on, only assign this user if this would not cause teams to be unbalanced. If teams WOULD be unbalanced, then flag this user as wanting to join this team. Return ID_TEAM_BALANCER_REQUESTED_TEAM_CHANGE_PENDING + // - If the destination team is full, flag this user as wanting to join this team. Return ID_TEAM_BALANCER_REQUESTED_TEAM_CHANGE_PENDING + // - Else, join this team. Return ID_TEAM_BALANCER_TEAM_ASSIGNED + + // Got RequestAnyTeam + // Put user on a team following the algorithm. No team is set as preferred. + + return RR_CONTINUE_PROCESSING; +} +void TeamBalancer::OnClosedConnection(SystemAddress systemAddress, RakNetGUID rakNetGUID, PI2_LostConnectionReason lostConnectionReason ) +{ + (void) systemAddress; + (void) lostConnectionReason; + + RemoveByGuid(rakNetGUID); +} +void TeamBalancer::RemoveByGuid(RakNetGUID rakNetGUID) +{ + // If we are the host, and the closed connection has a team, and teams are not locked: + if (WeAreHost()) + { + unsigned int droppedMemberIndex = GetMemberIndex(rakNetGUID); + if (droppedMemberIndex!=(unsigned int)-1) + { + TeamId droppedTeam = teamMembers[droppedMemberIndex].currentTeam; + RemoveTeamMember(droppedMemberIndex); + if (lockTeams==false) + { + if (forceTeamsToBeEven) + { + // - teams were forced to be even, then run the even team algorithm + EvenTeams(); + } + else + { + // - teams were NOT forced to be even, and the team the dropped player on was full, then move users wanting to join that team (if any) + if (teamMemberCounts[ droppedTeam ]==teamLimits[ droppedTeam ]-1) + { + MoveMemberThatWantsToJoinTeam(droppedTeam); + } + } + } + } + } +} +void TeamBalancer::OnStatusUpdateToNewHost(Packet *packet) +{ + if (WeAreHost()==false) + return; + + if (allowHostMigration==false) + return; + + BitStream bsIn(packet->data,packet->length,false); + bsIn.IgnoreBytes(2); + TeamMember tm; + bsIn.Read(tm.currentTeam); + bsIn.Read(tm.requestedTeam); + + if (tm.currentTeam!=UNASSIGNED_TEAM_ID && tm.currentTeam>teamLimits.Size()) + { + RakAssert("Current team out of range in TeamBalancer::OnStatusUpdateToNewHost" && 0); + return; + } + + if (tm.requestedTeam!=UNASSIGNED_TEAM_ID && tm.requestedTeam>teamLimits.Size()) + { + RakAssert("Requested team out of range in TeamBalancer::OnStatusUpdateToNewHost" && 0); + return; + } + + unsigned int memberIndex = GetMemberIndex(packet->guid); + if (memberIndex==(unsigned int) -1) + { + tm.memberGuid=packet->guid; + + // Add this system (by GUID) to the list of members if he is not already there + // Also update his requested team flag. + // Do not process balancing on requested teams, since we don't necessarily have all data from all systems yet and hopefully the state during the host migration was stable. + if (tm.currentTeam==UNASSIGNED_TEAM_ID) + { + // Assign a default team, then add team member + if (tm.requestedTeam==UNASSIGNED_TEAM_ID) + { + // Assign a default team + tm.currentTeam=GetNextDefaultTeam(); + } + else + { + // Assign to requested team if possible. Otherwise, assign to a default team + if (TeamWouldBeOverpopulatedOnAddition(tm.requestedTeam, teamMembers.Size())==false) + { + tm.currentTeam=tm.requestedTeam; + } + else + { + tm.currentTeam=GetNextDefaultTeam(); + } + } + } + + if (tm.currentTeam==UNASSIGNED_TEAM_ID) + { + RakAssert("Too many members asking for teams!" && 0); + return; + } + NotifyTeamAssigment(AddTeamMember(tm)); + } +} +void TeamBalancer::OnCancelTeamRequest(Packet *packet) +{ + if (WeAreHost()==false) + return; + + unsigned int memberIndex = GetMemberIndex(packet->guid); + if (memberIndex!=(unsigned int)-1) + teamMembers[memberIndex].requestedTeam=UNASSIGNED_TEAM_ID; +} +void TeamBalancer::OnRequestAnyTeam(Packet *packet) +{ + if (WeAreHost()==false) + return; + + unsigned int memberIndex = GetMemberIndex(packet->guid); + if (memberIndex==(unsigned int)-1) + { + TeamMember tm; + tm.currentTeam=GetNextDefaultTeam(); + tm.requestedTeam=UNASSIGNED_TEAM_ID; + tm.memberGuid=packet->guid; + if (tm.currentTeam==UNASSIGNED_TEAM_ID) + { + RakAssert("Too many members asking for teams!" && 0); + return; + } + NotifyTeamAssigment(AddTeamMember(tm)); + } +} +void TeamBalancer::OnRequestSpecificTeam(Packet *packet) +{ + if (WeAreHost()==false) + return; + + BitStream bsIn(packet->data,packet->length,false); + bsIn.IgnoreBytes(2); + TeamMember tm; + bsIn.Read(tm.requestedTeam); + + if (tm.requestedTeam==UNASSIGNED_TEAM_ID) + { + RemoveByGuid(packet->guid); + NotifyNoTeam(packet->guid); + return; + } + + if (tm.requestedTeam>teamLimits.Size()) + { + RakAssert("Requested team out of range in TeamBalancer::OnRequestSpecificTeam" && 0); + return; + } + unsigned int memberIndex = GetMemberIndex(packet->guid); + if (memberIndex==(unsigned int) -1) + { + tm.memberGuid=packet->guid; + + // Assign to requested team if possible. Otherwise, assign to a default team + if (TeamWouldBeOverpopulatedOnAddition(tm.requestedTeam, teamMembers.Size())==false) + tm.currentTeam=tm.requestedTeam; + else + tm.currentTeam=GetNextDefaultTeam(); + if (tm.currentTeam==UNASSIGNED_TEAM_ID) + { + RakAssert("Too many members asking for teams!" && 0); + return; + } + NotifyTeamAssigment(AddTeamMember(tm)); + } + else + { + teamMembers[memberIndex].requestedTeam=tm.requestedTeam; + TeamId oldTeamThisUserWasOn = teamMembers[memberIndex].currentTeam; + + if (lockTeams) + { + NotifyTeamsLocked(packet->guid, tm.requestedTeam); + return; + } + + // Assign to requested team if possible. Otherwise, assign to a default team + if (TeamsWouldBeEvenOnSwitch(tm.requestedTeam,oldTeamThisUserWasOn)==true) + { + SwitchMemberTeam(memberIndex,tm.requestedTeam); + NotifyTeamAssigment(memberIndex); + } + else + { + // If someone wants to join this user's old team, and we want to join their team, they can swap + unsigned int swappableMemberIndex; + for (swappableMemberIndex=0; swappableMemberIndex < teamMembers.Size(); swappableMemberIndex++) + { + if (teamMembers[swappableMemberIndex].currentTeam==tm.requestedTeam && teamMembers[swappableMemberIndex].requestedTeam==oldTeamThisUserWasOn) + break; + } + + if (swappableMemberIndex!=teamMembers.Size()) + { + SwapTeamMembersByRequest(memberIndex,swappableMemberIndex); + NotifyTeamAssigment(memberIndex); + NotifyTeamAssigment(swappableMemberIndex); + } + else + { + // Full or would not be even + NotifyTeamSwitchPending(packet->guid, tm.requestedTeam); + } + } + } +} +unsigned int TeamBalancer::GetMemberIndex(RakNetGUID guid) +{ + for (unsigned int i=0; i < teamMembers.Size(); i++) + { + if (teamMembers[i].memberGuid==guid) + return i; + } + return (unsigned int) -1; +} +unsigned int TeamBalancer::AddTeamMember(const TeamMember &tm) +{ + if (tm.currentTeam>teamLimits.Size()) + { + RakAssert("TeamBalancer::AddTeamMember team index out of bounds" && 0); + return (unsigned int) -1; + } + + RakAssert(tm.currentTeam!=UNASSIGNED_TEAM_ID); + + teamMembers.Push(tm,__FILE__,__LINE__); + if (teamMemberCounts.Size() overpopulatedTeams; + TeamId teamMemberCountsIndex; + unsigned int memberIndexToSwitch; + for (teamMemberCountsIndex=0; teamMemberCountsIndex0); + memberIndexToSwitch=GetMemberIndexToSwitchTeams(overpopulatedTeams,teamMemberCountsIndex); + RakAssert(memberIndexToSwitch!=(unsigned int)-1); + SwitchMemberTeam(memberIndexToSwitch,teamMemberCountsIndex); + // Tell this member he switched teams + NotifyTeamAssigment(memberIndexToSwitch); + } + } +} +unsigned int TeamBalancer::GetMemberIndexToSwitchTeams(const DataStructures::List &sourceTeamNumbers, TeamId targetTeamNumber) +{ + DataStructures::List preferredSwapIndices; + DataStructures::List potentialSwapIndices; + unsigned int i,j; + for (j=0; j < sourceTeamNumbers.Size(); j++) + { + RakAssert(sourceTeamNumbers[j]!=targetTeamNumber); + for (i=0; i < teamMembers.Size(); i++) + { + if (teamMembers[i].currentTeam==sourceTeamNumbers[j]) + { + if (teamMembers[i].requestedTeam==targetTeamNumber) + preferredSwapIndices.Push(i,__FILE__,__LINE__); + else + potentialSwapIndices.Push(i,__FILE__,__LINE__); + } + } + } + + if (preferredSwapIndices.Size()>0) + { + return preferredSwapIndices[ randomMT() % preferredSwapIndices.Size() ]; + } + else if (potentialSwapIndices.Size()>0) + { + return potentialSwapIndices[ randomMT() % potentialSwapIndices.Size() ]; + } + else + { + return (unsigned int) -1; + } +} +void TeamBalancer::SwitchMemberTeam(unsigned int teamMemberIndex, TeamId destinationTeam) +{ + teamMemberCounts[ teamMembers[teamMemberIndex].currentTeam ]=teamMemberCounts[ teamMembers[teamMemberIndex].currentTeam ]-1; + teamMemberCounts[ destinationTeam ]=teamMemberCounts[ destinationTeam ]+1; + teamMembers[teamMemberIndex].currentTeam=destinationTeam; + if (teamMembers[teamMemberIndex].requestedTeam==destinationTeam) + teamMembers[teamMemberIndex].requestedTeam=UNASSIGNED_TEAM_ID; +} +void TeamBalancer::GetOverpopulatedTeams(DataStructures::List &overpopulatedTeams, int maxTeamSize) +{ + overpopulatedTeams.Clear(true,__FILE__,__LINE__); + for (TeamId i=0; i < teamMemberCounts.Size(); i++) + { + if (teamMemberCounts[i]>=maxTeamSize) + overpopulatedTeams.Push(i,__FILE__,__LINE__); + } +} +void TeamBalancer::NotifyTeamAssigment(unsigned int teamMemberIndex) +{ + RakAssert(teamMemberIndex < teamMembers.Size()); + if (teamMemberIndex>=teamMembers.Size()) + return; + + BitStream bsOut; + bsOut.Write((MessageID)ID_TEAM_BALANCER_TEAM_ASSIGNED); + bsOut.Write(teamMembers[teamMemberIndex].currentTeam); + rakPeerInterface->Send(&bsOut,HIGH_PRIORITY,RELIABLE_ORDERED,0,teamMembers[teamMemberIndex].memberGuid,false); +} +bool TeamBalancer::WeAreHost(void) const +{ + return hostGuid==rakPeerInterface->GetGuidFromSystemAddress(UNASSIGNED_SYSTEM_ADDRESS); +} +PluginReceiveResult TeamBalancer::OnTeamAssigned(Packet *packet) +{ + if (packet->guid!=hostGuid) + return RR_STOP_PROCESSING_AND_DEALLOCATE; + + BitStream bsIn(packet->data,packet->length,false); + bsIn.IgnoreBytes(1); + bsIn.Read(currentTeam); + if (currentTeam==UNASSIGNED_TEAM_ID) + requestedTeam=UNASSIGNED_TEAM_ID; + + expectingToReceiveTeamNumber=false; + + return RR_CONTINUE_PROCESSING; +} +PluginReceiveResult TeamBalancer::OnRequestedTeamChangePending(Packet *packet) +{ + if (packet->guid!=hostGuid) + return RR_STOP_PROCESSING_AND_DEALLOCATE; + + expectingToReceiveTeamNumber=false; + + return RR_CONTINUE_PROCESSING; +} +PluginReceiveResult TeamBalancer::OnTeamsLocked(Packet *packet) +{ + if (packet->guid!=hostGuid) + return RR_STOP_PROCESSING_AND_DEALLOCATE; + + expectingToReceiveTeamNumber=false; + + return RR_CONTINUE_PROCESSING; +} +TeamId TeamBalancer::GetNextDefaultTeam(void) +{ + // Accounting for team balancing and team limits, get the team a player should be placed on + switch (defaultAssigmentAlgorithm) + { + case SMALLEST_TEAM: + { + return GetSmallestNonFullTeam(); + } + + case FILL_IN_ORDER: + { + return GetFirstNonFullTeam(); + } + + default: + { + RakAssert("TeamBalancer::GetNextDefaultTeam unknown algorithm enumeration" && 0); + return UNASSIGNED_TEAM_ID; + } + } +} +bool TeamBalancer::TeamWouldBeOverpopulatedOnAddition(TeamId teamId, unsigned int teamMemberSize) +{ + // Accounting for team balancing and team limits, would this team be overpopulated if a member was added to it? + if (teamMemberCounts[teamId]>=teamLimits[teamId]) + { + return true; + } + + if (forceTeamsToBeEven) + { + int allowedLimit = teamMemberSize/teamLimits.Size() + 1; + return teamMemberCounts[teamId]>=allowedLimit; + } + + return false; +} +bool TeamBalancer::TeamWouldBeUnderpopulatedOnLeave(TeamId teamId, unsigned int teamMemberSize) +{ + if (forceTeamsToBeEven) + { + unsigned int minMembersOnASingleTeam = (teamMemberSize-1)/teamLimits.Size(); + return teamMemberCounts[teamId]<=minMembersOnASingleTeam; + } + return false; +} +TeamId TeamBalancer::GetSmallestNonFullTeam(void) const +{ + TeamId idx; + unsigned long smallestTeamCount=MAX_UNSIGNED_LONG; + TeamId smallestTeamIndex = UNASSIGNED_TEAM_ID; + for (idx=0; idx < teamMemberCounts.Size(); idx++) + { + if (teamMemberCounts[idx] membersThatWantToJoinTheTeam; + for (TeamId i=0; i < teamMembers.Size(); i++) + { + if (teamMembers[i].requestedTeam==teamId) + membersThatWantToJoinTheTeam.Push(i,__FILE__,__LINE__); + } + + if (membersThatWantToJoinTheTeam.Size()>0) + { + TeamId oldTeam; + unsigned int swappedMemberIndex = membersThatWantToJoinTheTeam[ randomMT() % membersThatWantToJoinTheTeam.Size() ]; + oldTeam=teamMembers[swappedMemberIndex].currentTeam; + SwitchMemberTeam(swappedMemberIndex,teamId); + NotifyTeamAssigment(swappedMemberIndex); + return oldTeam; + } + return UNASSIGNED_TEAM_ID; +} +void TeamBalancer::NotifyTeamsLocked(RakNetGUID target, TeamId requestedTeam) +{ + BitStream bsOut; + bsOut.Write((MessageID)ID_TEAM_BALANCER_TEAMS_LOCKED); + bsOut.Write(requestedTeam); + rakPeerInterface->Send(&bsOut,HIGH_PRIORITY,RELIABLE_ORDERED,0,target,false); +} +void TeamBalancer::NotifyTeamSwitchPending(RakNetGUID target, TeamId requestedTeam) +{ + BitStream bsOut; + bsOut.Write((MessageID)ID_TEAM_BALANCER_REQUESTED_TEAM_CHANGE_PENDING); + bsOut.Write(requestedTeam); + rakPeerInterface->Send(&bsOut,HIGH_PRIORITY,RELIABLE_ORDERED,0,target,false); +} +void TeamBalancer::SwapTeamMembersByRequest(unsigned int memberIndex1, unsigned int memberIndex2) +{ + TeamId index1Team = teamMembers[memberIndex1].currentTeam; + teamMembers[memberIndex1].currentTeam=teamMembers[memberIndex2].currentTeam; + teamMembers[memberIndex2].currentTeam=index1Team; + teamMembers[memberIndex1].requestedTeam=UNASSIGNED_TEAM_ID; + teamMembers[memberIndex2].requestedTeam=UNASSIGNED_TEAM_ID; +} +void TeamBalancer::NotifyNoTeam(RakNetGUID target) +{ + BitStream bsOut; + bsOut.Write((MessageID)ID_TEAM_BALANCER_TEAM_ASSIGNED); + bsOut.Write((unsigned char)UNASSIGNED_TEAM_ID); + rakPeerInterface->Send(&bsOut,HIGH_PRIORITY,RELIABLE_ORDERED,0,target,false); +} +bool TeamBalancer::TeamsWouldBeEvenOnSwitch(TeamId t1, TeamId t2) +{ + RakAssert(teamMembers.Size()!=0); + return TeamWouldBeOverpopulatedOnAddition(t1, teamMembers.Size()-1)==false && + TeamWouldBeUnderpopulatedOnLeave(t2, teamMembers.Size()-1)==false; +} +void TeamBalancer::SetAllowHostMigration(bool allow) +{ + allowHostMigration=allow; +} + +#endif // _RAKNET_SUPPORT_* diff --git a/RakNet/Sources/TeamBalancer.h b/RakNet/Sources/TeamBalancer.h new file mode 100644 index 0000000..0caebd7 --- /dev/null +++ b/RakNet/Sources/TeamBalancer.h @@ -0,0 +1,197 @@ +/// \file TeamBalancer.h +/// \brief Set and network team selection (supports peer to peer or client/server) +/// \details Automatically handles transmission and resolution of team selection, including team switching and balancing +/// +/// This file is part of RakNet Copyright 2003 Jenkins Software LLC +/// +/// Usage of RakNet is subject to the appropriate license agreement. + +#include "NativeFeatureIncludes.h" +#if _RAKNET_SUPPORT_TeamBalancer==1 + +#ifndef __TEAM_BALANCER_H +#define __TEAM_BALANCER_H + +class RakPeerInterface; +#include "PluginInterface2.h" +#include "RakMemoryOverride.h" +#include "NativeTypes.h" +#include "DS_List.h" +#include "RakString.h" + +namespace RakNet +{ + +/// \defgroup TEAM_BALANCER_GROUP TeamBalancer +/// \brief Set and network team selection (supports peer to peer or client/server) +/// \details Automatically handles transmission and resolution of team selection, including team switching and balancing +/// \ingroup PLUGINS_GROUP + +/// 0...254 for your team number identifiers. 255 is reserved as undefined. +/// \ingroup TEAM_BALANCER_GROUP +typedef unsigned char TeamId; + +#define UNASSIGNED_TEAM_ID 255 + +/// \brief Set and network team selection (supports peer to peer or client/server) +/// \details Automatically handles transmission and resolution of team selection, including team switching and balancing.
      +/// Usage: TODO +/// \ingroup TEAM_BALANCER_GROUP +class RAK_DLL_EXPORT TeamBalancer : public PluginInterface2 +{ +public: + TeamBalancer(); + virtual ~TeamBalancer(); + + /// \brief Define which system processes team communication and maintains the list of teams + /// \details One system is responsible for maintaining the team list and determining which system is on which team. + /// For a client/server system, this would be the server. + /// For a peer to peer system, this would be one of the peers. If using FullyConnectedMesh2, this would be called with the value returned by FullyConnectedMesh2::GetHostSystem(). Update when you get ID_FCM2_INFORM_FCMGUID + /// \sa SetAllowHostMigration() + /// \param[in] _hostGuid One system we are connected to that will resolve team assignments. + void SetHostGuid(RakNetGUID _hostGuid); + + /// \brief Set the limit to the number of players on each team + /// \details SetTeamSizeLimits() must be called on the host, so the host can enforce the maximum number of players on each team. + /// SetTeamSizeLimits() can be called on all systems if desired - for example, in a P2P environment you may wish to call it on all systems in advanced in case you become host. + /// Calling this function when teams have already been created does not affect existing teams. + /// \param[in] teamLimits The maximum number of people per team, by index. For example, a list of size 3 with values 1,2,3 would allow 1 person on team 0, 2 people on team 1, adn 3 people on team 2. + void SetTeamSizeLimits(const DataStructures::List &_teamLimits); + + /// \brief Set the limit to the number of players on each team + /// \details SetTeamSizeLimits() must be called on the host, so the host can enforce the maximum number of players on each team. + /// SetTeamSizeLimits() can be called on all systems if desired - for example, in a P2P environment you may wish to call it on all systems in advanced in case you become host. + /// Calling this function when teams have already been created does not affect existing teams. + /// \param[in] values The maximum number of people per team, by index. For example, a list of size 3 with values 1,2,3 would allow 1 person on team 0, 2 people on team 1, adn 3 people on team 2. + /// \param[in] valuesLength Length of the values array + void SetTeamSizeLimits(unsigned short *values, int valuesLength); + + enum DefaultAssigmentAlgorithm + { + /// Among all the teams, join the team with the smallest number of players + SMALLEST_TEAM, + /// Join the team with the lowest index that has open slots. + FILL_IN_ORDER + }; + /// \brief Determine how players' teams will be set when they call RequestAnyTeam() + /// \details Based on the specified enumeration, a player will join a team automatically + /// Defaults to SMALLEST_TEAM + /// This function is only used by the host + /// \param[in] daa Enumeration describing the algorithm to use + void SetDefaultAssignmentAlgorithm(DefaultAssigmentAlgorithm daa); + + /// \brief By default, teams can be unbalanced up to the team size limit defined by SetTeamSizeLimits() + /// \details If SetForceEvenTeams(true) is called on the host, then teams cannot be unbalanced by more than 1 player + /// If teams are uneven at the time that SetForceEvenTeams(true) is called, players at randomly will be switched, and will be notified of ID_TEAM_BALANCER_TEAM_ASSIGNED + /// If players disconnect from the host such that teams would not be even, and teams are not locked, then a player from the largest team is randomly moved to even the teams. + /// Defaults to false + /// \note SetLockTeams(true) takes priority over SetForceEvenTeams(), so if teams are currently locked, this function will have no effect until teams become unlocked. + /// \param[in] force True to force even teams. False to allow teams to not be evenly matched + void SetForceEvenTeams(bool force); + + /// \brief If set, calls to RequestSpecificTeam() and RequestAnyTeam() will return the team you are currently on. + /// \details However, if those functions are called and you do not have a team, then you will be assigned to a default team according to SetDefaultAssignmentAlgorithm() and possibly SetForceEvenTeams(true) + /// If \a lock is false, and SetForceEvenTeams() was called with \a force as true, and teams are currently uneven, they will be made even, and those players randomly moved will get ID_TEAM_BALANCER_TEAM_ASSIGNED + /// Defaults to false + /// \param[in] lock True to lock teams, false to unlock + void SetLockTeams(bool lock); + + /// Set your requested team. UNASSIGNED_TEAM_ID means no team. + /// After enough time for network communication, ID_TEAM_BALANCER_SET_TEAM will be returned with your current team, or + /// If team switch is not possible, ID_TEAM_BALANCER_REQUESTED_TEAM_CHANGE_PENDING or ID_TEAM_BALANCER_TEAMS_LOCKED will be returned. + /// In the case of ID_TEAM_BALANCER_REQUESTED_TEAM_CHANGE_PENDING the request will stay in memory. ID_TEAM_BALANCER_SET_TEAM will be returned when someone on the desired team leaves or wants to switch to your team. + /// If SetLockTeams(true) is called while you have a request pending, you will get ID_TEAM_BALANCER_TEAMS_LOCKED + /// \pre Call SetTeamSizeLimits() on the host and call SetHostGuid() on this system. If the host is not running the TeamBalancer plugin or did not have SetTeamSizeLimits() called, then you will not get any response. + /// \param[in] desiredTeam An index representing your team number. The index should range from 0 to one less than the size of the list passed to SetTeamSizeLimits() on the host. You can also pass UNASSIGNED_TEAM_ID to not be on any team (such as if spectating) + /// \return True on request sent, false on SetHostGuid() was not called. + bool RequestSpecificTeam(TeamId desiredTeam); + + /// If ID_TEAM_BALANCER_REQUESTED_TEAM_CHANGE_PENDING is returned after a call to RequestSpecificTeam(), the request will stay in memory on the host and execute when available, or until the teams become locked. + /// You can cancel the request by calling CancelRequestSpecificTeam(), in which case you will stay on your existing team. + /// \note Due to latency, even after calling CancelRequestSpecificTeam() you may still get ID_TEAM_BALANCER_SET_TEAM if the packet was already in transmission. + void CancelRequestSpecificTeam(void); + + /// Allow host to pick your team, based on whatever algorithm it uses for default team assignments. + /// This only has an effect if you are not currently on a team (GetMyTeam() returns UNASSIGNED_TEAM_ID) + /// \pre Call SetTeamSizeLimits() on the host and call SetHostGuid() on this system + void RequestAnyTeam(void); + + /// Returns your team. + /// As your team changes, you are notified through the ID_TEAM_BALANCER_TEAM_ASSIGNED packet in byte 1. + /// Returns UNASSIGNED_TEAM_ID initially + /// \pre For this to return anything other than UNASSIGNED_TEAM_ID, connect to a properly initialized host and RequestSpecificTeam() or RequestAnyTeam() first + /// \return UNASSIGNED_TEAM_ID for no team. Otherwise, the index should range from 0 to one less than the size of the list passed to SetTeamSizeLimits() on the host + TeamId GetMyTeam(void) const; + + /// Allow systems to change the host + /// If true, this is a security hole, but needed for peer to peer + /// For client server, set to false + /// Defaults to true + /// param[in] allow True to allow host migration, false to not allow + void SetAllowHostMigration(bool allow); + + struct TeamMember + { + RakNetGUID memberGuid; + TeamId currentTeam; + TeamId requestedTeam; + }; + +protected: + + /// \internal + virtual PluginReceiveResult OnReceive(Packet *packet); + /// \internal + virtual void OnClosedConnection(SystemAddress systemAddress, RakNetGUID rakNetGUID, PI2_LostConnectionReason lostConnectionReason ); + + void OnStatusUpdateToNewHost(Packet *packet); + void OnCancelTeamRequest(Packet *packet); + void OnRequestAnyTeam(Packet *packet); + void OnRequestSpecificTeam(Packet *packet); + + RakNetGUID hostGuid; + TeamId currentTeam; + TeamId requestedTeam; + DefaultAssigmentAlgorithm defaultAssigmentAlgorithm; + bool forceTeamsToBeEven; + bool lockTeams; + bool expectingToReceiveTeamNumber; // So if we lose the connection while processing, we request the same info of the new host + bool allowHostMigration; + + DataStructures::List teamLimits; + DataStructures::List teamMemberCounts; + DataStructures::List teamMembers; + unsigned int GetMemberIndex(RakNetGUID guid); + unsigned int AddTeamMember(const TeamMember &tm); // Returns index of new member + void RemoveTeamMember(unsigned int index); + void EvenTeams(void); + unsigned int GetMemberIndexToSwitchTeams(const DataStructures::List &sourceTeamNumbers, TeamId targetTeamNumber); + void GetOverpopulatedTeams(DataStructures::List &overpopulatedTeams, int maxTeamSize); + void SwitchMemberTeam(unsigned int teamMemberIndex, TeamId destinationTeam); + void NotifyTeamAssigment(unsigned int teamMemberIndex); + bool WeAreHost(void) const; + PluginReceiveResult OnTeamAssigned(Packet *packet); + PluginReceiveResult OnRequestedTeamChangePending(Packet *packet); + PluginReceiveResult OnTeamsLocked(Packet *packet); + void GetMinMaxTeamMembers(int &minMembersOnASingleTeam, int &maxMembersOnASingleTeam); + TeamId GetNextDefaultTeam(void); // Accounting for team balancing and team limits, get the team a player should be placed on + bool TeamWouldBeOverpopulatedOnAddition(TeamId teamId, unsigned int teamMemberSize); // Accounting for team balancing and team limits, would this team be overpopulated if a member was added to it? + bool TeamWouldBeUnderpopulatedOnLeave(TeamId teamId, unsigned int teamMemberSize); + TeamId GetSmallestNonFullTeam(void) const; + TeamId GetFirstNonFullTeam(void) const; + void MoveMemberThatWantsToJoinTeam(TeamId teamId); + TeamId MoveMemberThatWantsToJoinTeamInternal(TeamId teamId); + void NotifyTeamsLocked(RakNetGUID target, TeamId requestedTeam); + void NotifyTeamSwitchPending(RakNetGUID target, TeamId requestedTeam); + void NotifyNoTeam(RakNetGUID target); + void SwapTeamMembersByRequest(unsigned int memberIndex1, unsigned int memberIndex2); + void RemoveByGuid(RakNetGUID rakNetGUID); + bool TeamsWouldBeEvenOnSwitch(TeamId t1, TeamId t2); + +}; + +} // namespace RakNet + +#endif + +#endif // _RAKNET_SUPPORT_* diff --git a/RakNet/Sources/TelnetTransport.cpp b/RakNet/Sources/TelnetTransport.cpp new file mode 100644 index 0000000..6b4c70f --- /dev/null +++ b/RakNet/Sources/TelnetTransport.cpp @@ -0,0 +1,360 @@ +#include "NativeFeatureIncludes.h" +#if _RAKNET_SUPPORT_TelnetTransport==1 + +#include "TelnetTransport.h" +#include "TCPInterface.h" +#include +#include +#include +#include "LinuxStrings.h" + +// #define _PRINTF_DEBUG + +#define ECHO_INPUT + +#ifdef _MSC_VER +#pragma warning( push ) +#endif + +TelnetTransport::TelnetTransport() +{ + tcpInterface=0; + sendSuffix=0; + sendPrefix=0; +} +TelnetTransport::~TelnetTransport() +{ + Stop(); + if (sendSuffix) + rakFree_Ex(sendSuffix, __FILE__, __LINE__ ); + if (sendPrefix) + rakFree_Ex(sendPrefix, __FILE__, __LINE__ ); +} +bool TelnetTransport::Start(unsigned short port, bool serverMode) +{ + (void) serverMode; + AutoAllocate(); + RakAssert(serverMode); + return tcpInterface->Start(port, 64); +} +void TelnetTransport::Stop(void) +{ + if (tcpInterface==0) return; + tcpInterface->Stop(); + unsigned i; + for (i=0; i < remoteClients.Size(); i++) + RakNet::OP_DELETE(remoteClients[i], __FILE__, __LINE__); + remoteClients.Clear(false, __FILE__, __LINE__); + RakNet::OP_DELETE(tcpInterface, __FILE__, __LINE__); + tcpInterface=0; +} +void TelnetTransport::Send( SystemAddress systemAddress, const char *data,... ) +{ + if (tcpInterface==0) return; + + if (data==0 || data[0]==0) + return; + + char text[REMOTE_MAX_TEXT_INPUT]; + size_t prefixLength; + if (sendPrefix) + { + strcpy(text, sendPrefix); + prefixLength = strlen(sendPrefix); + } + else + { + text[0]=0; + prefixLength=0; + } + va_list ap; + va_start(ap, data); + _vsnprintf(text+prefixLength, REMOTE_MAX_TEXT_INPUT-prefixLength, data, ap); + va_end(ap); + text[REMOTE_MAX_TEXT_INPUT-1]=0; + + if (sendSuffix) + { + size_t length = strlen(text); + size_t availableChars = REMOTE_MAX_TEXT_INPUT-length-1; + strncat(text, sendSuffix, availableChars); + } + + tcpInterface->Send(text, (unsigned int) strlen(text), systemAddress, false); +} +void TelnetTransport::CloseConnection( SystemAddress systemAddress ) +{ + tcpInterface->CloseConnection(systemAddress); +} +Packet* TelnetTransport::Receive( void ) +{ + if (tcpInterface==0) return 0; + Packet *p = tcpInterface->Receive(); + if (p==0) + return 0; + + /* + if (p->data[0]==255) + { + unsigned i; + for (i=0; i < p->length; i++) + { + RAKNET_DEBUG_PRINTF("%i ", p->data[i]); + } + RAKNET_DEBUG_PRINTF("\n"); + tcpInterface->DeallocatePacket(p); + return 0; + } + */ + + // Get this guy's cursor buffer. This is real bullcrap that I have to do this. + unsigned i; + TelnetClient *remoteClient=0; + for (i=0; i < remoteClients.Size(); i++) + { + if (remoteClients[i]->systemAddress==p->systemAddress) + remoteClient=remoteClients[i]; + } + //RakAssert(remoteClient); + if (remoteClient==0) + { + tcpInterface->DeallocatePacket(p); + return 0; + } + + + if (p->length==3 && p->data[0]==27 && p->data[1]==91 && p->data[2]==65) + { + if (remoteClient->lastSentTextInput[0]) + { + // Up arrow, return last string + for (int i=0; remoteClient->textInput[i]; i++) + remoteClient->textInput[i]=8; + strcat(remoteClient->textInput, remoteClient->lastSentTextInput); + tcpInterface->Send((const char *)remoteClient->textInput, strlen(remoteClient->textInput), p->systemAddress, false); + strcpy(remoteClient->textInput,remoteClient->lastSentTextInput); + remoteClient->cursorPosition=strlen(remoteClient->textInput); + } + + return 0; + } + + + // 127 is delete - ignore that + // 9 is tab + // 27 is escape + if (p->data[0]>=127 || p->data[0]==9 || p->data[0]==27) + { + tcpInterface->DeallocatePacket(p); + return 0; + } + + // Hack - I don't know what the hell this is about but cursor keys send 3 characters at a time. I can block these + //Up=27,91,65 + //Down=27,91,66 + //Right=27,91,67 + //Left=27,91,68 + if (p->length==3 && p->data[0]==27 && p->data[1]==91 && p->data[2]>=65 && p->data[2]<=68) + { + tcpInterface->DeallocatePacket(p); + return 0; + } + + + + // Echo +#ifdef ECHO_INPUT + tcpInterface->Send((const char *)p->data, p->length, p->systemAddress, false); +#endif + + bool gotLine; + // Process each character in turn + for (i=0; i < p->length; i++) + { + +#ifdef ECHO_INPUT + if (p->data[i]==8) + { + char spaceThenBack[2]; + spaceThenBack[0]=' '; + spaceThenBack[1]=8; + tcpInterface->Send((const char *)spaceThenBack, 2, p->systemAddress, false); + } +#endif + + gotLine=ReassembleLine(remoteClient, p->data[i]); + if (gotLine && remoteClient->textInput[0]) + { + + Packet *reassembledLine = (Packet*) rakMalloc_Ex(sizeof(Packet), __FILE__, __LINE__); + reassembledLine->length=(unsigned int) strlen(remoteClient->textInput); + memcpy(remoteClient->lastSentTextInput, remoteClient->textInput, reassembledLine->length+1); + RakAssert(reassembledLine->length < REMOTE_MAX_TEXT_INPUT); + reassembledLine->data= (unsigned char*) rakMalloc_Ex( reassembledLine->length+1, __FILE__, __LINE__ ); + memcpy(reassembledLine->data, remoteClient->textInput, reassembledLine->length); +#ifdef _PRINTF_DEBUG + memset(remoteClient->textInput, 0, REMOTE_MAX_TEXT_INPUT); +#endif + reassembledLine->data[reassembledLine->length]=0; + reassembledLine->systemAddress=p->systemAddress; + tcpInterface->DeallocatePacket(p); + return reassembledLine; + } + } + + tcpInterface->DeallocatePacket(p); + return 0; +} +void TelnetTransport::DeallocatePacket( Packet *packet ) +{ + if (tcpInterface==0) return; + rakFree_Ex(packet->data, __FILE__, __LINE__ ); + rakFree_Ex(packet, __FILE__, __LINE__ ); +} +SystemAddress TelnetTransport::HasNewIncomingConnection(void) +{ + unsigned i; + SystemAddress newConnection; + newConnection = tcpInterface->HasNewIncomingConnection(); + // 03/16/06 Can't force the stupid windows telnet to use line mode or local echo so now I have to track all the remote players and their + // input buffer + if (newConnection != UNASSIGNED_SYSTEM_ADDRESS) + { + unsigned char command[10]; + // http://www.pcmicro.com/netfoss/RFC857.html + // IAC WON'T ECHO + command[0]=255; // IAC + //command[1]=253; // WON'T + command[1]=251; // WILL + command[2]=1; // ECHO + tcpInterface->Send((const char*)command, 3, newConnection, false); + + /* + // Tell the other side to use line mode + // http://www.faqs.org/rfcs/rfc1184.html + // IAC DO LINEMODE + // command[0]=255; // IAC + // command[1]=252; // DO + // command[2]=34; // LINEMODE + // tcpInterface->Send((const char*)command, 3, newConnection); + + */ + + TelnetClient *remoteClient=0; + for (i=0; i < remoteClients.Size(); i++) + { + if (remoteClients[i]->systemAddress==newConnection) + { + remoteClient=remoteClients[i]; + remoteClient->cursorPosition=0; + } + } + + if (remoteClient==0) + { + remoteClient=new TelnetClient; + remoteClient->lastSentTextInput[0]=0; + remoteClient->cursorPosition=0; + remoteClient->systemAddress=newConnection; +#ifdef _PRINTF_DEBUG + memset(remoteClient->textInput, 0, REMOTE_MAX_TEXT_INPUT); +#endif + } + + remoteClients.Insert(remoteClient, __FILE__, __LINE__); + } + return newConnection; +} +SystemAddress TelnetTransport::HasLostConnection(void) +{ + SystemAddress systemAddress; + unsigned i; + systemAddress=tcpInterface->HasLostConnection(); + if (systemAddress!=UNASSIGNED_SYSTEM_ADDRESS) + { + for (i=0; i < remoteClients.Size(); i++) + { + if (remoteClients[i]->systemAddress==systemAddress) + { + RakNet::OP_DELETE(remoteClients[i], __FILE__, __LINE__); + remoteClients[i]=remoteClients[remoteClients.Size()-1]; + remoteClients.RemoveFromEnd(); + } + } + } + return systemAddress; +} +CommandParserInterface* TelnetTransport::GetCommandParser(void) +{ + return 0; +} +void TelnetTransport::SetSendSuffix(const char *suffix) +{ + if (sendSuffix) + { + rakFree_Ex(sendSuffix, __FILE__, __LINE__ ); + sendSuffix=0; + } + if (suffix) + { + sendSuffix = (char*) rakMalloc_Ex(strlen(suffix)+1, __FILE__, __LINE__); + strcpy(sendSuffix, suffix); + } +} +void TelnetTransport::SetSendPrefix(const char *prefix) +{ + if (sendPrefix) + { + rakFree_Ex(sendPrefix, __FILE__, __LINE__ ); + sendPrefix=0; + } + if (prefix) + { + sendPrefix = (char*) rakMalloc_Ex(strlen(prefix)+1, __FILE__, __LINE__); + strcpy(sendPrefix, prefix); + } +} +void TelnetTransport::AutoAllocate(void) +{ + if (tcpInterface==0) + tcpInterface=new TCPInterface; +} +bool TelnetTransport::ReassembleLine(TelnetTransport::TelnetClient* remoteClient, unsigned char c) +{ + if (c=='\n') + { + remoteClient->textInput[remoteClient->cursorPosition]=0; + remoteClient->cursorPosition=0; +#ifdef _PRINTF_DEBUG + RAKNET_DEBUG_PRINTF("[Done] %s\n", remoteClient->textInput); +#endif + return true; + } + else if (c==8) // backspace + { + if (remoteClient->cursorPosition>0) + { + remoteClient->textInput[--remoteClient->cursorPosition]=0; +#ifdef _PRINTF_DEBUG + RAKNET_DEBUG_PRINTF("[Back] %s\n", remoteClient->textInput); +#endif + } + } + else if (c>=32 && c <127) + { + if (remoteClient->cursorPosition < REMOTE_MAX_TEXT_INPUT) + { + remoteClient->textInput[remoteClient->cursorPosition++]=c; +#ifdef _PRINTF_DEBUG + RAKNET_DEBUG_PRINTF("[Norm] %s\n", remoteClient->textInput); +#endif + } + } + return false; +} + +#ifdef _MSC_VER +#pragma warning( pop ) +#endif + +#endif // _RAKNET_SUPPORT_* diff --git a/RakNet/Sources/TelnetTransport.h b/RakNet/Sources/TelnetTransport.h new file mode 100644 index 0000000..038f7a1 --- /dev/null +++ b/RakNet/Sources/TelnetTransport.h @@ -0,0 +1,63 @@ +/// \file +/// \brief Contains TelnetTransport , used to supports the telnet transport protocol. Insecure +/// +/// This file is part of RakNet Copyright 2003 Jenkins Software LLC +/// +/// Usage of RakNet is subject to the appropriate license agreement. + +#include "NativeFeatureIncludes.h" +#if _RAKNET_SUPPORT_TelnetTransport==1 + +#ifndef __TELNET_TRANSPORT +#define __TELNET_TRANSPORT + +#include "TransportInterface.h" +#include "DS_List.h" +#include "Export.h" +class TCPInterface; +struct TelnetClient; + +/// \brief Use TelnetTransport to easily allow windows telnet to connect to your ConsoleServer +/// \details To run Windows telnet, go to your start menu, click run, and in the edit box type "telnet " where is the ip address.
      +/// of your ConsoleServer (most likely the same IP as your game).
      +/// This implementation always echos commands. +class RAK_DLL_EXPORT TelnetTransport : public TransportInterface +{ +public: + TelnetTransport(); + virtual ~TelnetTransport(); + bool Start(unsigned short port, bool serverMode); + void Stop(void); + void Send( SystemAddress systemAddress, const char *data, ... ); + void CloseConnection( SystemAddress systemAddress ); + Packet* Receive( void ); + void DeallocatePacket( Packet *packet ); + SystemAddress HasNewIncomingConnection(void); + SystemAddress HasLostConnection(void); + CommandParserInterface* GetCommandParser(void); + void SetSendSuffix(const char *suffix); + void SetSendPrefix(const char *prefix); +protected: + + struct TelnetClient + { + SystemAddress systemAddress; + char textInput[REMOTE_MAX_TEXT_INPUT]; + char lastSentTextInput[REMOTE_MAX_TEXT_INPUT]; + unsigned cursorPosition; + }; + + TCPInterface *tcpInterface; + void AutoAllocate(void); + bool ReassembleLine(TelnetTransport::TelnetClient* telnetClient, unsigned char c); + + // Crap this sucks but because windows telnet won't send line at a time, I have to reconstruct the lines at the server per player + DataStructures::List remoteClients; + + char *sendSuffix, *sendPrefix; + +}; + +#endif + +#endif // _RAKNET_SUPPORT_* diff --git a/RakNet/Sources/ThreadPool.h b/RakNet/Sources/ThreadPool.h new file mode 100644 index 0000000..929a6b2 --- /dev/null +++ b/RakNet/Sources/ThreadPool.h @@ -0,0 +1,589 @@ +#ifndef __THREAD_POOL_H +#define __THREAD_POOL_H + +#include "RakMemoryOverride.h" +#include "DS_Queue.h" +#include "SimpleMutex.h" +#include "Export.h" +#include "RakThread.h" +#include "SignaledEvent.h" + +#ifdef _MSC_VER +#pragma warning( push ) +#endif + +class ThreadDataInterface +{ +public: + ThreadDataInterface() {} + virtual ~ThreadDataInterface() {} + + virtual void* PerThreadFactory(void *context)=0; + virtual void PerThreadDestructor(void* factoryResult, void *context)=0; +}; +/// A simple class to create worker threads that processes a queue of functions with data. +/// This class does not allocate or deallocate memory. It is up to the user to handle memory management. +/// InputType and OutputType are stored directly in a queue. For large structures, if you plan to delete from the middle of the queue, +/// you might wish to store pointers rather than the structures themselves so the array can shift efficiently. +template +struct RAK_DLL_EXPORT ThreadPool +{ + ThreadPool(); + ~ThreadPool(); + + /// Start the specified number of threads. + /// \param[in] numThreads The number of threads to start + /// \param[in] stackSize 0 for default (except on consoles). + /// \param[in] _perThreadInit User callback to return data stored per thread. Pass 0 if not needed. + /// \param[in] _perThreadDeinit User callback to destroy data stored per thread, created by _perThreadInit. Pass 0 if not needed. + /// \return True on success, false on failure. + bool StartThreads(int numThreads, int stackSize, void* (*_perThreadInit)()=0, void (*_perThreadDeinit)(void*)=0); + + // Alternate form of _perThreadDataFactory, _perThreadDataDestructor + void SetThreadDataInterface(ThreadDataInterface *tdi, void *context); + + /// Stops all threads + void StopThreads(void); + + /// Adds a function to a queue with data to pass to that function. This function will be called from the thread + /// Memory management is your responsibility! This class does not allocate or deallocate memory. + /// The best way to deallocate \a inputData is in userCallback. If you call EndThreads such that callbacks were not called, you + /// can iterate through the inputQueue and deallocate all pending input data there + /// The best way to deallocate output is as it is returned to you from GetOutput. Similarly, if you end the threads such that + /// not all output was returned, you can iterate through outputQueue and deallocate it there. + /// \param[in] workerThreadCallback The function to call from the thread + /// \param[in] inputData The parameter to pass to \a userCallback + void AddInput(OutputType (*workerThreadCallback)(InputType, bool *returnOutput, void* perThreadData), InputType inputData); + + /// Adds to the output queue + /// Use it if you want to inject output into the same queue that the system uses. Normally you would not use this. Consider it a convenience function. + /// \param[in] outputData The output to inject + void AddOutput(OutputType outputData); + + /// Returns true if output from GetOutput is waiting. + /// \return true if output is waiting, false otherwise + bool HasOutput(void); + + /// Inaccurate but fast version of HasOutput. If this returns true, you should still check HasOutput for the real value. + /// \return true if output is probably waiting, false otherwise + bool HasOutputFast(void); + + /// Returns true if input from GetInput is waiting. + /// \return true if input is waiting, false otherwise + bool HasInput(void); + + /// Inaccurate but fast version of HasInput. If this returns true, you should still check HasInput for the real value. + /// \return true if input is probably waiting, false otherwise + bool HasInputFast(void); + + /// Gets the output of a call to \a userCallback + /// HasOutput must return true before you call this function. Otherwise it will assert. + /// \return The output of \a userCallback. If you have different output signatures, it is up to you to encode the data to indicate this + OutputType GetOutput(void); + + /// Clears internal buffers + void Clear(void); + + /// Lock the input buffer before calling the functions InputSize, InputAtIndex, and RemoveInputAtIndex + /// It is only necessary to lock the input or output while the threads are running + void LockInput(void); + + /// Unlock the input buffer after you are done with the functions InputSize, GetInputAtIndex, and RemoveInputAtIndex + void UnlockInput(void); + + /// Length of the input queue + unsigned InputSize(void); + + /// Get the input at a specified index + InputType GetInputAtIndex(unsigned index); + + /// Remove input from a specific index. This does NOT do memory deallocation - it only removes the item from the queue + void RemoveInputAtIndex(unsigned index); + + /// Lock the output buffer before calling the functions OutputSize, OutputAtIndex, and RemoveOutputAtIndex + /// It is only necessary to lock the input or output while the threads are running + void LockOutput(void); + + /// Unlock the output buffer after you are done with the functions OutputSize, GetOutputAtIndex, and RemoveOutputAtIndex + void UnlockOutput(void); + + /// Length of the output queue + unsigned OutputSize(void); + + /// Get the output at a specified index + OutputType GetOutputAtIndex(unsigned index); + + /// Remove output from a specific index. This does NOT do memory deallocation - it only removes the item from the queue + void RemoveOutputAtIndex(unsigned index); + + /// Removes all items from the input queue + void ClearInput(void); + + /// Removes all items from the output queue + void ClearOutput(void); + + /// Are any of the threads working, or is input or output available? + bool IsWorking(void); + + /// The number of currently active threads. + int NumThreadsWorking(void); + + /// Did we call Start? + bool WasStarted(void); + + // Block until all threads are stopped. + bool Pause(void); + + // Continue running + void Resume(void); + +protected: + // It is valid to cancel input before it is processed. To do so, lock the inputQueue with inputQueueMutex, + // Scan the list, and remove the item you don't want. + SimpleMutex inputQueueMutex, outputQueueMutex, workingThreadCountMutex, runThreadsMutex; + + void* (*perThreadDataFactory)(); + void (*perThreadDataDestructor)(void*); + + // inputFunctionQueue & inputQueue are paired arrays so if you delete from one at a particular index you must delete from the other + // at the same index + DataStructures::Queue inputFunctionQueue; + DataStructures::Queue inputQueue; + DataStructures::Queue outputQueue; + + ThreadDataInterface *threadDataInterface; + void *tdiContext; + + + template + friend RAK_THREAD_DECLARATION(WorkerThread); + + /* +#ifdef _WIN32 + friend unsigned __stdcall WorkerThread( LPVOID arguments ); +#else + friend void* WorkerThread( void* arguments ); +#endif + */ + + /// \internal + bool runThreads; + /// \internal + int numThreadsRunning; + /// \internal + int numThreadsWorking; + /// \internal + SimpleMutex numThreadsRunningMutex; + + SignaledEvent quitAndIncomingDataEvents; +}; + +#include "ThreadPool.h" +#include "RakSleep.h" +#ifdef _WIN32 +#else +#include +#endif + +#ifdef _MSC_VER +#pragma warning(disable:4127) +#pragma warning( disable : 4701 ) // potentially uninitialized local variable 'inputData' used +#endif + +template +RAK_THREAD_DECLARATION(WorkerThread) +/* +#ifdef _WIN32 +unsigned __stdcall WorkerThread( LPVOID arguments ) +#else +void* WorkerThread( void* arguments ) +#endif +*/ +{ + bool returnOutput; + ThreadPool *threadPool = (ThreadPool*) arguments; + ThreadOutputType (*userCallback)(ThreadInputType, bool *, void*); + ThreadInputType inputData; + ThreadOutputType callbackOutput; + + userCallback=0; + + void *perThreadData; + if (threadPool->perThreadDataFactory) + perThreadData=threadPool->perThreadDataFactory(); + else if (threadPool->threadDataInterface) + perThreadData=threadPool->threadDataInterface->PerThreadFactory(threadPool->tdiContext); + else + perThreadData=0; + + // Increase numThreadsRunning + threadPool->numThreadsRunningMutex.Lock(); + ++threadPool->numThreadsRunning; + threadPool->numThreadsRunningMutex.Unlock(); + + while (1) + { +#ifdef _WIN32 + if (userCallback==0) + { + threadPool->quitAndIncomingDataEvents.WaitOnEvent(INFINITE); + } +#endif + + threadPool->runThreadsMutex.Lock(); + if (threadPool->runThreads==false) + { + threadPool->runThreadsMutex.Unlock(); + break; + } + threadPool->runThreadsMutex.Unlock(); + + threadPool->workingThreadCountMutex.Lock(); + ++threadPool->numThreadsWorking; + threadPool->workingThreadCountMutex.Unlock(); + + // Read input data + userCallback=0; + threadPool->inputQueueMutex.Lock(); + if (threadPool->inputFunctionQueue.Size()) + { + userCallback=threadPool->inputFunctionQueue.Pop(); + inputData=threadPool->inputQueue.Pop(); + } + threadPool->inputQueueMutex.Unlock(); + + if (userCallback) + { + callbackOutput=userCallback(inputData, &returnOutput,perThreadData); + if (returnOutput) + { + threadPool->outputQueueMutex.Lock(); + threadPool->outputQueue.Push(callbackOutput, __FILE__, __LINE__ ); + threadPool->outputQueueMutex.Unlock(); + } + } + + threadPool->workingThreadCountMutex.Lock(); + --threadPool->numThreadsWorking; + threadPool->workingThreadCountMutex.Unlock(); + } + + // Decrease numThreadsRunning + threadPool->numThreadsRunningMutex.Lock(); + --threadPool->numThreadsRunning; + threadPool->numThreadsRunningMutex.Unlock(); + + if (threadPool->perThreadDataDestructor) + threadPool->perThreadDataDestructor(perThreadData); + else if (threadPool->threadDataInterface) + threadPool->threadDataInterface->PerThreadDestructor(perThreadData, threadPool->tdiContext); + + return 0; +} +template +ThreadPool::ThreadPool() +{ + runThreads=false; + numThreadsRunning=0; + threadDataInterface=0; + tdiContext=0; + numThreadsWorking=0; + +} +template +ThreadPool::~ThreadPool() +{ + StopThreads(); + Clear(); +} +template +bool ThreadPool::StartThreads(int numThreads, int stackSize, void* (*_perThreadDataFactory)(), void (*_perThreadDataDestructor)(void *)) +{ + (void) stackSize; + + runThreadsMutex.Lock(); + if (runThreads==true) + { + runThreadsMutex.Unlock(); + return false; + } + runThreadsMutex.Unlock(); + + quitAndIncomingDataEvents.InitEvent(); + + perThreadDataFactory=_perThreadDataFactory; + perThreadDataDestructor=_perThreadDataDestructor; + + runThreadsMutex.Lock(); + runThreads=true; + runThreadsMutex.Unlock(); + + numThreadsWorking=0; + unsigned threadId = 0; + (void) threadId; + int i; + for (i=0; i < numThreads; i++) + { + int errorCode = RakNet::RakThread::Create(WorkerThread, this); + if (errorCode!=0) + { + StopThreads(); + return false; + } + } + // Wait for number of threads running to increase to numThreads + bool done=false; + while (done==false) + { + RakSleep(50); + numThreadsRunningMutex.Lock(); + if (numThreadsRunning==numThreads) + done=true; + numThreadsRunningMutex.Unlock(); + } + + return true; +} +template +void ThreadPool::SetThreadDataInterface(ThreadDataInterface *tdi, void *context) +{ + threadDataInterface=tdi; + tdiContext=context; +} +template +void ThreadPool::StopThreads(void) +{ + runThreadsMutex.Lock(); + if (runThreads==false) + { + runThreadsMutex.Unlock(); + return; + } + + runThreads=false; + runThreadsMutex.Unlock(); + + // Wait for number of threads running to decrease to 0 + bool done=false; + while (done==false) + { + quitAndIncomingDataEvents.SetEvent(); + + RakSleep(50); + numThreadsRunningMutex.Lock(); + if (numThreadsRunning==0) + done=true; + numThreadsRunningMutex.Unlock(); + } + + quitAndIncomingDataEvents.CloseEvent(); +} +template +void ThreadPool::AddInput(OutputType (*workerThreadCallback)(InputType, bool *returnOutput, void* perThreadData), InputType inputData) +{ + inputQueueMutex.Lock(); + inputQueue.Push(inputData, __FILE__, __LINE__ ); + inputFunctionQueue.Push(workerThreadCallback, __FILE__, __LINE__ ); + inputQueueMutex.Unlock(); + + quitAndIncomingDataEvents.SetEvent(); +} +template +void ThreadPool::AddOutput(OutputType outputData) +{ + outputQueueMutex.Lock(); + outputQueue.Push(outputData, __FILE__, __LINE__ ); + outputQueueMutex.Unlock(); +} +template +bool ThreadPool::HasOutputFast(void) +{ + return outputQueue.IsEmpty()==false; +} +template +bool ThreadPool::HasOutput(void) +{ + bool res; + outputQueueMutex.Lock(); + res=outputQueue.IsEmpty()==false; + outputQueueMutex.Unlock(); + return res; +} +template +bool ThreadPool::HasInputFast(void) +{ + return inputQueue.IsEmpty()==false; +} +template +bool ThreadPool::HasInput(void) +{ + bool res; + inputQueueMutex.Lock(); + res=inputQueue.IsEmpty()==false; + inputQueueMutex.Unlock(); + return res; +} +template +OutputType ThreadPool::GetOutput(void) +{ + // Real output check + OutputType output; + outputQueueMutex.Lock(); + output=outputQueue.Pop(); + outputQueueMutex.Unlock(); + return output; +} +template +void ThreadPool::Clear(void) +{ + runThreadsMutex.Lock(); + if (runThreads) + { + runThreadsMutex.Unlock(); + inputQueueMutex.Lock(); + inputFunctionQueue.Clear(__FILE__, __LINE__); + inputQueue.Clear(__FILE__, __LINE__); + inputQueueMutex.Unlock(); + + outputQueueMutex.Lock(); + outputQueue.Clear(__FILE__, __LINE__); + outputQueueMutex.Unlock(); + } + else + { + inputFunctionQueue.Clear(__FILE__, __LINE__); + inputQueue.Clear(__FILE__, __LINE__); + outputQueue.Clear(__FILE__, __LINE__); + } +} +template +void ThreadPool::LockInput(void) +{ + inputQueueMutex.Lock(); +} +template +void ThreadPool::UnlockInput(void) +{ + inputQueueMutex.Unlock(); +} +template +unsigned ThreadPool::InputSize(void) +{ + return inputQueue.Size(); +} +template +InputType ThreadPool::GetInputAtIndex(unsigned index) +{ + return inputQueue[index]; +} +template +void ThreadPool::RemoveInputAtIndex(unsigned index) +{ + inputQueue.RemoveAtIndex(index); + inputFunctionQueue.RemoveAtIndex(index); +} +template +void ThreadPool::LockOutput(void) +{ + outputQueueMutex.Lock(); +} +template +void ThreadPool::UnlockOutput(void) +{ + outputQueueMutex.Unlock(); +} +template +unsigned ThreadPool::OutputSize(void) +{ + return outputQueue.Size(); +} +template +OutputType ThreadPool::GetOutputAtIndex(unsigned index) +{ + return outputQueue[index]; +} +template +void ThreadPool::RemoveOutputAtIndex(unsigned index) +{ + outputQueue.RemoveAtIndex(index); +} +template +void ThreadPool::ClearInput(void) +{ + inputQueue.Clear(__FILE__,__LINE__); + inputFunctionQueue.Clear(__FILE__,__LINE__); +} + +template +void ThreadPool::ClearOutput(void) +{ + outputQueue.Clear(__FILE__,__LINE__); +} +template +bool ThreadPool::IsWorking(void) +{ + bool isWorking; +// workingThreadCountMutex.Lock(); +// isWorking=numThreadsWorking!=0; +// workingThreadCountMutex.Unlock(); + +// if (isWorking) +// return true; + + // Bug fix: Originally the order of these two was reversed. + // It's possible with the thread timing that working could have been false, then it picks up the data in the other thread, then it checks + // here and sees there is no data. So it thinks the thread is not working when it was. + if (HasOutputFast() && HasOutput()) + return true; + + if (HasInputFast() && HasInput()) + return true; + + // Need to check is working again, in case the thread was between the first and second checks + workingThreadCountMutex.Lock(); + isWorking=numThreadsWorking!=0; + workingThreadCountMutex.Unlock(); + + return isWorking; +} + +template +int ThreadPool::NumThreadsWorking(void) +{ + return numThreadsWorking; +} + +template +bool ThreadPool::WasStarted(void) +{ + bool b; + runThreadsMutex.Lock(); + b = runThreads; + runThreadsMutex.Unlock(); + return b; +} +template +bool ThreadPool::Pause(void) +{ + if (WasStarted()==false) + return false; + + workingThreadCountMutex.Lock(); + while (numThreadsWorking>0) + { + RakSleep(30); + } + return true; +} +template +void ThreadPool::Resume(void) +{ + workingThreadCountMutex.Unlock(); +} + +#ifdef _MSC_VER +#pragma warning( pop ) +#endif + +#endif + diff --git a/RakNet/Sources/ThreadsafePacketLogger.cpp b/RakNet/Sources/ThreadsafePacketLogger.cpp new file mode 100644 index 0000000..6bc0dea --- /dev/null +++ b/RakNet/Sources/ThreadsafePacketLogger.cpp @@ -0,0 +1,36 @@ +#include "NativeFeatureIncludes.h" +#if _RAKNET_SUPPORT_PacketLogger==1 + +#include "ThreadsafePacketLogger.h" +#include + +ThreadsafePacketLogger::ThreadsafePacketLogger() +{ + +} +ThreadsafePacketLogger::~ThreadsafePacketLogger() +{ + char **msg; + while ((msg = logMessages.ReadLock()) != 0) + { + rakFree_Ex((*msg), __FILE__, __LINE__ ); + } +} +void ThreadsafePacketLogger::Update(void) +{ + char **msg; + while ((msg = logMessages.ReadLock()) != 0) + { + WriteLog(*msg); + rakFree_Ex((*msg), __FILE__, __LINE__ ); + } +} +void ThreadsafePacketLogger::AddToLog(const char *str) +{ + char **msg = logMessages.WriteLock(); + *msg = (char*) rakMalloc_Ex( strlen(str)+1, __FILE__, __LINE__ ); + strcpy(*msg, str); + logMessages.WriteUnlock(); +} + +#endif // _RAKNET_SUPPORT_* diff --git a/RakNet/Sources/ThreadsafePacketLogger.h b/RakNet/Sources/ThreadsafePacketLogger.h new file mode 100644 index 0000000..1cba203 --- /dev/null +++ b/RakNet/Sources/ThreadsafePacketLogger.h @@ -0,0 +1,35 @@ +/// \file +/// \brief Derivation of the packet logger to defer the call to WriteLog until the user thread. +/// +/// This file is part of RakNet Copyright 2003 Jenkins Software LLC +/// +/// Usage of RakNet is subject to the appropriate license agreement. + +#include "NativeFeatureIncludes.h" +#if _RAKNET_SUPPORT_PacketLogger==1 + +#ifndef __THREADSAFE_PACKET_LOGGER_H +#define __THREADSAFE_PACKET_LOGGER_H + +#include "PacketLogger.h" +#include "SingleProducerConsumer.h" + +/// \ingroup PACKETLOGGER_GROUP +/// \brief Same as PacketLogger, but writes output in the user thread. +class RAK_DLL_EXPORT ThreadsafePacketLogger : public PacketLogger +{ +public: + ThreadsafePacketLogger(); + virtual ~ThreadsafePacketLogger(); + + virtual void Update(void); + +protected: + virtual void AddToLog(const char *str); + + DataStructures::SingleProducerConsumer logMessages; +}; + +#endif + +#endif // _RAKNET_SUPPORT_* diff --git a/RakNet/Sources/TransportInterface.h b/RakNet/Sources/TransportInterface.h new file mode 100644 index 0000000..cb83d8b --- /dev/null +++ b/RakNet/Sources/TransportInterface.h @@ -0,0 +1,77 @@ +/// \file +/// \brief Contains TransportInterface from which you can derive custom transport providers for ConsoleServer. +/// +/// This file is part of RakNet Copyright 2003 Jenkins Software LLC +/// +/// Usage of RakNet is subject to the appropriate license agreement. + + +#ifndef __TRANSPORT_INTERFACE_H +#define __TRANSPORT_INTERFACE_H + +#include "RakNetTypes.h" +#include "Export.h" +#include "RakMemoryOverride.h" + +#define REMOTE_MAX_TEXT_INPUT 2048 + +class CommandParserInterface; + +/// \brief Defines an interface that is used to send and receive null-terminated strings. +/// \details In practice this is only used by the CommandParser system for for servers. +class RAK_DLL_EXPORT TransportInterface +{ +public: + TransportInterface() {} + virtual ~TransportInterface() {} + + /// Start the transport provider on the indicated port. + /// \param[in] port The port to start the transport provider on + /// \param[in] serverMode If true, you should allow incoming connections (I don't actually use this anywhere) + /// \return Return true on success, false on failure. + virtual bool Start(unsigned short port, bool serverMode)=0; + + /// Stop the transport provider. You can clear memory and shutdown threads here. + virtual void Stop(void)=0; + + /// Send a null-terminated string to \a systemAddress + /// If your transport method requires particular formatting of the outgoing data (e.g. you don't just send strings) you can do it here + /// and parse it out in Receive(). + /// \param[in] systemAddress The player to send the string to + /// \param[in] data format specifier - same as RAKNET_DEBUG_PRINTF + /// \param[in] ... format specification arguments - same as RAKNET_DEBUG_PRINTF + virtual void Send( SystemAddress systemAddress, const char *data, ... )=0; + + /// Disconnect \a systemAddress . The binary address and port defines the SystemAddress structure. + /// \param[in] systemAddress The player/address to disconnect + virtual void CloseConnection( SystemAddress systemAddress )=0; + + /// Return a string. The string should be allocated and written to Packet::data . + /// The byte length should be written to Packet::length . The player/address should be written to Packet::systemAddress + /// If your transport protocol adds special formatting to the data stream you should parse it out before returning it in the packet + /// and thus only return a string in Packet::data + /// \return The packet structure containing the result of Receive, or 0 if no data is available + virtual Packet* Receive( void )=0; + + /// Deallocate the Packet structure returned by Receive + /// \param[in] The packet to deallocate + virtual void DeallocatePacket( Packet *packet )=0; + + /// If a new system connects to you, you should queue that event and return the systemAddress/address of that player in this function. + /// \return The SystemAddress/address of the system + virtual SystemAddress HasNewIncomingConnection(void)=0; + + /// If a system loses the connection, you should queue that event and return the systemAddress/address of that player in this function. + /// \return The SystemAddress/address of the system + virtual SystemAddress HasLostConnection(void)=0; + + /// Your transport provider can itself have command parsers if the transport layer has user-modifiable features + /// For example, your transport layer may have a password which you want remote users to be able to set or you may want + /// to allow remote users to turn on or off command echo + /// \return 0 if you do not need a command parser - otherwise the desired derivation of CommandParserInterface + virtual CommandParserInterface* GetCommandParser(void)=0; +protected: +}; + +#endif + diff --git a/RakNet/Sources/UDPForwarder.cpp b/RakNet/Sources/UDPForwarder.cpp new file mode 100644 index 0000000..573a555 --- /dev/null +++ b/RakNet/Sources/UDPForwarder.cpp @@ -0,0 +1,457 @@ +#include "UDPForwarder.h" +#include "GetTime.h" +#include "MTUSize.h" +#include "SocketLayer.h" +#include "WSAStartupSingleton.h" +#include "RakSleep.h" + +using namespace RakNet; +static const unsigned short DEFAULT_MAX_FORWARD_ENTRIES=64; + +RAK_THREAD_DECLARATION(UpdateUDPForwarder); + +bool operator<( const DataStructures::MLKeyRef &inputKey, const UDPForwarder::ForwardEntry *cls ) +{ + return inputKey.Get().source < cls->srcAndDest.source || + (inputKey.Get().source == cls->srcAndDest.source && inputKey.Get().destination < cls->srcAndDest.destination); +} +bool operator>( const DataStructures::MLKeyRef &inputKey, const UDPForwarder::ForwardEntry *cls ) +{ + return inputKey.Get().source > cls->srcAndDest.source || + (inputKey.Get().source == cls->srcAndDest.source && inputKey.Get().destination > cls->srcAndDest.destination); +} +bool operator==( const DataStructures::MLKeyRef &inputKey, const UDPForwarder::ForwardEntry *cls ) +{ + return inputKey.Get().source == cls->srcAndDest.source && inputKey.Get().destination == cls->srcAndDest.destination; +} + + +UDPForwarder::ForwardEntry::ForwardEntry() {readSocket=INVALID_SOCKET; timeLastDatagramForwarded=RakNet::GetTime();} +UDPForwarder::ForwardEntry::~ForwardEntry() { + if (readSocket!=INVALID_SOCKET) + closesocket(readSocket); +} + +UDPForwarder::UDPForwarder() +{ +#ifdef _WIN32 + WSAStartupSingleton::AddRef(); +#endif + + maxForwardEntries=DEFAULT_MAX_FORWARD_ENTRIES; + isRunning=false; + threadRunning=false; +} +UDPForwarder::~UDPForwarder() +{ + Shutdown(); + +#ifdef _WIN32 + WSAStartupSingleton::Deref(); +#endif +} +void UDPForwarder::Startup(void) +{ + if (isRunning==true) + return; + + isRunning=true; + threadRunning=false; + +#ifdef UDP_FORWARDER_EXECUTE_THREADED + int errorCode = RakNet::RakThread::Create(UpdateUDPForwarder, this); + if ( errorCode != 0 ) + { + RakAssert(0); + return; + } + + while (threadRunning==false) + RakSleep(30); +#endif +} +void UDPForwarder::Shutdown(void) +{ + if (isRunning==false) + return; + + isRunning=false; + +#ifdef UDP_FORWARDER_EXECUTE_THREADED + while (threadRunning==true) + RakSleep(30); +#endif + + forwardList.ClearPointers(true,__FILE__,__LINE__); +} +void UDPForwarder::Update(void) +{ +#ifndef UDP_FORWARDER_EXECUTE_THREADED + UpdateThreaded(); +#endif + +} +void UDPForwarder::UpdateThreaded(void) +{ + fd_set readFD; + //fd_set exceptionFD; + FD_ZERO(&readFD); +// FD_ZERO(&exceptionFD); + timeval tv; + int selectResult; + tv.tv_sec=0; + tv.tv_usec=0; + + RakNetTime curTime = RakNet::GetTime(); + + SOCKET largestDescriptor=0; + DataStructures::DefaultIndexType i; + + // Remove unused entries + i=0; + while (i < forwardList.GetSize()) + { + if (curTime > forwardList[i]->timeLastDatagramForwarded && // Account for timestamp wrap + curTime > forwardList[i]->timeLastDatagramForwarded+forwardList[i]->timeoutOnNoDataMS) + { + RakNet::OP_DELETE(forwardList[i],__FILE__,__LINE__); + forwardList.RemoveAtIndex(i,__FILE__,__LINE__); + } + else + i++; + } + + if (forwardList.GetSize()==0) + return; + + for (i=0; i < forwardList.GetSize(); i++) + { +#ifdef _MSC_VER +#pragma warning( disable : 4127 ) // warning C4127: conditional expression is constant +#endif + FD_SET(forwardList[i]->readSocket, &readFD); +// FD_SET(forwardList[i]->readSocket, &exceptionFD); + + if (forwardList[i]->readSocket > largestDescriptor) + largestDescriptor = forwardList[i]->readSocket; + } + +#if defined(_PS3) || defined(__PS3__) || defined(SN_TARGET_PS3) + +#else + selectResult=(int) select((int) largestDescriptor+1, &readFD, 0, 0, &tv); +#endif + + char data[ MAXIMUM_MTU_SIZE ]; + sockaddr_in sa; + socklen_t len2; + + if (selectResult > 0) + { + for (i=0; i < forwardList.GetSize(); i++) + { + ForwardEntry *feSource = forwardList[i]; + if (FD_ISSET(feSource->readSocket, &readFD)) + { + const int flag=0; + int receivedDataLen, len=0; + unsigned short portnum=0; + len2 = sizeof( sa ); + sa.sin_family = AF_INET; + receivedDataLen = recvfrom( feSource->readSocket, data, MAXIMUM_MTU_SIZE, flag, ( sockaddr* ) & sa, ( socklen_t* ) & len2 ); + portnum = ntohs( sa.sin_port ); + + if (feSource->srcAndDest.source.binaryAddress==sa.sin_addr.s_addr) + { + if (feSource->updatedSourceAddress==false) + { + feSource->updatedSourceAddress=true; + + if (feSource->srcAndDest.source.port!=portnum) + { + // Remove both source and dest from list, update addresses, and reinsert in order + forwardList.RemoveAtIndex(i,__FILE__,__LINE__); + DataStructures::DefaultIndexType destIndex; + SrcAndDest srcAndDest; + srcAndDest.source=feSource->srcAndDest.destination; + srcAndDest.destination=feSource->srcAndDest.source; + destIndex=forwardList.GetIndexOf(srcAndDest); + ForwardEntry *feDest = forwardList[destIndex]; + forwardList.RemoveAtIndex(destIndex,__FILE__,__LINE__); + feSource->srcAndDest.source.port=portnum; + feDest->srcAndDest.destination.port=portnum; + + // Reinsert to preserve list order + forwardList.Push(feSource,feSource->srcAndDest,__FILE__,__LINE__); + forwardList.Push(feDest,feDest->srcAndDest,__FILE__,__LINE__); + + feSource->timeLastDatagramForwarded=curTime; + feDest->timeLastDatagramForwarded=curTime; + } + } + + if (feSource->srcAndDest.source.port==portnum) + { + // Forward to destination + len=0; + sockaddr_in saOut; + saOut.sin_port = htons( feSource->srcAndDest.destination.port ); // User port + saOut.sin_addr.s_addr = feSource->srcAndDest.destination.binaryAddress; + saOut.sin_family = AF_INET; + do + { + len = sendto( feSource->writeSocket, data, receivedDataLen, 0, ( const sockaddr* ) & saOut, sizeof( saOut ) ); + } + while ( len == 0 ); + + feSource->timeLastDatagramForwarded=curTime; + } + } + } + } + } +} +void UDPForwarder::SetMaxForwardEntries(unsigned short maxEntries) +{ + RakAssert(maxEntries>0 && maxEntries<65535/2); + maxForwardEntries=maxEntries; +} +int UDPForwarder::GetMaxForwardEntries(void) const +{ + return maxForwardEntries; +} +int UDPForwarder::GetUsedForwardEntries(void) const +{ + return (int) forwardList.GetSize(); +} +unsigned short UDPForwarder::AddForwardingEntry(SrcAndDest srcAndDest, RakNetTimeMS timeoutOnNoDataMS, const char *forceHostAddress) +{ + DataStructures::DefaultIndexType insertionIndex; + insertionIndex = forwardList.GetInsertionIndex(srcAndDest); + if (insertionIndex!=(DataStructures::DefaultIndexType)-1) + { + int sock_opt; + sockaddr_in listenerSocketAddress; + listenerSocketAddress.sin_port = 0; + ForwardEntry *fe = RakNet::OP_NEW(__FILE__,__LINE__); + fe->srcAndDest=srcAndDest; + fe->timeoutOnNoDataMS=timeoutOnNoDataMS; + fe->readSocket = socket( AF_INET, SOCK_DGRAM, 0 ); + fe->updatedSourceAddress=false; + + //printf("Made socket %i\n", fe->readSocket); + + // This doubles the max throughput rate + sock_opt=1024*256; + setsockopt(fe->readSocket, SOL_SOCKET, SO_RCVBUF, ( char * ) & sock_opt, sizeof ( sock_opt ) ); + + // Immediate hard close. Don't linger the readSocket, or recreating the readSocket quickly on Vista fails. + sock_opt=0; + setsockopt(fe->readSocket, SOL_SOCKET, SO_LINGER, ( char * ) & sock_opt, sizeof ( sock_opt ) ); + + listenerSocketAddress.sin_family = AF_INET; + + if (forceHostAddress && forceHostAddress[0]) + { + listenerSocketAddress.sin_addr.s_addr = inet_addr( forceHostAddress ); + } + else + { + listenerSocketAddress.sin_addr.s_addr = INADDR_ANY; + } + + int ret = bind( fe->readSocket, ( struct sockaddr * ) & listenerSocketAddress, sizeof( listenerSocketAddress ) ); + if (ret==-1) + { + RakNet::OP_DELETE(fe,__FILE__,__LINE__); + return 0; + } + + DataStructures::DefaultIndexType oldSize = forwardList.GetSize(); + forwardList.InsertAtIndex(fe,insertionIndex,__FILE__,__LINE__); + RakAssert(forwardList.GetIndexOf(fe->srcAndDest)!=(unsigned int) -1); + RakAssert(forwardList.GetSize()==oldSize+1); + return SocketLayer::GetLocalPort ( fe->readSocket ); + } + else + { + // Takeover existing + DataStructures::DefaultIndexType existingSrcIndex,existingDstIndex; + SrcAndDest dest; + ForwardEntry *feSrc, *feDst; + dest.destination=srcAndDest.source; + dest.source=srcAndDest.destination; + existingSrcIndex = forwardList.GetIndexOf(srcAndDest); + feSrc=forwardList[existingSrcIndex]; + existingDstIndex = forwardList.GetIndexOf(dest); + feSrc->timeLastDatagramForwarded=RakNet::GetTime(); + if (existingDstIndex==(DataStructures::DefaultIndexType)-1) + { + feDst=0; + } + else + { + // Refresh destination so communication isn't unidirectional + feDst=forwardList[existingDstIndex]; + feDst->timeLastDatagramForwarded=feSrc->timeLastDatagramForwarded; + } + + + return SocketLayer::GetLocalPort ( feSrc->readSocket ); + } +} +UDPForwarderResult UDPForwarder::StartForwarding(SystemAddress source, SystemAddress destination, RakNetTimeMS timeoutOnNoDataMS, const char *forceHostAddress, + unsigned short *srcToDestPort, unsigned short *destToSourcePort, SOCKET *srcToDestSocket, SOCKET *destToSourceSocket) +{ + // Invalid parameters? + if (timeoutOnNoDataMS == 0 || timeoutOnNoDataMS > UDP_FORWARDER_MAXIMUM_TIMEOUT || source==UNASSIGNED_SYSTEM_ADDRESS || destination==UNASSIGNED_SYSTEM_ADDRESS) + return UDPFORWARDER_INVALID_PARAMETERS; + +#ifdef UDP_FORWARDER_EXECUTE_THREADED + ThreadOperation threadOperation; + threadOperation.source=source; + threadOperation.destination=destination; + threadOperation.timeoutOnNoDataMS=timeoutOnNoDataMS; + threadOperation.forceHostAddress=forceHostAddress; + threadOperation.operation=ThreadOperation::TO_START_FORWARDING; + threadOperationIncomingMutex.Lock(); + threadOperationIncomingQueue.Push(threadOperation, __FILE__, __LINE__ ); + threadOperationIncomingMutex.Unlock(); + + while (1) + { + RakSleep(0); + threadOperationOutgoingMutex.Lock(); + if (threadOperationOutgoingQueue.Size()!=0) + { + threadOperation=threadOperationOutgoingQueue.Pop(); + threadOperationOutgoingMutex.Unlock(); + if (srcToDestPort) + *srcToDestPort=threadOperation.srcToDestPort; + if (destToSourcePort) + *destToSourcePort=threadOperation.destToSourcePort; + if (srcToDestSocket) + *srcToDestSocket=threadOperation.srcToDestSocket; + if (destToSourceSocket) + *destToSourceSocket=threadOperation.destToSourceSocket; + return threadOperation.result; + } + threadOperationOutgoingMutex.Unlock(); + + } +#else + return StartForwardingThreaded(source, destination, timeoutOnNoDataMS, forceHostAddress, srcToDestPort, destToSourcePort, srcToDestSocket, destToSourceSocket); +#endif + +} +UDPForwarderResult UDPForwarder::StartForwardingThreaded(SystemAddress source, SystemAddress destination, RakNetTimeMS timeoutOnNoDataMS, const char *forceHostAddress, + unsigned short *srcToDestPort, unsigned short *destToSourcePort, SOCKET *srcToDestSocket, SOCKET *destToSourceSocket) +{ + SrcAndDest srcAndDest; + srcAndDest.destination=destination; + srcAndDest.source=source; + SrcAndDest destAndSrc; + destAndSrc.destination=source; + destAndSrc.source=destination; + +// Just takeover existing if this happens +// if (forwardList.GetIndexOf(srcAndDest) != (DataStructures::DefaultIndexType) -1) +// return UDPFORWARDER_FORWARDING_ALREADY_EXISTS; + + *srcToDestPort = AddForwardingEntry(srcAndDest, timeoutOnNoDataMS, forceHostAddress); + + if (*srcToDestPort==0) + return UDPFORWARDER_NO_SOCKETS; + + *destToSourcePort = AddForwardingEntry(destAndSrc, timeoutOnNoDataMS, forceHostAddress); + + if (*destToSourcePort==0) + return UDPFORWARDER_NO_SOCKETS; + + DataStructures::DefaultIndexType idxSrcAndDest, idxDestAndSrc; + idxSrcAndDest = forwardList.GetIndexOf(srcAndDest); + idxDestAndSrc = forwardList.GetIndexOf(destAndSrc); + forwardList[idxSrcAndDest]->writeSocket=forwardList[idxDestAndSrc]->readSocket; + forwardList[idxDestAndSrc]->writeSocket=forwardList[idxSrcAndDest]->readSocket; + if (*srcToDestSocket) + *srcToDestSocket=forwardList[idxSrcAndDest]->writeSocket; + if (*destToSourceSocket) + *destToSourceSocket=forwardList[idxSrcAndDest]->readSocket; + + return UDPFORWARDER_SUCCESS; +} +void UDPForwarder::StopForwarding(SystemAddress source, SystemAddress destination) +{ +#ifdef UDP_FORWARDER_EXECUTE_THREADED + ThreadOperation threadOperation; + threadOperation.source=source; + threadOperation.destination=destination; + threadOperation.operation=ThreadOperation::TO_STOP_FORWARDING; + threadOperationIncomingMutex.Lock(); + threadOperationIncomingQueue.Push(threadOperation, __FILE__, __LINE__ ); + threadOperationIncomingMutex.Unlock(); +#else + StopForwardingThreaded(source, destination); +#endif +} +void UDPForwarder::StopForwardingThreaded(SystemAddress source, SystemAddress destination) +{ + + SrcAndDest srcAndDest; + srcAndDest.destination=destination; + srcAndDest.source=source; + DataStructures::DefaultIndexType idx = forwardList.GetIndexOf(srcAndDest); + if (idx!=(DataStructures::DefaultIndexType)-1) + { + RakNet::OP_DELETE(forwardList[idx],__FILE__,__LINE__); + forwardList.RemoveAtIndex(idx,__FILE__,__LINE__); + } + + srcAndDest.destination=source; + srcAndDest.source=destination; + + idx = forwardList.GetIndexOf(srcAndDest); + if (idx!=(DataStructures::DefaultIndexType)-1) + { + RakNet::OP_DELETE(forwardList[idx],__FILE__,__LINE__); + forwardList.RemoveAtIndex(idx,__FILE__,__LINE__); + } +} +#ifdef UDP_FORWARDER_EXECUTE_THREADED +RAK_THREAD_DECLARATION(UpdateUDPForwarder) +{ + UDPForwarder * udpForwarder = ( UDPForwarder * ) arguments; + udpForwarder->threadRunning=true; + UDPForwarder::ThreadOperation threadOperation; + while (udpForwarder->isRunning) + { + udpForwarder->threadOperationIncomingMutex.Lock(); + while (udpForwarder->threadOperationIncomingQueue.Size()) + { + threadOperation=udpForwarder->threadOperationIncomingQueue.Pop(); + udpForwarder->threadOperationIncomingMutex.Unlock(); + if (threadOperation.operation==UDPForwarder::ThreadOperation::TO_START_FORWARDING) + { + threadOperation.result=udpForwarder->StartForwardingThreaded(threadOperation.source, threadOperation.destination, threadOperation.timeoutOnNoDataMS, + threadOperation.forceHostAddress, &threadOperation.srcToDestPort, &threadOperation.destToSourcePort, &threadOperation.srcToDestSocket, &threadOperation.destToSourceSocket); + udpForwarder->threadOperationOutgoingMutex.Lock(); + udpForwarder->threadOperationOutgoingQueue.Push(threadOperation, __FILE__, __LINE__ ); + udpForwarder->threadOperationOutgoingMutex.Unlock(); + } + else + { + udpForwarder->StopForwardingThreaded(threadOperation.source, threadOperation.destination); + } + + + udpForwarder->threadOperationIncomingMutex.Lock(); + } + udpForwarder->threadOperationIncomingMutex.Unlock(); + + udpForwarder->UpdateThreaded(); + RakSleep(0); + } + udpForwarder->threadRunning=false; + return 0; +} +#endif diff --git a/RakNet/Sources/UDPForwarder.h b/RakNet/Sources/UDPForwarder.h new file mode 100644 index 0000000..1116c4f --- /dev/null +++ b/RakNet/Sources/UDPForwarder.h @@ -0,0 +1,147 @@ +/// \file +/// \brief Forwards UDP datagrams. Independent of RakNet's protocol. +/// +/// This file is part of RakNet Copyright 2003 Jenkins Software LLC +/// +/// Usage of RakNet is subject to the appropriate license agreement. + + +#ifndef __UDP_FORWARDER_H +#define __UDP_FORWARDER_H + +#include "Export.h" +#include "DS_Multilist.h" +#include "RakNetTypes.h" +#include "SocketIncludes.h" +#include "UDPProxyCommon.h" +#include "SimpleMutex.h" +#include "RakString.h" +#include "RakThread.h" +#include "DS_Queue.h" + +#define UDP_FORWARDER_EXECUTE_THREADED + +namespace RakNet +{ + +enum UDPForwarderResult +{ + UDPFORWARDER_FORWARDING_ALREADY_EXISTS, + UDPFORWARDER_NO_SOCKETS, + UDPFORWARDER_INVALID_PARAMETERS, + UDPFORWARDER_SUCCESS, + +}; + +/// \brief Forwards UDP datagrams. Independent of RakNet's protocol. +/// \ingroup NAT_PUNCHTHROUGH_GROUP +class RAK_DLL_EXPORT UDPForwarder +{ +public: + UDPForwarder(); + ~UDPForwarder(); + + /// Starts the system. + /// Required to call before StartForwarding + void Startup(void); + + /// Stops the system, and frees all sockets + void Shutdown(void); + + /// Call on a regular basis, unless using UDP_FORWARDER_EXECUTE_THREADED. + /// Will call select() on all sockets and forward messages. + void Update(void); + + /// Sets the maximum number of forwarding entries allowed + /// Set according to your available bandwidth and the estimated average bandwidth per forwarded address. + /// A single connection requires 2 entries, as connections are bi-directional. + /// \param[in] maxEntries The maximum number of simultaneous forwarding entries. Defaults to 64 (32 connections) + void SetMaxForwardEntries(unsigned short maxEntries); + + /// \return The \a maxEntries parameter passed to SetMaxForwardEntries(), or the default if it was never called + int GetMaxForwardEntries(void) const; + + /// \note Each call to StartForwarding uses up two forwarding entries, since communications are bidirectional + /// \return How many entries have been used + int GetUsedForwardEntries(void) const; + + /// Forwards datagrams from source to destination, and vice-versa + /// Does nothing if this forward entry already exists via a previous call + /// \pre Call Startup() + /// \note RakNet's protocol will ensure a message is sent at least every 5 seconds, so if routing RakNet messages, it is a reasonable value for timeoutOnNoDataMS, plus an extra few seconds for latency + /// \param[in] source The source IP and port + /// \param[in] destination Where to forward to (and vice-versa) + /// \param[in] timeoutOnNoDataMS If no messages are forwarded for this many MS, then automatically remove this entry. Currently hardcoded to UDP_FORWARDER_MAXIMUM_TIMEOUT (else the call fails) + /// \param[in] forceHostAddress Force binding on a particular address. 0 to use any. + /// \param[out] srcToDestPort Port to go from source to destination + /// \param[out] destToSourcePort Port to go from destination to source + /// \param[out] srcToDestSocket Socket to go from source to destination + /// \param[out] destToSourceSocket Socket to go from destination to source + /// \return UDPForwarderResult + UDPForwarderResult StartForwarding(SystemAddress source, SystemAddress destination, RakNetTimeMS timeoutOnNoDataMS, const char *forceHostAddress, + unsigned short *srcToDestPort, unsigned short *destToSourcePort, SOCKET *srcToDestSocket, SOCKET *destToSourceSocket); + + /// No longer forward datagrams from source to destination + /// \param[in] source The source IP and port + /// \param[in] destination Where to forward to + void StopForwarding(SystemAddress source, SystemAddress destination); + + struct SrcAndDest + { + SystemAddress source; + SystemAddress destination; + }; + + struct ForwardEntry + { + ForwardEntry(); + ~ForwardEntry(); + SrcAndDest srcAndDest; + RakNetTimeMS timeLastDatagramForwarded; + SOCKET readSocket; + SOCKET writeSocket; + RakNetTimeMS timeoutOnNoDataMS; + bool updatedSourceAddress; + }; + +#ifdef UDP_FORWARDER_EXECUTE_THREADED + struct ThreadOperation + { + enum { + TO_NONE, + TO_START_FORWARDING, + TO_STOP_FORWARDING, + } operation; + + SystemAddress source; + SystemAddress destination; + RakNetTimeMS timeoutOnNoDataMS; + RakNet::RakString forceHostAddress; + unsigned short srcToDestPort; + unsigned short destToSourcePort; + SOCKET srcToDestSocket; + SOCKET destToSourceSocket; + UDPForwarderResult result; + }; + SimpleMutex threadOperationIncomingMutex,threadOperationOutgoingMutex; + DataStructures::Queue threadOperationIncomingQueue; + DataStructures::Queue threadOperationOutgoingQueue; +#endif + void UpdateThreaded(void); + UDPForwarderResult StartForwardingThreaded(SystemAddress source, SystemAddress destination, RakNetTimeMS timeoutOnNoDataMS, const char *forceHostAddress, + unsigned short *srcToDestPort, unsigned short *destToSourcePort, SOCKET *srcToDestSocket, SOCKET *destToSourceSocket); + void StopForwardingThreaded(SystemAddress source, SystemAddress destination); + + DataStructures::Multilist forwardList; + unsigned short maxForwardEntries; + + unsigned short AddForwardingEntry(SrcAndDest srcAndDest, RakNetTimeMS timeoutOnNoDataMS, const char *forceHostAddress); + + + bool isRunning, threadRunning; + +}; + +} // End namespace + +#endif diff --git a/RakNet/Sources/UDPProxyClient.cpp b/RakNet/Sources/UDPProxyClient.cpp new file mode 100644 index 0000000..f9fb8f2 --- /dev/null +++ b/RakNet/Sources/UDPProxyClient.cpp @@ -0,0 +1,296 @@ +#include "NativeFeatureIncludes.h" +#if _RAKNET_SUPPORT_UDPProxyClient==1 + +#include "UDPProxyClient.h" +#include "BitStream.h" +#include "UDPProxyCommon.h" +#include "RakPeerInterface.h" +#include "MessageIdentifiers.h" +#include "GetTime.h" + +using namespace RakNet; +static const int DEFAULT_UNRESPONSIVE_PING_TIME=1000; + +bool operator<( const DataStructures::MLKeyRef &inputKey, const UDPProxyClient::ServerWithPing &cls ) {return inputKey.Get().serverAddress < cls.serverAddress;} +bool operator>( const DataStructures::MLKeyRef &inputKey, const UDPProxyClient::ServerWithPing &cls ) {return inputKey.Get().serverAddress > cls.serverAddress;} +bool operator==( const DataStructures::MLKeyRef &inputKey, const UDPProxyClient::ServerWithPing &cls ) {return inputKey.Get().serverAddress == cls.serverAddress;} + + +UDPProxyClient::UDPProxyClient() +{ + resultHandler=0; +} +UDPProxyClient::~UDPProxyClient() +{ + Clear(); +} +void UDPProxyClient::SetResultHandler(UDPProxyClientResultHandler *rh) +{ + resultHandler=rh; +} +bool UDPProxyClient::RequestForwarding(SystemAddress proxyCoordinator, SystemAddress sourceAddress, RakNetGUID targetGuid, RakNetTimeMS timeoutOnNoDataMS, RakNet::BitStream *serverSelectionBitstream) +{ + if (rakPeerInterface->IsConnected(proxyCoordinator,false,false)==false) + return false; + + // Pretty much a bug not to set the result handler, as otherwise you won't know if the operation succeeed or not + RakAssert(resultHandler!=0); + if (resultHandler==0) + return false; + + BitStream outgoingBs; + outgoingBs.Write((MessageID)ID_UDP_PROXY_GENERAL); + outgoingBs.Write((MessageID)ID_UDP_PROXY_FORWARDING_REQUEST_FROM_CLIENT_TO_COORDINATOR); + outgoingBs.Write(sourceAddress); + outgoingBs.Write(false); + outgoingBs.Write(targetGuid); + outgoingBs.Write(timeoutOnNoDataMS); + if (serverSelectionBitstream && serverSelectionBitstream->GetNumberOfBitsUsed()>0) + { + outgoingBs.Write(true); + outgoingBs.Write(serverSelectionBitstream); + } + else + { + outgoingBs.Write(false); + } + rakPeerInterface->Send(&outgoingBs, MEDIUM_PRIORITY, RELIABLE_ORDERED, 0, proxyCoordinator, false); + + return true; +} +bool UDPProxyClient::RequestForwarding(SystemAddress proxyCoordinator, SystemAddress sourceAddress, SystemAddress targetAddressAsSeenFromCoordinator, RakNetTimeMS timeoutOnNoDataMS, RakNet::BitStream *serverSelectionBitstream) +{ + if (rakPeerInterface->IsConnected(proxyCoordinator,false,false)==false) + return false; + + // Pretty much a bug not to set the result handler, as otherwise you won't know if the operation succeeed or not + RakAssert(resultHandler!=0); + if (resultHandler==0) + return false; + + BitStream outgoingBs; + outgoingBs.Write((MessageID)ID_UDP_PROXY_GENERAL); + outgoingBs.Write((MessageID)ID_UDP_PROXY_FORWARDING_REQUEST_FROM_CLIENT_TO_COORDINATOR); + outgoingBs.Write(sourceAddress); + outgoingBs.Write(true); + outgoingBs.Write(targetAddressAsSeenFromCoordinator); + outgoingBs.Write(timeoutOnNoDataMS); + if (serverSelectionBitstream && serverSelectionBitstream->GetNumberOfBitsUsed()>0) + { + outgoingBs.Write(true); + outgoingBs.Write(serverSelectionBitstream); + } + else + { + outgoingBs.Write(false); + } + rakPeerInterface->Send(&outgoingBs, MEDIUM_PRIORITY, RELIABLE_ORDERED, 0, proxyCoordinator, false); + + return true; +} +void UDPProxyClient::Update(void) +{ + DataStructures::DefaultIndexType idx1=0; + while (idx1 < pingServerGroups.GetSize()) + { + PingServerGroup *psg = pingServerGroups[idx1]; + + if (psg->serversToPing.GetSize() > 0 && + RakNet::GetTime() > psg->startPingTime+DEFAULT_UNRESPONSIVE_PING_TIME) + { + // If they didn't reply within DEFAULT_UNRESPONSIVE_PING_TIME, just give up on them + psg->SendPingedServersToCoordinator(rakPeerInterface); + + RakNet::OP_DELETE(psg,__FILE__,__LINE__); + pingServerGroups.RemoveAtIndex(idx1, __FILE__, __LINE__ ); + } + else + idx1++; + } + +} +PluginReceiveResult UDPProxyClient::OnReceive(Packet *packet) +{ + if (packet->data[0]==ID_PONG) + { + DataStructures::DefaultIndexType idx1, idx2; + PingServerGroup *psg; + for (idx1=0; idx1 < pingServerGroups.GetSize(); idx1++) + { + psg = pingServerGroups[idx1]; + for (idx2=0; idx2 < psg->serversToPing.GetSize(); idx2++) + { + if (psg->serversToPing[idx2].serverAddress==packet->systemAddress) + { + RakNet::BitStream bsIn(packet->data,packet->length,false); + bsIn.IgnoreBytes(sizeof(MessageID)); + RakNetTime sentTime; + bsIn.Read(sentTime); + RakNetTime curTime=RakNet::GetTime(); + int ping; + if (curTime>sentTime) + ping=(int) (curTime-sentTime); + else + ping=0; + psg->serversToPing[idx2].ping=(unsigned short) ping; + + // If all servers to ping are now pinged, reply to coordinator + if (psg->AreAllServersPinged()) + { + psg->SendPingedServersToCoordinator(rakPeerInterface); + RakNet::OP_DELETE(psg,__FILE__,__LINE__); + pingServerGroups.RemoveAtIndex(idx1, __FILE__, __LINE__ ); + } + + return RR_STOP_PROCESSING_AND_DEALLOCATE; + } + } + + } + } + else if (packet->data[0]==ID_UDP_PROXY_GENERAL && packet->length>1) + { + switch (packet->data[1]) + { + case ID_UDP_PROXY_PING_SERVERS_FROM_COORDINATOR_TO_CLIENT: + { + OnPingServers(packet); + } + break; + case ID_UDP_PROXY_FORWARDING_SUCCEEDED: + case ID_UDP_PROXY_ALL_SERVERS_BUSY: + case ID_UDP_PROXY_IN_PROGRESS: + case ID_UDP_PROXY_NO_SERVERS_ONLINE: + case ID_UDP_PROXY_RECIPIENT_GUID_NOT_CONNECTED_TO_COORDINATOR: + case ID_UDP_PROXY_FORWARDING_NOTIFICATION: + { + SystemAddress senderAddress, targetAddress; + RakNet::BitStream incomingBs(packet->data, packet->length, false); + incomingBs.IgnoreBytes(sizeof(MessageID)*2); + incomingBs.Read(senderAddress); + incomingBs.Read(targetAddress); + + switch (packet->data[1]) + { + case ID_UDP_PROXY_FORWARDING_NOTIFICATION: + case ID_UDP_PROXY_FORWARDING_SUCCEEDED: + { + unsigned short srcToDestPort; + unsigned short destToSourcePort; + RakNet::RakString serverIP; + incomingBs.Read(serverIP); + incomingBs.Read(srcToDestPort); + incomingBs.Read(destToSourcePort); + if (packet->data[1]==ID_UDP_PROXY_FORWARDING_SUCCEEDED) + { + if (resultHandler) + resultHandler->OnForwardingSuccess(serverIP.C_String(), srcToDestPort, destToSourcePort, packet->systemAddress, senderAddress, targetAddress, this); + } + else + { + // Send a datagram to the proxy, so if we are behind a router, that router adds an entry to the routing table. + // Otherwise the router would block the incoming datagrams from source + // It doesn't matter if the message actually arrives as long as it goes through the router + rakPeerInterface->Ping(serverIP.C_String(), destToSourcePort, false); + + if (resultHandler) + resultHandler->OnForwardingNotification(serverIP.C_String(), srcToDestPort, destToSourcePort, packet->systemAddress, senderAddress, targetAddress, this); + } + } + break; + case ID_UDP_PROXY_ALL_SERVERS_BUSY: + if (resultHandler) + resultHandler->OnAllServersBusy(packet->systemAddress, senderAddress, targetAddress, this); + break; + case ID_UDP_PROXY_IN_PROGRESS: + if (resultHandler) + resultHandler->OnForwardingInProgress(packet->systemAddress, senderAddress, targetAddress, this); + break; + case ID_UDP_PROXY_NO_SERVERS_ONLINE: + if (resultHandler) + resultHandler->OnNoServersOnline(packet->systemAddress, senderAddress, targetAddress, this); + break; + case ID_UDP_PROXY_RECIPIENT_GUID_NOT_CONNECTED_TO_COORDINATOR: + { + RakNetGUID targetGuid; + incomingBs.Read(targetGuid); + if (resultHandler) + resultHandler->OnRecipientNotConnected(packet->systemAddress, senderAddress, targetAddress, targetGuid, this); + break; + } + } + + } + return RR_STOP_PROCESSING_AND_DEALLOCATE; + } + } + return RR_CONTINUE_PROCESSING; +} +void UDPProxyClient::OnRakPeerShutdown(void) +{ + Clear(); +} +void UDPProxyClient::OnPingServers(Packet *packet) +{ + RakNet::BitStream incomingBs(packet->data, packet->length, false); + incomingBs.IgnoreBytes(2); + + PingServerGroup *psg = RakNet::OP_NEW(__FILE__,__LINE__); + + ServerWithPing swp; + incomingBs.Read(psg->sata.senderClientAddress); + incomingBs.Read(psg->sata.targetClientAddress); + psg->startPingTime=RakNet::GetTime(); + psg->coordinatorAddressForPings=packet->systemAddress; + unsigned short serverListSize; + incomingBs.Read(serverListSize); + SystemAddress serverAddress; + unsigned short serverListIndex; + char ipStr[64]; + for (serverListIndex=0; serverListIndexserversToPing.Push(swp, __FILE__, __LINE__ ); + swp.serverAddress.ToString(false,ipStr); + rakPeerInterface->Ping(ipStr,swp.serverAddress.port,false,0); + } + pingServerGroups.Push(psg,__FILE__,__LINE__); +} + +bool UDPProxyClient::PingServerGroup::AreAllServersPinged(void) const +{ + DataStructures::DefaultIndexType serversToPingIndex; + for (serversToPingIndex=0; serversToPingIndex < serversToPing.GetSize(); serversToPingIndex++) + { + if (serversToPing[serversToPingIndex].ping==DEFAULT_UNRESPONSIVE_PING_TIME) + return false; + } + return true; +} + +void UDPProxyClient::PingServerGroup::SendPingedServersToCoordinator(RakPeerInterface *rakPeerInterface) +{ + BitStream outgoingBs; + outgoingBs.Write((MessageID)ID_UDP_PROXY_GENERAL); + outgoingBs.Write((MessageID)ID_UDP_PROXY_PING_SERVERS_REPLY_FROM_CLIENT_TO_COORDINATOR); + outgoingBs.Write(sata.senderClientAddress); + outgoingBs.Write(sata.targetClientAddress); + unsigned short serversToPingSize = (unsigned short) serversToPing.GetSize(); + outgoingBs.Write(serversToPingSize); + DataStructures::DefaultIndexType serversToPingIndex; + for (serversToPingIndex=0; serversToPingIndex < serversToPingSize; serversToPingIndex++) + { + outgoingBs.Write(serversToPing[serversToPingIndex].serverAddress); + outgoingBs.Write(serversToPing[serversToPingIndex].ping); + } + rakPeerInterface->Send(&outgoingBs, MEDIUM_PRIORITY, RELIABLE_ORDERED, 0, coordinatorAddressForPings, false); +} +void UDPProxyClient::Clear(void) +{ + pingServerGroups.ClearPointers(false,__FILE__,__LINE__); +} + + +#endif // _RAKNET_SUPPORT_* + diff --git a/RakNet/Sources/UDPProxyClient.h b/RakNet/Sources/UDPProxyClient.h new file mode 100644 index 0000000..953f7b9 --- /dev/null +++ b/RakNet/Sources/UDPProxyClient.h @@ -0,0 +1,170 @@ +/// \file +/// \brief A RakNet plugin performing networking to communicate with UDPProxyCoordinator. Ultimately used to tell UDPProxyServer to forward UDP packets. +/// +/// 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. + +#include "NativeFeatureIncludes.h" +#if _RAKNET_SUPPORT_UDPProxyClient==1 + +#ifndef __UDP_PROXY_CLIENT_H +#define __UDP_PROXY_CLIENT_H + +#include "Export.h" +#include "DS_Multilist.h" +#include "RakNetTypes.h" +#include "PluginInterface2.h" + +/// \defgroup UDP_PROXY_GROUP UDPProxy +/// \brief Forwards UDP datagrams from one system to another. Protocol independent +/// \details Used when NatPunchthroughClient fails +/// \ingroup PLUGINS_GROUP + +namespace RakNet +{ +class UDPProxyClient; + +/// Callback to handle results of calling UDPProxyClient::RequestForwarding() +/// \ingroup UDP_PROXY_GROUP +struct UDPProxyClientResultHandler +{ + UDPProxyClientResultHandler() {} + virtual ~UDPProxyClientResultHandler() {} + + /// Called when our forwarding request was completed. We can now connect to \a targetAddress by using \a proxyAddress instead + /// \param[out] proxyIPAddress IP Address of the proxy server, which will forward messages to targetAddress + /// \param[out] proxyPort Remote port to use on the proxy server, which will forward messages to targetAddress + /// \param[out] reverseProxyPort The port the destination will reply back to us on. You may not need this. + /// \param[out] proxyCoordinator \a proxyCoordinator parameter originally passed to UDPProxyClient::RequestForwarding + /// \param[out] sourceAddress \a sourceAddress parameter passed to UDPProxyClient::RequestForwarding. If it was UNASSIGNED_SYSTEM_ADDRESS, it is now our external IP address. + /// \param[out] targetAddress \a targetAddress parameter originally passed to UDPProxyClient::RequestForwarding + /// \param[out] proxyClient The plugin that is calling this callback + virtual void OnForwardingSuccess(const char *proxyIPAddress, unsigned short proxyPort, unsigned short reverseProxyPort, + SystemAddress proxyCoordinator, SystemAddress sourceAddress, SystemAddress targetAddress, RakNet::UDPProxyClient *proxyClientPlugin)=0; + + /// Called when another system has setup forwarding, with our system as the target address. + /// Plugin automatically sends a datagram to proxyIPAddress before this callback, to open our router if necessary. + /// \param[out] proxyIPAddress IP Address of the proxy server, which will forward messages to targetAddress + /// \param[out] proxyPort Remote port to use on the proxy server, which will forward messages to targetAddress + /// \param[out] reverseProxyPort The port the destination will reply back to us on. You may not need this. + /// \param[out] proxyCoordinator \a proxyCoordinator parameter originally passed to UDPProxyClient::RequestForwarding + /// \param[out] sourceAddress \a sourceAddress parameter passed to UDPProxyClient::RequestForwarding. This is originating source IP address of the remote system that will be sending to us. + /// \param[out] targetAddress \a targetAddress parameter originally passed to UDPProxyClient::RequestForwarding. This is our external IP address. + /// \param[out] proxyClient The plugin that is calling this callback + virtual void OnForwardingNotification(const char *proxyIPAddress, unsigned short proxyPort, unsigned short reverseProxyPort, + SystemAddress proxyCoordinator, SystemAddress sourceAddress, SystemAddress targetAddress, RakNet::UDPProxyClient *proxyClientPlugin)=0; + + /// Called when our forwarding request failed, because no UDPProxyServers are connected to UDPProxyCoordinator + /// \param[out] proxyCoordinator \a proxyCoordinator parameter originally passed to UDPProxyClient::RequestForwarding + /// \param[out] sourceAddress \a sourceAddress parameter passed to UDPProxyClient::RequestForwarding. If it was UNASSIGNED_SYSTEM_ADDRESS, it is now our external IP address. + /// \param[out] targetAddress \a targetAddress parameter originally passed to UDPProxyClient::RequestForwarding + /// \param[out] proxyClient The plugin that is calling this callback + virtual void OnNoServersOnline(SystemAddress proxyCoordinator, SystemAddress sourceAddress, SystemAddress targetAddress, RakNet::UDPProxyClient *proxyClientPlugin)=0; + + /// Called when our forwarding request failed, because no UDPProxyServers are connected to UDPProxyCoordinator + /// \param[out] proxyCoordinator \a proxyCoordinator parameter originally passed to UDPProxyClient::RequestForwarding + /// \param[out] sourceAddress \a sourceAddress parameter passed to UDPProxyClient::RequestForwarding. If it was UNASSIGNED_SYSTEM_ADDRESS, it is now our external IP address. + /// \param[out] targetAddress \a targetAddress parameter originally passed to UDPProxyClient::RequestForwarding + /// \param[out] targetGuid \a targetGuid parameter originally passed to UDPProxyClient::RequestForwarding + /// \param[out] proxyClient The plugin that is calling this callback + virtual void OnRecipientNotConnected(SystemAddress proxyCoordinator, SystemAddress sourceAddress, SystemAddress targetAddress, RakNetGUID targetGuid, RakNet::UDPProxyClient *proxyClientPlugin)=0; + + /// Called when our forwarding request failed, because all UDPProxyServers that are connected to UDPProxyCoordinator are at their capacity + /// Either add more servers, or increase capacity via UDPForwarder::SetMaxForwardEntries() + /// \param[out] proxyCoordinator \a proxyCoordinator parameter originally passed to UDPProxyClient::RequestForwarding + /// \param[out] sourceAddress \a sourceAddress parameter passed to UDPProxyClient::RequestForwarding. If it was UNASSIGNED_SYSTEM_ADDRESS, it is now our external IP address. + /// \param[out] targetAddress \a targetAddress parameter originally passed to UDPProxyClient::RequestForwarding + /// \param[out] proxyClient The plugin that is calling this callback + virtual void OnAllServersBusy(SystemAddress proxyCoordinator, SystemAddress sourceAddress, SystemAddress targetAddress, RakNet::UDPProxyClient *proxyClientPlugin)=0; + + /// Called when our forwarding request is already in progress on the \a proxyCoordinator. + /// This can be ignored, but indicates an unneeded second request + /// \param[out] proxyCoordinator \a proxyCoordinator parameter originally passed to UDPProxyClient::RequestForwarding + /// \param[out] sourceAddress \a sourceAddress parameter passed to UDPProxyClient::RequestForwarding. If it was UNASSIGNED_SYSTEM_ADDRESS, it is now our external IP address. + /// \param[out] targetAddress \a targetAddress parameter originally passed to UDPProxyClient::RequestForwarding + /// \param[out] proxyClient The plugin that is calling this callback + virtual void OnForwardingInProgress(SystemAddress proxyCoordinator, SystemAddress sourceAddress, SystemAddress targetAddress, RakNet::UDPProxyClient *proxyClientPlugin)=0; +}; + + +/// \brief Communicates with UDPProxyCoordinator, in order to find a UDPProxyServer to forward our datagrams. +/// \details When NAT Punchthrough fails, it is possible to use a non-NAT system to forward messages from us to the recipient, and vice-versa.
      +/// The class to forward messages is UDPForwarder, and it is triggered over the network via the UDPProxyServer plugin.
      +/// The UDPProxyClient connects to UDPProxyCoordinator to get a list of servers running UDPProxyServer, and the coordinator will relay our forwarding request +/// \sa NatPunchthroughServer +/// \sa NatPunchthroughClient +/// \ingroup UDP_PROXY_GROUP +class RAK_DLL_EXPORT UDPProxyClient : public PluginInterface2 +{ +public: + UDPProxyClient(); + ~UDPProxyClient(); + + /// Receives the results of calling RequestForwarding() + /// Set before calling RequestForwarding or you won't know what happened + /// \param[in] resultHandler + void SetResultHandler(UDPProxyClientResultHandler *rh); + + /// Sends a request to proxyCoordinator to find a server and have that server setup UDPForwarder::StartForwarding() on our address to \a targetAddressAsSeenFromCoordinator + /// The forwarded datagrams can be from any UDP source, not just RakNet + /// \pre Must be connected to \a proxyCoordinator + /// \pre Systems running UDPProxyServer must be connected to \a proxyCoordinator and logged in via UDPProxyCoordinator::LoginServer() or UDPProxyServer::LoginToCoordinator() + /// \note May still fail, if all proxy servers have no open connections. + /// \note RakNet's protocol will ensure a message is sent at least every 5 seconds, so if routing RakNet messages, it is a reasonable value for timeoutOnNoDataMS, plus an extra few seconds for latency. + /// \param[in] proxyCoordinator System we are connected to that is running the UDPProxyCoordinator plugin + /// \param[in] sourceAddress External IP address of the system we want to forward messages from. This does not have to be our own system. To specify our own system, you can pass UNASSIGNED_SYSTEM_ADDRESS which the coordinator will treat as our external IP address. + /// \param[in] targetAddressAsSeenFromCoordinator External IP address of the system we want to forward messages to. If this system is connected to UDPProxyCoordinator at this address using RakNet, that system will ping the server and thus open the router for incoming communication. In any other case, you are responsible for doing your own network communication to have that system ping the server. See also targetGuid in the other version of RequestForwarding(), to avoid the need to know the IP address to the coordinator of the destination. + /// \param[in] timeoutOnNoData If no data is sent by the forwarded systems, how long before removing the forward entry from UDPForwarder? UDP_FORWARDER_MAXIMUM_TIMEOUT is the maximum value. Recommended high enough to not drop valid connections, but low enough to not waste forwarding slots on the proxy server. + /// \param[in] serverSelectionBitstream If you want to send data to UDPProxyCoordinator::GetBestServer(), write it here + /// \return true if the request was sent, false if we are not connected to proxyCoordinator + bool RequestForwarding(SystemAddress proxyCoordinator, SystemAddress sourceAddress, SystemAddress targetAddressAsSeenFromCoordinator, RakNetTimeMS timeoutOnNoDataMS, RakNet::BitStream *serverSelectionBitstream=0); + + /// Same as above, but specify the target with a GUID, in case you don't know what its address is to the coordinator + /// If requesting forwarding to a RakNet enabled system, then it is easier to use targetGuid instead of targetAddressAsSeenFromCoordinator + bool RequestForwarding(SystemAddress proxyCoordinator, SystemAddress sourceAddress, RakNetGUID targetGuid, RakNetTimeMS timeoutOnNoDataMS, RakNet::BitStream *serverSelectionBitstream=0); + + /// \internal + virtual void Update(void); + virtual PluginReceiveResult OnReceive(Packet *packet); + virtual void OnRakPeerShutdown(void); + + struct ServerWithPing + { + unsigned short ping; + SystemAddress serverAddress; + }; + struct SenderAndTargetAddress + { + SystemAddress senderClientAddress; + SystemAddress targetClientAddress; + }; + struct PingServerGroup + { + SenderAndTargetAddress sata; + RakNetTime startPingTime; + SystemAddress coordinatorAddressForPings; + DataStructures::Multilist serversToPing; + bool AreAllServersPinged(void) const; + void SendPingedServersToCoordinator(RakPeerInterface *rakPeerInterface); + }; + DataStructures::Multilist pingServerGroups; +protected: + + void OnPingServers(Packet *packet); + void Clear(void); + UDPProxyClientResultHandler *resultHandler; + +}; + +} // End namespace + +#endif + +#endif // _RAKNET_SUPPORT_* diff --git a/RakNet/Sources/UDPProxyCommon.h b/RakNet/Sources/UDPProxyCommon.h new file mode 100644 index 0000000..14593e3 --- /dev/null +++ b/RakNet/Sources/UDPProxyCommon.h @@ -0,0 +1,57 @@ +#ifndef __UDP_PROXY_COMMON_H +#define __UDP_PROXY_COMMON_H + +// System flow: +/* +UDPProxyClient: End user +UDPProxyServer: open server, to route messages from end users that can't connect to each other using UDPForwarder class. +UDPProxyCoordinator: Server somewhere, connected to by RakNet, to maintain a list of UDPProxyServer + +UDPProxyServer + On startup, log into UDPProxyCoordinator and register self + +UDPProxyClient + Wish to open route to X + Send message to UDPProxyCoordinator containing X, desired timeout + Wait for success or failure + +UDPProxyCoordinator: +* Get openRouteRequest + If no servers registered, return failure + Add entry to memory + chooseBestUDPProxyServer() (overridable, chooses at random by default) + Query this server to StartForwarding(). Return success or failure + If failure, choose another server from the remaining list. If none remaining, return failure. Else return success. +* Disconnect: + If disconnected system is pending client on openRouteRequest, delete that request + If disconnected system is UDPProxyServer, remove from list. For each pending client for this server, choose from remaining servers. +* Login: + Add to UDPProxyServer list, validating password if set +*/ + +// Stored in the second byte after ID_UDP_PROXY_GENERAL +// Otherwise MessageIdentifiers.h is too cluttered and will hit the limit on enumerations in a single byte +enum UDPProxyMessages +{ + ID_UDP_PROXY_FORWARDING_SUCCEEDED, + ID_UDP_PROXY_FORWARDING_NOTIFICATION, + ID_UDP_PROXY_NO_SERVERS_ONLINE, + ID_UDP_PROXY_RECIPIENT_GUID_NOT_CONNECTED_TO_COORDINATOR, + ID_UDP_PROXY_ALL_SERVERS_BUSY, + ID_UDP_PROXY_IN_PROGRESS, + ID_UDP_PROXY_FORWARDING_REQUEST_FROM_CLIENT_TO_COORDINATOR, + ID_UDP_PROXY_PING_SERVERS_FROM_COORDINATOR_TO_CLIENT, + ID_UDP_PROXY_PING_SERVERS_REPLY_FROM_CLIENT_TO_COORDINATOR, + ID_UDP_PROXY_FORWARDING_REQUEST_FROM_COORDINATOR_TO_SERVER, + ID_UDP_PROXY_FORWARDING_REPLY_FROM_SERVER_TO_COORDINATOR, + ID_UDP_PROXY_LOGIN_REQUEST_FROM_SERVER_TO_COORDINATOR, + ID_UDP_PROXY_LOGIN_SUCCESS_FROM_COORDINATOR_TO_SERVER, + ID_UDP_PROXY_ALREADY_LOGGED_IN_FROM_COORDINATOR_TO_SERVER, + ID_UDP_PROXY_NO_PASSWORD_SET_FROM_COORDINATOR_TO_SERVER, + ID_UDP_PROXY_WRONG_PASSWORD_FROM_COORDINATOR_TO_SERVER +}; + + +#define UDP_FORWARDER_MAXIMUM_TIMEOUT (60000 * 10) + +#endif diff --git a/RakNet/Sources/UDPProxyCoordinator.cpp b/RakNet/Sources/UDPProxyCoordinator.cpp new file mode 100644 index 0000000..e7f4ca8 --- /dev/null +++ b/RakNet/Sources/UDPProxyCoordinator.cpp @@ -0,0 +1,485 @@ +#include "NativeFeatureIncludes.h" +#if _RAKNET_SUPPORT_UDPProxyCoordinator==1 + +#include "UDPProxyCoordinator.h" +#include "BitStream.h" +#include "UDPProxyCommon.h" +#include "RakPeerInterface.h" +#include "MessageIdentifiers.h" +#include "Rand.h" +#include "GetTime.h" +#include "UDPForwarder.h" + +// Larger than the client version +static const int DEFAULT_CLIENT_UNRESPONSIVE_PING_TIME=2000; +static const int DEFAULT_UNRESPONSIVE_PING_TIME=DEFAULT_CLIENT_UNRESPONSIVE_PING_TIME+1000; + +using namespace RakNet; + +bool operator<( const DataStructures::MLKeyRef &inputKey, const UDPProxyCoordinator::ServerWithPing &cls ) {return inputKey.Get() < cls.ping;} +bool operator>( const DataStructures::MLKeyRef &inputKey, const UDPProxyCoordinator::ServerWithPing &cls ) {return inputKey.Get() > cls.ping;} +bool operator==( const DataStructures::MLKeyRef &inputKey, const UDPProxyCoordinator::ServerWithPing &cls ) {return inputKey.Get() == cls.ping;} + +bool operator<( const DataStructures::MLKeyRef &inputKey, const UDPProxyCoordinator::ForwardingRequest *cls ) +{ + return inputKey.Get().senderClientAddress < cls->sata.senderClientAddress || + (inputKey.Get().senderClientAddress == cls->sata.senderClientAddress && inputKey.Get().targetClientAddress < cls->sata.targetClientAddress); +} +bool operator>( const DataStructures::MLKeyRef &inputKey, const UDPProxyCoordinator::ForwardingRequest *cls ) +{ + return inputKey.Get().senderClientAddress > cls->sata.senderClientAddress || + (inputKey.Get().senderClientAddress == cls->sata.senderClientAddress && inputKey.Get().targetClientAddress > cls->sata.targetClientAddress); +} +bool operator==( const DataStructures::MLKeyRef &inputKey, const UDPProxyCoordinator::ForwardingRequest *cls ) +{ + return inputKey.Get().senderClientAddress == cls->sata.senderClientAddress && inputKey.Get().targetClientAddress == cls->sata.targetClientAddress; +} + +UDPProxyCoordinator::UDPProxyCoordinator() +{ + +} +UDPProxyCoordinator::~UDPProxyCoordinator() +{ + Clear(); +} +void UDPProxyCoordinator::SetRemoteLoginPassword(RakNet::RakString password) +{ + remoteLoginPassword=password; +} +void UDPProxyCoordinator::Update(void) +{ + DataStructures::DefaultIndexType idx; + RakNetTime curTime = RakNet::GetTime(); + ForwardingRequest *fw; + idx=0; + while (idx < forwardingRequestList.GetSize()) + { + fw=forwardingRequestList[idx]; + if (fw->timeRequestedPings!=0 && + curTime > fw->timeRequestedPings + DEFAULT_UNRESPONSIVE_PING_TIME) + { + fw->OrderRemainingServersToTry(); + fw->timeRequestedPings=0; + TryNextServer(fw->sata, fw); + idx++; + } + else if (fw->timeoutAfterSuccess!=0 && + curTime > fw->timeoutAfterSuccess) + { + // Forwarding request succeeded, we waited a bit to prevent duplicates. Can forget about the entry now. + RakNet::OP_DELETE(fw,__FILE__,__LINE__); + forwardingRequestList.RemoveAtIndex(idx,__FILE__,__LINE__); + } + else + idx++; + } +} +PluginReceiveResult UDPProxyCoordinator::OnReceive(Packet *packet) +{ + if (packet->data[0]==ID_UDP_PROXY_GENERAL && packet->length>1) + { + switch (packet->data[1]) + { + case ID_UDP_PROXY_FORWARDING_REQUEST_FROM_CLIENT_TO_COORDINATOR: + OnForwardingRequestFromClientToCoordinator(packet); + return RR_STOP_PROCESSING_AND_DEALLOCATE; + case ID_UDP_PROXY_LOGIN_REQUEST_FROM_SERVER_TO_COORDINATOR: + OnLoginRequestFromServerToCoordinator(packet); + return RR_STOP_PROCESSING_AND_DEALLOCATE; + case ID_UDP_PROXY_FORWARDING_REPLY_FROM_SERVER_TO_COORDINATOR: + OnForwardingReplyFromServerToCoordinator(packet); + return RR_STOP_PROCESSING_AND_DEALLOCATE; + case ID_UDP_PROXY_PING_SERVERS_REPLY_FROM_CLIENT_TO_COORDINATOR: + OnPingServersReplyFromClientToCoordinator(packet); + return RR_STOP_PROCESSING_AND_DEALLOCATE; + } + } + return RR_CONTINUE_PROCESSING; +} +void UDPProxyCoordinator::OnClosedConnection(SystemAddress systemAddress, RakNetGUID rakNetGUID, PI2_LostConnectionReason lostConnectionReason ) +{ + (void) lostConnectionReason; + (void) rakNetGUID; + + DataStructures::DefaultIndexType idx, idx2; + + idx=0; + while (idx < forwardingRequestList.GetSize()) + { + if (forwardingRequestList[idx]->requestingAddress==systemAddress) + { + // Guy disconnected before the attempt completed + RakNet::OP_DELETE(forwardingRequestList[idx], __FILE__, __LINE__); + forwardingRequestList.RemoveAtIndex(idx, __FILE__, __LINE__ ); + } + else + idx++; + } + + idx = serverList.GetIndexOf(systemAddress); + if (idx!=(DataStructures::DefaultIndexType)-1) + { + ForwardingRequest *fw; + // For each pending client for this server, choose from remaining servers. + for (idx2=0; idx2 < forwardingRequestList.GetSize(); idx2++) + { + fw = forwardingRequestList[idx2]; + if (fw->currentlyAttemptedServerAddress==systemAddress) + { + // Try the next server + TryNextServer(fw->sata, fw); + } + } + + // Remove dead server + serverList.RemoveAtIndex(idx, __FILE__, __LINE__ ); + } +} +void UDPProxyCoordinator::OnForwardingRequestFromClientToCoordinator(Packet *packet) +{ + RakNet::BitStream incomingBs(packet->data, packet->length, false); + incomingBs.IgnoreBytes(2); + SystemAddress sourceAddress; + incomingBs.Read(sourceAddress); + if (sourceAddress==UNASSIGNED_SYSTEM_ADDRESS) + sourceAddress=packet->systemAddress; + SystemAddress targetAddress; + RakNetGUID targetGuid; + bool usesAddress; + incomingBs.Read(usesAddress); + if (usesAddress) + { + incomingBs.Read(targetAddress); + } + else + { + incomingBs.Read(targetGuid); + targetAddress=rakPeerInterface->GetSystemAddressFromGuid(targetGuid); + } + ForwardingRequest *fw = RakNet::OP_NEW(__FILE__,__LINE__); + fw->timeoutAfterSuccess=0; + incomingBs.Read(fw->timeoutOnNoDataMS); + bool hasServerSelectionBitstream; + incomingBs.Read(hasServerSelectionBitstream); + if (hasServerSelectionBitstream) + incomingBs.Read(&(fw->serverSelectionBitstream)); + + RakNet::BitStream outgoingBs; + SenderAndTargetAddress sata; + sata.senderClientAddress=sourceAddress; + sata.targetClientAddress=targetAddress; + SenderAndTargetAddress sataReversed; + sataReversed.senderClientAddress=targetAddress; + sataReversed.targetClientAddress=sourceAddress; + DataStructures::DefaultIndexType insertionIndex; + insertionIndex = forwardingRequestList.GetInsertionIndex(sata); + if (insertionIndex==(DataStructures::DefaultIndexType)-1 || + forwardingRequestList.GetInsertionIndex(sataReversed)==(DataStructures::DefaultIndexType)-1) + { + outgoingBs.Write((MessageID)ID_UDP_PROXY_GENERAL); + outgoingBs.Write((MessageID)ID_UDP_PROXY_IN_PROGRESS); + outgoingBs.Write(sata.senderClientAddress); + outgoingBs.Write(targetAddress); + rakPeerInterface->Send(&outgoingBs, MEDIUM_PRIORITY, RELIABLE_ORDERED, 0, packet->systemAddress, false); + RakNet::OP_DELETE(fw, __FILE__, __LINE__); + return; + } + + if (serverList.GetSize()==0) + { + outgoingBs.Write((MessageID)ID_UDP_PROXY_GENERAL); + outgoingBs.Write((MessageID)ID_UDP_PROXY_NO_SERVERS_ONLINE); + outgoingBs.Write(sata.senderClientAddress); + outgoingBs.Write(targetAddress); + rakPeerInterface->Send(&outgoingBs, MEDIUM_PRIORITY, RELIABLE_ORDERED, 0, packet->systemAddress, false); + RakNet::OP_DELETE(fw, __FILE__, __LINE__); + return; + } + + if (rakPeerInterface->IsConnected(targetAddress)==false && usesAddress==false) + { + outgoingBs.Write((MessageID)ID_UDP_PROXY_GENERAL); + outgoingBs.Write((MessageID)ID_UDP_PROXY_RECIPIENT_GUID_NOT_CONNECTED_TO_COORDINATOR); + outgoingBs.Write(sata.senderClientAddress); + outgoingBs.Write(targetAddress); + outgoingBs.Write(targetGuid); + rakPeerInterface->Send(&outgoingBs, MEDIUM_PRIORITY, RELIABLE_ORDERED, 0, packet->systemAddress, false); + RakNet::OP_DELETE(fw, __FILE__, __LINE__); + return; + } + + fw->sata=sata; + fw->requestingAddress=packet->systemAddress; + + if (serverList.GetSize()>1) + { + outgoingBs.Write((MessageID)ID_UDP_PROXY_GENERAL); + outgoingBs.Write((MessageID)ID_UDP_PROXY_PING_SERVERS_FROM_COORDINATOR_TO_CLIENT); + outgoingBs.Write(sourceAddress); + outgoingBs.Write(targetAddress); + unsigned short serverListSize = (unsigned short) serverList.GetSize(); + outgoingBs.Write(serverListSize); + DataStructures::DefaultIndexType idx; + for (idx=0; idx < serverList.GetSize(); idx++) + outgoingBs.Write(serverList[idx]); + rakPeerInterface->Send(&outgoingBs, MEDIUM_PRIORITY, RELIABLE_ORDERED, 0, sourceAddress, false); + rakPeerInterface->Send(&outgoingBs, MEDIUM_PRIORITY, RELIABLE_ORDERED, 0, targetAddress, false); + fw->timeRequestedPings=RakNet::GetTime(); + DataStructures::DefaultIndexType copyIndex; + for (copyIndex=0; copyIndex < serverList.GetSize(); copyIndex++) + fw->remainingServersToTry.Push(serverList[copyIndex], __FILE__, __LINE__ ); + forwardingRequestList.InsertAtIndex(fw, insertionIndex, __FILE__, __LINE__ ); + } + else + { + fw->timeRequestedPings=0; + fw->currentlyAttemptedServerAddress=serverList[0]; + forwardingRequestList.InsertAtIndex(fw, insertionIndex, __FILE__, __LINE__ ); + SendForwardingRequest(sourceAddress, targetAddress, fw->currentlyAttemptedServerAddress, fw->timeoutOnNoDataMS); + } +} + +void UDPProxyCoordinator::SendForwardingRequest(SystemAddress sourceAddress, SystemAddress targetAddress, SystemAddress serverAddress, RakNetTimeMS timeoutOnNoDataMS) +{ + RakNet::BitStream outgoingBs; + // Send request to desired server + outgoingBs.Write((MessageID)ID_UDP_PROXY_GENERAL); + outgoingBs.Write((MessageID)ID_UDP_PROXY_FORWARDING_REQUEST_FROM_COORDINATOR_TO_SERVER); + outgoingBs.Write(sourceAddress); + outgoingBs.Write(targetAddress); + outgoingBs.Write(timeoutOnNoDataMS); + rakPeerInterface->Send(&outgoingBs, MEDIUM_PRIORITY, RELIABLE_ORDERED, 0, serverAddress, false); +} +void UDPProxyCoordinator::OnLoginRequestFromServerToCoordinator(Packet *packet) +{ + RakNet::BitStream incomingBs(packet->data, packet->length, false); + incomingBs.IgnoreBytes(2); + RakNet::RakString password; + incomingBs.Read(password); + RakNet::BitStream outgoingBs; + + if (remoteLoginPassword.IsEmpty()) + { + outgoingBs.Write((MessageID)ID_UDP_PROXY_GENERAL); + outgoingBs.Write((MessageID)ID_UDP_PROXY_NO_PASSWORD_SET_FROM_COORDINATOR_TO_SERVER); + outgoingBs.Write(password); + rakPeerInterface->Send(&outgoingBs, MEDIUM_PRIORITY, RELIABLE_ORDERED, 0, packet->systemAddress, false); + return; + } + + if (remoteLoginPassword!=password) + { + outgoingBs.Write((MessageID)ID_UDP_PROXY_GENERAL); + outgoingBs.Write((MessageID)ID_UDP_PROXY_WRONG_PASSWORD_FROM_COORDINATOR_TO_SERVER); + outgoingBs.Write(password); + rakPeerInterface->Send(&outgoingBs, MEDIUM_PRIORITY, RELIABLE_ORDERED, 0, packet->systemAddress, false); + return; + } + + DataStructures::DefaultIndexType insertionIndex; + insertionIndex=serverList.GetInsertionIndex(packet->systemAddress); + if (insertionIndex==(DataStructures::DefaultIndexType)-1) + { + outgoingBs.Write((MessageID)ID_UDP_PROXY_GENERAL); + outgoingBs.Write((MessageID)ID_UDP_PROXY_ALREADY_LOGGED_IN_FROM_COORDINATOR_TO_SERVER); + outgoingBs.Write(password); + rakPeerInterface->Send(&outgoingBs, MEDIUM_PRIORITY, RELIABLE_ORDERED, 0, packet->systemAddress, false); + return; + } + serverList.InsertAtIndex(packet->systemAddress, insertionIndex, __FILE__, __LINE__ ); + outgoingBs.Write((MessageID)ID_UDP_PROXY_GENERAL); + outgoingBs.Write((MessageID)ID_UDP_PROXY_LOGIN_SUCCESS_FROM_COORDINATOR_TO_SERVER); + outgoingBs.Write(password); + rakPeerInterface->Send(&outgoingBs, MEDIUM_PRIORITY, RELIABLE_ORDERED, 0, packet->systemAddress, false); +} +void UDPProxyCoordinator::OnForwardingReplyFromServerToCoordinator(Packet *packet) +{ + RakNet::BitStream incomingBs(packet->data, packet->length, false); + incomingBs.IgnoreBytes(2); + SenderAndTargetAddress sata; + incomingBs.Read(sata.senderClientAddress); + incomingBs.Read(sata.targetClientAddress); + DataStructures::DefaultIndexType index = forwardingRequestList.GetIndexOf(sata); + if (index==(DataStructures::DefaultIndexType)-1) + { + // The guy disconnected before the request finished + return; + } + ForwardingRequest *fw = forwardingRequestList[index]; + + UDPForwarderResult success; + unsigned char c; + incomingBs.Read(c); + success=(UDPForwarderResult)c; + + RakNet::BitStream outgoingBs; + if (success==UDPFORWARDER_SUCCESS) + { + char serverIP[64]; + packet->systemAddress.ToString(false,serverIP); + unsigned short srcToDestPort; + unsigned short destToSourcePort; + incomingBs.Read(srcToDestPort); + incomingBs.Read(destToSourcePort); + + outgoingBs.Write((MessageID)ID_UDP_PROXY_GENERAL); + outgoingBs.Write((MessageID)ID_UDP_PROXY_FORWARDING_SUCCEEDED); + outgoingBs.Write(sata.senderClientAddress); + outgoingBs.Write(sata.targetClientAddress); + outgoingBs.Write(RakNet::RakString(serverIP)); + outgoingBs.Write(srcToDestPort); + outgoingBs.Write(destToSourcePort); + rakPeerInterface->Send(&outgoingBs, MEDIUM_PRIORITY, RELIABLE_ORDERED, 0, fw->requestingAddress, false); + + outgoingBs.Reset(); + outgoingBs.Write((MessageID)ID_UDP_PROXY_GENERAL); + outgoingBs.Write((MessageID)ID_UDP_PROXY_FORWARDING_NOTIFICATION); + outgoingBs.Write(sata.senderClientAddress); + outgoingBs.Write(sata.targetClientAddress); + outgoingBs.Write(RakNet::RakString(serverIP)); + outgoingBs.Write(srcToDestPort); + outgoingBs.Write(destToSourcePort); + rakPeerInterface->Send(&outgoingBs, MEDIUM_PRIORITY, RELIABLE_ORDERED, 0, sata.targetClientAddress, false); + + // 05/18/09 Keep the entry around for some time after success, so duplicates are reported if attempting forwarding from the target system before notification of success + fw->timeoutAfterSuccess=RakNet::GetTime()+fw->timeoutOnNoDataMS; + // forwardingRequestList.RemoveAtIndex(index); + // RakNet::OP_DELETE(fw,__FILE__,__LINE__); + + return; + } + else if (success==UDPFORWARDER_NO_SOCKETS) + { + // Try next server + TryNextServer(sata, fw); + } + else + { + RakAssert(success==UDPFORWARDER_FORWARDING_ALREADY_EXISTS); + + // Return in progress + outgoingBs.Write((MessageID)ID_UDP_PROXY_GENERAL); + outgoingBs.Write((MessageID)ID_UDP_PROXY_IN_PROGRESS); + outgoingBs.Write(sata.senderClientAddress); + outgoingBs.Write(sata.targetClientAddress); + rakPeerInterface->Send(&outgoingBs, MEDIUM_PRIORITY, RELIABLE_ORDERED, 0, fw->requestingAddress, false); + forwardingRequestList.RemoveAtIndex(index,__FILE__,__LINE__); + RakNet::OP_DELETE(fw,__FILE__,__LINE__); + } +} +void UDPProxyCoordinator::OnPingServersReplyFromClientToCoordinator(Packet *packet) +{ + RakNet::BitStream incomingBs(packet->data, packet->length, false); + incomingBs.IgnoreBytes(2); + unsigned short serversToPingSize; + SystemAddress serverAddress; + SenderAndTargetAddress sata; + incomingBs.Read(sata.senderClientAddress); + incomingBs.Read(sata.targetClientAddress); + DataStructures::DefaultIndexType index = forwardingRequestList.GetIndexOf(sata); + if (index==(DataStructures::DefaultIndexType)-1) + return; + unsigned short idx; + ServerWithPing swp; + ForwardingRequest *fw = forwardingRequestList[index]; + if (fw->timeRequestedPings==0) + return; + + incomingBs.Read(serversToPingSize); + if (packet->systemAddress==sata.senderClientAddress) + { + for (idx=0; idx < serversToPingSize; idx++) + { + incomingBs.Read(swp.serverAddress); + incomingBs.Read(swp.ping); + fw->sourceServerPings.Push(swp, swp.ping, __FILE__, __LINE__); + } + } + else + { + for (idx=0; idx < serversToPingSize; idx++) + { + incomingBs.Read(swp.serverAddress); + incomingBs.Read(swp.ping); + fw->targetServerPings.Push(swp, swp.ping, __FILE__, __LINE__); + } + } + + // Both systems have to give us pings to progress here. Otherwise will timeout in Update() + if (fw->sourceServerPings.GetSize()>0 && + fw->targetServerPings.GetSize()>0) + { + fw->OrderRemainingServersToTry(); + fw->timeRequestedPings=0; + TryNextServer(fw->sata, fw); + } +} +void UDPProxyCoordinator::TryNextServer(SenderAndTargetAddress sata, ForwardingRequest *fw) +{ + bool pickedGoodServer=false; + while(fw->remainingServersToTry.GetSize()>0) + { + fw->currentlyAttemptedServerAddress=fw->remainingServersToTry.Pop(__FILE__, __LINE__ ); + if (serverList.GetIndexOf(fw->currentlyAttemptedServerAddress)!=(DataStructures::DefaultIndexType)-1) + { + pickedGoodServer=true; + break; + } + } + + if (pickedGoodServer==false) + { + SendAllBusy(sata.senderClientAddress, sata.targetClientAddress, fw->requestingAddress); + forwardingRequestList.RemoveAtKey(sata,true,__FILE__,__LINE__); + RakNet::OP_DELETE(fw,__FILE__,__LINE__); + return; + } + + SendForwardingRequest(sata.senderClientAddress, sata.targetClientAddress, fw->currentlyAttemptedServerAddress, fw->timeoutOnNoDataMS); +} +void UDPProxyCoordinator::SendAllBusy(SystemAddress senderClientAddress, SystemAddress targetClientAddress, SystemAddress requestingAddress) +{ + RakNet::BitStream outgoingBs; + outgoingBs.Write((MessageID)ID_UDP_PROXY_GENERAL); + outgoingBs.Write((MessageID)ID_UDP_PROXY_ALL_SERVERS_BUSY); + outgoingBs.Write(senderClientAddress); + outgoingBs.Write(targetClientAddress); + rakPeerInterface->Send(&outgoingBs, MEDIUM_PRIORITY, RELIABLE_ORDERED, 0, requestingAddress, false); +} +void UDPProxyCoordinator::Clear(void) +{ + serverList.Clear(true, __FILE__, __LINE__); + forwardingRequestList.ClearPointers(true, __FILE__, __LINE__); +} +void UDPProxyCoordinator::ForwardingRequest::OrderRemainingServersToTry(void) +{ + DataStructures::Multilist swpList; + swpList.SetSortOrder(true); + if (sourceServerPings.GetSize()==0 && targetServerPings.GetSize()==0) + return; + + DataStructures::DefaultIndexType idx; + UDPProxyCoordinator::ServerWithPing swp; + for (idx=0; idx < remainingServersToTry.GetSize(); idx++) + { + swp.serverAddress=remainingServersToTry[idx]; + swp.ping=0; + if (sourceServerPings.GetSize()) + swp.ping+=(unsigned short) sourceServerPings[idx].ping; + else + swp.ping+=(unsigned short) DEFAULT_CLIENT_UNRESPONSIVE_PING_TIME; + if (targetServerPings.GetSize()) + swp.ping+=(unsigned short) targetServerPings[idx].ping; + else + swp.ping+=(unsigned short) DEFAULT_CLIENT_UNRESPONSIVE_PING_TIME; + swpList.Push(swp, swp.ping, __FILE__, __LINE__); + } + remainingServersToTry.Clear(true, __FILE__, __LINE__ ); + for (idx=0; idx < swpList.GetSize(); idx++) + { + remainingServersToTry.Push(swpList[idx].serverAddress, __FILE__, __LINE__ ); + } +} + +#endif // _RAKNET_SUPPORT_* diff --git a/RakNet/Sources/UDPProxyCoordinator.h b/RakNet/Sources/UDPProxyCoordinator.h new file mode 100644 index 0000000..d8763bd --- /dev/null +++ b/RakNet/Sources/UDPProxyCoordinator.h @@ -0,0 +1,104 @@ +/// \file +/// \brief Essentially maintains a list of servers running UDPProxyServer, and some state management for UDPProxyClient to find a free server to forward datagrams +/// +/// 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. + +#include "NativeFeatureIncludes.h" +#if _RAKNET_SUPPORT_UDPProxyCoordinator==1 + +#ifndef __UDP_PROXY_COORDINATOR_H +#define __UDP_PROXY_COORDINATOR_H + +#include "Export.h" +#include "DS_Multilist.h" +#include "RakNetTypes.h" +#include "PluginInterface2.h" +#include "RakString.h" +#include "BitStream.h" + +namespace RakNet +{ + /// When NAT Punchthrough fails, it is possible to use a non-NAT system to forward messages from us to the recipient, and vice-versa + /// The class to forward messages is UDPForwarder, and it is triggered over the network via the UDPProxyServer plugin. + /// The UDPProxyClient connects to UDPProxyCoordinator to get a list of servers running UDPProxyServer, and the coordinator will relay our forwarding request + /// \brief Middleman between UDPProxyServer and UDPProxyClient, maintaining a list of UDPProxyServer, and managing state for clients to find an available forwarding server. + /// \ingroup NAT_PUNCHTHROUGH_GROUP + class RAK_DLL_EXPORT UDPProxyCoordinator : public PluginInterface2 + { + public: + UDPProxyCoordinator(); + virtual ~UDPProxyCoordinator(); + + /// For UDPProxyServers logging in remotely, they must pass a password to UDPProxyServer::LoginToCoordinator(). It must match the password set here. + /// If no password is set, they cannot login remotely. + /// By default, no password is set + void SetRemoteLoginPassword(RakNet::RakString password); + + /// \internal + virtual void Update(void); + virtual PluginReceiveResult OnReceive(Packet *packet); + virtual void OnClosedConnection(SystemAddress systemAddress, RakNetGUID rakNetGUID, PI2_LostConnectionReason lostConnectionReason ); + + struct SenderAndTargetAddress + { + SystemAddress senderClientAddress; + SystemAddress targetClientAddress; + }; + + struct ServerWithPing + { + unsigned short ping; + SystemAddress serverAddress; + }; + + struct ForwardingRequest + { + RakNetTimeMS timeoutOnNoDataMS; + RakNetTimeMS timeoutAfterSuccess; + SenderAndTargetAddress sata; + SystemAddress requestingAddress; // Which system originally sent the network message to start forwarding + SystemAddress currentlyAttemptedServerAddress; + DataStructures::Multilist remainingServersToTry; + RakNet::BitStream serverSelectionBitstream; + + DataStructures::Multilist sourceServerPings, targetServerPings; + RakNetTime timeRequestedPings; + // Order based on sourceServerPings and targetServerPings + void OrderRemainingServersToTry(void); + + }; + + protected: + void OnForwardingRequestFromClientToCoordinator(Packet *packet); + void OnLoginRequestFromServerToCoordinator(Packet *packet); + void OnForwardingReplyFromServerToCoordinator(Packet *packet); + void OnPingServersReplyFromClientToCoordinator(Packet *packet); + void TryNextServer(SenderAndTargetAddress sata, ForwardingRequest *fw); + void SendAllBusy(SystemAddress senderClientAddress, SystemAddress targetClientAddress, SystemAddress requestingAddress); + void Clear(void); + + void SendForwardingRequest(SystemAddress sourceAddress, SystemAddress targetAddress, SystemAddress serverAddress, RakNetTimeMS timeoutOnNoDataMS); + + // Logged in servers + DataStructures::Multilist serverList; + + // Forwarding requests in progress + DataStructures::Multilist forwardingRequestList; + + RakNet::RakString remoteLoginPassword; + + }; + +} // End namespace + +#endif + +#endif // _RAKNET_SUPPORT_* diff --git a/RakNet/Sources/UDPProxyServer.cpp b/RakNet/Sources/UDPProxyServer.cpp new file mode 100644 index 0000000..1cfcce1 --- /dev/null +++ b/RakNet/Sources/UDPProxyServer.cpp @@ -0,0 +1,158 @@ +#include "NativeFeatureIncludes.h" +#if _RAKNET_SUPPORT_UDPProxyServer==1 + +#include "UDPProxyServer.h" +#include "BitStream.h" +#include "UDPProxyCommon.h" +#include "RakPeerInterface.h" +#include "MessageIdentifiers.h" + +using namespace RakNet; + +UDPProxyServer::UDPProxyServer() +{ + resultHandler=0; +} +UDPProxyServer::~UDPProxyServer() +{ + +} +void UDPProxyServer::SetResultHandler(UDPProxyServerResultHandler *rh) +{ + resultHandler=rh; +} +bool UDPProxyServer::LoginToCoordinator(RakNet::RakString password, SystemAddress coordinatorAddress) +{ + DataStructures::DefaultIndexType insertionIndex; + insertionIndex = loggingInCoordinators.GetInsertionIndex(coordinatorAddress); + if (insertionIndex==(DataStructures::DefaultIndexType)-1) + return false; + if (loggedInCoordinators.GetInsertionIndex(coordinatorAddress)==(DataStructures::DefaultIndexType)-1) + return false; + RakNet::BitStream outgoingBs; + outgoingBs.Write((MessageID)ID_UDP_PROXY_GENERAL); + outgoingBs.Write((MessageID)ID_UDP_PROXY_LOGIN_REQUEST_FROM_SERVER_TO_COORDINATOR); + outgoingBs.Write(password); + rakPeerInterface->Send(&outgoingBs, MEDIUM_PRIORITY, RELIABLE_ORDERED, 0, coordinatorAddress, false); + loggingInCoordinators.InsertAtIndex(coordinatorAddress, insertionIndex, __FILE__, __LINE__ ); + return true; +} +void UDPProxyServer::Update(void) +{ + udpForwarder.Update(); +} +PluginReceiveResult UDPProxyServer::OnReceive(Packet *packet) +{ + // Make sure incoming messages from from UDPProxyCoordinator + + if (packet->data[0]==ID_UDP_PROXY_GENERAL && packet->length>1) + { + switch (packet->data[1]) + { + case ID_UDP_PROXY_FORWARDING_REQUEST_FROM_COORDINATOR_TO_SERVER: + if (loggedInCoordinators.GetIndexOf(packet->systemAddress)!=(DataStructures::DefaultIndexType)-1) + { + OnForwardingRequestFromCoordinatorToServer(packet); + return RR_STOP_PROCESSING_AND_DEALLOCATE; + } + break; + case ID_UDP_PROXY_NO_PASSWORD_SET_FROM_COORDINATOR_TO_SERVER: + case ID_UDP_PROXY_WRONG_PASSWORD_FROM_COORDINATOR_TO_SERVER: + case ID_UDP_PROXY_ALREADY_LOGGED_IN_FROM_COORDINATOR_TO_SERVER: + case ID_UDP_PROXY_LOGIN_SUCCESS_FROM_COORDINATOR_TO_SERVER: + { + DataStructures::DefaultIndexType removalIndex = loggingInCoordinators.GetIndexOf(packet->systemAddress); + if (removalIndex!=(DataStructures::DefaultIndexType)-1) + { + loggingInCoordinators.RemoveAtKey(packet->systemAddress, false, __FILE__, __LINE__ ); + + RakNet::BitStream incomingBs(packet->data, packet->length, false); + incomingBs.IgnoreBytes(2); + RakNet::RakString password; + incomingBs.Read(password); + switch (packet->data[1]) + { + case ID_UDP_PROXY_NO_PASSWORD_SET_FROM_COORDINATOR_TO_SERVER: + if (resultHandler) + resultHandler->OnNoPasswordSet(password, this); + break; + case ID_UDP_PROXY_WRONG_PASSWORD_FROM_COORDINATOR_TO_SERVER: + if (resultHandler) + resultHandler->OnWrongPassword(password, this); + break; + case ID_UDP_PROXY_ALREADY_LOGGED_IN_FROM_COORDINATOR_TO_SERVER: + if (resultHandler) + resultHandler->OnAlreadyLoggedIn(password, this); + break; + case ID_UDP_PROXY_LOGIN_SUCCESS_FROM_COORDINATOR_TO_SERVER: + RakAssert(loggedInCoordinators.GetIndexOf(packet->systemAddress)==(unsigned int)-1); + loggedInCoordinators.Push(packet->systemAddress, __FILE__, __LINE__); + if (resultHandler) + resultHandler->OnLoginSuccess(password, this); + break; + } + } + + + return RR_STOP_PROCESSING_AND_DEALLOCATE; + } + } + } + return RR_CONTINUE_PROCESSING; +} +void UDPProxyServer::OnClosedConnection(SystemAddress systemAddress, RakNetGUID rakNetGUID, PI2_LostConnectionReason lostConnectionReason ) +{ + (void) lostConnectionReason; + (void) rakNetGUID; + + loggingInCoordinators.RemoveAtKey(systemAddress,false, __FILE__, __LINE__ ); + loggedInCoordinators.RemoveAtKey(systemAddress,false, __FILE__, __LINE__ ); +} +void UDPProxyServer::OnRakPeerStartup(void) +{ + udpForwarder.Startup(); +} +void UDPProxyServer::OnRakPeerShutdown(void) +{ + udpForwarder.Shutdown(); + loggingInCoordinators.Clear(true,__FILE__,__LINE__); + loggedInCoordinators.Clear(true,__FILE__,__LINE__); +} +void UDPProxyServer::OnAttach(void) +{ + if (rakPeerInterface->IsActive()) + OnRakPeerStartup(); +} +void UDPProxyServer::OnDetach(void) +{ + OnRakPeerShutdown(); +} +void UDPProxyServer::OnForwardingRequestFromCoordinatorToServer(Packet *packet) +{ + SystemAddress sourceAddress, targetAddress; + RakNet::BitStream incomingBs(packet->data, packet->length, false); + incomingBs.IgnoreBytes(2); + incomingBs.Read(sourceAddress); + incomingBs.Read(targetAddress); + RakNetTimeMS timeoutOnNoDataMS; + incomingBs.Read(timeoutOnNoDataMS); + RakAssert(timeoutOnNoDataMS > 0 && timeoutOnNoDataMS < UDP_FORWARDER_MAXIMUM_TIMEOUT); + + unsigned short srcToDestPort; + unsigned short destToSourcePort; + UDPForwarderResult success = udpForwarder.StartForwarding(sourceAddress, targetAddress, timeoutOnNoDataMS, 0, &srcToDestPort, &destToSourcePort, 0, 0); + RakNet::BitStream outgoingBs; + outgoingBs.Write((MessageID)ID_UDP_PROXY_GENERAL); + outgoingBs.Write((MessageID)ID_UDP_PROXY_FORWARDING_REPLY_FROM_SERVER_TO_COORDINATOR); + outgoingBs.Write(sourceAddress); + outgoingBs.Write(targetAddress); + outgoingBs.Write((unsigned char) success); + if (success==UDPFORWARDER_SUCCESS) + { + outgoingBs.Write(srcToDestPort); + outgoingBs.Write(destToSourcePort); + } + rakPeerInterface->Send(&outgoingBs, MEDIUM_PRIORITY, RELIABLE_ORDERED, 0, packet->systemAddress, false); +} + +#endif // _RAKNET_SUPPORT_* diff --git a/RakNet/Sources/UDPProxyServer.h b/RakNet/Sources/UDPProxyServer.h new file mode 100644 index 0000000..3c752af --- /dev/null +++ b/RakNet/Sources/UDPProxyServer.h @@ -0,0 +1,111 @@ +/// \file +/// \brief A RakNet plugin performing networking to communicate with UDPProxyServer. It allows UDPProxyServer to control our instance of UDPForwarder. +/// +/// 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. + +#include "NativeFeatureIncludes.h" +#if _RAKNET_SUPPORT_UDPProxyServer==1 + +#ifndef __UDP_PROXY_SERVER_H +#define __UDP_PROXY_SERVER_H + +#include "Export.h" +#include "DS_Multilist.h" +#include "RakNetTypes.h" +#include "PluginInterface2.h" +#include "UDPForwarder.h" +#include "RakString.h" + +namespace RakNet +{ +class UDPProxyServer; + +/// Callback to handle results of calling UDPProxyServer::LoginToCoordinator() +/// \ingroup UDP_PROXY_GROUP +struct UDPProxyServerResultHandler +{ + UDPProxyServerResultHandler() {} + virtual ~UDPProxyServerResultHandler() {} + + /// Called when our login succeeds + /// \param[out] usedPassword The password we passed to UDPProxyServer::LoginToCoordinator() + /// \param[out] proxyServer The plugin calling this callback + virtual void OnLoginSuccess(RakNet::RakString usedPassword, RakNet::UDPProxyServer *proxyServerPlugin)=0; + + /// We are already logged in. + /// This login failed, but the system is operational as if it succeeded + /// \param[out] usedPassword The password we passed to UDPProxyServer::LoginToCoordinator() + /// \param[out] proxyServer The plugin calling this callback + virtual void OnAlreadyLoggedIn(RakNet::RakString usedPassword, RakNet::UDPProxyServer *proxyServerPlugin)=0; + + /// The coordinator operator forgot to call UDPProxyCoordinator::SetRemoteLoginPassword() + /// \param[out] usedPassword The password we passed to UDPProxyServer::LoginToCoordinator() + /// \param[out] proxyServer The plugin calling this callback + virtual void OnNoPasswordSet(RakNet::RakString usedPassword, RakNet::UDPProxyServer *proxyServerPlugin)=0; + + /// The coordinator operator set a different password in UDPProxyCoordinator::SetRemoteLoginPassword() than what we passed + /// \param[out] usedPassword The password we passed to UDPProxyServer::LoginToCoordinator() + /// \param[out] proxyServer The plugin calling this callback + virtual void OnWrongPassword(RakNet::RakString usedPassword, RakNet::UDPProxyServer *proxyServerPlugin)=0; +}; + +/// \brief UDPProxyServer to control our instance of UDPForwarder +/// \details When NAT Punchthrough fails, it is possible to use a non-NAT system to forward messages from us to the recipient, and vice-versa.
      +/// The class to forward messages is UDPForwarder, and it is triggered over the network via the UDPProxyServer plugin.
      +/// The UDPProxyServer connects to UDPProxyServer to get a list of servers running UDPProxyServer, and the coordinator will relay our forwarding request. +/// \ingroup UDP_PROXY_GROUP +class RAK_DLL_EXPORT UDPProxyServer : public PluginInterface2 +{ +public: + UDPProxyServer(); + ~UDPProxyServer(); + + /// Receives the results of calling LoginToCoordinator() + /// Set before calling LoginToCoordinator or you won't know what happened + /// \param[in] resultHandler + void SetResultHandler(UDPProxyServerResultHandler *rh); + + /// Before the coordinator will register the UDPProxyServer, you must login + /// \pre Must be connected to the coordinator + /// \pre Coordinator must have set a password with UDPProxyCoordinator::SetRemoteLoginPassword() + /// \returns false if already logged in, or logging in. Returns true otherwise + bool LoginToCoordinator(RakNet::RakString password, SystemAddress coordinatorAddress); + + /// Operative class that performs the forwarding + /// Exposed so you can call UDPForwarder::SetMaxForwardEntries() if you want to change away from the default + /// UDPForwarder::Startup(), UDPForwarder::Shutdown(), and UDPForwarder::Update() are called automatically by the plugin + UDPForwarder udpForwarder; + + virtual void OnAttach(void); + virtual void OnDetach(void); + + /// \internal + virtual void Update(void); + virtual PluginReceiveResult OnReceive(Packet *packet); + virtual void OnClosedConnection(SystemAddress systemAddress, RakNetGUID rakNetGUID, PI2_LostConnectionReason lostConnectionReason ); + virtual void OnRakPeerStartup(void); + virtual void OnRakPeerShutdown(void); + +protected: + void OnForwardingRequestFromCoordinatorToServer(Packet *packet); + + DataStructures::Multilist loggingInCoordinators; + DataStructures::Multilist loggedInCoordinators; + + UDPProxyServerResultHandler *resultHandler; + +}; + +} // End namespace + +#endif + +#endif // _RAKNET_SUPPORT_* diff --git a/RakNet/Sources/VariableDeltaSerializer.cpp b/RakNet/Sources/VariableDeltaSerializer.cpp new file mode 100644 index 0000000..040ea56 --- /dev/null +++ b/RakNet/Sources/VariableDeltaSerializer.cpp @@ -0,0 +1,275 @@ +#include "VariableDeltaSerializer.h" + +using namespace RakNet; + +VariableDeltaSerializer::VariableDeltaSerializer() {didComparisonThisTick=false;} +VariableDeltaSerializer::~VariableDeltaSerializer() {RemoveRemoteSystemVariableHistory();} + +VariableDeltaSerializer::SerializationContext::SerializationContext() {variableHistoryIdentical=0; variableHistoryUnique=0;} +VariableDeltaSerializer::SerializationContext::~SerializationContext() {} + +void VariableDeltaSerializer::OnMessageReceipt(RakNetGUID guid, uint32_t receiptId, bool messageArrived) +{ + // Module? + if (messageArrived) + FreeVarsAssociatedWithReceipt(guid, receiptId); + else + DirtyAndFreeVarsAssociatedWithReceipt(guid, receiptId); + +} + +void VariableDeltaSerializer::BeginUnreliableAckedSerialize(SerializationContext *context, RakNetGUID _guid, BitStream *_bitStream, uint32_t _sendReceipt) +{ + RakAssert(_guid!=UNASSIGNED_RAKNET_GUID); + context->anyVariablesWritten=false; + context->guid=_guid; + context->bitStream=_bitStream; + if (context->variableHistoryUnique==0) + context->variableHistoryUnique=StartVariableHistoryWrite(_guid); + context->variableHistory=context->variableHistoryUnique; + context->sendReceipt=_sendReceipt; + context->changedVariables = AllocChangedVariablesList(); + context->newSystemSend=false; + context->serializationMode=UNRELIABLE_WITH_ACK_RECEIPT; +} + +void VariableDeltaSerializer::BeginUniqueSerialize(SerializationContext *context, RakNetGUID _guid, BitStream *_bitStream) +{ + RakAssert(_guid!=UNASSIGNED_RAKNET_GUID); + context->anyVariablesWritten=false; + context->guid=_guid; + context->bitStream=_bitStream; + if (context->variableHistoryUnique==0) + context->variableHistoryUnique=StartVariableHistoryWrite(_guid); + context->variableHistory=context->variableHistoryUnique; + context->newSystemSend=false; + + context->serializationMode=RELIABLE; +} + + +void VariableDeltaSerializer::BeginIdenticalSerialize(SerializationContext *context, bool _isFirstSendToRemoteSystem, BitStream *_bitStream) +{ + context->anyVariablesWritten=false; + context->guid=UNASSIGNED_RAKNET_GUID; + context->bitStream=_bitStream; + context->serializationMode=RELIABLE; + if (context->variableHistoryIdentical==0) + context->variableHistoryIdentical=StartVariableHistoryWrite(UNASSIGNED_RAKNET_GUID); + context->variableHistory=context->variableHistoryIdentical; + context->newSystemSend=_isFirstSendToRemoteSystem; +} + +void VariableDeltaSerializer::EndSerialize(SerializationContext *context) +{ + if (context->serializationMode==UNRELIABLE_WITH_ACK_RECEIPT) + { + if (context->anyVariablesWritten==false) + { + context->bitStream->Reset(); + FreeChangedVariablesList(context->changedVariables); + return; + } + + StoreChangedVariablesList(context->variableHistory, context->changedVariables, context->sendReceipt); + } + else + { + if (context->variableHistoryIdentical) + { + if (didComparisonThisTick==false) + { + didComparisonThisTick=true; + identicalSerializationBs.Reset(); + + if (context->anyVariablesWritten==false) + { + context->bitStream->Reset(); + return; + } + + identicalSerializationBs.Write(context->bitStream); + context->bitStream->ResetReadPointer(); + } + else + { + context->bitStream->Write(&identicalSerializationBs); + identicalSerializationBs.ResetReadPointer(); + } + } + else if (context->anyVariablesWritten==false) + { + context->bitStream->Reset(); + return; + } + } +} + +void VariableDeltaSerializer::BeginDeserialize(DeserializationContext *context, BitStream *_bitStream) +{ + context->bitStream=_bitStream; +} + +void VariableDeltaSerializer::EndDeserialize(DeserializationContext *context) +{ + (void) context; +} + +void VariableDeltaSerializer::AddRemoteSystemVariableHistory(RakNetGUID guid) +{ + (void) guid; +} + +void VariableDeltaSerializer::RemoveRemoteSystemVariableHistory(RakNetGUID guid) +{ + unsigned int idx,idx2; + idx = GetVarsWrittenPerRemoteSystemListIndex(guid); + if (idx==(unsigned int)-1) + return; + + if (remoteSystemVariableHistoryList[idx]->guid==guid) + { + // Memory pool doesn't call destructor + for (idx2=0; idx2 < remoteSystemVariableHistoryList[idx]->updatedVariablesHistory.Size(); idx2++) + { + FreeChangedVariablesList(remoteSystemVariableHistoryList[idx]->updatedVariablesHistory[idx2]); + } + + delete remoteSystemVariableHistoryList[idx]; + remoteSystemVariableHistoryList.RemoveAtIndexFast(idx); + return; + } +} + +int VariableDeltaSerializer::UpdatedVariablesListPtrComp( const uint32_t &key, ChangedVariablesList* const &data ) +{ + if (keysendReceipt) + return -1; + if (key==data->sendReceipt) + return 0; + return 1; +} + +void VariableDeltaSerializer::FreeVarsAssociatedWithReceipt(RakNetGUID guid, uint32_t receiptId) +{ + unsigned int idx, idx2; + idx = GetVarsWrittenPerRemoteSystemListIndex(guid); + if (idx==(unsigned int)-1) + return; + + RemoteSystemVariableHistory* vprs = remoteSystemVariableHistoryList[idx]; + bool objectExists; + idx2=vprs->updatedVariablesHistory.GetIndexFromKey(receiptId,&objectExists); + if (objectExists) + { + // Free this history node + FreeChangedVariablesList(vprs->updatedVariablesHistory[idx2]); + vprs->updatedVariablesHistory.RemoveAtIndex(idx2); + } +} + +void VariableDeltaSerializer::DirtyAndFreeVarsAssociatedWithReceipt(RakNetGUID guid, uint32_t receiptId) +{ + unsigned int idx, idx2; + idx = GetVarsWrittenPerRemoteSystemListIndex(guid); + if (idx==(unsigned int)-1) + return; + + RemoteSystemVariableHistory* vprs = remoteSystemVariableHistoryList[idx]; + bool objectExists; + idx2=vprs->updatedVariablesHistory.GetIndexFromKey(receiptId,&objectExists); + if (objectExists) + { + // 'Dirty' all variables sent this update, meaning they will be resent the next time Serialize() is called + vprs->variableListDeltaTracker.FlagDirtyFromBitArray(vprs->updatedVariablesHistory[idx2]->bitField); + + // Free this history node + FreeChangedVariablesList(vprs->updatedVariablesHistory[idx2]); + vprs->updatedVariablesHistory.RemoveAtIndex(idx2); + } +} +unsigned int VariableDeltaSerializer::GetVarsWrittenPerRemoteSystemListIndex(RakNetGUID guid) +{ + unsigned int idx; + for (idx=0; idx < remoteSystemVariableHistoryList.Size(); idx++) + { + if (remoteSystemVariableHistoryList[idx]->guid==guid) + return idx; + } + return (unsigned int) -1; +} +void VariableDeltaSerializer::RemoveRemoteSystemVariableHistory(void) +{ + unsigned int idx,idx2; + for (idx=0; idx < remoteSystemVariableHistoryList.Size(); idx++) + { + for (idx2=0; idx2 < remoteSystemVariableHistoryList[idx]->updatedVariablesHistory.Size(); idx2++) + { + FreeChangedVariablesList(remoteSystemVariableHistoryList[idx]->updatedVariablesHistory[idx2]); + } + + delete remoteSystemVariableHistoryList[idx]; + } + remoteSystemVariableHistoryList.Clear(false,__FILE__,__LINE__); +} + +VariableDeltaSerializer::RemoteSystemVariableHistory* VariableDeltaSerializer::GetRemoteSystemVariableHistory(RakNetGUID guid) +{ + unsigned int rshli = GetRemoteSystemHistoryListIndex(guid); + return remoteSystemVariableHistoryList[rshli]; +} + +VariableDeltaSerializer::ChangedVariablesList *VariableDeltaSerializer::AllocChangedVariablesList(void) +{ + VariableDeltaSerializer::ChangedVariablesList *p = updatedVariablesMemoryPool.Allocate(__FILE__,__LINE__); + p->bitWriteIndex=0; + p->bitField[0]=0; + return p; +} +void VariableDeltaSerializer::FreeChangedVariablesList(ChangedVariablesList *changedVariables) +{ + updatedVariablesMemoryPool.Release(changedVariables, __FILE__,__LINE__); +} +void VariableDeltaSerializer::StoreChangedVariablesList(RemoteSystemVariableHistory *variableHistory, ChangedVariablesList *changedVariables, uint32_t sendReceipt) +{ + changedVariables->sendReceipt=sendReceipt; + variableHistory->updatedVariablesHistory.Insert(changedVariables->sendReceipt,changedVariables,true,__FILE__,__LINE__); +} + +VariableDeltaSerializer::RemoteSystemVariableHistory *VariableDeltaSerializer::StartVariableHistoryWrite(RakNetGUID guid) +{ + RemoteSystemVariableHistory *variableHistory; + + unsigned int rshli = GetRemoteSystemHistoryListIndex(guid); + if (rshli==(unsigned int) -1) + { + variableHistory = new RemoteSystemVariableHistory; + variableHistory->guid=guid; + remoteSystemVariableHistoryList.Push(variableHistory,__FILE__,__LINE__); + } + else + { + variableHistory=remoteSystemVariableHistoryList[rshli]; + } + + variableHistory->variableListDeltaTracker.StartWrite(); + return variableHistory; +} +unsigned int VariableDeltaSerializer::GetRemoteSystemHistoryListIndex(RakNetGUID guid) +{ + // Find the variable tracker for the target system + unsigned int idx; + for (idx=0; idx < remoteSystemVariableHistoryList.Size(); idx++) + { + if (remoteSystemVariableHistoryList[idx]->guid==guid) + { + return idx; + } + } + return (unsigned int) -1; +} + +void VariableDeltaSerializer::OnPreSerializeTick(void) +{ + didComparisonThisTick=false; +} diff --git a/RakNet/Sources/VariableDeltaSerializer.h b/RakNet/Sources/VariableDeltaSerializer.h new file mode 100644 index 0000000..d8ba5d7 --- /dev/null +++ b/RakNet/Sources/VariableDeltaSerializer.h @@ -0,0 +1,257 @@ +#ifndef __VARIABLE_DELTA_SERIALIZER_H +#define __VARIABLE_DELTA_SERIALIZER_H + +#include "VariableListDeltaTracker.h" +#include "DS_MemoryPool.h" +#include "NativeTypes.h" +#include "BitStream.h" +#include "PacketPriority.h" +#include "DS_OrderedList.h" + +namespace RakNet +{ + +/// \brief Class to compare memory values of variables in a current state to a prior state +/// Results of the comparisons will be written to a bitStream, such that only changed variables get written
      +/// Can be used with ReplicaManager3 to Serialize a Replica3 per-variable, rather than comparing the entire object against itself
      +/// Usage:
      +///
      +/// 1. Call BeginUnreliableAckedSerialize(), BeginUniqueSerialize(), or BeginIdenticalSerialize(). In the case of Replica3, this would be in the Serialize() call
      +/// 2. For each variable of the type in step 1, call Serialize(). The same variables must be serialized every tick()
      +/// 3. Call EndSerialize()
      +/// 4. Repeat step 1 for each of the other categories of how to send varaibles +/// +/// On the receiver: +/// +/// 1. Call BeginDeserialize(). In the case of Replica3, this would be in the Deserialize() call +/// 2. Call DeserializeVariable() for each variable, in the same order as was Serialized() +/// 3. Call EndSerialize() +/// \sa The ReplicaManager3 sample +class VariableDeltaSerializer +{ +protected: + struct RemoteSystemVariableHistory; + struct ChangedVariablesList; + +public: + VariableDeltaSerializer(); + ~VariableDeltaSerializer(); + + struct SerializationContext + { + SerializationContext(); + ~SerializationContext(); + + RakNetGUID guid; + BitStream *bitStream; + uint32_t rakPeerSendReceipt; + RemoteSystemVariableHistory *variableHistory; + RemoteSystemVariableHistory *variableHistoryIdentical; + RemoteSystemVariableHistory *variableHistoryUnique; + ChangedVariablesList *changedVariables; + uint32_t sendReceipt; + PacketReliability serializationMode; + bool anyVariablesWritten; + bool newSystemSend; // Force send all, do not record + }; + + struct DeserializationContext + { + BitStream *bitStream; + }; + + /// \brief Call before doing one or more SerializeVariable calls when the data will be sent UNRELIABLE_WITH_ACK_RECEIPT + /// The last value of each variable will be saved per remote system. Additionally, a history of \a _sendReceipts is stored to determine what to resend on packetloss. + /// When variables are lost, they will be flagged dirty and always resent to the system that lost it + /// Disadvantages: Every variable for every remote system is copied internally, in addition to a history list of what variables changed for which \a _sendReceipt. Very memory and CPU intensive for multiple connections. + /// Advantages: When data needs to be resent by RakNet, RakNet can only resend the value it currently has. This allows the application to control the resend, sending the most recent value of the variable. The end result is that bandwidth is used more efficiently because old data is never sent. + /// \pre Upon getting ID_SND_RECEIPT_LOSS or ID_SND_RECEIPT_ACKED call OnMessageReceipt() + /// \pre AddRemoteSystemVariableHistory() and RemoveRemoteSystemVariableHistory() must be called for new and lost connections + /// \param[in] context Holds the context of this group of serialize calls. This can be a stack object just passed to the function. + /// \param[in] _guid Which system we are sending to + /// \param[in] _bitSteam Which bitStream to write to + /// \param[in] _sendReceipt Returned from RakPeer::IncrementNextSendReceipt() and passed to the Send() or SendLists() function. Identifies this update for ID_SND_RECEIPT_LOSS and ID_SND_RECEIPT_ACKED + void BeginUnreliableAckedSerialize(SerializationContext *context, RakNetGUID _guid, BitStream *_bitStream, uint32_t _sendReceipt); + + /// \brief Call before doing one or more SerializeVariable calls for data that may be sent differently to every remote system (such as an invisibility flag that only teammates can see) + /// The last value of each variable will be saved per remote system. + /// Unlike BeginUnreliableAckedSerialize(), send receipts are not necessary + /// Disadvantages: Every variable for every remote system is copied internally. Very memory and CPU intensive for multiple connections. + /// Advantages: When data is sent differently depending on the recipient, this system can make things easier to use and is as efficient as it can be. + /// \pre AddRemoteSystemVariableHistory() and RemoveRemoteSystemVariableHistory() must be called for new and lost connections + /// \param[in] context Holds the context of this group of serialize calls. This can be a stack object just passed to the function. + /// \param[in] _guid Which system we are sending to + /// \param[in] _bitSteam Which bitStream to write to + void BeginUniqueSerialize(SerializationContext *context, RakNetGUID _guid, BitStream *_bitStream); + + /// \brief Call before doing one or more SerializeVariable calls for data that is sent with the same value to every remote system (such as health, position, etc.) + /// This is the most common type of serialization, and also the most efficient + /// Disadvantages: A copy of every variable still needs to be held, although only once + /// Advantages: After the first serialization, the last serialized bitStream will be used for subsequent sends + /// \pre Call OnPreSerializeTick() before doing any calls to BeginIdenticalSerialize() for each of your objects, once per game tick + /// \param[in] context Holds the context of this group of serialize calls. This can be a stack object just passed to the function. + /// \param[in] _isFirstSerializeToThisSystem Pass true if this is the first time ever serializing to this system (the initial download). This way all variables will be written, rather than checking against prior sent values. + /// \param[in] _bitSteam Which bitStream to write to + void BeginIdenticalSerialize(SerializationContext *context, bool _isFirstSerializeToThisSystem, BitStream *_bitStream); + + /// \brief Call after BeginUnreliableAckedSerialize(), BeginUniqueSerialize(), or BeginIdenticalSerialize(), then after calling SerializeVariable() one or more times + /// \param[in] context Same context pointer passed to BeginUnreliableAckedSerialize(), BeginUniqueSerialize(), or BeginIdenticalSerialize() + void EndSerialize(SerializationContext *context); + + /// \brief Call when you receive the BitStream written by SerializeVariable(), before calling DeserializeVariable() + /// \param[in] context Holds the context of this group of deserialize calls. This can be a stack object just passed to the function. + /// \param[in] _bitStream Pass the bitStream originally passed to and written to by serialize calls + void BeginDeserialize(DeserializationContext *context, BitStream *_bitStream); + + /// \param[in] context Same context pointer passed to BeginDeserialize() + void EndDeserialize(DeserializationContext *context); + + /// BeginUnreliableAckedSerialize() and BeginUniqueSerialize() require knowledge of when connections are added and dropped + /// Call AddRemoteSystemVariableHistory() and RemoveRemoteSystemVariableHistory() to notify the system of these events + /// \param[in] _guid Which system we are sending to + void AddRemoteSystemVariableHistory(RakNetGUID guid); + + /// BeginUnreliableAckedSerialize() and BeginUniqueSerialize() require knowledge of when connections are added and dropped + /// Call AddRemoteSystemVariableHistory() and RemoveRemoteSystemVariableHistory() to notify the system of these events + /// \param[in] _guid Which system we are sending to + void RemoveRemoteSystemVariableHistory(RakNetGUID guid); + + /// BeginIdenticalSerialize() requires knowledge of when serialization has started for an object across multiple systems + /// This way it can setup the flag to do new comparisons against the last sent values, rather than just resending the last sent bitStream + /// For Replica3, overload and call this from Replica3::OnUserReplicaPreSerializeTick() + void OnPreSerializeTick(void); + + /// Call when getting ID_SND_RECEIPT_LOSS or ID_SND_RECEIPT_ACKED for a particular system + /// Example: + /// + /// uint32_t msgNumber; + /// memcpy(&msgNumber, packet->data+1, 4); + /// DataStructures::Multilist replicaListOut; + /// replicaManager.GetReplicasCreatedByMe(replicaListOut); + /// DataStructures::DefaultIndexType idx; + /// for (idx=0; idx < replicaListOut.GetSize(); idx++) + /// { + /// ((SampleReplica*)replicaListOut[idx])->NotifyReplicaOfMessageDeliveryStatus(packet->guid,msgNumber, packet->data[0]==ID_SND_RECEIPT_ACKED); + /// } + /// + /// \param[in] guid Which system we are sending to + /// \param[in] receiptId Encoded in bytes 1-4 inclusive of ID_SND_RECEIPT_LOSS and ID_SND_RECEIPT_ACKED + /// \param[in] messageArrived True for ID_SND_RECEIPT_ACKED, false otherwise + void OnMessageReceipt(RakNetGUID guid, uint32_t receiptId, bool messageArrived); + + /// Call to Serialize a variable + /// Will write to the bitSteam passed to \a context true, variableValue if the variable has changed or has never been written. Otherwise will write false. + /// \pre You have called BeginUnreliableAckedSerialize(), BeginUniqueSerialize(), or BeginIdenticalSerialize() + /// \pre Will also require calling OnPreSerializeTick() if using BeginIdenticalSerialize() + /// \note Be sure to call EndSerialize() after finishing all serializations + /// \param[in] context Same context pointer passed to BeginUnreliableAckedSerialize(), BeginUniqueSerialize(), or BeginIdenticalSerialize() + /// \param[in] variable A variable to write to the bitStream passed to \a context + template + void SerializeVariable(SerializationContext *context, const VarType &variable) + { + if (context->newSystemSend) + { + if (context->variableHistory->variableListDeltaTracker.IsPastEndOfList()==false) + { + // previously sent data to another system + context->bitStream->Write(true); + context->bitStream->Write(variable); + context->anyVariablesWritten=true; + } + else + { + // never sent data to another system + context->variableHistory->variableListDeltaTracker.WriteVarToBitstream(variable, context->bitStream); + context->anyVariablesWritten=true; + } + } + else if (context->serializationMode==UNRELIABLE_WITH_ACK_RECEIPT) + { + context->anyVariablesWritten|= + context->variableHistory->variableListDeltaTracker.WriteVarToBitstream(variable, context->bitStream, context->changedVariables->bitField, context->changedVariables->bitWriteIndex++); + } + else + { + if (context->variableHistoryIdentical) + { + // Identical serialization to a number of systems + if (didComparisonThisTick==false) + context->anyVariablesWritten|= + context->variableHistory->variableListDeltaTracker.WriteVarToBitstream(variable, context->bitStream); + // Else bitstream is written to at the end + } + else + { + // Per-system serialization + context->anyVariablesWritten|= + context->variableHistory->variableListDeltaTracker.WriteVarToBitstream(variable, context->bitStream); + } + } + } + + /// Call to deserialize into a variable + /// \pre You have called BeginDeserialize() + /// \note Be sure to call EndDeserialize() after finishing all deserializations + /// \param[in] context Same context pointer passed to BeginDeserialize() + /// \param[in] variable A variable to write to the bitStream passed to \a context + template + bool DeserializeVariable(DeserializationContext *context, VarType &variable) + { + return VariableListDeltaTracker::ReadVarFromBitstream(variable, context->bitStream); + } + + + +protected: + + // For a given send receipt from RakPeer::Send() track which variables we updated + // That way if that send does not arrive (ID_SND_RECEIPT_LOSS) we can mark those variables as dirty to resend them with current values + struct ChangedVariablesList + { + uint32_t sendReceipt; + unsigned short bitWriteIndex; + unsigned char bitField[56]; + }; + + // static int Replica2ObjectComp( const uint32_t &key, ChangedVariablesList* const &data ); + + static int UpdatedVariablesListPtrComp( const uint32_t &key, ChangedVariablesList* const &data ); + + // For each remote system, track the last values of variables we sent to them, and the history of what values changed per call to Send() + // Every serialize if a variable changes from its last value, send it out again + // Also if a send does not arrive (ID_SND_RECEIPT_LOSS) we use updatedVariablesHistory to mark those variables as dirty, to resend them unreliably with the current values + struct RemoteSystemVariableHistory + { + RakNetGUID guid; + VariableListDeltaTracker variableListDeltaTracker; + DataStructures::OrderedList updatedVariablesHistory; + }; + /// A list of RemoteSystemVariableHistory indexed by guid, one per connection that we serialize to + /// List is added to when SerializeConstruction is called, and removed from when SerializeDestruction is called, or when a given connection is dropped + DataStructures::List remoteSystemVariableHistoryList; + + // Because the ChangedVariablesList is created every serialize and destroyed every receipt I use a pool to avoid fragmentation + DataStructures::MemoryPool updatedVariablesMemoryPool; + + bool didComparisonThisTick; + RakNet::BitStream identicalSerializationBs; + + void FreeVarsAssociatedWithReceipt(RakNetGUID guid, uint32_t receiptId); + void DirtyAndFreeVarsAssociatedWithReceipt(RakNetGUID guid, uint32_t receiptId); + unsigned int GetVarsWrittenPerRemoteSystemListIndex(RakNetGUID guid); + void RemoveRemoteSystemVariableHistory(void); + + RemoteSystemVariableHistory* GetRemoteSystemVariableHistory(RakNetGUID guid); + + ChangedVariablesList *AllocChangedVariablesList(void); + void FreeChangedVariablesList(ChangedVariablesList *changedVariables); + void StoreChangedVariablesList(RemoteSystemVariableHistory *variableHistory, ChangedVariablesList *changedVariables, uint32_t sendReceipt); + + RemoteSystemVariableHistory *StartVariableHistoryWrite(RakNetGUID guid); + unsigned int GetRemoteSystemHistoryListIndex(RakNetGUID guid); + +}; + +} + +#endif diff --git a/RakNet/Sources/VariableListDeltaTracker.cpp b/RakNet/Sources/VariableListDeltaTracker.cpp new file mode 100644 index 0000000..cdbe96e --- /dev/null +++ b/RakNet/Sources/VariableListDeltaTracker.cpp @@ -0,0 +1,40 @@ +#include "VariableListDeltaTracker.h" + +using namespace RakNet; + +VariableListDeltaTracker::VariableListDeltaTracker() {nextWriteIndex=0;} +VariableListDeltaTracker::~VariableListDeltaTracker() +{ + unsigned int i; + for (i=0; i < variableList.Size(); i++) + rakFree_Ex(variableList[i].lastData,__FILE__,__LINE__); +} + +// Call before using a series of WriteVar +void VariableListDeltaTracker::StartWrite(void) {nextWriteIndex=0;} + +void VariableListDeltaTracker::FlagDirtyFromBitArray(unsigned char *bArray) +{ + unsigned short readOffset=0; + for (readOffset=0; readOffset < variableList.Size(); readOffset++) + { + bool result = ( bArray[ readOffset >> 3 ] & ( 0x80 >> ( readOffset & 7 ) ) ) !=0; + + if (result==true) + variableList[readOffset].isDirty=true; + } +} +VariableListDeltaTracker::VariableLastValueNode::VariableLastValueNode() +{ + lastData=0; +} +VariableListDeltaTracker::VariableLastValueNode::VariableLastValueNode(const unsigned char *data, int _byteLength) +{ + lastData=(char*) rakMalloc_Ex(_byteLength,__FILE__,__LINE__); + memcpy(lastData,data,_byteLength); + byteLength=_byteLength; + isDirty=false; +} +VariableListDeltaTracker::VariableLastValueNode::~VariableLastValueNode() +{ +} \ No newline at end of file diff --git a/RakNet/Sources/VariableListDeltaTracker.h b/RakNet/Sources/VariableListDeltaTracker.h new file mode 100644 index 0000000..bc39d9c --- /dev/null +++ b/RakNet/Sources/VariableListDeltaTracker.h @@ -0,0 +1,131 @@ +#include "NativeTypes.h" +#include "DS_List.h" +#include "RakMemoryOverride.h" +#include "BitStream.h" + +namespace RakNet +{ +/// Class to write a series of variables, copy the contents to memory, and return if the newly written value is different than what was last written +/// Can also encode the reads, writes, and results directly to/from a bitstream +class VariableListDeltaTracker +{ +public: + VariableListDeltaTracker(); + ~VariableListDeltaTracker(); + + // Call before using a series of WriteVar + void StartWrite(void); + + bool IsPastEndOfList(void) const {return nextWriteIndex>=variableList.Size();} + + /// Records the passed value of the variable to memory, and returns true if the value is different from the write before that (or if it is the first write) + /// \pre Call StartWrite() before doing the first of a series of calls to WriteVar or other functions that call WriteVar + /// \note Variables must be of the same type, written in the same order, each time + template + bool WriteVar(const VarType &varData) + { + RakNet::BitStream temp; + temp.Write(varData); + if (nextWriteIndex>=variableList.Size()) + { + variableList.Push(VariableLastValueNode(temp.GetData(),temp.GetNumberOfBytesUsed()),__FILE__,__LINE__); + nextWriteIndex++; + return true; // Different because it's new + } + + if (temp.GetNumberOfBytesUsed()!=variableList[nextWriteIndex].byteLength) + { + variableList[nextWriteIndex].lastData=(char*) rakRealloc_Ex(variableList[nextWriteIndex].lastData, temp.GetNumberOfBytesUsed(),__FILE__,__LINE__); + variableList[nextWriteIndex].byteLength=temp.GetNumberOfBytesUsed(); + memcpy(variableList[nextWriteIndex].lastData,temp.GetData(),temp.GetNumberOfBytesUsed()); + nextWriteIndex++; + variableList[nextWriteIndex].isDirty=false; + return true; // Different because the serialized size is different + } + if (variableList[nextWriteIndex].isDirty==false && memcmp(temp.GetData(),variableList[nextWriteIndex].lastData, variableList[nextWriteIndex].byteLength)==0) + { + nextWriteIndex++; + return false; // Same because not dirty and memcmp is the same + } + + variableList[nextWriteIndex].isDirty=false; + memcpy(variableList[nextWriteIndex].lastData,temp.GetData(),temp.GetNumberOfBytesUsed()); + nextWriteIndex++; + return true; // Different because dirty or memcmp was different + } + /// Calls WriteVar. If the variable has changed, writes true, and writes the variable. Otherwise writes false. + template + bool WriteVarToBitstream(const VarType &varData, RakNet::BitStream *bitStream) + { + bool wasDifferent = WriteVar(varData); + bitStream->Write(wasDifferent); + if (wasDifferent) + { + bitStream->Write(varData); + return true; + } + return false; + } + /// Calls WriteVarToBitstream(). Additionally, adds the boolean result of WriteVar() to boolean bit array + template + bool WriteVarToBitstream(const VarType &varData, RakNet::BitStream *bitStream, unsigned char *bArray, unsigned short writeOffset) + { + if (WriteVarToBitstream(varData,bitStream)==true) + { + BitSize_t numberOfBitsMod8 = writeOffset & 7; + + if ( numberOfBitsMod8 == 0 ) + bArray[ writeOffset >> 3 ] = 0x80; + else + bArray[ writeOffset >> 3 ] |= 0x80 >> ( numberOfBitsMod8 ); // Set the bit to 1 + + return true; + } + else + { + if ( ( writeOffset & 7 ) == 0 ) + bArray[ writeOffset >> 3 ] = 0; + + return false; + } + } + + /// Paired with a call to WriteVarToBitstream(), will read a variable if it had changed. Otherwise the values remains the same. + template + static bool ReadVarFromBitstream(const VarType &varData, RakNet::BitStream *bitStream) + { + bool wasWritten; + if (bitStream->Read(wasWritten)==false) + return false; + if (wasWritten) + { + if (bitStream->Read(varData)==false) + return false; + } + return wasWritten; + } + + /// Variables flagged dirty will cause WriteVar() to return true, even if the variable had not otherwise changed + /// This updates all the variables in the list, where in each index \a varsWritten is true, so will the variable at the corresponding index be flagged dirty + void FlagDirtyFromBitArray(unsigned char *bArray); + + /// \internal + struct VariableLastValueNode + { + VariableLastValueNode(); + VariableLastValueNode(const unsigned char *data, int _byteLength); + ~VariableLastValueNode(); + char *lastData; + int byteLength; + bool isDirty; + }; + +protected: + /// \internal + DataStructures::List variableList; + /// \internal + unsigned int nextWriteIndex; +}; + + +} diff --git a/RakNet/Sources/VariadicSQLParser.cpp b/RakNet/Sources/VariadicSQLParser.cpp new file mode 100644 index 0000000..a831ab8 --- /dev/null +++ b/RakNet/Sources/VariadicSQLParser.cpp @@ -0,0 +1,146 @@ +#include "VariadicSQLParser.h" +#include "BitStream.h" +#include + +using namespace VariadicSQLParser; + +struct TypeMapping +{ + char inputType; + const char *type; +}; +const int NUM_TYPE_MAPPINGS=7; +TypeMapping typeMappings[NUM_TYPE_MAPPINGS] = +{ + {'i', "int"}, + {'d', "int"}, + {'s', "text"}, + {'b', "bool"}, + {'f', "numeric"}, + {'g', "double precision"}, + {'a', "bytea"}, +}; +unsigned int GetTypeMappingIndex(char c) +{ + unsigned int i; + for (i=0; i < NUM_TYPE_MAPPINGS; i++ ) + if (typeMappings[i].inputType==c) + return i; + return (unsigned int)-1; +} +const char* VariadicSQLParser::GetTypeMappingAtIndex(int i) +{ + return typeMappings[i].type; +} +void VariadicSQLParser::GetTypeMappingIndices( const char *format, DataStructures::List &indices ) +{ + bool previousCharWasPercentSign; + unsigned int i; + unsigned int typeMappingIndex; + indices.Clear(false, __FILE__, __LINE__); + unsigned int len = (unsigned int) strlen(format); + previousCharWasPercentSign=false; + for (i=0; i < len; i++) + { + if (previousCharWasPercentSign==true ) + { + typeMappingIndex = GetTypeMappingIndex(format[i]); + if (typeMappingIndex!=(unsigned int) -1) + { + IndexAndType iat; + iat.strIndex=i-1; + iat.typeMappingIndex=typeMappingIndex; + indices.Insert(iat, __FILE__, __LINE__ ); + } + } + + previousCharWasPercentSign=format[i]=='%'; + } +} +void VariadicSQLParser::ExtractArguments( va_list argptr, const DataStructures::List &indices, char ***argumentBinary, int **argumentLengths ) +{ + if (indices.Size()==0) + return; + + unsigned int i; + *argumentBinary=RakNet::OP_NEW_ARRAY(indices.Size(), __FILE__,__LINE__); + *argumentLengths=RakNet::OP_NEW_ARRAY(indices.Size(), __FILE__,__LINE__); + + char **paramData=*argumentBinary; + int *paramLength=*argumentLengths; + + int variadicArgIndex; + for (variadicArgIndex=0, i=0; i < indices.Size(); i++, variadicArgIndex++) + { + switch (typeMappings[indices[i].typeMappingIndex].inputType) + { + case 'i': + case 'd': + { + int val = va_arg( argptr, int ); + paramLength[i]=sizeof(val); + paramData[i]=(char*) rakMalloc_Ex(paramLength[i], __FILE__, __LINE__); + memcpy(paramData[i], &val, paramLength[i]); + if (RakNet::BitStream::IsNetworkOrder()==false) RakNet::BitStream::ReverseBytesInPlace((unsigned char*) paramData[i], paramLength[i]); + } + break; + case 's': + { + char* val = va_arg( argptr, char* ); + paramLength[i]=(int) strlen(val); + paramData[i]=(char*) rakMalloc_Ex(paramLength[i]+1, __FILE__, __LINE__); + memcpy(paramData[i], val, paramLength[i]+1); + } + break; + case 'b': + { + bool val = (va_arg( argptr, int )!=0); + paramLength[i]=sizeof(val); + paramData[i]=(char*) rakMalloc_Ex(paramLength[i], __FILE__, __LINE__); + memcpy(paramData[i], &val, paramLength[i]); + if (RakNet::BitStream::IsNetworkOrder()==false) RakNet::BitStream::ReverseBytesInPlace((unsigned char*) paramData[i], paramLength[i]); + } + break; + case 'f': + { + // On MSVC at least, this only works with double as the 2nd param + float val = (float) va_arg( argptr, double ); + //float val = va_arg( argptr, float ); + paramLength[i]=sizeof(val); + paramData[i]=(char*) rakMalloc_Ex(paramLength[i], __FILE__, __LINE__); + memcpy(paramData[i], &val, paramLength[i]); + if (RakNet::BitStream::IsNetworkOrder()==false) RakNet::BitStream::ReverseBytesInPlace((unsigned char*) paramData[i], paramLength[i]); + } + break; + case 'g': + { + double val = va_arg( argptr, double ); + paramLength[i]=sizeof(val); + paramData[i]=(char*) rakMalloc_Ex(paramLength[i], __FILE__, __LINE__); + memcpy(paramData[i], &val, paramLength[i]); + if (RakNet::BitStream::IsNetworkOrder()==false) RakNet::BitStream::ReverseBytesInPlace((unsigned char*) paramData[i], paramLength[i]); + } + break; + case 'a': + { + char* val = va_arg( argptr, char* ); + paramLength[i]=va_arg( argptr, unsigned int ); + paramData[i]=(char*) rakMalloc_Ex(paramLength[i], __FILE__, __LINE__); + memcpy(paramData[i], val, paramLength[i]); + } + break; + } + } + +} +void VariadicSQLParser::FreeArguments(const DataStructures::List &indices, char **argumentBinary, int *argumentLengths) +{ + if (indices.Size()==0) + return; + + unsigned int i; + for (i=0; i < indices.Size(); i++) + rakFree_Ex(argumentBinary[i],__FILE__,__LINE__); + RakNet::OP_DELETE_ARRAY(argumentBinary,__FILE__,__LINE__); + RakNet::OP_DELETE_ARRAY(argumentLengths,__FILE__,__LINE__); +} diff --git a/RakNet/Sources/VariadicSQLParser.h b/RakNet/Sources/VariadicSQLParser.h new file mode 100644 index 0000000..96b1c36 --- /dev/null +++ b/RakNet/Sources/VariadicSQLParser.h @@ -0,0 +1,24 @@ +#ifndef __VARIADIC_SQL_PARSER_H +#define __VARIADIC_SQL_PARSER_H + +#include "DS_List.h" + +#include + +namespace VariadicSQLParser +{ + struct IndexAndType + { + unsigned int strIndex; + unsigned int typeMappingIndex; + }; + const char* GetTypeMappingAtIndex(int i); + void GetTypeMappingIndices( const char *format, DataStructures::List &indices ); + // Given an SQL string with variadic arguments, allocate argumentBinary and argumentLengths, and hold the parameters in binary format + // Last 2 parameters are out parameters + void ExtractArguments( va_list argptr, const DataStructures::List &indices, char ***argumentBinary, int **argumentLengths ); + void FreeArguments(const DataStructures::List &indices, char **argumentBinary, int *argumentLengths); +} + + +#endif diff --git a/RakNet/Sources/WSAStartupSingleton.cpp b/RakNet/Sources/WSAStartupSingleton.cpp new file mode 100644 index 0000000..3db7a8a --- /dev/null +++ b/RakNet/Sources/WSAStartupSingleton.cpp @@ -0,0 +1,68 @@ +#include "WSAStartupSingleton.h" + +#if defined(_XBOX) || defined(X360) + +#elif defined(_WIN32) +#include +#include +#endif +#include "RakNetDefines.h" +#include + +int WSAStartupSingleton::refCount=0; + +WSAStartupSingleton::WSAStartupSingleton() {} +WSAStartupSingleton::~WSAStartupSingleton() {} +void WSAStartupSingleton::AddRef(void) +{ +#ifdef _WIN32 + + refCount++; + + if (refCount!=1) + return; + +#if defined(_XBOX) || defined(X360) + +#endif + + WSADATA winsockInfo; + if ( WSAStartup( MAKEWORD( 2, 2 ), &winsockInfo ) != 0 ) + { +#if !defined(_XBOX) && !defined(X360) && defined(_DEBUG) + DWORD dwIOError = GetLastError(); + LPVOID messageBuffer; + FormatMessage( FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, + NULL, dwIOError, MAKELANGID( LANG_NEUTRAL, SUBLANG_DEFAULT ), // Default language + ( LPTSTR ) & messageBuffer, 0, NULL ); + // something has gone wrong here... + RAKNET_DEBUG_PRINTF( "WSAStartup failed:Error code - %d\n%s", dwIOError, messageBuffer ); + //Free the buffer. + LocalFree( messageBuffer ); +#endif + } + +#endif +} +void WSAStartupSingleton::Deref(void) +{ +#ifdef _WIN32 + if (refCount==0) + return; + + if (refCount>1) + { + refCount--; + return; + } + + WSACleanup(); + +#if defined(_XBOX) || defined(X360) + +#endif + + + refCount=0; +#endif +} diff --git a/RakNet/Sources/WSAStartupSingleton.h b/RakNet/Sources/WSAStartupSingleton.h new file mode 100644 index 0000000..2ac9042 --- /dev/null +++ b/RakNet/Sources/WSAStartupSingleton.h @@ -0,0 +1,16 @@ +#ifndef __WSA_STARTUP_SINGLETON_H +#define __WSA_STARTUP_SINGLETON_H + +class WSAStartupSingleton +{ +public: + WSAStartupSingleton(); + ~WSAStartupSingleton(); + static void AddRef(void); + static void Deref(void); + +protected: + static int refCount; +}; + +#endif diff --git a/RakNet/Sources/WindowsIncludes.h b/RakNet/Sources/WindowsIncludes.h new file mode 100644 index 0000000..47b4729 --- /dev/null +++ b/RakNet/Sources/WindowsIncludes.h @@ -0,0 +1,16 @@ +#if defined(__X360__) || defined(_XBOX) +#elif defined (_WIN32) +#include +#include + +// Must always include Winsock2.h before windows.h +// or else: +// winsock2.h(99) : error C2011: 'fd_set' : 'struct' type redefinition +// winsock2.h(134) : warning C4005: 'FD_SET' : macro redefinition +// winsock.h(83) : see previous definition of 'FD_SET' +// winsock2.h(143) : error C2011: 'timeval' : 'struct' type redefinition +// winsock2.h(199) : error C2011: 'hostent' : 'struct' type redefinition +// winsock2.h(212) : error C2011: 'netent' : 'struct' type redefinition +// winsock2.h(219) : error C2011: 'servent' : 'struct' type redefinition + +#endif diff --git a/RakNet/Sources/_FindFirst.cpp b/RakNet/Sources/_FindFirst.cpp new file mode 100644 index 0000000..fecd8c6 --- /dev/null +++ b/RakNet/Sources/_FindFirst.cpp @@ -0,0 +1,132 @@ +/** +* Original file by the_viking, fixed by R√¥mulo Fernandes, fixed by Emmanuel Nars +* Should emulate windows finddata structure +*/ +#if (defined(__GNUC__) || defined(__GCCXML__)) && !defined(__WIN32) +#include "_FindFirst.h" +#include "DS_List.h" + +#include +#if !defined(_PS3) && !defined(__PS3__) && !defined(SN_TARGET_PS3) +#include +#endif + +static DataStructures::List< _findinfo_t* > fileInfo; + +#include "RakMemoryOverride.h" +#include "RakAssert.h" + +/** +* _findfirst - equivalent +*/ +long _findfirst(const char *name, _finddata_t *f) +{ + RakNet::RakString nameCopy = name; + RakNet::RakString filter; + + // This is linux only, so don't bother with '\' + const char* lastSep = strrchr(name,'/'); + if(!lastSep) + { + // filter pattern only is given, search current directory. + filter = nameCopy; + nameCopy = "."; + } else + { + // strip filter pattern from directory name, leave + // trailing '/' intact. + filter = lastSep+1; + unsigned sepIndex = lastSep - name; + nameCopy.Erase(sepIndex+1, nameCopy.GetLength() - sepIndex-1); + } + + DIR* dir = opendir(nameCopy); + + if(!dir) return -1; + + _findinfo_t* fi = RakNet::OP_NEW<_findinfo_t>( __FILE__, __LINE__ ); + fi->filter = filter; + fi->dirName = nameCopy; // we need to remember this for stat() + fi->openedDir = dir; + fileInfo.Insert(fi, __FILE__, __LINE__); + + long ret = fileInfo.Size()-1; + + // Retrieve the first file. We cannot rely on the first item + // being '.' + if (_findnext(ret, f) == -1) return -1; + else return ret; +} + +#if defined(_PS3) || defined(__PS3__) || defined(SN_TARGET_PS3) + +#else +int _findnext(long h, _finddata_t *f) +{ + RakAssert(h >= 0 && h < (long)fileInfo.Size()); + if (h < 0 || h >= (long)fileInfo.Size()) return -1; + + _findinfo_t* fi = fileInfo[h]; + + while(true) + { + dirent* entry = readdir(fi->openedDir); + if(entry == 0) return -1; + + // Only report stuff matching our filter + if (fnmatch(fi->filter, entry->d_name, FNM_PATHNAME) != 0) continue; + + // To reliably determine the entry's type, we must do + // a stat... don't rely on entry->d_type, as this + // might be unavailable! + struct stat filestat; + RakNet::RakString fullPath = fi->dirName + entry->d_name; + if (stat(fullPath, &filestat) != 0) + { + RAKNET_DEBUG_PRINTF("Cannot stat %s\n", fullPath.C_String()); + continue; + } + + if (S_ISREG(filestat.st_mode)) + { + f->attrib = _A_NORMAL; + } else if (S_ISDIR(filestat.st_mode)) + { + f->attrib = _A_SUBDIR; + } else continue; // We are interested in files and + // directories only. Links currently + // are not supported. + + f->size = filestat.st_size; + strncpy(f->name, entry->d_name, STRING_BUFFER_SIZE); + + return 0; + } + + return -1; +} +#endif + + + + +/** + * _findclose - equivalent + */ +int _findclose(long h) +{ + if (h==-1) return 0; + + if (h < 0 || h >= (long)fileInfo.Size()) + { + RakAssert(false); + return -1; + } + + _findinfo_t* fi = fileInfo[h]; + closedir(fi->openedDir); + fileInfo.RemoveAtIndex(h); + RakNet::OP_DELETE(fi, __FILE__, __LINE__); + return 0; +} +#endif diff --git a/RakNet/Sources/_FindFirst.h b/RakNet/Sources/_FindFirst.h new file mode 100644 index 0000000..de49f88 --- /dev/null +++ b/RakNet/Sources/_FindFirst.h @@ -0,0 +1,56 @@ +/// +/// Original file by the_viking, fixed by Rômulo Fernandes +/// Should emulate windows finddata structure +/// + +#ifndef GCC_FINDFIRST_H +#define GCC_FINDFIRST_H + +#if (defined(__GNUC__) || defined(__GCCXML__)) && !defined(__WIN32) + +#include + +#include "RakString.h" + +#define _A_NORMAL 0x00 // Normal file +#define _A_RDONLY 0x01 // Read-only file +#define _A_HIDDEN 0x02 // Hidden file +#define _A_SYSTEM 0x04 // System file +#define _A_VOLID 0x08 // Volume ID +#define _A_SUBDIR 0x10 // Subdirectory +#define _A_ARCH 0x20 // File changed since last archive +#define FA_NORMAL 0x00 // Synonym of _A_NORMAL +#define FA_RDONLY 0x01 // Synonym of _A_RDONLY +#define FA_HIDDEN 0x02 // Synonym of _A_HIDDEN +#define FA_SYSTEM 0x04 // Synonym of _A_SYSTEM +#define FA_LABEL 0x08 // Synonym of _A_VOLID +#define FA_DIREC 0x10 // Synonym of _A_SUBDIR +#define FA_ARCH 0x20 // Synonym of _A_ARCH + + +const unsigned STRING_BUFFER_SIZE = 512; + +typedef struct _finddata_t +{ + char name[STRING_BUFFER_SIZE]; + int attrib; + unsigned long size; +} _finddata; + +/** + * Hold information about the current search + */ +typedef struct _findinfo_t +{ + DIR* openedDir; + RakNet::RakString filter; + RakNet::RakString dirName; +} _findinfo; + +long _findfirst(const char *name, _finddata_t *f); +int _findnext(long h, _finddata_t *f); +int _findclose(long h); + +#endif +#endif + diff --git a/RakNet/Sources/gettimeofday.cpp b/RakNet/Sources/gettimeofday.cpp new file mode 100644 index 0000000..8b3ddf1 --- /dev/null +++ b/RakNet/Sources/gettimeofday.cpp @@ -0,0 +1,55 @@ +#if defined(_XBOX) || defined(X360) + +#endif + +#if defined(_WIN32) && !defined(__GNUC__) &&!defined(__GCCXML__) + +#include "gettimeofday.h" + +// From http://www.openasthra.com/c-tidbits/gettimeofday-function-for-windows/ + +#include "WindowsIncludes.h" + +#if defined(_MSC_VER) || defined(_MSC_EXTENSIONS) + #define DELTA_EPOCH_IN_MICROSECS 11644473600000000Ui64 +#else + #define DELTA_EPOCH_IN_MICROSECS 11644473600000000ULL +#endif + +int gettimeofday(struct timeval *tv, struct timezone *tz) +{ + FILETIME ft; + unsigned __int64 tmpres = 0; + static int tzflag; + + if (NULL != tv) + { + GetSystemTimeAsFileTime(&ft); + + tmpres |= ft.dwHighDateTime; + tmpres <<= 32; + tmpres |= ft.dwLowDateTime; + + /*converting file time to unix epoch*/ + tmpres /= 10; /*convert into microseconds*/ + tmpres -= DELTA_EPOCH_IN_MICROSECS; + tv->tv_sec = (long)(tmpres / 1000000UL); + tv->tv_usec = (long)(tmpres % 1000000UL); + } + + if (NULL != tz) + { + if (!tzflag) + { + _tzset(); + tzflag++; + } + tz->tz_minuteswest = _timezone / 60; + tz->tz_dsttime = _daylight; + } + + return 0; +} + +#endif + diff --git a/RakNet/Sources/gettimeofday.h b/RakNet/Sources/gettimeofday.h new file mode 100644 index 0000000..b851b78 --- /dev/null +++ b/RakNet/Sources/gettimeofday.h @@ -0,0 +1,49 @@ +#ifndef __GET_TIME_OF_DAY_H +#define __GET_TIME_OF_DAY_H + +#if defined(_WIN32) && !defined(__GNUC__) &&!defined(__GCCXML__) +#include < time.h > +struct timezone +{ + int tz_minuteswest; /* minutes W of Greenwich */ + int tz_dsttime; /* type of dst correction */ +}; +int gettimeofday(struct timeval *tv, struct timezone *tz); +#else +#include +#include + +// Uncomment this if you need to +/* +// http://www.halcode.com/archives/2008/08/26/retrieving-system-time-gettimeofday/ +struct timezone +{ + int tz_minuteswest; + int tz_dsttime; +}; + +#ifdef __cplusplus + +void GetSystemTimeAsFileTime(FILETIME*); + +inline int gettimeofday(struct timeval* p, void* tz ) +{ + union { + long long ns100; // time since 1 Jan 1601 in 100ns units + FILETIME ft; + } now; + + GetSystemTimeAsFileTime( &(now.ft) ); + p->tv_usec=(long)((now.ns100 / 10LL) % 1000000LL ); + p->tv_sec= (long)((now.ns100-(116444736000000000LL))/10000000LL); + return 0; +} + +#else + int gettimeofday(struct timeval* p, void* tz ); +#endif +*/ + +#endif + +#endif diff --git a/RakNet/Sources/rdlmalloc-options.h b/RakNet/Sources/rdlmalloc-options.h new file mode 100644 index 0000000..9f2e091 --- /dev/null +++ b/RakNet/Sources/rdlmalloc-options.h @@ -0,0 +1,28 @@ +#ifdef _RAKNET_SUPPORT_DL_MALLOC + +#ifndef __DLMALLOC_OPTIONS_H +#define __DLMALLOC_OPTIONS_H + +#include "NativeTypes.h" +#include "Export.h" +#include +#include + +#define MSPACES 1 +#define ONLY_MSPACES 1 +#define USE_DL_PREFIX 1 +#define NO_MALLINFO 1 +// Make it threadsafe +#define USE_LOCKS 1 + +extern RAK_DLL_EXPORT void * (*dlMallocMMap) (size_t size); +extern RAK_DLL_EXPORT void * (*dlMallocDirectMMap) (size_t size); +extern RAK_DLL_EXPORT int (*dlMallocMUnmap) (void* ptr, size_t size); + +#define MMAP(s) dlMallocMMap(s) +#define MUNMAP(a, s) dlMallocMUnmap((a), (s)) +#define DIRECT_MMAP(s) dlMallocDirectMMap(s) + +#endif + +#endif // _RAKNET_SUPPORT_DL_MALLOC \ No newline at end of file diff --git a/RakNet/Sources/rdlmalloc.cpp b/RakNet/Sources/rdlmalloc.cpp new file mode 100644 index 0000000..fa3e62c --- /dev/null +++ b/RakNet/Sources/rdlmalloc.cpp @@ -0,0 +1,4097 @@ +#ifdef _RAKNET_SUPPORT_DL_MALLOC + +#include "rdlmalloc.h" + +/* --------------------------- Lock preliminaries ------------------------ */ + +/* +When locks are defined, there is one global lock, plus +one per-mspace lock. + +The global lock_ensures that mparams.magic and other unique +mparams values are initialized only once. It also protects +sequences of calls to MORECORE. In many cases sys_alloc requires +two calls, that should not be interleaved with calls by other +threads. This does not protect against direct calls to MORECORE +by other threads not using this lock, so there is still code to +cope the best we can on interference. + +Per-mspace locks surround calls to malloc, free, etc. To enable use +in layered extensions, per-mspace locks are reentrant. + +Because lock-protected regions generally have bounded times, it is +OK to use the supplied simple spinlocks in the custom versions for +x86. Spinlocks are likely to improve performance for lightly +contended applications, but worsen performance under heavy +contention. + +If USE_LOCKS is > 1, the definitions of lock routines here are +bypassed, in which case you will need to define the type MLOCK_T, +and at least INITIAL_LOCK, ACQUIRE_LOCK, RELEASE_LOCK and possibly +TRY_LOCK (which is not used in this malloc, but commonly needed in +extensions.) You must also declare a +static MLOCK_T malloc_global_mutex = { initialization values };. + +*/ + +#if USE_LOCKS == 1 + +#if USE_SPIN_LOCKS && SPIN_LOCKS_AVAILABLE +#if defined(_XBOX) || defined(X360) + +#elif !defined(DL_PLATFORM_WIN32) + +/* Custom pthread-style spin locks on x86 and x64 for gcc */ +struct pthread_mlock_t { + volatile unsigned int l; + unsigned int c; + pthread_t threadid; +}; +#define MLOCK_T struct pthread_mlock_t +#define CURRENT_THREAD pthread_self() +#define INITIAL_LOCK(sl) ((sl)->threadid = 0, (sl)->l = (sl)->c = 0, 0) +#define ACQUIRE_LOCK(sl) pthread_acquire_lock(sl) +#define RELEASE_LOCK(sl) pthread_release_lock(sl) +#define TRY_LOCK(sl) pthread_try_lock(sl) +#define SPINS_PER_YIELD 63 + +static MLOCK_T malloc_global_mutex = { 0, 0, 0}; + +static FORCEINLINE int pthread_acquire_lock (MLOCK_T *sl) { + int spins = 0; + volatile unsigned int* lp = &sl->l; + for (;;) { + if (*lp != 0) { + if (sl->threadid == CURRENT_THREAD) { + ++sl->c; + return 0; + } + } + else { + /* place args to cmpxchgl in locals to evade oddities in some gccs */ + int cmp = 0; + int val = 1; + int ret; + __asm__ __volatile__ ("lock; cmpxchgl %1, %2" + : "=a" (ret) + : "r" (val), "m" (*(lp)), "0"(cmp) + : "memory", "cc"); + if (!ret) { + assert(!sl->threadid); + sl->threadid = CURRENT_THREAD; + sl->c = 1; + return 0; + } + } + if ((++spins & SPINS_PER_YIELD) == 0) { +#if defined (__SVR4) && defined (__sun) /* solaris */ + thr_yield(); +#else +#if defined(__linux__) || defined(__FreeBSD__) || defined(__APPLE__) + sched_yield(); +#else /* no-op yield on unknown systems */ + ; +#endif /* __linux__ || __FreeBSD__ || __APPLE__ */ +#endif /* solaris */ + } + } +} + +static FORCEINLINE void pthread_release_lock (MLOCK_T *sl) { + volatile unsigned int* lp = &sl->l; + assert(*lp != 0); + assert(sl->threadid == CURRENT_THREAD); + if (--sl->c == 0) { + sl->threadid = 0; + int prev = 0; + int ret; + __asm__ __volatile__ ("lock; xchgl %0, %1" + : "=r" (ret) + : "m" (*(lp)), "0"(prev) + : "memory"); + } +} + +static FORCEINLINE int pthread_try_lock (MLOCK_T *sl) { + volatile unsigned int* lp = &sl->l; + if (*lp != 0) { + if (sl->threadid == CURRENT_THREAD) { + ++sl->c; + return 1; + } + } + else { + int cmp = 0; + int val = 1; + int ret; + __asm__ __volatile__ ("lock; cmpxchgl %1, %2" + : "=a" (ret) + : "r" (val), "m" (*(lp)), "0"(cmp) + : "memory", "cc"); + if (!ret) { + assert(!sl->threadid); + sl->threadid = CURRENT_THREAD; + sl->c = 1; + return 1; + } + } + return 0; +} + + +#else /* DL_PLATFORM_WIN32 */ +/* Custom win32-style spin locks on x86 and x64 for MSC */ +struct win32_mlock_t { + volatile long l; + unsigned int c; + long threadid; +}; + +#define MLOCK_T struct win32_mlock_t +#define CURRENT_THREAD GetCurrentThreadId() +#define INITIAL_LOCK(sl) ((sl)->threadid = 0, (sl)->l = (sl)->c = 0, 0) +#define ACQUIRE_LOCK(sl) win32_acquire_lock(sl) +#define RELEASE_LOCK(sl) win32_release_lock(sl) +#define TRY_LOCK(sl) win32_try_lock(sl) +#define SPINS_PER_YIELD 63 + +static MLOCK_T malloc_global_mutex = { 0, 0, 0}; + +static FORCEINLINE int win32_acquire_lock (MLOCK_T *sl) { + int spins = 0; + for (;;) { + if (sl->l != 0) { + if (sl->threadid == CURRENT_THREAD) { + ++sl->c; + return 0; + } + } + else { + if (!interlockedexchange(&sl->l, 1)) { + assert(!sl->threadid); + sl->threadid = CURRENT_THREAD; + sl->c = 1; + return 0; + } + } + if ((++spins & SPINS_PER_YIELD) == 0) + SleepEx(0, FALSE); + } +} + +static FORCEINLINE void win32_release_lock (MLOCK_T *sl) { + assert(sl->threadid == CURRENT_THREAD); + assert(sl->l != 0); + if (--sl->c == 0) { + sl->threadid = 0; + interlockedexchange (&sl->l, 0); + } +} + +static FORCEINLINE int win32_try_lock (MLOCK_T *sl) { + if (sl->l != 0) { + if (sl->threadid == CURRENT_THREAD) { + ++sl->c; + return 1; + } + } + else { + if (!interlockedexchange(&sl->l, 1)){ + assert(!sl->threadid); + sl->threadid = CURRENT_THREAD; + sl->c = 1; + return 1; + } + } + return 0; +} + +#endif /* DL_PLATFORM_WIN32 */ +#else /* USE_SPIN_LOCKS */ + +#ifndef DL_PLATFORM_WIN32 +/* pthreads-based locks */ + +#define MLOCK_T pthread_mutex_t +#define CURRENT_THREAD pthread_self() +#define INITIAL_LOCK(sl) pthread_init_lock(sl) +#define ACQUIRE_LOCK(sl) pthread_mutex_lock(sl) +#define RELEASE_LOCK(sl) pthread_mutex_unlock(sl) +#define TRY_LOCK(sl) (!pthread_mutex_trylock(sl)) + +static MLOCK_T malloc_global_mutex = PTHREAD_MUTEX_INITIALIZER; + +/* Cope with old-style linux recursive lock initialization by adding */ +/* skipped internal declaration from pthread.h */ +#ifdef linux +#ifndef PTHREAD_MUTEX_RECURSIVE +extern int pthread_mutexattr_setkind_np __P ((pthread_mutexattr_t *__attr, + int __kind)); +#define PTHREAD_MUTEX_RECURSIVE PTHREAD_MUTEX_RECURSIVE_NP +#define pthread_mutexattr_settype(x,y) pthread_mutexattr_setkind_np(x,y) +#endif +#endif + +static int pthread_init_lock (MLOCK_T *sl) { + pthread_mutexattr_t attr; + if (pthread_mutexattr_init(&attr)) return 1; + if (pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE)) return 1; + if (pthread_mutex_init(sl, &attr)) return 1; + if (pthread_mutexattr_destroy(&attr)) return 1; + return 0; +} + +#else /* DL_PLATFORM_WIN32 */ +/* Win32 critical sections */ +#define MLOCK_T CRITICAL_SECTION +#define CURRENT_THREAD GetCurrentThreadId() +#define INITIAL_LOCK(s) (!InitializeCriticalSectionAndSpinCount((s), 0x80000000|4000)) +#define ACQUIRE_LOCK(s) (EnterCriticalSection(sl), 0) +#define RELEASE_LOCK(s) LeaveCriticalSection(sl) +#define TRY_LOCK(s) TryEnterCriticalSection(sl) +#define NEED_GLOBAL_LOCK_INIT + +static MLOCK_T malloc_global_mutex; +static volatile long malloc_global_mutex_status; + +/* Use spin loop to initialize global lock */ +static void init_malloc_global_mutex() { + for (;;) { + long stat = malloc_global_mutex_status; + if (stat > 0) + return; + /* transition to < 0 while initializing, then to > 0) */ + if (stat == 0 && + interlockedcompareexchange(&malloc_global_mutex_status, -1, 0) == 0) { + InitializeCriticalSection(&malloc_global_mutex); + interlockedexchange(&malloc_global_mutex_status,1); + return; + } + SleepEx(0, FALSE); + } +} + +#endif /* DL_PLATFORM_WIN32 */ +#endif /* USE_SPIN_LOCKS */ +#endif /* USE_LOCKS == 1 */ + +/* ----------------------- User-defined locks ------------------------ */ + +#if USE_LOCKS > 1 +/* Define your own lock implementation here */ +/* #define INITIAL_LOCK(sl) ... */ +/* #define ACQUIRE_LOCK(sl) ... */ +/* #define RELEASE_LOCK(sl) ... */ +/* #define TRY_LOCK(sl) ... */ +/* static MLOCK_T malloc_global_mutex = ... */ +#endif /* USE_LOCKS > 1 */ + +/* ----------------------- Lock-based state ------------------------ */ + +#if USE_LOCKS +#define USE_LOCK_BIT (2U) +#else /* USE_LOCKS */ +#define USE_LOCK_BIT (0U) +#define INITIAL_LOCK(l) +#endif /* USE_LOCKS */ + +#if USE_LOCKS +#ifndef ACQUIRE_MALLOC_GLOBAL_LOCK +#define ACQUIRE_MALLOC_GLOBAL_LOCK() ACQUIRE_LOCK(&malloc_global_mutex); +#endif +#ifndef RELEASE_MALLOC_GLOBAL_LOCK +#define RELEASE_MALLOC_GLOBAL_LOCK() RELEASE_LOCK(&malloc_global_mutex); +#endif +#else /* USE_LOCKS */ +#define ACQUIRE_MALLOC_GLOBAL_LOCK() +#define RELEASE_MALLOC_GLOBAL_LOCK() +#endif /* USE_LOCKS */ + + +/* ----------------------- Chunk representations ------------------------ */ + +/* +(The following includes lightly edited explanations by Colin Plumb.) + +The malloc_chunk declaration below is misleading (but accurate and +necessary). It declares a "view" into memory allowing access to +necessary fields at known offsets from a given base. + +Chunks of memory are maintained using a `boundary tag' method as +originally described by Knuth. (See the paper by Paul Wilson +ftp://ftp.cs.utexas.edu/pub/garbage/allocsrv.ps for a survey of such +techniques.) Sizes of free chunks are stored both in the front of +each chunk and at the end. This makes consolidating fragmented +chunks into bigger chunks fast. The head fields also hold bits +representing whether chunks are free or in use. + +Here are some pictures to make it clearer. They are "exploded" to +show that the state of a chunk can be thought of as extending from +the high 31 bits of the head field of its header through the +prev_foot and PINUSE_BIT bit of the following chunk header. + +A chunk that's in use looks like: + +chunk-> +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +| Size of previous chunk (if P = 0) | ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ |P| +| Size of this chunk 1| +-+ +mem-> +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +| | ++- -+ +| | ++- -+ +| : ++- size - sizeof(size_t) available payload bytes -+ +: | +chunk-> +- -+ +| | ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ |1| +| Size of next chunk (may or may not be in use) | +-+ +mem-> +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + +And if it's free, it looks like this: + +chunk-> +- -+ +| User payload (must be in use, or we would have merged!) | ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ |P| +| Size of this chunk 0| +-+ +mem-> +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +| Next pointer | ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +| Prev pointer | ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +| : ++- size - sizeof(struct chunk) unused bytes -+ +: | +chunk-> +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +| Size of this chunk | ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ |0| +| Size of next chunk (must be in use, or we would have merged)| +-+ +mem-> +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +| : ++- User payload -+ +: | ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +|0| ++-+ +Note that since we always merge adjacent free chunks, the chunks +adjacent to a free chunk must be in use. + +Given a pointer to a chunk (which can be derived trivially from the +payload pointer) we can, in O(1) time, find out whether the adjacent +chunks are free, and if so, unlink them from the lists that they +are on and merge them with the current chunk. + +Chunks always begin on even word boundaries, so the mem portion +(which is returned to the user) is also on an even word boundary, and +thus at least double-word aligned. + +The P (PINUSE_BIT) bit, stored in the unused low-order bit of the +chunk size (which is always a multiple of two words), is an in-use +bit for the *previous* chunk. If that bit is *clear*, then the +word before the current chunk size contains the previous chunk +size, and can be used to find the front of the previous chunk. +The very first chunk allocated always has this bit set, preventing +access to non-existent (or non-owned) memory. If pinuse is set for +any given chunk, then you CANNOT determine the size of the +previous chunk, and might even get a memory addressing fault when +trying to do so. + +The C (CINUSE_BIT) bit, stored in the unused second-lowest bit of +the chunk size redundantly records whether the current chunk is +inuse (unless the chunk is mmapped). This redundancy enables usage +checks within free and realloc, and reduces indirection when freeing +and consolidating chunks. + +Each freshly allocated chunk must have both cinuse and pinuse set. +That is, each allocated chunk borders either a previously allocated +and still in-use chunk, or the base of its memory arena. This is +ensured by making all allocations from the the `lowest' part of any +found chunk. Further, no free chunk physically borders another one, +so each free chunk is known to be preceded and followed by either +inuse chunks or the ends of memory. + +Note that the `foot' of the current chunk is actually represented +as the prev_foot of the NEXT chunk. This makes it easier to +deal with alignments etc but can be very confusing when trying +to extend or adapt this code. + +The exceptions to all this are + +1. The special chunk `top' is the top-most available chunk (i.e., +the one bordering the end of available memory). It is treated +specially. Top is never included in any bin, is used only if +no other chunk is available, and is released back to the +system if it is very large (see M_TRIM_THRESHOLD). In effect, +the top chunk is treated as larger (and thus less well +fitting) than any other available chunk. The top chunk +doesn't update its trailing size field since there is no next +contiguous chunk that would have to index off it. However, +space is still allocated for it (TOP_FOOT_SIZE) to enable +separation or merging when space is extended. + +3. Chunks allocated via mmap, have both cinuse and pinuse bits +cleared in their head fields. Because they are allocated +one-by-one, each must carry its own prev_foot field, which is +also used to hold the offset this chunk has within its mmapped +region, which is needed to preserve alignment. Each mmapped +chunk is trailed by the first two fields of a fake next-chunk +for sake of usage checks. + +*/ + +struct malloc_chunk { + size_t prev_foot; /* Size of previous chunk (if free). */ + size_t head; /* Size and inuse bits. */ + struct malloc_chunk* fd; /* double links -- used only if free. */ + struct malloc_chunk* bk; +}; + +typedef struct malloc_chunk mchunk; +typedef struct malloc_chunk* mchunkptr; +typedef struct malloc_chunk* sbinptr; /* The type of bins of chunks */ +typedef unsigned int bindex_t; /* Described below */ +typedef unsigned int binmap_t; /* Described below */ +typedef unsigned int flag_t; /* The type of various bit flag sets */ + +/* ------------------- Chunks sizes and alignments ----------------------- */ + +#define MCHUNK_SIZE (sizeof(mchunk)) + +#if FOOTERS +#define CHUNK_OVERHEAD (TWO_SIZE_T_SIZES) +#else /* FOOTERS */ +#define CHUNK_OVERHEAD (SIZE_T_SIZE) +#endif /* FOOTERS */ + +/* MMapped chunks need a second word of overhead ... */ +#define MMAP_CHUNK_OVERHEAD (TWO_SIZE_T_SIZES) +/* ... and additional padding for fake next-chunk at foot */ +#define MMAP_FOOT_PAD (FOUR_SIZE_T_SIZES) + +/* The smallest size we can malloc is an aligned minimal chunk */ +#define MIN_CHUNK_SIZE\ + ((MCHUNK_SIZE + CHUNK_ALIGN_MASK) & ~CHUNK_ALIGN_MASK) + +/* conversion from malloc headers to user pointers, and back */ +#define chunk2mem(p) ((void*)((char*)(p) + TWO_SIZE_T_SIZES)) +#define mem2chunk(mem) ((mchunkptr)((char*)(mem) - TWO_SIZE_T_SIZES)) +/* chunk associated with aligned address A */ +#define align_as_chunk(A) (mchunkptr)((A) + align_offset(chunk2mem(A))) + +/* Bounds on request (not chunk) sizes. */ +#define MAX_REQUEST ((-MIN_CHUNK_SIZE) << 2) +#define MIN_REQUEST (MIN_CHUNK_SIZE - CHUNK_OVERHEAD - SIZE_T_ONE) + +/* pad request bytes into a usable size */ +#define pad_request(req) \ + (((req) + CHUNK_OVERHEAD + CHUNK_ALIGN_MASK) & ~CHUNK_ALIGN_MASK) + +/* pad request, checking for minimum (but not maximum) */ +#define request2size(req) \ + (((req) < MIN_REQUEST)? MIN_CHUNK_SIZE : pad_request(req)) + + +/* ------------------ Operations on head and foot fields ----------------- */ + +/* +The head field of a chunk is or'ed with PINUSE_BIT when previous +adjacent chunk in use, and or'ed with CINUSE_BIT if this chunk is in +use, unless mmapped, in which case both bits are cleared. + +FLAG4_BIT is not used by this malloc, but might be useful in extensions. +*/ + +#define PINUSE_BIT (SIZE_T_ONE) +#define CINUSE_BIT (SIZE_T_TWO) +#define FLAG4_BIT (SIZE_T_FOUR) +#define INUSE_BITS (PINUSE_BIT|CINUSE_BIT) +#define FLAG_BITS (PINUSE_BIT|CINUSE_BIT|FLAG4_BIT) + +/* Head value for fenceposts */ +#define FENCEPOST_HEAD (INUSE_BITS|SIZE_T_SIZE) + +/* extraction of fields from head words */ +#define cinuse(p) ((p)->head & CINUSE_BIT) +#define pinuse(p) ((p)->head & PINUSE_BIT) +#define is_inuse(p) (((p)->head & INUSE_BITS) != PINUSE_BIT) +#define is_mmapped(p) (((p)->head & INUSE_BITS) == 0) + +#define chunksize(p) ((p)->head & ~(FLAG_BITS)) + +#define clear_pinuse(p) ((p)->head &= ~PINUSE_BIT) + +/* Treat space at ptr +/- offset as a chunk */ +#define chunk_plus_offset(p, s) ((mchunkptr)(((char*)(p)) + (s))) +#define chunk_minus_offset(p, s) ((mchunkptr)(((char*)(p)) - (s))) + +/* Ptr to next or previous physical malloc_chunk. */ +#define next_chunk(p) ((mchunkptr)( ((char*)(p)) + ((p)->head & ~FLAG_BITS))) +#define prev_chunk(p) ((mchunkptr)( ((char*)(p)) - ((p)->prev_foot) )) + +/* extract next chunk's pinuse bit */ +#define next_pinuse(p) ((next_chunk(p)->head) & PINUSE_BIT) + +/* Get/set size at footer */ +#define get_foot(p, s) (((mchunkptr)((char*)(p) + (s)))->prev_foot) +#define set_foot(p, s) (((mchunkptr)((char*)(p) + (s)))->prev_foot = (s)) + +/* Set size, pinuse bit, and foot */ +#define set_size_and_pinuse_of_free_chunk(p, s)\ + ((p)->head = (s|PINUSE_BIT), set_foot(p, s)) + +/* Set size, pinuse bit, foot, and clear next pinuse */ +#define set_free_with_pinuse(p, s, n)\ + (clear_pinuse(n), set_size_and_pinuse_of_free_chunk(p, s)) + +/* Get the internal overhead associated with chunk p */ +#define overhead_for(p)\ + (is_mmapped(p)? MMAP_CHUNK_OVERHEAD : CHUNK_OVERHEAD) + +/* Return true if malloced space is not necessarily cleared */ +#if MMAP_CLEARS +#define calloc_must_clear(p) (!is_mmapped(p)) +#else /* MMAP_CLEARS */ +#define calloc_must_clear(p) (1) +#endif /* MMAP_CLEARS */ + +/* ---------------------- Overlaid data structures ----------------------- */ + +/* +When chunks are not in use, they are treated as nodes of either +lists or trees. + +"Small" chunks are stored in circular doubly-linked lists, and look +like this: + +chunk-> +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +| Size of previous chunk | ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +`head:' | Size of chunk, in bytes |P| +mem-> +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +| Forward pointer to next chunk in list | ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +| Back pointer to previous chunk in list | ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +| Unused space (may be 0 bytes long) . +. . +. | +nextchunk-> +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +`foot:' | Size of chunk, in bytes | ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + +Larger chunks are kept in a form of bitwise digital trees (aka +tries) keyed on chunksizes. Because malloc_tree_chunks are only for +free chunks greater than 256 bytes, their size doesn't impose any +constraints on user chunk sizes. Each node looks like: + +chunk-> +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +| Size of previous chunk | ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +`head:' | Size of chunk, in bytes |P| +mem-> +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +| Forward pointer to next chunk of same size | ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +| Back pointer to previous chunk of same size | ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +| Pointer to left child (child[0]) | ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +| Pointer to right child (child[1]) | ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +| Pointer to parent | ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +| bin index of this chunk | ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +| Unused space . +. | +nextchunk-> +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +`foot:' | Size of chunk, in bytes | ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + +Each tree holding treenodes is a tree of unique chunk sizes. Chunks +of the same size are arranged in a circularly-linked list, with only +the oldest chunk (the next to be used, in our FIFO ordering) +actually in the tree. (Tree members are distinguished by a non-null +parent pointer.) If a chunk with the same size an an existing node +is inserted, it is linked off the existing node using pointers that +work in the same way as fd/bk pointers of small chunks. + +Each tree contains a power of 2 sized range of chunk sizes (the +smallest is 0x100 <= x < 0x180), which is is divided in half at each +tree level, with the chunks in the smaller half of the range (0x100 +<= x < 0x140 for the top nose) in the left subtree and the larger +half (0x140 <= x < 0x180) in the right subtree. This is, of course, +done by inspecting individual bits. + +Using these rules, each node's left subtree contains all smaller +sizes than its right subtree. However, the node at the root of each +subtree has no particular ordering relationship to either. (The +dividing line between the subtree sizes is based on trie relation.) +If we remove the last chunk of a given size from the interior of the +tree, we need to replace it with a leaf node. The tree ordering +rules permit a node to be replaced by any leaf below it. + +The smallest chunk in a tree (a common operation in a best-fit +allocator) can be found by walking a path to the leftmost leaf in +the tree. Unlike a usual binary tree, where we follow left child +pointers until we reach a null, here we follow the right child +pointer any time the left one is null, until we reach a leaf with +both child pointers null. The smallest chunk in the tree will be +somewhere along that path. + +The worst case number of steps to add, find, or remove a node is +bounded by the number of bits differentiating chunks within +bins. Under current bin calculations, this ranges from 6 up to 21 +(for 32 bit sizes) or up to 53 (for 64 bit sizes). The typical case +is of course much better. +*/ + +struct malloc_tree_chunk { + /* The first four fields must be compatible with malloc_chunk */ + size_t prev_foot; + size_t head; + struct malloc_tree_chunk* fd; + struct malloc_tree_chunk* bk; + + struct malloc_tree_chunk* child[2]; + struct malloc_tree_chunk* parent; + bindex_t index; +}; + +typedef struct malloc_tree_chunk tchunk; +typedef struct malloc_tree_chunk* tchunkptr; +typedef struct malloc_tree_chunk* tbinptr; /* The type of bins of trees */ + +/* A little helper macro for trees */ +#define leftmost_child(t) ((t)->child[0] != 0? (t)->child[0] : (t)->child[1]) + +/* ----------------------------- Segments -------------------------------- */ + +/* +Each malloc space may include non-contiguous segments, held in a +list headed by an embedded malloc_segment record representing the +top-most space. Segments also include flags holding properties of +the space. Large chunks that are directly allocated by mmap are not +included in this list. They are instead independently created and +destroyed without otherwise keeping track of them. + +Segment management mainly comes into play for spaces allocated by +MMAP. Any call to MMAP might or might not return memory that is +adjacent to an existing segment. MORECORE normally contiguously +extends the current space, so this space is almost always adjacent, +which is simpler and faster to deal with. (This is why MORECORE is +used preferentially to MMAP when both are available -- see +sys_alloc.) When allocating using MMAP, we don't use any of the +hinting mechanisms (inconsistently) supported in various +implementations of unix mmap, or distinguish reserving from +committing memory. Instead, we just ask for space, and exploit +contiguity when we get it. It is probably possible to do +better than this on some systems, but no general scheme seems +to be significantly better. + +Management entails a simpler variant of the consolidation scheme +used for chunks to reduce fragmentation -- new adjacent memory is +normally prepended or appended to an existing segment. However, +there are limitations compared to chunk consolidation that mostly +reflect the fact that segment processing is relatively infrequent +(occurring only when getting memory from system) and that we +don't expect to have huge numbers of segments: + +* Segments are not indexed, so traversal requires linear scans. (It +would be possible to index these, but is not worth the extra +overhead and complexity for most programs on most platforms.) +* New segments are only appended to old ones when holding top-most +memory; if they cannot be prepended to others, they are held in +different segments. + +Except for the top-most segment of an mstate, each segment record +is kept at the tail of its segment. Segments are added by pushing +segment records onto the list headed by &mstate.seg for the +containing mstate. + +Segment flags control allocation/merge/deallocation policies: +* If EXTERN_BIT set, then we did not allocate this segment, +and so should not try to deallocate or merge with others. +(This currently holds only for the initial segment passed +into rak_create_mspace_with_base.) +* If USE_MMAP_BIT set, the segment may be merged with +other surrounding mmapped segments and trimmed/de-allocated +using munmap. +* If neither bit is set, then the segment was obtained using +MORECORE so can be merged with surrounding MORECORE'd segments +and deallocated/trimmed using MORECORE with negative arguments. +*/ + +struct malloc_segment { + char* base; /* base address */ + size_t size; /* allocated size */ + struct malloc_segment* next; /* ptr to next segment */ + flag_t sflags; /* mmap and extern flag */ +}; + +#define is_mmapped_segment(S) ((S)->sflags & USE_MMAP_BIT) +#define is_extern_segment(S) ((S)->sflags & EXTERN_BIT) + +typedef struct malloc_segment msegment; +typedef struct malloc_segment* msegmentptr; + +/* ---------------------------- malloc_state ----------------------------- */ + +/* +A malloc_state holds all of the bookkeeping for a space. +The main fields are: + +Top +The topmost chunk of the currently active segment. Its size is +cached in topsize. The actual size of topmost space is +topsize+TOP_FOOT_SIZE, which includes space reserved for adding +fenceposts and segment records if necessary when getting more +space from the system. The size at which to autotrim top is +cached from mparams in trim_check, except that it is disabled if +an autotrim fails. + +Designated victim (dv) +This is the preferred chunk for servicing small requests that +don't have exact fits. It is normally the chunk split off most +recently to service another small request. Its size is cached in +dvsize. The link fields of this chunk are not maintained since it +is not kept in a bin. + +SmallBins +An array of bin headers for free chunks. These bins hold chunks +with sizes less than MIN_LARGE_SIZE bytes. Each bin contains +chunks of all the same size, spaced 8 bytes apart. To simplify +use in double-linked lists, each bin header acts as a malloc_chunk +pointing to the real first node, if it exists (else pointing to +itself). This avoids special-casing for headers. But to avoid +waste, we allocate only the fd/bk pointers of bins, and then use +repositioning tricks to treat these as the fields of a chunk. + +TreeBins +Treebins are pointers to the roots of trees holding a range of +sizes. There are 2 equally spaced treebins for each power of two +from TREE_SHIFT to TREE_SHIFT+16. The last bin holds anything +larger. + +Bin maps +There is one bit map for small bins ("smallmap") and one for +treebins ("treemap). Each bin sets its bit when non-empty, and +clears the bit when empty. Bit operations are then used to avoid +bin-by-bin searching -- nearly all "search" is done without ever +looking at bins that won't be selected. The bit maps +conservatively use 32 bits per map word, even if on 64bit system. +For a good description of some of the bit-based techniques used +here, see Henry S. Warren Jr's book "Hacker's Delight" (and +supplement at http://hackersdelight.org/). Many of these are +intended to reduce the branchiness of paths through malloc etc, as +well as to reduce the number of memory locations read or written. + +Segments +A list of segments headed by an embedded malloc_segment record +representing the initial space. + +Address check support +The least_addr field is the least address ever obtained from +MORECORE or MMAP. Attempted frees and reallocs of any address less +than this are trapped (unless INSECURE is defined). + +Magic tag +A cross-check field that should always hold same value as mparams.magic. + +Flags +Bits recording whether to use MMAP, locks, or contiguous MORECORE + +Statistics +Each space keeps track of current and maximum system memory +obtained via MORECORE or MMAP. + +Trim support +Fields holding the amount of unused topmost memory that should trigger +timming, and a counter to force periodic scanning to release unused +non-topmost segments. + +Locking +If USE_LOCKS is defined, the "mutex" lock is acquired and released +around every public call using this mspace. + +Extension support +A void* pointer and a size_t field that can be used to help implement +extensions to this malloc. +*/ + +/* Bin types, widths and sizes */ +#define NSMALLBINS (32U) +#define NTREEBINS (32U) +#define SMALLBIN_SHIFT (3U) +#define SMALLBIN_WIDTH (SIZE_T_ONE << SMALLBIN_SHIFT) +#define TREEBIN_SHIFT (8U) +#define MIN_LARGE_SIZE (SIZE_T_ONE << TREEBIN_SHIFT) +#define MAX_SMALL_SIZE (MIN_LARGE_SIZE - SIZE_T_ONE) +#define MAX_SMALL_REQUEST (MAX_SMALL_SIZE - CHUNK_ALIGN_MASK - CHUNK_OVERHEAD) + +struct malloc_state { + binmap_t smallmap; + binmap_t treemap; + size_t dvsize; + size_t topsize; + char* least_addr; + mchunkptr dv; + mchunkptr top; + size_t trim_check; + size_t release_checks; + size_t magic; + mchunkptr smallbins[(NSMALLBINS+1)*2]; + tbinptr treebins[NTREEBINS]; + size_t footprint; + size_t max_footprint; + flag_t mflags; +#if USE_LOCKS + MLOCK_T mutex; /* locate lock among fields that rarely change */ +#endif /* USE_LOCKS */ + msegment seg; + void* extp; /* Unused but available for extensions */ + size_t exts; +}; + +typedef struct malloc_state* mstate; + +/* ------------- Global malloc_state and malloc_params ------------------- */ + +/* +malloc_params holds global properties, including those that can be +dynamically set using mallopt. There is a single instance, mparams, +initialized in init_mparams. Note that the non-zeroness of "magic" +also serves as an initialization flag. +*/ + +struct malloc_params { + volatile size_t magic; + size_t page_size; + size_t granularity; + size_t mmap_threshold; + size_t trim_threshold; + flag_t default_mflags; +}; + +static struct malloc_params mparams; + +/* Ensure mparams initialized */ +#define ensure_initialization() (void)(mparams.magic != 0 || init_mparams()) + +#if !ONLY_MSPACES + +/* The global malloc_state used for all non-"mspace" calls */ +static struct malloc_state _gm_; +#define gm (&_gm_) +#define is_global(M) ((M) == &_gm_) + +#endif /* !ONLY_MSPACES */ + +#define is_initialized(M) ((M)->top != 0) + +/* -------------------------- system alloc setup ------------------------- */ + +/* Operations on mflags */ + +#define use_lock(M) ((M)->mflags & USE_LOCK_BIT) +#define enable_lock(M) ((M)->mflags |= USE_LOCK_BIT) +#define disable_lock(M) ((M)->mflags &= ~USE_LOCK_BIT) + +#define use_mmap(M) ((M)->mflags & USE_MMAP_BIT) +#define enable_mmap(M) ((M)->mflags |= USE_MMAP_BIT) +#define disable_mmap(M) ((M)->mflags &= ~USE_MMAP_BIT) + +#define use_noncontiguous(M) ((M)->mflags & USE_NONCONTIGUOUS_BIT) +#define disable_contiguous(M) ((M)->mflags |= USE_NONCONTIGUOUS_BIT) + +#define set_lock(M,L)\ + ((M)->mflags = (L)?\ + ((M)->mflags | USE_LOCK_BIT) :\ + ((M)->mflags & ~USE_LOCK_BIT)) + +/* page-align a size */ +#define page_align(S)\ + (((S) + (mparams.page_size - SIZE_T_ONE)) & ~(mparams.page_size - SIZE_T_ONE)) + +/* granularity-align a size */ +#define granularity_align(S)\ + (((S) + (mparams.granularity - SIZE_T_ONE))\ + & ~(mparams.granularity - SIZE_T_ONE)) + + +/* For mmap, use granularity alignment on windows, else page-align */ +#ifdef DL_PLATFORM_WIN32 +#define mmap_align(S) granularity_align(S) +#else +#define mmap_align(S) page_align(S) +#endif + +/* For sys_alloc, enough padding to ensure can malloc request on success */ +#define SYS_ALLOC_PADDING (TOP_FOOT_SIZE + MALLOC_ALIGNMENT) + +#define is_page_aligned(S)\ + (((size_t)(S) & (mparams.page_size - SIZE_T_ONE)) == 0) +#define is_granularity_aligned(S)\ + (((size_t)(S) & (mparams.granularity - SIZE_T_ONE)) == 0) + +/* True if segment S holds address A */ +#define segment_holds(S, A)\ + ((char*)(A) >= S->base && (char*)(A) < S->base + S->size) + +/* Return segment holding given address */ +static msegmentptr segment_holding(mstate m, char* addr) { + msegmentptr sp = &m->seg; + for (;;) { + if (addr >= sp->base && addr < sp->base + sp->size) + return sp; + if ((sp = sp->next) == 0) + return 0; + } +} + +/* Return true if segment contains a segment link */ +static int has_segment_link(mstate m, msegmentptr ss) { + msegmentptr sp = &m->seg; + for (;;) { + if ((char*)sp >= ss->base && (char*)sp < ss->base + ss->size) + return 1; + if ((sp = sp->next) == 0) + return 0; + } +} + +#ifndef MORECORE_CANNOT_TRIM +#define should_trim(M,s) ((s) > (M)->trim_check) +#else /* MORECORE_CANNOT_TRIM */ +#define should_trim(M,s) (0) +#endif /* MORECORE_CANNOT_TRIM */ + +/* +TOP_FOOT_SIZE is padding at the end of a segment, including space +that may be needed to place segment records and fenceposts when new +noncontiguous segments are added. +*/ +#define TOP_FOOT_SIZE\ + (align_offset(chunk2mem(0))+pad_request(sizeof(struct malloc_segment))+MIN_CHUNK_SIZE) + + +/* ------------------------------- Hooks -------------------------------- */ + +/* +PREACTION should be defined to return 0 on success, and nonzero on +failure. If you are not using locking, you can redefine these to do +anything you like. +*/ + +#if USE_LOCKS + +#define PREACTION(M) ((use_lock(M))? ACQUIRE_LOCK(&(M)->mutex) : 0) +#define POSTACTION(M) { if (use_lock(M)) RELEASE_LOCK(&(M)->mutex); } +#else /* USE_LOCKS */ + +#ifndef PREACTION +#define PREACTION(M) (0) +#endif /* PREACTION */ + +#ifndef POSTACTION +#define POSTACTION(M) +#endif /* POSTACTION */ + +#endif /* USE_LOCKS */ + +/* +CORRUPTION_ERROR_ACTION is triggered upon detected bad addresses. +USAGE_ERROR_ACTION is triggered on detected bad frees and +reallocs. The argument p is an address that might have triggered the +fault. It is ignored by the two predefined actions, but might be +useful in custom actions that try to help diagnose errors. +*/ + +#if PROCEED_ON_ERROR + +/* A count of the number of corruption errors causing resets */ +int malloc_corruption_error_count; + +/* default corruption action */ +static void reset_on_error(mstate m); + +#define CORRUPTION_ERROR_ACTION(m) reset_on_error(m) +#define USAGE_ERROR_ACTION(m, p) + +#else /* PROCEED_ON_ERROR */ + +#ifndef CORRUPTION_ERROR_ACTION +#define CORRUPTION_ERROR_ACTION(m) ABORT +#endif /* CORRUPTION_ERROR_ACTION */ + +#ifndef USAGE_ERROR_ACTION +#define USAGE_ERROR_ACTION(m,p) ABORT +#endif /* USAGE_ERROR_ACTION */ + +#endif /* PROCEED_ON_ERROR */ + +/* -------------------------- Debugging setup ---------------------------- */ + +#if ! DEBUG + +#define check_free_chunk(M,P) +#define check_inuse_chunk(M,P) +#define check_malloced_chunk(M,P,N) +#define check_mmapped_chunk(M,P) +#define check_malloc_state(M) +#define check_top_chunk(M,P) + +#else /* DEBUG */ +#define check_free_chunk(M,P) do_check_free_chunk(M,P) +#define check_inuse_chunk(M,P) do_check_inuse_chunk(M,P) +#define check_top_chunk(M,P) do_check_top_chunk(M,P) +#define check_malloced_chunk(M,P,N) do_check_malloced_chunk(M,P,N) +#define check_mmapped_chunk(M,P) do_check_mmapped_chunk(M,P) +#define check_malloc_state(M) do_check_malloc_state(M) + +static void do_check_any_chunk(mstate m, mchunkptr p); +static void do_check_top_chunk(mstate m, mchunkptr p); +static void do_check_mmapped_chunk(mstate m, mchunkptr p); +static void do_check_inuse_chunk(mstate m, mchunkptr p); +static void do_check_free_chunk(mstate m, mchunkptr p); +static void do_check_malloced_chunk(mstate m, void* mem, size_t s); +static void do_check_tree(mstate m, tchunkptr t); +static void do_check_treebin(mstate m, bindex_t i); +static void do_check_smallbin(mstate m, bindex_t i); +static void do_check_malloc_state(mstate m); +static int bin_find(mstate m, mchunkptr x); +static size_t traverse_and_check(mstate m); +#endif /* DEBUG */ + +/* ---------------------------- Indexing Bins ---------------------------- */ + +#define is_small(s) (((s) >> SMALLBIN_SHIFT) < NSMALLBINS) +#define small_index(s) ((s) >> SMALLBIN_SHIFT) +#define small_index2size(i) ((i) << SMALLBIN_SHIFT) +#define MIN_SMALL_INDEX (small_index(MIN_CHUNK_SIZE)) + +/* addressing by index. See above about smallbin repositioning */ +#define smallbin_at(M, i) ((sbinptr)((char*)&((M)->smallbins[(i)<<1]))) +#define treebin_at(M,i) (&((M)->treebins[i])) + +/* assign tree index for size S to variable I. Use x86 asm if possible */ +#if defined(__GNUC__) && (defined(__i386__) || defined(__x86_64__)) +#define compute_tree_index(S, I)\ +{\ + unsigned int X = S >> TREEBIN_SHIFT;\ + if (X == 0)\ + I = 0;\ + else if (X > 0xFFFF)\ + I = NTREEBINS-1;\ + else {\ + unsigned int K;\ + __asm__("bsrl\t%1, %0\n\t" : "=r" (K) : "g" (X));\ + I = (bindex_t)((K << 1) + ((S >> (K + (TREEBIN_SHIFT-1)) & 1)));\ +}\ +} + +#elif defined (__INTEL_COMPILER) +#define compute_tree_index(S, I)\ +{\ + size_t X = S >> TREEBIN_SHIFT;\ + if (X == 0)\ + I = 0;\ + else if (X > 0xFFFF)\ + I = NTREEBINS-1;\ + else {\ + unsigned int K = _bit_scan_reverse (X); \ + I = (bindex_t)((K << 1) + ((S >> (K + (TREEBIN_SHIFT-1)) & 1)));\ +}\ +} + +#elif defined(_MSC_VER) && _MSC_VER>=1300 && defined(DL_PLATFORM_WIN32) +#define compute_tree_index(S, I)\ +{\ + size_t X = S >> TREEBIN_SHIFT;\ + if (X == 0)\ + I = 0;\ + else if (X > 0xFFFF)\ + I = NTREEBINS-1;\ + else {\ + unsigned int K;\ + _BitScanReverse((DWORD *) &K, X);\ + I = (bindex_t)((K << 1) + ((S >> (K + (TREEBIN_SHIFT-1)) & 1)));\ +}\ +} + +#else /* GNUC */ +#define compute_tree_index(S, I)\ +{\ + size_t X = S >> TREEBIN_SHIFT;\ + if (X == 0)\ + I = 0;\ + else if (X > 0xFFFF)\ + I = NTREEBINS-1;\ + else {\ + unsigned int Y = (unsigned int)X;\ + unsigned int N = ((Y - 0x100) >> 16) & 8;\ + unsigned int K = (((Y <<= N) - 0x1000) >> 16) & 4;\ + N += K;\ + N += K = (((Y <<= K) - 0x4000) >> 16) & 2;\ + K = 14 - N + ((Y <<= K) >> 15);\ + I = (K << 1) + ((S >> (K + (TREEBIN_SHIFT-1)) & 1));\ +}\ +} +#endif /* GNUC */ + +/* Bit representing maximum resolved size in a treebin at i */ +#define bit_for_tree_index(i) \ + (i == NTREEBINS-1)? (SIZE_T_BITSIZE-1) : (((i) >> 1) + TREEBIN_SHIFT - 2) + +/* Shift placing maximum resolved bit in a treebin at i as sign bit */ +#define leftshift_for_tree_index(i) \ + ((i == NTREEBINS-1)? 0 : \ + ((SIZE_T_BITSIZE-SIZE_T_ONE) - (((i) >> 1) + TREEBIN_SHIFT - 2))) + +/* The size of the smallest chunk held in bin with index i */ +#define minsize_for_tree_index(i) \ + ((SIZE_T_ONE << (((i) >> 1) + TREEBIN_SHIFT)) | \ + (((size_t)((i) & SIZE_T_ONE)) << (((i) >> 1) + TREEBIN_SHIFT - 1))) + + +/* ------------------------ Operations on bin maps ----------------------- */ + +/* bit corresponding to given index */ +#define idx2bit(i) ((binmap_t)(1) << (i)) + +/* Mark/Clear bits with given index */ +#define mark_smallmap(M,i) ((M)->smallmap |= idx2bit(i)) +#define clear_smallmap(M,i) ((M)->smallmap &= ~idx2bit(i)) +#define smallmap_is_marked(M,i) ((M)->smallmap & idx2bit(i)) + +#define mark_treemap(M,i) ((M)->treemap |= idx2bit(i)) +#define clear_treemap(M,i) ((M)->treemap &= ~idx2bit(i)) +#define treemap_is_marked(M,i) ((M)->treemap & idx2bit(i)) + +/* isolate the least set bit of a bitmap */ +#define least_bit(x) ((x) & -(x)) + +/* mask with all bits to left of least bit of x on */ +#define left_bits(x) ((x<<1) | -(x<<1)) + +/* mask with all bits to left of or equal to least bit of x on */ +#define same_or_left_bits(x) ((x) | -(x)) + +/* index corresponding to given bit. Use x86 asm if possible */ + +#if defined(__GNUC__) && (defined(__i386__) || defined(__x86_64__)) +#define compute_bit2idx(X, I)\ +{\ + unsigned int J;\ + __asm__("bsfl\t%1, %0\n\t" : "=r" (J) : "g" (X));\ + I = (bindex_t)J;\ +} + +#elif defined (__INTEL_COMPILER) +#define compute_bit2idx(X, I)\ +{\ + unsigned int J;\ + J = _bit_scan_forward (X); \ + I = (bindex_t)J;\ +} + +#elif defined(_MSC_VER) && _MSC_VER>=1300 && defined(DL_PLATFORM_WIN32) +#define compute_bit2idx(X, I)\ +{\ + unsigned int J;\ + _BitScanForward((DWORD *) &J, X);\ + I = (bindex_t)J;\ +} + +#elif USE_BUILTIN_FFS +#define compute_bit2idx(X, I) I = ffs(X)-1 + +#else +#define compute_bit2idx(X, I)\ +{\ + unsigned int Y = X - 1;\ + unsigned int K = Y >> (16-4) & 16;\ + unsigned int N = K; Y >>= K;\ + N += K = Y >> (8-3) & 8; Y >>= K;\ + N += K = Y >> (4-2) & 4; Y >>= K;\ + N += K = Y >> (2-1) & 2; Y >>= K;\ + N += K = Y >> (1-0) & 1; Y >>= K;\ + I = (bindex_t)(N + Y);\ +} +#endif /* GNUC */ + + +/* ----------------------- Runtime Check Support ------------------------- */ + +/* +For security, the main invariant is that malloc/free/etc never +writes to a static address other than malloc_state, unless static +malloc_state itself has been corrupted, which cannot occur via +malloc (because of these checks). In essence this means that we +believe all pointers, sizes, maps etc held in malloc_state, but +check all of those linked or offsetted from other embedded data +structures. These checks are interspersed with main code in a way +that tends to minimize their run-time cost. + +When FOOTERS is defined, in addition to range checking, we also +verify footer fields of inuse chunks, which can be used guarantee +that the mstate controlling malloc/free is intact. This is a +streamlined version of the approach described by William Robertson +et al in "Run-time Detection of Heap-based Overflows" LISA'03 +http://www.usenix.org/events/lisa03/tech/robertson.html The footer +of an inuse chunk holds the xor of its mstate and a random seed, +that is checked upon calls to free() and realloc(). This is +(probablistically) unguessable from outside the program, but can be +computed by any code successfully malloc'ing any chunk, so does not +itself provide protection against code that has already broken +security through some other means. Unlike Robertson et al, we +always dynamically check addresses of all offset chunks (previous, +next, etc). This turns out to be cheaper than relying on hashes. +*/ + +#if !INSECURE +/* Check if address a is at least as high as any from MORECORE or MMAP */ +#define ok_address(M, a) ((char*)(a) >= (M)->least_addr) +/* Check if address of next chunk n is higher than base chunk p */ +#define ok_next(p, n) ((char*)(p) < (char*)(n)) +/* Check if p has inuse status */ +#define ok_inuse(p) is_inuse(p) +/* Check if p has its pinuse bit on */ +#define ok_pinuse(p) pinuse(p) + +#else /* !INSECURE */ +#define ok_address(M, a) (1) +#define ok_next(b, n) (1) +#define ok_inuse(p) (1) +#define ok_pinuse(p) (1) +#endif /* !INSECURE */ + +#if (FOOTERS && !INSECURE) +/* Check if (alleged) mstate m has expected magic field */ +#define ok_magic(M) ((M)->magic == mparams.magic) +#else /* (FOOTERS && !INSECURE) */ +#define ok_magic(M) (1) +#endif /* (FOOTERS && !INSECURE) */ + + +/* In gcc, use __builtin_expect to minimize impact of checks */ +#if !INSECURE +#if defined(__GNUC__) && __GNUC__ >= 3 +#define RTCHECK(e) __builtin_expect(e, 1) +#else /* GNUC */ +#define RTCHECK(e) (e) +#endif /* GNUC */ +#else /* !INSECURE */ +#define RTCHECK(e) (1) +#endif /* !INSECURE */ + +/* macros to set up inuse chunks with or without footers */ + +#if !FOOTERS + +#define mark_inuse_foot(M,p,s) + +/* Macros for setting head/foot of non-mmapped chunks */ + +/* Set cinuse bit and pinuse bit of next chunk */ +#define set_inuse(M,p,s)\ + ((p)->head = (((p)->head & PINUSE_BIT)|s|CINUSE_BIT),\ + ((mchunkptr)(((char*)(p)) + (s)))->head |= PINUSE_BIT) + +/* Set cinuse and pinuse of this chunk and pinuse of next chunk */ +#define set_inuse_and_pinuse(M,p,s)\ + ((p)->head = (s|PINUSE_BIT|CINUSE_BIT),\ + ((mchunkptr)(((char*)(p)) + (s)))->head |= PINUSE_BIT) + +/* Set size, cinuse and pinuse bit of this chunk */ +#define set_size_and_pinuse_of_inuse_chunk(M, p, s)\ + ((p)->head = (s|PINUSE_BIT|CINUSE_BIT)) + +#else /* FOOTERS */ + +/* Set foot of inuse chunk to be xor of mstate and seed */ +#define mark_inuse_foot(M,p,s)\ + (((mchunkptr)((char*)(p) + (s)))->prev_foot = ((size_t)(M) ^ mparams.magic)) + +#define get_mstate_for(p)\ + ((mstate)(((mchunkptr)((char*)(p) +\ + (chunksize(p))))->prev_foot ^ mparams.magic)) + +#define set_inuse(M,p,s)\ + ((p)->head = (((p)->head & PINUSE_BIT)|s|CINUSE_BIT),\ + (((mchunkptr)(((char*)(p)) + (s)))->head |= PINUSE_BIT), \ + mark_inuse_foot(M,p,s)) + +#define set_inuse_and_pinuse(M,p,s)\ + ((p)->head = (s|PINUSE_BIT|CINUSE_BIT),\ + (((mchunkptr)(((char*)(p)) + (s)))->head |= PINUSE_BIT),\ + mark_inuse_foot(M,p,s)) + +#define set_size_and_pinuse_of_inuse_chunk(M, p, s)\ + ((p)->head = (s|PINUSE_BIT|CINUSE_BIT),\ + mark_inuse_foot(M, p, s)) + +#endif /* !FOOTERS */ + +/* ---------------------------- setting mparams -------------------------- */ + +/* Initialize mparams */ +static int init_mparams(void) { +#ifdef NEED_GLOBAL_LOCK_INIT + if (malloc_global_mutex_status <= 0) + init_malloc_global_mutex(); +#endif + + ACQUIRE_MALLOC_GLOBAL_LOCK(); + if (mparams.magic == 0) { + size_t magic; + size_t psize; + size_t gsize; + +#ifndef DL_PLATFORM_WIN32 + psize = malloc_getpagesize; + gsize = ((DEFAULT_GRANULARITY != 0)? DEFAULT_GRANULARITY : psize); +#else /* DL_PLATFORM_WIN32 */ + { + SYSTEM_INFO system_info; + GetSystemInfo(&system_info); + psize = system_info.dwPageSize; + gsize = ((DEFAULT_GRANULARITY != 0)? +DEFAULT_GRANULARITY : system_info.dwAllocationGranularity); + } +#endif /* DL_PLATFORM_WIN32 */ + + /* Sanity-check configuration: + size_t must be unsigned and as wide as pointer type. + ints must be at least 4 bytes. + alignment must be at least 8. + Alignment, min chunk size, and page size must all be powers of 2. + */ + if ((sizeof(size_t) != sizeof(char*)) || + (MAX_SIZE_T < MIN_CHUNK_SIZE) || + (sizeof(int) < 4) || + (MALLOC_ALIGNMENT < (size_t)8U) || + ((MALLOC_ALIGNMENT & (MALLOC_ALIGNMENT-SIZE_T_ONE)) != 0) || + ((MCHUNK_SIZE & (MCHUNK_SIZE-SIZE_T_ONE)) != 0) || + ((gsize & (gsize-SIZE_T_ONE)) != 0) || + ((psize & (psize-SIZE_T_ONE)) != 0)) + ABORT; + + mparams.granularity = gsize; + mparams.page_size = psize; + mparams.mmap_threshold = DEFAULT_MMAP_THRESHOLD; + mparams.trim_threshold = DEFAULT_TRIM_THRESHOLD; +#if MORECORE_CONTIGUOUS + mparams.default_mflags = USE_LOCK_BIT|USE_MMAP_BIT; +#else /* MORECORE_CONTIGUOUS */ + mparams.default_mflags = USE_LOCK_BIT|USE_MMAP_BIT|USE_NONCONTIGUOUS_BIT; +#endif /* MORECORE_CONTIGUOUS */ + +#if !ONLY_MSPACES + /* Set up lock for main malloc area */ + gm->mflags = mparams.default_mflags; + INITIAL_LOCK(&gm->mutex); +#endif + + { +#if USE_DEV_RANDOM + int fd; + unsigned char buf[sizeof(size_t)]; + /* Try to use /dev/urandom, else fall back on using time */ + if ((fd = open("/dev/urandom", O_RDONLY)) >= 0 && + read(fd, buf, sizeof(buf)) == sizeof(buf)) { + magic = *((size_t *) buf); + close(fd); + } + else +#endif /* USE_DEV_RANDOM */ + +#if defined(_XBOX) || defined(X360) + +#elif defined(DL_PLATFORM_WIN32) + magic = (size_t)(GetTickCount() ^ (size_t)0x55555555U); +#else + magic = (size_t)(time(0) ^ (size_t)0x55555555U); +#endif + magic |= (size_t)8U; /* ensure nonzero */ + magic &= ~(size_t)7U; /* improve chances of fault for bad values */ + mparams.magic = magic; + } + } + + RELEASE_MALLOC_GLOBAL_LOCK(); + return 1; +} + +/* support for mallopt */ +static int change_mparam(int param_number, int value) { + size_t val; + ensure_initialization(); + val = (value == -1)? MAX_SIZE_T : (size_t)value; + switch(param_number) { + case M_TRIM_THRESHOLD: + mparams.trim_threshold = val; + return 1; + case M_GRANULARITY: + if (val >= mparams.page_size && ((val & (val-1)) == 0)) { + mparams.granularity = val; + return 1; + } + else + return 0; + case M_MMAP_THRESHOLD: + mparams.mmap_threshold = val; + return 1; + default: + return 0; + } +} + +#if DEBUG +/* ------------------------- Debugging Support --------------------------- */ + +/* Check properties of any chunk, whether free, inuse, mmapped etc */ +static void do_check_any_chunk(mstate m, mchunkptr p) { + assert((is_aligned(chunk2mem(p))) || (p->head == FENCEPOST_HEAD)); + assert(ok_address(m, p)); +} + +/* Check properties of top chunk */ +static void do_check_top_chunk(mstate m, mchunkptr p) { + msegmentptr sp = segment_holding(m, (char*)p); + size_t sz = p->head & ~INUSE_BITS; /* third-lowest bit can be set! */ + assert(sp != 0); + assert((is_aligned(chunk2mem(p))) || (p->head == FENCEPOST_HEAD)); + assert(ok_address(m, p)); + assert(sz == m->topsize); + assert(sz > 0); + assert(sz == ((sp->base + sp->size) - (char*)p) - TOP_FOOT_SIZE); + assert(pinuse(p)); + assert(!pinuse(chunk_plus_offset(p, sz))); +} + +/* Check properties of (inuse) mmapped chunks */ +static void do_check_mmapped_chunk(mstate m, mchunkptr p) { + size_t sz = chunksize(p); + size_t len = (sz + (p->prev_foot) + MMAP_FOOT_PAD); + assert(is_mmapped(p)); + assert(use_mmap(m)); + assert((is_aligned(chunk2mem(p))) || (p->head == FENCEPOST_HEAD)); + assert(ok_address(m, p)); + assert(!is_small(sz)); + assert((len & (mparams.page_size-SIZE_T_ONE)) == 0); + assert(chunk_plus_offset(p, sz)->head == FENCEPOST_HEAD); + assert(chunk_plus_offset(p, sz+SIZE_T_SIZE)->head == 0); +} + +/* Check properties of inuse chunks */ +static void do_check_inuse_chunk(mstate m, mchunkptr p) { + do_check_any_chunk(m, p); + assert(is_inuse(p)); + assert(next_pinuse(p)); + /* If not pinuse and not mmapped, previous chunk has OK offset */ + assert(is_mmapped(p) || pinuse(p) || next_chunk(prev_chunk(p)) == p); + if (is_mmapped(p)) + do_check_mmapped_chunk(m, p); +} + +/* Check properties of free chunks */ +static void do_check_free_chunk(mstate m, mchunkptr p) { + size_t sz = chunksize(p); + mchunkptr next = chunk_plus_offset(p, sz); + do_check_any_chunk(m, p); + assert(!is_inuse(p)); + assert(!next_pinuse(p)); + assert (!is_mmapped(p)); + if (p != m->dv && p != m->top) { + if (sz >= MIN_CHUNK_SIZE) { + assert((sz & CHUNK_ALIGN_MASK) == 0); + assert(is_aligned(chunk2mem(p))); + assert(next->prev_foot == sz); + assert(pinuse(p)); + assert (next == m->top || is_inuse(next)); + assert(p->fd->bk == p); + assert(p->bk->fd == p); + } + else /* markers are always of size SIZE_T_SIZE */ + assert(sz == SIZE_T_SIZE); + } +} + +/* Check properties of malloced chunks at the point they are malloced */ +static void do_check_malloced_chunk(mstate m, void* mem, size_t s) { + if (mem != 0) { + mchunkptr p = mem2chunk(mem); + size_t sz = p->head & ~INUSE_BITS; + do_check_inuse_chunk(m, p); + assert((sz & CHUNK_ALIGN_MASK) == 0); + assert(sz >= MIN_CHUNK_SIZE); + assert(sz >= s); + /* unless mmapped, size is less than MIN_CHUNK_SIZE more than request */ + assert(is_mmapped(p) || sz < (s + MIN_CHUNK_SIZE)); + } +} + +/* Check a tree and its subtrees. */ +static void do_check_tree(mstate m, tchunkptr t) { + tchunkptr head = 0; + tchunkptr u = t; + bindex_t tindex = t->index; + size_t tsize = chunksize(t); + bindex_t idx; + compute_tree_index(tsize, idx); + assert(tindex == idx); + assert(tsize >= MIN_LARGE_SIZE); + assert(tsize >= minsize_for_tree_index(idx)); + assert((idx == NTREEBINS-1) || (tsize < minsize_for_tree_index((idx+1)))); + + do { /* traverse through chain of same-sized nodes */ + do_check_any_chunk(m, ((mchunkptr)u)); + assert(u->index == tindex); + assert(chunksize(u) == tsize); + assert(!is_inuse(u)); + assert(!next_pinuse(u)); + assert(u->fd->bk == u); + assert(u->bk->fd == u); + if (u->parent == 0) { + assert(u->child[0] == 0); + assert(u->child[1] == 0); + } + else { + assert(head == 0); /* only one node on chain has parent */ + head = u; + assert(u->parent != u); + assert (u->parent->child[0] == u || + u->parent->child[1] == u || + *((tbinptr*)(u->parent)) == u); + if (u->child[0] != 0) { + assert(u->child[0]->parent == u); + assert(u->child[0] != u); + do_check_tree(m, u->child[0]); + } + if (u->child[1] != 0) { + assert(u->child[1]->parent == u); + assert(u->child[1] != u); + do_check_tree(m, u->child[1]); + } + if (u->child[0] != 0 && u->child[1] != 0) { + assert(chunksize(u->child[0]) < chunksize(u->child[1])); + } + } + u = u->fd; + } while (u != t); + assert(head != 0); +} + +/* Check all the chunks in a treebin. */ +static void do_check_treebin(mstate m, bindex_t i) { + tbinptr* tb = treebin_at(m, i); + tchunkptr t = *tb; + int empty = (m->treemap & (1U << i)) == 0; + if (t == 0) + assert(empty); + if (!empty) + do_check_tree(m, t); +} + +/* Check all the chunks in a smallbin. */ +static void do_check_smallbin(mstate m, bindex_t i) { + sbinptr b = smallbin_at(m, i); + mchunkptr p = b->bk; + unsigned int empty = (m->smallmap & (1U << i)) == 0; + if (p == b) + assert(empty); + if (!empty) { + for (; p != b; p = p->bk) { + size_t size = chunksize(p); + mchunkptr q; + /* each chunk claims to be free */ + do_check_free_chunk(m, p); + /* chunk belongs in bin */ + assert(small_index(size) == i); + assert(p->bk == b || chunksize(p->bk) == chunksize(p)); + /* chunk is followed by an inuse chunk */ + q = next_chunk(p); + if (q->head != FENCEPOST_HEAD) + do_check_inuse_chunk(m, q); + } + } +} + +/* Find x in a bin. Used in other check functions. */ +static int bin_find(mstate m, mchunkptr x) { + size_t size = chunksize(x); + if (is_small(size)) { + bindex_t sidx = small_index(size); + sbinptr b = smallbin_at(m, sidx); + if (smallmap_is_marked(m, sidx)) { + mchunkptr p = b; + do { + if (p == x) + return 1; + } while ((p = p->fd) != b); + } + } + else { + bindex_t tidx; + compute_tree_index(size, tidx); + if (treemap_is_marked(m, tidx)) { + tchunkptr t = *treebin_at(m, tidx); + size_t sizebits = size << leftshift_for_tree_index(tidx); + while (t != 0 && chunksize(t) != size) { + t = t->child[(sizebits >> (SIZE_T_BITSIZE-SIZE_T_ONE)) & 1]; + sizebits <<= 1; + } + if (t != 0) { + tchunkptr u = t; + do { + if (u == (tchunkptr)x) + return 1; + } while ((u = u->fd) != t); + } + } + } + return 0; +} + +/* Traverse each chunk and check it; return total */ +static size_t traverse_and_check(mstate m) { + size_t sum = 0; + if (is_initialized(m)) { + msegmentptr s = &m->seg; + sum += m->topsize + TOP_FOOT_SIZE; + while (s != 0) { + mchunkptr q = align_as_chunk(s->base); + mchunkptr lastq = 0; + assert(pinuse(q)); + while (segment_holds(s, q) && + q != m->top && q->head != FENCEPOST_HEAD) { + sum += chunksize(q); + if (is_inuse(q)) { + assert(!bin_find(m, q)); + do_check_inuse_chunk(m, q); + } + else { + assert(q == m->dv || bin_find(m, q)); + assert(lastq == 0 || is_inuse(lastq)); /* Not 2 consecutive free */ + do_check_free_chunk(m, q); + } + lastq = q; + q = next_chunk(q); + } + s = s->next; + } + } + return sum; +} + +/* Check all properties of malloc_state. */ +static void do_check_malloc_state(mstate m) { + bindex_t i; + size_t total; + /* check bins */ + for (i = 0; i < NSMALLBINS; ++i) + do_check_smallbin(m, i); + for (i = 0; i < NTREEBINS; ++i) + do_check_treebin(m, i); + + if (m->dvsize != 0) { /* check dv chunk */ + do_check_any_chunk(m, m->dv); + assert(m->dvsize == chunksize(m->dv)); + assert(m->dvsize >= MIN_CHUNK_SIZE); + assert(bin_find(m, m->dv) == 0); + } + + if (m->top != 0) { /* check top chunk */ + do_check_top_chunk(m, m->top); + /*assert(m->topsize == chunksize(m->top)); redundant */ + assert(m->topsize > 0); + assert(bin_find(m, m->top) == 0); + } + + total = traverse_and_check(m); + assert(total <= m->footprint); + assert(m->footprint <= m->max_footprint); +} +#endif /* DEBUG */ + +/* ----------------------------- statistics ------------------------------ */ + +#if !NO_MALLINFO +static struct mallinfo internal_mallinfo(mstate m) { + struct mallinfo nm = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; + ensure_initialization(); + if (!PREACTION(m)) { + check_malloc_state(m); + if (is_initialized(m)) { + size_t nfree = SIZE_T_ONE; /* top always free */ + size_t mfree = m->topsize + TOP_FOOT_SIZE; + size_t sum = mfree; + msegmentptr s = &m->seg; + while (s != 0) { + mchunkptr q = align_as_chunk(s->base); + while (segment_holds(s, q) && + q != m->top && q->head != FENCEPOST_HEAD) { + size_t sz = chunksize(q); + sum += sz; + if (!is_inuse(q)) { + mfree += sz; + ++nfree; + } + q = next_chunk(q); + } + s = s->next; + } + + nm.arena = sum; + nm.ordblks = nfree; + nm.hblkhd = m->footprint - sum; + nm.usmblks = m->max_footprint; + nm.uordblks = m->footprint - mfree; + nm.fordblks = mfree; + nm.keepcost = m->topsize; + } + + POSTACTION(m); + } + return nm; +} +#endif /* !NO_MALLINFO */ + +static void internal_malloc_stats(mstate m) { + ensure_initialization(); + if (!PREACTION(m)) { + size_t maxfp = 0; + size_t fp = 0; + size_t used = 0; + check_malloc_state(m); + if (is_initialized(m)) { + msegmentptr s = &m->seg; + maxfp = m->max_footprint; + fp = m->footprint; + used = fp - (m->topsize + TOP_FOOT_SIZE); + + while (s != 0) { + mchunkptr q = align_as_chunk(s->base); + while (segment_holds(s, q) && + q != m->top && q->head != FENCEPOST_HEAD) { + if (!is_inuse(q)) + used -= chunksize(q); + q = next_chunk(q); + } + s = s->next; + } + } + + fprintf(stderr, "max system bytes = %10lu\n", (unsigned long)(maxfp)); + fprintf(stderr, "system bytes = %10lu\n", (unsigned long)(fp)); + fprintf(stderr, "in use bytes = %10lu\n", (unsigned long)(used)); + + POSTACTION(m); + } +} + +/* ----------------------- Operations on smallbins ----------------------- */ + +/* +Various forms of linking and unlinking are defined as macros. Even +the ones for trees, which are very long but have very short typical +paths. This is ugly but reduces reliance on inlining support of +compilers. +*/ + +/* Link a free chunk into a smallbin */ +#define insert_small_chunk(M, P, S) {\ + bindex_t I = small_index(S);\ + mchunkptr B = smallbin_at(M, I);\ + mchunkptr F = B;\ + assert(S >= MIN_CHUNK_SIZE);\ + if (!smallmap_is_marked(M, I))\ + mark_smallmap(M, I);\ + else if (RTCHECK(ok_address(M, B->fd)))\ + F = B->fd;\ + else {\ + CORRUPTION_ERROR_ACTION(M);\ +}\ + B->fd = P;\ + F->bk = P;\ + P->fd = F;\ + P->bk = B;\ +} + +/* Unlink a chunk from a smallbin */ +#define unlink_small_chunk(M, P, S) {\ + mchunkptr F = P->fd;\ + mchunkptr B = P->bk;\ + bindex_t I = small_index(S);\ + assert(P != B);\ + assert(P != F);\ + assert(chunksize(P) == small_index2size(I));\ + if (F == B)\ + clear_smallmap(M, I);\ + else if (RTCHECK((F == smallbin_at(M,I) || ok_address(M, F)) &&\ + (B == smallbin_at(M,I) || ok_address(M, B)))) {\ + F->bk = B;\ + B->fd = F;\ +}\ + else {\ + CORRUPTION_ERROR_ACTION(M);\ +}\ +} + +/* Unlink the first chunk from a smallbin */ +#define unlink_first_small_chunk(M, B, P, I) {\ + mchunkptr F = P->fd;\ + assert(P != B);\ + assert(P != F);\ + assert(chunksize(P) == small_index2size(I));\ + if (B == F)\ + clear_smallmap(M, I);\ + else if (RTCHECK(ok_address(M, F))) {\ + B->fd = F;\ + F->bk = B;\ +}\ + else {\ + CORRUPTION_ERROR_ACTION(M);\ +}\ +} + + + +/* Replace dv node, binning the old one */ +/* Used only when dvsize known to be small */ +#define replace_dv(M, P, S) {\ + size_t DVS = M->dvsize;\ + if (DVS != 0) {\ + mchunkptr DV = M->dv;\ + assert(is_small(DVS));\ + insert_small_chunk(M, DV, DVS);\ + }\ + M->dvsize = S;\ + M->dv = P;\ +} + +/* ------------------------- Operations on trees ------------------------- */ + +/* Insert chunk into tree */ +#define insert_large_chunk(M, X, S) {\ + tbinptr* H;\ + bindex_t I;\ + compute_tree_index(S, I);\ + H = treebin_at(M, I);\ + X->index = I;\ + X->child[0] = X->child[1] = 0;\ + if (!treemap_is_marked(M, I)) {\ + mark_treemap(M, I);\ + *H = X;\ + X->parent = (tchunkptr)H;\ + X->fd = X->bk = X;\ + }\ + else {\ + tchunkptr T = *H;\ + size_t K = S << leftshift_for_tree_index(I);\ + for (;;) {\ + if (chunksize(T) != S) {\ + tchunkptr* C = &(T->child[(K >> (SIZE_T_BITSIZE-SIZE_T_ONE)) & 1]);\ + K <<= 1;\ + if (*C != 0)\ + T = *C;\ + else if (RTCHECK(ok_address(M, C))) {\ + *C = X;\ + X->parent = T;\ + X->fd = X->bk = X;\ + break;\ +}\ + else {\ + CORRUPTION_ERROR_ACTION(M);\ + break;\ +}\ + }\ + else {\ + tchunkptr F = T->fd;\ + if (RTCHECK(ok_address(M, T) && ok_address(M, F))) {\ + T->fd = F->bk = X;\ + X->fd = F;\ + X->bk = T;\ + X->parent = 0;\ + break;\ + }\ + else {\ + CORRUPTION_ERROR_ACTION(M);\ + break;\ +}\ +}\ + }\ +}\ +} + +/* +Unlink steps: + +1. If x is a chained node, unlink it from its same-sized fd/bk links +and choose its bk node as its replacement. +2. If x was the last node of its size, but not a leaf node, it must +be replaced with a leaf node (not merely one with an open left or +right), to make sure that lefts and rights of descendents +correspond properly to bit masks. We use the rightmost descendent +of x. We could use any other leaf, but this is easy to locate and +tends to counteract removal of leftmosts elsewhere, and so keeps +paths shorter than minimally guaranteed. This doesn't loop much +because on average a node in a tree is near the bottom. +3. If x is the base of a chain (i.e., has parent links) relink +x's parent and children to x's replacement (or null if none). +*/ + +#define unlink_large_chunk(M, X) {\ + tchunkptr XP = X->parent;\ + tchunkptr R;\ + if (X->bk != X) {\ + tchunkptr F = X->fd;\ + R = X->bk;\ + if (RTCHECK(ok_address(M, F))) {\ + F->bk = R;\ + R->fd = F;\ + }\ + else {\ + CORRUPTION_ERROR_ACTION(M);\ +}\ + }\ + else {\ + tchunkptr* RP;\ + if (((R = *(RP = &(X->child[1]))) != 0) ||\ + ((R = *(RP = &(X->child[0]))) != 0)) {\ + tchunkptr* CP;\ + while ((*(CP = &(R->child[1])) != 0) ||\ + (*(CP = &(R->child[0])) != 0)) {\ + R = *(RP = CP);\ +}\ + if (RTCHECK(ok_address(M, RP)))\ + *RP = 0;\ + else {\ + CORRUPTION_ERROR_ACTION(M);\ +}\ +}\ +}\ + if (XP != 0) {\ + tbinptr* H = treebin_at(M, X->index);\ + if (X == *H) {\ + if ((*H = R) == 0) \ + clear_treemap(M, X->index);\ + }\ + else if (RTCHECK(ok_address(M, XP))) {\ + if (XP->child[0] == X) \ + XP->child[0] = R;\ + else \ + XP->child[1] = R;\ +}\ + else\ + CORRUPTION_ERROR_ACTION(M);\ + if (R != 0) {\ + if (RTCHECK(ok_address(M, R))) {\ + tchunkptr C0, C1;\ + R->parent = XP;\ + if ((C0 = X->child[0]) != 0) {\ + if (RTCHECK(ok_address(M, C0))) {\ + R->child[0] = C0;\ + C0->parent = R;\ + }\ + else\ + CORRUPTION_ERROR_ACTION(M);\ + }\ + if ((C1 = X->child[1]) != 0) {\ + if (RTCHECK(ok_address(M, C1))) {\ + R->child[1] = C1;\ + C1->parent = R;\ + }\ + else\ + CORRUPTION_ERROR_ACTION(M);\ + }\ + }\ + else\ + CORRUPTION_ERROR_ACTION(M);\ + }\ + }\ +} + +/* Relays to large vs small bin operations */ + +#define insert_chunk(M, P, S)\ + if (is_small(S)) insert_small_chunk(M, P, S)\ + else { tchunkptr TP = (tchunkptr)(P); insert_large_chunk(M, TP, S); } + +#define unlink_chunk(M, P, S)\ + if (is_small(S)) unlink_small_chunk(M, P, S)\ + else { tchunkptr TP = (tchunkptr)(P); unlink_large_chunk(M, TP); } + + +/* Relays to internal calls to malloc/free from realloc, memalign etc */ + +#if ONLY_MSPACES +#define internal_malloc(m, b) rak_mspace_malloc(m, b) +#define internal_free(m, mem) rak_mspace_free(m,mem); +#else /* ONLY_MSPACES */ +#if MSPACES +#define internal_malloc(m, b)\ + (m == gm)? rdlmalloc(b) : rak_mspace_malloc(m, b) +#define internal_free(m, mem)\ + if (m == gm) rdlfree(mem); else rak_mspace_free(m,mem); +#else /* MSPACES */ +#define internal_malloc(m, b) rdlmalloc(b) +#define internal_free(m, mem) rdlfree(mem) +#endif /* MSPACES */ +#endif /* ONLY_MSPACES */ + +/* ----------------------- Direct-mmapping chunks ----------------------- */ + +/* +Directly mmapped chunks are set up with an offset to the start of +the mmapped region stored in the prev_foot field of the chunk. This +allows reconstruction of the required argument to MUNMAP when freed, +and also allows adjustment of the returned chunk to meet alignment +requirements (especially in memalign). +*/ + +/* Malloc using mmap */ +static void* mmap_alloc(mstate m, size_t nb) { + size_t mmsize = mmap_align(nb + SIX_SIZE_T_SIZES + CHUNK_ALIGN_MASK); + if (mmsize > nb) { /* Check for wrap around 0 */ + char* mm = (char*)(CALL_DIRECT_MMAP(mmsize)); + if (mm != CMFAIL) { + size_t offset = align_offset(chunk2mem(mm)); + size_t psize = mmsize - offset - MMAP_FOOT_PAD; + mchunkptr p = (mchunkptr)(mm + offset); + p->prev_foot = offset; + p->head = psize; + mark_inuse_foot(m, p, psize); + chunk_plus_offset(p, psize)->head = FENCEPOST_HEAD; + chunk_plus_offset(p, psize+SIZE_T_SIZE)->head = 0; + + if (m->least_addr == 0 || mm < m->least_addr) + m->least_addr = mm; + if ((m->footprint += mmsize) > m->max_footprint) + m->max_footprint = m->footprint; + assert(is_aligned(chunk2mem(p))); + check_mmapped_chunk(m, p); + return chunk2mem(p); + } + } + return 0; +} + +/* Realloc using mmap */ +static mchunkptr mmap_resize(mstate m, mchunkptr oldp, size_t nb) { + size_t oldsize = chunksize(oldp); + if (is_small(nb)) /* Can't shrink mmap regions below small size */ + return 0; + /* Keep old chunk if big enough but not too big */ + if (oldsize >= nb + SIZE_T_SIZE && + (oldsize - nb) <= (mparams.granularity << 1)) + return oldp; + else { + size_t offset = oldp->prev_foot; + size_t oldmmsize = oldsize + offset + MMAP_FOOT_PAD; + size_t newmmsize = mmap_align(nb + SIX_SIZE_T_SIZES + CHUNK_ALIGN_MASK); + char* cp = (char*)CALL_MREMAP((char*)oldp - offset, + oldmmsize, newmmsize, 1); + if (cp != CMFAIL) { + mchunkptr newp = (mchunkptr)(cp + offset); + size_t psize = newmmsize - offset - MMAP_FOOT_PAD; + newp->head = psize; + mark_inuse_foot(m, newp, psize); + chunk_plus_offset(newp, psize)->head = FENCEPOST_HEAD; + chunk_plus_offset(newp, psize+SIZE_T_SIZE)->head = 0; + + if (cp < m->least_addr) + m->least_addr = cp; + if ((m->footprint += newmmsize - oldmmsize) > m->max_footprint) + m->max_footprint = m->footprint; + check_mmapped_chunk(m, newp); + return newp; + } + } + return 0; +} + +/* -------------------------- mspace management -------------------------- */ + +/* Initialize top chunk and its size */ +static void init_top(mstate m, mchunkptr p, size_t psize) { + /* Ensure alignment */ + size_t offset = align_offset(chunk2mem(p)); + p = (mchunkptr)((char*)p + offset); + psize -= offset; + + m->top = p; + m->topsize = psize; + p->head = psize | PINUSE_BIT; + /* set size of fake trailing chunk holding overhead space only once */ + chunk_plus_offset(p, psize)->head = TOP_FOOT_SIZE; + m->trim_check = mparams.trim_threshold; /* reset on each update */ +} + +/* Initialize bins for a new mstate that is otherwise zeroed out */ +static void init_bins(mstate m) { + /* Establish circular links for smallbins */ + bindex_t i; + for (i = 0; i < NSMALLBINS; ++i) { + sbinptr bin = smallbin_at(m,i); + bin->fd = bin->bk = bin; + } +} + +#if PROCEED_ON_ERROR + +/* default corruption action */ +static void reset_on_error(mstate m) { + int i; + ++malloc_corruption_error_count; + /* Reinitialize fields to forget about all memory */ + m->smallbins = m->treebins = 0; + m->dvsize = m->topsize = 0; + m->seg.base = 0; + m->seg.size = 0; + m->seg.next = 0; + m->top = m->dv = 0; + for (i = 0; i < NTREEBINS; ++i) + *treebin_at(m, i) = 0; + init_bins(m); +} +#endif /* PROCEED_ON_ERROR */ + +/* Allocate chunk and prepend remainder with chunk in successor base. */ +static void* prepend_alloc(mstate m, char* newbase, char* oldbase, + size_t nb) { + mchunkptr p = align_as_chunk(newbase); + mchunkptr oldfirst = align_as_chunk(oldbase); + size_t psize = (char*)oldfirst - (char*)p; + mchunkptr q = chunk_plus_offset(p, nb); + size_t qsize = psize - nb; + set_size_and_pinuse_of_inuse_chunk(m, p, nb); + + assert((char*)oldfirst > (char*)q); + assert(pinuse(oldfirst)); + assert(qsize >= MIN_CHUNK_SIZE); + + /* consolidate remainder with first chunk of old base */ + if (oldfirst == m->top) { + size_t tsize = m->topsize += qsize; + m->top = q; + q->head = tsize | PINUSE_BIT; + check_top_chunk(m, q); + } + else if (oldfirst == m->dv) { + size_t dsize = m->dvsize += qsize; + m->dv = q; + set_size_and_pinuse_of_free_chunk(q, dsize); + } + else { + if (!is_inuse(oldfirst)) { + size_t nsize = chunksize(oldfirst); + unlink_chunk(m, oldfirst, nsize); + oldfirst = chunk_plus_offset(oldfirst, nsize); + qsize += nsize; + } + set_free_with_pinuse(q, qsize, oldfirst); + insert_chunk(m, q, qsize); + check_free_chunk(m, q); + } + + check_malloced_chunk(m, chunk2mem(p), nb); + return chunk2mem(p); +} + +/* Add a segment to hold a new noncontiguous region */ +static void add_segment(mstate m, char* tbase, size_t tsize, flag_t mmapped) { + /* Determine locations and sizes of segment, fenceposts, old top */ + char* old_top = (char*)m->top; + msegmentptr oldsp = segment_holding(m, old_top); + char* old_end = oldsp->base + oldsp->size; + size_t ssize = pad_request(sizeof(struct malloc_segment)); + char* rawsp = old_end - (ssize + FOUR_SIZE_T_SIZES + CHUNK_ALIGN_MASK); + size_t offset = align_offset(chunk2mem(rawsp)); + char* asp = rawsp + offset; + char* csp = (asp < (old_top + MIN_CHUNK_SIZE))? old_top : asp; + mchunkptr sp = (mchunkptr)csp; + msegmentptr ss = (msegmentptr)(chunk2mem(sp)); + mchunkptr tnext = chunk_plus_offset(sp, ssize); + mchunkptr p = tnext; + int nfences = 0; + + /* reset top to new space */ + init_top(m, (mchunkptr)tbase, tsize - TOP_FOOT_SIZE); + + /* Set up segment record */ + assert(is_aligned(ss)); + set_size_and_pinuse_of_inuse_chunk(m, sp, ssize); + *ss = m->seg; /* Push current record */ + m->seg.base = tbase; + m->seg.size = tsize; + m->seg.sflags = mmapped; + m->seg.next = ss; + + /* Insert trailing fenceposts */ + for (;;) { + mchunkptr nextp = chunk_plus_offset(p, SIZE_T_SIZE); + p->head = FENCEPOST_HEAD; + ++nfences; + if ((char*)(&(nextp->head)) < old_end) + p = nextp; + else + break; + } + assert(nfences >= 2); + + /* Insert the rest of old top into a bin as an ordinary free chunk */ + if (csp != old_top) { + mchunkptr q = (mchunkptr)old_top; + size_t psize = csp - old_top; + mchunkptr tn = chunk_plus_offset(q, psize); + set_free_with_pinuse(q, psize, tn); + insert_chunk(m, q, psize); + } + + check_top_chunk(m, m->top); +} + +/* -------------------------- System allocation -------------------------- */ + +/* Get memory from system using MORECORE or MMAP */ +static void* sys_alloc(mstate m, size_t nb) { + char* tbase = CMFAIL; + size_t tsize = 0; + flag_t mmap_flag = 0; + + ensure_initialization(); + + /* Directly map large chunks, but only if already initialized */ + if (use_mmap(m) && nb >= mparams.mmap_threshold && m->topsize != 0) { + void* mem = mmap_alloc(m, nb); + if (mem != 0) + return mem; + } + + /* + Try getting memory in any of three ways (in most-preferred to + least-preferred order): + 1. A call to MORECORE that can normally contiguously extend memory. + (disabled if not MORECORE_CONTIGUOUS or not HAVE_MORECORE or + or main space is mmapped or a previous contiguous call failed) + 2. A call to MMAP new space (disabled if not HAVE_MMAP). + Note that under the default settings, if MORECORE is unable to + fulfill a request, and HAVE_MMAP is true, then mmap is + used as a noncontiguous system allocator. This is a useful backup + strategy for systems with holes in address spaces -- in this case + sbrk cannot contiguously expand the heap, but mmap may be able to + find space. + 3. A call to MORECORE that cannot usually contiguously extend memory. + (disabled if not HAVE_MORECORE) + + In all cases, we need to request enough bytes from system to ensure + we can malloc nb bytes upon success, so pad with enough space for + top_foot, plus alignment-pad to make sure we don't lose bytes if + not on boundary, and round this up to a granularity unit. + */ + + if (MORECORE_CONTIGUOUS && !use_noncontiguous(m)) { + char* br = CMFAIL; + msegmentptr ss = (m->top == 0)? 0 : segment_holding(m, (char*)m->top); + size_t asize = 0; + ACQUIRE_MALLOC_GLOBAL_LOCK(); + + if (ss == 0) { /* First time through or recovery */ + char* base = (char*)CALL_MORECORE(0); + if (base != CMFAIL) { + asize = granularity_align(nb + SYS_ALLOC_PADDING); + /* Adjust to end on a page boundary */ + if (!is_page_aligned(base)) + asize += (page_align((size_t)base) - (size_t)base); + /* Can't call MORECORE if size is negative when treated as signed */ + if (asize < HALF_MAX_SIZE_T && + (br = (char*)(CALL_MORECORE(asize))) == base) { + tbase = base; + tsize = asize; + } + } + } + else { + /* Subtract out existing available top space from MORECORE request. */ + asize = granularity_align(nb - m->topsize + SYS_ALLOC_PADDING); + /* Use mem here only if it did continuously extend old space */ + if (asize < HALF_MAX_SIZE_T && + (br = (char*)(CALL_MORECORE(asize))) == ss->base+ss->size) { + tbase = br; + tsize = asize; + } + } + + if (tbase == CMFAIL) { /* Cope with partial failure */ + if (br != CMFAIL) { /* Try to use/extend the space we did get */ + if (asize < HALF_MAX_SIZE_T && + asize < nb + SYS_ALLOC_PADDING) { + size_t esize = granularity_align(nb + SYS_ALLOC_PADDING - asize); + if (esize < HALF_MAX_SIZE_T) { + char* end = (char*)CALL_MORECORE(esize); + if (end != CMFAIL) + asize += esize; + else { /* Can't use; try to release */ + (void) CALL_MORECORE(-asize); + br = CMFAIL; + } + } + } + } + if (br != CMFAIL) { /* Use the space we did get */ + tbase = br; + tsize = asize; + } + else + disable_contiguous(m); /* Don't try contiguous path in the future */ + } + + RELEASE_MALLOC_GLOBAL_LOCK(); + } + + if (HAVE_MMAP && tbase == CMFAIL) { /* Try MMAP */ + size_t rsize = granularity_align(nb + SYS_ALLOC_PADDING); + if (rsize > nb) { /* Fail if wraps around zero */ + char* mp = (char*)(CALL_MMAP(rsize)); + if (mp != CMFAIL) { + tbase = mp; + tsize = rsize; + mmap_flag = USE_MMAP_BIT; + } + } + } + + if (HAVE_MORECORE && tbase == CMFAIL) { /* Try noncontiguous MORECORE */ + size_t asize = granularity_align(nb + SYS_ALLOC_PADDING); + if (asize < HALF_MAX_SIZE_T) { + char* br = CMFAIL; + char* end = CMFAIL; + ACQUIRE_MALLOC_GLOBAL_LOCK(); + br = (char*)(CALL_MORECORE(asize)); + end = (char*)(CALL_MORECORE(0)); + RELEASE_MALLOC_GLOBAL_LOCK(); + if (br != CMFAIL && end != CMFAIL && br < end) { + size_t ssize = end - br; + if (ssize > nb + TOP_FOOT_SIZE) { + tbase = br; + tsize = ssize; + } + } + } + } + + if (tbase != CMFAIL) { + + if ((m->footprint += tsize) > m->max_footprint) + m->max_footprint = m->footprint; + + if (!is_initialized(m)) { /* first-time initialization */ + if (m->least_addr == 0 || tbase < m->least_addr) + m->least_addr = tbase; + m->seg.base = tbase; + m->seg.size = tsize; + m->seg.sflags = mmap_flag; + m->magic = mparams.magic; + m->release_checks = MAX_RELEASE_CHECK_RATE; + init_bins(m); +#if !ONLY_MSPACES + if (is_global(m)) + init_top(m, (mchunkptr)tbase, tsize - TOP_FOOT_SIZE); + else +#endif + { + /* Offset top by embedded malloc_state */ + mchunkptr mn = next_chunk(mem2chunk(m)); + init_top(m, mn, (size_t)((tbase + tsize) - (char*)mn) -TOP_FOOT_SIZE); + } + } + + else { + /* Try to merge with an existing segment */ + msegmentptr sp = &m->seg; + /* Only consider most recent segment if traversal suppressed */ + while (sp != 0 && tbase != sp->base + sp->size) + sp = (NO_SEGMENT_TRAVERSAL) ? 0 : sp->next; + if (sp != 0 && + !is_extern_segment(sp) && + (sp->sflags & USE_MMAP_BIT) == mmap_flag && + segment_holds(sp, m->top)) { /* append */ + sp->size += tsize; + init_top(m, m->top, m->topsize + tsize); + } + else { + if (tbase < m->least_addr) + m->least_addr = tbase; + sp = &m->seg; + while (sp != 0 && sp->base != tbase + tsize) + sp = (NO_SEGMENT_TRAVERSAL) ? 0 : sp->next; + if (sp != 0 && + !is_extern_segment(sp) && + (sp->sflags & USE_MMAP_BIT) == mmap_flag) { + char* oldbase = sp->base; + sp->base = tbase; + sp->size += tsize; + return prepend_alloc(m, tbase, oldbase, nb); + } + else + add_segment(m, tbase, tsize, mmap_flag); + } + } + + if (nb < m->topsize) { /* Allocate from new or extended top space */ + size_t rsize = m->topsize -= nb; + mchunkptr p = m->top; + mchunkptr r = m->top = chunk_plus_offset(p, nb); + r->head = rsize | PINUSE_BIT; + set_size_and_pinuse_of_inuse_chunk(m, p, nb); + check_top_chunk(m, m->top); + check_malloced_chunk(m, chunk2mem(p), nb); + return chunk2mem(p); + } + } + + MALLOC_FAILURE_ACTION; + return 0; +} + +/* ----------------------- system deallocation -------------------------- */ + +/* Unmap and unlink any mmapped segments that don't contain used chunks */ +static size_t release_unused_segments(mstate m) { + size_t released = 0; + int nsegs = 0; + msegmentptr pred = &m->seg; + msegmentptr sp = pred->next; + while (sp != 0) { + char* base = sp->base; + size_t size = sp->size; + msegmentptr next = sp->next; + ++nsegs; + if (is_mmapped_segment(sp) && !is_extern_segment(sp)) { + mchunkptr p = align_as_chunk(base); + size_t psize = chunksize(p); + /* Can unmap if first chunk holds entire segment and not pinned */ + if (!is_inuse(p) && (char*)p + psize >= base + size - TOP_FOOT_SIZE) { + tchunkptr tp = (tchunkptr)p; + assert(segment_holds(sp, (char*)sp)); + if (p == m->dv) { + m->dv = 0; + m->dvsize = 0; + } + else { + unlink_large_chunk(m, tp); + } + if (CALL_MUNMAP(base, size) == 0) { + released += size; + m->footprint -= size; + /* unlink obsoleted record */ + sp = pred; + sp->next = next; + } + else { /* back out if cannot unmap */ + insert_large_chunk(m, tp, psize); + } + } + } + if (NO_SEGMENT_TRAVERSAL) /* scan only first segment */ + break; + pred = sp; + sp = next; + } + /* Reset check counter */ + m->release_checks = ((nsegs > MAX_RELEASE_CHECK_RATE)? +nsegs : MAX_RELEASE_CHECK_RATE); + return released; +} + +static int sys_trim(mstate m, size_t pad) { + size_t released = 0; + ensure_initialization(); + if (pad < MAX_REQUEST && is_initialized(m)) { + pad += TOP_FOOT_SIZE; /* ensure enough room for segment overhead */ + + if (m->topsize > pad) { + /* Shrink top space in granularity-size units, keeping at least one */ + size_t unit = mparams.granularity; + size_t extra = ((m->topsize - pad + (unit - SIZE_T_ONE)) / unit - + SIZE_T_ONE) * unit; + msegmentptr sp = segment_holding(m, (char*)m->top); + + if (!is_extern_segment(sp)) { + if (is_mmapped_segment(sp)) { + if (HAVE_MMAP && + sp->size >= extra && + !has_segment_link(m, sp)) { /* can't shrink if pinned */ + size_t newsize = sp->size - extra; + /* Prefer mremap, fall back to munmap */ + if ((CALL_MREMAP(sp->base, sp->size, newsize, 0) != MFAIL) || + (CALL_MUNMAP(sp->base + newsize, extra) == 0)) { + released = extra; + } + } + } + else if (HAVE_MORECORE) { + if (extra >= HALF_MAX_SIZE_T) /* Avoid wrapping negative */ + extra = (HALF_MAX_SIZE_T) + SIZE_T_ONE - unit; + ACQUIRE_MALLOC_GLOBAL_LOCK(); + { + /* Make sure end of memory is where we last set it. */ + char* old_br = (char*)(CALL_MORECORE(0)); + if (old_br == sp->base + sp->size) { + char* rel_br = (char*)(CALL_MORECORE(-extra)); + char* new_br = (char*)(CALL_MORECORE(0)); + if (rel_br != CMFAIL && new_br < old_br) + released = old_br - new_br; + } + } + RELEASE_MALLOC_GLOBAL_LOCK(); + } + } + + if (released != 0) { + sp->size -= released; + m->footprint -= released; + init_top(m, m->top, m->topsize - released); + check_top_chunk(m, m->top); + } + } + + /* Unmap any unused mmapped segments */ + if (HAVE_MMAP) + released += release_unused_segments(m); + + /* On failure, disable autotrim to avoid repeated failed future calls */ + if (released == 0 && m->topsize > m->trim_check) + m->trim_check = MAX_SIZE_T; + } + + return (released != 0)? 1 : 0; +} + + +/* ---------------------------- malloc support --------------------------- */ + +/* allocate a large request from the best fitting chunk in a treebin */ +static void* tmalloc_large(mstate m, size_t nb) { + tchunkptr v = 0; + size_t rsize = -nb; /* Unsigned negation */ + tchunkptr t; + bindex_t idx; + compute_tree_index(nb, idx); + if ((t = *treebin_at(m, idx)) != 0) { + /* Traverse tree for this bin looking for node with size == nb */ + size_t sizebits = nb << leftshift_for_tree_index(idx); + tchunkptr rst = 0; /* The deepest untaken right subtree */ + for (;;) { + tchunkptr rt; + size_t trem = chunksize(t) - nb; + if (trem < rsize) { + v = t; + if ((rsize = trem) == 0) + break; + } + rt = t->child[1]; + t = t->child[(sizebits >> (SIZE_T_BITSIZE-SIZE_T_ONE)) & 1]; + if (rt != 0 && rt != t) + rst = rt; + if (t == 0) { + t = rst; /* set t to least subtree holding sizes > nb */ + break; + } + sizebits <<= 1; + } + } + if (t == 0 && v == 0) { /* set t to root of next non-empty treebin */ + binmap_t leftbits = left_bits(idx2bit(idx)) & m->treemap; + if (leftbits != 0) { + bindex_t i; + binmap_t leastbit = least_bit(leftbits); + compute_bit2idx(leastbit, i); + t = *treebin_at(m, i); + } + } + + while (t != 0) { /* find smallest of tree or subtree */ + size_t trem = chunksize(t) - nb; + if (trem < rsize) { + rsize = trem; + v = t; + } + t = leftmost_child(t); + } + + /* If dv is a better fit, return 0 so malloc will use it */ + if (v != 0 && rsize < (size_t)(m->dvsize - nb)) { + if (RTCHECK(ok_address(m, v))) { /* split */ + mchunkptr r = chunk_plus_offset(v, nb); + assert(chunksize(v) == rsize + nb); + if (RTCHECK(ok_next(v, r))) { + unlink_large_chunk(m, v); + if (rsize < MIN_CHUNK_SIZE) + set_inuse_and_pinuse(m, v, (rsize + nb)); + else { + set_size_and_pinuse_of_inuse_chunk(m, v, nb); + set_size_and_pinuse_of_free_chunk(r, rsize); + insert_chunk(m, r, rsize); + } + return chunk2mem(v); + } + } + CORRUPTION_ERROR_ACTION(m); + } + return 0; +} + +/* allocate a small request from the best fitting chunk in a treebin */ +static void* tmalloc_small(mstate m, size_t nb) { + tchunkptr t, v; + size_t rsize; + bindex_t i; + binmap_t leastbit = least_bit(m->treemap); + compute_bit2idx(leastbit, i); + v = t = *treebin_at(m, i); + rsize = chunksize(t) - nb; + + while ((t = leftmost_child(t)) != 0) { + size_t trem = chunksize(t) - nb; + if (trem < rsize) { + rsize = trem; + v = t; + } + } + + if (RTCHECK(ok_address(m, v))) { + mchunkptr r = chunk_plus_offset(v, nb); + assert(chunksize(v) == rsize + nb); + if (RTCHECK(ok_next(v, r))) { + unlink_large_chunk(m, v); + if (rsize < MIN_CHUNK_SIZE) + set_inuse_and_pinuse(m, v, (rsize + nb)); + else { + set_size_and_pinuse_of_inuse_chunk(m, v, nb); + set_size_and_pinuse_of_free_chunk(r, rsize); + replace_dv(m, r, rsize); + } + return chunk2mem(v); + } + } + + CORRUPTION_ERROR_ACTION(m); + return 0; +} + +/* --------------------------- realloc support --------------------------- */ + +static void* internal_realloc(mstate m, void* oldmem, size_t bytes) { + if (bytes >= MAX_REQUEST) { + MALLOC_FAILURE_ACTION; + return 0; + } + if (!PREACTION(m)) { + mchunkptr oldp = mem2chunk(oldmem); + size_t oldsize = chunksize(oldp); + mchunkptr next = chunk_plus_offset(oldp, oldsize); + mchunkptr newp = 0; + void* extra = 0; + + /* Try to either shrink or extend into top. Else malloc-copy-free */ + + if (RTCHECK(ok_address(m, oldp) && ok_inuse(oldp) && + ok_next(oldp, next) && ok_pinuse(next))) { + size_t nb = request2size(bytes); + if (is_mmapped(oldp)) + newp = mmap_resize(m, oldp, nb); + else if (oldsize >= nb) { /* already big enough */ + size_t rsize = oldsize - nb; + newp = oldp; + if (rsize >= MIN_CHUNK_SIZE) { + mchunkptr remainder = chunk_plus_offset(newp, nb); + set_inuse(m, newp, nb); + set_inuse_and_pinuse(m, remainder, rsize); + extra = chunk2mem(remainder); + } + } + else if (next == m->top && oldsize + m->topsize > nb) { + /* Expand into top */ + size_t newsize = oldsize + m->topsize; + size_t newtopsize = newsize - nb; + mchunkptr newtop = chunk_plus_offset(oldp, nb); + set_inuse(m, oldp, nb); + newtop->head = newtopsize |PINUSE_BIT; + m->top = newtop; + m->topsize = newtopsize; + newp = oldp; + } + } + else { + USAGE_ERROR_ACTION(m, oldmem); + POSTACTION(m); + return 0; + } +#if DEBUG + if (newp != 0) { + check_inuse_chunk(m, newp); /* Check requires lock */ + } +#endif + + POSTACTION(m); + + if (newp != 0) { + if (extra != 0) { + internal_free(m, extra); + } + return chunk2mem(newp); + } + else { + void* newmem = internal_malloc(m, bytes); + if (newmem != 0) { + size_t oc = oldsize - overhead_for(oldp); + memcpy(newmem, oldmem, (oc < bytes)? oc : bytes); + internal_free(m, oldmem); + } + return newmem; + } + } + return 0; +} + +/* --------------------------- memalign support -------------------------- */ + +static void* internal_memalign(mstate m, size_t alignment, size_t bytes) { + if (alignment <= MALLOC_ALIGNMENT) /* Can just use malloc */ + return internal_malloc(m, bytes); + if (alignment < MIN_CHUNK_SIZE) /* must be at least a minimum chunk size */ + alignment = MIN_CHUNK_SIZE; + if ((alignment & (alignment-SIZE_T_ONE)) != 0) {/* Ensure a power of 2 */ + size_t a = MALLOC_ALIGNMENT << 1; + while (a < alignment) a <<= 1; + alignment = a; + } + + if (bytes >= MAX_REQUEST - alignment) { + if (m != 0) { /* Test isn't needed but avoids compiler warning */ + MALLOC_FAILURE_ACTION; + } + } + else { + size_t nb = request2size(bytes); + size_t req = nb + alignment + MIN_CHUNK_SIZE - CHUNK_OVERHEAD; + char* mem = (char*)internal_malloc(m, req); + if (mem != 0) { + void* leader = 0; + void* trailer = 0; + mchunkptr p = mem2chunk(mem); + + if (PREACTION(m)) return 0; + if ((((size_t)(mem)) % alignment) != 0) { /* misaligned */ + /* + Find an aligned spot inside chunk. Since we need to give + back leading space in a chunk of at least MIN_CHUNK_SIZE, if + the first calculation places us at a spot with less than + MIN_CHUNK_SIZE leader, we can move to the next aligned spot. + We've allocated enough total room so that this is always + possible. + */ + char* br = (char*)mem2chunk((size_t)(((size_t)(mem + + alignment - + SIZE_T_ONE)) & + -alignment)); + char* pos = ((size_t)(br - (char*)(p)) >= MIN_CHUNK_SIZE)? +br : br+alignment; + mchunkptr newp = (mchunkptr)pos; + size_t leadsize = pos - (char*)(p); + size_t newsize = chunksize(p) - leadsize; + + if (is_mmapped(p)) { /* For mmapped chunks, just adjust offset */ + newp->prev_foot = p->prev_foot + leadsize; + newp->head = newsize; + } + else { /* Otherwise, give back leader, use the rest */ + set_inuse(m, newp, newsize); + set_inuse(m, p, leadsize); + leader = chunk2mem(p); + } + p = newp; + } + + /* Give back spare room at the end */ + if (!is_mmapped(p)) { + size_t size = chunksize(p); + if (size > nb + MIN_CHUNK_SIZE) { + size_t remainder_size = size - nb; + mchunkptr remainder = chunk_plus_offset(p, nb); + set_inuse(m, p, nb); + set_inuse(m, remainder, remainder_size); + trailer = chunk2mem(remainder); + } + } + + assert (chunksize(p) >= nb); + assert((((size_t)(chunk2mem(p))) % alignment) == 0); + check_inuse_chunk(m, p); + POSTACTION(m); + if (leader != 0) { + internal_free(m, leader); + } + if (trailer != 0) { + internal_free(m, trailer); + } + return chunk2mem(p); + } + } + return 0; +} + +/* ------------------------ comalloc/coalloc support --------------------- */ + +static void** ialloc(mstate m, + size_t n_elements, + size_t* sizes, + int opts, + void* chunks[]) { + /* + This provides common support for independent_X routines, handling + all of the combinations that can result. + + The opts arg has: + bit 0 set if all elements are same size (using sizes[0]) + bit 1 set if elements should be zeroed + */ + + size_t element_size; /* chunksize of each element, if all same */ + size_t contents_size; /* total size of elements */ + size_t array_size; /* request size of pointer array */ + void* mem; /* malloced aggregate space */ + mchunkptr p; /* corresponding chunk */ + size_t remainder_size; /* remaining bytes while splitting */ + void** marray; /* either "chunks" or malloced ptr array */ + mchunkptr array_chunk; /* chunk for malloced ptr array */ + flag_t was_enabled; /* to disable mmap */ + size_t size; + size_t i; + + ensure_initialization(); + /* compute array length, if needed */ + if (chunks != 0) { + if (n_elements == 0) + return chunks; /* nothing to do */ + marray = chunks; + array_size = 0; + } + else { + /* if empty req, must still return chunk representing empty array */ + if (n_elements == 0) + return (void**)internal_malloc(m, 0); + marray = 0; + array_size = request2size(n_elements * (sizeof(void*))); + } + + /* compute total element size */ + if (opts & 0x1) { /* all-same-size */ + element_size = request2size(*sizes); + contents_size = n_elements * element_size; + } + else { /* add up all the sizes */ + element_size = 0; + contents_size = 0; + for (i = 0; i != n_elements; ++i) + contents_size += request2size(sizes[i]); + } + + size = contents_size + array_size; + + /* + Allocate the aggregate chunk. First disable direct-mmapping so + malloc won't use it, since we would not be able to later + free/realloc space internal to a segregated mmap region. + */ + was_enabled = use_mmap(m); + disable_mmap(m); + mem = internal_malloc(m, size - CHUNK_OVERHEAD); + if (was_enabled) + enable_mmap(m); + if (mem == 0) + return 0; + + if (PREACTION(m)) return 0; + p = mem2chunk(mem); + remainder_size = chunksize(p); + + assert(!is_mmapped(p)); + + if (opts & 0x2) { /* optionally clear the elements */ + memset((size_t*)mem, 0, remainder_size - SIZE_T_SIZE - array_size); + } + + /* If not provided, allocate the pointer array as final part of chunk */ + if (marray == 0) { + size_t array_chunk_size; + array_chunk = chunk_plus_offset(p, contents_size); + array_chunk_size = remainder_size - contents_size; + marray = (void**) (chunk2mem(array_chunk)); + set_size_and_pinuse_of_inuse_chunk(m, array_chunk, array_chunk_size); + remainder_size = contents_size; + } + + /* split out elements */ + for (i = 0; ; ++i) { + marray[i] = chunk2mem(p); + if (i != n_elements-1) { + if (element_size != 0) + size = element_size; + else + size = request2size(sizes[i]); + remainder_size -= size; + set_size_and_pinuse_of_inuse_chunk(m, p, size); + p = chunk_plus_offset(p, size); + } + else { /* the final element absorbs any overallocation slop */ + set_size_and_pinuse_of_inuse_chunk(m, p, remainder_size); + break; + } + } + +#if DEBUG + if (marray != chunks) { + /* final element must have exactly exhausted chunk */ + if (element_size != 0) { + assert(remainder_size == element_size); + } + else { + assert(remainder_size == request2size(sizes[i])); + } + check_inuse_chunk(m, mem2chunk(marray)); + } + for (i = 0; i != n_elements; ++i) + check_inuse_chunk(m, mem2chunk(marray[i])); + +#endif /* DEBUG */ + + POSTACTION(m); + return marray; +} + + +/* -------------------------- public routines ---------------------------- */ + +#if !ONLY_MSPACES + +void* rdlmalloc(size_t bytes) { + /* + Basic algorithm: + If a small request (< 256 bytes minus per-chunk overhead): + 1. If one exists, use a remainderless chunk in associated smallbin. + (Remainderless means that there are too few excess bytes to + represent as a chunk.) + 2. If it is big enough, use the dv chunk, which is normally the + chunk adjacent to the one used for the most recent small request. + 3. If one exists, split the smallest available chunk in a bin, + saving remainder in dv. + 4. If it is big enough, use the top chunk. + 5. If available, get memory from system and use it + Otherwise, for a large request: + 1. Find the smallest available binned chunk that fits, and use it + if it is better fitting than dv chunk, splitting if necessary. + 2. If better fitting than any binned chunk, use the dv chunk. + 3. If it is big enough, use the top chunk. + 4. If request size >= mmap threshold, try to directly mmap this chunk. + 5. If available, get memory from system and use it + + The ugly goto's here ensure that postaction occurs along all paths. + */ + +#if USE_LOCKS + ensure_initialization(); /* initialize in sys_alloc if not using locks */ +#endif + + if (!PREACTION(gm)) { + void* mem; + size_t nb; + if (bytes <= MAX_SMALL_REQUEST) { + bindex_t idx; + binmap_t smallbits; + nb = (bytes < MIN_REQUEST)? MIN_CHUNK_SIZE : pad_request(bytes); + idx = small_index(nb); + smallbits = gm->smallmap >> idx; + + if ((smallbits & 0x3U) != 0) { /* Remainderless fit to a smallbin. */ + mchunkptr b, p; + idx += ~smallbits & 1; /* Uses next bin if idx empty */ + b = smallbin_at(gm, idx); + p = b->fd; + assert(chunksize(p) == small_index2size(idx)); + unlink_first_small_chunk(gm, b, p, idx); + set_inuse_and_pinuse(gm, p, small_index2size(idx)); + mem = chunk2mem(p); + check_malloced_chunk(gm, mem, nb); + goto postaction; + } + + else if (nb > gm->dvsize) { + if (smallbits != 0) { /* Use chunk in next nonempty smallbin */ + mchunkptr b, p, r; + size_t rsize; + bindex_t i; + binmap_t leftbits = (smallbits << idx) & left_bits(idx2bit(idx)); + binmap_t leastbit = least_bit(leftbits); + compute_bit2idx(leastbit, i); + b = smallbin_at(gm, i); + p = b->fd; + assert(chunksize(p) == small_index2size(i)); + unlink_first_small_chunk(gm, b, p, i); + rsize = small_index2size(i) - nb; + /* Fit here cannot be remainderless if 4byte sizes */ + if (SIZE_T_SIZE != 4 && rsize < MIN_CHUNK_SIZE) + set_inuse_and_pinuse(gm, p, small_index2size(i)); + else { + set_size_and_pinuse_of_inuse_chunk(gm, p, nb); + r = chunk_plus_offset(p, nb); + set_size_and_pinuse_of_free_chunk(r, rsize); + replace_dv(gm, r, rsize); + } + mem = chunk2mem(p); + check_malloced_chunk(gm, mem, nb); + goto postaction; + } + + else if (gm->treemap != 0 && (mem = tmalloc_small(gm, nb)) != 0) { + check_malloced_chunk(gm, mem, nb); + goto postaction; + } + } + } + else if (bytes >= MAX_REQUEST) + nb = MAX_SIZE_T; /* Too big to allocate. Force failure (in sys alloc) */ + else { + nb = pad_request(bytes); + if (gm->treemap != 0 && (mem = tmalloc_large(gm, nb)) != 0) { + check_malloced_chunk(gm, mem, nb); + goto postaction; + } + } + + if (nb <= gm->dvsize) { + size_t rsize = gm->dvsize - nb; + mchunkptr p = gm->dv; + if (rsize >= MIN_CHUNK_SIZE) { /* split dv */ + mchunkptr r = gm->dv = chunk_plus_offset(p, nb); + gm->dvsize = rsize; + set_size_and_pinuse_of_free_chunk(r, rsize); + set_size_and_pinuse_of_inuse_chunk(gm, p, nb); + } + else { /* exhaust dv */ + size_t dvs = gm->dvsize; + gm->dvsize = 0; + gm->dv = 0; + set_inuse_and_pinuse(gm, p, dvs); + } + mem = chunk2mem(p); + check_malloced_chunk(gm, mem, nb); + goto postaction; + } + + else if (nb < gm->topsize) { /* Split top */ + size_t rsize = gm->topsize -= nb; + mchunkptr p = gm->top; + mchunkptr r = gm->top = chunk_plus_offset(p, nb); + r->head = rsize | PINUSE_BIT; + set_size_and_pinuse_of_inuse_chunk(gm, p, nb); + mem = chunk2mem(p); + check_top_chunk(gm, gm->top); + check_malloced_chunk(gm, mem, nb); + goto postaction; + } + + mem = sys_alloc(gm, nb); + +postaction: + POSTACTION(gm); + return mem; + } + + return 0; +} + +void rdlfree(void* mem) { + /* + Consolidate freed chunks with preceeding or succeeding bordering + free chunks, if they exist, and then place in a bin. Intermixed + with special cases for top, dv, mmapped chunks, and usage errors. + */ + + if (mem != 0) { + mchunkptr p = mem2chunk(mem); +#if FOOTERS + mstate fm = get_mstate_for(p); + if (!ok_magic(fm)) { + USAGE_ERROR_ACTION(fm, p); + return; + } +#else /* FOOTERS */ +#define fm gm +#endif /* FOOTERS */ + if (!PREACTION(fm)) { + check_inuse_chunk(fm, p); + if (RTCHECK(ok_address(fm, p) && ok_inuse(p))) { + size_t psize = chunksize(p); + mchunkptr next = chunk_plus_offset(p, psize); + if (!pinuse(p)) { + size_t prevsize = p->prev_foot; + if (is_mmapped(p)) { + psize += prevsize + MMAP_FOOT_PAD; + if (CALL_MUNMAP((char*)p - prevsize, psize) == 0) + fm->footprint -= psize; + goto postaction; + } + else { + mchunkptr prev = chunk_minus_offset(p, prevsize); + psize += prevsize; + p = prev; + if (RTCHECK(ok_address(fm, prev))) { /* consolidate backward */ + if (p != fm->dv) { + unlink_chunk(fm, p, prevsize); + } + else if ((next->head & INUSE_BITS) == INUSE_BITS) { + fm->dvsize = psize; + set_free_with_pinuse(p, psize, next); + goto postaction; + } + } + else + goto erroraction; + } + } + + if (RTCHECK(ok_next(p, next) && ok_pinuse(next))) { + if (!cinuse(next)) { /* consolidate forward */ + if (next == fm->top) { + size_t tsize = fm->topsize += psize; + fm->top = p; + p->head = tsize | PINUSE_BIT; + if (p == fm->dv) { + fm->dv = 0; + fm->dvsize = 0; + } + if (should_trim(fm, tsize)) + sys_trim(fm, 0); + goto postaction; + } + else if (next == fm->dv) { + size_t dsize = fm->dvsize += psize; + fm->dv = p; + set_size_and_pinuse_of_free_chunk(p, dsize); + goto postaction; + } + else { + size_t nsize = chunksize(next); + psize += nsize; + unlink_chunk(fm, next, nsize); + set_size_and_pinuse_of_free_chunk(p, psize); + if (p == fm->dv) { + fm->dvsize = psize; + goto postaction; + } + } + } + else + set_free_with_pinuse(p, psize, next); + + if (is_small(psize)) { + insert_small_chunk(fm, p, psize); + check_free_chunk(fm, p); + } + else { + tchunkptr tp = (tchunkptr)p; + insert_large_chunk(fm, tp, psize); + check_free_chunk(fm, p); + if (--fm->release_checks == 0) + release_unused_segments(fm); + } + goto postaction; + } + } +erroraction: + USAGE_ERROR_ACTION(fm, p); +postaction: + POSTACTION(fm); + } + } +#if !FOOTERS +#undef fm +#endif /* FOOTERS */ +} + +void* rdlcalloc(size_t n_elements, size_t elem_size) { + void* mem; + size_t req = 0; + if (n_elements != 0) { + req = n_elements * elem_size; + if (((n_elements | elem_size) & ~(size_t)0xffff) && + (req / n_elements != elem_size)) + req = MAX_SIZE_T; /* force downstream failure on overflow */ + } + mem = rdlmalloc(req); + if (mem != 0 && calloc_must_clear(mem2chunk(mem))) + memset(mem, 0, req); + return mem; +} + +void* rdlrealloc(void* oldmem, size_t bytes) { + if (oldmem == 0) + return rdlmalloc(bytes); +#ifdef REALLOC_ZERO_BYTES_FREES + if (bytes == 0) { + rdlfree(oldmem); + return 0; + } +#endif /* REALLOC_ZERO_BYTES_FREES */ + else { +#if ! FOOTERS + mstate m = gm; +#else /* FOOTERS */ + mstate m = get_mstate_for(mem2chunk(oldmem)); + if (!ok_magic(m)) { + USAGE_ERROR_ACTION(m, oldmem); + return 0; + } +#endif /* FOOTERS */ + return internal_realloc(m, oldmem, bytes); + } +} + +void* rdlmemalign(size_t alignment, size_t bytes) { + return internal_memalign(gm, alignment, bytes); +} + +void** rdlindependent_calloc(size_t n_elements, size_t elem_size, + void* chunks[]) { + size_t sz = elem_size; /* serves as 1-element array */ + return ialloc(gm, n_elements, &sz, 3, chunks); +} + +void** rdlindependent_comalloc(size_t n_elements, size_t sizes[], + void* chunks[]) { + return ialloc(gm, n_elements, sizes, 0, chunks); +} + +void* rdlvalloc(size_t bytes) { + size_t pagesz; + ensure_initialization(); + pagesz = mparams.page_size; + return rdlmemalign(pagesz, bytes); +} + +void* rdlpvalloc(size_t bytes) { + size_t pagesz; + ensure_initialization(); + pagesz = mparams.page_size; + return rdlmemalign(pagesz, (bytes + pagesz - SIZE_T_ONE) & ~(pagesz - SIZE_T_ONE)); +} + +int rdlmalloc_trim(size_t pad) { + int result = 0; + ensure_initialization(); + if (!PREACTION(gm)) { + result = sys_trim(gm, pad); + POSTACTION(gm); + } + return result; +} + +size_t rdlmalloc_footprint(void) { + return gm->footprint; +} + +size_t dlmalloc_max_footprint(void) { + return gm->max_footprint; +} + +#if !NO_MALLINFO +struct mallinfo rdlmallinfo(void) { + return internal_mallinfo(gm); +} +#endif /* NO_MALLINFO */ + +void rdlmalloc_stats() { + internal_malloc_stats(gm); +} + +int rdlmallopt(int param_number, int value) { + return change_mparam(param_number, value); +} + +#endif /* !ONLY_MSPACES */ + +size_t rdlmalloc_usable_size(void* mem) { + if (mem != 0) { + mchunkptr p = mem2chunk(mem); + if (is_inuse(p)) + return chunksize(p) - overhead_for(p); + } + return 0; +} + +/* ----------------------------- user mspaces ---------------------------- */ + +#if MSPACES + +static mstate init_user_mstate(char* tbase, size_t tsize) { + size_t msize = pad_request(sizeof(struct malloc_state)); + mchunkptr mn; + mchunkptr msp = align_as_chunk(tbase); + mstate m = (mstate)(chunk2mem(msp)); + memset(m, 0, msize); + INITIAL_LOCK(&m->mutex); + msp->head = (msize|INUSE_BITS); + m->seg.base = m->least_addr = tbase; + m->seg.size = m->footprint = m->max_footprint = tsize; + m->magic = mparams.magic; + m->release_checks = MAX_RELEASE_CHECK_RATE; + m->mflags = mparams.default_mflags; + m->extp = 0; + m->exts = 0; + disable_contiguous(m); + init_bins(m); + mn = next_chunk(mem2chunk(m)); + init_top(m, mn, (size_t)((tbase + tsize) - (char*)mn) - TOP_FOOT_SIZE); + check_top_chunk(m, m->top); + return m; +} + +mspace rak_create_mspace(size_t capacity, int locked) { + mstate m = 0; + size_t msize; + ensure_initialization(); + msize = pad_request(sizeof(struct malloc_state)); + if (capacity < (size_t) -(msize + TOP_FOOT_SIZE + mparams.page_size)) { + size_t rs = ((capacity == 0)? mparams.granularity : + (capacity + TOP_FOOT_SIZE + msize)); + size_t tsize = granularity_align(rs); + char* tbase = (char*)(CALL_MMAP(tsize)); + if (tbase != CMFAIL) { + m = init_user_mstate(tbase, tsize); + m->seg.sflags = USE_MMAP_BIT; + set_lock(m, locked); + } + } + return (mspace)m; +} + +mspace rak_create_mspace_with_base(void* base, size_t capacity, int locked) { + mstate m = 0; + size_t msize; + ensure_initialization(); + msize = pad_request(sizeof(struct malloc_state)); + if (capacity > msize + TOP_FOOT_SIZE && + capacity < (size_t) -(msize + TOP_FOOT_SIZE + mparams.page_size)) { + m = init_user_mstate((char*)base, capacity); + m->seg.sflags = EXTERN_BIT; + set_lock(m, locked); + } + return (mspace)m; +} + +int rak_mspace_track_large_chunks(mspace msp, int enable) { + int ret = 0; + mstate ms = (mstate)msp; + if (!PREACTION(ms)) { + if (!use_mmap(ms)) + ret = 1; + if (!enable) + enable_mmap(ms); + else + disable_mmap(ms); + POSTACTION(ms); + } + return ret; +} + +size_t rak_destroy_mspace(mspace msp) { + size_t freed = 0; + mstate ms = (mstate)msp; + if (ok_magic(ms)) { + msegmentptr sp = &ms->seg; + while (sp != 0) { + char* base = sp->base; + size_t size = sp->size; + flag_t flag = sp->sflags; + sp = sp->next; + if ((flag & USE_MMAP_BIT) && !(flag & EXTERN_BIT) && + CALL_MUNMAP(base, size) == 0) + freed += size; + } + } + else { + USAGE_ERROR_ACTION(ms,ms); + } + return freed; +} + +/* +mspace versions of routines are near-clones of the global +versions. This is not so nice but better than the alternatives. +*/ + + +void* rak_mspace_malloc(mspace msp, size_t bytes) { + mstate ms = (mstate)msp; + if (!ok_magic(ms)) { + USAGE_ERROR_ACTION(ms,ms); + return 0; + } + if (!PREACTION(ms)) { + void* mem; + size_t nb; + if (bytes <= MAX_SMALL_REQUEST) { + bindex_t idx; + binmap_t smallbits; + nb = (bytes < MIN_REQUEST)? MIN_CHUNK_SIZE : pad_request(bytes); + idx = small_index(nb); + smallbits = ms->smallmap >> idx; + + if ((smallbits & 0x3U) != 0) { /* Remainderless fit to a smallbin. */ + mchunkptr b, p; + idx += ~smallbits & 1; /* Uses next bin if idx empty */ + b = smallbin_at(ms, idx); + p = b->fd; + assert(chunksize(p) == small_index2size(idx)); + unlink_first_small_chunk(ms, b, p, idx); + set_inuse_and_pinuse(ms, p, small_index2size(idx)); + mem = chunk2mem(p); + check_malloced_chunk(ms, mem, nb); + goto postaction; + } + + else if (nb > ms->dvsize) { + if (smallbits != 0) { /* Use chunk in next nonempty smallbin */ + mchunkptr b, p, r; + size_t rsize; + bindex_t i; + binmap_t leftbits = (smallbits << idx) & left_bits(idx2bit(idx)); + binmap_t leastbit = least_bit(leftbits); + compute_bit2idx(leastbit, i); + b = smallbin_at(ms, i); + p = b->fd; + assert(chunksize(p) == small_index2size(i)); + unlink_first_small_chunk(ms, b, p, i); + rsize = small_index2size(i) - nb; + /* Fit here cannot be remainderless if 4byte sizes */ + if (SIZE_T_SIZE != 4 && rsize < MIN_CHUNK_SIZE) + set_inuse_and_pinuse(ms, p, small_index2size(i)); + else { + set_size_and_pinuse_of_inuse_chunk(ms, p, nb); + r = chunk_plus_offset(p, nb); + set_size_and_pinuse_of_free_chunk(r, rsize); + replace_dv(ms, r, rsize); + } + mem = chunk2mem(p); + check_malloced_chunk(ms, mem, nb); + goto postaction; + } + + else if (ms->treemap != 0 && (mem = tmalloc_small(ms, nb)) != 0) { + check_malloced_chunk(ms, mem, nb); + goto postaction; + } + } + } + else if (bytes >= MAX_REQUEST) + nb = MAX_SIZE_T; /* Too big to allocate. Force failure (in sys alloc) */ + else { + nb = pad_request(bytes); + if (ms->treemap != 0 && (mem = tmalloc_large(ms, nb)) != 0) { + check_malloced_chunk(ms, mem, nb); + goto postaction; + } + } + + if (nb <= ms->dvsize) { + size_t rsize = ms->dvsize - nb; + mchunkptr p = ms->dv; + if (rsize >= MIN_CHUNK_SIZE) { /* split dv */ + mchunkptr r = ms->dv = chunk_plus_offset(p, nb); + ms->dvsize = rsize; + set_size_and_pinuse_of_free_chunk(r, rsize); + set_size_and_pinuse_of_inuse_chunk(ms, p, nb); + } + else { /* exhaust dv */ + size_t dvs = ms->dvsize; + ms->dvsize = 0; + ms->dv = 0; + set_inuse_and_pinuse(ms, p, dvs); + } + mem = chunk2mem(p); + check_malloced_chunk(ms, mem, nb); + goto postaction; + } + + else if (nb < ms->topsize) { /* Split top */ + size_t rsize = ms->topsize -= nb; + mchunkptr p = ms->top; + mchunkptr r = ms->top = chunk_plus_offset(p, nb); + r->head = rsize | PINUSE_BIT; + set_size_and_pinuse_of_inuse_chunk(ms, p, nb); + mem = chunk2mem(p); + check_top_chunk(ms, ms->top); + check_malloced_chunk(ms, mem, nb); + goto postaction; + } + + mem = sys_alloc(ms, nb); + +postaction: + POSTACTION(ms); + return mem; + } + + return 0; +} + +void rak_mspace_free(mspace msp, void* mem) { + if (mem != 0) { + mchunkptr p = mem2chunk(mem); +#if FOOTERS + mstate fm = get_mstate_for(p); + msp = msp; /* placate people compiling -Wunused */ +#else /* FOOTERS */ + mstate fm = (mstate)msp; +#endif /* FOOTERS */ + if (!ok_magic(fm)) { + USAGE_ERROR_ACTION(fm, p); + return; + } + if (!PREACTION(fm)) { + check_inuse_chunk(fm, p); + if (RTCHECK(ok_address(fm, p) && ok_inuse(p))) { + size_t psize = chunksize(p); + mchunkptr next = chunk_plus_offset(p, psize); + if (!pinuse(p)) { + size_t prevsize = p->prev_foot; + if (is_mmapped(p)) { + psize += prevsize + MMAP_FOOT_PAD; + if (CALL_MUNMAP((char*)p - prevsize, psize) == 0) + fm->footprint -= psize; + goto postaction; + } + else { + mchunkptr prev = chunk_minus_offset(p, prevsize); + psize += prevsize; + p = prev; + if (RTCHECK(ok_address(fm, prev))) { /* consolidate backward */ + if (p != fm->dv) { + unlink_chunk(fm, p, prevsize); + } + else if ((next->head & INUSE_BITS) == INUSE_BITS) { + fm->dvsize = psize; + set_free_with_pinuse(p, psize, next); + goto postaction; + } + } + else + goto erroraction; + } + } + + if (RTCHECK(ok_next(p, next) && ok_pinuse(next))) { + if (!cinuse(next)) { /* consolidate forward */ + if (next == fm->top) { + size_t tsize = fm->topsize += psize; + fm->top = p; + p->head = tsize | PINUSE_BIT; + if (p == fm->dv) { + fm->dv = 0; + fm->dvsize = 0; + } + if (should_trim(fm, tsize)) + sys_trim(fm, 0); + goto postaction; + } + else if (next == fm->dv) { + size_t dsize = fm->dvsize += psize; + fm->dv = p; + set_size_and_pinuse_of_free_chunk(p, dsize); + goto postaction; + } + else { + size_t nsize = chunksize(next); + psize += nsize; + unlink_chunk(fm, next, nsize); + set_size_and_pinuse_of_free_chunk(p, psize); + if (p == fm->dv) { + fm->dvsize = psize; + goto postaction; + } + } + } + else + set_free_with_pinuse(p, psize, next); + + if (is_small(psize)) { + insert_small_chunk(fm, p, psize); + check_free_chunk(fm, p); + } + else { + tchunkptr tp = (tchunkptr)p; + insert_large_chunk(fm, tp, psize); + check_free_chunk(fm, p); + if (--fm->release_checks == 0) + release_unused_segments(fm); + } + goto postaction; + } + } +erroraction: + USAGE_ERROR_ACTION(fm, p); +postaction: + POSTACTION(fm); + } + } +} + +void* rak_mspace_calloc(mspace msp, size_t n_elements, size_t elem_size) { + void* mem; + size_t req = 0; + mstate ms = (mstate)msp; + if (!ok_magic(ms)) { + USAGE_ERROR_ACTION(ms,ms); + return 0; + } + if (n_elements != 0) { + req = n_elements * elem_size; + if (((n_elements | elem_size) & ~(size_t)0xffff) && + (req / n_elements != elem_size)) + req = MAX_SIZE_T; /* force downstream failure on overflow */ + } + mem = internal_malloc(ms, req); + if (mem != 0 && calloc_must_clear(mem2chunk(mem))) + memset(mem, 0, req); + return mem; +} + +void* rak_mspace_realloc(mspace msp, void* oldmem, size_t bytes) { + if (oldmem == 0) + return rak_mspace_malloc(msp, bytes); +#ifdef REALLOC_ZERO_BYTES_FREES + if (bytes == 0) { + rak_mspace_free(msp, oldmem); + return 0; + } +#endif /* REALLOC_ZERO_BYTES_FREES */ + else { +#if FOOTERS + mchunkptr p = mem2chunk(oldmem); + mstate ms = get_mstate_for(p); +#else /* FOOTERS */ + mstate ms = (mstate)msp; +#endif /* FOOTERS */ + if (!ok_magic(ms)) { + USAGE_ERROR_ACTION(ms,ms); + return 0; + } + return internal_realloc(ms, oldmem, bytes); + } +} + +void* rak_mspace_memalign(mspace msp, size_t alignment, size_t bytes) { + mstate ms = (mstate)msp; + if (!ok_magic(ms)) { + USAGE_ERROR_ACTION(ms,ms); + return 0; + } + return internal_memalign(ms, alignment, bytes); +} + +void** rak_mspace_independent_calloc(mspace msp, size_t n_elements, + size_t elem_size, void* chunks[]) { + size_t sz = elem_size; /* serves as 1-element array */ + mstate ms = (mstate)msp; + if (!ok_magic(ms)) { + USAGE_ERROR_ACTION(ms,ms); + return 0; + } + return ialloc(ms, n_elements, &sz, 3, chunks); +} + +void** rak_mspace_independent_comalloc(mspace msp, size_t n_elements, + size_t sizes[], void* chunks[]) { + mstate ms = (mstate)msp; + if (!ok_magic(ms)) { + USAGE_ERROR_ACTION(ms,ms); + return 0; + } + return ialloc(ms, n_elements, sizes, 0, chunks); +} + +int rak_mspace_trim(mspace msp, size_t pad) { + int result = 0; + mstate ms = (mstate)msp; + if (ok_magic(ms)) { + if (!PREACTION(ms)) { + result = sys_trim(ms, pad); + POSTACTION(ms); + } + } + else { + USAGE_ERROR_ACTION(ms,ms); + } + return result; +} + +void rak_mspace_malloc_stats(mspace msp) { + mstate ms = (mstate)msp; + if (ok_magic(ms)) { + internal_malloc_stats(ms); + } + else { + USAGE_ERROR_ACTION(ms,ms); + } +} + +size_t rak_mspace_footprint(mspace msp) { + size_t result = 0; + mstate ms = (mstate)msp; + if (ok_magic(ms)) { + result = ms->footprint; + } + else { + USAGE_ERROR_ACTION(ms,ms); + } + return result; +} + + +size_t mspace_max_footprint(mspace msp) { + size_t result = 0; + mstate ms = (mstate)msp; + if (ok_magic(ms)) { + result = ms->max_footprint; + } + else { + USAGE_ERROR_ACTION(ms,ms); + } + return result; +} + + +#if !NO_MALLINFO +struct mallinfo rak_mspace_mallinfo(mspace msp) { + mstate ms = (mstate)msp; + if (!ok_magic(ms)) { + USAGE_ERROR_ACTION(ms,ms); + } + return internal_mallinfo(ms); +} +#endif /* NO_MALLINFO */ + +size_t rak_mspace_usable_size(void* mem) { + if (mem != 0) { + mchunkptr p = mem2chunk(mem); + if (is_inuse(p)) + return chunksize(p) - overhead_for(p); + } + return 0; +} + +int rak_mspace_mallopt(int param_number, int value) { + return change_mparam(param_number, value); +} + +#endif /* MSPACES */ + + +/* -------------------- Alternative MORECORE functions ------------------- */ + +/* +Guidelines for creating a custom version of MORECORE: + +* For best performance, MORECORE should allocate in multiples of pagesize. +* MORECORE may allocate more memory than requested. (Or even less, +but this will usually result in a malloc failure.) +* MORECORE must not allocate memory when given argument zero, but +instead return one past the end address of memory from previous +nonzero call. +* For best performance, consecutive calls to MORECORE with positive +arguments should return increasing addresses, indicating that +space has been contiguously extended. +* Even though consecutive calls to MORECORE need not return contiguous +addresses, it must be OK for malloc'ed chunks to span multiple +regions in those cases where they do happen to be contiguous. +* MORECORE need not handle negative arguments -- it may instead +just return MFAIL when given negative arguments. +Negative arguments are always multiples of pagesize. MORECORE +must not misinterpret negative args as large positive unsigned +args. You can suppress all such calls from even occurring by defining +MORECORE_CANNOT_TRIM, + +As an example alternative MORECORE, here is a custom allocator +kindly contributed for pre-OSX macOS. It uses virtually but not +necessarily physically contiguous non-paged memory (locked in, +present and won't get swapped out). You can use it by uncommenting +this section, adding some #includes, and setting up the appropriate +defines above: + +#define MORECORE osMoreCore + +There is also a shutdown routine that should somehow be called for +cleanup upon program exit. + +#define MAX_POOL_ENTRIES 100 +#define MINIMUM_MORECORE_SIZE (64 * 1024U) +static int next_os_pool; +void *our_os_pools[MAX_POOL_ENTRIES]; + +void *osMoreCore(int size) +{ +void *ptr = 0; +static void *sbrk_top = 0; + +if (size > 0) +{ +if (size < MINIMUM_MORECORE_SIZE) +size = MINIMUM_MORECORE_SIZE; +if (CurrentExecutionLevel() == kTaskLevel) +ptr = PoolAllocateResident(size + RM_PAGE_SIZE, 0); +if (ptr == 0) +{ +return (void *) MFAIL; +} +// save ptrs so they can be freed during cleanup +our_os_pools[next_os_pool] = ptr; +next_os_pool++; +ptr = (void *) ((((size_t) ptr) + RM_PAGE_MASK) & ~RM_PAGE_MASK); +sbrk_top = (char *) ptr + size; +return ptr; +} +else if (size < 0) +{ +// we don't currently support shrink behavior +return (void *) MFAIL; +} +else +{ +return sbrk_top; +} +} + +// cleanup any allocated memory pools +// called as last thing before shutting down driver + +void osCleanupMem(void) +{ +void **ptr; + +for (ptr = our_os_pools; ptr < &our_os_pools[MAX_POOL_ENTRIES]; ptr++) +if (*ptr) +{ +PoolDeallocate(*ptr); +*ptr = 0; +} +} + +*/ + + +/* ----------------------------------------------------------------------- +History: +V2.8.4 Wed May 27 09:56:23 2009 Doug Lea (dl at gee) +* Use zeros instead of prev foot for is_mmapped +* Add rak_mspace_track_large_chunks; thanks to Jean Brouwers +* Fix set_inuse in internal_realloc; thanks to Jean Brouwers +* Fix insufficient sys_alloc padding when using 16byte alignment +* Fix bad error check in rak_mspace_footprint +* Adaptations for ptmalloc; thanks to Wolfram Gloger. +* Reentrant spin locks; thanks to Earl Chew and others +* Win32 improvements; thanks to Niall Douglas and Earl Chew +* Add NO_SEGMENT_TRAVERSAL and MAX_RELEASE_CHECK_RATE options +* Extension hook in malloc_state +* Various small adjustments to reduce warnings on some compilers +* Various configuration extensions/changes for more platforms. Thanks +to all who contributed these. + +V2.8.3 Thu Sep 22 11:16:32 2005 Doug Lea (dl at gee) +* Add max_footprint functions +* Ensure all appropriate literals are size_t +* Fix conditional compilation problem for some #define settings +* Avoid concatenating segments with the one provided +in rak_create_mspace_with_base +* Rename some variables to avoid compiler shadowing warnings +* Use explicit lock initialization. +* Better handling of sbrk interference. +* Simplify and fix segment insertion, trimming and mspace_destroy +* Reinstate REALLOC_ZERO_BYTES_FREES option from 2.7.x +* Thanks especially to Dennis Flanagan for help on these. + +V2.8.2 Sun Jun 12 16:01:10 2005 Doug Lea (dl at gee) +* Fix memalign brace error. + +V2.8.1 Wed Jun 8 16:11:46 2005 Doug Lea (dl at gee) +* Fix improper #endif nesting in C++ +* Add explicit casts needed for C++ + +V2.8.0 Mon May 30 14:09:02 2005 Doug Lea (dl at gee) +* Use trees for large bins +* Support mspaces +* Use segments to unify sbrk-based and mmap-based system allocation, +removing need for emulation on most platforms without sbrk. +* Default safety checks +* Optional footer checks. Thanks to William Robertson for the idea. +* Internal code refactoring +* Incorporate suggestions and platform-specific changes. +Thanks to Dennis Flanagan, Colin Plumb, Niall Douglas, +Aaron Bachmann, Emery Berger, and others. +* Speed up non-fastbin processing enough to remove fastbins. +* Remove useless cfree() to avoid conflicts with other apps. +* Remove internal memcpy, memset. Compilers handle builtins better. +* Remove some options that no one ever used and rename others. + +V2.7.2 Sat Aug 17 09:07:30 2002 Doug Lea (dl at gee) +* Fix malloc_state bitmap array misdeclaration + +V2.7.1 Thu Jul 25 10:58:03 2002 Doug Lea (dl at gee) +* Allow tuning of FIRST_SORTED_BIN_SIZE +* Use PTR_UINT as type for all ptr->int casts. Thanks to John Belmonte. +* Better detection and support for non-contiguousness of MORECORE. +Thanks to Andreas Mueller, Conal Walsh, and Wolfram Gloger +* Bypass most of malloc if no frees. Thanks To Emery Berger. +* Fix freeing of old top non-contiguous chunk im sysmalloc. +* Raised default trim and map thresholds to 256K. +* Fix mmap-related #defines. Thanks to Lubos Lunak. +* Fix copy macros; added LACKS_FCNTL_H. Thanks to Neal Walfield. +* Branch-free bin calculation +* Default trim and mmap thresholds now 256K. + +V2.7.0 Sun Mar 11 14:14:06 2001 Doug Lea (dl at gee) +* Introduce independent_comalloc and independent_calloc. +Thanks to Michael Pachos for motivation and help. +* Make optional .h file available +* Allow > 2GB requests on 32bit systems. +* new DL_PLATFORM_WIN32 sbrk, mmap, munmap, lock code from . +Thanks also to Andreas Mueller , +and Anonymous. +* Allow override of MALLOC_ALIGNMENT (Thanks to Ruud Waij for +helping test this.) +* memalign: check alignment arg +* realloc: don't try to shift chunks backwards, since this +leads to more fragmentation in some programs and doesn't +seem to help in any others. +* Collect all cases in malloc requiring system memory into sysmalloc +* Use mmap as backup to sbrk +* Place all internal state in malloc_state +* Introduce fastbins (although similar to 2.5.1) +* Many minor tunings and cosmetic improvements +* Introduce USE_PUBLIC_MALLOC_WRAPPERS, USE_MALLOC_LOCK +* Introduce MALLOC_FAILURE_ACTION, MORECORE_CONTIGUOUS +Thanks to Tony E. Bennett and others. +* Include errno.h to support default failure action. + +V2.6.6 Sun Dec 5 07:42:19 1999 Doug Lea (dl at gee) +* return null for negative arguments +* Added Several DL_PLATFORM_WIN32 cleanups from Martin C. Fong +* Add 'LACKS_SYS_PARAM_H' for those systems without 'sys/param.h' +(e.g. DL_PLATFORM_WIN32 platforms) +* Cleanup header file inclusion for DL_PLATFORM_WIN32 platforms +* Cleanup code to avoid Microsoft Visual C++ compiler complaints +* Add 'USE_DL_PREFIX' to quickly allow co-existence with existing +memory allocation routines +* Set 'malloc_getpagesize' for DL_PLATFORM_WIN32 platforms (needs more work) +* Use 'assert' rather than 'ASSERT' in DL_PLATFORM_WIN32 code to conform to +usage of 'assert' in non-DL_PLATFORM_WIN32 code +* Improve DL_PLATFORM_WIN32 'sbrk()' emulation's 'findRegion()' routine to +avoid infinite loop +* Always call 'fREe()' rather than 'free()' + +V2.6.5 Wed Jun 17 15:57:31 1998 Doug Lea (dl at gee) +* Fixed ordering problem with boundary-stamping + +V2.6.3 Sun May 19 08:17:58 1996 Doug Lea (dl at gee) +* Added pvalloc, as recommended by H.J. Liu +* Added 64bit pointer support mainly from Wolfram Gloger +* Added anonymously donated DL_PLATFORM_WIN32 sbrk emulation +* Malloc, calloc, getpagesize: add optimizations from Raymond Nijssen +* malloc_extend_top: fix mask error that caused wastage after +foreign sbrks +* Add linux mremap support code from HJ Liu + +V2.6.2 Tue Dec 5 06:52:55 1995 Doug Lea (dl at gee) +* Integrated most documentation with the code. +* Add support for mmap, with help from +Wolfram Gloger (Gloger@lrz.uni-muenchen.de). +* Use last_remainder in more cases. +* Pack bins using idea from colin@nyx10.cs.du.edu +* Use ordered bins instead of best-fit threshhold +* Eliminate block-local decls to simplify tracing and debugging. +* Support another case of realloc via move into top +* Fix error occuring when initial sbrk_base not word-aligned. +* Rely on page size for units instead of SBRK_UNIT to +avoid surprises about sbrk alignment conventions. +* Add mallinfo, mallopt. Thanks to Raymond Nijssen +(raymond@es.ele.tue.nl) for the suggestion. +* Add `pad' argument to malloc_trim and top_pad mallopt parameter. +* More precautions for cases where other routines call sbrk, +courtesy of Wolfram Gloger (Gloger@lrz.uni-muenchen.de). +* Added macros etc., allowing use in linux libc from +H.J. Lu (hjl@gnu.ai.mit.edu) +* Inverted this history list + +V2.6.1 Sat Dec 2 14:10:57 1995 Doug Lea (dl at gee) +* Re-tuned and fixed to behave more nicely with V2.6.0 changes. +* Removed all preallocation code since under current scheme +the work required to undo bad preallocations exceeds +the work saved in good cases for most test programs. +* No longer use return list or unconsolidated bins since +no scheme using them consistently outperforms those that don't +given above changes. +* Use best fit for very large chunks to prevent some worst-cases. +* Added some support for debugging + +V2.6.0 Sat Nov 4 07:05:23 1995 Doug Lea (dl at gee) +* Removed footers when chunks are in use. Thanks to +Paul Wilson (wilson@cs.texas.edu) for the suggestion. + +V2.5.4 Wed Nov 1 07:54:51 1995 Doug Lea (dl at gee) +* Added malloc_trim, with help from Wolfram Gloger +(wmglo@Dent.MED.Uni-Muenchen.DE). + +V2.5.3 Tue Apr 26 10:16:01 1994 Doug Lea (dl at g) + +V2.5.2 Tue Apr 5 16:20:40 1994 Doug Lea (dl at g) +* realloc: try to expand in both directions +* malloc: swap order of clean-bin strategy; +* realloc: only conditionally expand backwards +* Try not to scavenge used bins +* Use bin counts as a guide to preallocation +* Occasionally bin return list chunks in first scan +* Add a few optimizations from colin@nyx10.cs.du.edu + +V2.5.1 Sat Aug 14 15:40:43 1993 Doug Lea (dl at g) +* faster bin computation & slightly different binning +* merged all consolidations to one part of malloc proper +(eliminating old malloc_find_space & malloc_clean_bin) +* Scan 2 returns chunks (not just 1) +* Propagate failure in realloc if malloc returns 0 +* Add stuff to allow compilation on non-ANSI compilers +from kpv@research.att.com + +V2.5 Sat Aug 7 07:41:59 1993 Doug Lea (dl at g.oswego.edu) +* removed potential for odd address access in prev_chunk +* removed dependency on getpagesize.h +* misc cosmetics and a bit more internal documentation +* anticosmetics: mangled names in macros to evade debugger strangeness +* tested on sparc, hp-700, dec-mips, rs6000 +with gcc & native cc (hp, dec only) allowing +Detlefs & Zorn comparison study (in SIGPLAN Notices.) + +Trial version Fri Aug 28 13:14:29 1992 Doug Lea (dl at g.oswego.edu) +* Based loosely on libg++-1.2X malloc. (It retains some of the overall +structure of old version, but most details differ.) + +*/ + +#endif // _RAKNET_SUPPORT_DL_MALLOC diff --git a/RakNet/Sources/rdlmalloc.h b/RakNet/Sources/rdlmalloc.h new file mode 100644 index 0000000..e2c990e --- /dev/null +++ b/RakNet/Sources/rdlmalloc.h @@ -0,0 +1,2206 @@ +#ifdef _RAKNET_SUPPORT_DL_MALLOC + +/* +Default header file for malloc-2.8.x, written by Doug Lea +and released to the public domain, as explained at +http://creativecommons.org/licenses/publicdomain. + +last update: Wed May 27 14:25:17 2009 Doug Lea (dl at gee) + +This header is for ANSI C/C++ only. You can set any of +the following #defines before including: + +* If USE_DL_PREFIX is defined, it is assumed that malloc.c +was also compiled with this option, so all routines +have names starting with "dl". + +* If HAVE_USR_INCLUDE_MALLOC_H is defined, it is assumed that this +file will be #included AFTER . This is needed only if +your system defines a struct mallinfo that is incompatible with the +standard one declared here. Otherwise, you can include this file +INSTEAD of your system system . At least on ANSI, all +declarations should be compatible with system versions + +* If MSPACES is defined, declarations for mspace versions are included. +*/ + +#ifndef MALLOC_280_H +#define MALLOC_280_H + +#include "rdlmalloc-options.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#include /* for size_t */ + +#ifndef ONLY_MSPACES +#define ONLY_MSPACES 0 /* define to a value */ +#endif /* ONLY_MSPACES */ +#ifndef NO_MALLINFO +#define NO_MALLINFO 0 +#endif /* NO_MALLINFO */ + + +#if !ONLY_MSPACES + +#ifndef USE_DL_PREFIX +#define rdlcalloc calloc +#define rdlfree free +#define rdlmalloc malloc +#define rdlmemalign memalign +#define rdlrealloc realloc +#define rdlvalloc valloc +#define rdlpvalloc pvalloc +#define rdlmallinfo mallinfo +#define rdlmallopt mallopt +#define rdlmalloc_trim malloc_trim +#define rdlmalloc_stats malloc_stats +#define rdlmalloc_usable_size malloc_usable_size +#define rdlmalloc_footprint malloc_footprint +#define rdlindependent_calloc independent_calloc +#define rdlindependent_comalloc independent_comalloc +#endif /* USE_DL_PREFIX */ +#if !NO_MALLINFO +#ifndef HAVE_USR_INCLUDE_MALLOC_H +#ifndef _MALLOC_H +#ifndef MALLINFO_FIELD_TYPE +#define MALLINFO_FIELD_TYPE size_t +#endif /* MALLINFO_FIELD_TYPE */ +#ifndef STRUCT_MALLINFO_DECLARED +#define STRUCT_MALLINFO_DECLARED 1 + struct mallinfo { + MALLINFO_FIELD_TYPE arena; /* non-mmapped space allocated from system */ + MALLINFO_FIELD_TYPE ordblks; /* number of free chunks */ + MALLINFO_FIELD_TYPE smblks; /* always 0 */ + MALLINFO_FIELD_TYPE hblks; /* always 0 */ + MALLINFO_FIELD_TYPE hblkhd; /* space in mmapped regions */ + MALLINFO_FIELD_TYPE usmblks; /* maximum total allocated space */ + MALLINFO_FIELD_TYPE fsmblks; /* always 0 */ + MALLINFO_FIELD_TYPE uordblks; /* total allocated space */ + MALLINFO_FIELD_TYPE fordblks; /* total free space */ + MALLINFO_FIELD_TYPE keepcost; /* releasable (via malloc_trim) space */ + }; +#endif /* STRUCT_MALLINFO_DECLARED */ +#endif /* _MALLOC_H */ +#endif /* HAVE_USR_INCLUDE_MALLOC_H */ +#endif /* !NO_MALLINFO */ + + /* + malloc(size_t n) + Returns a pointer to a newly allocated chunk of at least n bytes, or + null if no space is available, in which case errno is set to ENOMEM + on ANSI C systems. + + If n is zero, malloc returns a minimum-sized chunk. (The minimum + size is 16 bytes on most 32bit systems, and 32 bytes on 64bit + systems.) Note that size_t is an unsigned type, so calls with + arguments that would be negative if signed are interpreted as + requests for huge amounts of space, which will often fail. The + maximum supported value of n differs across systems, but is in all + cases less than the maximum representable value of a size_t. + */ + void* rdlmalloc(size_t); + + /* + free(void* p) + Releases the chunk of memory pointed to by p, that had been previously + allocated using malloc or a related routine such as realloc. + It has no effect if p is null. If p was not malloced or already + freed, free(p) will by default cuase the current program to abort. + */ + void rdlfree(void*); + + /* + calloc(size_t n_elements, size_t element_size); + Returns a pointer to n_elements * element_size bytes, with all locations + set to zero. + */ + void* rdlcalloc(size_t, size_t); + + /* + realloc(void* p, size_t n) + Returns a pointer to a chunk of size n that contains the same data + as does chunk p up to the minimum of (n, p's size) bytes, or null + if no space is available. + + The returned pointer may or may not be the same as p. The algorithm + prefers extending p in most cases when possible, otherwise it + employs the equivalent of a malloc-copy-free sequence. + + If p is null, realloc is equivalent to malloc. + + If space is not available, realloc returns null, errno is set (if on + ANSI) and p is NOT freed. + + if n is for fewer bytes than already held by p, the newly unused + space is lopped off and freed if possible. realloc with a size + argument of zero (re)allocates a minimum-sized chunk. + + The old unix realloc convention of allowing the last-free'd chunk + to be used as an argument to realloc is not supported. + */ + + void* rdlrealloc(void*, size_t); + + /* + memalign(size_t alignment, size_t n); + Returns a pointer to a newly allocated chunk of n bytes, aligned + in accord with the alignment argument. + + The alignment argument should be a power of two. If the argument is + not a power of two, the nearest greater power is used. + 8-byte alignment is guaranteed by normal malloc calls, so don't + bother calling memalign with an argument of 8 or less. + + Overreliance on memalign is a sure way to fragment space. + */ + void* rdlmemalign(size_t, size_t); + + /* + valloc(size_t n); + Equivalent to memalign(pagesize, n), where pagesize is the page + size of the system. If the pagesize is unknown, 4096 is used. + */ + void* rdlvalloc(size_t); + + /* + mallopt(int parameter_number, int parameter_value) + Sets tunable parameters The format is to provide a + (parameter-number, parameter-value) pair. mallopt then sets the + corresponding parameter to the argument value if it can (i.e., so + long as the value is meaningful), and returns 1 if successful else + 0. SVID/XPG/ANSI defines four standard param numbers for mallopt, + normally defined in malloc.h. None of these are use in this malloc, + so setting them has no effect. But this malloc also supports other + options in mallopt: + + Symbol param # default allowed param values + M_TRIM_THRESHOLD -1 2*1024*1024 any (-1U disables trimming) + M_GRANULARITY -2 page size any power of 2 >= page size + M_MMAP_THRESHOLD -3 256*1024 any (or 0 if no MMAP support) + */ + int rdlmallopt(int, int); + +#define M_TRIM_THRESHOLD (-1) +#define M_GRANULARITY (-2) +#define M_MMAP_THRESHOLD (-3) + + + /* + malloc_footprint(); + Returns the number of bytes obtained from the system. The total + number of bytes allocated by malloc, realloc etc., is less than this + value. Unlike mallinfo, this function returns only a precomputed + result, so can be called frequently to monitor memory consumption. + Even if locks are otherwise defined, this function does not use them, + so results might not be up to date. + */ + size_t rdlmalloc_footprint(); + +#if !NO_MALLINFO + /* + mallinfo() + Returns (by copy) a struct containing various summary statistics: + + arena: current total non-mmapped bytes allocated from system + ordblks: the number of free chunks + smblks: always zero. + hblks: current number of mmapped regions + hblkhd: total bytes held in mmapped regions + usmblks: the maximum total allocated space. This will be greater + than current total if trimming has occurred. + fsmblks: always zero + uordblks: current total allocated space (normal or mmapped) + fordblks: total free space + keepcost: the maximum number of bytes that could ideally be released + back to system via malloc_trim. ("ideally" means that + it ignores page restrictions etc.) + + Because these fields are ints, but internal bookkeeping may + be kept as longs, the reported values may wrap around zero and + thus be inaccurate. + */ + + struct mallinfo rdlmallinfo(void); +#endif /* NO_MALLINFO */ + + /* + independent_calloc(size_t n_elements, size_t element_size, void* chunks[]); + + independent_calloc is similar to calloc, but instead of returning a + single cleared space, it returns an array of pointers to n_elements + independent elements that can hold contents of size elem_size, each + of which starts out cleared, and can be independently freed, + realloc'ed etc. The elements are guaranteed to be adjacently + allocated (this is not guaranteed to occur with multiple callocs or + mallocs), which may also improve cache locality in some + applications. + + The "chunks" argument is optional (i.e., may be null, which is + probably the most typical usage). If it is null, the returned array + is itself dynamically allocated and should also be freed when it is + no longer needed. Otherwise, the chunks array must be of at least + n_elements in length. It is filled in with the pointers to the + chunks. + + In either case, independent_calloc returns this pointer array, or + null if the allocation failed. If n_elements is zero and "chunks" + is null, it returns a chunk representing an array with zero elements + (which should be freed if not wanted). + + Each element must be individually freed when it is no longer + needed. If you'd like to instead be able to free all at once, you + should instead use regular calloc and assign pointers into this + space to represent elements. (In this case though, you cannot + independently free elements.) + + independent_calloc simplifies and speeds up implementations of many + kinds of pools. It may also be useful when constructing large data + structures that initially have a fixed number of fixed-sized nodes, + but the number is not known at compile time, and some of the nodes + may later need to be freed. For example: + + struct Node { int item; struct Node* next; }; + + struct Node* build_list() { + struct Node** pool; + int n = read_number_of_nodes_needed(); + if (n <= 0) return 0; + pool = (struct Node**)(independent_calloc(n, sizeof(struct Node), 0); + if (pool == 0) die(); + // organize into a linked list... + struct Node* first = pool[0]; + for (i = 0; i < n-1; ++i) + pool[i]->next = pool[i+1]; + free(pool); // Can now free the array (or not, if it is needed later) + return first; + } + */ + void** rdlindependent_calloc(size_t, size_t, void**); + + /* + independent_comalloc(size_t n_elements, size_t sizes[], void* chunks[]); + + independent_comalloc allocates, all at once, a set of n_elements + chunks with sizes indicated in the "sizes" array. It returns + an array of pointers to these elements, each of which can be + independently freed, realloc'ed etc. The elements are guaranteed to + be adjacently allocated (this is not guaranteed to occur with + multiple callocs or mallocs), which may also improve cache locality + in some applications. + + The "chunks" argument is optional (i.e., may be null). If it is null + the returned array is itself dynamically allocated and should also + be freed when it is no longer needed. Otherwise, the chunks array + must be of at least n_elements in length. It is filled in with the + pointers to the chunks. + + In either case, independent_comalloc returns this pointer array, or + null if the allocation failed. If n_elements is zero and chunks is + null, it returns a chunk representing an array with zero elements + (which should be freed if not wanted). + + Each element must be individually freed when it is no longer + needed. If you'd like to instead be able to free all at once, you + should instead use a single regular malloc, and assign pointers at + particular offsets in the aggregate space. (In this case though, you + cannot independently free elements.) + + independent_comallac differs from independent_calloc in that each + element may have a different size, and also that it does not + automatically clear elements. + + independent_comalloc can be used to speed up allocation in cases + where several structs or objects must always be allocated at the + same time. For example: + + struct Head { ... } + struct Foot { ... } + + void send_message(char* msg) { + int msglen = strlen(msg); + size_t sizes[3] = { sizeof(struct Head), msglen, sizeof(struct Foot) }; + void* chunks[3]; + if (independent_comalloc(3, sizes, chunks) == 0) + die(); + struct Head* head = (struct Head*)(chunks[0]); + char* body = (char*)(chunks[1]); + struct Foot* foot = (struct Foot*)(chunks[2]); + // ... + } + + In general though, independent_comalloc is worth using only for + larger values of n_elements. For small values, you probably won't + detect enough difference from series of malloc calls to bother. + + Overuse of independent_comalloc can increase overall memory usage, + since it cannot reuse existing noncontiguous small chunks that + might be available for some of the elements. + */ + void** rdlindependent_comalloc(size_t, size_t*, void**); + + + /* + pvalloc(size_t n); + Equivalent to valloc(minimum-page-that-holds(n)), that is, + round up n to nearest pagesize. + */ + void* rdlpvalloc(size_t); + + /* + malloc_trim(size_t pad); + + If possible, gives memory back to the system (via negative arguments + to sbrk) if there is unused memory at the `high' end of the malloc + pool or in unused MMAP segments. You can call this after freeing + large blocks of memory to potentially reduce the system-level memory + requirements of a program. However, it cannot guarantee to reduce + memory. Under some allocation patterns, some large free blocks of + memory will be locked between two used chunks, so they cannot be + given back to the system. + + The `pad' argument to malloc_trim represents the amount of free + trailing space to leave untrimmed. If this argument is zero, only + the minimum amount of memory to maintain internal data structures + will be left. Non-zero arguments can be supplied to maintain enough + trailing space to service future expected allocations without having + to re-obtain memory from the system. + + Malloc_trim returns 1 if it actually released any memory, else 0. + */ + int rdlmalloc_trim(size_t); + + /* + malloc_stats(); + Prints on stderr the amount of space obtained from the system (both + via sbrk and mmap), the maximum amount (which may be more than + current if malloc_trim and/or munmap got called), and the current + number of bytes allocated via malloc (or realloc, etc) but not yet + freed. Note that this is the number of bytes allocated, not the + number requested. It will be larger than the number requested + because of alignment and bookkeeping overhead. Because it includes + alignment wastage as being in use, this figure may be greater than + zero even when no user-level chunks are allocated. + + The reported current and maximum system memory can be inaccurate if + a program makes other calls to system memory allocation functions + (normally sbrk) outside of malloc. + + malloc_stats prints only the most commonly interesting statistics. + More information can be obtained by calling mallinfo. + */ + void rdlmalloc_stats(); + +#endif /* !ONLY_MSPACES */ + + /* + malloc_usable_size(void* p); + + Returns the number of bytes you can actually use in + an allocated chunk, which may be more than you requested (although + often not) due to alignment and minimum size constraints. + You can use this many bytes without worrying about + overwriting other allocated objects. This is not a particularly great + programming practice. malloc_usable_size can be more useful in + debugging and assertions, for example: + + p = malloc(n); + assert(malloc_usable_size(p) >= 256); + */ + size_t rdlmalloc_usable_size(void*); + + +#if MSPACES + + /* + mspace is an opaque type representing an independent + region of space that supports rak_mspace_malloc, etc. + */ + typedef void* mspace; + + /* + rak_create_mspace creates and returns a new independent space with the + given initial capacity, or, if 0, the default granularity size. It + returns null if there is no system memory available to create the + space. If argument locked is non-zero, the space uses a separate + lock to control access. The capacity of the space will grow + dynamically as needed to service rak_mspace_malloc requests. You can + control the sizes of incremental increases of this space by + compiling with a different DEFAULT_GRANULARITY or dynamically + setting with mallopt(M_GRANULARITY, value). + */ + mspace rak_create_mspace(size_t capacity, int locked); + + /* + rak_destroy_mspace destroys the given space, and attempts to return all + of its memory back to the system, returning the total number of + bytes freed. After destruction, the results of access to all memory + used by the space become undefined. + */ + size_t rak_destroy_mspace(mspace msp); + + /* + rak_create_mspace_with_base uses the memory supplied as the initial base + of a new mspace. Part (less than 128*sizeof(size_t) bytes) of this + space is used for bookkeeping, so the capacity must be at least this + large. (Otherwise 0 is returned.) When this initial space is + exhausted, additional memory will be obtained from the system. + Destroying this space will deallocate all additionally allocated + space (if possible) but not the initial base. + */ + mspace rak_create_mspace_with_base(void* base, size_t capacity, int locked); + + /* + rak_mspace_track_large_chunks controls whether requests for large chunks + are allocated in their own untracked mmapped regions, separate from + others in this mspace. By default large chunks are not tracked, + which reduces fragmentation. However, such chunks are not + necessarily released to the system upon rak_destroy_mspace. Enabling + tracking by setting to true may increase fragmentation, but avoids + leakage when relying on rak_destroy_mspace to release all memory + allocated using this space. The function returns the previous + setting. + */ + int rak_mspace_track_large_chunks(mspace msp, int enable); + + /* + rak_mspace_malloc behaves as malloc, but operates within + the given space. + */ + void* rak_mspace_malloc(mspace msp, size_t bytes); + + /* + rak_mspace_free behaves as free, but operates within + the given space. + + If compiled with FOOTERS==1, rak_mspace_free is not actually needed. + free may be called instead of rak_mspace_free because freed chunks from + any space are handled by their originating spaces. + */ + void rak_mspace_free(mspace msp, void* mem); + + /* + rak_mspace_realloc behaves as realloc, but operates within + the given space. + + If compiled with FOOTERS==1, rak_mspace_realloc is not actually + needed. realloc may be called instead of rak_mspace_realloc because + realloced chunks from any space are handled by their originating + spaces. + */ + void* rak_mspace_realloc(mspace msp, void* mem, size_t newsize); + + /* + rak_mspace_calloc behaves as calloc, but operates within + the given space. + */ + void* rak_mspace_calloc(mspace msp, size_t n_elements, size_t elem_size); + + /* + rak_mspace_memalign behaves as memalign, but operates within + the given space. + */ + void* rak_mspace_memalign(mspace msp, size_t alignment, size_t bytes); + + /* + rak_mspace_independent_calloc behaves as independent_calloc, but + operates within the given space. + */ + void** rak_mspace_independent_calloc(mspace msp, size_t n_elements, + size_t elem_size, void* chunks[]); + + /* + rak_mspace_independent_comalloc behaves as independent_comalloc, but + operates within the given space. + */ + void** rak_mspace_independent_comalloc(mspace msp, size_t n_elements, + size_t sizes[], void* chunks[]); + + /* + rak_mspace_footprint() returns the number of bytes obtained from the + system for this space. + */ + size_t rak_mspace_footprint(mspace msp); + + +#if !NO_MALLINFO + /* + rak_mspace_mallinfo behaves as mallinfo, but reports properties of + the given space. + */ + struct mallinfo rak_mspace_mallinfo(mspace msp); +#endif /* NO_MALLINFO */ + + /* + malloc_usable_size(void* p) behaves the same as malloc_usable_size; + */ + size_t rak_mspace_usable_size(void* mem); + + /* + rak_mspace_malloc_stats behaves as malloc_stats, but reports + properties of the given space. + */ + void rak_mspace_malloc_stats(mspace msp); + + /* + rak_mspace_trim behaves as malloc_trim, but + operates within the given space. + */ + int rak_mspace_trim(mspace msp, size_t pad); + + /* + An alias for mallopt. + */ + int rak_mspace_mallopt(int, int); + +#endif /* MSPACES */ + +#ifdef __cplusplus +}; /* end of extern "C" */ +#endif + +/* +This is a version (aka rdlmalloc) of malloc/free/realloc written by +Doug Lea and released to the public domain, as explained at +http://creativecommons.org/licenses/publicdomain. Send questions, +comments, complaints, performance data, etc to dl@cs.oswego.edu + +* Version 2.8.4 Wed May 27 09:56:23 2009 Doug Lea (dl at gee) + +Note: There may be an updated version of this malloc obtainable at +ftp://gee.cs.oswego.edu/pub/misc/malloc.c +Check before installing! + +* Quickstart + +This library is all in one file to simplify the most common usage: +ftp it, compile it (-O3), and link it into another program. All of +the compile-time options default to reasonable values for use on +most platforms. You might later want to step through various +compile-time and dynamic tuning options. + +For convenience, an include file for code using this malloc is at: +ftp://gee.cs.oswego.edu/pub/misc/malloc-2.8.4.h +You don't really need this .h file unless you call functions not +defined in your system include files. The .h file contains only the +excerpts from this file needed for using this malloc on ANSI C/C++ +systems, so long as you haven't changed compile-time options about +naming and tuning parameters. If you do, then you can create your +own malloc.h that does include all settings by cutting at the point +indicated below. Note that you may already by default be using a C +library containing a malloc that is based on some version of this +malloc (for example in linux). You might still want to use the one +in this file to customize settings or to avoid overheads associated +with library versions. + +* Vital statistics: + +Supported pointer/size_t representation: 4 or 8 bytes +size_t MUST be an unsigned type of the same width as +pointers. (If you are using an ancient system that declares +size_t as a signed type, or need it to be a different width +than pointers, you can use a previous release of this malloc +(e.g. 2.7.2) supporting these.) + +Alignment: 8 bytes (default) +This suffices for nearly all current machines and C compilers. +However, you can define MALLOC_ALIGNMENT to be wider than this +if necessary (up to 128bytes), at the expense of using more space. + +Minimum overhead per allocated chunk: 4 or 8 bytes (if 4byte sizes) +8 or 16 bytes (if 8byte sizes) +Each malloced chunk has a hidden word of overhead holding size +and status information, and additional cross-check word +if FOOTERS is defined. + +Minimum allocated size: 4-byte ptrs: 16 bytes (including overhead) +8-byte ptrs: 32 bytes (including overhead) + +Even a request for zero bytes (i.e., malloc(0)) returns a +pointer to something of the minimum allocatable size. +The maximum overhead wastage (i.e., number of extra bytes +allocated than were requested in malloc) is less than or equal +to the minimum size, except for requests >= mmap_threshold that +are serviced via mmap(), where the worst case wastage is about +32 bytes plus the remainder from a system page (the minimal +mmap unit); typically 4096 or 8192 bytes. + +Security: static-safe; optionally more or less +The "security" of malloc refers to the ability of malicious +code to accentuate the effects of errors (for example, freeing +space that is not currently malloc'ed or overwriting past the +ends of chunks) in code that calls malloc. This malloc +guarantees not to modify any memory locations below the base of +heap, i.e., static variables, even in the presence of usage +errors. The routines additionally detect most improper frees +and reallocs. All this holds as long as the static bookkeeping +for malloc itself is not corrupted by some other means. This +is only one aspect of security -- these checks do not, and +cannot, detect all possible programming errors. + +If FOOTERS is defined nonzero, then each allocated chunk +carries an additional check word to verify that it was malloced +from its space. These check words are the same within each +execution of a program using malloc, but differ across +executions, so externally crafted fake chunks cannot be +freed. This improves security by rejecting frees/reallocs that +could corrupt heap memory, in addition to the checks preventing +writes to statics that are always on. This may further improve +security at the expense of time and space overhead. (Note that +FOOTERS may also be worth using with MSPACES.) + +By default detected errors cause the program to abort (calling +"abort()"). You can override this to instead proceed past +errors by defining PROCEED_ON_ERROR. In this case, a bad free +has no effect, and a malloc that encounters a bad address +caused by user overwrites will ignore the bad address by +dropping pointers and indices to all known memory. This may +be appropriate for programs that should continue if at all +possible in the face of programming errors, although they may +run out of memory because dropped memory is never reclaimed. + +If you don't like either of these options, you can define +CORRUPTION_ERROR_ACTION and USAGE_ERROR_ACTION to do anything +else. And if if you are sure that your program using malloc has +no errors or vulnerabilities, you can define INSECURE to 1, +which might (or might not) provide a small performance improvement. + +Thread-safety: NOT thread-safe unless USE_LOCKS defined +When USE_LOCKS is defined, each public call to malloc, free, +etc is surrounded with either a pthread mutex or a win32 +spinlock (depending on DL_PLATFORM_WIN32). This is not especially fast, and +can be a major bottleneck. It is designed only to provide +minimal protection in concurrent environments, and to provide a +basis for extensions. If you are using malloc in a concurrent +program, consider instead using nedmalloc +(http://www.nedprod.com/programs/portable/nedmalloc/) or +ptmalloc (See http://www.malloc.de), which are derived +from versions of this malloc. + +System requirements: Any combination of MORECORE and/or MMAP/MUNMAP +This malloc can use unix sbrk or any emulation (invoked using +the CALL_MORECORE macro) and/or mmap/munmap or any emulation +(invoked using CALL_MMAP/CALL_MUNMAP) to get and release system +memory. On most unix systems, it tends to work best if both +MORECORE and MMAP are enabled. On Win32, it uses emulations +based on VirtualAlloc. It also uses common C library functions +like memset. + +Compliance: I believe it is compliant with the Single Unix Specification +(See http://www.unix.org). Also SVID/XPG, ANSI C, and probably +others as well. + +* Overview of algorithms + +This is not the fastest, most space-conserving, most portable, or +most tunable malloc ever written. However it is among the fastest +while also being among the most space-conserving, portable and +tunable. Consistent balance across these factors results in a good +general-purpose allocator for malloc-intensive programs. + +In most ways, this malloc is a best-fit allocator. Generally, it +chooses the best-fitting existing chunk for a request, with ties +broken in approximately least-recently-used order. (This strategy +normally maintains low fragmentation.) However, for requests less +than 256bytes, it deviates from best-fit when there is not an +exactly fitting available chunk by preferring to use space adjacent +to that used for the previous small request, as well as by breaking +ties in approximately most-recently-used order. (These enhance +locality of series of small allocations.) And for very large requests +(>= 256Kb by default), it relies on system memory mapping +facilities, if supported. (This helps avoid carrying around and +possibly fragmenting memory used only for large chunks.) + +All operations (except malloc_stats and mallinfo) have execution +times that are bounded by a constant factor of the number of bits in +a size_t, not counting any clearing in calloc or copying in realloc, +or actions surrounding MORECORE and MMAP that have times +proportional to the number of non-contiguous regions returned by +system allocation routines, which is often just 1. In real-time +applications, you can optionally suppress segment traversals using +NO_SEGMENT_TRAVERSAL, which assures bounded execution even when +system allocators return non-contiguous spaces, at the typical +expense of carrying around more memory and increased fragmentation. + +The implementation is not very modular and seriously overuses +macros. Perhaps someday all C compilers will do as good a job +inlining modular code as can now be done by brute-force expansion, +but now, enough of them seem not to. + +Some compilers issue a lot of warnings about code that is +dead/unreachable only on some platforms, and also about intentional +uses of negation on unsigned types. All known cases of each can be +ignored. + +For a longer but out of date high-level description, see +http://gee.cs.oswego.edu/dl/html/malloc.html + +* MSPACES +If MSPACES is defined, then in addition to malloc, free, etc., +this file also defines rak_mspace_malloc, rak_mspace_free, etc. These +are versions of malloc routines that take an "mspace" argument +obtained using rak_create_mspace, to control all internal bookkeeping. +If ONLY_MSPACES is defined, only these versions are compiled. +So if you would like to use this allocator for only some allocations, +and your system malloc for others, you can compile with +ONLY_MSPACES and then do something like... +static mspace mymspace = rak_create_mspace(0,0); // for example +#define mymalloc(bytes) rak_mspace_malloc(mymspace, bytes) + +(Note: If you only need one instance of an mspace, you can instead +use "USE_DL_PREFIX" to relabel the global malloc.) + +You can similarly create thread-local allocators by storing +mspaces as thread-locals. For example: +static __thread mspace tlms = 0; +void* tlmalloc(size_t bytes) { +if (tlms == 0) tlms = rak_create_mspace(0, 0); +return rak_mspace_malloc(tlms, bytes); +} +void tlfree(void* mem) { rak_mspace_free(tlms, mem); } + +Unless FOOTERS is defined, each mspace is completely independent. +You cannot allocate from one and free to another (although +conformance is only weakly checked, so usage errors are not always +caught). If FOOTERS is defined, then each chunk carries around a tag +indicating its originating mspace, and frees are directed to their +originating spaces. + +------------------------- Compile-time options --------------------------- + +Be careful in setting #define values for numerical constants of type +size_t. On some systems, literal values are not automatically extended +to size_t precision unless they are explicitly casted. You can also +use the symbolic values MAX_SIZE_T, SIZE_T_ONE, etc below. + +DL_PLATFORM_WIN32 default: defined if _WIN32 defined +Defining DL_PLATFORM_WIN32 sets up defaults for MS environment and compilers. +Otherwise defaults are for unix. Beware that there seem to be some +cases where this malloc might not be a pure drop-in replacement for +Win32 malloc: Random-looking failures from Win32 GDI API's (eg; +SetDIBits()) may be due to bugs in some video driver implementations +when pixel buffers are malloc()ed, and the region spans more than +one VirtualAlloc()ed region. Because rdlmalloc uses a small (64Kb) +default granularity, pixel buffers may straddle virtual allocation +regions more often than when using the Microsoft allocator. You can +avoid this by using VirtualAlloc() and VirtualFree() for all pixel +buffers rather than using malloc(). If this is not possible, +recompile this malloc with a larger DEFAULT_GRANULARITY. + +MALLOC_ALIGNMENT default: (size_t)8 +Controls the minimum alignment for malloc'ed chunks. It must be a +power of two and at least 8, even on machines for which smaller +alignments would suffice. It may be defined as larger than this +though. Note however that code and data structures are optimized for +the case of 8-byte alignment. + +MSPACES default: 0 (false) +If true, compile in support for independent allocation spaces. +This is only supported if HAVE_MMAP is true. + +ONLY_MSPACES default: 0 (false) +If true, only compile in mspace versions, not regular versions. + +USE_LOCKS default: 0 (false) +Causes each call to each public routine to be surrounded with +pthread or DL_PLATFORM_WIN32 mutex lock/unlock. (If set true, this can be +overridden on a per-mspace basis for mspace versions.) If set to a +non-zero value other than 1, locks are used, but their +implementation is left out, so lock functions must be supplied manually, +as described below. + +USE_SPIN_LOCKS default: 1 iff USE_LOCKS and on x86 using gcc or MSC +If true, uses custom spin locks for locking. This is currently +supported only for x86 platforms using gcc or recent MS compilers. +Otherwise, posix locks or win32 critical sections are used. + +FOOTERS default: 0 +If true, provide extra checking and dispatching by placing +information in the footers of allocated chunks. This adds +space and time overhead. + +INSECURE default: 0 +If true, omit checks for usage errors and heap space overwrites. + +USE_DL_PREFIX default: NOT defined +Causes compiler to prefix all public routines with the string 'dl'. +This can be useful when you only want to use this malloc in one part +of a program, using your regular system malloc elsewhere. + +ABORT default: defined as abort() +Defines how to abort on failed checks. On most systems, a failed +check cannot die with an "assert" or even print an informative +message, because the underlying print routines in turn call malloc, +which will fail again. Generally, the best policy is to simply call +abort(). It's not very useful to do more than this because many +errors due to overwriting will show up as address faults (null, odd +addresses etc) rather than malloc-triggered checks, so will also +abort. Also, most compilers know that abort() does not return, so +can better optimize code conditionally calling it. + +PROCEED_ON_ERROR default: defined as 0 (false) +Controls whether detected bad addresses cause them to bypassed +rather than aborting. If set, detected bad arguments to free and +realloc are ignored. And all bookkeeping information is zeroed out +upon a detected overwrite of freed heap space, thus losing the +ability to ever return it from malloc again, but enabling the +application to proceed. If PROCEED_ON_ERROR is defined, the +static variable malloc_corruption_error_count is compiled in +and can be examined to see if errors have occurred. This option +generates slower code than the default abort policy. + +DEBUG default: NOT defined +The DEBUG setting is mainly intended for people trying to modify +this code or diagnose problems when porting to new platforms. +However, it may also be able to better isolate user errors than just +using runtime checks. The assertions in the check routines spell +out in more detail the assumptions and invariants underlying the +algorithms. The checking is fairly extensive, and will slow down +execution noticeably. Calling malloc_stats or mallinfo with DEBUG +set will attempt to check every non-mmapped allocated and free chunk +in the course of computing the summaries. + +ABORT_ON_ASSERT_FAILURE default: defined as 1 (true) +Debugging assertion failures can be nearly impossible if your +version of the assert macro causes malloc to be called, which will +lead to a cascade of further failures, blowing the runtime stack. +ABORT_ON_ASSERT_FAILURE cause assertions failures to call abort(), +which will usually make debugging easier. + +MALLOC_FAILURE_ACTION default: sets errno to ENOMEM, or no-op on win32 +The action to take before "return 0" when malloc fails to be able to +return memory because there is none available. + +HAVE_MORECORE default: 1 (true) unless win32 or ONLY_MSPACES +True if this system supports sbrk or an emulation of it. + +MORECORE default: sbrk +The name of the sbrk-style system routine to call to obtain more +memory. See below for guidance on writing custom MORECORE +functions. The type of the argument to sbrk/MORECORE varies across +systems. It cannot be size_t, because it supports negative +arguments, so it is normally the signed type of the same width as +size_t (sometimes declared as "intptr_t"). It doesn't much matter +though. Internally, we only call it with arguments less than half +the max value of a size_t, which should work across all reasonable +possibilities, although sometimes generating compiler warnings. + +MORECORE_CONTIGUOUS default: 1 (true) if HAVE_MORECORE +If true, take advantage of fact that consecutive calls to MORECORE +with positive arguments always return contiguous increasing +addresses. This is true of unix sbrk. It does not hurt too much to +set it true anyway, since malloc copes with non-contiguities. +Setting it false when definitely non-contiguous saves time +and possibly wasted space it would take to discover this though. + +MORECORE_CANNOT_TRIM default: NOT defined +True if MORECORE cannot release space back to the system when given +negative arguments. This is generally necessary only if you are +using a hand-crafted MORECORE function that cannot handle negative +arguments. + +NO_SEGMENT_TRAVERSAL default: 0 +If non-zero, suppresses traversals of memory segments +returned by either MORECORE or CALL_MMAP. This disables +merging of segments that are contiguous, and selectively +releasing them to the OS if unused, but bounds execution times. + +HAVE_MMAP default: 1 (true) +True if this system supports mmap or an emulation of it. If so, and +HAVE_MORECORE is not true, MMAP is used for all system +allocation. If set and HAVE_MORECORE is true as well, MMAP is +primarily used to directly allocate very large blocks. It is also +used as a backup strategy in cases where MORECORE fails to provide +space from system. Note: A single call to MUNMAP is assumed to be +able to unmap memory that may have be allocated using multiple calls +to MMAP, so long as they are adjacent. + +HAVE_MREMAP default: 1 on linux, else 0 +If true realloc() uses mremap() to re-allocate large blocks and +extend or shrink allocation spaces. + +MMAP_CLEARS default: 1 except on WINCE. +True if mmap clears memory so calloc doesn't need to. This is true +for standard unix mmap using /dev/zero and on DL_PLATFORM_WIN32 except for WINCE. + +USE_BUILTIN_FFS default: 0 (i.e., not used) +Causes malloc to use the builtin ffs() function to compute indices. +Some compilers may recognize and intrinsify ffs to be faster than the +supplied C version. Also, the case of x86 using gcc is special-cased +to an asm instruction, so is already as fast as it can be, and so +this setting has no effect. Similarly for Win32 under recent MS compilers. +(On most x86s, the asm version is only slightly faster than the C version.) + +malloc_getpagesize default: derive from system includes, or 4096. +The system page size. To the extent possible, this malloc manages +memory from the system in page-size units. This may be (and +usually is) a function rather than a constant. This is ignored +if DL_PLATFORM_WIN32, where page size is determined using getSystemInfo during +initialization. + +USE_DEV_RANDOM default: 0 (i.e., not used) +Causes malloc to use /dev/random to initialize secure magic seed for +stamping footers. Otherwise, the current time is used. + +NO_MALLINFO default: 0 +If defined, don't compile "mallinfo". This can be a simple way +of dealing with mismatches between system declarations and +those in this file. + +MALLINFO_FIELD_TYPE default: size_t +The type of the fields in the mallinfo struct. This was originally +defined as "int" in SVID etc, but is more usefully defined as +size_t. The value is used only if HAVE_USR_INCLUDE_MALLOC_H is not set + +REALLOC_ZERO_BYTES_FREES default: not defined +This should be set if a call to realloc with zero bytes should +be the same as a call to free. Some people think it should. Otherwise, +since this malloc returns a unique pointer for malloc(0), so does +realloc(p, 0). + +LACKS_UNISTD_H, LACKS_FCNTL_H, LACKS_SYS_PARAM_H, LACKS_SYS_MMAN_H +LACKS_STRINGS_H, LACKS_STRING_H, LACKS_SYS_TYPES_H, LACKS_ERRNO_H +LACKS_STDLIB_H default: NOT defined unless on DL_PLATFORM_WIN32 +Define these if your system does not have these header files. +You might need to manually insert some of the declarations they provide. + +DEFAULT_GRANULARITY default: page size if MORECORE_CONTIGUOUS, +system_info.dwAllocationGranularity in DL_PLATFORM_WIN32, +otherwise 64K. +Also settable using mallopt(M_GRANULARITY, x) +The unit for allocating and deallocating memory from the system. On +most systems with contiguous MORECORE, there is no reason to +make this more than a page. However, systems with MMAP tend to +either require or encourage larger granularities. You can increase +this value to prevent system allocation functions to be called so +often, especially if they are slow. The value must be at least one +page and must be a power of two. Setting to 0 causes initialization +to either page size or win32 region size. (Note: In previous +versions of malloc, the equivalent of this option was called +"TOP_PAD") + +DEFAULT_TRIM_THRESHOLD default: 2MB +Also settable using mallopt(M_TRIM_THRESHOLD, x) +The maximum amount of unused top-most memory to keep before +releasing via malloc_trim in free(). Automatic trimming is mainly +useful in long-lived programs using contiguous MORECORE. Because +trimming via sbrk can be slow on some systems, and can sometimes be +wasteful (in cases where programs immediately afterward allocate +more large chunks) the value should be high enough so that your +overall system performance would improve by releasing this much +memory. As a rough guide, you might set to a value close to the +average size of a process (program) running on your system. +Releasing this much memory would allow such a process to run in +memory. Generally, it is worth tuning trim thresholds when a +program undergoes phases where several large chunks are allocated +and released in ways that can reuse each other's storage, perhaps +mixed with phases where there are no such chunks at all. The trim +value must be greater than page size to have any useful effect. To +disable trimming completely, you can set to MAX_SIZE_T. Note that the trick +some people use of mallocing a huge space and then freeing it at +program startup, in an attempt to reserve system memory, doesn't +have the intended effect under automatic trimming, since that memory +will immediately be returned to the system. + +DEFAULT_MMAP_THRESHOLD default: 256K +Also settable using mallopt(M_MMAP_THRESHOLD, x) +The request size threshold for using MMAP to directly service a +request. Requests of at least this size that cannot be allocated +using already-existing space will be serviced via mmap. (If enough +normal freed space already exists it is used instead.) Using mmap +segregates relatively large chunks of memory so that they can be +individually obtained and released from the host system. A request +serviced through mmap is never reused by any other request (at least +not directly; the system may just so happen to remap successive +requests to the same locations). Segregating space in this way has +the benefits that: Mmapped space can always be individually released +back to the system, which helps keep the system level memory demands +of a long-lived program low. Also, mapped memory doesn't become +`locked' between other chunks, as can happen with normally allocated +chunks, which means that even trimming via malloc_trim would not +release them. However, it has the disadvantage that the space +cannot be reclaimed, consolidated, and then used to service later +requests, as happens with normal chunks. The advantages of mmap +nearly always outweigh disadvantages for "large" chunks, but the +value of "large" may vary across systems. The default is an +empirically derived value that works well in most systems. You can +disable mmap by setting to MAX_SIZE_T. + +MAX_RELEASE_CHECK_RATE default: 4095 unless not HAVE_MMAP +The number of consolidated frees between checks to release +unused segments when freeing. When using non-contiguous segments, +especially with multiple mspaces, checking only for topmost space +doesn't always suffice to trigger trimming. To compensate for this, +free() will, with a period of MAX_RELEASE_CHECK_RATE (or the +current number of segments, if greater) try to release unused +segments to the OS when freeing chunks that result in +consolidation. The best value for this parameter is a compromise +between slowing down frees with relatively costly checks that +rarely trigger versus holding on to unused memory. To effectively +disable, set to MAX_SIZE_T. This may lead to a very slight speed +improvement at the expense of carrying around more memory. +*/ + +/* Version identifier to allow people to support multiple versions */ +#ifndef DLMALLOC_VERSION +#define DLMALLOC_VERSION 20804 +#endif /* DLMALLOC_VERSION */ + +#include "rdlmalloc-options.h" + +#ifndef WIN32 +#if defined(_XBOX) || defined(X360) +#else +#if defined(_WIN32) +#define DL_PLATFORM_WIN32 1 +#endif /* _WIN32 */ +#ifdef _WIN32_WCE +#define LACKS_FCNTL_H +#define DL_PLATFORM_WIN32 1 +#endif /* _WIN32_WCE */ +#endif +#else +#define DL_PLATFORM_WIN32 1 +#endif /* DL_PLATFORM_WIN32 */ + +#if defined(_XBOX) || defined(X360) + +#endif + +#if defined(_PS3) || defined(__PS3__) || defined(SN_TARGET_PS3) + +#endif + + +#ifdef DL_PLATFORM_WIN32 +#define WIN32_LEAN_AND_MEAN +#include +#define HAVE_MMAP 1 +#define HAVE_MORECORE 0 +#define LACKS_UNISTD_H +#define LACKS_SYS_PARAM_H +#define LACKS_SYS_MMAN_H +#define LACKS_STRING_H +#define LACKS_STRINGS_H +#define LACKS_SYS_TYPES_H +#define LACKS_ERRNO_H +#ifndef MALLOC_FAILURE_ACTION +#define MALLOC_FAILURE_ACTION +#endif /* MALLOC_FAILURE_ACTION */ +#ifdef _WIN32_WCE /* WINCE reportedly does not clear */ +#define MMAP_CLEARS 0 +#else +#define MMAP_CLEARS 1 +#endif /* _WIN32_WCE */ +#endif /* DL_PLATFORM_WIN32 */ + +#if defined(DARWIN) || defined(_DARWIN) +/* Mac OSX docs advise not to use sbrk; it seems better to use mmap */ +#ifndef HAVE_MORECORE +#define HAVE_MORECORE 0 +#define HAVE_MMAP 1 +/* OSX allocators provide 16 byte alignment */ +#ifndef MALLOC_ALIGNMENT +#define MALLOC_ALIGNMENT ((size_t)16U) +#endif +#endif /* HAVE_MORECORE */ +#endif /* DARWIN */ + +#ifndef LACKS_SYS_TYPES_H +#include /* For size_t */ +#endif /* LACKS_SYS_TYPES_H */ + +#if (defined(__GNUC__) && ((defined(__i386__) || defined(__x86_64__)))) || (defined(_MSC_VER) && _MSC_VER>=1310) +#define SPIN_LOCKS_AVAILABLE 1 +#else +#define SPIN_LOCKS_AVAILABLE 0 +#endif + +/* The maximum possible size_t value has all bits set */ +#define MAX_SIZE_T (~(size_t)0) + +#ifndef ONLY_MSPACES +#define ONLY_MSPACES 0 /* define to a value */ +#else +#define ONLY_MSPACES 1 +#endif /* ONLY_MSPACES */ +#ifndef MSPACES +#if ONLY_MSPACES +#define MSPACES 1 +#else /* ONLY_MSPACES */ +#define MSPACES 0 +#endif /* ONLY_MSPACES */ +#endif /* MSPACES */ +#ifndef MALLOC_ALIGNMENT +#define MALLOC_ALIGNMENT ((size_t)8U) +#endif /* MALLOC_ALIGNMENT */ +#ifndef FOOTERS +#define FOOTERS 0 +#endif /* FOOTERS */ +#ifndef ABORT +#define ABORT abort() +#endif /* ABORT */ +#ifndef ABORT_ON_ASSERT_FAILURE +#define ABORT_ON_ASSERT_FAILURE 1 +#endif /* ABORT_ON_ASSERT_FAILURE */ +#ifndef PROCEED_ON_ERROR +#define PROCEED_ON_ERROR 0 +#endif /* PROCEED_ON_ERROR */ +#ifndef USE_LOCKS +#define USE_LOCKS 0 +#endif /* USE_LOCKS */ +#ifndef USE_SPIN_LOCKS +#if USE_LOCKS && SPIN_LOCKS_AVAILABLE +#define USE_SPIN_LOCKS 1 +#else +#define USE_SPIN_LOCKS 0 +#endif /* USE_LOCKS && SPIN_LOCKS_AVAILABLE. */ +#endif /* USE_SPIN_LOCKS */ +#ifndef INSECURE +#define INSECURE 0 +#endif /* INSECURE */ +#ifndef HAVE_MMAP +#define HAVE_MMAP 1 +#endif /* HAVE_MMAP */ +#ifndef MMAP_CLEARS +#define MMAP_CLEARS 1 +#endif /* MMAP_CLEARS */ +#ifndef HAVE_MREMAP +#ifdef linux +#define HAVE_MREMAP 1 +#else /* linux */ +#define HAVE_MREMAP 0 +#endif /* linux */ +#endif /* HAVE_MREMAP */ +#ifndef MALLOC_FAILURE_ACTION +#define MALLOC_FAILURE_ACTION errno = ENOMEM; +#endif /* MALLOC_FAILURE_ACTION */ +#ifndef HAVE_MORECORE +#if ONLY_MSPACES +#define HAVE_MORECORE 0 +#else /* ONLY_MSPACES */ +#define HAVE_MORECORE 1 +#endif /* ONLY_MSPACES */ +#endif /* HAVE_MORECORE */ +#if !HAVE_MORECORE +#define MORECORE_CONTIGUOUS 0 +#else /* !HAVE_MORECORE */ +#define MORECORE_DEFAULT sbrk +#ifndef MORECORE_CONTIGUOUS +#define MORECORE_CONTIGUOUS 1 +#endif /* MORECORE_CONTIGUOUS */ +#endif /* HAVE_MORECORE */ +#ifndef DEFAULT_GRANULARITY +#if (MORECORE_CONTIGUOUS || defined(DL_PLATFORM_WIN32)) +#define DEFAULT_GRANULARITY (0) /* 0 means to compute in init_mparams */ +#else /* MORECORE_CONTIGUOUS */ +#define DEFAULT_GRANULARITY ((size_t)64U * (size_t)1024U) +#endif /* MORECORE_CONTIGUOUS */ +#endif /* DEFAULT_GRANULARITY */ +#ifndef DEFAULT_TRIM_THRESHOLD +#ifndef MORECORE_CANNOT_TRIM +#define DEFAULT_TRIM_THRESHOLD ((size_t)2U * (size_t)1024U * (size_t)1024U) +#else /* MORECORE_CANNOT_TRIM */ +#define DEFAULT_TRIM_THRESHOLD MAX_SIZE_T +#endif /* MORECORE_CANNOT_TRIM */ +#endif /* DEFAULT_TRIM_THRESHOLD */ +#ifndef DEFAULT_MMAP_THRESHOLD +#if HAVE_MMAP +#define DEFAULT_MMAP_THRESHOLD ((size_t)256U * (size_t)1024U) +#else /* HAVE_MMAP */ +#define DEFAULT_MMAP_THRESHOLD MAX_SIZE_T +#endif /* HAVE_MMAP */ +#endif /* DEFAULT_MMAP_THRESHOLD */ +#ifndef MAX_RELEASE_CHECK_RATE +#if HAVE_MMAP +#define MAX_RELEASE_CHECK_RATE 4095 +#else +#define MAX_RELEASE_CHECK_RATE MAX_SIZE_T +#endif /* HAVE_MMAP */ +#endif /* MAX_RELEASE_CHECK_RATE */ +#ifndef USE_BUILTIN_FFS +#define USE_BUILTIN_FFS 0 +#endif /* USE_BUILTIN_FFS */ +#ifndef USE_DEV_RANDOM +#define USE_DEV_RANDOM 0 +#endif /* USE_DEV_RANDOM */ +#ifndef NO_MALLINFO +#define NO_MALLINFO 0 +#endif /* NO_MALLINFO */ +#ifndef MALLINFO_FIELD_TYPE +#define MALLINFO_FIELD_TYPE size_t +#endif /* MALLINFO_FIELD_TYPE */ +#ifndef NO_SEGMENT_TRAVERSAL +#define NO_SEGMENT_TRAVERSAL 0 +#endif /* NO_SEGMENT_TRAVERSAL */ + +/* +mallopt tuning options. SVID/XPG defines four standard parameter +numbers for mallopt, normally defined in malloc.h. None of these +are used in this malloc, so setting them has no effect. But this +malloc does support the following options. +*/ + +#define M_TRIM_THRESHOLD (-1) +#define M_GRANULARITY (-2) +#define M_MMAP_THRESHOLD (-3) + +/* ------------------------ Mallinfo declarations ------------------------ */ + +#if !NO_MALLINFO +/* +This version of malloc supports the standard SVID/XPG mallinfo +routine that returns a struct containing usage properties and +statistics. It should work on any system that has a +/usr/include/malloc.h defining struct mallinfo. The main +declaration needed is the mallinfo struct that is returned (by-copy) +by mallinfo(). The malloinfo struct contains a bunch of fields that +are not even meaningful in this version of malloc. These fields are +are instead filled by mallinfo() with other numbers that might be of +interest. + +HAVE_USR_INCLUDE_MALLOC_H should be set if you have a +/usr/include/malloc.h file that includes a declaration of struct +mallinfo. If so, it is included; else a compliant version is +declared below. These must be precisely the same for mallinfo() to +work. The original SVID version of this struct, defined on most +systems with mallinfo, declares all fields as ints. But some others +define as unsigned long. If your system defines the fields using a +type of different width than listed here, you MUST #include your +system version and #define HAVE_USR_INCLUDE_MALLOC_H. +*/ + +/* #define HAVE_USR_INCLUDE_MALLOC_H */ + +#ifdef HAVE_USR_INCLUDE_MALLOC_H +#include "/usr/include/malloc.h" +#else /* HAVE_USR_INCLUDE_MALLOC_H */ +#ifndef STRUCT_MALLINFO_DECLARED +#define STRUCT_MALLINFO_DECLARED 1 +struct mallinfo { + MALLINFO_FIELD_TYPE arena; /* non-mmapped space allocated from system */ + MALLINFO_FIELD_TYPE ordblks; /* number of free chunks */ + MALLINFO_FIELD_TYPE smblks; /* always 0 */ + MALLINFO_FIELD_TYPE hblks; /* always 0 */ + MALLINFO_FIELD_TYPE hblkhd; /* space in mmapped regions */ + MALLINFO_FIELD_TYPE usmblks; /* maximum total allocated space */ + MALLINFO_FIELD_TYPE fsmblks; /* always 0 */ + MALLINFO_FIELD_TYPE uordblks; /* total allocated space */ + MALLINFO_FIELD_TYPE fordblks; /* total free space */ + MALLINFO_FIELD_TYPE keepcost; /* releasable (via malloc_trim) space */ +}; +#endif /* STRUCT_MALLINFO_DECLARED */ +#endif /* HAVE_USR_INCLUDE_MALLOC_H */ +#endif /* NO_MALLINFO */ + +/* +Try to persuade compilers to inline. The most critical functions for +inlining are defined as macros, so these aren't used for them. +*/ + +#ifndef FORCEINLINE +#if defined(__GNUC__) +#define FORCEINLINE __inline __attribute__ ((always_inline)) +#elif defined(_MSC_VER) +#define FORCEINLINE __forceinline +#endif +#endif +#ifndef NOINLINE +#if defined(__GNUC__) +#define NOINLINE __attribute__ ((noinline)) +#elif defined(_MSC_VER) +#define NOINLINE __declspec(noinline) +#else +#define NOINLINE +#endif +#endif + +#ifdef __cplusplus +extern "C" { +#ifndef FORCEINLINE +#define FORCEINLINE inline +#endif +#endif /* __cplusplus */ +#ifndef FORCEINLINE +#define FORCEINLINE +#endif + +#if !ONLY_MSPACES + + /* ------------------- Declarations of public routines ------------------- */ + +#ifndef USE_DL_PREFIX +#define rdlcalloc calloc +#define rdlfree free +#define rdlmalloc malloc +#define rdlmemalign memalign +#define rdlrealloc realloc +#define rdlvalloc valloc +#define rdlpvalloc pvalloc +#define rdlmallinfo mallinfo +#define rdlmallopt mallopt +#define rdlmalloc_trim malloc_trim +#define rdlmalloc_stats malloc_stats +#define rdlmalloc_usable_size malloc_usable_size +#define rdlmalloc_footprint malloc_footprint +#define dlmalloc_max_footprint malloc_max_footprint +#define rdlindependent_calloc independent_calloc +#define rdlindependent_comalloc independent_comalloc +#endif /* USE_DL_PREFIX */ + + + /* + malloc(size_t n) + Returns a pointer to a newly allocated chunk of at least n bytes, or + null if no space is available, in which case errno is set to ENOMEM + on ANSI C systems. + + If n is zero, malloc returns a minimum-sized chunk. (The minimum + size is 16 bytes on most 32bit systems, and 32 bytes on 64bit + systems.) Note that size_t is an unsigned type, so calls with + arguments that would be negative if signed are interpreted as + requests for huge amounts of space, which will often fail. The + maximum supported value of n differs across systems, but is in all + cases less than the maximum representable value of a size_t. + */ + void* rdlmalloc(size_t); + + /* + free(void* p) + Releases the chunk of memory pointed to by p, that had been previously + allocated using malloc or a related routine such as realloc. + It has no effect if p is null. If p was not malloced or already + freed, free(p) will by default cause the current program to abort. + */ + void rdlfree(void*); + + /* + calloc(size_t n_elements, size_t element_size); + Returns a pointer to n_elements * element_size bytes, with all locations + set to zero. + */ + void* rdlcalloc(size_t, size_t); + + /* + realloc(void* p, size_t n) + Returns a pointer to a chunk of size n that contains the same data + as does chunk p up to the minimum of (n, p's size) bytes, or null + if no space is available. + + The returned pointer may or may not be the same as p. The algorithm + prefers extending p in most cases when possible, otherwise it + employs the equivalent of a malloc-copy-free sequence. + + If p is null, realloc is equivalent to malloc. + + If space is not available, realloc returns null, errno is set (if on + ANSI) and p is NOT freed. + + if n is for fewer bytes than already held by p, the newly unused + space is lopped off and freed if possible. realloc with a size + argument of zero (re)allocates a minimum-sized chunk. + + The old unix realloc convention of allowing the last-free'd chunk + to be used as an argument to realloc is not supported. + */ + + void* rdlrealloc(void*, size_t); + + /* + memalign(size_t alignment, size_t n); + Returns a pointer to a newly allocated chunk of n bytes, aligned + in accord with the alignment argument. + + The alignment argument should be a power of two. If the argument is + not a power of two, the nearest greater power is used. + 8-byte alignment is guaranteed by normal malloc calls, so don't + bother calling memalign with an argument of 8 or less. + + Overreliance on memalign is a sure way to fragment space. + */ + void* rdlmemalign(size_t, size_t); + + /* + valloc(size_t n); + Equivalent to memalign(pagesize, n), where pagesize is the page + size of the system. If the pagesize is unknown, 4096 is used. + */ + void* rdlvalloc(size_t); + + /* + mallopt(int parameter_number, int parameter_value) + Sets tunable parameters The format is to provide a + (parameter-number, parameter-value) pair. mallopt then sets the + corresponding parameter to the argument value if it can (i.e., so + long as the value is meaningful), and returns 1 if successful else + 0. To workaround the fact that mallopt is specified to use int, + not size_t parameters, the value -1 is specially treated as the + maximum unsigned size_t value. + + SVID/XPG/ANSI defines four standard param numbers for mallopt, + normally defined in malloc.h. None of these are use in this malloc, + so setting them has no effect. But this malloc also supports other + options in mallopt. See below for details. Briefly, supported + parameters are as follows (listed defaults are for "typical" + configurations). + + Symbol param # default allowed param values + M_TRIM_THRESHOLD -1 2*1024*1024 any (-1 disables) + M_GRANULARITY -2 page size any power of 2 >= page size + M_MMAP_THRESHOLD -3 256*1024 any (or 0 if no MMAP support) + */ + int rdlmallopt(int, int); + + /* + malloc_footprint(); + Returns the number of bytes obtained from the system. The total + number of bytes allocated by malloc, realloc etc., is less than this + value. Unlike mallinfo, this function returns only a precomputed + result, so can be called frequently to monitor memory consumption. + Even if locks are otherwise defined, this function does not use them, + so results might not be up to date. + */ + size_t rdlmalloc_footprint(void); + + /* + malloc_max_footprint(); + Returns the maximum number of bytes obtained from the system. This + value will be greater than current footprint if deallocated space + has been reclaimed by the system. The peak number of bytes allocated + by malloc, realloc etc., is less than this value. Unlike mallinfo, + this function returns only a precomputed result, so can be called + frequently to monitor memory consumption. Even if locks are + otherwise defined, this function does not use them, so results might + not be up to date. + */ + size_t dlmalloc_max_footprint(void); + +#if !NO_MALLINFO + /* + mallinfo() + Returns (by copy) a struct containing various summary statistics: + + arena: current total non-mmapped bytes allocated from system + ordblks: the number of free chunks + smblks: always zero. + hblks: current number of mmapped regions + hblkhd: total bytes held in mmapped regions + usmblks: the maximum total allocated space. This will be greater + than current total if trimming has occurred. + fsmblks: always zero + uordblks: current total allocated space (normal or mmapped) + fordblks: total free space + keepcost: the maximum number of bytes that could ideally be released + back to system via malloc_trim. ("ideally" means that + it ignores page restrictions etc.) + + Because these fields are ints, but internal bookkeeping may + be kept as longs, the reported values may wrap around zero and + thus be inaccurate. + */ + struct mallinfo rdlmallinfo(void); +#endif /* NO_MALLINFO */ + + /* + independent_calloc(size_t n_elements, size_t element_size, void* chunks[]); + + independent_calloc is similar to calloc, but instead of returning a + single cleared space, it returns an array of pointers to n_elements + independent elements that can hold contents of size elem_size, each + of which starts out cleared, and can be independently freed, + realloc'ed etc. The elements are guaranteed to be adjacently + allocated (this is not guaranteed to occur with multiple callocs or + mallocs), which may also improve cache locality in some + applications. + + The "chunks" argument is optional (i.e., may be null, which is + probably the most typical usage). If it is null, the returned array + is itself dynamically allocated and should also be freed when it is + no longer needed. Otherwise, the chunks array must be of at least + n_elements in length. It is filled in with the pointers to the + chunks. + + In either case, independent_calloc returns this pointer array, or + null if the allocation failed. If n_elements is zero and "chunks" + is null, it returns a chunk representing an array with zero elements + (which should be freed if not wanted). + + Each element must be individually freed when it is no longer + needed. If you'd like to instead be able to free all at once, you + should instead use regular calloc and assign pointers into this + space to represent elements. (In this case though, you cannot + independently free elements.) + + independent_calloc simplifies and speeds up implementations of many + kinds of pools. It may also be useful when constructing large data + structures that initially have a fixed number of fixed-sized nodes, + but the number is not known at compile time, and some of the nodes + may later need to be freed. For example: + + struct Node { int item; struct Node* next; }; + + struct Node* build_list() { + struct Node** pool; + int n = read_number_of_nodes_needed(); + if (n <= 0) return 0; + pool = (struct Node**)(independent_calloc(n, sizeof(struct Node), 0); + if (pool == 0) die(); + // organize into a linked list... + struct Node* first = pool[0]; + for (i = 0; i < n-1; ++i) + pool[i]->next = pool[i+1]; + free(pool); // Can now free the array (or not, if it is needed later) + return first; + } + */ + void** rdlindependent_calloc(size_t, size_t, void**); + + /* + independent_comalloc(size_t n_elements, size_t sizes[], void* chunks[]); + + independent_comalloc allocates, all at once, a set of n_elements + chunks with sizes indicated in the "sizes" array. It returns + an array of pointers to these elements, each of which can be + independently freed, realloc'ed etc. The elements are guaranteed to + be adjacently allocated (this is not guaranteed to occur with + multiple callocs or mallocs), which may also improve cache locality + in some applications. + + The "chunks" argument is optional (i.e., may be null). If it is null + the returned array is itself dynamically allocated and should also + be freed when it is no longer needed. Otherwise, the chunks array + must be of at least n_elements in length. It is filled in with the + pointers to the chunks. + + In either case, independent_comalloc returns this pointer array, or + null if the allocation failed. If n_elements is zero and chunks is + null, it returns a chunk representing an array with zero elements + (which should be freed if not wanted). + + Each element must be individually freed when it is no longer + needed. If you'd like to instead be able to free all at once, you + should instead use a single regular malloc, and assign pointers at + particular offsets in the aggregate space. (In this case though, you + cannot independently free elements.) + + independent_comallac differs from independent_calloc in that each + element may have a different size, and also that it does not + automatically clear elements. + + independent_comalloc can be used to speed up allocation in cases + where several structs or objects must always be allocated at the + same time. For example: + + struct Head { ... } + struct Foot { ... } + + void send_message(char* msg) { + int msglen = strlen(msg); + size_t sizes[3] = { sizeof(struct Head), msglen, sizeof(struct Foot) }; + void* chunks[3]; + if (independent_comalloc(3, sizes, chunks) == 0) + die(); + struct Head* head = (struct Head*)(chunks[0]); + char* body = (char*)(chunks[1]); + struct Foot* foot = (struct Foot*)(chunks[2]); + // ... + } + + In general though, independent_comalloc is worth using only for + larger values of n_elements. For small values, you probably won't + detect enough difference from series of malloc calls to bother. + + Overuse of independent_comalloc can increase overall memory usage, + since it cannot reuse existing noncontiguous small chunks that + might be available for some of the elements. + */ + void** rdlindependent_comalloc(size_t, size_t*, void**); + + + /* + pvalloc(size_t n); + Equivalent to valloc(minimum-page-that-holds(n)), that is, + round up n to nearest pagesize. + */ + void* rdlpvalloc(size_t); + + /* + malloc_trim(size_t pad); + + If possible, gives memory back to the system (via negative arguments + to sbrk) if there is unused memory at the `high' end of the malloc + pool or in unused MMAP segments. You can call this after freeing + large blocks of memory to potentially reduce the system-level memory + requirements of a program. However, it cannot guarantee to reduce + memory. Under some allocation patterns, some large free blocks of + memory will be locked between two used chunks, so they cannot be + given back to the system. + + The `pad' argument to malloc_trim represents the amount of free + trailing space to leave untrimmed. If this argument is zero, only + the minimum amount of memory to maintain internal data structures + will be left. Non-zero arguments can be supplied to maintain enough + trailing space to service future expected allocations without having + to re-obtain memory from the system. + + Malloc_trim returns 1 if it actually released any memory, else 0. + */ + int rdlmalloc_trim(size_t); + + /* + malloc_stats(); + Prints on stderr the amount of space obtained from the system (both + via sbrk and mmap), the maximum amount (which may be more than + current if malloc_trim and/or munmap got called), and the current + number of bytes allocated via malloc (or realloc, etc) but not yet + freed. Note that this is the number of bytes allocated, not the + number requested. It will be larger than the number requested + because of alignment and bookkeeping overhead. Because it includes + alignment wastage as being in use, this figure may be greater than + zero even when no user-level chunks are allocated. + + The reported current and maximum system memory can be inaccurate if + a program makes other calls to system memory allocation functions + (normally sbrk) outside of malloc. + + malloc_stats prints only the most commonly interesting statistics. + More information can be obtained by calling mallinfo. + */ + void rdlmalloc_stats(void); + +#endif /* ONLY_MSPACES */ + + /* + malloc_usable_size(void* p); + + Returns the number of bytes you can actually use in + an allocated chunk, which may be more than you requested (although + often not) due to alignment and minimum size constraints. + You can use this many bytes without worrying about + overwriting other allocated objects. This is not a particularly great + programming practice. malloc_usable_size can be more useful in + debugging and assertions, for example: + + p = malloc(n); + assert(malloc_usable_size(p) >= 256); + */ + size_t rdlmalloc_usable_size(void*); + + +#if MSPACES + + /* + mspace is an opaque type representing an independent + region of space that supports rak_mspace_malloc, etc. + */ + typedef void* mspace; + + /* + rak_create_mspace creates and returns a new independent space with the + given initial capacity, or, if 0, the default granularity size. It + returns null if there is no system memory available to create the + space. If argument locked is non-zero, the space uses a separate + lock to control access. The capacity of the space will grow + dynamically as needed to service rak_mspace_malloc requests. You can + control the sizes of incremental increases of this space by + compiling with a different DEFAULT_GRANULARITY or dynamically + setting with mallopt(M_GRANULARITY, value). + */ + mspace rak_create_mspace(size_t capacity, int locked); + + /* + rak_destroy_mspace destroys the given space, and attempts to return all + of its memory back to the system, returning the total number of + bytes freed. After destruction, the results of access to all memory + used by the space become undefined. + */ + size_t rak_destroy_mspace(mspace msp); + + /* + rak_create_mspace_with_base uses the memory supplied as the initial base + of a new mspace. Part (less than 128*sizeof(size_t) bytes) of this + space is used for bookkeeping, so the capacity must be at least this + large. (Otherwise 0 is returned.) When this initial space is + exhausted, additional memory will be obtained from the system. + Destroying this space will deallocate all additionally allocated + space (if possible) but not the initial base. + */ + mspace rak_create_mspace_with_base(void* base, size_t capacity, int locked); + + /* + rak_mspace_track_large_chunks controls whether requests for large chunks + are allocated in their own untracked mmapped regions, separate from + others in this mspace. By default large chunks are not tracked, + which reduces fragmentation. However, such chunks are not + necessarily released to the system upon rak_destroy_mspace. Enabling + tracking by setting to true may increase fragmentation, but avoids + leakage when relying on rak_destroy_mspace to release all memory + allocated using this space. The function returns the previous + setting. + */ + int rak_mspace_track_large_chunks(mspace msp, int enable); + + + /* + rak_mspace_malloc behaves as malloc, but operates within + the given space. + */ + void* rak_mspace_malloc(mspace msp, size_t bytes); + + /* + rak_mspace_free behaves as free, but operates within + the given space. + + If compiled with FOOTERS==1, rak_mspace_free is not actually needed. + free may be called instead of rak_mspace_free because freed chunks from + any space are handled by their originating spaces. + */ + void rak_mspace_free(mspace msp, void* mem); + + /* + rak_mspace_realloc behaves as realloc, but operates within + the given space. + + If compiled with FOOTERS==1, rak_mspace_realloc is not actually + needed. realloc may be called instead of rak_mspace_realloc because + realloced chunks from any space are handled by their originating + spaces. + */ + void* rak_mspace_realloc(mspace msp, void* mem, size_t newsize); + + /* + rak_mspace_calloc behaves as calloc, but operates within + the given space. + */ + void* rak_mspace_calloc(mspace msp, size_t n_elements, size_t elem_size); + + /* + rak_mspace_memalign behaves as memalign, but operates within + the given space. + */ + void* rak_mspace_memalign(mspace msp, size_t alignment, size_t bytes); + + /* + rak_mspace_independent_calloc behaves as independent_calloc, but + operates within the given space. + */ + void** rak_mspace_independent_calloc(mspace msp, size_t n_elements, + size_t elem_size, void* chunks[]); + + /* + rak_mspace_independent_comalloc behaves as independent_comalloc, but + operates within the given space. + */ + void** rak_mspace_independent_comalloc(mspace msp, size_t n_elements, + size_t sizes[], void* chunks[]); + + /* + rak_mspace_footprint() returns the number of bytes obtained from the + system for this space. + */ + size_t rak_mspace_footprint(mspace msp); + + /* + mspace_max_footprint() returns the peak number of bytes obtained from the + system for this space. + */ + size_t mspace_max_footprint(mspace msp); + + +#if !NO_MALLINFO + /* + rak_mspace_mallinfo behaves as mallinfo, but reports properties of + the given space. + */ + struct mallinfo rak_mspace_mallinfo(mspace msp); +#endif /* NO_MALLINFO */ + + /* + malloc_usable_size(void* p) behaves the same as malloc_usable_size; + */ + size_t rak_mspace_usable_size(void* mem); + + /* + rak_mspace_malloc_stats behaves as malloc_stats, but reports + properties of the given space. + */ + void rak_mspace_malloc_stats(mspace msp); + + /* + rak_mspace_trim behaves as malloc_trim, but + operates within the given space. + */ + int rak_mspace_trim(mspace msp, size_t pad); + + /* + An alias for mallopt. + */ + int rak_mspace_mallopt(int, int); + +#endif /* MSPACES */ + +#ifdef __cplusplus +}; /* end of extern "C" */ +#endif /* __cplusplus */ + +/* +======================================================================== +To make a fully customizable malloc.h header file, cut everything +above this line, put into file malloc.h, edit to suit, and #include it +on the next line, as well as in programs that use this malloc. +======================================================================== +*/ + +/* #include "malloc.h" */ + +/*------------------------------ internal #includes ---------------------- */ + +#ifdef DL_PLATFORM_WIN32 +#pragma warning( disable : 4146 ) /* no "unsigned" warnings */ +#endif /* DL_PLATFORM_WIN32 */ + +#include /* for printing in malloc_stats */ + +#ifndef LACKS_ERRNO_H +#include /* for MALLOC_FAILURE_ACTION */ +#endif /* LACKS_ERRNO_H */ + +#if FOOTERS || DEBUG +#include /* for magic initialization */ +#endif /* FOOTERS */ + +#ifndef LACKS_STDLIB_H +#include /* for abort() */ +#endif /* LACKS_STDLIB_H */ + +#ifdef DEBUG +#if ABORT_ON_ASSERT_FAILURE +#undef assert +#define assert(x) if(!(x)) ABORT +#else /* ABORT_ON_ASSERT_FAILURE */ +#include +#endif /* ABORT_ON_ASSERT_FAILURE */ +#else /* DEBUG */ +#ifndef assert +#define assert(x) +#endif +#define DEBUG 0 +#endif /* DEBUG */ + +#ifndef LACKS_STRING_H +#include /* for memset etc */ +#endif /* LACKS_STRING_H */ + +#if USE_BUILTIN_FFS +#ifndef LACKS_STRINGS_H +#include /* for ffs */ +#endif /* LACKS_STRINGS_H */ +#endif /* USE_BUILTIN_FFS */ + +#if HAVE_MMAP +#ifndef LACKS_SYS_MMAN_H +/* On some versions of linux, mremap decl in mman.h needs __USE_GNU set */ +#if (defined(linux) && !defined(__USE_GNU)) +#define __USE_GNU 1 +#include /* for mmap */ +#undef __USE_GNU +#else +#include /* for mmap */ +#endif /* linux */ +#endif /* LACKS_SYS_MMAN_H */ +#ifndef LACKS_FCNTL_H +#include +#endif /* LACKS_FCNTL_H */ +#endif /* HAVE_MMAP */ + +#ifndef LACKS_UNISTD_H +#include /* for sbrk, sysconf */ +#else /* LACKS_UNISTD_H */ +#if !defined(__FreeBSD__) && !defined(__OpenBSD__) && !defined(__NetBSD__) +extern void* sbrk(ptrdiff_t); +#endif /* FreeBSD etc */ +#endif /* LACKS_UNISTD_H */ + +/* Declarations for locking */ +#if USE_LOCKS +#if defined(_XBOX) || defined(X360) + +#elif !defined(DL_PLATFORM_WIN32) +#include +#if defined (__SVR4) && defined (__sun) /* solaris */ +#include +#endif /* solaris */ +#else +#ifndef _M_AMD64 +/* These are already defined on AMD64 builds */ +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + LONG __cdecl _InterlockedCompareExchange(LONG volatile *Dest, LONG Exchange, LONG Comp); + LONG __cdecl _InterlockedExchange(LONG volatile *Target, LONG Value); +#ifdef __cplusplus +} +#endif /* __cplusplus */ +#endif /* _M_AMD64 */ +#pragma intrinsic (_InterlockedCompareExchange) +#pragma intrinsic (_InterlockedExchange) +#define interlockedcompareexchange _InterlockedCompareExchange +#define interlockedexchange _InterlockedExchange +#endif /* Win32 */ +#endif /* USE_LOCKS */ + +/* Declarations for bit scanning on win32 */ +#if defined(_MSC_VER) && _MSC_VER>=1300 && defined(DL_PLATFORM_WIN32) +#ifndef BitScanForward /* Try to avoid pulling in WinNT.h */ +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + unsigned char _BitScanForward(unsigned long *index, unsigned long mask); + unsigned char _BitScanReverse(unsigned long *index, unsigned long mask); +#ifdef __cplusplus +} +#endif /* __cplusplus */ + +#define BitScanForward _BitScanForward +#define BitScanReverse _BitScanReverse +#pragma intrinsic(_BitScanForward) +#pragma intrinsic(_BitScanReverse) +#endif /* BitScanForward */ +#endif /* defined(_MSC_VER) && _MSC_VER>=1300 */ + +#ifndef DL_PLATFORM_WIN32 +#ifndef malloc_getpagesize +# ifdef _SC_PAGESIZE /* some SVR4 systems omit an underscore */ +# ifndef _SC_PAGE_SIZE +# define _SC_PAGE_SIZE _SC_PAGESIZE +# endif +# endif +# ifdef _SC_PAGE_SIZE +# define malloc_getpagesize sysconf(_SC_PAGE_SIZE) +# else +# if defined(BSD) || defined(DGUX) || defined(HAVE_GETPAGESIZE) +extern size_t getpagesize(); +# define malloc_getpagesize getpagesize() +# else +# ifdef DL_PLATFORM_WIN32 /* use supplied emulation of getpagesize */ +# define malloc_getpagesize getpagesize() +# else +# ifndef LACKS_SYS_PARAM_H +# include +# endif +# ifdef EXEC_PAGESIZE +# define malloc_getpagesize EXEC_PAGESIZE +# else +# ifdef NBPG +# ifndef CLSIZE +# define malloc_getpagesize NBPG +# else +# define malloc_getpagesize (NBPG * CLSIZE) +# endif +# else +# ifdef NBPC +# define malloc_getpagesize NBPC +# else +# ifdef PAGESIZE +# define malloc_getpagesize PAGESIZE +# else /* just guess */ +# define malloc_getpagesize ((size_t)4096U) +# endif +# endif +# endif +# endif +# endif +# endif +# endif +#endif +#endif + + + +/* ------------------- size_t and alignment properties -------------------- */ + +/* The byte and bit size of a size_t */ +#define SIZE_T_SIZE (sizeof(size_t)) +#define SIZE_T_BITSIZE (sizeof(size_t) << 3) + +/* Some constants coerced to size_t */ +/* Annoying but necessary to avoid errors on some platforms */ +#define SIZE_T_ZERO ((size_t)0) +#define SIZE_T_ONE ((size_t)1) +#define SIZE_T_TWO ((size_t)2) +#define SIZE_T_FOUR ((size_t)4) +#define TWO_SIZE_T_SIZES (SIZE_T_SIZE<<1) +#define FOUR_SIZE_T_SIZES (SIZE_T_SIZE<<2) +#define SIX_SIZE_T_SIZES (FOUR_SIZE_T_SIZES+TWO_SIZE_T_SIZES) +#define HALF_MAX_SIZE_T (MAX_SIZE_T / 2U) + +/* The bit mask value corresponding to MALLOC_ALIGNMENT */ +#define CHUNK_ALIGN_MASK (MALLOC_ALIGNMENT - SIZE_T_ONE) + +/* True if address a has acceptable alignment */ +#define is_aligned(A) (((size_t)((A)) & (CHUNK_ALIGN_MASK)) == 0) + +/* the number of bytes to offset an address to align it */ +#define align_offset(A)\ + ((((size_t)(A) & CHUNK_ALIGN_MASK) == 0)? 0 :\ + ((MALLOC_ALIGNMENT - ((size_t)(A) & CHUNK_ALIGN_MASK)) & CHUNK_ALIGN_MASK)) + +/* -------------------------- MMAP preliminaries ------------------------- */ + +/* +If HAVE_MORECORE or HAVE_MMAP are false, we just define calls and +checks to fail so compiler optimizer can delete code rather than +using so many "#if"s. +*/ + + +/* MORECORE and MMAP must return MFAIL on failure */ +#define MFAIL ((void*)(MAX_SIZE_T)) +#define CMFAIL ((char*)(MFAIL)) /* defined for convenience */ + +#if HAVE_MMAP + +#if defined(_XBOX) || defined(X360) + +#elif defined(_PS3) || defined(__PS3__) || defined(SN_TARGET_PS3) + +#elif !defined(DL_PLATFORM_WIN32) + #define RAK_MUNMAP_DEFAULT(a, s) munmap((a), (s)) + #define MMAP_PROT (PROT_READ|PROT_WRITE) + #if !defined(MAP_ANONYMOUS) && defined(MAP_ANON) + #define MAP_ANONYMOUS MAP_ANON + #endif /* MAP_ANON */ + #ifdef MAP_ANONYMOUS + #define MMAP_FLAGS (MAP_PRIVATE|MAP_ANONYMOUS) + #define RAK_MMAP_DEFAULT(s) mmap(0, (s), MMAP_PROT, MMAP_FLAGS, -1, 0) + #else /* MAP_ANONYMOUS */ + /* + Nearly all versions of mmap support MAP_ANONYMOUS, so the following + is unlikely to be needed, but is supplied just in case. + */ + #define MMAP_FLAGS (MAP_PRIVATE) + static int dev_zero_fd = -1; /* Cached file descriptor for /dev/zero. */ + #define RAK_MMAP_DEFAULT(s) ((dev_zero_fd < 0) ? \ + (dev_zero_fd = open("/dev/zero", O_RDWR), \ + mmap(0, (s), MMAP_PROT, MMAP_FLAGS, dev_zero_fd, 0)) : \ + mmap(0, (s), MMAP_PROT, MMAP_FLAGS, dev_zero_fd, 0)) + #endif /* MAP_ANONYMOUS */ + + #define RAK_DIRECT_MMAP_DEFAULT(s) RAK_MMAP_DEFAULT(s) + +#else /* DL_PLATFORM_WIN32 */ + + /* Win32 MMAP via VirtualAlloc */ + static FORCEINLINE void* win32mmap(size_t size) { + void* ptr = VirtualAlloc(0, size, MEM_RESERVE|MEM_COMMIT, PAGE_READWRITE); + return (ptr != 0)? ptr: MFAIL; + } + + /* For direct MMAP, use MEM_TOP_DOWN to minimize interference */ + static FORCEINLINE void* win32direct_mmap(size_t size) { + void* ptr = VirtualAlloc(0, size, MEM_RESERVE|MEM_COMMIT|MEM_TOP_DOWN, + PAGE_READWRITE); + return (ptr != 0)? ptr: MFAIL; + } + + /* This function supports releasing coalesed segments */ + static FORCEINLINE int win32munmap(void* ptr, size_t size) { + MEMORY_BASIC_INFORMATION minfo; + char* cptr = (char*)ptr; + while (size) { + if (VirtualQuery(cptr, &minfo, sizeof(minfo)) == 0) + return -1; + if (minfo.BaseAddress != cptr || minfo.AllocationBase != cptr || + minfo.State != MEM_COMMIT || minfo.RegionSize > size) + return -1; + if (VirtualFree(cptr, 0, MEM_RELEASE) == 0) + return -1; + cptr += minfo.RegionSize; + size -= minfo.RegionSize; + } + return 0; + } + + #define RAK_MMAP_DEFAULT(s) win32mmap(s) + #define RAK_MUNMAP_DEFAULT(a, s) win32munmap((a), (s)) + #define RAK_DIRECT_MMAP_DEFAULT(s) win32direct_mmap(s) +#endif /* DL_PLATFORM_WIN32 */ +#endif /* HAVE_MMAP */ + +#if HAVE_MREMAP +#ifndef DL_PLATFORM_WIN32 +#define MREMAP_DEFAULT(addr, osz, nsz, mv) mremap((addr), (osz), (nsz), (mv)) +#endif /* DL_PLATFORM_WIN32 */ +#endif /* HAVE_MREMAP */ + + +/** +* Define CALL_MORECORE +*/ +#if HAVE_MORECORE +#ifdef MORECORE +#define CALL_MORECORE(S) MORECORE(S) +#else /* MORECORE */ +#define CALL_MORECORE(S) MORECORE_DEFAULT(S) +#endif /* MORECORE */ +#else /* HAVE_MORECORE */ +#define CALL_MORECORE(S) MFAIL +#endif /* HAVE_MORECORE */ + +/** +* Define CALL_MMAP/CALL_MUNMAP/CALL_DIRECT_MMAP +*/ +#if HAVE_MMAP +#define USE_MMAP_BIT (SIZE_T_ONE) + +#ifdef MMAP +#define CALL_MMAP(s) MMAP(s) +#else /* MMAP */ +#define CALL_MMAP(s) RAK_MMAP_DEFAULT(s) +#endif /* MMAP */ +#ifdef MUNMAP +#define CALL_MUNMAP(a, s) MUNMAP((a), (s)) +#else /* MUNMAP */ +#define CALL_MUNMAP(a, s) RAK_MUNMAP_DEFAULT((a), (s)) +#endif /* MUNMAP */ +#ifdef DIRECT_MMAP +#define CALL_DIRECT_MMAP(s) DIRECT_MMAP(s) +#else /* DIRECT_MMAP */ +#define CALL_DIRECT_MMAP(s) RAK_DIRECT_MMAP_DEFAULT(s) +#endif /* DIRECT_MMAP */ +#else /* HAVE_MMAP */ +#define USE_MMAP_BIT (SIZE_T_ZERO) + +#define MMAP(s) MFAIL +#define MUNMAP(a, s) (-1) +#define DIRECT_MMAP(s) MFAIL +#define CALL_DIRECT_MMAP(s) DIRECT_MMAP(s) +#define CALL_MMAP(s) MMAP(s) +#define CALL_MUNMAP(a, s) MUNMAP((a), (s)) +#endif /* HAVE_MMAP */ + +/** +* Define CALL_MREMAP +*/ +#if HAVE_MMAP && HAVE_MREMAP +#ifdef MREMAP +#define CALL_MREMAP(addr, osz, nsz, mv) MREMAP((addr), (osz), (nsz), (mv)) +#else /* MREMAP */ +#define CALL_MREMAP(addr, osz, nsz, mv) MREMAP_DEFAULT((addr), (osz), (nsz), (mv)) +#endif /* MREMAP */ +#else /* HAVE_MMAP && HAVE_MREMAP */ +#define CALL_MREMAP(addr, osz, nsz, mv) MFAIL +#endif /* HAVE_MMAP && HAVE_MREMAP */ + +/* mstate bit set if continguous morecore disabled or failed */ +#define USE_NONCONTIGUOUS_BIT (4U) + +/* segment bit set in rak_create_mspace_with_base */ +#define EXTERN_BIT (8U) + + +#endif /* MALLOC_280_H */ + +#endif // _RAKNET_SUPPORT_DL_MALLOC diff --git a/RakNet/Sources/rijndael.cpp b/RakNet/Sources/rijndael.cpp new file mode 100644 index 0000000..d6a3376 --- /dev/null +++ b/RakNet/Sources/rijndael.cpp @@ -0,0 +1,797 @@ +// rijndael-alg-fst.c v2.0 August '99 +// Optimised ANSI C code +// authors: v1.0: Antoon Bosselaers +// v2.0: Vincent Rijmen + + +/* + * taken from the 'aescrypt' project: www.sf.net/projects/aescrypt + * See LICENSE-EST for the license applicable to this file + */ + + +// 14.Dec.2005 Cirilo: Removed silly hex keys; keys are now effectively unsigned char. + +// KevinJ - TODO - What the hell is __UNUS? It causes DevCPP not to compile. I don't know what this is for so I'm taking it out entirely +/* +#if (defined(__GNUC__) || defined(__GCCXML__)) +#define __UNUS __attribute__((unused)) +#else +*/ +#define __UNUS +//#endif + +#include +#include +#include +#include "Rijndael.h" + +// KevinJ - Added this to just generate a random initialization vector +#include "Rand.h" + +#define SC ((BC - 4) >> 1) + +#include "Rijndael-Boxes.h" + +static int ROUNDS; + +static word8 shifts[3][4][2] = { + { + {0, 0}, + {1, 3}, + {2, 2}, + {3, 1} + }, + + { + {0, 0}, + {1, 5}, + {2, 4}, + {3, 3} + }, + + { + {0, 0}, + {1, 7}, + {3, 5}, + {4, 4} + } +}; + +word8 mul(word8 a, word8 b) { + // multiply two elements of GF(2^m) + // needed for MixColumn and InvMixColumn + + if (a && b) + return Alogtable[(Logtable[a] + Logtable[b])%255]; + else + return 0; +} + +void KeyAddition(word8 a[4][4], word8 rk[4][4], word8 BC) { + // XOR corresponding text input and round key input bytes + int i, j; + + for(i = 0; i < BC; i++) + for(j = 0; j < 4; j++) + a[i][j] ^= rk[i][j]; +} + +void ShiftRow(word8 a[4][4], word8 d, word8 BC) { + // Row 0 remains unchanged + // The other three rows are shifted a variable amount + + word8 tmp[4]; + int i, j; + + for(i = 1; i < 4; i++) { + for(j = 0; j < BC; j++) + tmp[j] = a[(j + shifts[SC][i][d]) % BC][i]; + for(j = 0; j < BC; j++) + a[j][i] = tmp[j]; + } +} + +void Substitution(word8 a[4][4], word8 box[256], word8 BC) { + // Replace every byte of the input by the byte at that place + // in the nonlinear S-box + + int i, j; + + for(i = 0; i < BC; i++) + for(j = 0; j < 4; j++) + a[i][j] = box[a[i][j]] ; +} + +void MixColumn(word8 a[4][4], word8 BC) { + // Mix the four bytes of every column in a linear way + + word8 b[4][4]; + int i, j; + + for(j = 0; j < BC; j++) + for(i = 0; i < 4; i++) + b[j][i] = mul(2,a[j][i]) + ^ mul(3,a[j][(i + 1) % 4]) + ^ a[j][(i + 2) % 4] + ^ a[j][(i + 3) % 4]; + for(i = 0; i < 4; i++) + for(j = 0; j < BC; j++) + a[j][i] = b[j][i]; +} + +void InvMixColumn(word8 a[4][4], word8 BC) { + // Mix the four bytes of every column in a linear way + // This is the opposite operation of Mixcolumn + + int j; + + for(j = 0; j < BC; j++) + *((word32*)a[j]) = *((word32*)U1[a[j][0]]) + ^ *((word32*)U2[a[j][1]]) + ^ *((word32*)U3[a[j][2]]) + ^ *((word32*)U4[a[j][3]]); + + +} + +int rijndaelKeySched (word8 k[MAXKC][4], int keyBits __UNUS, word8 W[MAXROUNDS+1][4][4]) +{ + + (void) keyBits; + + // Calculate the necessary round keys + // The number of calculations depends on keyBits and blockBits + + int j, r, t, rconpointer = 0; + word8 tk[MAXKC][4]; + int KC = ROUNDS - 6; + + for(j = KC-1; j >= 0; j--) + *((word32*)tk[j]) = *((word32*)k[j]); + r = 0; + t = 0; + // copy values into round key array + for(j = 0; (j < KC) && (r < (ROUNDS+1)); ) { + for (; (j < KC) && (t < 4); j++, t++) + *((word32*)W[r][t]) = *((word32*)tk[j]); + if (t == 4) { + r++; + t = 0; + } + } + + while (r < (ROUNDS+1)) { // while not enough round key material calculated + // calculate new values + tk[0][0] ^= S[tk[KC-1][1]]; + tk[0][1] ^= S[tk[KC-1][2]]; + tk[0][2] ^= S[tk[KC-1][3]]; + tk[0][3] ^= S[tk[KC-1][0]]; + tk[0][0] ^= rcon[rconpointer++]; + + if (KC != 8) + for(j = 1; j < KC; j++) + *((word32*)tk[j]) ^= *((word32*)tk[j-1]); + else { + for(j = 1; j < KC/2; j++) + *((word32*)tk[j]) ^= *((word32*)tk[j-1]); + tk[KC/2][0] ^= S[tk[KC/2 - 1][0]]; + tk[KC/2][1] ^= S[tk[KC/2 - 1][1]]; + tk[KC/2][2] ^= S[tk[KC/2 - 1][2]]; + tk[KC/2][3] ^= S[tk[KC/2 - 1][3]]; + for(j = KC/2 + 1; j < KC; j++) + *((word32*)tk[j]) ^= *((word32*)tk[j-1]); + } + // copy values into round key array + for(j = 0; (j < KC) && (r < (ROUNDS+1)); ) { + for (; (j < KC) && (t < 4); j++, t++) + *((word32*)W[r][t]) = *((word32*)tk[j]); + if (t == 4) { + r++; + t = 0; + } + } + } + + return 0; +} + +int rijndaelKeyEnctoDec (int keyBits __UNUS, word8 W[MAXROUNDS+1][4][4]) +{ + (void) keyBits; + + int r; + + for (r = 1; r < ROUNDS; r++) { + InvMixColumn(W[r], 4); + } + return 0; +} + +int rijndaelEncrypt (word8 a[16], word8 b[16], word8 rk[MAXROUNDS+1][4][4]) +{ + // Encryption of one block. + + int r; + word8 temp[4][4]; + + *((word32*)temp[0]) = *((word32*)a) ^ *((word32*)rk[0][0]); + *((word32*)temp[1]) = *((word32*)(a+4)) ^ *((word32*)rk[0][1]); + *((word32*)temp[2]) = *((word32*)(a+8)) ^ *((word32*)rk[0][2]); + *((word32*)temp[3]) = *((word32*)(a+12)) ^ *((word32*)rk[0][3]); + *((word32*)b) = *((word32*)T1[temp[0][0]]) + ^ *((word32*)T2[temp[1][1]]) + ^ *((word32*)T3[temp[2][2]]) + ^ *((word32*)T4[temp[3][3]]); + *((word32*)(b+4)) = *((word32*)T1[temp[1][0]]) + ^ *((word32*)T2[temp[2][1]]) + ^ *((word32*)T3[temp[3][2]]) + ^ *((word32*)T4[temp[0][3]]); + *((word32*)(b+8)) = *((word32*)T1[temp[2][0]]) + ^ *((word32*)T2[temp[3][1]]) + ^ *((word32*)T3[temp[0][2]]) + ^ *((word32*)T4[temp[1][3]]); + *((word32*)(b+12)) = *((word32*)T1[temp[3][0]]) + ^ *((word32*)T2[temp[0][1]]) + ^ *((word32*)T3[temp[1][2]]) + ^ *((word32*)T4[temp[2][3]]); + for(r = 1; r < ROUNDS-1; r++) { + *((word32*)temp[0]) = *((word32*)b) ^ *((word32*)rk[r][0]); + *((word32*)temp[1]) = *((word32*)(b+4)) ^ *((word32*)rk[r][1]); + *((word32*)temp[2]) = *((word32*)(b+8)) ^ *((word32*)rk[r][2]); + *((word32*)temp[3]) = *((word32*)(b+12)) ^ *((word32*)rk[r][3]); + *((word32*)b) = *((word32*)T1[temp[0][0]]) + ^ *((word32*)T2[temp[1][1]]) + ^ *((word32*)T3[temp[2][2]]) + ^ *((word32*)T4[temp[3][3]]); + *((word32*)(b+4)) = *((word32*)T1[temp[1][0]]) + ^ *((word32*)T2[temp[2][1]]) + ^ *((word32*)T3[temp[3][2]]) + ^ *((word32*)T4[temp[0][3]]); + *((word32*)(b+8)) = *((word32*)T1[temp[2][0]]) + ^ *((word32*)T2[temp[3][1]]) + ^ *((word32*)T3[temp[0][2]]) + ^ *((word32*)T4[temp[1][3]]); + *((word32*)(b+12)) = *((word32*)T1[temp[3][0]]) + ^ *((word32*)T2[temp[0][1]]) + ^ *((word32*)T3[temp[1][2]]) + ^ *((word32*)T4[temp[2][3]]); + } + // last round is special + *((word32*)temp[0]) = *((word32*)b) ^ *((word32*)rk[ROUNDS-1][0]); + *((word32*)temp[1]) = *((word32*)(b+4)) ^ *((word32*)rk[ROUNDS-1][1]); + *((word32*)temp[2]) = *((word32*)(b+8)) ^ *((word32*)rk[ROUNDS-1][2]); + *((word32*)temp[3]) = *((word32*)(b+12)) ^ *((word32*)rk[ROUNDS-1][3]); + b[0] = T1[temp[0][0]][1]; + b[1] = T1[temp[1][1]][1]; + b[2] = T1[temp[2][2]][1]; + b[3] = T1[temp[3][3]][1]; + b[4] = T1[temp[1][0]][1]; + b[5] = T1[temp[2][1]][1]; + b[6] = T1[temp[3][2]][1]; + b[7] = T1[temp[0][3]][1]; + b[8] = T1[temp[2][0]][1]; + b[9] = T1[temp[3][1]][1]; + b[10] = T1[temp[0][2]][1]; + b[11] = T1[temp[1][3]][1]; + b[12] = T1[temp[3][0]][1]; + b[13] = T1[temp[0][1]][1]; + b[14] = T1[temp[1][2]][1]; + b[15] = T1[temp[2][3]][1]; + *((word32*)b) ^= *((word32*)rk[ROUNDS][0]); + *((word32*)(b+4)) ^= *((word32*)rk[ROUNDS][1]); + *((word32*)(b+8)) ^= *((word32*)rk[ROUNDS][2]); + *((word32*)(b+12)) ^= *((word32*)rk[ROUNDS][3]); + + return 0; +} + +int rijndaelEncryptRound (word8 a[4][4], + word8 rk[MAXROUNDS+1][4][4], int rounds) +// Encrypt only a certain number of rounds. +// Only used in the Intermediate Value Known Answer Test. + +{ + int r; + word8 temp[4][4]; + + + // make number of rounds sane + if (rounds > ROUNDS) rounds = ROUNDS; + + *((word32*)a[0]) = *((word32*)a[0]) ^ *((word32*)rk[0][0]); + *((word32*)a[1]) = *((word32*)a[1]) ^ *((word32*)rk[0][1]); + *((word32*)a[2]) = *((word32*)a[2]) ^ *((word32*)rk[0][2]); + *((word32*)a[3]) = *((word32*)a[3]) ^ *((word32*)rk[0][3]); + + for(r = 1; (r <= rounds) && (r < ROUNDS); r++) { + *((word32*)temp[0]) = *((word32*)T1[a[0][0]]) + ^ *((word32*)T2[a[1][1]]) + ^ *((word32*)T3[a[2][2]]) + ^ *((word32*)T4[a[3][3]]); + *((word32*)temp[1]) = *((word32*)T1[a[1][0]]) + ^ *((word32*)T2[a[2][1]]) + ^ *((word32*)T3[a[3][2]]) + ^ *((word32*)T4[a[0][3]]); + *((word32*)temp[2]) = *((word32*)T1[a[2][0]]) + ^ *((word32*)T2[a[3][1]]) + ^ *((word32*)T3[a[0][2]]) + ^ *((word32*)T4[a[1][3]]); + *((word32*)temp[3]) = *((word32*)T1[a[3][0]]) + ^ *((word32*)T2[a[0][1]]) + ^ *((word32*)T3[a[1][2]]) + ^ *((word32*)T4[a[2][3]]); + *((word32*)a[0]) = *((word32*)temp[0]) ^ *((word32*)rk[r][0]); + *((word32*)a[1]) = *((word32*)temp[1]) ^ *((word32*)rk[r][1]); + *((word32*)a[2]) = *((word32*)temp[2]) ^ *((word32*)rk[r][2]); + *((word32*)a[3]) = *((word32*)temp[3]) ^ *((word32*)rk[r][3]); + } + if (rounds == ROUNDS) { + // last round is special + temp[0][0] = T1[a[0][0]][1]; + temp[0][1] = T1[a[1][1]][1]; + temp[0][2] = T1[a[2][2]][1]; + temp[0][3] = T1[a[3][3]][1]; + temp[1][0] = T1[a[1][0]][1]; + temp[1][1] = T1[a[2][1]][1]; + temp[1][2] = T1[a[3][2]][1]; + temp[1][3] = T1[a[0][3]][1]; + temp[2][0] = T1[a[2][0]][1]; + temp[2][1] = T1[a[3][1]][1]; + temp[2][2] = T1[a[0][2]][1]; + temp[2][3] = T1[a[1][3]][1]; + temp[3][0] = T1[a[3][0]][1]; + temp[3][1] = T1[a[0][1]][1]; + temp[3][2] = T1[a[1][2]][1]; + temp[3][3] = T1[a[2][3]][1]; + *((word32*)a[0]) = *((word32*)temp[0]) ^ *((word32*)rk[ROUNDS][0]); + *((word32*)a[1]) = *((word32*)temp[1]) ^ *((word32*)rk[ROUNDS][1]); + *((word32*)a[2]) = *((word32*)temp[2]) ^ *((word32*)rk[ROUNDS][2]); + *((word32*)a[3]) = *((word32*)temp[3]) ^ *((word32*)rk[ROUNDS][3]); + } + + return 0; +} + + +int rijndaelDecrypt (word8 a[16], word8 b[16], word8 rk[MAXROUNDS+1][4][4]) +{ + int r; + word8 temp[4][4]; + + + *((word32*)temp[0]) = *((word32*)a) ^ *((word32*)rk[ROUNDS][0]); + *((word32*)temp[1]) = *((word32*)(a+4)) ^ *((word32*)rk[ROUNDS][1]); + *((word32*)temp[2]) = *((word32*)(a+8)) ^ *((word32*)rk[ROUNDS][2]); + *((word32*)temp[3]) = *((word32*)(a+12)) ^ *((word32*)rk[ROUNDS][3]); + *((word32*)b) = *((word32*)T5[temp[0][0]]) + ^ *((word32*)T6[temp[3][1]]) + ^ *((word32*)T7[temp[2][2]]) + ^ *((word32*)T8[temp[1][3]]); + *((word32*)(b+4)) = *((word32*)T5[temp[1][0]]) + ^ *((word32*)T6[temp[0][1]]) + ^ *((word32*)T7[temp[3][2]]) + ^ *((word32*)T8[temp[2][3]]); + *((word32*)(b+8)) = *((word32*)T5[temp[2][0]]) + ^ *((word32*)T6[temp[1][1]]) + ^ *((word32*)T7[temp[0][2]]) + ^ *((word32*)T8[temp[3][3]]); + *((word32*)(b+12)) = *((word32*)T5[temp[3][0]]) + ^ *((word32*)T6[temp[2][1]]) + ^ *((word32*)T7[temp[1][2]]) + ^ *((word32*)T8[temp[0][3]]); + for(r = ROUNDS-1; r > 1; r--) { + *((word32*)temp[0]) = *((word32*)b) ^ *((word32*)rk[r][0]); + *((word32*)temp[1]) = *((word32*)(b+4)) ^ *((word32*)rk[r][1]); + *((word32*)temp[2]) = *((word32*)(b+8)) ^ *((word32*)rk[r][2]); + *((word32*)temp[3]) = *((word32*)(b+12)) ^ *((word32*)rk[r][3]); + *((word32*)b) = *((word32*)T5[temp[0][0]]) + ^ *((word32*)T6[temp[3][1]]) + ^ *((word32*)T7[temp[2][2]]) + ^ *((word32*)T8[temp[1][3]]); + *((word32*)(b+4)) = *((word32*)T5[temp[1][0]]) + ^ *((word32*)T6[temp[0][1]]) + ^ *((word32*)T7[temp[3][2]]) + ^ *((word32*)T8[temp[2][3]]); + *((word32*)(b+8)) = *((word32*)T5[temp[2][0]]) + ^ *((word32*)T6[temp[1][1]]) + ^ *((word32*)T7[temp[0][2]]) + ^ *((word32*)T8[temp[3][3]]); + *((word32*)(b+12)) = *((word32*)T5[temp[3][0]]) + ^ *((word32*)T6[temp[2][1]]) + ^ *((word32*)T7[temp[1][2]]) + ^ *((word32*)T8[temp[0][3]]); + } + // last round is special + *((word32*)temp[0]) = *((word32*)b) ^ *((word32*)rk[1][0]); + *((word32*)temp[1]) = *((word32*)(b+4)) ^ *((word32*)rk[1][1]); + *((word32*)temp[2]) = *((word32*)(b+8)) ^ *((word32*)rk[1][2]); + *((word32*)temp[3]) = *((word32*)(b+12)) ^ *((word32*)rk[1][3]); + b[0] = S5[temp[0][0]]; + b[1] = S5[temp[3][1]]; + b[2] = S5[temp[2][2]]; + b[3] = S5[temp[1][3]]; + b[4] = S5[temp[1][0]]; + b[5] = S5[temp[0][1]]; + b[6] = S5[temp[3][2]]; + b[7] = S5[temp[2][3]]; + b[8] = S5[temp[2][0]]; + b[9] = S5[temp[1][1]]; + b[10] = S5[temp[0][2]]; + b[11] = S5[temp[3][3]]; + b[12] = S5[temp[3][0]]; + b[13] = S5[temp[2][1]]; + b[14] = S5[temp[1][2]]; + b[15] = S5[temp[0][3]]; + *((word32*)b) ^= *((word32*)rk[0][0]); + *((word32*)(b+4)) ^= *((word32*)rk[0][1]); + *((word32*)(b+8)) ^= *((word32*)rk[0][2]); + *((word32*)(b+12)) ^= *((word32*)rk[0][3]); + + return 0; +} + + +int rijndaelDecryptRound (word8 a[4][4], + word8 rk[MAXROUNDS+1][4][4], int rounds) +// Decrypt only a certain number of rounds. +// Only used in the Intermediate Value Known Answer Test. +// Operations rearranged such that the intermediate values +// of decryption correspond with the intermediate values +// of encryption. + +{ + int r; + + + // make number of rounds sane + if (rounds > ROUNDS) rounds = ROUNDS; + + // First the special round: + // without InvMixColumn + // with extra KeyAddition + + KeyAddition(a,rk[ROUNDS],4); + Substitution(a,Si,4); + ShiftRow(a,1,4); + + // ROUNDS-1 ordinary rounds + + for(r = ROUNDS-1; r > rounds; r--) { + KeyAddition(a,rk[r],4); + InvMixColumn(a,4); + Substitution(a,Si,4); + ShiftRow(a,1,4); + } + + if (rounds == 0) { + // End with the extra key addition + + KeyAddition(a,rk[0],4); + } + + return 0; +} + +/*** End Rijndael algorithm, Begin the AES Interface ***/ + + +int makeKey(keyInstance *key, BYTE direction, int keyByteLen, char *keyMaterial) +{ + word8 k[MAXKC][4]; + int i; + int keyLen = keyByteLen*8; + + if (key == NULL) { + return BAD_KEY_INSTANCE; + } + + if ((direction == DIR_ENCRYPT) || (direction == DIR_DECRYPT)) { + key->direction = direction; + } else { + return BAD_KEY_DIR; + } + + if ((keyLen == 128) || (keyLen == 192) || (keyLen == 256)) { + key->keyLen = keyLen; + } else { + return BAD_KEY_MAT; + } + + if ( keyMaterial ) { + strncpy(key->keyMaterial, keyMaterial, keyByteLen); + } else { + return BAD_KEY_MAT; + } + + ROUNDS = keyLen/32 + 6; + + // initialize key schedule: + for(i = 0; i < key->keyLen/8; i++) { + k[i / 4][i % 4] = (word8) key->keyMaterial[i]; + } + rijndaelKeySched (k, key->keyLen, key->keySched); + if (direction == DIR_DECRYPT) + rijndaelKeyEnctoDec (key->keyLen, key->keySched); + + return TRUE; +} + +int cipherInit(cipherInstance *cipher, BYTE mode, char *IV) +{ + int i; + + if ((mode == MODE_ECB) || (mode == MODE_CBC) || (mode == MODE_CFB1)) { + cipher->mode = mode; + } else { + return BAD_CIPHER_MODE; + } + + + if (IV != NULL) { + for(i = 0; i < 16; i++) cipher->IV[i] = IV[i]; + } + else + { + // KevinJ - Added this to just generate a random initialization vector + for(i = 0; i < 16; i++) + cipher->IV[i]=(BYTE)randomMT(); + } + + return TRUE; +} + + +int blockEncrypt(cipherInstance *cipher, + keyInstance *key, BYTE *input, int inputByteLen, BYTE *outBuffer) +{ + int i, k, numBlocks; + word8 block[16], iv[4][4]; + int inputLen = inputByteLen*8; + + if (cipher == NULL || + key == NULL || + key->direction == DIR_DECRYPT) { + return BAD_CIPHER_STATE; + } + + + numBlocks = inputLen/128; + + switch (cipher->mode) { + case MODE_ECB: + for (i = numBlocks; i > 0; i--) { + + rijndaelEncrypt (input, outBuffer, key->keySched); + + input += 16; + outBuffer += 16; + } + break; + + case MODE_CBC: +#if STRICT_ALIGN + memcpy(block,cipher->IV,16); +#else + *((word32*)block) = *((word32*)(cipher->IV)); + *((word32*)(block+4)) = *((word32*)(cipher->IV+4)); + *((word32*)(block+8)) = *((word32*)(cipher->IV+8)); + *((word32*)(block+12)) = *((word32*)(cipher->IV+12)); +#endif + + for (i = numBlocks; i > 0; i--) { + *((word32*)block) ^= *((word32*)(input)); + *((word32*)(block+4)) ^= *((word32*)(input+4)); + *((word32*)(block+8)) ^= *((word32*)(input+8)); + *((word32*)(block+12)) ^= *((word32*)(input+12)); + + rijndaelEncrypt (block, outBuffer, key->keySched); + + input += 16; + outBuffer += 16; + } + break; + + case MODE_CFB1: +#if STRICT_ALIGN + memcpy(iv,cipher->IV,16); +#else + *((word32*)iv[0]) = *((word32*)(cipher->IV)); + *((word32*)iv[1]) = *((word32*)(cipher->IV+4)); + *((word32*)iv[2]) = *((word32*)(cipher->IV+8)); + *((word32*)iv[3]) = *((word32*)(cipher->IV+12)); +#endif + for (i = numBlocks; i > 0; i--) { + for (k = 0; k < 128; k++) { + *((word32*)block) = *((word32*)iv[0]); + *((word32*)(block+4)) = *((word32*)iv[1]); + *((word32*)(block+8)) = *((word32*)iv[2]); + *((word32*)(block+12)) = *((word32*)iv[3]); + + rijndaelEncrypt (block, block, key->keySched); + outBuffer[k/8] ^= (block[0] & 0x80) >> (k & 7); + iv[0][0] = (iv[0][0] << 1) | (iv[0][1] >> 7); + iv[0][1] = (iv[0][1] << 1) | (iv[0][2] >> 7); + iv[0][2] = (iv[0][2] << 1) | (iv[0][3] >> 7); + iv[0][3] = (iv[0][3] << 1) | (iv[1][0] >> 7); + iv[1][0] = (iv[1][0] << 1) | (iv[1][1] >> 7); + iv[1][1] = (iv[1][1] << 1) | (iv[1][2] >> 7); + iv[1][2] = (iv[1][2] << 1) | (iv[1][3] >> 7); + iv[1][3] = (iv[1][3] << 1) | (iv[2][0] >> 7); + iv[2][0] = (iv[2][0] << 1) | (iv[2][1] >> 7); + iv[2][1] = (iv[2][1] << 1) | (iv[2][2] >> 7); + iv[2][2] = (iv[2][2] << 1) | (iv[2][3] >> 7); + iv[2][3] = (iv[2][3] << 1) | (iv[3][0] >> 7); + iv[3][0] = (iv[3][0] << 1) | (iv[3][1] >> 7); + iv[3][1] = (iv[3][1] << 1) | (iv[3][2] >> 7); + iv[3][2] = (iv[3][2] << 1) | (iv[3][3] >> 7); + iv[3][3] = (word8)((iv[3][3] << 1) | (outBuffer[k/8] >> (7-(k&7))) & 1); + } + } + break; + + default: + return BAD_CIPHER_STATE; + } + + return numBlocks*128; +} + +int blockDecrypt(cipherInstance *cipher, + keyInstance *key, BYTE *input, int inputByteLen, BYTE *outBuffer) +{ + int i, k, numBlocks; + word8 block[16], iv[4][4]; + int inputLen = inputByteLen*8; + + if (cipher == NULL || + key == NULL || + cipher->mode != MODE_CFB1 && key->direction == DIR_ENCRYPT) { + return BAD_CIPHER_STATE; + } + + + numBlocks = inputLen/128; + + switch (cipher->mode) { + case MODE_ECB: + for (i = numBlocks; i > 0; i--) { + + rijndaelDecrypt (input, outBuffer, key->keySched); + + input += 16; + outBuffer += 16; + + } + break; + + case MODE_CBC: + // first block + + rijndaelDecrypt (input, block, key->keySched); +#if STRICT_ALIGN + memcpy(outBuffer,cipher->IV,16); + *((word32*)(outBuffer)) ^= *((word32*)block); + *((word32*)(outBuffer+4)) ^= *((word32*)(block+4)); + *((word32*)(outBuffer+8)) ^= *((word32*)(block+8)); + *((word32*)(outBuffer+12)) ^= *((word32*)(block+12)); +#else + *((word32*)(outBuffer)) = *((word32*)block) ^ *((word32*)(cipher->IV)); + *((word32*)(outBuffer+4)) = *((word32*)(block+4)) ^ *((word32*)(cipher->IV+4)); + *((word32*)(outBuffer+8)) = *((word32*)(block+8)) ^ *((word32*)(cipher->IV+8)); + *((word32*)(outBuffer+12)) = *((word32*)(block+12)) ^ *((word32*)(cipher->IV+12)); +#endif + + // next blocks + for (i = numBlocks-1; i > 0; i--) { + + rijndaelDecrypt (input, block, key->keySched); + + *((word32*)(outBuffer+16)) = *((word32*)block) ^ + *((word32*)(input-16)); + *((word32*)(outBuffer+20)) = *((word32*)(block+4)) ^ + *((word32*)(input-12)); + *((word32*)(outBuffer+24)) = *((word32*)(block+8)) ^ + *((word32*)(input-8)); + *((word32*)(outBuffer+28)) = *((word32*)(block+12)) ^ + *((word32*)(input-4)); + + input += 16; + outBuffer += 16; + } + break; + + case MODE_CFB1: +#if STRICT_ALIGN + memcpy(iv,cipher->IV,16); +#else + *((word32*)iv[0]) = *((word32*)(cipher->IV)); + *((word32*)iv[1]) = *((word32*)(cipher->IV+4)); + *((word32*)iv[2]) = *((word32*)(cipher->IV+8)); + *((word32*)iv[3]) = *((word32*)(cipher->IV+12)); +#endif + for (i = numBlocks; i > 0; i--) { + for (k = 0; k < 128; k++) { + *((word32*)block) = *((word32*)iv[0]); + *((word32*)(block+4)) = *((word32*)iv[1]); + *((word32*)(block+8)) = *((word32*)iv[2]); + *((word32*)(block+12)) = *((word32*)iv[3]); + + rijndaelEncrypt (block, block, key->keySched); + iv[0][0] = (iv[0][0] << 1) | (iv[0][1] >> 7); + iv[0][1] = (iv[0][1] << 1) | (iv[0][2] >> 7); + iv[0][2] = (iv[0][2] << 1) | (iv[0][3] >> 7); + iv[0][3] = (iv[0][3] << 1) | (iv[1][0] >> 7); + iv[1][0] = (iv[1][0] << 1) | (iv[1][1] >> 7); + iv[1][1] = (iv[1][1] << 1) | (iv[1][2] >> 7); + iv[1][2] = (iv[1][2] << 1) | (iv[1][3] >> 7); + iv[1][3] = (iv[1][3] << 1) | (iv[2][0] >> 7); + iv[2][0] = (iv[2][0] << 1) | (iv[2][1] >> 7); + iv[2][1] = (iv[2][1] << 1) | (iv[2][2] >> 7); + iv[2][2] = (iv[2][2] << 1) | (iv[2][3] >> 7); + iv[2][3] = (iv[2][3] << 1) | (iv[3][0] >> 7); + iv[3][0] = (iv[3][0] << 1) | (iv[3][1] >> 7); + iv[3][1] = (iv[3][1] << 1) | (iv[3][2] >> 7); + iv[3][2] = (iv[3][2] << 1) | (iv[3][3] >> 7); + iv[3][3] = (word8)((iv[3][3] << 1) | (input[k/8] >> (7-(k&7))) & 1); + outBuffer[k/8] ^= (block[0] & 0x80) >> (k & 7); + } + } + break; + + default: + return BAD_CIPHER_STATE; + } + + return numBlocks*128; +} + + +/** + * cipherUpdateRounds: + * + * Encrypts/Decrypts exactly one full block a specified number of rounds. + * Only used in the Intermediate Value Known Answer Test. + * + * Returns: + * TRUE - on success + * BAD_CIPHER_STATE - cipher in bad state (e.g., not initialized) + */ + +int cipherUpdateRounds(cipherInstance *cipher, + keyInstance *key, BYTE *input, int inputLen __UNUS, BYTE *outBuffer, int rounds) +{ + (void) inputLen; + + int j; + word8 block[4][4]; + + if (cipher == NULL || + key == NULL) { + return BAD_CIPHER_STATE; + } + + for (j = 3; j >= 0; j--) { + // parse input stream into rectangular array + *((word32*)block[j]) = *((word32*)(input+4*j)); + } + + switch (key->direction) { + case DIR_ENCRYPT: + rijndaelEncryptRound (block, key->keySched, rounds); + break; + + case DIR_DECRYPT: + rijndaelDecryptRound (block, key->keySched, rounds); + break; + + default: return BAD_KEY_DIR; + } + + for (j = 3; j >= 0; j--) { + // parse rectangular array into output ciphertext bytes + *((word32*)(outBuffer+4*j)) = *((word32*)block[j]); + } + + return TRUE; +} diff --git a/VisualStudio/ProxyServer.sln b/VisualStudio/ProxyServer.sln new file mode 100644 index 0000000..e062591 --- /dev/null +++ b/VisualStudio/ProxyServer.sln @@ -0,0 +1,22 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 15 +VisualStudioVersion = 15.0.26430.12 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "ProxyServer", "ProxyServer.vcxproj", "{DBDB145C-9663-4F33-A8C2-C67927FA3EF2}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Win32 = Debug|Win32 + Release|Win32 = Release|Win32 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {DBDB145C-9663-4F33-A8C2-C67927FA3EF2}.Debug|Win32.ActiveCfg = Debug|Win32 + {DBDB145C-9663-4F33-A8C2-C67927FA3EF2}.Debug|Win32.Build.0 = Debug|Win32 + {DBDB145C-9663-4F33-A8C2-C67927FA3EF2}.Release|Win32.ActiveCfg = Release|Win32 + {DBDB145C-9663-4F33-A8C2-C67927FA3EF2}.Release|Win32.Build.0 = Release|Win32 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal diff --git a/VisualStudio/ProxyServer.vcproj b/VisualStudio/ProxyServer.vcproj new file mode 100644 index 0000000..a108d87 --- /dev/null +++ b/VisualStudio/ProxyServer.vcproj @@ -0,0 +1,1061 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/VisualStudio/ProxyServer.vcxproj b/VisualStudio/ProxyServer.vcxproj new file mode 100644 index 0000000..83cce45 --- /dev/null +++ b/VisualStudio/ProxyServer.vcxproj @@ -0,0 +1,319 @@ + + + + + Debug + Win32 + + + Release + Win32 + + + + {DBDB145C-9663-4F33-A8C2-C67927FA3EF2} + ProxyServer + Win32Proj + 10.0.10586.0 + + + + Application + v141 + Unicode + true + + + Application + v141 + Unicode + + + + + + + + + + + + + <_ProjectFileVersion>15.0.26419.1 + + + $(SolutionDir)$(Configuration)\ + $(Configuration)\ + true + + + $(SolutionDir)$(Configuration)\ + $(Configuration)\ + false + + + + Disabled + ../RakNet/Sources;../Common;%(AdditionalIncludeDirectories) + WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions) + true + EnableFastChecks + MultiThreadedDebug + + Level3 + EditAndContinue + + + ws2_32.lib;%(AdditionalDependencies) + true + Console + MachineX86 + + + + + MaxSpeed + true + ../RakNet/Sources;../Common;%(AdditionalIncludeDirectories) + WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions) + MultiThreadedDLL + true + + Level3 + ProgramDatabase + + + ws2_32.lib;%(AdditionalDependencies) + true + Console + true + true + MachineX86 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/VisualStudio/ProxyServer.vcxproj.filters b/VisualStudio/ProxyServer.vcxproj.filters new file mode 100644 index 0000000..177ce42 --- /dev/null +++ b/VisualStudio/ProxyServer.vcxproj.filters @@ -0,0 +1,673 @@ + + + + + {4FC737F1-C7A5-4376-A066-2A32D752A2FF} + cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx + + + {cab031ee-3054-4ae0-95e5-ccc980ff2f67} + + + {f7d1c6d3-a34a-4047-9a96-0f1ae2d14307} + + + + + Source Files + + + Common + + + Common + + + RakNet + + + RakNet + + + RakNet + + + RakNet + + + RakNet + + + RakNet + + + RakNet + + + RakNet + + + RakNet + + + RakNet + + + RakNet + + + RakNet + + + RakNet + + + RakNet + + + RakNet + + + RakNet + + + RakNet + + + RakNet + + + RakNet + + + RakNet + + + RakNet + + + RakNet + + + RakNet + + + RakNet + + + RakNet + + + RakNet + + + RakNet + + + RakNet + + + RakNet + + + RakNet + + + RakNet + + + RakNet + + + RakNet + + + RakNet + + + RakNet + + + RakNet + + + RakNet + + + RakNet + + + RakNet + + + RakNet + + + RakNet + + + RakNet + + + RakNet + + + RakNet + + + RakNet + + + RakNet + + + RakNet + + + RakNet + + + RakNet + + + RakNet + + + RakNet + + + RakNet + + + RakNet + + + RakNet + + + RakNet + + + RakNet + + + RakNet + + + RakNet + + + RakNet + + + RakNet + + + RakNet + + + RakNet + + + RakNet + + + RakNet + + + RakNet + + + RakNet + + + RakNet + + + RakNet + + + RakNet + + + RakNet + + + RakNet + + + RakNet + + + RakNet + + + RakNet + + + RakNet + + + RakNet + + + RakNet + + + RakNet + + + RakNet + + + + + Source Files + + + Common + + + Common + + + RakNet + + + RakNet + + + RakNet + + + RakNet + + + RakNet + + + RakNet + + + RakNet + + + RakNet + + + RakNet + + + RakNet + + + RakNet + + + RakNet + + + RakNet + + + RakNet + + + RakNet + + + RakNet + + + RakNet + + + RakNet + + + RakNet + + + RakNet + + + RakNet + + + RakNet + + + RakNet + + + RakNet + + + RakNet + + + RakNet + + + RakNet + + + RakNet + + + RakNet + + + RakNet + + + RakNet + + + RakNet + + + RakNet + + + RakNet + + + RakNet + + + RakNet + + + RakNet + + + RakNet + + + RakNet + + + RakNet + + + RakNet + + + RakNet + + + RakNet + + + RakNet + + + RakNet + + + RakNet + + + RakNet + + + RakNet + + + RakNet + + + RakNet + + + RakNet + + + RakNet + + + RakNet + + + RakNet + + + RakNet + + + RakNet + + + RakNet + + + RakNet + + + RakNet + + + RakNet + + + RakNet + + + RakNet + + + RakNet + + + RakNet + + + RakNet + + + RakNet + + + RakNet + + + RakNet + + + RakNet + + + RakNet + + + RakNet + + + RakNet + + + RakNet + + + RakNet + + + RakNet + + + RakNet + + + RakNet + + + RakNet + + + RakNet + + + RakNet + + + RakNet + + + RakNet + + + RakNet + + + RakNet + + + RakNet + + + RakNet + + + RakNet + + + RakNet + + + RakNet + + + RakNet + + + RakNet + + + RakNet + + + RakNet + + + RakNet + + + RakNet + + + RakNet + + + RakNet + + + RakNet + + + RakNet + + + RakNet + + + RakNet + + + RakNet + + + RakNet + + + RakNet + + + RakNet + + + RakNet + + + RakNet + + + RakNet + + + RakNet + + + RakNet + + + RakNet + + + RakNet + + + RakNet + + + RakNet + + + RakNet + + + RakNet + + + RakNet + + + RakNet + + + RakNet + + + RakNet + + + RakNet + + + RakNet + + + RakNet + + + RakNet + + + RakNet + + + RakNet + + + RakNet + + + RakNet + + + RakNet + + + RakNet + + + RakNet + + + RakNet + + + RakNet + + + \ No newline at end of file diff --git a/config/unity-proxyserver b/config/unity-proxyserver new file mode 100644 index 0000000..005cfe2 --- /dev/null +++ b/config/unity-proxyserver @@ -0,0 +1,63 @@ +#! /bin/sh +# +# unity-proxyserver +# +# Author: Unity Technologies +# +# chkconfig: 2345 98 02 +# description: A proxy server for tunnelling traffic between +# Unity game servers and clients. +# processname: ProxyServer +# pidfile: /home/unityserver/Production/ProxyServer/ProxyServer.pid +# config: /etc/monitrc +# +# ------------------------------------ +# Change this to suit your setup + +BINARYNAME=ProxyServer +OPTIONS="-f 1.2.3.4:50005" + +HOMEDIR=/home/unityserver/Production/ProxyServer +COMMAND="./${BINARYNAME} -d ${OPTIONS}" +USERNAME=unityserver + +# ------------------------------------ + +RETVAL=0 + +# See how we were called. +case "$1" in + start) + [ -f ${HOMEDIR}/${BINARYNAME}.pid ] && echo $BINARYNAME already running && exit 0 + echo -n "Starting $BINARYNAME: " + su $USERNAME -c "cd ${HOMEDIR} && ${COMMAND}" + RETVAL=$? + echo + [ $RETVAL = 0 ] && touch /var/lock/subsys/$BINARYNAME + ;; + stop) + echo -n "Stopping $BINARYNAME: " + cat ${HOMEDIR}/${BINARYNAME}.pid | xargs kill + RETVAL=$? + echo + [ $RETVAL = 0 ] && rm -f /var/lock/subsys/BINARYNAME + ;; + restart) + $0 stop + sleep 5 + $0 start + RETVAL=$? + ;; + condrestart) + [ -e /var/lock/subsys/$BINARYNAME ] && $0 restart + ;; + status) + status $BINARYNAME + RETVAL=$? + ;; + *) + echo "Usage: $0 {start|stop|restart|condrestart|status}" + exit 1 +esac + +exit $RETVAL diff --git a/xcode/ProxyServer.xcodeproj/project.pbxproj b/xcode/ProxyServer.xcodeproj/project.pbxproj new file mode 100644 index 0000000..c727c72 --- /dev/null +++ b/xcode/ProxyServer.xcodeproj/project.pbxproj @@ -0,0 +1,457 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 42; + objects = { + +/* Begin PBXBuildFile section */ + 6458A517121BED4800D40A32 /* _FindFirst.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 6458A50F121BED4800D40A32 /* _FindFirst.cpp */; }; + 6458A518121BED4800D40A32 /* BigInt.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 6458A511121BED4800D40A32 /* BigInt.cpp */; }; + 6458A519121BED4800D40A32 /* BitStream.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 6458A513121BED4800D40A32 /* BitStream.cpp */; }; + 6458A51A121BED4800D40A32 /* CCRakNetUDT.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 6458A515121BED4800D40A32 /* CCRakNetUDT.cpp */; }; + 6458A53C121BEDBA00D40A32 /* GetTime.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 6458A523121BEDBA00D40A32 /* GetTime.cpp */; }; + 6458A53D121BEDBA00D40A32 /* Itoa.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 6458A525121BEDBA00D40A32 /* Itoa.cpp */; }; + 6458A53E121BEDBA00D40A32 /* NatPunchthroughClient.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 6458A527121BEDBA00D40A32 /* NatPunchthroughClient.cpp */; }; + 6458A53F121BEDBA00D40A32 /* PluginInterface2.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 6458A529121BEDBA00D40A32 /* PluginInterface2.cpp */; }; + 6458A540121BEDBA00D40A32 /* RakMemoryOverride.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 6458A52B121BEDBA00D40A32 /* RakMemoryOverride.cpp */; }; + 6458A541121BEDBA00D40A32 /* RakNetSocket.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 6458A52D121BEDBA00D40A32 /* RakNetSocket.cpp */; }; + 6458A542121BEDBA00D40A32 /* RakPeer.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 6458A52F121BEDBA00D40A32 /* RakPeer.cpp */; }; + 6458A543121BEDBA00D40A32 /* RakString.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 6458A532121BEDBA00D40A32 /* RakString.cpp */; }; + 6458A544121BEDBA00D40A32 /* RakThread.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 6458A534121BEDBA00D40A32 /* RakThread.cpp */; }; + 6458A545121BEDBA00D40A32 /* RSACrypt.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 6458A536121BEDBA00D40A32 /* RSACrypt.cpp */; }; + 6458A546121BEDBA00D40A32 /* SignaledEvent.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 6458A538121BEDBA00D40A32 /* SignaledEvent.cpp */; }; + 6458A547121BEDBA00D40A32 /* SuperFastHash.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 6458A53A121BEDBA00D40A32 /* SuperFastHash.cpp */; }; + 6458A554121BEE1800D40A32 /* RakNetTypes.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 6458A552121BEE1800D40A32 /* RakNetTypes.cpp */; }; + 6458A559121BEE3E00D40A32 /* SHA1.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 6458A557121BEE3E00D40A32 /* SHA1.cpp */; }; + 6458A55C121BEE5000D40A32 /* RakSleep.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 6458A55A121BEE5000D40A32 /* RakSleep.cpp */; }; + 6458A565121BEEAB00D40A32 /* Rand.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 6458A55F121BEEAB00D40A32 /* Rand.cpp */; }; + 6458A566121BEEAB00D40A32 /* SocketLayer.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 6458A561121BEEAB00D40A32 /* SocketLayer.cpp */; }; + 6458A567121BEEAB00D40A32 /* StringCompressor.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 6458A563121BEEAB00D40A32 /* StringCompressor.cpp */; }; + 6458A570121BEEE400D40A32 /* RakNetworkFactory.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 6458A56A121BEEE400D40A32 /* RakNetworkFactory.cpp */; }; + 6458A571121BEEE400D40A32 /* ReliabilityLayer.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 6458A56C121BEEE400D40A32 /* ReliabilityLayer.cpp */; }; + 6458A572121BEEE400D40A32 /* RPCMap.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 6458A56E121BEEE400D40A32 /* RPCMap.cpp */; }; + 6458A57B121BEF5100D40A32 /* DataBlockEncryptor.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 6458A575121BEF5100D40A32 /* DataBlockEncryptor.cpp */; }; + 6458A57C121BEF5100D40A32 /* NetworkIDManager.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 6458A577121BEF5100D40A32 /* NetworkIDManager.cpp */; }; + 6458A57D121BEF5100D40A32 /* SimpleMutex.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 6458A579121BEF5100D40A32 /* SimpleMutex.cpp */; }; + 6458A584121BEF9C00D40A32 /* DS_HuffmanEncodingTree.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 6458A580121BEF9C00D40A32 /* DS_HuffmanEncodingTree.cpp */; }; + 6458A585121BEF9C00D40A32 /* PacketFileLogger.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 6458A582121BEF9C00D40A32 /* PacketFileLogger.cpp */; }; + 6458A58A121BEFB900D40A32 /* PacketLogger.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 6458A588121BEFB900D40A32 /* PacketLogger.cpp */; }; + 6458A598121BEFFD00D40A32 /* rijndael.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 6458A596121BEFFD00D40A32 /* rijndael.cpp */; }; + 6458A59B121BF01E00D40A32 /* StringTable.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 6458A599121BF01E00D40A32 /* StringTable.cpp */; }; + 6458A5A0121BF03500D40A32 /* CheckSum.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 6458A59E121BF03500D40A32 /* CheckSum.cpp */; }; + 6458A5A3121BF05700D40A32 /* LinuxStrings.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 6458A5A2121BF05700D40A32 /* LinuxStrings.cpp */; }; + 6481526C12D4A11500FD8891 /* Log.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 6481526912D4A11500FD8891 /* Log.cpp */; }; + 6481526D12D4A11500FD8891 /* Utility.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 6481526B12D4A11500FD8891 /* Utility.cpp */; }; + 64B02EA70D699F3F00D97C85 /* ProxyServer.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 64B02EA60D699F3F00D97C85 /* ProxyServer.cpp */; }; +/* End PBXBuildFile section */ + +/* Begin PBXCopyFilesBuildPhase section */ + 8DD76F690486A84900D96B5E /* CopyFiles */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 8; + dstPath = /usr/share/man/man1/; + dstSubfolderSpec = 0; + files = ( + ); + runOnlyForDeploymentPostprocessing = 1; + }; +/* End PBXCopyFilesBuildPhase section */ + +/* Begin PBXFileReference section */ + 6458A50F121BED4800D40A32 /* _FindFirst.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = _FindFirst.cpp; sourceTree = ""; }; + 6458A510121BED4800D40A32 /* _FindFirst.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = _FindFirst.h; sourceTree = ""; }; + 6458A511121BED4800D40A32 /* BigInt.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = BigInt.cpp; sourceTree = ""; }; + 6458A512121BED4800D40A32 /* BigInt.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = BigInt.h; sourceTree = ""; }; + 6458A513121BED4800D40A32 /* BitStream.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = BitStream.cpp; sourceTree = ""; }; + 6458A514121BED4800D40A32 /* BitStream.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = BitStream.h; sourceTree = ""; }; + 6458A515121BED4800D40A32 /* CCRakNetUDT.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = CCRakNetUDT.cpp; sourceTree = ""; }; + 6458A516121BED4800D40A32 /* CCRakNetUDT.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CCRakNetUDT.h; sourceTree = ""; }; + 6458A523121BEDBA00D40A32 /* GetTime.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = GetTime.cpp; sourceTree = ""; }; + 6458A524121BEDBA00D40A32 /* GetTime.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GetTime.h; sourceTree = ""; }; + 6458A525121BEDBA00D40A32 /* Itoa.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = Itoa.cpp; sourceTree = ""; }; + 6458A526121BEDBA00D40A32 /* Itoa.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Itoa.h; sourceTree = ""; }; + 6458A527121BEDBA00D40A32 /* NatPunchthroughClient.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = NatPunchthroughClient.cpp; sourceTree = ""; }; + 6458A528121BEDBA00D40A32 /* NatPunchthroughClient.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = NatPunchthroughClient.h; sourceTree = ""; }; + 6458A529121BEDBA00D40A32 /* PluginInterface2.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = PluginInterface2.cpp; sourceTree = ""; }; + 6458A52A121BEDBA00D40A32 /* PluginInterface2.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PluginInterface2.h; sourceTree = ""; }; + 6458A52B121BEDBA00D40A32 /* RakMemoryOverride.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = RakMemoryOverride.cpp; sourceTree = ""; }; + 6458A52C121BEDBA00D40A32 /* RakMemoryOverride.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RakMemoryOverride.h; sourceTree = ""; }; + 6458A52D121BEDBA00D40A32 /* RakNetSocket.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = RakNetSocket.cpp; sourceTree = ""; }; + 6458A52E121BEDBA00D40A32 /* RakNetSocket.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RakNetSocket.h; sourceTree = ""; }; + 6458A52F121BEDBA00D40A32 /* RakPeer.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = RakPeer.cpp; sourceTree = ""; }; + 6458A530121BEDBA00D40A32 /* RakPeer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RakPeer.h; sourceTree = ""; }; + 6458A531121BEDBA00D40A32 /* RakPeerInterface.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RakPeerInterface.h; sourceTree = ""; }; + 6458A532121BEDBA00D40A32 /* RakString.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = RakString.cpp; sourceTree = ""; }; + 6458A533121BEDBA00D40A32 /* RakString.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RakString.h; sourceTree = ""; }; + 6458A534121BEDBA00D40A32 /* RakThread.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = RakThread.cpp; sourceTree = ""; }; + 6458A535121BEDBA00D40A32 /* RakThread.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RakThread.h; sourceTree = ""; }; + 6458A536121BEDBA00D40A32 /* RSACrypt.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = RSACrypt.cpp; sourceTree = ""; }; + 6458A537121BEDBA00D40A32 /* RSACrypt.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RSACrypt.h; sourceTree = ""; }; + 6458A538121BEDBA00D40A32 /* SignaledEvent.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = SignaledEvent.cpp; sourceTree = ""; }; + 6458A539121BEDBA00D40A32 /* SignaledEvent.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SignaledEvent.h; sourceTree = ""; }; + 6458A53A121BEDBA00D40A32 /* SuperFastHash.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = SuperFastHash.cpp; sourceTree = ""; }; + 6458A53B121BEDBA00D40A32 /* SuperFastHash.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SuperFastHash.h; sourceTree = ""; }; + 6458A552121BEE1800D40A32 /* RakNetTypes.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = RakNetTypes.cpp; sourceTree = ""; }; + 6458A553121BEE1800D40A32 /* RakNetTypes.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RakNetTypes.h; sourceTree = ""; }; + 6458A557121BEE3E00D40A32 /* SHA1.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = SHA1.cpp; sourceTree = ""; }; + 6458A558121BEE3E00D40A32 /* SHA1.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SHA1.h; sourceTree = ""; }; + 6458A55A121BEE5000D40A32 /* RakSleep.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = RakSleep.cpp; sourceTree = ""; }; + 6458A55B121BEE5000D40A32 /* RakSleep.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RakSleep.h; sourceTree = ""; }; + 6458A55F121BEEAB00D40A32 /* Rand.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = Rand.cpp; sourceTree = ""; }; + 6458A560121BEEAB00D40A32 /* Rand.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Rand.h; sourceTree = ""; }; + 6458A561121BEEAB00D40A32 /* SocketLayer.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = SocketLayer.cpp; sourceTree = ""; }; + 6458A562121BEEAB00D40A32 /* SocketLayer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SocketLayer.h; sourceTree = ""; }; + 6458A563121BEEAB00D40A32 /* StringCompressor.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = StringCompressor.cpp; sourceTree = ""; }; + 6458A564121BEEAB00D40A32 /* StringCompressor.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = StringCompressor.h; sourceTree = ""; }; + 6458A56A121BEEE400D40A32 /* RakNetworkFactory.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = RakNetworkFactory.cpp; sourceTree = ""; }; + 6458A56B121BEEE400D40A32 /* RakNetworkFactory.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RakNetworkFactory.h; sourceTree = ""; }; + 6458A56C121BEEE400D40A32 /* ReliabilityLayer.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = ReliabilityLayer.cpp; sourceTree = ""; }; + 6458A56D121BEEE400D40A32 /* ReliabilityLayer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ReliabilityLayer.h; sourceTree = ""; }; + 6458A56E121BEEE400D40A32 /* RPCMap.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = RPCMap.cpp; sourceTree = ""; }; + 6458A56F121BEEE400D40A32 /* RPCMap.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RPCMap.h; sourceTree = ""; }; + 6458A575121BEF5100D40A32 /* DataBlockEncryptor.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = DataBlockEncryptor.cpp; sourceTree = ""; }; + 6458A576121BEF5100D40A32 /* DataBlockEncryptor.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DataBlockEncryptor.h; sourceTree = ""; }; + 6458A577121BEF5100D40A32 /* NetworkIDManager.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = NetworkIDManager.cpp; sourceTree = ""; }; + 6458A578121BEF5100D40A32 /* NetworkIDManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = NetworkIDManager.h; sourceTree = ""; }; + 6458A579121BEF5100D40A32 /* SimpleMutex.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = SimpleMutex.cpp; sourceTree = ""; }; + 6458A57A121BEF5100D40A32 /* SimpleMutex.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SimpleMutex.h; sourceTree = ""; }; + 6458A580121BEF9C00D40A32 /* DS_HuffmanEncodingTree.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = DS_HuffmanEncodingTree.cpp; sourceTree = ""; }; + 6458A581121BEF9C00D40A32 /* DS_HuffmanEncodingTree.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DS_HuffmanEncodingTree.h; sourceTree = ""; }; + 6458A582121BEF9C00D40A32 /* PacketFileLogger.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = PacketFileLogger.cpp; sourceTree = ""; }; + 6458A583121BEF9C00D40A32 /* PacketFileLogger.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PacketFileLogger.h; sourceTree = ""; }; + 6458A588121BEFB900D40A32 /* PacketLogger.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = PacketLogger.cpp; sourceTree = ""; }; + 6458A589121BEFB900D40A32 /* PacketLogger.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PacketLogger.h; sourceTree = ""; }; + 6458A596121BEFFD00D40A32 /* rijndael.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = rijndael.cpp; sourceTree = ""; }; + 6458A597121BEFFD00D40A32 /* Rijndael.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Rijndael.h; sourceTree = ""; }; + 6458A599121BF01E00D40A32 /* StringTable.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = StringTable.cpp; sourceTree = ""; }; + 6458A59A121BF01E00D40A32 /* StringTable.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = StringTable.h; sourceTree = ""; }; + 6458A59E121BF03500D40A32 /* CheckSum.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = CheckSum.cpp; sourceTree = ""; }; + 6458A59F121BF03500D40A32 /* CheckSum.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CheckSum.h; sourceTree = ""; }; + 6458A5A1121BF05700D40A32 /* LinuxStrings.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = LinuxStrings.h; sourceTree = ""; }; + 6458A5A2121BF05700D40A32 /* LinuxStrings.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = LinuxStrings.cpp; sourceTree = ""; }; + 6481526912D4A11500FD8891 /* Log.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = Log.cpp; path = ../Common/Log.cpp; sourceTree = SOURCE_ROOT; }; + 6481526A12D4A11500FD8891 /* Log.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = Log.h; path = ../Common/Log.h; sourceTree = SOURCE_ROOT; }; + 6481526B12D4A11500FD8891 /* Utility.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = Utility.cpp; path = ../Common/Utility.cpp; sourceTree = SOURCE_ROOT; }; + 6481538112D4BC9C00FD8891 /* Utility.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = Utility.h; path = ../Common/Utility.h; sourceTree = SOURCE_ROOT; }; + 64B02EA50D699F3F00D97C85 /* ProxyServer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ProxyServer.h; sourceTree = ""; }; + 64B02EA60D699F3F00D97C85 /* ProxyServer.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = ProxyServer.cpp; sourceTree = ""; }; + 64D11C5C11A6B732008C6FB2 /* ProxyServer */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = ProxyServer; sourceTree = BUILT_PRODUCTS_DIR; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 8DD76F660486A84900D96B5E /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 08FB7794FE84155DC02AAC07 /* xcode */ = { + isa = PBXGroup; + children = ( + 6481526812D4A11500FD8891 /* Common */, + 6458A4E5121BECC100D40A32 /* RakNet */, + 08FB7795FE84155DC02AAC07 /* Source */, + 64D11C5D11A6B732008C6FB2 /* Products */, + ); + name = xcode; + sourceTree = ""; + }; + 08FB7795FE84155DC02AAC07 /* Source */ = { + isa = PBXGroup; + children = ( + 64B02EA50D699F3F00D97C85 /* ProxyServer.h */, + 64B02EA60D699F3F00D97C85 /* ProxyServer.cpp */, + ); + name = Source; + path = ..; + sourceTree = ""; + }; + 6458A4E5121BECC100D40A32 /* RakNet */ = { + isa = PBXGroup; + children = ( + 6458A4E6121BECC600D40A32 /* Sources */, + ); + name = RakNet; + path = ../RakNet; + sourceTree = ""; + }; + 6458A4E6121BECC600D40A32 /* Sources */ = { + isa = PBXGroup; + children = ( + 6458A5A1121BF05700D40A32 /* LinuxStrings.h */, + 6458A5A2121BF05700D40A32 /* LinuxStrings.cpp */, + 6458A59E121BF03500D40A32 /* CheckSum.cpp */, + 6458A59F121BF03500D40A32 /* CheckSum.h */, + 6458A599121BF01E00D40A32 /* StringTable.cpp */, + 6458A59A121BF01E00D40A32 /* StringTable.h */, + 6458A596121BEFFD00D40A32 /* rijndael.cpp */, + 6458A597121BEFFD00D40A32 /* Rijndael.h */, + 6458A588121BEFB900D40A32 /* PacketLogger.cpp */, + 6458A589121BEFB900D40A32 /* PacketLogger.h */, + 6458A580121BEF9C00D40A32 /* DS_HuffmanEncodingTree.cpp */, + 6458A581121BEF9C00D40A32 /* DS_HuffmanEncodingTree.h */, + 6458A582121BEF9C00D40A32 /* PacketFileLogger.cpp */, + 6458A583121BEF9C00D40A32 /* PacketFileLogger.h */, + 6458A575121BEF5100D40A32 /* DataBlockEncryptor.cpp */, + 6458A576121BEF5100D40A32 /* DataBlockEncryptor.h */, + 6458A577121BEF5100D40A32 /* NetworkIDManager.cpp */, + 6458A578121BEF5100D40A32 /* NetworkIDManager.h */, + 6458A579121BEF5100D40A32 /* SimpleMutex.cpp */, + 6458A57A121BEF5100D40A32 /* SimpleMutex.h */, + 6458A56A121BEEE400D40A32 /* RakNetworkFactory.cpp */, + 6458A56B121BEEE400D40A32 /* RakNetworkFactory.h */, + 6458A56C121BEEE400D40A32 /* ReliabilityLayer.cpp */, + 6458A56D121BEEE400D40A32 /* ReliabilityLayer.h */, + 6458A56E121BEEE400D40A32 /* RPCMap.cpp */, + 6458A56F121BEEE400D40A32 /* RPCMap.h */, + 6458A55F121BEEAB00D40A32 /* Rand.cpp */, + 6458A560121BEEAB00D40A32 /* Rand.h */, + 6458A561121BEEAB00D40A32 /* SocketLayer.cpp */, + 6458A562121BEEAB00D40A32 /* SocketLayer.h */, + 6458A563121BEEAB00D40A32 /* StringCompressor.cpp */, + 6458A564121BEEAB00D40A32 /* StringCompressor.h */, + 6458A55A121BEE5000D40A32 /* RakSleep.cpp */, + 6458A55B121BEE5000D40A32 /* RakSleep.h */, + 6458A557121BEE3E00D40A32 /* SHA1.cpp */, + 6458A558121BEE3E00D40A32 /* SHA1.h */, + 6458A552121BEE1800D40A32 /* RakNetTypes.cpp */, + 6458A553121BEE1800D40A32 /* RakNetTypes.h */, + 6458A523121BEDBA00D40A32 /* GetTime.cpp */, + 6458A524121BEDBA00D40A32 /* GetTime.h */, + 6458A525121BEDBA00D40A32 /* Itoa.cpp */, + 6458A526121BEDBA00D40A32 /* Itoa.h */, + 6458A527121BEDBA00D40A32 /* NatPunchthroughClient.cpp */, + 6458A528121BEDBA00D40A32 /* NatPunchthroughClient.h */, + 6458A529121BEDBA00D40A32 /* PluginInterface2.cpp */, + 6458A52A121BEDBA00D40A32 /* PluginInterface2.h */, + 6458A52B121BEDBA00D40A32 /* RakMemoryOverride.cpp */, + 6458A52C121BEDBA00D40A32 /* RakMemoryOverride.h */, + 6458A52D121BEDBA00D40A32 /* RakNetSocket.cpp */, + 6458A52E121BEDBA00D40A32 /* RakNetSocket.h */, + 6458A52F121BEDBA00D40A32 /* RakPeer.cpp */, + 6458A530121BEDBA00D40A32 /* RakPeer.h */, + 6458A531121BEDBA00D40A32 /* RakPeerInterface.h */, + 6458A532121BEDBA00D40A32 /* RakString.cpp */, + 6458A533121BEDBA00D40A32 /* RakString.h */, + 6458A534121BEDBA00D40A32 /* RakThread.cpp */, + 6458A535121BEDBA00D40A32 /* RakThread.h */, + 6458A536121BEDBA00D40A32 /* RSACrypt.cpp */, + 6458A537121BEDBA00D40A32 /* RSACrypt.h */, + 6458A538121BEDBA00D40A32 /* SignaledEvent.cpp */, + 6458A539121BEDBA00D40A32 /* SignaledEvent.h */, + 6458A53A121BEDBA00D40A32 /* SuperFastHash.cpp */, + 6458A53B121BEDBA00D40A32 /* SuperFastHash.h */, + 6458A50F121BED4800D40A32 /* _FindFirst.cpp */, + 6458A510121BED4800D40A32 /* _FindFirst.h */, + 6458A511121BED4800D40A32 /* BigInt.cpp */, + 6458A512121BED4800D40A32 /* BigInt.h */, + 6458A513121BED4800D40A32 /* BitStream.cpp */, + 6458A514121BED4800D40A32 /* BitStream.h */, + 6458A515121BED4800D40A32 /* CCRakNetUDT.cpp */, + 6458A516121BED4800D40A32 /* CCRakNetUDT.h */, + ); + path = Sources; + sourceTree = ""; + }; + 6481526812D4A11500FD8891 /* Common */ = { + isa = PBXGroup; + children = ( + 6481526912D4A11500FD8891 /* Log.cpp */, + 6481526A12D4A11500FD8891 /* Log.h */, + 6481526B12D4A11500FD8891 /* Utility.cpp */, + 6481538112D4BC9C00FD8891 /* Utility.h */, + ); + name = Common; + path = ../Common; + sourceTree = SOURCE_ROOT; + }; + 64D11C5D11A6B732008C6FB2 /* Products */ = { + isa = PBXGroup; + children = ( + 64D11C5C11A6B732008C6FB2 /* ProxyServer */, + ); + name = Products; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 8DD76F620486A84900D96B5E /* ProxyServer */ = { + isa = PBXNativeTarget; + buildConfigurationList = 1DEB923108733DC60010E9CD /* Build configuration list for PBXNativeTarget "ProxyServer" */; + buildPhases = ( + 8DD76F640486A84900D96B5E /* Sources */, + 8DD76F660486A84900D96B5E /* Frameworks */, + 8DD76F690486A84900D96B5E /* CopyFiles */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = ProxyServer; + productInstallPath = "$(HOME)/bin"; + productName = xcode; + productReference = 64D11C5C11A6B732008C6FB2 /* ProxyServer */; + productType = "com.apple.product-type.tool"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 08FB7793FE84155DC02AAC07 /* Project object */ = { + isa = PBXProject; + buildConfigurationList = 1DEB923508733DC60010E9CD /* Build configuration list for PBXProject "ProxyServer" */; + compatibilityVersion = "Xcode 2.4"; + developmentRegion = English; + hasScannedForEncodings = 1; + knownRegions = ( + English, + Japanese, + French, + German, + ); + mainGroup = 08FB7794FE84155DC02AAC07 /* xcode */; + productRefGroup = 64D11C5D11A6B732008C6FB2 /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 8DD76F620486A84900D96B5E /* ProxyServer */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXSourcesBuildPhase section */ + 8DD76F640486A84900D96B5E /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 64B02EA70D699F3F00D97C85 /* ProxyServer.cpp in Sources */, + 6458A517121BED4800D40A32 /* _FindFirst.cpp in Sources */, + 6458A518121BED4800D40A32 /* BigInt.cpp in Sources */, + 6458A519121BED4800D40A32 /* BitStream.cpp in Sources */, + 6458A51A121BED4800D40A32 /* CCRakNetUDT.cpp in Sources */, + 6458A53C121BEDBA00D40A32 /* GetTime.cpp in Sources */, + 6458A53D121BEDBA00D40A32 /* Itoa.cpp in Sources */, + 6458A53E121BEDBA00D40A32 /* NatPunchthroughClient.cpp in Sources */, + 6458A53F121BEDBA00D40A32 /* PluginInterface2.cpp in Sources */, + 6458A540121BEDBA00D40A32 /* RakMemoryOverride.cpp in Sources */, + 6458A541121BEDBA00D40A32 /* RakNetSocket.cpp in Sources */, + 6458A542121BEDBA00D40A32 /* RakPeer.cpp in Sources */, + 6458A543121BEDBA00D40A32 /* RakString.cpp in Sources */, + 6458A544121BEDBA00D40A32 /* RakThread.cpp in Sources */, + 6458A545121BEDBA00D40A32 /* RSACrypt.cpp in Sources */, + 6458A546121BEDBA00D40A32 /* SignaledEvent.cpp in Sources */, + 6458A547121BEDBA00D40A32 /* SuperFastHash.cpp in Sources */, + 6458A554121BEE1800D40A32 /* RakNetTypes.cpp in Sources */, + 6458A559121BEE3E00D40A32 /* SHA1.cpp in Sources */, + 6458A55C121BEE5000D40A32 /* RakSleep.cpp in Sources */, + 6458A565121BEEAB00D40A32 /* Rand.cpp in Sources */, + 6458A566121BEEAB00D40A32 /* SocketLayer.cpp in Sources */, + 6458A567121BEEAB00D40A32 /* StringCompressor.cpp in Sources */, + 6458A570121BEEE400D40A32 /* RakNetworkFactory.cpp in Sources */, + 6458A571121BEEE400D40A32 /* ReliabilityLayer.cpp in Sources */, + 6458A572121BEEE400D40A32 /* RPCMap.cpp in Sources */, + 6458A57B121BEF5100D40A32 /* DataBlockEncryptor.cpp in Sources */, + 6458A57C121BEF5100D40A32 /* NetworkIDManager.cpp in Sources */, + 6458A57D121BEF5100D40A32 /* SimpleMutex.cpp in Sources */, + 6458A584121BEF9C00D40A32 /* DS_HuffmanEncodingTree.cpp in Sources */, + 6458A585121BEF9C00D40A32 /* PacketFileLogger.cpp in Sources */, + 6458A58A121BEFB900D40A32 /* PacketLogger.cpp in Sources */, + 6458A598121BEFFD00D40A32 /* rijndael.cpp in Sources */, + 6458A59B121BF01E00D40A32 /* StringTable.cpp in Sources */, + 6458A5A0121BF03500D40A32 /* CheckSum.cpp in Sources */, + 6458A5A3121BF05700D40A32 /* LinuxStrings.cpp in Sources */, + 6481526C12D4A11500FD8891 /* Log.cpp in Sources */, + 6481526D12D4A11500FD8891 /* Utility.cpp in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin XCBuildConfiguration section */ + 1DEB923208733DC60010E9CD /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + COPY_PHASE_STRIP = NO; + GCC_DYNAMIC_NO_PIC = NO; + GCC_ENABLE_FIX_AND_CONTINUE = YES; + GCC_MODEL_TUNING = G5; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "_GLIBCXX_DEBUG=1", + "_GLIBCXX_DEBUG_PEDANTIC=1", + ); + INSTALL_PATH = /usr/local/bin; + PRODUCT_NAME = xcode; + ZERO_LINK = YES; + }; + name = Debug; + }; + 1DEB923308733DC60010E9CD /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ARCHS = i386; + DEBUG_INFORMATION_FORMAT = dwarf; + GCC_MODEL_TUNING = G5; + GCC_VERSION = 4.0; + INSTALL_PATH = /usr/local/bin; + PRODUCT_NAME = ProxyServer; + SDKROOT = /Developer/SDKs/MacOSX10.4u.sdk; + }; + name = Release; + }; + 1DEB923608733DC60010E9CD /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + GCC_VERSION = 4.0; + GCC_WARN_ABOUT_RETURN_TYPE = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + PREBINDING = NO; + SDKROOT = /Developer/SDKs/MacOSX10.4u.sdk; + }; + name = Debug; + }; + 1DEB923708733DC60010E9CD /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ARCHS = "$(ARCHS_STANDARD_32_BIT_PRE_XCODE_3_1)"; + ARCHS_STANDARD_32_BIT_PRE_XCODE_3_1 = "ppc i386"; + GCC_C_LANGUAGE_STANDARD = c99; + GCC_VERSION = 4.0; + GCC_WARN_ABOUT_RETURN_TYPE = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + PREBINDING = NO; + SDKROOT = /Developer/SDKs/MacOSX10.4u.sdk; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 1DEB923108733DC60010E9CD /* Build configuration list for PBXNativeTarget "ProxyServer" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 1DEB923208733DC60010E9CD /* Debug */, + 1DEB923308733DC60010E9CD /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 1DEB923508733DC60010E9CD /* Build configuration list for PBXProject "ProxyServer" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 1DEB923608733DC60010E9CD /* Debug */, + 1DEB923708733DC60010E9CD /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 08FB7793FE84155DC02AAC07 /* Project object */; +}