Initial commit
This commit is contained in:
@@ -0,0 +1,34 @@
|
||||
# ZNet/Makefile
|
||||
# Makefile for ZNet, requires GNU "make"
|
||||
|
||||
BINNAME := $(DIST)/libZNet.a
|
||||
ifeq ($(TARGET),debug)
|
||||
BINNAME := $(subst .a,_d.a, $(BINNAME))
|
||||
endif
|
||||
|
||||
SRC := \
|
||||
ZNetBandwidthMeter.cpp \
|
||||
ZNetClient.cpp \
|
||||
ZNetHost.cpp \
|
||||
ZNetPacketChannel.cpp \
|
||||
ZNetPeer.cpp \
|
||||
ZNetPrivate.cpp \
|
||||
ZNetServer.cpp
|
||||
|
||||
OBJ := $(addprefix obj/$(ARCH)/$(TARGET)/,$(subst .cpp,.o,$(SRC)) )
|
||||
|
||||
$(shell mkdir -p obj/$(ARCH)/$(TARGET))
|
||||
|
||||
$(BINNAME): $(OBJ)
|
||||
$(AR) cru $@ $+
|
||||
$(RANLIB) $@
|
||||
|
||||
# CLEAN
|
||||
clean:
|
||||
@-rm -r -f obj $(DIST)/libZNet*.a
|
||||
|
||||
|
||||
# *.cpp files to *.o files
|
||||
obj/$(ARCH)/$(TARGET)/%.o: %.cpp
|
||||
@echo CXX $@
|
||||
@$(CXX) $(CXXFLAGS) -c $*.cpp -o obj/$(ARCH)/$(TARGET)/$*.o
|
||||
@@ -0,0 +1,200 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project DefaultTargets="Build" ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<ItemGroup Label="ProjectConfigurations">
|
||||
<ProjectConfiguration Include="Debug|Win32">
|
||||
<Configuration>Debug</Configuration>
|
||||
<Platform>Win32</Platform>
|
||||
</ProjectConfiguration>
|
||||
<ProjectConfiguration Include="Debug|x64">
|
||||
<Configuration>Debug</Configuration>
|
||||
<Platform>x64</Platform>
|
||||
</ProjectConfiguration>
|
||||
<ProjectConfiguration Include="Release|Win32">
|
||||
<Configuration>Release</Configuration>
|
||||
<Platform>Win32</Platform>
|
||||
</ProjectConfiguration>
|
||||
<ProjectConfiguration Include="Release|x64">
|
||||
<Configuration>Release</Configuration>
|
||||
<Platform>x64</Platform>
|
||||
</ProjectConfiguration>
|
||||
</ItemGroup>
|
||||
<PropertyGroup Label="Globals">
|
||||
<ProjectGuid>{76D72C18-B505-4D8C-B146-5348F6E5CA12}</ProjectGuid>
|
||||
<RootNamespace>ZNet</RootNamespace>
|
||||
</PropertyGroup>
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="Configuration">
|
||||
<ConfigurationType>StaticLibrary</ConfigurationType>
|
||||
<UseDebugLibraries>true</UseDebugLibraries>
|
||||
<PlatformToolset>v110</PlatformToolset>
|
||||
<CharacterSet>MultiByte</CharacterSet>
|
||||
<WholeProgramOptimization>true</WholeProgramOptimization>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="Configuration">
|
||||
<ConfigurationType>StaticLibrary</ConfigurationType>
|
||||
<UseDebugLibraries>true</UseDebugLibraries>
|
||||
<PlatformToolset>v110</PlatformToolset>
|
||||
<CharacterSet>MultiByte</CharacterSet>
|
||||
<WholeProgramOptimization>true</WholeProgramOptimization>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="Configuration">
|
||||
<ConfigurationType>StaticLibrary</ConfigurationType>
|
||||
<UseDebugLibraries>false</UseDebugLibraries>
|
||||
<PlatformToolset>v110</PlatformToolset>
|
||||
<WholeProgramOptimization>true</WholeProgramOptimization>
|
||||
<CharacterSet>MultiByte</CharacterSet>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="Configuration">
|
||||
<ConfigurationType>StaticLibrary</ConfigurationType>
|
||||
<UseDebugLibraries>false</UseDebugLibraries>
|
||||
<PlatformToolset>v110</PlatformToolset>
|
||||
<WholeProgramOptimization>true</WholeProgramOptimization>
|
||||
<CharacterSet>MultiByte</CharacterSet>
|
||||
</PropertyGroup>
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
|
||||
<ImportGroup Label="ExtensionSettings">
|
||||
</ImportGroup>
|
||||
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
|
||||
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
|
||||
</ImportGroup>
|
||||
<ImportGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="PropertySheets">
|
||||
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
|
||||
</ImportGroup>
|
||||
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
|
||||
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
|
||||
</ImportGroup>
|
||||
<ImportGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="PropertySheets">
|
||||
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
|
||||
</ImportGroup>
|
||||
<PropertyGroup Label="UserMacros" />
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
|
||||
<IncludePath>$(SolutionDir)Include;$(SolutionDir)Lib\Include;$(IncludePath)</IncludePath>
|
||||
<OutDir>$(SolutionDir)Bin\x86\</OutDir>
|
||||
<IntDir>$(SolutionDir)Intermediate\$(Configuration)\$(Platform)\$(ProjectName)\</IntDir>
|
||||
<TargetName>$(ProjectName)-debug</TargetName>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
|
||||
<IncludePath>$(SolutionDir)Include;$(SolutionDir)Lib\Include;$(IncludePath)</IncludePath>
|
||||
<OutDir>$(SolutionDir)Bin\x86\</OutDir>
|
||||
<IntDir>$(SolutionDir)Intermediate\$(Configuration)\$(Platform)\$(ProjectName)\</IntDir>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
|
||||
<IncludePath>$(SolutionDir)Include;$(SolutionDir)Lib\Include;$(IncludePath)</IncludePath>
|
||||
<OutDir>$(SolutionDir)Bin\x64\</OutDir>
|
||||
<IntDir>$(SolutionDir)Intermediate\$(Configuration)\$(Platform)\$(ProjectName)\</IntDir>
|
||||
<TargetName>$(ProjectName)-debug</TargetName>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
|
||||
<IncludePath>$(SolutionDir)Include;$(SolutionDir)Lib\Include;$(IncludePath)</IncludePath>
|
||||
<OutDir>$(SolutionDir)Bin\x64\</OutDir>
|
||||
<IntDir>$(SolutionDir)Intermediate\$(Configuration)\$(Platform)\$(ProjectName)\</IntDir>
|
||||
</PropertyGroup>
|
||||
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
|
||||
<ClCompile>
|
||||
<WarningLevel>Level4</WarningLevel>
|
||||
<Optimization>Disabled</Optimization>
|
||||
<StringPooling>true</StringPooling>
|
||||
<MinimalRebuild>false</MinimalRebuild>
|
||||
<ExceptionHandling>false</ExceptionHandling>
|
||||
<BufferSecurityCheck>false</BufferSecurityCheck>
|
||||
<CreateHotpatchableImage>false</CreateHotpatchableImage>
|
||||
<RuntimeTypeInfo>false</RuntimeTypeInfo>
|
||||
<OpenMPSupport>false</OpenMPSupport>
|
||||
<MultiProcessorCompilation>true</MultiProcessorCompilation>
|
||||
<DebugInformationFormat>ProgramDatabase</DebugInformationFormat>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
<GenerateDebugInformation>true</GenerateDebugInformation>
|
||||
</Link>
|
||||
</ItemDefinitionGroup>
|
||||
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
|
||||
<ClCompile>
|
||||
<WarningLevel>Level4</WarningLevel>
|
||||
<Optimization>Disabled</Optimization>
|
||||
<StringPooling>true</StringPooling>
|
||||
<MinimalRebuild>false</MinimalRebuild>
|
||||
<ExceptionHandling>false</ExceptionHandling>
|
||||
<BufferSecurityCheck>false</BufferSecurityCheck>
|
||||
<CreateHotpatchableImage>false</CreateHotpatchableImage>
|
||||
<RuntimeTypeInfo>false</RuntimeTypeInfo>
|
||||
<OpenMPSupport>false</OpenMPSupport>
|
||||
<MultiProcessorCompilation>true</MultiProcessorCompilation>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
<GenerateDebugInformation>true</GenerateDebugInformation>
|
||||
</Link>
|
||||
</ItemDefinitionGroup>
|
||||
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
|
||||
<ClCompile>
|
||||
<WarningLevel>Level4</WarningLevel>
|
||||
<Optimization>MaxSpeed</Optimization>
|
||||
<FunctionLevelLinking>true</FunctionLevelLinking>
|
||||
<IntrinsicFunctions>true</IntrinsicFunctions>
|
||||
<StringPooling>true</StringPooling>
|
||||
<ExceptionHandling>false</ExceptionHandling>
|
||||
<BufferSecurityCheck>false</BufferSecurityCheck>
|
||||
<CreateHotpatchableImage>false</CreateHotpatchableImage>
|
||||
<RuntimeTypeInfo>false</RuntimeTypeInfo>
|
||||
<OpenMPSupport>false</OpenMPSupport>
|
||||
<MultiProcessorCompilation>true</MultiProcessorCompilation>
|
||||
<DebugInformationFormat>None</DebugInformationFormat>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
<GenerateDebugInformation>true</GenerateDebugInformation>
|
||||
<EnableCOMDATFolding>true</EnableCOMDATFolding>
|
||||
<OptimizeReferences>true</OptimizeReferences>
|
||||
</Link>
|
||||
</ItemDefinitionGroup>
|
||||
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
|
||||
<ClCompile>
|
||||
<WarningLevel>Level4</WarningLevel>
|
||||
<Optimization>MaxSpeed</Optimization>
|
||||
<FunctionLevelLinking>true</FunctionLevelLinking>
|
||||
<IntrinsicFunctions>true</IntrinsicFunctions>
|
||||
<StringPooling>true</StringPooling>
|
||||
<ExceptionHandling>false</ExceptionHandling>
|
||||
<BufferSecurityCheck>false</BufferSecurityCheck>
|
||||
<CreateHotpatchableImage>false</CreateHotpatchableImage>
|
||||
<RuntimeTypeInfo>false</RuntimeTypeInfo>
|
||||
<OpenMPSupport>false</OpenMPSupport>
|
||||
<MultiProcessorCompilation>true</MultiProcessorCompilation>
|
||||
<DebugInformationFormat>None</DebugInformationFormat>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
<GenerateDebugInformation>true</GenerateDebugInformation>
|
||||
<EnableCOMDATFolding>true</EnableCOMDATFolding>
|
||||
<OptimizeReferences>true</OptimizeReferences>
|
||||
</Link>
|
||||
</ItemDefinitionGroup>
|
||||
<ItemGroup>
|
||||
<ClInclude Include="..\Include\ZNet\ZNet.hpp" />
|
||||
<ClInclude Include="..\Include\ZNet\ZNetBandwidthMeter.hpp" />
|
||||
<ClInclude Include="..\Include\ZNet\ZNetClient.hpp" />
|
||||
<ClInclude Include="..\Include\ZNet\ZNetConsts.hpp" />
|
||||
<ClInclude Include="..\Include\ZNet\ZNetEvent.hpp" />
|
||||
<ClInclude Include="..\Include\ZNet\ZNetHost.hpp" />
|
||||
<ClInclude Include="..\Include\ZNet\ZNetPacket.hpp" />
|
||||
<ClInclude Include="..\Include\ZNet\ZNetPacketChannel.hpp" />
|
||||
<ClInclude Include="..\Include\ZNet\ZNetPeer.hpp" />
|
||||
<ClInclude Include="..\Include\ZNet\ZNetServer.hpp" />
|
||||
<ClInclude Include="..\Include\ZNet\ZNetUtil.hpp" />
|
||||
<ClInclude Include="ZNetPrivate.hpp" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClCompile Include="ZNetBandwidthMeter.cpp" />
|
||||
<ClCompile Include="ZNetClient.cpp" />
|
||||
<ClCompile Include="ZNetHost.cpp" />
|
||||
<ClCompile Include="ZNetPacketChannel.cpp" />
|
||||
<ClCompile Include="ZNetPeer.cpp" />
|
||||
<ClCompile Include="ZNetPrivate.cpp" />
|
||||
<ClCompile Include="ZNetServer.cpp" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\libsst-net\libsst-net.vcxproj">
|
||||
<Project>{e6679a2f-6c8e-4555-9228-6929c734cef4}</Project>
|
||||
</ProjectReference>
|
||||
</ItemGroup>
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
|
||||
<ImportGroup Label="ExtensionTargets">
|
||||
</ImportGroup>
|
||||
</Project>
|
||||
@@ -0,0 +1,78 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<ItemGroup>
|
||||
<Filter Include="Source Files">
|
||||
<UniqueIdentifier>{4FC737F1-C7A5-4376-A066-2A32D752A2FF}</UniqueIdentifier>
|
||||
<Extensions>cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx</Extensions>
|
||||
</Filter>
|
||||
<Filter Include="Header Files">
|
||||
<UniqueIdentifier>{93995380-89BD-4b04-88EB-625FBE52EBFB}</UniqueIdentifier>
|
||||
<Extensions>h;hpp;hxx;hm;inl;inc;xsd</Extensions>
|
||||
</Filter>
|
||||
<Filter Include="Resource Files">
|
||||
<UniqueIdentifier>{67DA6AB6-F800-4c08-8B7A-83BB121AAD01}</UniqueIdentifier>
|
||||
<Extensions>rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms</Extensions>
|
||||
</Filter>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClInclude Include="..\Include\ZNet\ZNet.hpp">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="..\Include\ZNet\ZNetPacket.hpp">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="..\Include\ZNet\ZNetConsts.hpp">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="..\Include\ZNet\ZNetEvent.hpp">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="..\Include\ZNet\ZNetUtil.hpp">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="..\Include\ZNet\ZNetPacketChannel.hpp">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="..\Include\ZNet\ZNetBandwidthMeter.hpp">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="ZNetPrivate.hpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="..\Include\ZNet\ZNetHost.hpp">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="..\Include\ZNet\ZNetClient.hpp">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="..\Include\ZNet\ZNetServer.hpp">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="..\Include\ZNet\ZNetPeer.hpp">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClCompile Include="ZNetHost.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="ZNetPeer.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="ZNetPacketChannel.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="ZNetBandwidthMeter.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="ZNetServer.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="ZNetClient.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="ZNetPrivate.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
@@ -0,0 +1,86 @@
|
||||
/*
|
||||
ZNetBandwidthMeter.cpp
|
||||
Author: Patrick Baggett <ptbaggett@762studios.com>
|
||||
Created: 7/10/2013
|
||||
|
||||
Purpose:
|
||||
|
||||
** NOT PART OF PUBLIC SDK **
|
||||
This class is not part of the public SDK; its fields and methods are not present
|
||||
in the documentation and cannot be guaranteed in future revisions.
|
||||
** NOT PART OF PUBLIC SDK **
|
||||
|
||||
Bandwidth metering using a simple token bucket algorithm. A single value is
|
||||
metered, so a incoming / outgoing each need an instance.
|
||||
|
||||
License:
|
||||
|
||||
Copyright 2013, 762 Studios
|
||||
*/
|
||||
|
||||
#include <ZNet/ZNetBandwidthMeter.hpp>
|
||||
|
||||
/*************************************************************************/
|
||||
|
||||
void ZNetBandwidthMeter::SetLimit(uint32_t newLimit)
|
||||
{
|
||||
limit = newLimit;
|
||||
|
||||
//Clamp existing token bucket
|
||||
if(tokens > newLimit)
|
||||
tokens = newLimit;
|
||||
|
||||
}
|
||||
|
||||
/*************************************************************************/
|
||||
|
||||
void ZNetBandwidthMeter::Reset(uint64_t newStartTime)
|
||||
{
|
||||
lastTime = newStartTime;
|
||||
tokens = limit;
|
||||
}
|
||||
|
||||
/*************************************************************************/
|
||||
|
||||
bool ZNetBandwidthMeter::TryAllocate(uint32_t bytes)
|
||||
{
|
||||
if(bytes <= tokens)
|
||||
{
|
||||
tokens -= bytes;
|
||||
return true;
|
||||
}
|
||||
|
||||
//Not enough
|
||||
return false;
|
||||
}
|
||||
|
||||
/*************************************************************************/
|
||||
|
||||
void ZNetBandwidthMeter::Update(uint64_t newTime)
|
||||
{
|
||||
//Sanity check -- the time _did_ monotonically increase, right?
|
||||
if(newTime > lastTime)
|
||||
{
|
||||
uint32_t delta = (uint32_t)(newTime - lastTime);
|
||||
|
||||
//If less than a second has passed...
|
||||
if(delta < 1000)
|
||||
{
|
||||
//We could do: delta / 1000.0 * limit, but that involves int -> float conversions
|
||||
//instead we do u32 x u32 = u64 multiply and divide by 1000, keeping it all in fixed
|
||||
//point happiness. Because delta < 1000, dividing by 1000 should put it back in the
|
||||
//range of a u32.
|
||||
uint32_t amount = (uint64_t)delta * (uint64_t)limit / 1000;
|
||||
|
||||
//Add newly generated tokens, but clamp to the limit.
|
||||
tokens += amount;
|
||||
if(tokens > limit)
|
||||
tokens = limit;
|
||||
}
|
||||
else //More than a second, so restore all tokens
|
||||
tokens = limit;
|
||||
|
||||
//Save this as the new time
|
||||
lastTime = newTime;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,268 @@
|
||||
/*
|
||||
ZNetClient.cpp
|
||||
Author: Patrick Baggett <ptbaggett@762studios.com>
|
||||
Created: 7/18/2013
|
||||
|
||||
Purpose:
|
||||
|
||||
ZNetClient -- extends ZNetHost to provide a client
|
||||
|
||||
License:
|
||||
|
||||
Copyright 2013, 762 Studios
|
||||
*/
|
||||
|
||||
#include <ZNet/ZNetClient.hpp>
|
||||
#include <ZNet/ZNetPacket.hpp>
|
||||
#include <SST/SST_Assert.h>
|
||||
#include <SST/SST_Endian.h>
|
||||
#include <SST/SST_Time.h>
|
||||
#include "ZNetPrivate.hpp"
|
||||
|
||||
#include <stdio.h> //HACKHACK printf
|
||||
|
||||
ZNetClient::ZNetClient()
|
||||
{
|
||||
connectedFlag = false;
|
||||
}
|
||||
|
||||
ZNetClient::~ZNetClient()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
/*************************************************************************/
|
||||
|
||||
int ZNetClient::Update()
|
||||
{
|
||||
bool hitError = false;
|
||||
uint8_t data[9000];
|
||||
|
||||
//Only update if we've got a server
|
||||
if(server.GetState() != STATE_UNCONNECTED)
|
||||
{
|
||||
SST_Socket s = server.GetSocket();
|
||||
SST_NetResult res;
|
||||
do
|
||||
{
|
||||
SST_NetAddress sender;
|
||||
size_t nrReceived;
|
||||
|
||||
res = SST_Net_RecvFrom(s, data, sizeof(data), 0, &sender, &nrReceived);
|
||||
|
||||
if(res == SSTNETRESULT_SUCCESS)
|
||||
{
|
||||
//OK, so is the packet from the server? If not, ignore it
|
||||
if(SST_Net_AddressCompare(&sender, server.GetNetAddress()) != 0)
|
||||
continue;
|
||||
|
||||
HandlePacket(data, (uint32_t)nrReceived);
|
||||
}
|
||||
else if(res == SSTNETRESULT_WOULDBLOCK)
|
||||
break;
|
||||
else
|
||||
hitError = true;
|
||||
|
||||
} while(res == SSTNETRESULT_SUCCESS);
|
||||
}
|
||||
static const char* str[] = {"UNCONNECTED", "HANDSHAKE", "CONNECTED", "SERVING"};
|
||||
printf("MY STATE: %s / %u ack\n", str[server.GetState()], server.GetPacketChannel(0)->GetLocalAck());
|
||||
|
||||
|
||||
//Send all channel data to server
|
||||
if(!this->SendToPeer(&server))
|
||||
hitError = true;
|
||||
|
||||
//Send acks
|
||||
server.SendAcksForAllChannels();
|
||||
server.ProcessLocalAcks();
|
||||
|
||||
if(hitError)
|
||||
return -1;
|
||||
|
||||
return HasEvent() ? 1 : 0;
|
||||
}
|
||||
|
||||
/*************************************************************************/
|
||||
|
||||
bool ZNetClient::Connect(SST_Socket s, SST_NetAddress* addr, uint32_t nrChannels, uint32_t userData)
|
||||
{
|
||||
SST_OS_DebugAssert(s != 0, "Invalid socket");
|
||||
SST_OS_DebugAssert(addr != NULL, "Invalid address");
|
||||
SST_OS_DebugAssert(server.GetState() == STATE_UNCONNECTED, "Must be unconnected, use Reset() first");
|
||||
|
||||
if(nrChannels == 0 || nrChannels > UINT8_MAX)
|
||||
return false;
|
||||
|
||||
|
||||
if(!server.Initialize(this, addr, s, nrChannels))
|
||||
return false;
|
||||
|
||||
if(SST_Net_SetNonblock(s, 1) != SSTNETRESULT_SUCCESS)
|
||||
return false;
|
||||
|
||||
//CONNECT always occurs on ZNET_SYSCHANNEL
|
||||
ZNetPacketChannel* channel = server.GetPacketChannel(ZNET_SYSCHANNEL);
|
||||
|
||||
userData = ZNetPrivate::ZNetByteOrder32(userData);
|
||||
|
||||
//Create a packet, copying the userdata
|
||||
ZNetPacket* packet = this->CreatePacket((void*)&userData, sizeof(userData), 0);
|
||||
if(packet == NULL)
|
||||
return false;
|
||||
|
||||
if(!channel->QueueForSending(packet, ZNETCMD_CONNECT))
|
||||
{
|
||||
packet->ReleaseReference();
|
||||
return false;
|
||||
}
|
||||
|
||||
//We're done with this
|
||||
packet->ReleaseReference();
|
||||
|
||||
//Note that we're trying!
|
||||
server.SetState(STATE_HANDSHAKE);
|
||||
return true;
|
||||
}
|
||||
|
||||
/*************************************************************************/
|
||||
|
||||
void ZNetClient::HandlePacket(const uint8_t* data, uint32_t dataSize)
|
||||
{
|
||||
ZBinaryBufferReader reader(data, dataSize, ZNET_BYTEORDER);
|
||||
static int COUNT = 0;
|
||||
|
||||
printf("(%u) Handling packet of %u length", ++COUNT, dataSize);
|
||||
//Valid packet header?
|
||||
if(!ZNetPrivate::ParseWirePacketHeader(&reader))
|
||||
{
|
||||
printf("...but it is invalid\n");
|
||||
return;
|
||||
}
|
||||
|
||||
ZNetPrivate::ZNetMessageContainer container;
|
||||
|
||||
int retval;
|
||||
uint64_t now = SST_OS_GetMilliTime();
|
||||
bool wasValid = true;
|
||||
do
|
||||
{
|
||||
retval = ZNetPrivate::ReadNextMessage(&reader, &container);
|
||||
printf("ReadNextMessage(): %d\n", retval);
|
||||
if(retval > 0)
|
||||
{
|
||||
switch(container.command)
|
||||
{
|
||||
//Server sent how far it is. We can safely act on this now because it does not generate any events.
|
||||
//ACKs themselves do not utilize sequence numbers (they always have 0)
|
||||
case ZNETCMD_ACK:
|
||||
{
|
||||
printf("ZNETCMD_ACK: CH %u: highestRecv = %u, highestSent = %u\n", container.channel, container.parsed.ack.highestReceived, container.parsed.ack.highestSent);
|
||||
//Ensure it is a valid ack
|
||||
if(container.channel < server.GetNumberChannels())
|
||||
{
|
||||
ZNetPacketChannel* channel = server.GetPacketChannel(container.channel);
|
||||
int32_t currentPing = server.GetPing();
|
||||
|
||||
//Update ACKs, calculating an adjusted ping
|
||||
channel->UpdateRemoteAck(container.parsed.ack.highestReceived, ¤tPing);
|
||||
//server.SetPing(currentPing);
|
||||
server.SetPing(75);
|
||||
}
|
||||
else
|
||||
wasValid = false;
|
||||
break;
|
||||
}
|
||||
|
||||
//Server sent data.
|
||||
case ZNETCMD_DATA:
|
||||
{
|
||||
printf("ZNETCMD_DATA found, ch = %u, length = %u, offset = %u, maxSize = %u\n",
|
||||
container.channel, container.parsed.data.length, container.parsed.data.offset,
|
||||
container.parsed.data.maxSize);
|
||||
if(container.channel < server.GetNumberChannels())
|
||||
{
|
||||
ZNetPacketChannel* ch = server.GetPacketChannel(container.channel);
|
||||
|
||||
ch->QueueData(&container);
|
||||
}
|
||||
else
|
||||
wasValid = false;
|
||||
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
//Server sent a connection response
|
||||
case ZNETCMD_CONNRESP:
|
||||
{
|
||||
printf("ZNETCMD_CONNRESP: code = %u\n", container.parsed.connect.userdata);
|
||||
if(container.channel == ZNET_SYSCHANNEL)
|
||||
{
|
||||
const uint32_t reasonCode = container.parsed.connect.userdata; //slightly less typing
|
||||
|
||||
//TODO: flags? can have server full!!!
|
||||
|
||||
|
||||
//Accepted?
|
||||
if(reasonCode == 0)
|
||||
{
|
||||
if(server.GetState() == STATE_HANDSHAKE)
|
||||
{
|
||||
//OK, we're good
|
||||
server.SetState(STATE_CONNECTED);
|
||||
|
||||
ZNetEvent ev;
|
||||
ev.packet = NULL;
|
||||
ev.userdata = reasonCode;
|
||||
ev.remote = &server;
|
||||
ev.type = ZNETEVENT_CONNECT;
|
||||
this->AddEvent(&ev);
|
||||
}
|
||||
|
||||
//We got it, mark that we did.
|
||||
server.GetPacketChannel(ZNET_SYSCHANNEL)->UpdateLocalAck(container.sequence);
|
||||
}
|
||||
else //Not accepted. In this case, the server doesn't consider us a client, so no acks required.
|
||||
{
|
||||
ZNetEvent ev;
|
||||
|
||||
ev.packet = NULL;
|
||||
ev.userdata = reasonCode;
|
||||
ev.remote = &server;
|
||||
ev.type = ZNETEVENT_DISCONNECT;
|
||||
|
||||
this->AddEvent(&ev);
|
||||
|
||||
//TODO: clear out CONNECT command
|
||||
|
||||
}
|
||||
} // if on system channel
|
||||
else //else not accepted.
|
||||
wasValid = false;
|
||||
break;
|
||||
}
|
||||
|
||||
case ZNETCMD_DISCONNECT:
|
||||
SST_OS_DebugError("Not yet implemented!");
|
||||
break;
|
||||
|
||||
//Ignore this command, it doesn't make any sense as a client
|
||||
case ZNETCMD_CONNECT:
|
||||
default:
|
||||
wasValid = false;
|
||||
break;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
} while(retval > 0);
|
||||
|
||||
//If all was valid, update last received time
|
||||
if(wasValid)
|
||||
server.SetLastReceived(now);
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,145 @@
|
||||
/*
|
||||
ZNetHost.cpp
|
||||
Author: Patrick Baggett <ptbaggett@762studios.com>
|
||||
Created: 6/4/2013
|
||||
|
||||
Purpose:
|
||||
|
||||
ZNet host class, represents the local system
|
||||
|
||||
License:
|
||||
|
||||
Copyright 2013, 762 Studios
|
||||
*/
|
||||
|
||||
#include <ZNet/ZNetHost.hpp>
|
||||
#include <ZNet/ZNetPacket.hpp>
|
||||
#include <ZNet/ZNetConsts.hpp>
|
||||
#include <ZNet/ZNetPeer.hpp>
|
||||
#include <SST/SST_Alloc.h> //SST_OS_Alloca()
|
||||
#include "ZNetPrivate.hpp"
|
||||
|
||||
#include <SST/SST_OS.h>
|
||||
#include <ZUtil/ZBinaryBufferReader.hpp>
|
||||
|
||||
#include <stdio.h> //HACKHACK
|
||||
|
||||
/*************************************************************************/
|
||||
|
||||
ZNetHost::ZNetHost()
|
||||
{
|
||||
mtu = ZNET_MTU_IPV4SAFE; //Safe, but small MTU as a default
|
||||
dropChance = 0;
|
||||
}
|
||||
|
||||
/*************************************************************************/
|
||||
|
||||
ZNetPacket* ZNetHost::CreatePacket(const void* initData, uint32_t dataSize, uint32_t flags)
|
||||
{
|
||||
ZNetPacket* packet;
|
||||
|
||||
//Validate that ONLY legitimate bits are set
|
||||
if(flags != 0)
|
||||
return NULL;
|
||||
|
||||
//Allocate packet structure + data
|
||||
packet = (ZNetPacket*)malloc(sizeof(ZNetPacket) + dataSize);
|
||||
if(packet == NULL)
|
||||
return NULL;
|
||||
|
||||
packet->flags = flags;
|
||||
packet->dataSize = dataSize;
|
||||
packet->refCount = 1;
|
||||
|
||||
if(initData != NULL)
|
||||
memcpy(packet->GetData(), initData, dataSize);
|
||||
|
||||
return packet;
|
||||
}
|
||||
|
||||
/*************************************************************************/
|
||||
|
||||
bool ZNetHost::GetNextEvent(ZNetEvent* eventReturn)
|
||||
{
|
||||
if(!events.Empty())
|
||||
{
|
||||
*eventReturn = events.PopFront();
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/*************************************************************************/
|
||||
|
||||
bool ZNetHost::SendToPeer(ZNetPeer* peer)
|
||||
{
|
||||
uint8_t* data = (uint8_t*)SST_OS_Alloca(GetMTU());
|
||||
bool unsent = false;
|
||||
|
||||
//TODO: No bandwidth allocations done here
|
||||
//TODO: send in round-robin fashion across channels
|
||||
|
||||
ZBinaryBufferWriter writer(data, GetMTU(), ZNET_BYTEORDER);
|
||||
ZNetPrivate::WriteWirePacketHeader(&writer);
|
||||
|
||||
//OUTER LOOP: for each channel
|
||||
for(uint32_t i=0; i<peer->GetNumberChannels(); i++)
|
||||
{
|
||||
ZNetPacketChannel* channel = peer->GetPacketChannel(i);
|
||||
bool moreInChannel = true;
|
||||
uint32_t index = 0;
|
||||
uint32_t newIndex;
|
||||
uint32_t nrWritten;
|
||||
|
||||
//INNER LOOP: fill buffer with channel data
|
||||
do
|
||||
{
|
||||
uint32_t remain = mtu - (uint32_t)writer.GetOffset();
|
||||
|
||||
if(!channel->FillBuffer((uint8_t*)writer.GetBufferWriteAddress(), remain, index, &newIndex, &nrWritten))//TODO: update to make use of ZBinaryBufferWriter interface
|
||||
{
|
||||
//Buffer is full, send now
|
||||
index = newIndex;
|
||||
writer.SeekForward(nrWritten);
|
||||
if(!ZNetPrivate::SendAll(peer->GetSocket(), peer->GetNetAddress(), data, (uint32_t)writer.GetOffset()))
|
||||
{
|
||||
printf("SendToPeer FAILED %u bytes\n", (uint32_t)writer.GetOffset());
|
||||
return false;
|
||||
}
|
||||
printf("SendToPeer -> Sent %u bytes\n", (uint32_t)writer.GetOffset());
|
||||
|
||||
//Restart!
|
||||
writer.Rewind();
|
||||
ZNetPrivate::WriteWirePacketHeader(&writer);
|
||||
unsent = false;
|
||||
}
|
||||
else //Done with this channel
|
||||
{
|
||||
if(nrWritten > 0 && !unsent)
|
||||
{
|
||||
unsent = true;
|
||||
writer.SeekForward(nrWritten);
|
||||
}
|
||||
moreInChannel = false;
|
||||
|
||||
}
|
||||
|
||||
} while(moreInChannel);
|
||||
|
||||
}
|
||||
|
||||
//Any unsent data?
|
||||
if(unsent)
|
||||
{
|
||||
//Then send it!
|
||||
if(!ZNetPrivate::SendAll(peer->GetSocket(), peer->GetNetAddress(), data, (uint32_t)writer.GetOffset()))
|
||||
{
|
||||
printf("SendToPeer FAILED %u bytes\n", (uint32_t)writer.GetOffset());
|
||||
return false;
|
||||
}
|
||||
printf("SendToPeer -> Sent %u bytes\n", (uint32_t)writer.GetOffset());
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
@@ -0,0 +1,506 @@
|
||||
/*
|
||||
ZNetPacketChannel.cpp
|
||||
Author: Patrick Baggett <ptbaggett@762studios.com>
|
||||
Created: 6/14/2013
|
||||
|
||||
Purpose:
|
||||
|
||||
** NOT PART OF PUBLIC SDK **
|
||||
This class is not part of the public SDK; its fields and methods are not present
|
||||
in the documentation and cannot be guaranteed in future revisions.
|
||||
** NOT PART OF PUBLIC SDK **
|
||||
|
||||
Queue of incoming and outgoing packets.
|
||||
|
||||
License:
|
||||
|
||||
Copyright 2013, 762 Studios
|
||||
*/
|
||||
|
||||
/*************************************************************************/
|
||||
|
||||
#include <ZNet/ZNetPacketChannel.hpp>
|
||||
#include <ZNet/ZNetPacket.hpp>
|
||||
#include <ZNet/ZNetHost.hpp>
|
||||
#include <ZUtil/ZBinaryBufferWriter.hpp>
|
||||
#include <ZSTL/ZListAlgo.hpp>
|
||||
#include <SST/SST_OS.h>
|
||||
#include "ZNetPrivate.hpp"
|
||||
|
||||
/*************************************************************************/
|
||||
|
||||
bool ZNetPacketChannel::QueueForSending(ZNetPacket* packet, uint32_t command)
|
||||
{
|
||||
printf("QueueForSending(): This sequence = %u\n", this->nextSequenceNumber);
|
||||
|
||||
//Check for a packet overflow
|
||||
if(packetCount+1 > overflowLimit)
|
||||
{
|
||||
printf("ZNetPacketChannel::QueueForSending(): Packet channel overflow!\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
//Since we're keeping this packet in this channel, increase its reference count
|
||||
packet->AddReference();
|
||||
|
||||
//Wrap this packet with some metadata
|
||||
ZNetQueuedPacket qp;
|
||||
qp.timeSent = 0;
|
||||
qp.packet = packet;
|
||||
qp.command = command;
|
||||
qp.subsequence = 0;
|
||||
qp.sequence = this->nextSequenceNumber;
|
||||
|
||||
|
||||
//Next sequence
|
||||
this->nextSequenceNumber++;
|
||||
|
||||
packets.PushBack(qp);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/*************************************************************************/
|
||||
|
||||
bool ZNetPacketChannel::FillBuffer(uint8_t* buffer, uint32_t bufferSize, uint32_t packetStartIndex, uint32_t* restartIndexReturn, uint32_t* bytesWritten)
|
||||
{
|
||||
ZList<ZNetQueuedPacket>::Iterator it = packets.Begin();
|
||||
uint32_t bufferSpaceRemaining = bufferSize;
|
||||
uint32_t nrWritten = 0;
|
||||
|
||||
//Skip to appropriate point in the list
|
||||
while(packetStartIndex > 0)
|
||||
{
|
||||
++it;
|
||||
--packetStartIndex;
|
||||
}
|
||||
|
||||
ZBinaryBufferWriter writer(buffer, bufferSize, ZNET_BYTEORDER);
|
||||
|
||||
//Write packets
|
||||
uint64_t now = SST_OS_GetMilliTime();
|
||||
do
|
||||
{
|
||||
//Nothing to do?
|
||||
if(it == packets.End())
|
||||
break;
|
||||
|
||||
ZNetQueuedPacket& qp = it.Get();
|
||||
uint32_t payloadSpace = bufferSpaceRemaining - ZNET_WIRESIZE_MESSAGE_HEADER; //This is how much payload we can handle.
|
||||
|
||||
//No space for a payload?
|
||||
if(payloadSpace == 0)
|
||||
break;
|
||||
|
||||
/*
|
||||
All packets other than DATA are actually quite small, and as such they are not fragmented -- either
|
||||
they fit into the buffer or they don't. The data packets can easily exceed an MTU. Because of that,
|
||||
they are fragmented at the byte level using the subsequence value. This allows us to fully fill MTU-
|
||||
sized packets as often as possible, however, it requires some special handling because fields are
|
||||
inserted into the front that vary in size.
|
||||
*/
|
||||
|
||||
uint8_t* packetData;
|
||||
uint32_t sendSize = 0; //MSVC 2012 complains about uninitialized
|
||||
|
||||
//Data packets are somewhat special
|
||||
if(qp.command == ZNETCMD_DATA)
|
||||
{
|
||||
SST_OS_DebugAssert(sendSize > 0, "Data packet has been fully confirmed, but still attempting to send?");
|
||||
const uint32_t leftToSend = qp.packet->dataSize - qp.subsequence;
|
||||
const uint32_t dataOverhead = 3*ZNetPrivate::PackedIntegerSize(qp.packet->dataSize); //3 fields, see ZNetPrivate.hpp
|
||||
|
||||
//Not enough space?
|
||||
if(dataOverhead >= payloadSpace)
|
||||
break;
|
||||
|
||||
//Subtract overhead from payload to get how much payload we can really deliver.
|
||||
payloadSpace -= dataOverhead;
|
||||
|
||||
//Can we fit all of the remaining data in this packet?
|
||||
if(leftToSend < payloadSpace)
|
||||
sendSize = leftToSend;
|
||||
else
|
||||
sendSize = payloadSpace; //No, so just send as much as we can
|
||||
|
||||
|
||||
printf("Sending %u bytes of ZNETCMD_DATA payload\n", sendSize);
|
||||
packetData = (qp.packet->GetData() + qp.subsequence);
|
||||
}
|
||||
else
|
||||
{
|
||||
packetData = qp.packet->GetData();
|
||||
sendSize = qp.packet->dataSize;
|
||||
printf("Sending %u bytes of regular (non-ZNETCMD_DATA)\n", sendSize);
|
||||
}
|
||||
qp.timeSent = now;
|
||||
|
||||
SST_OS_DebugAssert(sendSize != 0, "Should not have 0-sized value");
|
||||
|
||||
//Write the header and then the data
|
||||
ZNetPrivate::WriteWireMessageHeader(&writer, this->channelId, qp.packet->flags, qp.command, qp.sequence);
|
||||
writer.WriteU8Array(packetData, sendSize);
|
||||
|
||||
|
||||
//Record stats and advance to next packet
|
||||
nrWritten += (sendSize + ZNET_WIRESIZE_MESSAGE_HEADER); //i.e. payload + header
|
||||
packetStartIndex++;
|
||||
bufferSpaceRemaining -= (sendSize + ZNET_WIRESIZE_MESSAGE_HEADER);
|
||||
++it;
|
||||
} while(bufferSpaceRemaining > 0);
|
||||
|
||||
bool allDone = (it == packets.End());
|
||||
|
||||
//If a restart is needed, record where we left off.
|
||||
if(!allDone)
|
||||
*restartIndexReturn = packetStartIndex;
|
||||
|
||||
//Save the number of bytes written
|
||||
*bytesWritten = nrWritten;
|
||||
|
||||
return allDone;
|
||||
}
|
||||
|
||||
/*************************************************************************/
|
||||
|
||||
void ZNetPacketChannel::Deinitialize()
|
||||
{
|
||||
|
||||
//Unreference all packets and remove them from queues
|
||||
for(ZList<ZNetQueuedPacket>::Iterator it=packets.Begin(); it.HasCurrent(); it.Next())
|
||||
it.Get().packet->ReleaseReference();
|
||||
packets.Clear();
|
||||
|
||||
for(ZList<ZNetDataReassemblyPacket>::Iterator it=reassembly.Begin(); it.HasCurrent(); it.Next())
|
||||
it.Get().packet->ReleaseReference();
|
||||
reassembly.Clear();
|
||||
|
||||
for(ZList<ZNetQueuedPacket>::Iterator it=assembled.Begin(); it.HasCurrent(); it.Next())
|
||||
it.Get().packet->ReleaseReference();
|
||||
assembled.Clear();
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
/*************************************************************************/
|
||||
|
||||
void ZNetPacketChannel::UpdateRemoteAck(uint16_t newHighest, int32_t* pingAdjust)
|
||||
{
|
||||
printf("ZNetPacketChannel::UpdateRemoteAck(): current ack = %u, newHighest = %u\n", remoteAck, newHighest);
|
||||
//Nothing new
|
||||
if(newHighest == remoteAck)
|
||||
return;
|
||||
|
||||
uint16_t ackCount;
|
||||
|
||||
//Wrap around
|
||||
if(newHighest < remoteAck)
|
||||
{
|
||||
/*
|
||||
In a wrap around, we exceed the the fixed point notation of the next sequence number. To
|
||||
handle it, we count from where we are at to the highest possible value, then add the new
|
||||
local number -- this is the number of packets to dequeue. For example, if our window was
|
||||
100 and we were at 97, then "ack 4" means that 98, 99, 100, 1, 2, 3, 4 were received. We
|
||||
compute it as 100 - 97 + 4 == 7.
|
||||
*/
|
||||
ackCount = UINT16_MAX - remoteAck + newHighest;
|
||||
}
|
||||
else
|
||||
ackCount = newHighest - remoteAck;
|
||||
|
||||
printf("AckCount = %u\n", ackCount);
|
||||
if(ackCount > 0)
|
||||
{
|
||||
//Freeze time value now
|
||||
uint64_t now = SST_OS_GetMilliTime();
|
||||
int32_t currentPing = *pingAdjust;
|
||||
|
||||
while(ackCount > 0 && !packets.Empty())
|
||||
{
|
||||
ZNetQueuedPacket& qp = packets.Front();
|
||||
|
||||
|
||||
//Does this packet have a valid timeSent?
|
||||
if(qp.timeSent != 0)
|
||||
{
|
||||
|
||||
//Find out how much the RTT differs from the ping
|
||||
int32_t delta = currentPing - ((int32_t)(now - qp.timeSent));
|
||||
//Adjust ping by a fraction of RTT to smooth it out.
|
||||
currentPing += delta / 10;
|
||||
}
|
||||
|
||||
qp.packet->ReleaseReference();
|
||||
packets.PopFront();
|
||||
printf("Removed a packet!\n");
|
||||
ackCount--;
|
||||
this->packetCount--;
|
||||
}
|
||||
|
||||
//Save updated ping
|
||||
*pingAdjust = currentPing;
|
||||
}
|
||||
|
||||
//Because malicious clients can send anything, don't explode if this packet suggests
|
||||
//that we remove more than exist.
|
||||
//TODO: maybe log it? probably can help with debugging
|
||||
printf("Possibly wrong sequencing - this tells us to ACK more than we've sent\n");
|
||||
}
|
||||
|
||||
/*************************************************************************/
|
||||
|
||||
bool ZNetPacketChannel::QueueLocally(const ZNetPacketChannel::ZNetQueuedPacket* toQueue)
|
||||
{
|
||||
uint16_t seq = toQueue->sequence;
|
||||
|
||||
ZList<ZNetQueuedPacket>::Iterator it = assembled.Begin();
|
||||
|
||||
while(it.HasCurrent())
|
||||
{
|
||||
ZNetQueuedPacket& qp = it.Get();
|
||||
|
||||
if(ZNetPrivate::SequenceAfter(seq, qp.sequence)) //Incoming packet sequence number is after this packet
|
||||
it.Next();
|
||||
else if(ZNetPrivate::SequenceAfter(qp.sequence, seq)) //Incoming packet sequence number is before this packet
|
||||
{
|
||||
ZNetQueuedPacket newEntry;
|
||||
|
||||
newEntry.command = toQueue->command;
|
||||
newEntry.sequence = toQueue->sequence;
|
||||
newEntry.subsequence = toQueue->subsequence;
|
||||
newEntry.packet = toQueue->packet;
|
||||
newEntry.timeSent = toQueue->timeSent;
|
||||
|
||||
//Add it in
|
||||
assembled.Insert(it, newEntry);
|
||||
break;
|
||||
}
|
||||
else if(qp.sequence == seq) //Overwrite old data
|
||||
{
|
||||
if(qp.packet != NULL)
|
||||
{
|
||||
qp.packet->ReleaseReference();
|
||||
qp.packet = toQueue->packet;
|
||||
}
|
||||
break;
|
||||
}
|
||||
} //end
|
||||
|
||||
|
||||
|
||||
//Doesn't appear to be valid
|
||||
return false;
|
||||
}
|
||||
|
||||
/*************************************************************************/
|
||||
|
||||
bool ZNetPacketChannel::QueueData(ZNetPrivate::ZNetMessageContainer* data)
|
||||
{
|
||||
ZList<ZNetDataReassemblyPacket>::Iterator it = reassembly.Begin();
|
||||
|
||||
/*
|
||||
Check for invalid offsets into packet.
|
||||
|
||||
The second and third checks look redundant, but for extremely large
|
||||
values, summing them can wrap to a smaller value, e.g. 0xFFFFFFFF + 0x0003
|
||||
will give 0x0002, and crash later when copying / allocating memory.
|
||||
*/
|
||||
if(data->parsed.data.offset + data->parsed.data.length > data->parsed.data.maxSize ||
|
||||
data->parsed.data.offset >= data->parsed.data.maxSize ||
|
||||
data->parsed.data.length >= data->parsed.data.maxSize)
|
||||
return false;
|
||||
|
||||
//If whole data logical packet was received
|
||||
if(data->parsed.data.length == data->parsed.data.maxSize)
|
||||
{
|
||||
if(data->parsed.data.offset == 0)
|
||||
{
|
||||
ZNetQueuedPacket qp;
|
||||
ZNetPacket* packet = GetHost()->CreatePacket(data->parsed.data.data, data->parsed.data.length, 0);
|
||||
|
||||
if(packet == NULL)//Out of memory
|
||||
return false;
|
||||
|
||||
qp.command = ZNETCMD_DATA;
|
||||
qp.packet = packet;
|
||||
qp.sequence = data->sequence;
|
||||
qp.timeSent = SST_OS_GetMilliTime();
|
||||
qp.subsequence = 0; //field not used
|
||||
return this->QueueLocally(&qp);
|
||||
}
|
||||
else
|
||||
return false; //invalid packet
|
||||
|
||||
}
|
||||
|
||||
//Required reassembly
|
||||
while(it.HasNext())
|
||||
{
|
||||
ZNetDataReassemblyPacket& reasm = it.Get();
|
||||
|
||||
//If the new packet comes after this packet, try next
|
||||
if(ZNetPrivate::SequenceAfter(data->sequence, reasm.sequence))
|
||||
{
|
||||
it.Next();
|
||||
continue;
|
||||
}
|
||||
|
||||
|
||||
if(reasm.sequence == data->sequence)
|
||||
{
|
||||
SST_OS_DebugAssert(reasm.packet != NULL, "Should not have NULL packet here");
|
||||
const uint32_t maxSize = reasm.packet->dataSize;
|
||||
|
||||
//TODO: when done debugging, remove this assert as untrusted input should not cause a crash
|
||||
SST_OS_DebugAssert(maxSize == data->parsed.data.maxSize, "Does not match.");
|
||||
if(maxSize != data->parsed.data.maxSize)
|
||||
return false;
|
||||
|
||||
const uint32_t begin = data->parsed.data.offset;
|
||||
const uint32_t end = begin + data->parsed.data.length;
|
||||
|
||||
//Does this packet contain data that can be appended directly?
|
||||
if(begin <= reasm.subsequence && end > reasm.subsequence)
|
||||
{
|
||||
uint8_t* rawbits = reasm.packet->GetData(); //Get raw logical packet
|
||||
|
||||
/* Copy from the subsequence point to the end point. This can be less than the incoming amount:
|
||||
|
||||
reasm.subsequence = 3
|
||||
|
||||
filled
|
||||
xxxxxxxxx
|
||||
[0][1][2][3][4][5][6][7][8]
|
||||
^-----^
|
||||
new packet. offset = 2, length = 3.
|
||||
|
||||
In this case, we copy only `end - subsequence` == 5 - 3 == 2 bytes.
|
||||
*/
|
||||
memcpy(rawbits+reasm.subsequence, data->parsed.data.data, end - reasm.subsequence);
|
||||
reasm.subsequence = end;
|
||||
|
||||
//Packet is fully assembled
|
||||
if(end == maxSize)
|
||||
{
|
||||
ZNetQueuedPacket qp;
|
||||
qp.command = ZNETCMD_DATA;
|
||||
qp.packet = reasm.packet;
|
||||
qp.sequence = data->sequence;
|
||||
qp.timeSent = SST_OS_GetMilliTime();
|
||||
qp.subsequence = 0; //field not used
|
||||
return this->QueueLocally(&qp);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
else //This packet comes before it.Get()
|
||||
{
|
||||
ZNetDataReassemblyPacket newAssembly;
|
||||
|
||||
ZNetPacket* packet = GetHost()->CreatePacket(NULL, data->parsed.data.maxSize, 0);
|
||||
if(packet == NULL)
|
||||
return false;
|
||||
|
||||
//Copy when offset is zero, otherwise, the other side is going to do a resend
|
||||
//anyways.
|
||||
if(data->parsed.data.offset == 0)
|
||||
{
|
||||
memcpy(packet->GetData(), data->parsed.data.data, data->parsed.data.length);
|
||||
newAssembly.subsequence = data->parsed.data.length;
|
||||
}
|
||||
else
|
||||
newAssembly.subsequence = 0;
|
||||
newAssembly.packet = packet;
|
||||
newAssembly.sequence = data->sequence;
|
||||
|
||||
//Insert before 'it'
|
||||
reassembly.Insert(it, newAssembly);
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/*************************************************************************/
|
||||
|
||||
void ZNetPacketChannel::UpdateLocalAck(uint16_t seq)
|
||||
{
|
||||
|
||||
sequencesFound.PushBack(seq);
|
||||
}
|
||||
|
||||
/*************************************************************************/
|
||||
|
||||
void ZNetPacketChannel::ProcessLocalAcks()
|
||||
{
|
||||
ZListAlgo::Sort(sequencesFound);
|
||||
|
||||
ZList<uint16_t>::Iterator it = sequencesFound.Begin();
|
||||
int32_t firstFound = -1; //This is the first value found before wraparounds
|
||||
|
||||
/*
|
||||
We want to find consecutive values starting at "localAck" and continuing. This takes two
|
||||
passes because there can be a wrap around and the data is sorted ascending, but values
|
||||
lower than "localAck" logically come _after_ it due to the wraparound.
|
||||
|
||||
In the first pass, we ignore wraparounds (i.e. localAck > seq). We check to see
|
||||
if the next is expected value of localAck+1. If it is, we bump the counter and check the
|
||||
next number. Stop when the list is reached or a discontinuity found.
|
||||
|
||||
In the second pass, we handle wraparounds, stopping when we reach the first value accepted
|
||||
in pass 1.
|
||||
*/
|
||||
|
||||
//PASS 1: handling pre-wrap arounds
|
||||
while(it.HasCurrent())
|
||||
{
|
||||
uint16_t thisSeq = it.Get();
|
||||
|
||||
//Skip smaller values as they are wraparound values
|
||||
if(thisSeq > localAck)
|
||||
{
|
||||
if(localAck+1 == thisSeq) //because > operator succeeded, we know that localAck+1 cannot overflow
|
||||
{
|
||||
if(firstFound == -1)
|
||||
firstFound = (int32_t)thisSeq;
|
||||
|
||||
printf("Acked a packet!\n");
|
||||
localAck++;
|
||||
}
|
||||
else
|
||||
{
|
||||
//discontinuity, stop here
|
||||
sequencesFound.Clear();
|
||||
return;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
//Next value
|
||||
it.Next();
|
||||
}
|
||||
|
||||
//If we made it here, we didn't hit any discontinuities (or the list was empty)
|
||||
|
||||
//PASS 2: handling wrap arounds
|
||||
it = sequencesFound.Begin();
|
||||
while(it.HasCurrent() && it.Get() != (uint16_t)firstFound)
|
||||
{
|
||||
uint16_t thisSeq = it.Get();
|
||||
|
||||
if(thisSeq != (uint16_t)firstFound && //stop when we hit the first value we processed in pass 1
|
||||
thisSeq == localAck+1)//localAck+1 may wraparound now
|
||||
{
|
||||
printf("(WRAP) Acked a packet!\n");
|
||||
localAck++;
|
||||
}
|
||||
else //discontinuity hit
|
||||
break;
|
||||
|
||||
}
|
||||
|
||||
//Now, remove all acks
|
||||
sequencesFound.Clear();
|
||||
}
|
||||
@@ -0,0 +1,156 @@
|
||||
/*
|
||||
ZNetPeer.cpp
|
||||
Author: Patrick Baggett <ptbaggett@762studios.com>
|
||||
Created: 7/18/2013
|
||||
|
||||
Purpose:
|
||||
|
||||
ZNet peer class, representing a remote host
|
||||
|
||||
License:
|
||||
|
||||
Copyright 2013, 762 Studios
|
||||
*/
|
||||
|
||||
/*************************************************************************/
|
||||
|
||||
#include <ZNet/ZNetPeer.hpp>
|
||||
#include <ZNet/ZNetPacketChannel.hpp>
|
||||
#include <ZNet/ZNetHost.hpp>
|
||||
#include "ZNetPrivate.hpp"
|
||||
#include <SST/SST_OS.h>
|
||||
#include <new>
|
||||
|
||||
|
||||
/*************************************************************************/
|
||||
|
||||
ZNetPeer::ZNetPeer()
|
||||
{
|
||||
lastValidIncoming = 0;
|
||||
lastOutgoingAck = 0;
|
||||
socketCopy = 0;
|
||||
channels = NULL;
|
||||
userdata = NULL;
|
||||
nrChannels = 0;
|
||||
ping = -1;
|
||||
state = STATE_UNCONNECTED;
|
||||
}
|
||||
|
||||
/*************************************************************************/
|
||||
|
||||
bool ZNetPeer::Initialize(ZNetHost* _host, const SST_NetAddress* newAddr, SST_Socket s, uint32_t _nrChannels)
|
||||
{
|
||||
SST_OS_DebugAssert(_host != NULL, "Host may not be NULL");
|
||||
SST_OS_DebugAssert(channels == NULL, "This should have been NULL; memory leak!");
|
||||
|
||||
channels = new(std::nothrow) ZNetPacketChannel[_nrChannels];
|
||||
if(channels == NULL)
|
||||
return false;
|
||||
|
||||
//Initialize channel ID (new[] doesn't allow non-default constructors)
|
||||
for(uint32_t i=0; i<_nrChannels; i++)
|
||||
{
|
||||
channels[i].SetChannelId(i);
|
||||
channels[i].SetHost(_host);
|
||||
}
|
||||
|
||||
//Store other properties
|
||||
memcpy(&addr, newAddr, sizeof(SST_NetAddress));
|
||||
nrChannels = _nrChannels;
|
||||
socketCopy = s;
|
||||
userdata = NULL;
|
||||
lastValidIncoming = SST_OS_GetMilliTime();
|
||||
state = STATE_HANDSHAKE;
|
||||
|
||||
//OK
|
||||
return true;
|
||||
}
|
||||
|
||||
/*************************************************************************/
|
||||
|
||||
void ZNetPeer::Deinitialize()
|
||||
{
|
||||
for(uint32_t i=0; i<nrChannels; i++)
|
||||
channels[i].Deinitialize();
|
||||
|
||||
delete[] channels;
|
||||
}
|
||||
|
||||
/*************************************************************************/
|
||||
|
||||
ZNetPacketChannel* ZNetPeer::GetPacketChannel(uint32_t chId)
|
||||
{
|
||||
//Really, this is trivial, but I don't want asserts to be conditional on whether
|
||||
//the user of the API has enabled them vs how the library was built.
|
||||
SST_OS_DebugAssert(chId < nrChannels, "Invalid channel number");
|
||||
|
||||
return &channels[chId];
|
||||
}
|
||||
|
||||
/*************************************************************************/
|
||||
|
||||
void ZNetPeer::ProcessLocalAcks()
|
||||
{
|
||||
for(uint32_t i=0; i<nrChannels; i++)
|
||||
channels[i].ProcessLocalAcks();
|
||||
}
|
||||
|
||||
/*************************************************************************/
|
||||
|
||||
void ZNetPeer::SendAcksForAllChannels()
|
||||
{
|
||||
uint8_t data[9000];
|
||||
uint32_t mtu = this->GetPacketChannel(ZNET_SYSCHANNEL)->GetHost()->GetMTU();
|
||||
bool unsent = true;
|
||||
|
||||
uint64_t now = SST_OS_GetMilliTime();
|
||||
|
||||
//TODO: maybe not the best idea...? it will start out by sending an ACK 0...kinda silly I guess
|
||||
if(lastOutgoingAck == 0 ||
|
||||
((ping > 0) && (now > lastOutgoingAck + (uint64_t)ping) && (now - lastOutgoingAck > 50)) ) //TODO: does a minimum ack latency of 50 make any sense? in the cases where ping is realllly low (e.g. <= 1msec) don't need to spam?
|
||||
{
|
||||
lastOutgoingAck = now;
|
||||
}
|
||||
else
|
||||
return;
|
||||
|
||||
|
||||
ZBinaryBufferWriter writer(data, mtu, ZNET_BYTEORDER);
|
||||
|
||||
//Write the write header
|
||||
ZNetPrivate::WriteWirePacketHeader(&writer);
|
||||
|
||||
//Write an ACK point for each channel
|
||||
for(uint32_t i=0; i<nrChannels; i++)
|
||||
{
|
||||
ZNetPacketChannel* channel = GetPacketChannel(i);
|
||||
uint32_t remain = mtu - (uint32_t)writer.GetOffset();
|
||||
|
||||
//Can we fit another ack?
|
||||
if(remain > ZNetPrivate::WireSizeForSimpleCommand(ZNETCMD_ACK))
|
||||
{
|
||||
//TODO: flags??
|
||||
ZNetPrivate::WriteWireMessageHeader(&writer, i, 0, ZNETCMD_ACK, 0);
|
||||
|
||||
writer.WriteU16(channel->GetLocalAck());
|
||||
writer.WriteU16(channel->GetHighestSent());
|
||||
|
||||
unsent = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
ZNetPrivate::SendAll(socketCopy, &addr, data, (uint32_t)writer.GetOffset());
|
||||
unsent = false;
|
||||
|
||||
//Restart
|
||||
writer.Rewind();
|
||||
ZNetPrivate::WriteWirePacketHeader(&writer);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if(unsent)
|
||||
ZNetPrivate::SendAll(socketCopy, &addr, data, (uint32_t)writer.GetOffset());
|
||||
}
|
||||
|
||||
/*************************************************************************/
|
||||
@@ -0,0 +1,174 @@
|
||||
/*
|
||||
ZNetPrivate.cpp
|
||||
Author: Patrick Baggett <ptbaggett@762studios.com>
|
||||
Created: 7/19/2013
|
||||
|
||||
Purpose:
|
||||
|
||||
ZNet private functions and definitions
|
||||
|
||||
License:
|
||||
|
||||
Copyright 2013, 762 Studios
|
||||
*/
|
||||
|
||||
#include "ZNetPrivate.hpp"
|
||||
#include <ZNet/ZNetPeer.hpp>
|
||||
#include <SST/SST_Assert.h>
|
||||
|
||||
/*************************************************************************/
|
||||
|
||||
bool ZNetPrivate::ParseWirePacketHeader(ZBinaryBufferReader* reader)
|
||||
{
|
||||
//Packet is absolutely too small to have any useful data (i.e. size < minimum overhead)
|
||||
if(reader->GetLength() < ZNET_WIRESIZE_PACKET_MINIMUM)
|
||||
return false;
|
||||
|
||||
if(reader->ReadU8() != ZNET_MAGIC)
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/*************************************************************************/
|
||||
|
||||
void ZNetPrivate::WriteWirePacketHeader(ZBinaryBufferWriter* writer)
|
||||
{
|
||||
writer->WriteU8(ZNET_MAGIC);
|
||||
}
|
||||
|
||||
/*************************************************************************/
|
||||
|
||||
void ZNetPrivate::WriteWireMessageHeader(ZBinaryBufferWriter* writer, uint32_t channel, uint32_t flags, uint32_t command, uint16_t sequenceNumber)
|
||||
{
|
||||
uint8_t merged = (uint8_t)(((flags & 0x0F) << 4) | (command & 0x0F));
|
||||
|
||||
writer->WriteU8((uint8_t)channel);
|
||||
writer->WriteU8(merged);
|
||||
writer->WriteU16(sequenceNumber);
|
||||
}
|
||||
|
||||
/*************************************************************************/
|
||||
|
||||
bool ZNetPrivate::SendAll(SST_Socket s, const SST_NetAddress* addr, uint8_t* data, uint32_t length)
|
||||
{
|
||||
size_t totalSent;
|
||||
if(SST_Net_SendTo(s, data, length, 0, addr, &totalSent) == SSTNETRESULT_SUCCESS)
|
||||
{
|
||||
//Return true if and only if all data was sent
|
||||
return ((uint32_t)totalSent == length);
|
||||
}
|
||||
|
||||
//Failed to send at all... :(
|
||||
return false;
|
||||
}
|
||||
|
||||
/*************************************************************************/
|
||||
|
||||
int ZNetPrivate::ReadNextMessage(ZBinaryBufferReader* reader, ZNetPrivate::ZNetMessageContainer* msgReturn)
|
||||
{
|
||||
if(reader->GetLength() - reader->GetOffset() == 0)
|
||||
return 0;
|
||||
if(!reader->CanRead(ZNET_WIRESIZE_MESSAGE_HEADER))
|
||||
return -1; //error in stream
|
||||
|
||||
//Read channel
|
||||
msgReturn->channel = reader->ReadU8();
|
||||
|
||||
//Read command+flags (merged 8-bit value)
|
||||
uint8_t cmdAndFlags = reader->ReadU8();
|
||||
|
||||
msgReturn->command = (cmdAndFlags & 0x0F) >> 0;
|
||||
msgReturn->flags = (cmdAndFlags & 0xF0) >> 4;
|
||||
|
||||
msgReturn->sequence = reader->ReadU16();
|
||||
|
||||
//Validate the command is OK
|
||||
if(!ZNetPrivate::IsValidCommand(msgReturn->command))
|
||||
return -1;
|
||||
|
||||
//TODO: validate flags for each command.
|
||||
|
||||
//Ensure we can read at least the minimum number of bytes required for the command
|
||||
if(!reader->CanRead(ZNetPrivate::MinimumSizeForCommand(msgReturn->command)))
|
||||
return -1;
|
||||
|
||||
switch(msgReturn->command)
|
||||
{
|
||||
case ZNETCMD_CONNECT:
|
||||
case ZNETCMD_DISCONNECT:
|
||||
case ZNETCMD_CONNRESP:
|
||||
{
|
||||
msgReturn->parsed.connect.userdata = reader->ReadU32();
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
case ZNETCMD_DATA:
|
||||
{
|
||||
if(msgReturn->flags & ZNETCMDFLAGS_LEN8)
|
||||
{
|
||||
msgReturn->parsed.data.offset = reader->ReadU8();
|
||||
msgReturn->parsed.data.maxSize = reader->ReadU8();
|
||||
msgReturn->parsed.data.length = reader->ReadU8();
|
||||
}
|
||||
else if(msgReturn->flags & ZNETCMDFLAGS_LEN16)
|
||||
{
|
||||
msgReturn->parsed.data.offset = reader->ReadU16();
|
||||
msgReturn->parsed.data.maxSize = reader->ReadU16();
|
||||
msgReturn->parsed.data.length = reader->ReadU16();
|
||||
}
|
||||
else if(msgReturn->flags & ZNETCMDFLAGS_LEN32)
|
||||
{
|
||||
msgReturn->parsed.data.offset = reader->ReadU32();
|
||||
msgReturn->parsed.data.maxSize = reader->ReadU32();
|
||||
msgReturn->parsed.data.length = reader->ReadU32();
|
||||
}
|
||||
msgReturn->parsed.data.data = (uint8_t*)reader->GetBufferReadAddress();
|
||||
|
||||
//Verify access
|
||||
if(!reader->CanRead(msgReturn->parsed.data.length))
|
||||
return -1;
|
||||
break;
|
||||
}
|
||||
|
||||
case ZNETCMD_ACK:
|
||||
{
|
||||
msgReturn->parsed.ack.highestReceived = reader->ReadU16();
|
||||
msgReturn->parsed.ack.highestSent = reader->ReadU16();
|
||||
|
||||
//Read 8-bit, 16-bit, or 32-bit subsequence
|
||||
if(msgReturn->flags & ZNETCMDFLAGS_LEN8)
|
||||
msgReturn->parsed.ack.subsequence = reader->ReadU8();
|
||||
else if(msgReturn->flags & ZNETCMDFLAGS_LEN16)
|
||||
msgReturn->parsed.ack.subsequence = reader->ReadU16();
|
||||
else if(msgReturn->flags & ZNETCMDFLAGS_LEN32)
|
||||
msgReturn->parsed.ack.subsequence = reader->ReadU32();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
//Read a full message
|
||||
return 1;
|
||||
}
|
||||
|
||||
/*************************************************************************/
|
||||
|
||||
uint32_t ZNetPrivate::MinimumSizeForCommand(uint32_t command)
|
||||
{
|
||||
uint32_t size;
|
||||
switch(command)
|
||||
{
|
||||
case ZNETCMD_CONNECT: size = sizeof(uint32_t); break;
|
||||
case ZNETCMD_DATA: size = sizeof(uint8_t)*3; break;
|
||||
case ZNETCMD_DISCONNECT: size = sizeof(uint32_t); break;
|
||||
case ZNETCMD_ACK: size = 2*sizeof(uint16_t); break;
|
||||
case ZNETCMD_CONNRESP: size = sizeof(uint32_t); break;
|
||||
default:
|
||||
SST_OS_DebugError("Should not reach here");
|
||||
size = UINT32_MAX;
|
||||
break;
|
||||
}
|
||||
|
||||
return size;
|
||||
}
|
||||
@@ -0,0 +1,243 @@
|
||||
/*
|
||||
ZNetPrivate.hpp
|
||||
Author: Patrick Baggett <ptbaggett@762studios.com>
|
||||
Created: 7/19/2013
|
||||
|
||||
Purpose:
|
||||
|
||||
ZNet private functions and definitions
|
||||
|
||||
License:
|
||||
|
||||
Copyright 2013, 762 Studios
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#ifndef _ZNETPRIVATE_HPP
|
||||
#define _ZNETPRIVATE_HPP
|
||||
|
||||
#include <ZUtil/ZBinaryBufferReader.hpp>
|
||||
#include <ZUtil/ZBinaryBufferWriter.hpp>
|
||||
#include <SST/SST_Net.h>
|
||||
|
||||
//========================================
|
||||
// Wire format constants
|
||||
//========================================
|
||||
|
||||
#define ZNET_BYTEORDER SST_BIG_ENDIAN //< Global byte order for all of ZNet
|
||||
|
||||
|
||||
#define ZNET_MAGIC 0x5A //'Z' in ASCII (i.e. uppercase zed)
|
||||
|
||||
#define ZNET_SYSCHANNEL 0x00u //System channel
|
||||
|
||||
#define ZNETCMD_CONNECT 0x00u //Connect request, sent from client to server
|
||||
#define ZNETCMD_DATA 0x01u //Data packet (or fragment of one)
|
||||
#define ZNETCMD_DISCONNECT 0x02u //Disconnect request
|
||||
#define ZNETCMD_ACK 0x03u //Acknowledge a range of packets, possibly including a subsequence value
|
||||
#define ZNETCMD_CONNRESP 0x04u //Connection response, send from server to client
|
||||
#define ZNETCMD_MAX 0x05u //First invalid value
|
||||
|
||||
//For ZNETCMD_ACK/DATA, the "flag" field will be set to one of these values.
|
||||
#define ZNETCMDFLAGS_LEN8 0x00u
|
||||
#define ZNETCMDFLAGS_LEN16 0x01u
|
||||
#define ZNETCMDFLAGS_LEN32 0x02u
|
||||
|
||||
#define ZNETCMDFLAGS_FULL 0x01u
|
||||
|
||||
/*
|
||||
Wire Format
|
||||
|
||||
RAW PACKET {
|
||||
u8 ZNET_MAGIC
|
||||
[ messages ]
|
||||
}
|
||||
|
||||
|
||||
MESSAGE {
|
||||
u8 channel
|
||||
u4 flags
|
||||
u4 cmd
|
||||
u16 seq
|
||||
[ message data ]
|
||||
}
|
||||
|
||||
CONNECT MESSAGE {
|
||||
u32 userdata
|
||||
}
|
||||
|
||||
DATA MESSAGE {
|
||||
[if flags == ZNETCMDFLAGS_LEN8]
|
||||
u8 offset
|
||||
u8 max
|
||||
u8 length
|
||||
[if flags == ZNETCMDFLAGS_LEN16]
|
||||
u16 offset
|
||||
u16 max
|
||||
u16 length
|
||||
[if flags == ZNETCMDFLAGS_LEN32]
|
||||
u32 offset
|
||||
u32 max
|
||||
u32 length
|
||||
|
||||
u8 data[ length ]
|
||||
}
|
||||
|
||||
DISCONNECT MESSAGE {
|
||||
u32 userdata
|
||||
}
|
||||
|
||||
ACK MESSAGE {
|
||||
u16 highestAcked : I have received up to this point
|
||||
u16 localSequence : I have sent up to this point
|
||||
|
||||
[if flags == ZNETCMDFLAGS_LEN8]
|
||||
u8 subsequence8
|
||||
[if flags == ZNETCMDFLAGS_LEN16]
|
||||
u16 subsequence16
|
||||
[if flags == ZNETCMDFLAGS_LEN32]
|
||||
u32 subsequence32
|
||||
|
||||
}
|
||||
|
||||
|
||||
*/
|
||||
|
||||
#define ZNET_WIRESIZE_PACKET_HEADER (sizeof(uint8_t)) //< Size of a packet header (overhead)
|
||||
#define ZNET_WIRESIZE_MESSAGE_HEADER (2*sizeof(uint8_t) + sizeof(uint16_t)) //< Size of a message header (overhead)
|
||||
|
||||
#define ZNET_WIRESIZE_PACKET_MINIMUM ZNET_WIRESIZE_PACKET_HEADER + ZNET_WIRESIZE_MESSAGE_HEADER
|
||||
|
||||
class ZNetPeer;
|
||||
|
||||
namespace ZNetPrivate
|
||||
{
|
||||
|
||||
//Used for CONNECT, DISCONNECT, CONNRESP
|
||||
struct ZNetMessageConnect
|
||||
{
|
||||
uint32_t userdata;
|
||||
};
|
||||
|
||||
//Used for ACK, ACKSUBSEQ
|
||||
struct ZNetMessageAck
|
||||
{
|
||||
uint32_t subsequence;
|
||||
uint16_t highestReceived; //Highest contiguous sequence remote host has received
|
||||
uint16_t highestSent; //Highest local sequence remote host has sent
|
||||
};
|
||||
|
||||
struct ZNetMessagePing
|
||||
{
|
||||
uint32_t token;
|
||||
};
|
||||
|
||||
//Used for DATA8, DATA16, DATA32
|
||||
struct ZNetMessageData
|
||||
{
|
||||
uint8_t* data; //Pointer within the packet
|
||||
uint32_t offset; //Offset into reassembly buffer
|
||||
uint32_t maxSize; //Maximum size of reassembled packet
|
||||
uint32_t length; //Length of this packet
|
||||
};
|
||||
|
||||
union ZNetMessage
|
||||
{
|
||||
ZNetMessageConnect connect;
|
||||
ZNetMessageAck ack;
|
||||
ZNetMessageData data;
|
||||
ZNetMessagePing ping;
|
||||
};
|
||||
|
||||
struct ZNetMessageContainer
|
||||
{
|
||||
uint32_t channel;
|
||||
uint32_t command;
|
||||
uint32_t flags;
|
||||
uint16_t sequence;
|
||||
ZNetMessage parsed;
|
||||
};
|
||||
|
||||
/*
|
||||
ZNetPrivate::ParseWirePacketHeader()
|
||||
|
||||
Parses the wire format packet header and returns true/false if successful.
|
||||
|
||||
@param reader - The reader set to the first byte of the packet in the proper byte order
|
||||
@return (bool) - True if the header is valid, false if not.
|
||||
*/
|
||||
bool ParseWirePacketHeader(ZBinaryBufferReader* reader);
|
||||
|
||||
void WriteWirePacketHeader(ZBinaryBufferWriter* writer);
|
||||
|
||||
void WriteWireMessageHeader(ZBinaryBufferWriter* writer, uint32_t channel, uint32_t flags, uint32_t command, uint16_t sequenceNumber);
|
||||
|
||||
bool SendAll(SST_Socket s, const SST_NetAddress* addr, uint8_t* data, uint32_t length);
|
||||
|
||||
/*
|
||||
ZNetPrivate::ReadNextMessage()
|
||||
|
||||
Attempt to parse the next message from a stream.
|
||||
|
||||
@param reader - The binary reader
|
||||
@param msgReturn - The parsed message (only when returning > 0)
|
||||
@return (int) - < 0: error in stream. 0: end of stream. > 0 successfully parsed message
|
||||
*/
|
||||
int ReadNextMessage(ZBinaryBufferReader* reader, ZNetPrivate::ZNetMessageContainer* msgReturn);
|
||||
|
||||
/*
|
||||
ZNetPrivate::MinimumSizeForCommand()
|
||||
|
||||
Gets the minimum size (in bytes) of a command's payload.
|
||||
|
||||
@return (uint32_t) - The number of bytes
|
||||
*/
|
||||
uint32_t MinimumSizeForCommand(uint32_t command);
|
||||
|
||||
/*
|
||||
ZNetPrivate::WireSizeForCommand()
|
||||
|
||||
Gets the approximate size for a simple command. This means packet header + message header + payload
|
||||
|
||||
@return (uint32_t) - The number of bytes
|
||||
*/
|
||||
inline uint32_t WireSizeForSimpleCommand(uint32_t command) { return MinimumSizeForCommand(command) + ZNET_WIRESIZE_PACKET_MINIMUM; }
|
||||
|
||||
/*
|
||||
ZNetPrivate::SequenceAfter()
|
||||
|
||||
Checks if seq1 comes after seq2, taking into account sequence number wraps.
|
||||
|
||||
@param seq1 - The sequence number to check
|
||||
@param seq2 - The existing sequence number
|
||||
@return (bool) - True if seq1 comes after seq2, false if seq2 comes first or equals seq1
|
||||
*/
|
||||
inline bool SequenceAfter(uint16_t seq1, uint16_t seq2)
|
||||
{
|
||||
return ((seq2 > seq1) && (seq2 - seq1 > UINT16_MAX/2)) || ((seq1 > seq2) && (seq1 - seq2 <= UINT16_MAX/2));
|
||||
}
|
||||
|
||||
inline uint32_t PackedIntegerSize(uint32_t value)
|
||||
{
|
||||
if(value <= UINT8_MAX) return sizeof(uint8_t);
|
||||
if(value <= UINT16_MAX) return sizeof(uint16_t);
|
||||
|
||||
return sizeof(uint32_t);
|
||||
}
|
||||
|
||||
void SendPingCommand(bool isReply, uint32_t givenToken, ZNetPeer* peer);
|
||||
|
||||
inline bool IsValidCommand(uint32_t command) { return (command < ZNETCMD_MAX); }
|
||||
|
||||
inline uint32_t ZNetByteOrder32(uint32_t v)
|
||||
{
|
||||
#if (SST_BYTEORDER_HOST != ZNET_BYTEORDER)
|
||||
v = SST_OS_ByteSwap32(v);
|
||||
#endif
|
||||
return v;
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
@@ -0,0 +1,403 @@
|
||||
/*
|
||||
ZNetServer.cpp
|
||||
Author: Patrick Baggett <ptbaggett@762studios.com>
|
||||
Created: 7/18/2013
|
||||
|
||||
Purpose:
|
||||
|
||||
ZNetServer -- extends ZNetHost to provide a server
|
||||
|
||||
License:
|
||||
|
||||
Copyright 2013, 762 Studios
|
||||
*/
|
||||
|
||||
/*************************************************************************/
|
||||
|
||||
#include <ZNet/ZNetServer.hpp>
|
||||
#include <ZNet/ZNetPeer.hpp>
|
||||
#include <ZNet/ZNetPacket.hpp>
|
||||
#include <ZUtil/ZBinaryBufferReader.hpp>
|
||||
#include <SST/SST_Assert.h> //assertions
|
||||
#include <SST/SST_Time.h>
|
||||
#include <new> //std::nothrow
|
||||
|
||||
#include "ZNetPrivate.hpp"
|
||||
|
||||
|
||||
#include <stdio.h> //TEMP
|
||||
|
||||
/*************************************************************************/
|
||||
|
||||
ZNetServer::ZNetServer()
|
||||
{
|
||||
connectCallback = (ZNetConnectCallback)NULL;
|
||||
callbackParam = NULL;
|
||||
peers = NULL;
|
||||
maxClients = 0;
|
||||
clientCount = 0;
|
||||
listenFlag = false;
|
||||
|
||||
for(size_t i=0; i<ZNET_MAX_SERVER_SOCKETS; i++)
|
||||
sockets[i] = 0;
|
||||
}
|
||||
|
||||
ZNetServer::~ZNetServer()
|
||||
{
|
||||
delete[] peers;
|
||||
|
||||
for(uint32_t i=0; i<ZNET_MAX_SERVER_SOCKETS; i++)
|
||||
{
|
||||
if(sockets[i] != 0)
|
||||
SST_Net_Close(sockets[i]);
|
||||
}
|
||||
}
|
||||
|
||||
/*************************************************************************/
|
||||
|
||||
bool ZNetServer::Initialize(uint32_t _maxClients, uint32_t _channelCount)
|
||||
{
|
||||
SST_OS_DebugAssert(_maxClients > 0, "Peer count must be at least one");
|
||||
SST_OS_DebugAssert(_channelCount > 0, "Channel count must be at least one");
|
||||
|
||||
//Allocate peer classes
|
||||
peers = new(std::nothrow) ZNetPeer[_maxClients];
|
||||
if(peers == NULL)
|
||||
return false;
|
||||
|
||||
this->maxClients = _maxClients;
|
||||
this->channelCount = _channelCount;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/*************************************************************************/
|
||||
|
||||
bool ZNetServer::AddSocket(SST_Socket s)
|
||||
{
|
||||
if(s == 0)
|
||||
return false;
|
||||
|
||||
//Scan for empty/already existing
|
||||
for(uint32_t i=0; i<ZNET_MAX_SERVER_SOCKETS; i++)
|
||||
{
|
||||
//Already added?
|
||||
if(sockets[i] == s)
|
||||
return true;
|
||||
else if(sockets[i] == 0) //Empty slot?
|
||||
{
|
||||
//Enable non-blocking mode
|
||||
//TODO: once libsst-net support socket sets, use those and update API to support wait times
|
||||
if(SST_Net_SetNonblock(s, 1) != SSTNETRESULT_SUCCESS)
|
||||
return false;
|
||||
|
||||
sockets[i] = s;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/*************************************************************************/
|
||||
|
||||
int ZNetServer::Update()
|
||||
{
|
||||
bool hitError = false;
|
||||
|
||||
uint64_t now = SST_OS_GetMilliTime();
|
||||
|
||||
//Update bandwidth allocation
|
||||
outBW.Update(now);
|
||||
|
||||
//Check for packets from each socket
|
||||
for(size_t i=0; i<ZNET_MAX_SERVER_SOCKETS; i++)
|
||||
{
|
||||
SST_NetAddress sender;
|
||||
size_t nrRecv;
|
||||
uint8_t data[9000]; //On IP networks, jumbo packet size are canonically 9000 bytes.
|
||||
|
||||
//Empty socket slot
|
||||
if(sockets[i] == 0)
|
||||
continue;
|
||||
|
||||
//Empty network packet queue
|
||||
SST_NetResult res;
|
||||
do
|
||||
{
|
||||
res = SST_Net_RecvFrom(sockets[i], data, sizeof(data), 0, &sender, &nrRecv);
|
||||
|
||||
//It is common that no data is available. In this case simply go to the next socket
|
||||
if(res == SSTNETRESULT_WOULDBLOCK)
|
||||
break;
|
||||
if(res == SSTNETRESULT_SUCCESS)
|
||||
this->HandlePacket(sockets[i], &sender, data, (uint32_t)nrRecv);
|
||||
else if(res == SSTNETRESULT_WOULDBLOCK)
|
||||
break;
|
||||
else
|
||||
hitError = true;
|
||||
|
||||
} while(res == SSTNETRESULT_SUCCESS);
|
||||
|
||||
}
|
||||
|
||||
//TODO: kind of hacky, refactor. should be able to share a lot of code with client
|
||||
now = SST_OS_GetMilliTime(); //since we could have processed a lot of data, update timestamp
|
||||
for(uint32_t i=0; i<maxClients; i++)
|
||||
{
|
||||
ZNetPeer* peer = &peers[i];
|
||||
if(peer->GetState() != STATE_UNCONNECTED)
|
||||
{
|
||||
|
||||
if(!this->SendToPeer(peer))
|
||||
hitError = true;
|
||||
|
||||
//printf("Peer %p has %u packets queued\n"
|
||||
peer->SendAcksForAllChannels();
|
||||
peer->ProcessLocalAcks();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
if(hitError)
|
||||
return -1;
|
||||
|
||||
return (HasEvent() ? 1 : 0);
|
||||
}
|
||||
|
||||
/*************************************************************************/
|
||||
|
||||
void ZNetServer::SendPacket(ZNetPacket* packet, ZNetPeer* peer, uint8_t channelId)
|
||||
{
|
||||
SST_OS_DebugAssert(packet != NULL, "Cannot send NULL packet");
|
||||
SST_OS_DebugAssert(peer != NULL, "Cannot send to NULL peer");
|
||||
SST_OS_DebugAssert(peer >= this->peers && peer <= &this->peers[maxClients], "Peer pointer isn't part of this group!");
|
||||
|
||||
ZNetPacketChannel* channel = peer->GetPacketChannel(channelId); //GetPacketChannel() does assertion checks for valid channels
|
||||
channel->QueueForSending(packet, ZNETCMD_DATA);
|
||||
|
||||
}
|
||||
|
||||
/*************************************************************************/
|
||||
|
||||
void ZNetServer::BroadcastPacket(ZNetPacket* packet, uint8_t channelId)
|
||||
{
|
||||
SST_OS_DebugAssert(packet != NULL, "Cannot send NULL packet");
|
||||
|
||||
for(uint32_t i=0; i<maxClients; i++)
|
||||
{
|
||||
if(peers[i].GetState() == STATE_CONNECTED)
|
||||
{
|
||||
peers[i].GetPacketChannel(channelId)->QueueForSending(packet, ZNETCMD_DATA);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/*************************************************************************/
|
||||
|
||||
void ZNetServer::HandlePacket(SST_Socket s, const SST_NetAddress* addr, const uint8_t* data, uint32_t dataSize)
|
||||
{
|
||||
ZNetPeer* peer = PeerForAddress(addr);
|
||||
ZBinaryBufferReader reader(data, dataSize, ZNET_BYTEORDER);
|
||||
|
||||
//basically: assert if peer != NULL then state != unconnected. otherwise, don't test anything
|
||||
SST_OS_DebugAssert(peer != NULL ? (peer->GetState() != STATE_UNCONNECTED) : true, "Peer state is invalid");
|
||||
|
||||
|
||||
//printf("Handling packet of %u length", dataSize);
|
||||
|
||||
//Valid packet header?
|
||||
if(!ZNetPrivate::ParseWirePacketHeader(&reader))
|
||||
{
|
||||
printf("...but it is invalid\n");
|
||||
return;
|
||||
}
|
||||
//printf("\n");
|
||||
|
||||
|
||||
ZNetPrivate::ZNetMessageContainer container;
|
||||
|
||||
int retval;
|
||||
uint64_t now = SST_OS_GetMilliTime();
|
||||
bool wasValid = true;
|
||||
do
|
||||
{
|
||||
retval = ZNetPrivate::ReadNextMessage(&reader, &container);
|
||||
if(retval > 0)
|
||||
{
|
||||
switch(container.command)
|
||||
{
|
||||
//==============================================================
|
||||
//Data received
|
||||
case ZNETCMD_DATA:
|
||||
{
|
||||
printf("ZNETCMD_DATA found, ch = %u, length = %u, offset = %u, maxSize = %u\n",
|
||||
container.channel, container.parsed.data.length, container.parsed.data.offset,
|
||||
container.parsed.data.maxSize);
|
||||
|
||||
if(container.channel < peer->GetNumberChannels())
|
||||
{
|
||||
ZNetPacketChannel* ch = peer->GetPacketChannel(container.channel);
|
||||
|
||||
ch->QueueData(&container);
|
||||
}
|
||||
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
//==============================================================
|
||||
//A new client wants to connect.
|
||||
case ZNETCMD_CONNECT:
|
||||
{
|
||||
printf("ZNETCMD_CONNECT, peer = %p\n", peer);
|
||||
//No peer yet
|
||||
if(peer == NULL)
|
||||
{
|
||||
peer = FindEmptyPeerSlot();
|
||||
printf("Empty peer slot == %p\n", peer);
|
||||
if(peer != NULL)
|
||||
{
|
||||
uint32_t clientFilterUserData;
|
||||
|
||||
//Any callback function?
|
||||
if(this->connectCallback)
|
||||
clientFilterUserData = connectCallback(addr, container.parsed.connect.userdata, callbackParam);
|
||||
else
|
||||
clientFilterUserData = 0; //default to success
|
||||
|
||||
//If the client passed the filter
|
||||
if(clientFilterUserData == 0)
|
||||
{
|
||||
printf("New client connected!\n");
|
||||
ZNetPacket* packet = this->CreatePacket(NULL, sizeof(uint32_t), 0);
|
||||
if(packet != NULL)
|
||||
{
|
||||
memset(packet->GetData(), 0, sizeof(uint32_t));
|
||||
|
||||
if(peer->Initialize(this, addr, s, this->channelCount))
|
||||
{
|
||||
printf("Queueing ZNETCMD_CONNRESP");
|
||||
peer->GetPacketChannel(ZNET_SYSCHANNEL)->QueueForSending(packet, ZNETCMD_CONNRESP);
|
||||
packet->ReleaseReference();
|
||||
|
||||
ZNetEvent ev;
|
||||
|
||||
ev.packet = NULL;
|
||||
ev.userdata = container.parsed.connect.userdata;
|
||||
ev.remote = peer;
|
||||
ev.type = ZNETEVENT_CONNECT;
|
||||
|
||||
AddEvent(&ev);
|
||||
}
|
||||
}
|
||||
}
|
||||
else //Did not pass the filter. Directly send a response since we won't be using channels.
|
||||
this->TrySendConnResp(s, addr, clientFilterUserData, 0);
|
||||
}
|
||||
else //Server is full
|
||||
{
|
||||
//It's non-critical, so if we don't have the BW, ignore it
|
||||
this->TrySendConnResp(s, addr, 0, ZNETCMDFLAGS_FULL);
|
||||
}
|
||||
|
||||
} // else peer is known
|
||||
/*
|
||||
There is no "else{}" after this. If the peer is already known to us, that means
|
||||
we've already queued a ZNETCMD_CONNRESP command in return. Once the remote host
|
||||
receives it, they will stop spamming us with CONNECT.
|
||||
*/
|
||||
|
||||
break;
|
||||
} //case CONNECT
|
||||
//==============================================================
|
||||
|
||||
//Client sent how far it is. We can safely act on this now because it does not generate any events.
|
||||
//ACKs themselves do not utilize sequence numbers (they always have 0)
|
||||
case ZNETCMD_ACK:
|
||||
{
|
||||
printf("ZNETCMD_ACK: CH %u: highestRecv = %u, highestSent = %u\n", container.channel, container.parsed.ack.highestReceived, container.parsed.ack.highestSent);
|
||||
//Ensure it is a valid ack
|
||||
if(container.channel < peer->GetNumberChannels())
|
||||
{
|
||||
ZNetPacketChannel* channel = peer->GetPacketChannel(container.channel);
|
||||
int32_t currentPing = peer->GetPing();
|
||||
|
||||
//Update ACKs, calculating an adjusted ping
|
||||
channel->UpdateRemoteAck(container.parsed.ack.highestReceived, ¤tPing);
|
||||
peer->SetPing(currentPing);
|
||||
}
|
||||
else
|
||||
wasValid = false;
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
SST_OS_DebugError("Not yet implemented!");
|
||||
break;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
} while(retval > 0);
|
||||
|
||||
//If all was valid, update last received time
|
||||
if(wasValid && peer)
|
||||
peer->SetLastReceived(now);
|
||||
|
||||
}
|
||||
|
||||
/*************************************************************************/
|
||||
|
||||
void ZNetServer::TrySendConnResp(SST_Socket s, const SST_NetAddress* addr, uint32_t reasonCode, uint32_t flags)
|
||||
{
|
||||
if(outBW.TryAllocate(ZNetPrivate::WireSizeForSimpleCommand(ZNETCMD_CONNRESP)))
|
||||
{
|
||||
uint8_t data[128];
|
||||
|
||||
ZBinaryBufferWriter writer(data, sizeof(data), ZNET_BYTEORDER);
|
||||
|
||||
ZNetPrivate::WriteWirePacketHeader(&writer);
|
||||
ZNetPrivate::WriteWireMessageHeader(&writer, 0, flags, ZNETCMD_CONNRESP, 0); //always ch0seq0
|
||||
writer.WriteU32(reasonCode);
|
||||
|
||||
//Send packet
|
||||
ZNetPrivate::SendAll(s, addr, data, (uint32_t)writer.GetOffset());
|
||||
}
|
||||
}
|
||||
|
||||
/*************************************************************************/
|
||||
|
||||
ZNetPeer* ZNetServer::PeerForAddress(const SST_NetAddress* addr) const
|
||||
{
|
||||
//TODO: At some point (maxClients > K), a hashtable would be faster. Unfortunately, ZHashMap does not have an allocation failure policy
|
||||
|
||||
for(uint32_t i=0; i<maxClients; i++)
|
||||
{
|
||||
ZNetPeer* peer = &peers[i];
|
||||
|
||||
if(peer->GetState() != STATE_UNCONNECTED)
|
||||
{
|
||||
if(SST_Net_AddressCompare(peer->GetNetAddress(), addr) == 0)
|
||||
return peer;
|
||||
}
|
||||
}
|
||||
|
||||
//Does not match anyone
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/*************************************************************************/
|
||||
|
||||
ZNetPeer* ZNetServer::FindEmptyPeerSlot()
|
||||
{
|
||||
for(uint32_t i=0; i<maxClients; i++)
|
||||
{
|
||||
if(peers[i].GetState() == STATE_UNCONNECTED)
|
||||
return &peers[i];
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Reference in New Issue
Block a user