Initial commit

This commit is contained in:
2026-04-03 00:22:39 -05:00
commit eca1e8c458
945 changed files with 218160 additions and 0 deletions

34
ZNet/Makefile Normal file
View File

@@ -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

200
ZNet/ZNet.vcxproj Normal file
View File

@@ -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>

78
ZNet/ZNet.vcxproj.filters Normal file
View File

@@ -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>

View File

@@ -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;
}
}

268
ZNet/ZNetClient.cpp Normal file
View File

@@ -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, &currentPing);
//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);
}

145
ZNet/ZNetHost.cpp Normal file
View File

@@ -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;
}

506
ZNet/ZNetPacketChannel.cpp Normal file
View File

@@ -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();
}

156
ZNet/ZNetPeer.cpp Normal file
View File

@@ -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());
}
/*************************************************************************/

174
ZNet/ZNetPrivate.cpp Normal file
View File

@@ -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;
}

243
ZNet/ZNetPrivate.hpp Normal file
View File

@@ -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

403
ZNet/ZNetServer.cpp Normal file
View File

@@ -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, &currentPing);
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.