/* ZNetServer.cpp Author: Patrick Baggett Created: 7/18/2013 Purpose: ZNetServer -- extends ZNetHost to provide a server License: Copyright 2013, 762 Studios */ /*************************************************************************/ #include #include #include #include #include //assertions #include #include //std::nothrow #include "ZNetPrivate.hpp" #include //TEMP /*************************************************************************/ ZNetServer::ZNetServer() { connectCallback = (ZNetConnectCallback)NULL; callbackParam = NULL; peers = NULL; maxClients = 0; clientCount = 0; listenFlag = false; for(size_t i=0; i 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; iHandlePacket(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; iGetState() != 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; iQueueForSending(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; iGetState() != 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