From 7b9c1d5a9e52c0be247e090377fdf7b4b7e6c5ad Mon Sep 17 00:00:00 2001 From: Tobias Zwick Date: Fri, 29 Nov 2013 17:28:04 +0700 Subject: [PATCH] refactor: consistently use time_t t... variables for times in network system, scheduler, gui The network used to cast GetTime() to int, but GetTime() is an unsigned long. This might cause problems if GetTime() returns big integers (see #251). To solve this, the StdSchedulerProc interface had to be extended with another function in order to eliminate the magic return value -1 of GetNextTick for "no scheduled execution". --- src/gui/C4Gui.h | 2 +- src/gui/C4GuiEdit.cpp | 12 +- src/network/C4GameControlNetwork.cpp | 27 +- src/network/C4GameControlNetwork.h | 9 +- src/network/C4NetIO.cpp | 51 +- src/network/C4NetIO.h | 13 +- src/network/C4Network2.cpp | 21 +- src/network/C4Network2.h | 884 +++---- src/network/C4Network2IO.cpp | 3286 +++++++++++++------------- src/network/C4Network2IO.h | 884 +++---- src/network/C4Network2Reference.cpp | 1289 +++++----- src/network/C4Network2Reference.h | 435 ++-- src/network/C4Packet2.cpp | 10 +- src/platform/GetTime.cpp | 4 +- src/platform/PlatformAbstraction.h | 526 ++--- src/platform/StdScheduler.cpp | 105 +- src/platform/StdScheduler.h | 32 +- 17 files changed, 3804 insertions(+), 3786 deletions(-) diff --git a/src/gui/C4Gui.h b/src/gui/C4Gui.h index 64f5abe7b..98ea42227 100644 --- a/src/gui/C4Gui.h +++ b/src/gui/C4Gui.h @@ -1233,7 +1233,7 @@ namespace C4GUI int32_t iCursorPos; // cursor position: char, before which the cursor is located int32_t iSelectionStart, iSelectionEnd; // selection range (start may be larger than end) int32_t iMaxTextLength; // maximum number of characters to be input here - DWORD dwLastInputTime; // time of last input (for cursor flashing) + time_t tLastInputTime; // time of last input (for cursor flashing) int32_t iXScroll; // horizontal scrolling char cPasswordMask; // character to be used for masking the contents. 0 for none. diff --git a/src/gui/C4GuiEdit.cpp b/src/gui/C4GuiEdit.cpp index cd52ed0b0..adaa35314 100644 --- a/src/gui/C4GuiEdit.cpp +++ b/src/gui/C4GuiEdit.cpp @@ -150,7 +150,7 @@ namespace C4GUI // reset selection iSelectionStart = iSelectionEnd = 0; // cursor might have moved: ensure it is shown - dwLastInputTime=GetTime(); + tLastInputTime=GetTime(); } void Edit::DeleteSelection() @@ -162,7 +162,7 @@ namespace C4GUI // adjust cursor pos if (iCursorPos > iSelBegin) iCursorPos = Max(iSelBegin, iCursorPos - iSelEnd + iSelBegin); // cursor might have moved: ensure it is shown - dwLastInputTime=GetTime(); + tLastInputTime=GetTime(); // nothing selected iSelectionStart = iSelectionEnd = iSelBegin; } @@ -189,7 +189,7 @@ namespace C4GUI // advance cursor iCursorPos += iTextLen; // cursor moved: ensure it is shown - dwLastInputTime=GetTime(); + tLastInputTime=GetTime(); ScrollCursorInView(); } // done; return whether everything was inserted @@ -452,7 +452,7 @@ namespace C4GUI iCursorPos += iMoveLength; } // show cursor - dwLastInputTime=GetTime(); + tLastInputTime=GetTime(); ScrollCursorInView(); // operation recognized return true; @@ -556,7 +556,7 @@ namespace C4GUI // select all iSelectionStart=0; iSelectionEnd=iCursorPos=SLen(Text); // begin with a flashing cursor - dwLastInputTime=GetTime(); + tLastInputTime=GetTime(); } void Edit::OnLooseFocus() @@ -629,7 +629,7 @@ namespace C4GUI // draw edit text pDraw->TextOut(pDrawText, *pFont, 1.0f, cgo.Surface, rcClientRect.x + cgo.TargetX - iXScroll, iY0 + cgo.TargetY - 1, dwFontClr, ALeft, false); // draw cursor - if (HasDrawFocus() && !(((dwLastInputTime-GetTime())/500)%2)) + if (HasDrawFocus() && !(((tLastInputTime-GetTime())/500)%2)) { char cAtCursor = pDrawText[iCursorPos]; pDrawText[iCursorPos]=0; int32_t w,h,wc; pFont->GetTextExtent(pDrawText, w, h, false); diff --git a/src/network/C4GameControlNetwork.cpp b/src/network/C4GameControlNetwork.cpp index 15e5500b5..17a19415e 100644 --- a/src/network/C4GameControlNetwork.cpp +++ b/src/network/C4GameControlNetwork.cpp @@ -33,10 +33,10 @@ C4GameControlNetwork::C4GameControlNetwork(C4GameControl *pnParent) : fEnabled(false), fRunning(false), iClientID(C4ClientIDUnknown), fActivated(false), iTargetTick(-1), - iControlPreSend(1), iWaitStart(-1), iAvgControlSendTime(0), iTargetFPS(38), + iControlPreSend(1), tWaitStart(0), iAvgControlSendTime(0), iTargetFPS(38), iControlSent(0), iControlReady(0), pCtrlStack(NULL), - iNextControlReqeust(0), + tNextControlRequest(0), pParent(pnParent) { assert(pParent); @@ -62,7 +62,8 @@ bool C4GameControlNetwork::Init(int32_t inClientID, bool fnHost, int32_t iStartT pNetwork->Clients.BroadcastMsgToConnClients(MkC4NetIOPacket(PID_ControlReq, C4PacketControlReq(iControlReady + 1))); // ok fEnabled = true; fRunning = false; - iTargetFPS = 38; iNextControlReqeust = GetTime() + C4ControlRequestInterval; + iTargetFPS = 38; + tNextControlRequest = GetTime() + C4ControlRequestInterval; return true; } @@ -88,8 +89,8 @@ void C4GameControlNetwork::Execute() // by main thread return; // Save time the control tick was reached - if (iWaitStart == -1) - iWaitStart = GetTime(); + if (!tWaitStart) + tWaitStart = GetTime(); // Execute any queued sync control ExecQueuedSyncCtrl(); @@ -116,7 +117,7 @@ bool C4GameControlNetwork::GetControl(C4Control *pCtrl, int32_t iTick) // by mai pCtrl->Append(pPkt->getControl()); // calc performance CalcPerformance(iTick); - iWaitStart = -1; + tWaitStart = 0; // ok return true; } @@ -421,7 +422,7 @@ void C4GameControlNetwork::CalcPerformance(int32_t iCtrlTick) C4GameControlPacket *pCtrl = getCtrl(pClient->getClientID(), iCtrlTick); if (!pCtrl) continue; // calc stats - pClient->AddPerf(pCtrl->getTime() - iWaitStart); + pClient->AddPerf(pCtrl->getTime() - tWaitStart); } // Now do PreSend-calcs based on ping times int32_t iControlSendTime; @@ -735,7 +736,7 @@ void C4GameControlNetwork::CheckCompleteCtrl(bool fSetEvent) // by both // target ctrl tick to reach? if (iControlReady < iTargetTick && (!fActivated || iControlSent > iControlReady) && - GetTime() >= iNextControlReqeust) + GetTime() >= tNextControlRequest) { Application.InteractiveThread.ThreadLogS("Network: Recovering: Requesting control for tick %d...", iControlReady + 1); // make request @@ -746,7 +747,7 @@ void C4GameControlNetwork::CheckCompleteCtrl(bool fSetEvent) // by both else ::Network.Clients.BroadcastMsgToConnClients(Pkt); // set time for next request - iNextControlReqeust = GetTime() + C4ControlRequestInterval; + tNextControlRequest = GetTime() + C4ControlRequestInterval; } } @@ -764,7 +765,7 @@ C4GameControlPacket *C4GameControlNetwork::PackCompleteCtrl(int32_t iTick) { // async mode: wait n extra frames for slow clients const int iMaxWait = (Config.Network.AsyncMaxWait * 1000) / iTargetFPS; - if (eMode != CNM_Async || iWaitStart == -1 || GetTime() <= uint32_t(iWaitStart + iMaxWait)) + if (eMode != CNM_Async || !tWaitStart || GetTime() <= tWaitStart + iMaxWait) return NULL; } @@ -795,7 +796,7 @@ C4GameControlPacket *C4GameControlNetwork::PackCompleteCtrl(int32_t iTick) ::Network.Clients.BroadcastMsgToConnClients(MkC4NetIOPacket(PID_Control, *pComplete)); // advance control request time - iNextControlReqeust = Max(iNextControlReqeust, GetTime() + C4ControlRequestInterval); + tNextControlRequest = Max(tNextControlRequest, GetTime() + C4ControlRequestInterval); // return return pComplete; @@ -851,7 +852,7 @@ void C4GameControlNetwork::ExecQueuedSyncCtrl() // by main thread C4GameControlPacket::C4GameControlPacket() : iClientID(C4ClientIDUnknown), iCtrlTick(-1), - iTime(GetTime()), + tTime(GetTime()), pNext(NULL) { @@ -860,7 +861,7 @@ C4GameControlPacket::C4GameControlPacket() C4GameControlPacket::C4GameControlPacket(const C4GameControlPacket &Pkt2) : C4PacketBase(Pkt2), iClientID(Pkt2.getClientID()), iCtrlTick(Pkt2.getCtrlTick()), - iTime(GetTime()), + tTime(GetTime()), pNext(NULL) { Ctrl.Copy(Pkt2.getControl()); diff --git a/src/network/C4GameControlNetwork.h b/src/network/C4GameControlNetwork.h index a0c10352d..ed302ca29 100644 --- a/src/network/C4GameControlNetwork.h +++ b/src/network/C4GameControlNetwork.h @@ -64,7 +64,8 @@ protected: volatile int32_t iControlPreSend; // statistics - int32_t iWaitStart; + time_t tWaitStart; + int32_t iAvgControlSendTime; int32_t iTargetFPS; // used for PreSend-colculation @@ -84,7 +85,7 @@ protected: C4GameControlPacket *pSyncCtrlQueue; // control request timing - uint32_t iNextControlReqeust; + time_t tNextControlRequest; // links C4GameControl *const pParent; @@ -181,7 +182,7 @@ public: protected: // header int32_t iClientID, iCtrlTick; - int32_t iTime; + time_t tTime; // data C4Control Ctrl; @@ -192,7 +193,7 @@ protected: public: int32_t getClientID() const { return iClientID; } int32_t getCtrlTick() const { return iCtrlTick; } - int32_t getTime() const { return iTime; } + time_t getTime() const { return tTime; } const C4Control &getControl() const { return Ctrl; } void Set(int32_t iClientID, int32_t iCtrlTick); diff --git a/src/network/C4NetIO.cpp b/src/network/C4NetIO.cpp index cb2b26453..9db30fb98 100644 --- a/src/network/C4NetIO.cpp +++ b/src/network/C4NetIO.cpp @@ -806,12 +806,6 @@ void C4NetIOTCP::GetFDs(std::vector & fds) } #endif - -int C4NetIOTCP::GetNextTick(int Now) // (mt-safe) -{ - return TO_INF; -} - bool C4NetIOTCP::GetStatistic(int *pBroadcastRate) // (mt-safe) { // no broadcast @@ -1700,11 +1694,6 @@ enum C4NetIOSimpleUDP::WaitResult C4NetIOSimpleUDP::WaitForSocket(int iTimeout) #endif // STDSCHEDULER_USE_EVENTS -int C4NetIOSimpleUDP::GetNextTick(int Now) -{ - return C4NetIO::TO_INF; -} - bool C4NetIOSimpleUDP::SetMCLoopback(int fLoopback) { // enable/disable MC loopback @@ -1806,7 +1795,7 @@ C4NetIOUDP::C4NetIOUDP() pPeerList(NULL), fSavePacket(false), fDelayedLoopbackTest(false), - iNextCheck(0), + tNextCheck(0), OPackets(iMaxOPacketBacklog), iOPacketCounter(0), iBroadcastRate(0) @@ -1840,7 +1829,7 @@ bool C4NetIOUDP::Init(uint16_t inPort) // set flags fInit = true; fMultiCast = false; - iNextCheck = GetTime() + iCheckInterval; + tNextCheck = GetTime() + iCheckInterval; // ok, that's all for now. // call InitBroadcast for more initialization fun @@ -2012,8 +2001,8 @@ bool C4NetIOUDP::Execute(int iMaxTime, pollfd *) // (mt-safe) ResetError(); // adjust maximum block time - int Now = GetTime(); - int iMaxBlock = GetNextTick(Now) - Now; + time_t tNow = GetTime(); + int iMaxBlock = GetNextTick(tNow) - tNow; if (iMaxTime == TO_INF || iMaxTime > iMaxBlock) iMaxTime = iMaxBlock; // execute subclass @@ -2021,7 +2010,7 @@ bool C4NetIOUDP::Execute(int iMaxTime, pollfd *) // (mt-safe) return false; // connection check needed? - if (iNextCheck <= GetTime()) + if (tNextCheck <= GetTime()) DoCheck(); // client timeout? for (Peer *pPeer = pPeerList; pPeer; pPeer = pPeer->Next) @@ -2106,18 +2095,18 @@ bool C4NetIOUDP::SetBroadcast(const addr_t &addr, bool fSet) // (mt-safe) return true; } -int C4NetIOUDP::GetNextTick(int Now) // (mt-safe) +time_t C4NetIOUDP::GetNextTick(time_t tNow) // (mt-safe) { // maximum time: check interval - int iTiming = Max(Now, iNextCheck); + time_t tTiming = Max(tNow, tNextCheck); // client timeouts (e.g. connection timeout) CStdShareLock PeerListLock(&PeerListCSec); for (Peer *pPeer = pPeerList; pPeer; pPeer = pPeer->Next) if (!pPeer->Closed()) if (pPeer->GetTimeout() > 0) - iTiming = Min(iTiming, Now + pPeer->GetTimeout()); + tTiming = Min(tTiming, tNow + pPeer->GetTimeout()); // return timing value - return iTiming; + return tTiming; } bool C4NetIOUDP::GetStatistic(int *pBroadcastRate) // (mt-safe) @@ -2496,7 +2485,7 @@ C4NetIOUDP::Peer::Peer(const sockaddr_in &naddr, C4NetIOUDP *pnParent) iIPacketCounter(0), iRIPacketCounter(0), iIMCPacketCounter(0), iRIMCPacketCounter(0), iMCAckPacketCounter(0), - iNextReCheck(0), + tNextReCheck(0), iIRate(0), iORate(0), iLoss(0) { ZeroMem(&addr2, sizeof(addr2)); @@ -2544,7 +2533,7 @@ bool C4NetIOUDP::Peer::Check(bool fForceCheck) if (eStatus != CS_Works) return true; // prevent re-check (check floods) // instead, ask for other packets that are missing until recheck is allowed - bool fNoReCheck = !!iNextReCheck && iNextReCheck > GetTime(); + bool fNoReCheck = !!tNextReCheck && tNextReCheck > GetTime(); if (!fNoReCheck) iLastPacketAsked = iLastMCPacketAsked = 0; unsigned int iStartAt = fNoReCheck ? Max(iLastPacketAsked + 1, iIPacketCounter) : iIPacketCounter; unsigned int iStartAtMC = fNoReCheck ? Max(iLastMCPacketAsked + 1, iIMCPacketCounter) : iIMCPacketCounter; @@ -2562,7 +2551,7 @@ bool C4NetIOUDP::Peer::Check(bool fForceCheck) int iEAskCnt = iAskCnt + iMCAskCnt; // no re-check limit? set it if (!fNoReCheck) - iNextReCheck = iEAskCnt ? GetTime() + iReCheckInterval : 0; + tNextReCheck = iEAskCnt ? GetTime() + iReCheckInterval : 0; // something to ask for? (or check forced?) if (iEAskCnt || fForceCheck) return DoCheck(iAskCnt, iMCAskCnt, iAskList); @@ -2627,7 +2616,7 @@ void C4NetIOUDP::Peer::OnRecv(const C4NetIOPacket &rPacket) // (mt-safe) else iRIPacketCounter = iIPacketCounter = pPkt->Nr; // clear incoming packets - IPackets.Clear(); IMCPackets.Clear(); iNextReCheck = 0; + IPackets.Clear(); IMCPackets.Clear(); tNextReCheck = 0; iLastPacketAsked = iLastMCPacketAsked = 0; // Activate Multicast? if (!pParent->fMultiCast) @@ -2786,9 +2775,9 @@ void C4NetIOUDP::Peer::Close(const char *szReason) // (mt-safe) void C4NetIOUDP::Peer::CheckTimeout() { // timeout set? - if (!iTimeout) return; + if (!tTimeout) return; // check - if (GetTime() > iTimeout) + if (GetTime() > tTimeout) OnTimeout(); } @@ -2937,9 +2926,9 @@ void C4NetIOUDP::Peer::CheckCompleteIPackets() void C4NetIOUDP::Peer::SetTimeout(int iLength, int iRetryCnt) // (mt-safe) { if (iLength != TO_INF) - iTimeout = GetTime() + iLength; + tTimeout = GetTime() + iLength; else - iTimeout = 0; + tTimeout = 0; iRetries = iRetryCnt; } @@ -3156,7 +3145,7 @@ void C4NetIOUDP::DoCheck() // (mt-safe) if (pPeer->Open()) pPeer->Check(); // set time for next check - iNextCheck = GetTime() + iCheckInterval; + tNextCheck = GetTime() + iCheckInterval; } // debug @@ -3204,9 +3193,9 @@ void C4NetIOUDP::CloseDebugLog() void C4NetIOUDP::DebugLogPkt(bool fOut, const C4NetIOPacket &Pkt) { StdStrBuf O; - unsigned int iTime = GetTime(); + time_t tTime = GetTime(); O.Format("%s %d:%02d:%02d:%03d %s:%d:", fOut ? "out" : "in ", - (iTime / 1000 / 60 / 60), (iTime / 1000 / 60) % 60, (iTime / 1000) % 60, iTime % 1000, + (tTime / 1000 / 60 / 60), (tTime / 1000 / 60) % 60, (tTime / 1000) % 60, tTime % 1000, inet_ntoa(Pkt.getAddr().sin_addr), htons(Pkt.getAddr().sin_port)); // header? diff --git a/src/network/C4NetIO.h b/src/network/C4NetIO.h index 4569a0654..ebd9fe3f8 100644 --- a/src/network/C4NetIO.h +++ b/src/network/C4NetIO.h @@ -218,7 +218,6 @@ public: #else virtual void GetFDs(std::vector & FDs); #endif - virtual int GetNextTick(int Now); // statistics virtual bool GetStatistic(int *pBroadcastRate); @@ -373,7 +372,6 @@ public: #else virtual void GetFDs(std::vector & FDs); #endif - virtual int GetNextTick(int Now); // not implemented virtual bool Connect(const addr_t &addr) { assert(false); return false; } @@ -460,7 +458,8 @@ public: virtual bool Broadcast(const C4NetIOPacket &rPacket); virtual bool SetBroadcast(const addr_t &addr, bool fSet = true); - virtual int GetNextTick(int Now); + virtual time_t GetNextTick(time_t tNow); + virtual bool IsScheduledExecution() { return true; } virtual bool GetStatistic(int *pBroadcastRate); virtual bool GetConnStatistic(const addr_t &addr, int *pIRate, int *pORate, int *pLoss); @@ -622,11 +621,11 @@ protected: CStdCSec OutCSec; // connection check time limit - unsigned int iNextReCheck; + time_t tNextReCheck; unsigned int iLastPacketAsked, iLastMCPacketAsked; // timeout - unsigned int iTimeout; + time_t tTimeout; unsigned int iRetries; // statistics @@ -662,7 +661,7 @@ protected: unsigned int GetMCAckPacketCounter() const { return iMCAckPacketCounter; } // timeout checking - int GetTimeout() { return iTimeout; } + time_t GetTimeout() { return tTimeout; } void CheckTimeout(); // selected for broadcast? @@ -728,7 +727,7 @@ protected: bool fDelayedLoopbackTest; // check timing - unsigned int iNextCheck; + time_t tNextCheck; // outgoing packet list (for multicast) PacketList OPackets; diff --git a/src/network/C4Network2.cpp b/src/network/C4Network2.cpp index 5e87035b6..c033f8ed5 100644 --- a/src/network/C4Network2.cpp +++ b/src/network/C4Network2.cpp @@ -146,7 +146,7 @@ C4Network2::C4Network2() pLobby(NULL), fLobbyRunning(false), pLobbyCountdown(NULL), iNextClientID(0), iLastChaseTargetUpdate(0), - iLastActivateRequest(0), + tLastActivateRequest(0), iLastReferenceUpdate(0), iLastLeagueUpdate(0), pLeagueClient(NULL), @@ -624,7 +624,7 @@ void C4Network2::Execute() else { // request activate, if neccessary - if (iLastActivateRequest) RequestActivate(); + if (tLastActivateRequest) RequestActivate(); } } @@ -664,7 +664,7 @@ void C4Network2::Clear() // stuff fAllowJoin = false; iDynamicTick = -1; fDynamicNeeded = false; - iLastActivateRequest = iLastChaseTargetUpdate = iLastReferenceUpdate = iLastLeagueUpdate = 0; + tLastActivateRequest = iLastChaseTargetUpdate = iLastReferenceUpdate = iLastLeagueUpdate = 0; fDelayedActivateReq = false; delete pVoteDialog; pVoteDialog = NULL; fPausedForVote = false; @@ -1502,7 +1502,8 @@ C4Network2Res::Ref C4Network2::RetrieveRes(const C4Network2ResCore &Core, int32_ { C4GUI::ProgressDialog *pDlg = NULL; bool fLog = false; - int32_t iProcess = -1; uint32_t iTimeout = GetTime() + iTimeoutLen; + int32_t iProcess = -1; + time_t tTimeout = GetTime() + iTimeoutLen; // wait for resource while (isEnabled()) { @@ -1534,12 +1535,12 @@ C4Network2Res::Ref C4Network2::RetrieveRes(const C4Network2ResCore &Core, int32_ if (pRes && pRes->getPresentPercent() != iProcess) { iProcess = pRes->getPresentPercent(); - iTimeout = GetTime() + iTimeoutLen; + tTimeout = GetTime() + iTimeoutLen; } else { // if not: check timeout - if (GetTime() > iTimeout) + if (GetTime() > tTimeout) { LogFatal(FormatString(LoadResStr("IDS_NET_ERR_RESTIMEOUT"), szResName).getData()); if (pDlg) delete pDlg; @@ -1576,7 +1577,7 @@ C4Network2Res::Ref C4Network2::RetrieveRes(const C4Network2ResCore &Core, int32_ } else { - if (!Application.ScheduleProcs(iTimeout - GetTime())) + if (!Application.ScheduleProcs(tTimeout - GetTime())) { return NULL; } } @@ -1761,7 +1762,7 @@ void C4Network2::RequestActivate() // neither observer nor activated? if (Game.Clients.getLocal()->isObserver() || Game.Clients.getLocal()->isActivated()) { - iLastActivateRequest = 0; + tLastActivateRequest = 0; return; } // host? just do it @@ -1774,7 +1775,7 @@ void C4Network2::RequestActivate() return; } // ensure interval - if (iLastActivateRequest && GetTime() < iLastActivateRequest + C4NetActivationReqInterval) + if (tLastActivateRequest && GetTime() < tLastActivateRequest + C4NetActivationReqInterval) return; // status not reached yet? May be chasing, let's delay this. if (!fStatusReached) @@ -1785,7 +1786,7 @@ void C4Network2::RequestActivate() // request Clients.SendMsgToHost(MkC4NetIOPacket(PID_ClientActReq, C4PacketActivateReq(Game.FrameCounter))); // store time - iLastActivateRequest = GetTime(); + tLastActivateRequest = GetTime(); } void C4Network2::DeactivateInactiveClients() diff --git a/src/network/C4Network2.h b/src/network/C4Network2.h index ebd3cd040..ca90c01db 100644 --- a/src/network/C4Network2.h +++ b/src/network/C4Network2.h @@ -1,442 +1,442 @@ -/* - * OpenClonk, http://www.openclonk.org - * - * Copyright (c) 2004-2007 Sven Eberhardt - * Copyright (c) 2004-2008 Peter Wortmann - * Copyright (c) 2006 Günther Brammer - * Copyright (c) 2010 Benjamin Herr - * Copyright (c) 2001-2009, RedWolf Design GmbH, http://www.clonk.de - * - * Portions might be copyrighted by other authors who have contributed - * to OpenClonk. - * - * Permission to use, copy, modify, and/or distribute this software for any - * purpose with or without fee is hereby granted, provided that the above - * copyright notice and this permission notice appear in all copies. - * See isc_license.txt for full license and disclaimer. - * - * "Clonk" is a registered trademark of Matthes Bender. - * See clonk_trademark_license.txt for full license. - */ -#ifndef INC_C4Network2 -#define INC_C4Network2 - -#include "C4NetIO.h" -#include "C4Network2Players.h" -#include "C4Network2IO.h" -#include "C4Network2Res.h" -#include "C4Network2Client.h" -#include "C4Control.h" -#include "C4Gui.h" -#include "C4GameParameters.h" - -// lobby predef - no need to include lobby in header just for the class ptr -namespace C4GameLobby { class MainDlg; class Countdown; } -class C4PacketJoinData; - -// standard ports -const int16_t C4NetStdPortTCP = 11112, - C4NetStdPortUDP = 11113, - C4NetStdPortDiscovery = 11114, - C4NetStdPortRefServer = 11111, - C4NetStdPortPuncher = 11115, - C4NetStdPortHTTP = 80; - -// resource retrieve wait timeout -const int C4NetResRetrieveTimeout = 100000; // (ms) - -// client (de)activation -const int C4NetActivationReqInterval = 5000, // (ms) - C4NetMaxBehind4Activation = 20, // (ticks) - C4NetDeactivationDelay = 500; // (ticks) - -// client chase -const unsigned int C4NetChaseTargetUpdateInterval = 5; // (s) - -// reference -const unsigned int C4NetReferenceUpdateInterval = 120; // (s) -const unsigned int C4NetMinLeagueUpdateInterval = 10; // (s) - -// voting -const unsigned int C4NetVotingTimeout = 10; // (s) -const unsigned int C4NetMinVotingInterval = 120; // (s) - -// streaming -const size_t C4NetStreamingMinBlockSize = 10 * 1024; -const size_t C4NetStreamingMaxBlockSize = 20 * 1024; -const int C4NetStreamingInterval = 30; // (s) - -enum C4NetGameState -{ - GS_None, // network not active - GS_Init, // connecting to host, waiting for join data - GS_Lobby, // lobby mode - GS_Pause, // game paused - GS_Go // game running -}; - -class C4Network2Status : public C4PacketBase -{ -public: - C4Network2Status(); - -protected: - C4NetGameState eState; - int32_t iCtrlMode; - int32_t iTargetCtrlTick; - -public: - C4NetGameState getState() const { return eState; } - int32_t getCtrlMode() const { return iCtrlMode; } - int32_t getTargetCtrlTick() const { return iTargetCtrlTick; } - const char *getStateName() const; - const char *getDescription() const; - - bool isEnabled() const { return eState != GS_None; } - bool isLobbyActive() const { return eState == GS_Lobby; } - bool isPastLobby() const { return eState > GS_Lobby; } - bool isPaused() const { return eState == GS_Pause; } - bool isRunning() const { return eState == GS_Go; } - - void Set(C4NetGameState eState, int32_t iTargetCtrlTick); - void SetCtrlMode(int32_t iCtrlMode); - void SetTargetTick(int32_t iTargetCtrlTick); - void Clear(); - - void CompileFunc(StdCompiler *pComp, bool fReference); - virtual void CompileFunc(StdCompiler *pComp); -}; - -class C4Network2 : private C4ApplicationSec1Timer -{ - friend class C4Network2IO; -public: - C4Network2(); - virtual ~C4Network2(); - -public: - // network i/o class - C4Network2IO NetIO; - - // resource list - C4Network2ResList ResList; - - // client list - C4Network2ClientList Clients; - - // player list - C4Network2Players Players; - - // game status - C4Network2Status Status; - -protected: - - // role - bool fHost; - - // options - bool fAllowJoin, fAllowObserve; - - // join resource - C4Network2ResCore ResDynamic; - - // resources - int32_t iDynamicTick; - bool fDynamicNeeded; - - // game status flags - bool fStatusAck, fStatusReached; - bool fChasing; - - // control - class C4GameControlNetwork *pControl; - - // lobby - C4GameLobby::MainDlg *pLobby; - bool fLobbyRunning; - C4GameLobby::Countdown *pLobbyCountdown; - - // master server used - StdCopyStrBuf MasterServerAddress; - - // clients - int32_t iNextClientID; - - // chase - uint32_t iLastChaseTargetUpdate; - - // activation - uint32_t iLastActivateRequest; - - // reference - uint32_t iLastReferenceUpdate; - uint32_t iLastLeagueUpdate, iLeagueUpdateDelay; - bool fLeagueEndSent; - - // league - class C4LeagueClient *pLeagueClient; - - // game password - StdStrBuf sPassword; - - // connection failed because password needed? - bool fWrongPassword; - - // delayed activation request? - bool fDelayedActivateReq; - - // voting - C4Control Votes; - class C4VoteDialog *pVoteDialog; - bool fPausedForVote; - time_t iVoteStartTime, iLastOwnVoting; - - // streaming - bool fStreaming; - time_t iLastStreamAttempt; - C4Record *pStreamedRecord; - StdBuf StreamingBuf; - z_stream StreamCompressor; - - class C4Network2HTTPClient *pStreamer; - unsigned int iCurrentStreamAmount, iCurrentStreamPosition; - -public: - - // data access - bool isEnabled() const { return Status.isEnabled(); } - bool isLobbyActive()const { return Status.isLobbyActive(); } - bool isPastLobby() const { return Status.isPastLobby(); } - bool isRunning() const { return Status.isRunning() && isStatusAck(); } - bool isPaused() const { return Status.isPaused() && isStatusAck(); } - bool isPausing() const { return Status.isPaused() && !fStatusAck; } - bool isHost() const { return fHost; } - bool isStatusAck() const { return fStatusAck; } - bool isFrozen() const; - - bool isJoinAllowed() const { return fAllowJoin; } - bool isObservingAllowed() const { return fAllowObserve; } - - class C4GameLobby::MainDlg *GetLobby() const { return pLobby; } // lobby publication - const char *GetPassword() const { return sPassword.getData(); } // Oh noez, now the password is public! - bool isPassworded() const { return !sPassword.isNull(); } - - // initialization result type - enum InitResult - { - IR_Success, IR_Error, IR_Fatal - }; - - // initialization - bool InitHost(bool fLobby); - InitResult InitClient(const class C4Network2Reference &Ref, bool fObserver); - InitResult InitClient(const class C4Network2Address *pAddrs, int iAddrCount, const class C4ClientCore &HostCore, const char *szPassword = NULL); - bool DoLobby(); - bool Start(); - bool Pause(); - bool Sync(); - bool FinalInit(); - - bool RetrieveScenario(char *szScenario); - C4Network2Res::Ref RetrieveRes(const C4Network2ResCore &Core, int32_t iTimeout, const char *szResName, bool fWaitForCore = false); - - // idle processes - void OnSec1Timer(); - void Execute(); - - // termination - void Clear(); - - // some options - bool ToggleAllowJoin(); - bool ToggleClientListDlg(); - void AllowJoin(bool fAllow); - void SetAllowObserve(bool fAllow); - void SetCtrlMode(int32_t iCtrlMode); - void SetPassword(const char *szToPassword); - StdStrBuf QueryClientPassword(); // ask client for a password; empty if user canceled - - // interface for C4Network2IO - void OnConn(C4Network2IOConnection *pConn); - void OnDisconn(C4Network2IOConnection *pConn); - void HandlePacket(char cStatus, const C4PacketBase *pBasePkt, C4Network2IOConnection *pConn); - void HandleLobbyPacket(char cStatus, const C4PacketBase *pBasePkt, C4Network2IOConnection *pConn); - - // runtime join stuff - void OnGameSynchronized(); - - // status - void DrawStatus(C4TargetFacet &cgo); - - // client activation - void RequestActivate(); - void DeactivateInactiveClients(); // host - - // league - void LeagueGameEvaluate(const char *szRecordName = NULL, const BYTE *pRecordSHA = NULL); - void LeagueSignupDisable(); // if "internet game" button is switched off in lobby: Remove from league server - bool LeagueSignupEnable(); // if "internet game" button is switched on in lobby: (re)Add to league server - void InvalidateReference(); // forces a recreation and re-send of the game reference in the next execution cycle - bool LeaguePlrAuth(C4PlayerInfo *pInfo); // client: get authentication for a player from the league server - bool LeaguePlrAuthCheck(C4PlayerInfo *pInfo); // host: check AUID of player info with league server - void LeagueNotifyDisconnect(int32_t iClientID, enum C4LeagueDisconnectReason eReason); // - void LeagueWaitNotBusy(); // block until league serveris no longer busy. Process update reply if last message was an update - void LeagueSurrender(); // forfeit in league - just fake a disconnect - void LeagueShowError(const char *szMsg); // show league error msg box in fullscreen; just log in console - - // voting - void Vote(C4ControlVoteType eType, bool fApprove = true, int32_t iData = 0); - void AddVote(const C4ControlVote &Vote); - C4IDPacket *GetVote(int32_t iClientID, C4ControlVoteType eType, int32_t iData); - void EndVote(C4ControlVoteType eType, bool fApprove, int32_t iData); - void OpenVoteDialog(); - void OpenSurrenderDialog(C4ControlVoteType eType, int32_t iData); - void OnVoteDialogClosed(); - - // lobby countdown - void StartLobbyCountdown(int32_t iCountdownTime); - void AbortLobbyCountdown(); - bool isLobbyCountDown() { return pLobbyCountdown != 0; } - - // streaming - size_t getPendingStreamData() const { return StreamingBuf.getSize() - StreamCompressor.avail_out; } - bool isStreaming() const; - bool StartStreaming(C4Record *pRecord); - bool FinishStreaming(); - bool StopStreaming(); - -protected: - - using C4ApplicationSec1Timer::Execute; // to avoid "virtual ... is hidden" warning - - // net i/o initialization - bool InitNetIO(bool fNoClientID, bool fHost); - - // handling of own packets - void HandleConn(const class C4PacketConn &Pkt, C4Network2IOConnection *pConn, C4Network2Client *pClient); - bool CheckConn(const C4ClientCore &CCore, C4Network2IOConnection *pConn, C4Network2Client *pClient, StdStrBuf * szReply); - bool HostConnect(const C4ClientCore &CCore, C4Network2IOConnection *pConn, StdStrBuf *szReply); - bool Join(C4ClientCore &CCore, C4Network2IOConnection *pConn, StdStrBuf *szReply); - void HandleConnRe(const class C4PacketConnRe &Pkt, C4Network2IOConnection *pConn, C4Network2Client *pClient); - void HandleStatus(const C4Network2Status &nStatus); - void HandleStatusAck(const C4Network2Status &nStatus, C4Network2Client *pClient); - void HandleActivateReq(int32_t iTick, C4Network2Client *pClient); - void HandleJoinData(const class C4PacketJoinData &rPkt); - - // events - void OnConnect(C4Network2Client *pClient, C4Network2IOConnection *pConn, const char *szMsg, bool fFirstConnection); - void OnConnectFail(C4Network2IOConnection *pConn); - void OnDisconnect(C4Network2Client *pClient, C4Network2IOConnection *pConn); - void OnClientConnect(C4Network2Client *pClient, C4Network2IOConnection *pConn); - void OnClientDisconnect(C4Network2Client *pClient); - - void SendJoinData(C4Network2Client *pClient); - - // resource list - bool CreateDynamic(bool fInit); - void RemoveDynamic(); - - // status changes - bool PauseGame(bool fAutoContinue); - bool ChangeGameStatus(C4NetGameState enState, int32_t iTargetCtrlTick, int32_t iCtrlMode = -1); - void CheckStatusReached(bool fFromFinalInit = false); - void CheckStatusAck(); - void OnStatusReached(); - void OnStatusAck(); - - // chase - void UpdateChaseTarget(); - - // league - bool InitLeague(bool *pCancel); - void DeinitLeague(); - bool LeagueStart(bool *pCancel); - bool LeagueUpdate(); - bool LeagueUpdateProcessReply(); - bool LeagueEnd(const char *szRecordName = NULL, const BYTE *pRecordSHA = NULL); - - // streaming - bool StreamIn(bool fFinish); - bool StreamOut(); -}; - -extern C4Network2 Network; - -class C4VoteDialog : public C4GUI::MessageDialog -{ -public: - C4VoteDialog(const char *szText, C4ControlVoteType eVoteType, int32_t iVoteData, bool fSurrender); - -private: - C4ControlVoteType eVoteType; - int32_t iVoteData; - bool fSurrender; - -public: - C4ControlVoteType getVoteType() const { return eVoteType; } - int32_t getVoteData() const { return iVoteData; } - -private: - virtual bool OnEnter() { UserClose(false); return true; } // the vote dialog defaults to "No" [MarkFra] - virtual void OnClosed(bool fOK); - - // true for dialogs that receive full keyboard and mouse input even in shared mode - virtual bool IsExclusiveDialog() { return true; } -}; - -// * Packets * - -class C4PacketJoinData : public C4PacketBase -{ -public: - C4PacketJoinData() { } - -protected: - - // the client ID - int32_t iClientID; - - // Dynamic data to boot - C4Network2ResCore Dynamic; - - // network status - C4Network2Status GameStatus; - - // control tick - int32_t iStartCtrlTick; - -public: - - // the game parameters - C4GameParameters Parameters; - -public: - const int32_t &getClientID() const { return iClientID; } - const C4Network2ResCore&getDynamicCore() const { return Dynamic; } - const C4Network2Status &getStatus() const { return GameStatus; } - int32_t getStartCtrlTick() const { return iStartCtrlTick; } - - void SetClientID(int32_t inClientID) { iClientID = inClientID; } - void SetGameStatus(const C4Network2Status &Status) { GameStatus = Status; } - void SetDynamicCore(const C4Network2ResCore &Core) { Dynamic = Core; } - void SetStartCtrlTick(int32_t iTick) { iStartCtrlTick = iTick; } - - virtual void CompileFunc(StdCompiler *pComp); -}; - -class C4PacketActivateReq : public C4PacketBase -{ -public: - C4PacketActivateReq(int32_t iTick = -1) : iTick(iTick) { } - -protected: - int32_t iTick; - -public: - int32_t getTick() const { return iTick; } - - virtual void CompileFunc(StdCompiler *pComp); -}; - -#endif +/* + * OpenClonk, http://www.openclonk.org + * + * Copyright (c) 2004-2007 Sven Eberhardt + * Copyright (c) 2004-2008 Peter Wortmann + * Copyright (c) 2006 Günther Brammer + * Copyright (c) 2010 Benjamin Herr + * Copyright (c) 2001-2009, RedWolf Design GmbH, http://www.clonk.de + * + * Portions might be copyrighted by other authors who have contributed + * to OpenClonk. + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * See isc_license.txt for full license and disclaimer. + * + * "Clonk" is a registered trademark of Matthes Bender. + * See clonk_trademark_license.txt for full license. + */ +#ifndef INC_C4Network2 +#define INC_C4Network2 + +#include "C4NetIO.h" +#include "C4Network2Players.h" +#include "C4Network2IO.h" +#include "C4Network2Res.h" +#include "C4Network2Client.h" +#include "C4Control.h" +#include "C4Gui.h" +#include "C4GameParameters.h" + +// lobby predef - no need to include lobby in header just for the class ptr +namespace C4GameLobby { class MainDlg; class Countdown; } +class C4PacketJoinData; + +// standard ports +const int16_t C4NetStdPortTCP = 11112, + C4NetStdPortUDP = 11113, + C4NetStdPortDiscovery = 11114, + C4NetStdPortRefServer = 11111, + C4NetStdPortPuncher = 11115, + C4NetStdPortHTTP = 80; + +// resource retrieve wait timeout +const int C4NetResRetrieveTimeout = 100000; // (ms) + +// client (de)activation +const int C4NetActivationReqInterval = 5000, // (ms) + C4NetMaxBehind4Activation = 20, // (ticks) + C4NetDeactivationDelay = 500; // (ticks) + +// client chase +const unsigned int C4NetChaseTargetUpdateInterval = 5; // (s) + +// reference +const unsigned int C4NetReferenceUpdateInterval = 120; // (s) +const unsigned int C4NetMinLeagueUpdateInterval = 10; // (s) + +// voting +const unsigned int C4NetVotingTimeout = 10; // (s) +const unsigned int C4NetMinVotingInterval = 120; // (s) + +// streaming +const size_t C4NetStreamingMinBlockSize = 10 * 1024; +const size_t C4NetStreamingMaxBlockSize = 20 * 1024; +const int C4NetStreamingInterval = 30; // (s) + +enum C4NetGameState +{ + GS_None, // network not active + GS_Init, // connecting to host, waiting for join data + GS_Lobby, // lobby mode + GS_Pause, // game paused + GS_Go // game running +}; + +class C4Network2Status : public C4PacketBase +{ +public: + C4Network2Status(); + +protected: + C4NetGameState eState; + int32_t iCtrlMode; + int32_t iTargetCtrlTick; + +public: + C4NetGameState getState() const { return eState; } + int32_t getCtrlMode() const { return iCtrlMode; } + int32_t getTargetCtrlTick() const { return iTargetCtrlTick; } + const char *getStateName() const; + const char *getDescription() const; + + bool isEnabled() const { return eState != GS_None; } + bool isLobbyActive() const { return eState == GS_Lobby; } + bool isPastLobby() const { return eState > GS_Lobby; } + bool isPaused() const { return eState == GS_Pause; } + bool isRunning() const { return eState == GS_Go; } + + void Set(C4NetGameState eState, int32_t iTargetCtrlTick); + void SetCtrlMode(int32_t iCtrlMode); + void SetTargetTick(int32_t iTargetCtrlTick); + void Clear(); + + void CompileFunc(StdCompiler *pComp, bool fReference); + virtual void CompileFunc(StdCompiler *pComp); +}; + +class C4Network2 : private C4ApplicationSec1Timer +{ + friend class C4Network2IO; +public: + C4Network2(); + virtual ~C4Network2(); + +public: + // network i/o class + C4Network2IO NetIO; + + // resource list + C4Network2ResList ResList; + + // client list + C4Network2ClientList Clients; + + // player list + C4Network2Players Players; + + // game status + C4Network2Status Status; + +protected: + + // role + bool fHost; + + // options + bool fAllowJoin, fAllowObserve; + + // join resource + C4Network2ResCore ResDynamic; + + // resources + int32_t iDynamicTick; + bool fDynamicNeeded; + + // game status flags + bool fStatusAck, fStatusReached; + bool fChasing; + + // control + class C4GameControlNetwork *pControl; + + // lobby + C4GameLobby::MainDlg *pLobby; + bool fLobbyRunning; + C4GameLobby::Countdown *pLobbyCountdown; + + // master server used + StdCopyStrBuf MasterServerAddress; + + // clients + int32_t iNextClientID; + + // chase + uint32_t iLastChaseTargetUpdate; + + // activation + time_t tLastActivateRequest; + + // reference + uint32_t iLastReferenceUpdate; + uint32_t iLastLeagueUpdate, iLeagueUpdateDelay; + bool fLeagueEndSent; + + // league + class C4LeagueClient *pLeagueClient; + + // game password + StdStrBuf sPassword; + + // connection failed because password needed? + bool fWrongPassword; + + // delayed activation request? + bool fDelayedActivateReq; + + // voting + C4Control Votes; + class C4VoteDialog *pVoteDialog; + bool fPausedForVote; + time_t iVoteStartTime, iLastOwnVoting; + + // streaming + bool fStreaming; + time_t iLastStreamAttempt; + C4Record *pStreamedRecord; + StdBuf StreamingBuf; + z_stream StreamCompressor; + + class C4Network2HTTPClient *pStreamer; + unsigned int iCurrentStreamAmount, iCurrentStreamPosition; + +public: + + // data access + bool isEnabled() const { return Status.isEnabled(); } + bool isLobbyActive()const { return Status.isLobbyActive(); } + bool isPastLobby() const { return Status.isPastLobby(); } + bool isRunning() const { return Status.isRunning() && isStatusAck(); } + bool isPaused() const { return Status.isPaused() && isStatusAck(); } + bool isPausing() const { return Status.isPaused() && !fStatusAck; } + bool isHost() const { return fHost; } + bool isStatusAck() const { return fStatusAck; } + bool isFrozen() const; + + bool isJoinAllowed() const { return fAllowJoin; } + bool isObservingAllowed() const { return fAllowObserve; } + + class C4GameLobby::MainDlg *GetLobby() const { return pLobby; } // lobby publication + const char *GetPassword() const { return sPassword.getData(); } // Oh noez, now the password is public! + bool isPassworded() const { return !sPassword.isNull(); } + + // initialization result type + enum InitResult + { + IR_Success, IR_Error, IR_Fatal + }; + + // initialization + bool InitHost(bool fLobby); + InitResult InitClient(const class C4Network2Reference &Ref, bool fObserver); + InitResult InitClient(const class C4Network2Address *pAddrs, int iAddrCount, const class C4ClientCore &HostCore, const char *szPassword = NULL); + bool DoLobby(); + bool Start(); + bool Pause(); + bool Sync(); + bool FinalInit(); + + bool RetrieveScenario(char *szScenario); + C4Network2Res::Ref RetrieveRes(const C4Network2ResCore &Core, int32_t iTimeout, const char *szResName, bool fWaitForCore = false); + + // idle processes + void OnSec1Timer(); + void Execute(); + + // termination + void Clear(); + + // some options + bool ToggleAllowJoin(); + bool ToggleClientListDlg(); + void AllowJoin(bool fAllow); + void SetAllowObserve(bool fAllow); + void SetCtrlMode(int32_t iCtrlMode); + void SetPassword(const char *szToPassword); + StdStrBuf QueryClientPassword(); // ask client for a password; empty if user canceled + + // interface for C4Network2IO + void OnConn(C4Network2IOConnection *pConn); + void OnDisconn(C4Network2IOConnection *pConn); + void HandlePacket(char cStatus, const C4PacketBase *pBasePkt, C4Network2IOConnection *pConn); + void HandleLobbyPacket(char cStatus, const C4PacketBase *pBasePkt, C4Network2IOConnection *pConn); + + // runtime join stuff + void OnGameSynchronized(); + + // status + void DrawStatus(C4TargetFacet &cgo); + + // client activation + void RequestActivate(); + void DeactivateInactiveClients(); // host + + // league + void LeagueGameEvaluate(const char *szRecordName = NULL, const BYTE *pRecordSHA = NULL); + void LeagueSignupDisable(); // if "internet game" button is switched off in lobby: Remove from league server + bool LeagueSignupEnable(); // if "internet game" button is switched on in lobby: (re)Add to league server + void InvalidateReference(); // forces a recreation and re-send of the game reference in the next execution cycle + bool LeaguePlrAuth(C4PlayerInfo *pInfo); // client: get authentication for a player from the league server + bool LeaguePlrAuthCheck(C4PlayerInfo *pInfo); // host: check AUID of player info with league server + void LeagueNotifyDisconnect(int32_t iClientID, enum C4LeagueDisconnectReason eReason); // + void LeagueWaitNotBusy(); // block until league serveris no longer busy. Process update reply if last message was an update + void LeagueSurrender(); // forfeit in league - just fake a disconnect + void LeagueShowError(const char *szMsg); // show league error msg box in fullscreen; just log in console + + // voting + void Vote(C4ControlVoteType eType, bool fApprove = true, int32_t iData = 0); + void AddVote(const C4ControlVote &Vote); + C4IDPacket *GetVote(int32_t iClientID, C4ControlVoteType eType, int32_t iData); + void EndVote(C4ControlVoteType eType, bool fApprove, int32_t iData); + void OpenVoteDialog(); + void OpenSurrenderDialog(C4ControlVoteType eType, int32_t iData); + void OnVoteDialogClosed(); + + // lobby countdown + void StartLobbyCountdown(int32_t iCountdownTime); + void AbortLobbyCountdown(); + bool isLobbyCountDown() { return pLobbyCountdown != 0; } + + // streaming + size_t getPendingStreamData() const { return StreamingBuf.getSize() - StreamCompressor.avail_out; } + bool isStreaming() const; + bool StartStreaming(C4Record *pRecord); + bool FinishStreaming(); + bool StopStreaming(); + +protected: + + using C4ApplicationSec1Timer::Execute; // to avoid "virtual ... is hidden" warning + + // net i/o initialization + bool InitNetIO(bool fNoClientID, bool fHost); + + // handling of own packets + void HandleConn(const class C4PacketConn &Pkt, C4Network2IOConnection *pConn, C4Network2Client *pClient); + bool CheckConn(const C4ClientCore &CCore, C4Network2IOConnection *pConn, C4Network2Client *pClient, StdStrBuf * szReply); + bool HostConnect(const C4ClientCore &CCore, C4Network2IOConnection *pConn, StdStrBuf *szReply); + bool Join(C4ClientCore &CCore, C4Network2IOConnection *pConn, StdStrBuf *szReply); + void HandleConnRe(const class C4PacketConnRe &Pkt, C4Network2IOConnection *pConn, C4Network2Client *pClient); + void HandleStatus(const C4Network2Status &nStatus); + void HandleStatusAck(const C4Network2Status &nStatus, C4Network2Client *pClient); + void HandleActivateReq(int32_t iTick, C4Network2Client *pClient); + void HandleJoinData(const class C4PacketJoinData &rPkt); + + // events + void OnConnect(C4Network2Client *pClient, C4Network2IOConnection *pConn, const char *szMsg, bool fFirstConnection); + void OnConnectFail(C4Network2IOConnection *pConn); + void OnDisconnect(C4Network2Client *pClient, C4Network2IOConnection *pConn); + void OnClientConnect(C4Network2Client *pClient, C4Network2IOConnection *pConn); + void OnClientDisconnect(C4Network2Client *pClient); + + void SendJoinData(C4Network2Client *pClient); + + // resource list + bool CreateDynamic(bool fInit); + void RemoveDynamic(); + + // status changes + bool PauseGame(bool fAutoContinue); + bool ChangeGameStatus(C4NetGameState enState, int32_t iTargetCtrlTick, int32_t iCtrlMode = -1); + void CheckStatusReached(bool fFromFinalInit = false); + void CheckStatusAck(); + void OnStatusReached(); + void OnStatusAck(); + + // chase + void UpdateChaseTarget(); + + // league + bool InitLeague(bool *pCancel); + void DeinitLeague(); + bool LeagueStart(bool *pCancel); + bool LeagueUpdate(); + bool LeagueUpdateProcessReply(); + bool LeagueEnd(const char *szRecordName = NULL, const BYTE *pRecordSHA = NULL); + + // streaming + bool StreamIn(bool fFinish); + bool StreamOut(); +}; + +extern C4Network2 Network; + +class C4VoteDialog : public C4GUI::MessageDialog +{ +public: + C4VoteDialog(const char *szText, C4ControlVoteType eVoteType, int32_t iVoteData, bool fSurrender); + +private: + C4ControlVoteType eVoteType; + int32_t iVoteData; + bool fSurrender; + +public: + C4ControlVoteType getVoteType() const { return eVoteType; } + int32_t getVoteData() const { return iVoteData; } + +private: + virtual bool OnEnter() { UserClose(false); return true; } // the vote dialog defaults to "No" [MarkFra] + virtual void OnClosed(bool fOK); + + // true for dialogs that receive full keyboard and mouse input even in shared mode + virtual bool IsExclusiveDialog() { return true; } +}; + +// * Packets * + +class C4PacketJoinData : public C4PacketBase +{ +public: + C4PacketJoinData() { } + +protected: + + // the client ID + int32_t iClientID; + + // Dynamic data to boot + C4Network2ResCore Dynamic; + + // network status + C4Network2Status GameStatus; + + // control tick + int32_t iStartCtrlTick; + +public: + + // the game parameters + C4GameParameters Parameters; + +public: + const int32_t &getClientID() const { return iClientID; } + const C4Network2ResCore&getDynamicCore() const { return Dynamic; } + const C4Network2Status &getStatus() const { return GameStatus; } + int32_t getStartCtrlTick() const { return iStartCtrlTick; } + + void SetClientID(int32_t inClientID) { iClientID = inClientID; } + void SetGameStatus(const C4Network2Status &Status) { GameStatus = Status; } + void SetDynamicCore(const C4Network2ResCore &Core) { Dynamic = Core; } + void SetStartCtrlTick(int32_t iTick) { iStartCtrlTick = iTick; } + + virtual void CompileFunc(StdCompiler *pComp); +}; + +class C4PacketActivateReq : public C4PacketBase +{ +public: + C4PacketActivateReq(int32_t iTick = -1) : iTick(iTick) { } + +protected: + int32_t iTick; + +public: + int32_t getTick() const { return iTick; } + + virtual void CompileFunc(StdCompiler *pComp); +}; + +#endif diff --git a/src/network/C4Network2IO.cpp b/src/network/C4Network2IO.cpp index cd95ba8ad..548e4f86f 100644 --- a/src/network/C4Network2IO.cpp +++ b/src/network/C4Network2IO.cpp @@ -1,1643 +1,1643 @@ -/* - * OpenClonk, http://www.openclonk.org - * - * Copyright (c) 2004-2008 Peter Wortmann - * Copyright (c) 2004, 2006-2007 Sven Eberhardt - * Copyright (c) 2005-2006, 2009, 2011 Günther Brammer - * Copyright (c) 2008 Matthes Bender - * Copyright (c) 2010 Benjamin Herr - * Copyright (c) 2012 Nicolas Hake - * Copyright (c) 2001-2009, RedWolf Design GmbH, http://www.clonk.de - * - * Portions might be copyrighted by other authors who have contributed - * to OpenClonk. - * - * Permission to use, copy, modify, and/or distribute this software for any - * purpose with or without fee is hereby granted, provided that the above - * copyright notice and this permission notice appear in all copies. - * See isc_license.txt for full license and disclaimer. - * - * "Clonk" is a registered trademark of Matthes Bender. - * See clonk_trademark_license.txt for full license. - */ -#include -#include -#include - -#include -#include -#include -#include -#include - -#include "network/C4Network2UPnP.h" - -#ifndef HAVE_WINSOCK -#include -#include -#include -#endif - -// internal structures -struct C4Network2IO::NetEvPacketData -{ - C4NetIOPacket Packet; - C4Network2IOConnection *Conn; -}; - -// compile options -#define C4NET2IO_DUMP_LEVEL 1 - -// *** C4Network2IO - -C4Network2IO::C4Network2IO() - : pNetIO_TCP(NULL), pNetIO_UDP(NULL), - pNetIODiscover(NULL), pRefServer(NULL), - UPnPMgr(NULL), - pConnList(NULL), - iNextConnID(0), - fAllowConnect(false), - pAutoAcceptList(NULL), - fExclusiveConn(false), - iLastExecute(0), iLastPing(0), iLastStatistic(0), - iTCPIRate(0), iTCPORate(0), iTCPBCRate(0), - iUDPIRate(0), iUDPORate(0), iUDPBCRate(0) -{ - ZeroMem(&PuncherAddr, sizeof(PuncherAddr)); -} - -C4Network2IO::~C4Network2IO() -{ - Clear(); -} - -bool C4Network2IO::Init(int16_t iPortTCP, int16_t iPortUDP, int16_t iPortDiscover, int16_t iPortRefServer, bool fBroadcast) // by main thread -{ - // Already initialized? Clear first - if (pNetIO_TCP || pNetIO_UDP) Clear(); - - // init members - iLastPing = iLastStatistic = GetTime(); - iTCPIRate = iTCPORate = iTCPBCRate = 0; - iUDPIRate = iUDPORate = iUDPBCRate = 0; - - // init event callback - C4InteractiveThread &Thread = Application.InteractiveThread; - Thread.SetCallback(Ev_Net_Conn, this); - Thread.SetCallback(Ev_Net_Disconn, this); - Thread.SetCallback(Ev_Net_Packet, this); - - // initialize UPnP manager - if (iPortTCP > 0 || iPortUDP > 0) - { - assert(!UPnPMgr); - UPnPMgr = new C4Network2UPnP; - } - - // initialize net i/o classes: TCP first - if (iPortTCP > 0) - { - // create - pNetIO_TCP = new C4NetIOTCP(); - // init - if (!pNetIO_TCP->Init(iPortTCP)) - { - LogF("Network: could not init TCP i/o (%s)", pNetIO_TCP->GetError() ? pNetIO_TCP->GetError() : ""); - delete pNetIO_TCP; pNetIO_TCP = NULL; - } - else - LogSilentF("Network: TCP initialized on port %d", iPortTCP); - - // add to thread, set callback - if (pNetIO_TCP) - { - Thread.AddProc(pNetIO_TCP); - pNetIO_TCP->SetCallback(this); - UPnPMgr->AddMapping(P_TCP, iPortTCP, iPortTCP); - } - - } - // then UDP - if (iPortUDP > 0) - { - // create - pNetIO_UDP = new C4NetIOUDP(); - // init - if (!pNetIO_UDP->Init(iPortUDP)) - { - LogF("Network: could not init UDP i/o (%s)", pNetIO_UDP->GetError() ? pNetIO_UDP->GetError() : ""); - delete pNetIO_UDP; pNetIO_UDP = NULL; - } - else - LogSilentF("Network: UDP initialized on port %d", iPortUDP); - - // broadcast deactivated for now, it will possibly cause problems with connection recovery -#if 0 - if (pNetIO_UDP && fBroadcast) - { - // init broadcast - C4NetIO::addr_t BCAddr; ZeroMem(&BCAddr, sizeof BCAddr); - if (!pNetIO_UDP->InitBroadcast(&BCAddr)) - LogF("Network: could not init UDP broadcast (%s)", pNetIO_UDP->GetError() ? pNetIO_UDP->GetError() : ""); - else - LogSilentF("Network: UDP broadcast using %s:%d", inet_ntoa(BCAddr.sin_addr), htons(BCAddr.sin_port)); - } -#endif - - // add to thread, set callback - if (pNetIO_UDP) - { - Thread.AddProc(pNetIO_UDP); - pNetIO_UDP->SetCallback(this); - UPnPMgr->AddMapping(P_UDP, iPortUDP, iPortUDP); - } - } - - // no protocols? - if (!pNetIO_TCP && !pNetIO_UDP) - { - LogFatal("Network: fatal - no protocols available!"); - Thread.ClearCallback(Ev_Net_Conn, this); - Thread.ClearCallback(Ev_Net_Disconn, this); - Thread.ClearCallback(Ev_Net_Packet, this); - return false; - } - - // discovery last - if (iPortDiscover > 0) - { - // create - pNetIODiscover = new C4Network2IODiscover(iPortRefServer); - pNetIODiscover->SetDiscoverable(false); - // init - if (!pNetIODiscover->Init(iPortDiscover)) - { - LogF("Network: could not init discovery (%s)", pNetIODiscover->GetError() ? pNetIODiscover->GetError() : ""); - delete pNetIODiscover; pNetIODiscover = NULL; - } - else - LogSilentF("Network: discovery initialized on port %d", iPortDiscover); - // add to thread - if (pNetIODiscover) - Thread.AddProc(pNetIODiscover); - } - - // plus reference server - if (iPortRefServer > 0) - { - // create - pRefServer = new C4Network2RefServer(); - // init - if (!pRefServer->Init(iPortRefServer)) - { - LogF("Network: could not init reference server (%s)", pNetIO_UDP->GetError() ? pNetIO_UDP->GetError() : ""); - delete pRefServer; pRefServer = NULL; - } - else - LogSilentF("Network: reference server initialized on port %d", iPortRefServer); - // add to thread - if (pRefServer) - Thread.AddProc(pRefServer); - } - - // own timer - iLastExecute = GetTime(); - Thread.AddProc(this); - - // ok - return true; -} - -void C4Network2IO::Clear() // by main thread -{ - // process remaining events - C4InteractiveThread &Thread = Application.InteractiveThread; - Thread.ProcessEvents(); - // clear event callbacks - Thread.ClearCallback(Ev_Net_Conn, this); - Thread.ClearCallback(Ev_Net_Disconn, this); - Thread.ClearCallback(Ev_Net_Packet, this); - // close all connections - CStdLock ConnListLock(&ConnListCSec); - for (C4Network2IOConnection *pConn = pConnList, *pNext; pConn; pConn = pNext) - { - pNext = pConn->pNext; - // close - pConn->Close(); - RemoveConnection(pConn); - } - // reset list - pConnList = NULL; - ConnListLock.Clear(); - // close net i/o classes - Thread.RemoveProc(this); - if (pNetIODiscover) { Thread.RemoveProc(pNetIODiscover); delete pNetIODiscover; pNetIODiscover = NULL; } - if (pNetIO_TCP) { Thread.RemoveProc(pNetIO_TCP); delete pNetIO_TCP; pNetIO_TCP = NULL; } - if (pNetIO_UDP) { Thread.RemoveProc(pNetIO_UDP); delete pNetIO_UDP; pNetIO_UDP = NULL; } - if (pRefServer) { Thread.RemoveProc(pRefServer); delete pRefServer; pRefServer = NULL; } - delete UPnPMgr; UPnPMgr = NULL; - // remove auto-accepts - ClearAutoAccept(); - // reset flags - fAllowConnect = fExclusiveConn = false; - // reset connection ID - iNextConnID = 0; -} - -void C4Network2IO::SetLocalCCore(const C4ClientCore &nCCore) -{ - CStdLock LCCoreLock(&LCCoreCSec); - LCCore = nCCore; -} - -C4NetIO *C4Network2IO::MsgIO() // by both -{ - if (pNetIO_UDP) return pNetIO_UDP; - if (pNetIO_TCP) return pNetIO_TCP; - return NULL; -} - -C4NetIO *C4Network2IO::DataIO() // by both -{ - if (pNetIO_TCP) return pNetIO_TCP; - if (pNetIO_UDP) return pNetIO_UDP; - return NULL; -} - -bool C4Network2IO::Connect(const C4NetIO::addr_t &addr, C4Network2IOProtocol eProt, const C4ClientCore &nCCore, const char *szPassword) // by main thread -{ - // get network class - C4NetIO *pNetIO = getNetIO(eProt); - if (!pNetIO) return false; - // already connected/connecting? - if (GetConnectionByConnAddr(addr, pNetIO)) return true; - // assign new connection ID, peer address isn't known yet - uint32_t iConnID = iNextConnID++; - C4NetIO::addr_t paddr; ZeroMem(&paddr, sizeof paddr); - // create connection object and add to list - C4Network2IOConnection *pConn = new C4Network2IOConnection(); - pConn->Set(pNetIO, eProt, paddr, addr, CS_Connect, szPassword, iConnID); - pConn->SetCCore(nCCore); - AddConnection(pConn); - // connect - if (!pConn->Connect()) - { - // show error - LogF("Network: could not connect to %s:%d using %s: %s", inet_ntoa(addr.sin_addr), htons(addr.sin_port), - getNetIOName(pNetIO), pNetIO->GetError() ? pNetIO->GetError() : ""); - pNetIO->ResetError(); - // remove class - RemoveConnection(pConn); - return false; - } - // ok, wait for connection - return true; -} - -void C4Network2IO::SetAcceptMode(bool fnAllowConnect) // by main thread -{ - fAllowConnect = fnAllowConnect; - // Allow connect? Allow discovery of this host - if (fAllowConnect) - { - if (pNetIODiscover) - { - pNetIODiscover->SetDiscoverable(true); - pNetIODiscover->Announce(); - } - } -} - -void C4Network2IO::SetExclusiveConnMode(bool fnExclusiveConn) // by main thread -{ - if (fExclusiveConn == fnExclusiveConn) - return; - // Set flag - fExclusiveConn = fnExclusiveConn; - // Allowed? Send all pending welcome packets - if (!fExclusiveConn) - SendConnPackets(); -} - -int C4Network2IO::getConnectionCount() // by main thread -{ - int iCount = 0; - CStdLock ConnListLock(&ConnListCSec); - for (C4Network2IOConnection *pConn = pConnList; pConn; pConn = pConn->pNext) - if (!pConn->isClosed()) - iCount++; - return iCount; -} - -void C4Network2IO::ClearAutoAccept() // by main thread -{ - CStdLock AALock(&AutoAcceptCSec); - // delete - while (pAutoAcceptList) - { - // remove - AutoAccept *pAcc = pAutoAcceptList; - pAutoAcceptList = pAcc->Next; - // delete - delete pAcc; - } -} - -void C4Network2IO::AddAutoAccept(const C4ClientCore &CCore) // by main thread -{ - CStdLock AALock(&AutoAcceptCSec); - // create - AutoAccept *pAcc = new AutoAccept(); - pAcc->CCore = CCore; - // add - pAcc->Next = pAutoAcceptList; - pAutoAcceptList = pAcc; -} - -void C4Network2IO::RemoveAutoAccept(const C4ClientCore &CCore) // by main thread -{ - CStdLock AALock(&AutoAcceptCSec); - // find & remove - AutoAccept *pAcc = pAutoAcceptList, *pLast = NULL; - while (pAcc) - if (pAcc->CCore.getDiffLevel(CCore) <= C4ClientCoreDL_IDMatch) - { - // unlink - AutoAccept *pDelete = pAcc; - pAcc = pAcc->Next; - (pLast ? pLast->Next : pAutoAcceptList) = pAcc; - // delete - delete pDelete; - } - else - { - // next peer - pLast = pAcc; - pAcc = pAcc->Next; - } -} - -C4Network2IOConnection *C4Network2IO::GetMsgConnection(int iClientID) // by main thread -{ - CStdLock ConnListLock(&ConnListCSec); - C4Network2IOConnection *pRes = NULL; - for (C4Network2IOConnection *pConn = pConnList; pConn; pConn = pConn->pNext) - if (pConn->isAccepted()) - if (pConn->getClientID() == iClientID) - if (pConn->getProtocol() == P_UDP || !pRes) - pRes = pConn; - // add reference - if (pRes) pRes->AddRef(); - return pRes; -} - -C4Network2IOConnection *C4Network2IO::GetDataConnection(int iClientID) // by main thread -{ - CStdLock ConnListLock(&ConnListCSec); - C4Network2IOConnection *pRes = NULL; - for (C4Network2IOConnection *pConn = pConnList; pConn; pConn = pConn->pNext) - if (pConn->isAccepted()) - if (pConn->getClientID() == iClientID) - if (pConn->getProtocol() == P_TCP || !pRes) - pRes = pConn; - // add reference - if (pRes) pRes->AddRef(); - return pRes; -} - -void C4Network2IO::BeginBroadcast(bool fSelectAll) -{ - // lock - BroadcastCSec.Enter(); - // reset all broadcast flags - CStdLock ConnListLock(&ConnListCSec); - for (C4Network2IOConnection *pConn = pConnList; pConn; pConn = pConn->pNext) - if (pConn->isOpen()) - pConn->SetBroadcastTarget(fSelectAll); -} - -void C4Network2IO::EndBroadcast() -{ - // unlock - BroadcastCSec.Leave(); -} - -bool C4Network2IO::Broadcast(const C4NetIOPacket &rPkt) -{ - bool fSuccess = true; - // There is no broadcasting atm, emulate it - CStdLock ConnListLock(&ConnListCSec); - for (C4Network2IOConnection *pConn = pConnList; pConn; pConn = pConn->pNext) - if (pConn->isOpen() && pConn->isBroadcastTarget()) - fSuccess &= pConn->Send(rPkt); - if(!fSuccess) - Log("Network: Warning! Broadcast failed."); - return fSuccess; -#if 0 - // broadcast using all available i/o classes - if (pNetIO_TCP) fSuccess &= pNetIO_TCP->Broadcast(rPkt); - if (pNetIO_UDP) fSuccess &= pNetIO_UDP->Broadcast(rPkt); - return fSuccess; -#endif -} - -bool C4Network2IO::SendMsgToClient(C4NetIOPacket &rPkt, int iClient) // by both -{ - // find msg connection - C4Network2IOConnection *pConn = GetMsgConnection(iClient); - if (!pConn) return false; - // send - bool fSuccess = pConn->Send(rPkt); - pConn->DelRef(); - return fSuccess; -} - -bool C4Network2IO::BroadcastMsg(const C4NetIOPacket &rPkt) // by both -{ - // TODO: ugly algorithm. do better - - // begin broadcast - BeginBroadcast(false); - // select one connection per reachable client - CStdLock ConnListLock(&ConnListCSec); - for (C4Network2IOConnection *pConn = pConnList; pConn; pConn = pConn->pNext) - if (pConn->isAccepted()) - { - if (pConn->getProtocol() == P_UDP) - pConn->SetBroadcastTarget(true); - else if (pConn->getProtocol() == P_TCP) - { - C4Network2IOConnection *pConn2 = GetMsgConnection(pConn->getClientID()); - if (pConn == pConn2) - pConn->SetBroadcastTarget(true); - pConn2->DelRef(); - } - } - // send - bool fSuccess = Broadcast(rPkt); - // end broadcast - EndBroadcast(); - // return - return fSuccess; -} - -bool C4Network2IO::Punch(C4NetIO::addr_t nPuncherAddr) -{ - // UDP must be initialized - if (!pNetIO_UDP) - return false; - // save address - PuncherAddr = nPuncherAddr; - // let's punch - return pNetIO_UDP->Connect(PuncherAddr); -} - -// C4NetIO interface -bool C4Network2IO::OnConn(const C4NetIO::addr_t &PeerAddr, const C4NetIO::addr_t &ConnectAddr, const C4NetIO::addr_t *pOwnAddr, C4NetIO *pNetIO) -{ - // puncher answer? We just make sure here a connection /can/ be established, so close it instantly. - if (pNetIO == pNetIO_UDP) - if (PuncherAddr.sin_addr.s_addr && AddrEqual(PuncherAddr, ConnectAddr)) - { - // got an address? - if (pOwnAddr) - OnPunch(*pOwnAddr); - // this is only a test connection - close it instantly - return false; - } -#if(C4NET2IO_DUMP_LEVEL > 1) - unsigned int iTime = GetTime(); - ThreadLogS("OnConn: %d:%02d:%02d:%03d: %s", - (iTime / 1000 / 60 / 60), (iTime / 1000 / 60) % 60, (iTime / 1000) % 60, iTime % 1000, - getNetIOName(pNetIO)); -#endif - // search connection - C4Network2IOConnection *pConn = NULL; - if (ConnectAddr.sin_addr.s_addr) - pConn = GetConnectionByConnAddr(ConnectAddr, pNetIO); - // not found? - if (!pConn) - { - // allow connect? - if (!fAllowConnect) return false; - // create new connection object - uint32_t iConnID = iNextConnID++; - pConn = new C4Network2IOConnection(); - pConn->Set(pNetIO, getNetIOProt(pNetIO), PeerAddr, ConnectAddr, CS_Connected, NULL, iConnID); - // add to list - AddConnection(pConn); - } - else - { - // already closed this connection (attempt)? - if (pConn->isClosed()) - return false; - if (!pConn->isOpen()) - { - // change status - pConn->SetStatus(CS_Connected); - pConn->SetPeerAddr(PeerAddr); - } - } - // send welcome packet, if appropriate - SendConnPackets(); -#if(C4NET2IO_DUMP_LEVEL > 0) - // log - Application.InteractiveThread.ThreadLogS("Network: got %s connection from %s:%d", getNetIOName(pNetIO), inet_ntoa(PeerAddr.sin_addr), htons(PeerAddr.sin_port)); -#endif - // do event (disabled - unused) - // pConn->AddRef(); PushNetEv(NE_Conn, pConn); - // ok - return true; -} - -void C4Network2IO::OnDisconn(const C4NetIO::addr_t &addr, C4NetIO *pNetIO, const char *szReason) -{ - // punch? - if (pNetIO == pNetIO_UDP) - if (PuncherAddr.sin_addr.s_addr && AddrEqual(PuncherAddr, addr)) - { - ZeroMem(&PuncherAddr, sizeof(PuncherAddr)); - return; - } -#if(C4NET2IO_DUMP_LEVEL > 1) - unsigned int iTime = GetTime(); - ThreadLogS("OnDisconn: %d:%02d:%02d:%03d: %s", - (iTime / 1000 / 60 / 60), (iTime / 1000 / 60) % 60, (iTime / 1000) % 60, iTime % 1000, - getNetIOName(pNetIO)); -#endif - // find connection - C4Network2IOConnection *pConn = GetConnection(addr, pNetIO); - if (!pConn) pConn = GetConnectionByConnAddr(addr, pNetIO); - if (!pConn) return; -#if(C4NET2IO_DUMP_LEVEL > 0) - // log - Application.InteractiveThread.ThreadLogS("Network: %s connection to %s:%d %s (%s)", - getNetIOName(pNetIO), inet_ntoa(addr.sin_addr), htons(addr.sin_port), pConn->isConnecting() ? "failed" : "closed" , szReason); -#endif - // already closed? ignore - if (!pConn->isClosed()) - // not accepted yet? count as connection failure - pConn->SetStatus(pConn->isHalfAccepted() ? CS_Closed : CS_ConnectFail); - // keep connection for main thread message - pConn->AddRef(); - // check for pending welcome packets - SendConnPackets(); - // signal to main thread - Application.InteractiveThread.PushEvent(Ev_Net_Disconn, pConn); - // don't remove connection from list - wait for postmortem or timeout -} - -void C4Network2IO::OnPacket(const class C4NetIOPacket &rPacket, C4NetIO *pNetIO) -{ -#if(C4NET2IO_DUMP_LEVEL > 1) - unsigned int iTime = GetTime(); - ThreadLogS("OnPacket: %d:%02d:%02d:%03d: status %02x %s", - (iTime / 1000 / 60 / 60), (iTime / 1000 / 60) % 60, (iTime / 1000) % 60, iTime % 1000, - rPacket.getStatus(), getNetIOName(pNetIO)); -#endif - if (!rPacket.getSize()) return; - // find connection - C4Network2IOConnection *pConn = GetConnection(rPacket.getAddr(), pNetIO); - if (!pConn) { Application.InteractiveThread.ThreadLog("Network: could not find connection for packet from %s:%d!", inet_ntoa(rPacket.getAddr().sin_addr), htons(rPacket.getAddr().sin_port)); return; } -#if(C4NET2IO_DUMP_LEVEL > 2) - if (GetTime() - iTime > 100) - ThreadLogS("OnPacket: ... blocked %d ms for finding the connection!", GetTime() - iTime); -#endif - // notify - pConn->OnPacketReceived(rPacket.getStatus()); - // handle packet - HandlePacket(rPacket, pConn, true); - // log time -#if(C4NET2IO_DUMP_LEVEL > 1) - if (GetTime() - iTime > 100) - ThreadLogS("OnPacket: ... blocked %d ms for handling!", GetTime() - iTime); -#endif -} - -void C4Network2IO::OnError(const char *strError, C4NetIO *pNetIO) -{ - // let's log it - Application.InteractiveThread.ThreadLog("Network: %s error: %s", getNetIOName(pNetIO), strError); -} - -bool C4Network2IO::Execute(int iTimeout, pollfd *) -{ - iLastExecute = GetTime(); - - // check for timeout - CheckTimeout(); - - // ping all open connections - if (!Inside(iLastPing, GetTime() - C4NetPingFreq, GetTime())) - { - Ping(); - iLastPing = iLastExecute; - } - - // do statistics - if (!Inside(iLastStatistic, GetTime() - C4NetStatisticsFreq, GetTime())) - { - GenerateStatistics(iLastExecute - iLastStatistic); - iLastStatistic = iLastExecute; - } - - // resources - ::Network.ResList.OnTimer(); - - // ok - return true; -} - -int C4Network2IO::GetNextTick(int Now) -{ - return iLastExecute + C4NetTimer; -} - -void C4Network2IO::OnThreadEvent(C4InteractiveEventType eEvent, void *pEventData) // by main thread -{ - switch (eEvent) - { - case Ev_Net_Conn: // got a connection - { - C4Network2IOConnection *pConn = reinterpret_cast(pEventData); - // do callback - ::Network.OnConn(pConn); - // remove reference - pConn->DelRef(); - } - break; - - case Ev_Net_Disconn: // connection closed - { - C4Network2IOConnection *pConn = reinterpret_cast(pEventData); - assert(pConn->isClosed()); - // do callback - ::Network.OnDisconn(pConn); - // remove reference - pConn->DelRef(); - } - break; - - case Ev_Net_Packet: // got packet - { - NetEvPacketData *pData = reinterpret_cast(pEventData); - // handle - HandlePacket(pData->Packet, pData->Conn, false); - // clear up - pData->Conn->DelRef(); - delete pData; - } - break; - - default: - // TODO - break; - } -} - -C4NetIO *C4Network2IO::getNetIO(C4Network2IOProtocol eProt) // by both -{ - switch (eProt) - { - case P_UDP: return pNetIO_UDP; - case P_TCP: return pNetIO_TCP; - default: return NULL; - } -} - -const char *C4Network2IO::getNetIOName(C4NetIO *pNetIO) -{ - if (!pNetIO) return "NULL"; - if (pNetIO == pNetIO_TCP) return "TCP"; - if (pNetIO == pNetIO_UDP) return "UDP"; - return "UNKNOWN"; -} - -C4Network2IOProtocol C4Network2IO::getNetIOProt(C4NetIO *pNetIO) -{ - if (!pNetIO) return P_NONE; - if (pNetIO == pNetIO_TCP) return P_TCP; - if (pNetIO == pNetIO_UDP) return P_UDP; - return P_NONE; -} - -void C4Network2IO::AddConnection(C4Network2IOConnection *pConn) // by both -{ - CStdLock ConnListLock(&ConnListCSec); - // add reference - pConn->AddRef(); - // add to list - pConn->pNext = pConnList; pConnList = pConn; -} - -void C4Network2IO::RemoveConnection(C4Network2IOConnection *pConn) // by both -{ - CStdLock ConnListLock(&ConnListCSec); - // search & remove - if (pConnList == pConn) - pConnList = pConn->pNext; - else - { - C4Network2IOConnection *pAct; - for (pAct = pConnList; pAct; pAct = pAct->pNext) - if (pAct->pNext == pConn) - break; - if (pAct) - pAct->pNext = pConn->pNext; - else - return; - } - // remove reference - pConn->pNext = NULL; pConn->DelRef(); -} - -C4Network2IOConnection *C4Network2IO::GetConnection(const C4NetIO::addr_t &addr, C4NetIO *pNetIO) // by both -{ - CStdLock ConnListLock(&ConnListCSec); - // search - for (C4Network2IOConnection *pConn = pConnList; pConn; pConn = pConn->pNext) - if (pConn->getNetClass() == pNetIO && AddrEqual(pConn->getPeerAddr(), addr)) - return pConn; - return NULL; -} - -C4Network2IOConnection *C4Network2IO::GetConnectionByConnAddr(const C4NetIO::addr_t &addr, C4NetIO *pNetIO) // by both -{ - CStdLock ConnListLock(&ConnListCSec); - // search - for (C4Network2IOConnection *pConn = pConnList; pConn; pConn = pConn->pNext) - if (pConn->getNetClass() == pNetIO && AddrEqual(pConn->getConnectAddr(), addr)) - return pConn; - return NULL; -} - -C4Network2IOConnection *C4Network2IO::GetConnectionByID(uint32_t iConnID) // by thread -{ - CStdLock ConnListLock(&ConnListCSec); - // search - for (C4Network2IOConnection *pConn = pConnList; pConn; pConn = pConn->pNext) - if (pConn->getID() == iConnID) - return pConn; - return NULL; -} - -void C4Network2IO::SetReference(C4Network2Reference *pReference) -{ - if (pRefServer) - pRefServer->SetReference(pReference); - else - delete pReference; -} - -bool C4Network2IO::IsReferenceNeeded() -{ - return !!pRefServer; -} - -bool C4Network2IO::doAutoAccept(const C4ClientCore &CCore, const C4Network2IOConnection &Conn) -{ - CStdLock AALock(&AutoAcceptCSec); - // check if connection with the given client should be allowed - for (AutoAccept *pAcc = pAutoAcceptList; pAcc; pAcc = pAcc->Next) - // core match? - if (CCore.getDiffLevel(pAcc->CCore) <= C4ClientCoreDL_IDMatch) - { - // check: already got another connection for this client? Peer IP must match, then. - for (C4Network2IOConnection *pConn = pConnList; pConn; pConn = pConn->pNext) - if (pConn->isAccepted() && - pConn->getCCore().getDiffLevel(CCore) <= C4ClientCoreDL_IDMatch && - pConn->getPeerAddr().sin_addr.s_addr != Conn.getPeerAddr().sin_addr.s_addr) - return false; - // not found or IP matches? Let pass - return true; - } - return false; -} - -bool C4Network2IO::HandlePacket(const C4NetIOPacket &rPacket, C4Network2IOConnection *pConn, bool fThread) -{ - // security: add connection reference - if (!pConn) return false; pConn->AddRef(); - - // accept only PID_Conn and PID_Ping on non-accepted connections - if(!pConn->isHalfAccepted()) - if(rPacket.getStatus() != PID_Conn && rPacket.getStatus() != PID_Ping && rPacket.getStatus() != PID_ConnRe) - return false; - - // unpack packet (yet another no-idea-why-it's-needed-cast) - C4IDPacket Pkt; C4PacketBase &PktB = Pkt; - try - { - PktB.unpack(rPacket); - } - catch (StdCompiler::Exception *pExc) - { - Application.InteractiveThread.ThreadLog("Network: error: Failed to unpack packet id %02x: %s", rPacket.getStatus(), pExc->Msg.getData()); - delete pExc; -#ifndef _DEBUG - pConn->Close(); -#endif - return false; - } - - // dump packet (network thread only) -#if(C4NET2IO_DUMP_LEVEL > 0) - if (fThread && Pkt.getPktType() != PID_Ping && Pkt.getPktType() != PID_Pong && Pkt.getPktType() != PID_NetResData) - { - unsigned int iTime = GetTime(); - // StdStrBuf PacketDump = DecompileToBuf(mkNamingAdaptrPacket); - StdStrBuf PacketHeader = FormatString("HandlePacket: %d:%02d:%02d:%03d by %s:%d (%lu bytes, counter %d)", - (iTime / 1000 / 60 / 60), (iTime / 1000 / 60) % 60, (iTime / 1000) % 60, iTime % 1000, - inet_ntoa(pConn->getPeerAddr().sin_addr), htons(pConn->getPeerAddr().sin_port), - static_cast(rPacket.getSize()), pConn->getInPacketCounter()); - StdStrBuf Dump = DecompileToBuf(mkNamingAdapt(Pkt, PacketHeader.getData())); - // Put it directly. The standard functions behind StdBuf.Format seem to choke when you pass them too much data. - Application.InteractiveThread.PushEvent(Ev_LogSilent, Dump.GrabPointer()); - } -#endif - - // search packet handling data - bool fSendToMainThread = false, fHandled = false; - for (const C4PktHandlingData *pHData = PktHandlingData; pHData->ID != PID_None; pHData++) - if (pHData->ID == rPacket.getStatus()) - { - // correct thread? - if (!pHData->ProcByThread == !fThread) - { - // connection accepted? - if (pHData->AcceptedOnly || pConn->isAccepted() || pConn->isClosed()) - { - fHandled = true; -#if(C4NET2IO_DUMP_LEVEL > 2) - unsigned int iStart = GetTime(); -#endif - - // call handler(s) - CallHandlers(pHData->HandlerID, &Pkt, pConn, fThread); - -#if(C4NET2IO_DUMP_LEVEL > 2) - if (fThread && GetTime() - iStart > 100) - ThreadLogS("HandlePacket: ... blocked for %d ms!", GetTime() - iStart); -#endif - - } - } - // transfer to main thread? - else if (!pHData->ProcByThread && fThread) - { - fHandled = true; - fSendToMainThread = true; - } - } - - // send to main thread? - if (fSendToMainThread) - { - // create data - NetEvPacketData *pEvData = new NetEvPacketData; - pEvData->Packet.Take(rPacket.Duplicate()); - pEvData->Conn = pConn; pConn->AddRef(); - // trigger event - if (!Application.InteractiveThread.PushEvent(Ev_Net_Packet, pEvData)) - Application.InteractiveThread.ThreadLogS("...push event "); - } - - // unhandled? - if (!fHandled && !pConn->isClosed()) - Application.InteractiveThread.ThreadLog("Network: Unhandled packet (status %02x)", rPacket.getStatus()); - - // remove connection reference - pConn->DelRef(); - return fHandled; -} - -void C4Network2IO::CallHandlers(int iHandlerID, const C4IDPacket *pPkt, C4Network2IOConnection *pConn, bool fThread) -{ - // emulate old callbacks - char cStatus = pPkt->getPktType(); - const C4PacketBase *pPacket = pPkt->getPkt(); - // this class (network thread) - if (iHandlerID & PH_C4Network2IO) - { - assert(fThread); - HandlePacket(cStatus, pPacket, pConn); - } - // main network class (main thread) - if (iHandlerID & PH_C4Network2) - { - assert(!fThread); - ::Network.HandlePacket(cStatus, pPacket, pConn); - } - // fullscreen lobby - if (iHandlerID & PH_C4GUIMainDlg) - { - assert(!fThread); - ::Network.HandleLobbyPacket(cStatus, pPacket, pConn); - } - // client list class (main thread) - if (iHandlerID & PH_C4Network2ClientList) - { - assert(!fThread); - ::Network.Clients.HandlePacket(cStatus, pPacket, pConn); - } - // player list class (main thread) - if (iHandlerID & PH_C4Network2Players) - { - assert(!fThread); - ::Network.Players.HandlePacket(cStatus, pPacket, pConn); - } - // resource list class (network thread) - if (iHandlerID & PH_C4Network2ResList) - { - assert(fThread); - ::Network.ResList.HandlePacket(cStatus, pPacket, pConn); - } - // network control (mixed) - if (iHandlerID & PH_C4GameControlNetwork) - { - ::Control.Network.HandlePacket(cStatus, pPacket, pConn); - } -} - -void C4Network2IO::HandlePacket(char cStatus, const C4PacketBase *pPacket, C4Network2IOConnection *pConn) -{ - // security - if (!pConn) return; - -#define GETPKT(type, name) \ - assert(pPacket); const type &name = \ - /*dynamic_cast*/ static_cast(*pPacket); - - switch (cStatus) - { - - case PID_Conn: // connection request - { - if (!pConn->isOpen()) break; - // get packet - GETPKT(C4PacketConn, rPkt) - // set connection ID - pConn->SetRemoteID(rPkt.getConnID()); - // check auto-accept - if (doAutoAccept(rPkt.getCCore(), *pConn)) - { - // send answer back - C4PacketConnRe pcr(true, false, "auto accept"); - if (!pConn->Send(MkC4NetIOPacket(PID_ConnRe, pcr))) - pConn->Close(); - // accept - pConn->SetStatus(CS_HalfAccepted); - pConn->SetCCore(rPkt.getCCore()); - pConn->SetAutoAccepted(); - } - // note that this packet will get processed by C4Network2, too (main thread) - } - break; - - case PID_ConnRe: // connection request reply - { - if (!pConn->isOpen()) break; - // conn not sent? That's fishy. - // FIXME: Note this happens if the peer has exclusive connection mode on. - if (!pConn->isConnSent()) - { - pConn->Close(); - break; - } - // get packet - GETPKT(C4PacketConnRe, rPkt) - // auto accept connection - if (rPkt.isOK()) - { - if (pConn->isHalfAccepted() && pConn->isAutoAccepted()) - pConn->SetAccepted(); - } - } - break; - - case PID_Ping: - { - if (!pConn->isOpen()) break; - GETPKT(C4PacketPing, rPkt) - // pong - C4PacketPing PktPong = rPkt; - pConn->Send(MkC4NetIOPacket(PID_Pong, PktPong)); - // remove received packets from log - pConn->ClearPacketLog(rPkt.getPacketCounter()); - } - break; - - case PID_Pong: - { - if (!pConn->isOpen()) break; - GETPKT(C4PacketPing, rPkt); - // save - pConn->SetPingTime(rPkt.getTravelTime()); - } - break; - - case PID_FwdReq: - { - GETPKT(C4PacketFwd, rPkt); - HandleFwdReq(rPkt, pConn); - } - break; - - case PID_Fwd: - { - GETPKT(C4PacketFwd, rPkt); - // only received accidently? - if (!rPkt.DoFwdTo(LCCore.getID())) break; - // handle - C4NetIOPacket Packet(rPkt.getData(), pConn->getPeerAddr()); - HandlePacket(Packet, pConn, true); - } - break; - - case PID_PostMortem: - { - GETPKT(C4PacketPostMortem, rPkt); - // Get connection - C4Network2IOConnection *pConn = GetConnectionByID(rPkt.getConnID()); - if (!pConn) return; - // Handle all packets - uint32_t iCounter; - for (iCounter = pConn->getInPacketCounter(); ; iCounter++) - { - // Get packet - const C4NetIOPacket *pPkt = rPkt.getPacket(iCounter); - if (!pPkt) break; - // Handle it - HandlePacket(*pPkt, pConn, true); - } - // Log - if (iCounter > pConn->getInPacketCounter()) - Application.InteractiveThread.ThreadLogS("Network: Recovered %d packets", iCounter - pConn->getInPacketCounter()); - // Remove the connection from our list - if (!pConn->isClosed()) - pConn->Close(); - RemoveConnection(pConn); - } - break; - - } - -#undef GETPKT -} - -void C4Network2IO::HandleFwdReq(const C4PacketFwd &rFwd, C4Network2IOConnection *pBy) -{ - CStdLock ConnListLock(&ConnListCSec); - // init packet - C4PacketFwd nFwd; - nFwd.SetListType(false); - // find all clients the message should be forwarded to - int iClientID; C4Network2IOConnection *pConn; - for (pConn = pConnList; pConn; pConn = pConn->pNext) - if (pConn->isAccepted()) - if ((iClientID = pConn->getClientID()) >= 0) - if (iClientID != pBy->getClientID()) - if (rFwd.DoFwdTo(iClientID) && !nFwd.DoFwdTo(iClientID)) - nFwd.AddClient(iClientID); - // check count (hardcoded: broadcast for > 2 clients) - if (nFwd.getClientCnt() <= 2) - { - C4NetIOPacket Pkt(rFwd.getData(), C4NetIO::addr_t()); - for (int i = 0; i < nFwd.getClientCnt(); i++) - if ((pConn = GetMsgConnection(nFwd.getClient(i)))) - { - pConn->Send(Pkt); - pConn->DelRef(); - } - } - else - { - // Temporarily unlock connection list for getting broadcast lock - // (might lead to deathlocks otherwise, as the lock is often taken - // in the opposite order) - ConnListLock.Clear(); - - BeginBroadcast(); - nFwd.SetData(rFwd.getData()); - // add all clients - CStdLock ConnListLock(&ConnListCSec); - for (int i = 0; i < nFwd.getClientCnt(); i++) - if ((pConn = GetMsgConnection(nFwd.getClient(i)))) - { - pConn->SetBroadcastTarget(true); - pConn->DelRef(); - } - // broadcast - Broadcast(MkC4NetIOPacket(PID_Fwd, nFwd)); - EndBroadcast(); - } - // doing a callback here; don't lock! - ConnListLock.Clear(); - // forward to self? - if (rFwd.DoFwdTo(LCCore.getID())) - { - C4NetIOPacket Packet(rFwd.getData(), pBy->getPeerAddr()); - HandlePacket(Packet, pBy, true); - } -} - -bool C4Network2IO::Ping() -{ - bool fSuccess = true; - // ping all connections - for (C4Network2IOConnection *pConn = pConnList; pConn; pConn = pConn->pNext) - if (pConn->isOpen()) - { - C4PacketPing Ping(pConn->getInPacketCounter(), pConn->getOutPacketCounter()); - fSuccess &= pConn->Send(MkC4NetIOPacket(PID_Ping, Ping)); - pConn->OnPing(); - } - return fSuccess; -#if 0 - // begin broadcast - BeginBroadcast(true); - // make packet - C4NetIOPacket Pkt = MkC4NetIOPacket(PID_Ping, C4PacketPing()); - // ping everyone - if (pNetIO_TCP) - if (!pNetIO_TCP->Broadcast(Pkt)) - { fSuccess = false; ThreadLog("Network: failed to broadcast TCP ping! (%s)", pNetIO_TCP->GetError()); pNetIO_TCP->ResetError(); } - if (pNetIO_UDP) - if (!pNetIO_UDP->Broadcast(Pkt)) - { fSuccess = false; ThreadLog("Network: failed to broadcast UDP ping! (%s)", pNetIO_TCP->GetError()); pNetIO_TCP->ResetError(); } - // end broadcast - EndBroadcast(); - // notify connections - for (C4Network2IOConnection *pConn = pConnList; pConn; pConn = pConn->pNext) - pConn->OnPing(); - // return - return fSuccess; -#endif -} - -void C4Network2IO::CheckTimeout() -{ - // acquire lock - CStdLock ConnListLock(&ConnListCSec); - // check all connections for timeout (use deletion-safe iteration method just in case) - for (C4Network2IOConnection *pConn = pConnList, *pNext; pConn; pConn = pNext) - { - pNext = pConn->pNext; - // status timeout - if (!pConn->isClosed() && !pConn->isAccepted()) - if (difftime(time(NULL), pConn->getTimestamp()) > C4NetAcceptTimeout) - { - Application.InteractiveThread.ThreadLogS("Network: connection accept timeout to %s:%d", inet_ntoa(pConn->getPeerAddr().sin_addr), htons(pConn->getPeerAddr().sin_port)); - pConn->Close(); - } - // ping timeout - if (pConn->isAccepted()) - if ((pConn->getLag() != -1 ? pConn->getLag() : 1000 * (time(NULL) - pConn->getTimestamp())) - > C4NetPingTimeout) - { - Application.InteractiveThread.ThreadLogS("Network: ping timeout to %s:%d", inet_ntoa(pConn->getPeerAddr().sin_addr), htons(pConn->getPeerAddr().sin_port)); - pConn->Close(); - } - // delayed connection removal - if (pConn->isClosed()) - if (difftime(time(NULL), pConn->getTimestamp()) > C4NetAcceptTimeout) - RemoveConnection(pConn); - } -} - -void C4Network2IO::GenerateStatistics(int iInterval) -{ - int iTCPIRateSum = 0, iTCPORateSum = 0, - iUDPIRateSum = 0, iUDPORateSum = 0; - - // acquire lock, get connection statistics - CStdLock ConnListLock(&ConnListCSec); - for (C4Network2IOConnection *pConn = pConnList; pConn; pConn = pConn->pNext) - if (pConn->isOpen()) - { - bool fTCP = pConn->getNetClass() == pNetIO_TCP; - pConn->DoStatistics(iInterval, fTCP ? &iTCPIRateSum : &iUDPIRateSum, - fTCP ? &iTCPORateSum : &iUDPORateSum); - } - ConnListLock.Clear(); - - // get broadcast statistics - int inTCPBCRate = 0, inUDPBCRate = 0; - if (pNetIO_TCP) pNetIO_TCP->GetStatistic(&inTCPBCRate); - if (pNetIO_UDP) pNetIO_UDP->GetStatistic(&inUDPBCRate); - - // normalize everything - iTCPIRateSum = iTCPIRateSum * 1000 / iInterval; - iTCPORateSum = iTCPORateSum * 1000 / iInterval; - iUDPIRateSum = iUDPIRateSum * 1000 / iInterval; - iUDPORateSum = iUDPORateSum * 1000 / iInterval; - inTCPBCRate = inTCPBCRate * 1000 / iInterval; - inUDPBCRate = inUDPBCRate * 1000 / iInterval; - - // clear - if (pNetIO_TCP) pNetIO_TCP->ClearStatistic(); - if (pNetIO_UDP) pNetIO_UDP->ClearStatistic(); - - // save back - iTCPIRate = iTCPIRateSum; iTCPORate = iTCPORateSum; iTCPBCRate = inTCPBCRate; - iUDPIRate = iUDPIRateSum; iUDPORate = iUDPORateSum; iUDPBCRate = inUDPBCRate; -} - -void C4Network2IO::SendConnPackets() -{ - CStdLock ConnListLock(&ConnListCSec); - - // exlusive conn? - if (fExclusiveConn) - // find a live connection - for (C4Network2IOConnection *pConn = pConnList; pConn; pConn = pConn->pNext) - if (pConn->isAccepted() || (!pConn->isClosed() && pConn->isConnSent())) - // do not sent additional conn packets - no other connection should succeed - return; - - // sent pending welcome packet(s) - for (C4Network2IOConnection *pConn = pConnList; pConn; pConn = pConn->pNext) - if (pConn->isOpen() && !pConn->isConnSent()) - { - // make packet - CStdLock LCCoreLock(&LCCoreCSec); - C4NetIOPacket Pkt = MkC4NetIOPacket(PID_Conn, C4PacketConn(LCCore, pConn->getID(), pConn->getPassword())); - LCCoreLock.Clear(); - // send - if (!pConn->Send(Pkt)) - pConn->Close(); - else - { - // set flag - pConn->SetConnSent(); - // only one conn packet at a time - if (fExclusiveConn) - return; - } - } - -} - -void C4Network2IO::OnPunch(C4NetIO::addr_t addr) -{ - // Sanity check - if (addr.sin_family != AF_INET && addr.sin_family != htons(AF_INET)) - return; - addr.sin_family = AF_INET; - ZeroMem(addr.sin_zero, sizeof(addr.sin_zero)); - // Add for local client - C4Network2Client *pLocal = ::Network.Clients.GetLocal(); - if (pLocal) - if (pLocal->AddAddr(C4Network2Address(addr, P_UDP), true)) - ::Network.InvalidateReference(); -} - -// *** C4Network2IOConnection - -C4Network2IOConnection::C4Network2IOConnection() - : pNetClass(NULL), - iID(~0), iRemoteID(~0), - fAutoAccept(false), - fBroadcastTarget(false), - iTimestamp(0), - iPingTime(-1), - iLastPing(ULONG_MAX), iLastPong(ULONG_MAX), - fConnSent(false), - fPostMortemSent(false), - iOutPacketCounter(0), iInPacketCounter(0), - pPacketLog(NULL), - pNext(NULL), - iRefCnt(0) -{ -} - -C4Network2IOConnection::~C4Network2IOConnection() -{ - assert(!iRefCnt); - // connection needs to be closed? - if (pNetClass && !isClosed()) Close(); - // clear the packet log - ClearPacketLog(); -} - -int C4Network2IOConnection::getLag() const -{ - // Last ping not answered yet? - if (iPingTime != -1 && iLastPing != ULONG_MAX && (iLastPong == ~0u || iLastPing > iLastPong)) - { - int iPingLag = GetTime() - iLastPing; - // Use it for lag measurement once it's larger then the last ping time - // (the ping time won't be better than this anyway once the pong's here) - return Max(iPingLag, iPingTime); - } - // Last ping result - return iPingTime; -} - -void C4Network2IOConnection::Set(C4NetIO *pnNetClass, C4Network2IOProtocol enProt, const C4NetIO::addr_t &nPeerAddr, const C4NetIO::addr_t &nConnectAddr, C4Network2IOConnStatus nStatus, const char *szPassword, uint32_t inID) -{ - // save data - pNetClass = pnNetClass; eProt = enProt; - PeerAddr = nPeerAddr; ConnectAddr = nConnectAddr; - Status = nStatus; - Password = szPassword; - iID = inID; - // initialize - fBroadcastTarget = false; - iTimestamp = time(NULL); iPingTime = -1; -} - -void C4Network2IOConnection::SetRemoteID(uint32_t inRemoteID) -{ - iRemoteID = inRemoteID; -} - -void C4Network2IOConnection::SetPeerAddr(const C4NetIO::addr_t &nPeerAddr) -{ - // just do it - PeerAddr = nPeerAddr; -} - -void C4Network2IOConnection::OnPing() -{ - // Still no pong for the last ping? - if (iLastPong < iLastPing) - return; - // Save time - iLastPing = GetTime(); -} - -void C4Network2IOConnection::SetPingTime(int inPingTime) -{ - // save it - iPingTime = inPingTime; - // pong received - save timestamp - iLastPong = GetTime(); -} - -void C4Network2IOConnection::SetStatus(C4Network2IOConnStatus nStatus) -{ - if (nStatus != Status) - { - // Connection can't return from these - assert(!isClosed()); - // set status - Status = nStatus; - // reset timestamp for connect/accept/close - if (Status == CS_Connect || Status == CS_Connected || Status == CS_Accepted || Status == CS_Closed) - iTimestamp = time(NULL); - } -} - -void C4Network2IOConnection::SetAutoAccepted() -{ - fAutoAccept = true; -} - -void C4Network2IOConnection::OnPacketReceived(uint8_t iPacketType) -{ - // Just count them - if (iPacketType >= PID_PacketLogStart) - iInPacketCounter++; -} - -void C4Network2IOConnection::ClearPacketLog(uint32_t iUntilID) -{ - // Search position of first packet to delete - PacketLogEntry *pPos, *pPrev = NULL; - for (pPos = pPacketLog; pPos; pPrev = pPos, pPos = pPos->Next) - if (pPos->Number < iUntilID) - break; - if (pPos) - { - // Remove packets from list - (pPrev ? pPrev->Next : pPacketLog) = NULL; - // Delete everything - while (pPos) - { - PacketLogEntry *pDelete = pPos; - pPos = pPos->Next; - delete pDelete; - } - } -} - -bool C4Network2IOConnection::CreatePostMortem(C4PacketPostMortem *pPkt) -{ - // Security - if (!pPkt) return false; - CStdLock PacketLogLock(&PacketLogCSec); - // Nothing to do? - if (!pPacketLog) return false; - // Already created? - if (fPostMortemSent) return false; - // Set connection ID and packet counter - pPkt->SetConnID(iRemoteID); - pPkt->SetPacketCounter(iOutPacketCounter); - // Add packets - for (PacketLogEntry *pEntry = pPacketLog; pEntry; pEntry = pEntry->Next) - pPkt->Add(pEntry->Pkt); - // Okay - fPostMortemSent = true; - return true; -} - -void C4Network2IOConnection::SetCCore(const C4ClientCore &nCCore) -{ - CStdLock CCoreLock(&CCoreCSec); - CCore = nCCore; -} - -bool C4Network2IOConnection::Connect() -{ - if (!pNetClass) return false; - // try connect - return pNetClass->Connect(ConnectAddr); -} - -void C4Network2IOConnection::Close() -{ - if (!pNetClass || isClosed()) return; - // set status - SetStatus(CS_Closed); - // close - pNetClass->Close(PeerAddr); -} - -bool C4Network2IOConnection::Send(const C4NetIOPacket &rPkt) -{ - // some packets shouldn't go into the log - if (rPkt.getStatus() < PID_PacketLogStart) - { - assert(isOpen()); - C4NetIOPacket Copy(rPkt); - Copy.SetAddr(PeerAddr); - return pNetClass->Send(Copy); - } - CStdLock PacketLogLock(&PacketLogCSec); - // create log entry - PacketLogEntry *pLogEntry = new PacketLogEntry(); - pLogEntry->Number = iOutPacketCounter++; - pLogEntry->Pkt = rPkt; - pLogEntry->Next = pPacketLog; - pPacketLog = pLogEntry; - // set address - pLogEntry->Pkt.SetAddr(PeerAddr); - // closed? No sweat, post mortem will reroute it later. - if (!isOpen()) - { - // post mortem already sent? This shouldn't happen - if (fPostMortemSent) { assert(false); return false; } - // okay then - return true; - } - // send - bool fSuccess = pNetClass->Send(pLogEntry->Pkt); - if (fSuccess) - assert(!fPostMortemSent); - else { - // Not being able to send a packet is actually a big deal, - // as this means that we will have hole in the packet - // order. Better close the connection - post mortem should - // ideally sort everything out from here. - LogF("Network: Fatal: Send failed (%s)", pNetClass->GetError()); - pNetClass->ResetError(); - Close(); - } - return fSuccess; -} - -void C4Network2IOConnection::SetBroadcastTarget(bool fSet) -{ - // Note that each thread will have to make sure that this flag won't be - // changed until Broadcast() is called. See C4Network2IO::BroadcastCSec. - pNetClass->SetBroadcast(PeerAddr, fSet); - fBroadcastTarget = fSet; -} - -void C4Network2IOConnection::DoStatistics(int iInterval, int *pIRateSum, int *pORateSum) -{ - // get C4NetIO statistics - int inIRate, inORate, inLoss; - if (!isOpen() || !pNetClass->GetConnStatistic(PeerAddr, &inIRate, &inORate, &inLoss)) - { - iIRate = iORate = iPacketLoss = 0; - return; - } - // normalize - inIRate = inIRate * 1000 / iInterval; - inORate = inORate * 1000 / iInterval; - // set - iIRate = inIRate; iORate = inORate; iPacketLoss = inLoss; - // sum up - if (pIRateSum) *pIRateSum += iIRate; - if (pORateSum) *pORateSum += iORate; -} - -void C4Network2IOConnection::AddRef() -{ - InterlockedIncrement(&iRefCnt); -} - -void C4Network2IOConnection::DelRef() -{ - if (!InterlockedDecrement(&iRefCnt)) - delete this; -} - - -// *** C4PacketPostMortem - -C4PacketPostMortem::C4PacketPostMortem() - : iConnID(~0), - iPacketCounter(~0), - iPacketCount(0), - pPackets(NULL) -{ - -} - -C4PacketPostMortem::~C4PacketPostMortem() -{ - while (pPackets) - { - PacketLink *pDelete = pPackets; - pPackets = pPackets->Next; - delete pDelete; - } - iPacketCount = 0; -} - -const C4NetIOPacket *C4PacketPostMortem::getPacket(uint32_t iNumber) const -{ - // Security - if (!Inside(iNumber, iPacketCounter - iPacketCount, iPacketCounter - 1)) - return NULL; - // Calculate position in list - iNumber = iNumber + iPacketCount - iPacketCounter; - // Search for the packet with the given number - PacketLink *pLink = pPackets; - for (; pLink && iNumber; iNumber--) - pLink = pLink->Next; - // Not found? - return pLink ? &pLink->Pkt : NULL; -} - -void C4PacketPostMortem::SetPacketCounter(uint32_t inPacketCounter) -{ - iPacketCounter = inPacketCounter; -} - -void C4PacketPostMortem::Add(const C4NetIOPacket &rPkt) -{ - // Add to head of list (reverse order) - PacketLink *pLink = new PacketLink(); - pLink->Pkt = rPkt; - pLink->Next = pPackets; - pPackets = pLink; - iPacketCount++; -} - -void C4PacketPostMortem::CompileFunc(StdCompiler *pComp) -{ - bool fCompiler = pComp->isCompiler(); - - // Connection ID, packet number and packet count - pComp->Value(mkNamingAdapt(iConnID, "ConnID")); - pComp->Value(mkNamingAdapt(iPacketCounter, "PacketCounter")); - pComp->Value(mkNamingAdapt(iPacketCount, "PacketCount")); - - // Packets - if (fCompiler) - { - // Read packets - for (uint32_t i = 0; i < iPacketCount; i++) - { - // Create list entry - PacketLink *pLink = new PacketLink(); - pLink->Next = pPackets; - pPackets = pLink; - // Compile data - pComp->Value(mkNamingAdapt(pLink->Pkt, "PacketData")); - } - // Reverse order - PacketLink *pPackets2 = pPackets; - pPackets = NULL; - while (pPackets2) - { - // Get link - PacketLink *pLink = pPackets2; - pPackets2 = pLink->Next; - // Readd to list - pLink->Next = pPackets; - pPackets = pLink; - } - } - else - { - // Write packets - for (PacketLink *pLink = pPackets; pLink; pLink = pLink->Next) - pComp->Value(mkNamingAdapt(pLink->Pkt, "PacketData")); - } -} +/* + * OpenClonk, http://www.openclonk.org + * + * Copyright (c) 2004-2008 Peter Wortmann + * Copyright (c) 2004, 2006-2007 Sven Eberhardt + * Copyright (c) 2005-2006, 2009, 2011 Günther Brammer + * Copyright (c) 2008 Matthes Bender + * Copyright (c) 2010 Benjamin Herr + * Copyright (c) 2012 Nicolas Hake + * Copyright (c) 2001-2009, RedWolf Design GmbH, http://www.clonk.de + * + * Portions might be copyrighted by other authors who have contributed + * to OpenClonk. + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * See isc_license.txt for full license and disclaimer. + * + * "Clonk" is a registered trademark of Matthes Bender. + * See clonk_trademark_license.txt for full license. + */ +#include +#include +#include + +#include +#include +#include +#include +#include + +#include "network/C4Network2UPnP.h" + +#ifndef HAVE_WINSOCK +#include +#include +#include +#endif + +// internal structures +struct C4Network2IO::NetEvPacketData +{ + C4NetIOPacket Packet; + C4Network2IOConnection *Conn; +}; + +// compile options +#define C4NET2IO_DUMP_LEVEL 1 + +// *** C4Network2IO + +C4Network2IO::C4Network2IO() + : pNetIO_TCP(NULL), pNetIO_UDP(NULL), + pNetIODiscover(NULL), pRefServer(NULL), + UPnPMgr(NULL), + pConnList(NULL), + iNextConnID(0), + fAllowConnect(false), + pAutoAcceptList(NULL), + fExclusiveConn(false), + tLastExecute(0), tLastPing(0), tLastStatistic(0), + iTCPIRate(0), iTCPORate(0), iTCPBCRate(0), + iUDPIRate(0), iUDPORate(0), iUDPBCRate(0) +{ + ZeroMem(&PuncherAddr, sizeof(PuncherAddr)); +} + +C4Network2IO::~C4Network2IO() +{ + Clear(); +} + +bool C4Network2IO::Init(int16_t iPortTCP, int16_t iPortUDP, int16_t iPortDiscover, int16_t iPortRefServer, bool fBroadcast) // by main thread +{ + // Already initialized? Clear first + if (pNetIO_TCP || pNetIO_UDP) Clear(); + + // init members + tLastPing = tLastStatistic = GetTime(); + iTCPIRate = iTCPORate = iTCPBCRate = 0; + iUDPIRate = iUDPORate = iUDPBCRate = 0; + + // init event callback + C4InteractiveThread &Thread = Application.InteractiveThread; + Thread.SetCallback(Ev_Net_Conn, this); + Thread.SetCallback(Ev_Net_Disconn, this); + Thread.SetCallback(Ev_Net_Packet, this); + + // initialize UPnP manager + if (iPortTCP > 0 || iPortUDP > 0) + { + assert(!UPnPMgr); + UPnPMgr = new C4Network2UPnP; + } + + // initialize net i/o classes: TCP first + if (iPortTCP > 0) + { + // create + pNetIO_TCP = new C4NetIOTCP(); + // init + if (!pNetIO_TCP->Init(iPortTCP)) + { + LogF("Network: could not init TCP i/o (%s)", pNetIO_TCP->GetError() ? pNetIO_TCP->GetError() : ""); + delete pNetIO_TCP; pNetIO_TCP = NULL; + } + else + LogSilentF("Network: TCP initialized on port %d", iPortTCP); + + // add to thread, set callback + if (pNetIO_TCP) + { + Thread.AddProc(pNetIO_TCP); + pNetIO_TCP->SetCallback(this); + UPnPMgr->AddMapping(P_TCP, iPortTCP, iPortTCP); + } + + } + // then UDP + if (iPortUDP > 0) + { + // create + pNetIO_UDP = new C4NetIOUDP(); + // init + if (!pNetIO_UDP->Init(iPortUDP)) + { + LogF("Network: could not init UDP i/o (%s)", pNetIO_UDP->GetError() ? pNetIO_UDP->GetError() : ""); + delete pNetIO_UDP; pNetIO_UDP = NULL; + } + else + LogSilentF("Network: UDP initialized on port %d", iPortUDP); + + // broadcast deactivated for now, it will possibly cause problems with connection recovery +#if 0 + if (pNetIO_UDP && fBroadcast) + { + // init broadcast + C4NetIO::addr_t BCAddr; ZeroMem(&BCAddr, sizeof BCAddr); + if (!pNetIO_UDP->InitBroadcast(&BCAddr)) + LogF("Network: could not init UDP broadcast (%s)", pNetIO_UDP->GetError() ? pNetIO_UDP->GetError() : ""); + else + LogSilentF("Network: UDP broadcast using %s:%d", inet_ntoa(BCAddr.sin_addr), htons(BCAddr.sin_port)); + } +#endif + + // add to thread, set callback + if (pNetIO_UDP) + { + Thread.AddProc(pNetIO_UDP); + pNetIO_UDP->SetCallback(this); + UPnPMgr->AddMapping(P_UDP, iPortUDP, iPortUDP); + } + } + + // no protocols? + if (!pNetIO_TCP && !pNetIO_UDP) + { + LogFatal("Network: fatal - no protocols available!"); + Thread.ClearCallback(Ev_Net_Conn, this); + Thread.ClearCallback(Ev_Net_Disconn, this); + Thread.ClearCallback(Ev_Net_Packet, this); + return false; + } + + // discovery last + if (iPortDiscover > 0) + { + // create + pNetIODiscover = new C4Network2IODiscover(iPortRefServer); + pNetIODiscover->SetDiscoverable(false); + // init + if (!pNetIODiscover->Init(iPortDiscover)) + { + LogF("Network: could not init discovery (%s)", pNetIODiscover->GetError() ? pNetIODiscover->GetError() : ""); + delete pNetIODiscover; pNetIODiscover = NULL; + } + else + LogSilentF("Network: discovery initialized on port %d", iPortDiscover); + // add to thread + if (pNetIODiscover) + Thread.AddProc(pNetIODiscover); + } + + // plus reference server + if (iPortRefServer > 0) + { + // create + pRefServer = new C4Network2RefServer(); + // init + if (!pRefServer->Init(iPortRefServer)) + { + LogF("Network: could not init reference server (%s)", pNetIO_UDP->GetError() ? pNetIO_UDP->GetError() : ""); + delete pRefServer; pRefServer = NULL; + } + else + LogSilentF("Network: reference server initialized on port %d", iPortRefServer); + // add to thread + if (pRefServer) + Thread.AddProc(pRefServer); + } + + // own timer + tLastExecute = GetTime(); + Thread.AddProc(this); + + // ok + return true; +} + +void C4Network2IO::Clear() // by main thread +{ + // process remaining events + C4InteractiveThread &Thread = Application.InteractiveThread; + Thread.ProcessEvents(); + // clear event callbacks + Thread.ClearCallback(Ev_Net_Conn, this); + Thread.ClearCallback(Ev_Net_Disconn, this); + Thread.ClearCallback(Ev_Net_Packet, this); + // close all connections + CStdLock ConnListLock(&ConnListCSec); + for (C4Network2IOConnection *pConn = pConnList, *pNext; pConn; pConn = pNext) + { + pNext = pConn->pNext; + // close + pConn->Close(); + RemoveConnection(pConn); + } + // reset list + pConnList = NULL; + ConnListLock.Clear(); + // close net i/o classes + Thread.RemoveProc(this); + if (pNetIODiscover) { Thread.RemoveProc(pNetIODiscover); delete pNetIODiscover; pNetIODiscover = NULL; } + if (pNetIO_TCP) { Thread.RemoveProc(pNetIO_TCP); delete pNetIO_TCP; pNetIO_TCP = NULL; } + if (pNetIO_UDP) { Thread.RemoveProc(pNetIO_UDP); delete pNetIO_UDP; pNetIO_UDP = NULL; } + if (pRefServer) { Thread.RemoveProc(pRefServer); delete pRefServer; pRefServer = NULL; } + delete UPnPMgr; UPnPMgr = NULL; + // remove auto-accepts + ClearAutoAccept(); + // reset flags + fAllowConnect = fExclusiveConn = false; + // reset connection ID + iNextConnID = 0; +} + +void C4Network2IO::SetLocalCCore(const C4ClientCore &nCCore) +{ + CStdLock LCCoreLock(&LCCoreCSec); + LCCore = nCCore; +} + +C4NetIO *C4Network2IO::MsgIO() // by both +{ + if (pNetIO_UDP) return pNetIO_UDP; + if (pNetIO_TCP) return pNetIO_TCP; + return NULL; +} + +C4NetIO *C4Network2IO::DataIO() // by both +{ + if (pNetIO_TCP) return pNetIO_TCP; + if (pNetIO_UDP) return pNetIO_UDP; + return NULL; +} + +bool C4Network2IO::Connect(const C4NetIO::addr_t &addr, C4Network2IOProtocol eProt, const C4ClientCore &nCCore, const char *szPassword) // by main thread +{ + // get network class + C4NetIO *pNetIO = getNetIO(eProt); + if (!pNetIO) return false; + // already connected/connecting? + if (GetConnectionByConnAddr(addr, pNetIO)) return true; + // assign new connection ID, peer address isn't known yet + uint32_t iConnID = iNextConnID++; + C4NetIO::addr_t paddr; ZeroMem(&paddr, sizeof paddr); + // create connection object and add to list + C4Network2IOConnection *pConn = new C4Network2IOConnection(); + pConn->Set(pNetIO, eProt, paddr, addr, CS_Connect, szPassword, iConnID); + pConn->SetCCore(nCCore); + AddConnection(pConn); + // connect + if (!pConn->Connect()) + { + // show error + LogF("Network: could not connect to %s:%d using %s: %s", inet_ntoa(addr.sin_addr), htons(addr.sin_port), + getNetIOName(pNetIO), pNetIO->GetError() ? pNetIO->GetError() : ""); + pNetIO->ResetError(); + // remove class + RemoveConnection(pConn); + return false; + } + // ok, wait for connection + return true; +} + +void C4Network2IO::SetAcceptMode(bool fnAllowConnect) // by main thread +{ + fAllowConnect = fnAllowConnect; + // Allow connect? Allow discovery of this host + if (fAllowConnect) + { + if (pNetIODiscover) + { + pNetIODiscover->SetDiscoverable(true); + pNetIODiscover->Announce(); + } + } +} + +void C4Network2IO::SetExclusiveConnMode(bool fnExclusiveConn) // by main thread +{ + if (fExclusiveConn == fnExclusiveConn) + return; + // Set flag + fExclusiveConn = fnExclusiveConn; + // Allowed? Send all pending welcome packets + if (!fExclusiveConn) + SendConnPackets(); +} + +int C4Network2IO::getConnectionCount() // by main thread +{ + int iCount = 0; + CStdLock ConnListLock(&ConnListCSec); + for (C4Network2IOConnection *pConn = pConnList; pConn; pConn = pConn->pNext) + if (!pConn->isClosed()) + iCount++; + return iCount; +} + +void C4Network2IO::ClearAutoAccept() // by main thread +{ + CStdLock AALock(&AutoAcceptCSec); + // delete + while (pAutoAcceptList) + { + // remove + AutoAccept *pAcc = pAutoAcceptList; + pAutoAcceptList = pAcc->Next; + // delete + delete pAcc; + } +} + +void C4Network2IO::AddAutoAccept(const C4ClientCore &CCore) // by main thread +{ + CStdLock AALock(&AutoAcceptCSec); + // create + AutoAccept *pAcc = new AutoAccept(); + pAcc->CCore = CCore; + // add + pAcc->Next = pAutoAcceptList; + pAutoAcceptList = pAcc; +} + +void C4Network2IO::RemoveAutoAccept(const C4ClientCore &CCore) // by main thread +{ + CStdLock AALock(&AutoAcceptCSec); + // find & remove + AutoAccept *pAcc = pAutoAcceptList, *pLast = NULL; + while (pAcc) + if (pAcc->CCore.getDiffLevel(CCore) <= C4ClientCoreDL_IDMatch) + { + // unlink + AutoAccept *pDelete = pAcc; + pAcc = pAcc->Next; + (pLast ? pLast->Next : pAutoAcceptList) = pAcc; + // delete + delete pDelete; + } + else + { + // next peer + pLast = pAcc; + pAcc = pAcc->Next; + } +} + +C4Network2IOConnection *C4Network2IO::GetMsgConnection(int iClientID) // by main thread +{ + CStdLock ConnListLock(&ConnListCSec); + C4Network2IOConnection *pRes = NULL; + for (C4Network2IOConnection *pConn = pConnList; pConn; pConn = pConn->pNext) + if (pConn->isAccepted()) + if (pConn->getClientID() == iClientID) + if (pConn->getProtocol() == P_UDP || !pRes) + pRes = pConn; + // add reference + if (pRes) pRes->AddRef(); + return pRes; +} + +C4Network2IOConnection *C4Network2IO::GetDataConnection(int iClientID) // by main thread +{ + CStdLock ConnListLock(&ConnListCSec); + C4Network2IOConnection *pRes = NULL; + for (C4Network2IOConnection *pConn = pConnList; pConn; pConn = pConn->pNext) + if (pConn->isAccepted()) + if (pConn->getClientID() == iClientID) + if (pConn->getProtocol() == P_TCP || !pRes) + pRes = pConn; + // add reference + if (pRes) pRes->AddRef(); + return pRes; +} + +void C4Network2IO::BeginBroadcast(bool fSelectAll) +{ + // lock + BroadcastCSec.Enter(); + // reset all broadcast flags + CStdLock ConnListLock(&ConnListCSec); + for (C4Network2IOConnection *pConn = pConnList; pConn; pConn = pConn->pNext) + if (pConn->isOpen()) + pConn->SetBroadcastTarget(fSelectAll); +} + +void C4Network2IO::EndBroadcast() +{ + // unlock + BroadcastCSec.Leave(); +} + +bool C4Network2IO::Broadcast(const C4NetIOPacket &rPkt) +{ + bool fSuccess = true; + // There is no broadcasting atm, emulate it + CStdLock ConnListLock(&ConnListCSec); + for (C4Network2IOConnection *pConn = pConnList; pConn; pConn = pConn->pNext) + if (pConn->isOpen() && pConn->isBroadcastTarget()) + fSuccess &= pConn->Send(rPkt); + if(!fSuccess) + Log("Network: Warning! Broadcast failed."); + return fSuccess; +#if 0 + // broadcast using all available i/o classes + if (pNetIO_TCP) fSuccess &= pNetIO_TCP->Broadcast(rPkt); + if (pNetIO_UDP) fSuccess &= pNetIO_UDP->Broadcast(rPkt); + return fSuccess; +#endif +} + +bool C4Network2IO::SendMsgToClient(C4NetIOPacket &rPkt, int iClient) // by both +{ + // find msg connection + C4Network2IOConnection *pConn = GetMsgConnection(iClient); + if (!pConn) return false; + // send + bool fSuccess = pConn->Send(rPkt); + pConn->DelRef(); + return fSuccess; +} + +bool C4Network2IO::BroadcastMsg(const C4NetIOPacket &rPkt) // by both +{ + // TODO: ugly algorithm. do better + + // begin broadcast + BeginBroadcast(false); + // select one connection per reachable client + CStdLock ConnListLock(&ConnListCSec); + for (C4Network2IOConnection *pConn = pConnList; pConn; pConn = pConn->pNext) + if (pConn->isAccepted()) + { + if (pConn->getProtocol() == P_UDP) + pConn->SetBroadcastTarget(true); + else if (pConn->getProtocol() == P_TCP) + { + C4Network2IOConnection *pConn2 = GetMsgConnection(pConn->getClientID()); + if (pConn == pConn2) + pConn->SetBroadcastTarget(true); + pConn2->DelRef(); + } + } + // send + bool fSuccess = Broadcast(rPkt); + // end broadcast + EndBroadcast(); + // return + return fSuccess; +} + +bool C4Network2IO::Punch(C4NetIO::addr_t nPuncherAddr) +{ + // UDP must be initialized + if (!pNetIO_UDP) + return false; + // save address + PuncherAddr = nPuncherAddr; + // let's punch + return pNetIO_UDP->Connect(PuncherAddr); +} + +// C4NetIO interface +bool C4Network2IO::OnConn(const C4NetIO::addr_t &PeerAddr, const C4NetIO::addr_t &ConnectAddr, const C4NetIO::addr_t *pOwnAddr, C4NetIO *pNetIO) +{ + // puncher answer? We just make sure here a connection /can/ be established, so close it instantly. + if (pNetIO == pNetIO_UDP) + if (PuncherAddr.sin_addr.s_addr && AddrEqual(PuncherAddr, ConnectAddr)) + { + // got an address? + if (pOwnAddr) + OnPunch(*pOwnAddr); + // this is only a test connection - close it instantly + return false; + } +#if(C4NET2IO_DUMP_LEVEL > 1) + time_t tTime = GetTime(); + ThreadLogS("OnConn: %d:%02d:%02d:%03d: %s", + (tTime / 1000 / 60 / 60), (tTime / 1000 / 60) % 60, (tTime / 1000) % 60, tTime % 1000, + getNetIOName(pNetIO)); +#endif + // search connection + C4Network2IOConnection *pConn = NULL; + if (ConnectAddr.sin_addr.s_addr) + pConn = GetConnectionByConnAddr(ConnectAddr, pNetIO); + // not found? + if (!pConn) + { + // allow connect? + if (!fAllowConnect) return false; + // create new connection object + uint32_t iConnID = iNextConnID++; + pConn = new C4Network2IOConnection(); + pConn->Set(pNetIO, getNetIOProt(pNetIO), PeerAddr, ConnectAddr, CS_Connected, NULL, iConnID); + // add to list + AddConnection(pConn); + } + else + { + // already closed this connection (attempt)? + if (pConn->isClosed()) + return false; + if (!pConn->isOpen()) + { + // change status + pConn->SetStatus(CS_Connected); + pConn->SetPeerAddr(PeerAddr); + } + } + // send welcome packet, if appropriate + SendConnPackets(); +#if(C4NET2IO_DUMP_LEVEL > 0) + // log + Application.InteractiveThread.ThreadLogS("Network: got %s connection from %s:%d", getNetIOName(pNetIO), inet_ntoa(PeerAddr.sin_addr), htons(PeerAddr.sin_port)); +#endif + // do event (disabled - unused) + // pConn->AddRef(); PushNetEv(NE_Conn, pConn); + // ok + return true; +} + +void C4Network2IO::OnDisconn(const C4NetIO::addr_t &addr, C4NetIO *pNetIO, const char *szReason) +{ + // punch? + if (pNetIO == pNetIO_UDP) + if (PuncherAddr.sin_addr.s_addr && AddrEqual(PuncherAddr, addr)) + { + ZeroMem(&PuncherAddr, sizeof(PuncherAddr)); + return; + } +#if(C4NET2IO_DUMP_LEVEL > 1) + time_t tTime = GetTime(); + ThreadLogS("OnDisconn: %d:%02d:%02d:%03d: %s", + (tTime / 1000 / 60 / 60), (tTime / 1000 / 60) % 60, (tTime / 1000) % 60, tTime % 1000, + getNetIOName(pNetIO)); +#endif + // find connection + C4Network2IOConnection *pConn = GetConnection(addr, pNetIO); + if (!pConn) pConn = GetConnectionByConnAddr(addr, pNetIO); + if (!pConn) return; +#if(C4NET2IO_DUMP_LEVEL > 0) + // log + Application.InteractiveThread.ThreadLogS("Network: %s connection to %s:%d %s (%s)", + getNetIOName(pNetIO), inet_ntoa(addr.sin_addr), htons(addr.sin_port), pConn->isConnecting() ? "failed" : "closed" , szReason); +#endif + // already closed? ignore + if (!pConn->isClosed()) + // not accepted yet? count as connection failure + pConn->SetStatus(pConn->isHalfAccepted() ? CS_Closed : CS_ConnectFail); + // keep connection for main thread message + pConn->AddRef(); + // check for pending welcome packets + SendConnPackets(); + // signal to main thread + Application.InteractiveThread.PushEvent(Ev_Net_Disconn, pConn); + // don't remove connection from list - wait for postmortem or timeout +} + +void C4Network2IO::OnPacket(const class C4NetIOPacket &rPacket, C4NetIO *pNetIO) +{ +#if(C4NET2IO_DUMP_LEVEL > 1) + time_t tTime = GetTime(); + ThreadLogS("OnPacket: %d:%02d:%02d:%03d: status %02x %s", + (tTime / 1000 / 60 / 60), (tTime / 1000 / 60) % 60, (tTime / 1000) % 60, tTime % 1000, + rPacket.getStatus(), getNetIOName(pNetIO)); +#endif + if (!rPacket.getSize()) return; + // find connection + C4Network2IOConnection *pConn = GetConnection(rPacket.getAddr(), pNetIO); + if (!pConn) { Application.InteractiveThread.ThreadLog("Network: could not find connection for packet from %s:%d!", inet_ntoa(rPacket.getAddr().sin_addr), htons(rPacket.getAddr().sin_port)); return; } +#if(C4NET2IO_DUMP_LEVEL > 2) + if (GetTime() - tTime > 100) + ThreadLogS("OnPacket: ... blocked %d ms for finding the connection!", GetTime() - tTime); +#endif + // notify + pConn->OnPacketReceived(rPacket.getStatus()); + // handle packet + HandlePacket(rPacket, pConn, true); + // log time +#if(C4NET2IO_DUMP_LEVEL > 1) + if (GetTime() - tTime > 100) + ThreadLogS("OnPacket: ... blocked %d ms for handling!", GetTime() - tTime); +#endif +} + +void C4Network2IO::OnError(const char *strError, C4NetIO *pNetIO) +{ + // let's log it + Application.InteractiveThread.ThreadLog("Network: %s error: %s", getNetIOName(pNetIO), strError); +} + +bool C4Network2IO::Execute(int iTimeout, pollfd *) +{ + tLastExecute = GetTime(); + + // check for timeout + CheckTimeout(); + + // ping all open connections + if (!Inside(tLastPing, GetTime() - C4NetPingFreq, GetTime())) + { + Ping(); + tLastPing = tLastExecute; + } + + // do statistics + if (!Inside(tLastStatistic, GetTime() - C4NetStatisticsFreq, GetTime())) + { + GenerateStatistics(tLastExecute - tLastStatistic); + tLastStatistic = tLastExecute; + } + + // resources + ::Network.ResList.OnTimer(); + + // ok + return true; +} + +time_t C4Network2IO::GetNextTick(time_t tNow) +{ + return tLastExecute + C4NetTimer; +} + +void C4Network2IO::OnThreadEvent(C4InteractiveEventType eEvent, void *pEventData) // by main thread +{ + switch (eEvent) + { + case Ev_Net_Conn: // got a connection + { + C4Network2IOConnection *pConn = reinterpret_cast(pEventData); + // do callback + ::Network.OnConn(pConn); + // remove reference + pConn->DelRef(); + } + break; + + case Ev_Net_Disconn: // connection closed + { + C4Network2IOConnection *pConn = reinterpret_cast(pEventData); + assert(pConn->isClosed()); + // do callback + ::Network.OnDisconn(pConn); + // remove reference + pConn->DelRef(); + } + break; + + case Ev_Net_Packet: // got packet + { + NetEvPacketData *pData = reinterpret_cast(pEventData); + // handle + HandlePacket(pData->Packet, pData->Conn, false); + // clear up + pData->Conn->DelRef(); + delete pData; + } + break; + + default: + // TODO + break; + } +} + +C4NetIO *C4Network2IO::getNetIO(C4Network2IOProtocol eProt) // by both +{ + switch (eProt) + { + case P_UDP: return pNetIO_UDP; + case P_TCP: return pNetIO_TCP; + default: return NULL; + } +} + +const char *C4Network2IO::getNetIOName(C4NetIO *pNetIO) +{ + if (!pNetIO) return "NULL"; + if (pNetIO == pNetIO_TCP) return "TCP"; + if (pNetIO == pNetIO_UDP) return "UDP"; + return "UNKNOWN"; +} + +C4Network2IOProtocol C4Network2IO::getNetIOProt(C4NetIO *pNetIO) +{ + if (!pNetIO) return P_NONE; + if (pNetIO == pNetIO_TCP) return P_TCP; + if (pNetIO == pNetIO_UDP) return P_UDP; + return P_NONE; +} + +void C4Network2IO::AddConnection(C4Network2IOConnection *pConn) // by both +{ + CStdLock ConnListLock(&ConnListCSec); + // add reference + pConn->AddRef(); + // add to list + pConn->pNext = pConnList; pConnList = pConn; +} + +void C4Network2IO::RemoveConnection(C4Network2IOConnection *pConn) // by both +{ + CStdLock ConnListLock(&ConnListCSec); + // search & remove + if (pConnList == pConn) + pConnList = pConn->pNext; + else + { + C4Network2IOConnection *pAct; + for (pAct = pConnList; pAct; pAct = pAct->pNext) + if (pAct->pNext == pConn) + break; + if (pAct) + pAct->pNext = pConn->pNext; + else + return; + } + // remove reference + pConn->pNext = NULL; pConn->DelRef(); +} + +C4Network2IOConnection *C4Network2IO::GetConnection(const C4NetIO::addr_t &addr, C4NetIO *pNetIO) // by both +{ + CStdLock ConnListLock(&ConnListCSec); + // search + for (C4Network2IOConnection *pConn = pConnList; pConn; pConn = pConn->pNext) + if (pConn->getNetClass() == pNetIO && AddrEqual(pConn->getPeerAddr(), addr)) + return pConn; + return NULL; +} + +C4Network2IOConnection *C4Network2IO::GetConnectionByConnAddr(const C4NetIO::addr_t &addr, C4NetIO *pNetIO) // by both +{ + CStdLock ConnListLock(&ConnListCSec); + // search + for (C4Network2IOConnection *pConn = pConnList; pConn; pConn = pConn->pNext) + if (pConn->getNetClass() == pNetIO && AddrEqual(pConn->getConnectAddr(), addr)) + return pConn; + return NULL; +} + +C4Network2IOConnection *C4Network2IO::GetConnectionByID(uint32_t iConnID) // by thread +{ + CStdLock ConnListLock(&ConnListCSec); + // search + for (C4Network2IOConnection *pConn = pConnList; pConn; pConn = pConn->pNext) + if (pConn->getID() == iConnID) + return pConn; + return NULL; +} + +void C4Network2IO::SetReference(C4Network2Reference *pReference) +{ + if (pRefServer) + pRefServer->SetReference(pReference); + else + delete pReference; +} + +bool C4Network2IO::IsReferenceNeeded() +{ + return !!pRefServer; +} + +bool C4Network2IO::doAutoAccept(const C4ClientCore &CCore, const C4Network2IOConnection &Conn) +{ + CStdLock AALock(&AutoAcceptCSec); + // check if connection with the given client should be allowed + for (AutoAccept *pAcc = pAutoAcceptList; pAcc; pAcc = pAcc->Next) + // core match? + if (CCore.getDiffLevel(pAcc->CCore) <= C4ClientCoreDL_IDMatch) + { + // check: already got another connection for this client? Peer IP must match, then. + for (C4Network2IOConnection *pConn = pConnList; pConn; pConn = pConn->pNext) + if (pConn->isAccepted() && + pConn->getCCore().getDiffLevel(CCore) <= C4ClientCoreDL_IDMatch && + pConn->getPeerAddr().sin_addr.s_addr != Conn.getPeerAddr().sin_addr.s_addr) + return false; + // not found or IP matches? Let pass + return true; + } + return false; +} + +bool C4Network2IO::HandlePacket(const C4NetIOPacket &rPacket, C4Network2IOConnection *pConn, bool fThread) +{ + // security: add connection reference + if (!pConn) return false; pConn->AddRef(); + + // accept only PID_Conn and PID_Ping on non-accepted connections + if(!pConn->isHalfAccepted()) + if(rPacket.getStatus() != PID_Conn && rPacket.getStatus() != PID_Ping && rPacket.getStatus() != PID_ConnRe) + return false; + + // unpack packet (yet another no-idea-why-it's-needed-cast) + C4IDPacket Pkt; C4PacketBase &PktB = Pkt; + try + { + PktB.unpack(rPacket); + } + catch (StdCompiler::Exception *pExc) + { + Application.InteractiveThread.ThreadLog("Network: error: Failed to unpack packet id %02x: %s", rPacket.getStatus(), pExc->Msg.getData()); + delete pExc; +#ifndef _DEBUG + pConn->Close(); +#endif + return false; + } + + // dump packet (network thread only) +#if(C4NET2IO_DUMP_LEVEL > 0) + if (fThread && Pkt.getPktType() != PID_Ping && Pkt.getPktType() != PID_Pong && Pkt.getPktType() != PID_NetResData) + { + time_t tTime = GetTime(); + // StdStrBuf PacketDump = DecompileToBuf(mkNamingAdaptrPacket); + StdStrBuf PacketHeader = FormatString("HandlePacket: %d:%02d:%02d:%03d by %s:%d (%lu bytes, counter %d)", + (tTime / 1000 / 60 / 60), (tTime / 1000 / 60) % 60, (tTime / 1000) % 60, tTime % 1000, + inet_ntoa(pConn->getPeerAddr().sin_addr), htons(pConn->getPeerAddr().sin_port), + static_cast(rPacket.getSize()), pConn->getInPacketCounter()); + StdStrBuf Dump = DecompileToBuf(mkNamingAdapt(Pkt, PacketHeader.getData())); + // Put it directly. The standard functions behind StdBuf.Format seem to choke when you pass them too much data. + Application.InteractiveThread.PushEvent(Ev_LogSilent, Dump.GrabPointer()); + } +#endif + + // search packet handling data + bool fSendToMainThread = false, fHandled = false; + for (const C4PktHandlingData *pHData = PktHandlingData; pHData->ID != PID_None; pHData++) + if (pHData->ID == rPacket.getStatus()) + { + // correct thread? + if (!pHData->ProcByThread == !fThread) + { + // connection accepted? + if (pHData->AcceptedOnly || pConn->isAccepted() || pConn->isClosed()) + { + fHandled = true; +#if(C4NET2IO_DUMP_LEVEL > 2) + time_t tStart = GetTime(); +#endif + + // call handler(s) + CallHandlers(pHData->HandlerID, &Pkt, pConn, fThread); + +#if(C4NET2IO_DUMP_LEVEL > 2) + if (fThread && GetTime() - tStart > 100) + ThreadLogS("HandlePacket: ... blocked for %d ms!", GetTime() - tStart); +#endif + + } + } + // transfer to main thread? + else if (!pHData->ProcByThread && fThread) + { + fHandled = true; + fSendToMainThread = true; + } + } + + // send to main thread? + if (fSendToMainThread) + { + // create data + NetEvPacketData *pEvData = new NetEvPacketData; + pEvData->Packet.Take(rPacket.Duplicate()); + pEvData->Conn = pConn; pConn->AddRef(); + // trigger event + if (!Application.InteractiveThread.PushEvent(Ev_Net_Packet, pEvData)) + Application.InteractiveThread.ThreadLogS("...push event "); + } + + // unhandled? + if (!fHandled && !pConn->isClosed()) + Application.InteractiveThread.ThreadLog("Network: Unhandled packet (status %02x)", rPacket.getStatus()); + + // remove connection reference + pConn->DelRef(); + return fHandled; +} + +void C4Network2IO::CallHandlers(int iHandlerID, const C4IDPacket *pPkt, C4Network2IOConnection *pConn, bool fThread) +{ + // emulate old callbacks + char cStatus = pPkt->getPktType(); + const C4PacketBase *pPacket = pPkt->getPkt(); + // this class (network thread) + if (iHandlerID & PH_C4Network2IO) + { + assert(fThread); + HandlePacket(cStatus, pPacket, pConn); + } + // main network class (main thread) + if (iHandlerID & PH_C4Network2) + { + assert(!fThread); + ::Network.HandlePacket(cStatus, pPacket, pConn); + } + // fullscreen lobby + if (iHandlerID & PH_C4GUIMainDlg) + { + assert(!fThread); + ::Network.HandleLobbyPacket(cStatus, pPacket, pConn); + } + // client list class (main thread) + if (iHandlerID & PH_C4Network2ClientList) + { + assert(!fThread); + ::Network.Clients.HandlePacket(cStatus, pPacket, pConn); + } + // player list class (main thread) + if (iHandlerID & PH_C4Network2Players) + { + assert(!fThread); + ::Network.Players.HandlePacket(cStatus, pPacket, pConn); + } + // resource list class (network thread) + if (iHandlerID & PH_C4Network2ResList) + { + assert(fThread); + ::Network.ResList.HandlePacket(cStatus, pPacket, pConn); + } + // network control (mixed) + if (iHandlerID & PH_C4GameControlNetwork) + { + ::Control.Network.HandlePacket(cStatus, pPacket, pConn); + } +} + +void C4Network2IO::HandlePacket(char cStatus, const C4PacketBase *pPacket, C4Network2IOConnection *pConn) +{ + // security + if (!pConn) return; + +#define GETPKT(type, name) \ + assert(pPacket); const type &name = \ + /*dynamic_cast*/ static_cast(*pPacket); + + switch (cStatus) + { + + case PID_Conn: // connection request + { + if (!pConn->isOpen()) break; + // get packet + GETPKT(C4PacketConn, rPkt) + // set connection ID + pConn->SetRemoteID(rPkt.getConnID()); + // check auto-accept + if (doAutoAccept(rPkt.getCCore(), *pConn)) + { + // send answer back + C4PacketConnRe pcr(true, false, "auto accept"); + if (!pConn->Send(MkC4NetIOPacket(PID_ConnRe, pcr))) + pConn->Close(); + // accept + pConn->SetStatus(CS_HalfAccepted); + pConn->SetCCore(rPkt.getCCore()); + pConn->SetAutoAccepted(); + } + // note that this packet will get processed by C4Network2, too (main thread) + } + break; + + case PID_ConnRe: // connection request reply + { + if (!pConn->isOpen()) break; + // conn not sent? That's fishy. + // FIXME: Note this happens if the peer has exclusive connection mode on. + if (!pConn->isConnSent()) + { + pConn->Close(); + break; + } + // get packet + GETPKT(C4PacketConnRe, rPkt) + // auto accept connection + if (rPkt.isOK()) + { + if (pConn->isHalfAccepted() && pConn->isAutoAccepted()) + pConn->SetAccepted(); + } + } + break; + + case PID_Ping: + { + if (!pConn->isOpen()) break; + GETPKT(C4PacketPing, rPkt) + // pong + C4PacketPing PktPong = rPkt; + pConn->Send(MkC4NetIOPacket(PID_Pong, PktPong)); + // remove received packets from log + pConn->ClearPacketLog(rPkt.getPacketCounter()); + } + break; + + case PID_Pong: + { + if (!pConn->isOpen()) break; + GETPKT(C4PacketPing, rPkt); + // save + pConn->SetPingTime(rPkt.getTravelTime()); + } + break; + + case PID_FwdReq: + { + GETPKT(C4PacketFwd, rPkt); + HandleFwdReq(rPkt, pConn); + } + break; + + case PID_Fwd: + { + GETPKT(C4PacketFwd, rPkt); + // only received accidently? + if (!rPkt.DoFwdTo(LCCore.getID())) break; + // handle + C4NetIOPacket Packet(rPkt.getData(), pConn->getPeerAddr()); + HandlePacket(Packet, pConn, true); + } + break; + + case PID_PostMortem: + { + GETPKT(C4PacketPostMortem, rPkt); + // Get connection + C4Network2IOConnection *pConn = GetConnectionByID(rPkt.getConnID()); + if (!pConn) return; + // Handle all packets + uint32_t iCounter; + for (iCounter = pConn->getInPacketCounter(); ; iCounter++) + { + // Get packet + const C4NetIOPacket *pPkt = rPkt.getPacket(iCounter); + if (!pPkt) break; + // Handle it + HandlePacket(*pPkt, pConn, true); + } + // Log + if (iCounter > pConn->getInPacketCounter()) + Application.InteractiveThread.ThreadLogS("Network: Recovered %d packets", iCounter - pConn->getInPacketCounter()); + // Remove the connection from our list + if (!pConn->isClosed()) + pConn->Close(); + RemoveConnection(pConn); + } + break; + + } + +#undef GETPKT +} + +void C4Network2IO::HandleFwdReq(const C4PacketFwd &rFwd, C4Network2IOConnection *pBy) +{ + CStdLock ConnListLock(&ConnListCSec); + // init packet + C4PacketFwd nFwd; + nFwd.SetListType(false); + // find all clients the message should be forwarded to + int iClientID; C4Network2IOConnection *pConn; + for (pConn = pConnList; pConn; pConn = pConn->pNext) + if (pConn->isAccepted()) + if ((iClientID = pConn->getClientID()) >= 0) + if (iClientID != pBy->getClientID()) + if (rFwd.DoFwdTo(iClientID) && !nFwd.DoFwdTo(iClientID)) + nFwd.AddClient(iClientID); + // check count (hardcoded: broadcast for > 2 clients) + if (nFwd.getClientCnt() <= 2) + { + C4NetIOPacket Pkt(rFwd.getData(), C4NetIO::addr_t()); + for (int i = 0; i < nFwd.getClientCnt(); i++) + if ((pConn = GetMsgConnection(nFwd.getClient(i)))) + { + pConn->Send(Pkt); + pConn->DelRef(); + } + } + else + { + // Temporarily unlock connection list for getting broadcast lock + // (might lead to deathlocks otherwise, as the lock is often taken + // in the opposite order) + ConnListLock.Clear(); + + BeginBroadcast(); + nFwd.SetData(rFwd.getData()); + // add all clients + CStdLock ConnListLock(&ConnListCSec); + for (int i = 0; i < nFwd.getClientCnt(); i++) + if ((pConn = GetMsgConnection(nFwd.getClient(i)))) + { + pConn->SetBroadcastTarget(true); + pConn->DelRef(); + } + // broadcast + Broadcast(MkC4NetIOPacket(PID_Fwd, nFwd)); + EndBroadcast(); + } + // doing a callback here; don't lock! + ConnListLock.Clear(); + // forward to self? + if (rFwd.DoFwdTo(LCCore.getID())) + { + C4NetIOPacket Packet(rFwd.getData(), pBy->getPeerAddr()); + HandlePacket(Packet, pBy, true); + } +} + +bool C4Network2IO::Ping() +{ + bool fSuccess = true; + // ping all connections + for (C4Network2IOConnection *pConn = pConnList; pConn; pConn = pConn->pNext) + if (pConn->isOpen()) + { + C4PacketPing Ping(pConn->getInPacketCounter(), pConn->getOutPacketCounter()); + fSuccess &= pConn->Send(MkC4NetIOPacket(PID_Ping, Ping)); + pConn->OnPing(); + } + return fSuccess; +#if 0 + // begin broadcast + BeginBroadcast(true); + // make packet + C4NetIOPacket Pkt = MkC4NetIOPacket(PID_Ping, C4PacketPing()); + // ping everyone + if (pNetIO_TCP) + if (!pNetIO_TCP->Broadcast(Pkt)) + { fSuccess = false; ThreadLog("Network: failed to broadcast TCP ping! (%s)", pNetIO_TCP->GetError()); pNetIO_TCP->ResetError(); } + if (pNetIO_UDP) + if (!pNetIO_UDP->Broadcast(Pkt)) + { fSuccess = false; ThreadLog("Network: failed to broadcast UDP ping! (%s)", pNetIO_TCP->GetError()); pNetIO_TCP->ResetError(); } + // end broadcast + EndBroadcast(); + // notify connections + for (C4Network2IOConnection *pConn = pConnList; pConn; pConn = pConn->pNext) + pConn->OnPing(); + // return + return fSuccess; +#endif +} + +void C4Network2IO::CheckTimeout() +{ + // acquire lock + CStdLock ConnListLock(&ConnListCSec); + // check all connections for timeout (use deletion-safe iteration method just in case) + for (C4Network2IOConnection *pConn = pConnList, *pNext; pConn; pConn = pNext) + { + pNext = pConn->pNext; + // status timeout + if (!pConn->isClosed() && !pConn->isAccepted()) + if (difftime(time(NULL), pConn->getTimestamp()) > C4NetAcceptTimeout) + { + Application.InteractiveThread.ThreadLogS("Network: connection accept timeout to %s:%d", inet_ntoa(pConn->getPeerAddr().sin_addr), htons(pConn->getPeerAddr().sin_port)); + pConn->Close(); + } + // ping timeout + if (pConn->isAccepted()) + if ((pConn->getLag() != -1 ? pConn->getLag() : 1000 * (time(NULL) - pConn->getTimestamp())) + > C4NetPingTimeout) + { + Application.InteractiveThread.ThreadLogS("Network: ping timeout to %s:%d", inet_ntoa(pConn->getPeerAddr().sin_addr), htons(pConn->getPeerAddr().sin_port)); + pConn->Close(); + } + // delayed connection removal + if (pConn->isClosed()) + if (difftime(time(NULL), pConn->getTimestamp()) > C4NetAcceptTimeout) + RemoveConnection(pConn); + } +} + +void C4Network2IO::GenerateStatistics(int iInterval) +{ + int iTCPIRateSum = 0, iTCPORateSum = 0, + iUDPIRateSum = 0, iUDPORateSum = 0; + + // acquire lock, get connection statistics + CStdLock ConnListLock(&ConnListCSec); + for (C4Network2IOConnection *pConn = pConnList; pConn; pConn = pConn->pNext) + if (pConn->isOpen()) + { + bool fTCP = pConn->getNetClass() == pNetIO_TCP; + pConn->DoStatistics(iInterval, fTCP ? &iTCPIRateSum : &iUDPIRateSum, + fTCP ? &iTCPORateSum : &iUDPORateSum); + } + ConnListLock.Clear(); + + // get broadcast statistics + int inTCPBCRate = 0, inUDPBCRate = 0; + if (pNetIO_TCP) pNetIO_TCP->GetStatistic(&inTCPBCRate); + if (pNetIO_UDP) pNetIO_UDP->GetStatistic(&inUDPBCRate); + + // normalize everything + iTCPIRateSum = iTCPIRateSum * 1000 / iInterval; + iTCPORateSum = iTCPORateSum * 1000 / iInterval; + iUDPIRateSum = iUDPIRateSum * 1000 / iInterval; + iUDPORateSum = iUDPORateSum * 1000 / iInterval; + inTCPBCRate = inTCPBCRate * 1000 / iInterval; + inUDPBCRate = inUDPBCRate * 1000 / iInterval; + + // clear + if (pNetIO_TCP) pNetIO_TCP->ClearStatistic(); + if (pNetIO_UDP) pNetIO_UDP->ClearStatistic(); + + // save back + iTCPIRate = iTCPIRateSum; iTCPORate = iTCPORateSum; iTCPBCRate = inTCPBCRate; + iUDPIRate = iUDPIRateSum; iUDPORate = iUDPORateSum; iUDPBCRate = inUDPBCRate; +} + +void C4Network2IO::SendConnPackets() +{ + CStdLock ConnListLock(&ConnListCSec); + + // exlusive conn? + if (fExclusiveConn) + // find a live connection + for (C4Network2IOConnection *pConn = pConnList; pConn; pConn = pConn->pNext) + if (pConn->isAccepted() || (!pConn->isClosed() && pConn->isConnSent())) + // do not sent additional conn packets - no other connection should succeed + return; + + // sent pending welcome packet(s) + for (C4Network2IOConnection *pConn = pConnList; pConn; pConn = pConn->pNext) + if (pConn->isOpen() && !pConn->isConnSent()) + { + // make packet + CStdLock LCCoreLock(&LCCoreCSec); + C4NetIOPacket Pkt = MkC4NetIOPacket(PID_Conn, C4PacketConn(LCCore, pConn->getID(), pConn->getPassword())); + LCCoreLock.Clear(); + // send + if (!pConn->Send(Pkt)) + pConn->Close(); + else + { + // set flag + pConn->SetConnSent(); + // only one conn packet at a time + if (fExclusiveConn) + return; + } + } + +} + +void C4Network2IO::OnPunch(C4NetIO::addr_t addr) +{ + // Sanity check + if (addr.sin_family != AF_INET && addr.sin_family != htons(AF_INET)) + return; + addr.sin_family = AF_INET; + ZeroMem(addr.sin_zero, sizeof(addr.sin_zero)); + // Add for local client + C4Network2Client *pLocal = ::Network.Clients.GetLocal(); + if (pLocal) + if (pLocal->AddAddr(C4Network2Address(addr, P_UDP), true)) + ::Network.InvalidateReference(); +} + +// *** C4Network2IOConnection + +C4Network2IOConnection::C4Network2IOConnection() + : pNetClass(NULL), + iID(~0), iRemoteID(~0), + fAutoAccept(false), + fBroadcastTarget(false), + iTimestamp(0), + iPingTime(-1), + tLastPing(ULONG_MAX), tLastPong(ULONG_MAX), + fConnSent(false), + fPostMortemSent(false), + iOutPacketCounter(0), iInPacketCounter(0), + pPacketLog(NULL), + pNext(NULL), + iRefCnt(0) +{ +} + +C4Network2IOConnection::~C4Network2IOConnection() +{ + assert(!iRefCnt); + // connection needs to be closed? + if (pNetClass && !isClosed()) Close(); + // clear the packet log + ClearPacketLog(); +} + +int C4Network2IOConnection::getLag() const +{ + // Last ping not answered yet? + if (iPingTime != -1 && tLastPing != ULONG_MAX && (tLastPong == ~0u || tLastPing > tLastPong)) + { + int iPingLag = GetTime() - tLastPing; + // Use it for lag measurement once it's larger then the last ping time + // (the ping time won't be better than this anyway once the pong's here) + return Max(iPingLag, iPingTime); + } + // Last ping result + return iPingTime; +} + +void C4Network2IOConnection::Set(C4NetIO *pnNetClass, C4Network2IOProtocol enProt, const C4NetIO::addr_t &nPeerAddr, const C4NetIO::addr_t &nConnectAddr, C4Network2IOConnStatus nStatus, const char *szPassword, uint32_t inID) +{ + // save data + pNetClass = pnNetClass; eProt = enProt; + PeerAddr = nPeerAddr; ConnectAddr = nConnectAddr; + Status = nStatus; + Password = szPassword; + iID = inID; + // initialize + fBroadcastTarget = false; + iTimestamp = time(NULL); iPingTime = -1; +} + +void C4Network2IOConnection::SetRemoteID(uint32_t inRemoteID) +{ + iRemoteID = inRemoteID; +} + +void C4Network2IOConnection::SetPeerAddr(const C4NetIO::addr_t &nPeerAddr) +{ + // just do it + PeerAddr = nPeerAddr; +} + +void C4Network2IOConnection::OnPing() +{ + // Still no pong for the last ping? + if (tLastPong < tLastPing) + return; + // Save time + tLastPing = GetTime(); +} + +void C4Network2IOConnection::SetPingTime(int inPingTime) +{ + // save it + iPingTime = inPingTime; + // pong received - save timestamp + tLastPong = GetTime(); +} + +void C4Network2IOConnection::SetStatus(C4Network2IOConnStatus nStatus) +{ + if (nStatus != Status) + { + // Connection can't return from these + assert(!isClosed()); + // set status + Status = nStatus; + // reset timestamp for connect/accept/close + if (Status == CS_Connect || Status == CS_Connected || Status == CS_Accepted || Status == CS_Closed) + iTimestamp = time(NULL); + } +} + +void C4Network2IOConnection::SetAutoAccepted() +{ + fAutoAccept = true; +} + +void C4Network2IOConnection::OnPacketReceived(uint8_t iPacketType) +{ + // Just count them + if (iPacketType >= PID_PacketLogStart) + iInPacketCounter++; +} + +void C4Network2IOConnection::ClearPacketLog(uint32_t iUntilID) +{ + // Search position of first packet to delete + PacketLogEntry *pPos, *pPrev = NULL; + for (pPos = pPacketLog; pPos; pPrev = pPos, pPos = pPos->Next) + if (pPos->Number < iUntilID) + break; + if (pPos) + { + // Remove packets from list + (pPrev ? pPrev->Next : pPacketLog) = NULL; + // Delete everything + while (pPos) + { + PacketLogEntry *pDelete = pPos; + pPos = pPos->Next; + delete pDelete; + } + } +} + +bool C4Network2IOConnection::CreatePostMortem(C4PacketPostMortem *pPkt) +{ + // Security + if (!pPkt) return false; + CStdLock PacketLogLock(&PacketLogCSec); + // Nothing to do? + if (!pPacketLog) return false; + // Already created? + if (fPostMortemSent) return false; + // Set connection ID and packet counter + pPkt->SetConnID(iRemoteID); + pPkt->SetPacketCounter(iOutPacketCounter); + // Add packets + for (PacketLogEntry *pEntry = pPacketLog; pEntry; pEntry = pEntry->Next) + pPkt->Add(pEntry->Pkt); + // Okay + fPostMortemSent = true; + return true; +} + +void C4Network2IOConnection::SetCCore(const C4ClientCore &nCCore) +{ + CStdLock CCoreLock(&CCoreCSec); + CCore = nCCore; +} + +bool C4Network2IOConnection::Connect() +{ + if (!pNetClass) return false; + // try connect + return pNetClass->Connect(ConnectAddr); +} + +void C4Network2IOConnection::Close() +{ + if (!pNetClass || isClosed()) return; + // set status + SetStatus(CS_Closed); + // close + pNetClass->Close(PeerAddr); +} + +bool C4Network2IOConnection::Send(const C4NetIOPacket &rPkt) +{ + // some packets shouldn't go into the log + if (rPkt.getStatus() < PID_PacketLogStart) + { + assert(isOpen()); + C4NetIOPacket Copy(rPkt); + Copy.SetAddr(PeerAddr); + return pNetClass->Send(Copy); + } + CStdLock PacketLogLock(&PacketLogCSec); + // create log entry + PacketLogEntry *pLogEntry = new PacketLogEntry(); + pLogEntry->Number = iOutPacketCounter++; + pLogEntry->Pkt = rPkt; + pLogEntry->Next = pPacketLog; + pPacketLog = pLogEntry; + // set address + pLogEntry->Pkt.SetAddr(PeerAddr); + // closed? No sweat, post mortem will reroute it later. + if (!isOpen()) + { + // post mortem already sent? This shouldn't happen + if (fPostMortemSent) { assert(false); return false; } + // okay then + return true; + } + // send + bool fSuccess = pNetClass->Send(pLogEntry->Pkt); + if (fSuccess) + assert(!fPostMortemSent); + else { + // Not being able to send a packet is actually a big deal, + // as this means that we will have hole in the packet + // order. Better close the connection - post mortem should + // ideally sort everything out from here. + LogF("Network: Fatal: Send failed (%s)", pNetClass->GetError()); + pNetClass->ResetError(); + Close(); + } + return fSuccess; +} + +void C4Network2IOConnection::SetBroadcastTarget(bool fSet) +{ + // Note that each thread will have to make sure that this flag won't be + // changed until Broadcast() is called. See C4Network2IO::BroadcastCSec. + pNetClass->SetBroadcast(PeerAddr, fSet); + fBroadcastTarget = fSet; +} + +void C4Network2IOConnection::DoStatistics(int iInterval, int *pIRateSum, int *pORateSum) +{ + // get C4NetIO statistics + int inIRate, inORate, inLoss; + if (!isOpen() || !pNetClass->GetConnStatistic(PeerAddr, &inIRate, &inORate, &inLoss)) + { + iIRate = iORate = iPacketLoss = 0; + return; + } + // normalize + inIRate = inIRate * 1000 / iInterval; + inORate = inORate * 1000 / iInterval; + // set + iIRate = inIRate; iORate = inORate; iPacketLoss = inLoss; + // sum up + if (pIRateSum) *pIRateSum += iIRate; + if (pORateSum) *pORateSum += iORate; +} + +void C4Network2IOConnection::AddRef() +{ + InterlockedIncrement(&iRefCnt); +} + +void C4Network2IOConnection::DelRef() +{ + if (!InterlockedDecrement(&iRefCnt)) + delete this; +} + + +// *** C4PacketPostMortem + +C4PacketPostMortem::C4PacketPostMortem() + : iConnID(~0), + iPacketCounter(~0), + iPacketCount(0), + pPackets(NULL) +{ + +} + +C4PacketPostMortem::~C4PacketPostMortem() +{ + while (pPackets) + { + PacketLink *pDelete = pPackets; + pPackets = pPackets->Next; + delete pDelete; + } + iPacketCount = 0; +} + +const C4NetIOPacket *C4PacketPostMortem::getPacket(uint32_t iNumber) const +{ + // Security + if (!Inside(iNumber, iPacketCounter - iPacketCount, iPacketCounter - 1)) + return NULL; + // Calculate position in list + iNumber = iNumber + iPacketCount - iPacketCounter; + // Search for the packet with the given number + PacketLink *pLink = pPackets; + for (; pLink && iNumber; iNumber--) + pLink = pLink->Next; + // Not found? + return pLink ? &pLink->Pkt : NULL; +} + +void C4PacketPostMortem::SetPacketCounter(uint32_t inPacketCounter) +{ + iPacketCounter = inPacketCounter; +} + +void C4PacketPostMortem::Add(const C4NetIOPacket &rPkt) +{ + // Add to head of list (reverse order) + PacketLink *pLink = new PacketLink(); + pLink->Pkt = rPkt; + pLink->Next = pPackets; + pPackets = pLink; + iPacketCount++; +} + +void C4PacketPostMortem::CompileFunc(StdCompiler *pComp) +{ + bool fCompiler = pComp->isCompiler(); + + // Connection ID, packet number and packet count + pComp->Value(mkNamingAdapt(iConnID, "ConnID")); + pComp->Value(mkNamingAdapt(iPacketCounter, "PacketCounter")); + pComp->Value(mkNamingAdapt(iPacketCount, "PacketCount")); + + // Packets + if (fCompiler) + { + // Read packets + for (uint32_t i = 0; i < iPacketCount; i++) + { + // Create list entry + PacketLink *pLink = new PacketLink(); + pLink->Next = pPackets; + pPackets = pLink; + // Compile data + pComp->Value(mkNamingAdapt(pLink->Pkt, "PacketData")); + } + // Reverse order + PacketLink *pPackets2 = pPackets; + pPackets = NULL; + while (pPackets2) + { + // Get link + PacketLink *pLink = pPackets2; + pPackets2 = pLink->Next; + // Readd to list + pLink->Next = pPackets; + pPackets = pLink; + } + } + else + { + // Write packets + for (PacketLink *pLink = pPackets; pLink; pLink = pLink->Next) + pComp->Value(mkNamingAdapt(pLink->Pkt, "PacketData")); + } +} diff --git a/src/network/C4Network2IO.h b/src/network/C4Network2IO.h index 5b308db9d..efaf199bc 100644 --- a/src/network/C4Network2IO.h +++ b/src/network/C4Network2IO.h @@ -1,441 +1,443 @@ -/* - * OpenClonk, http://www.openclonk.org - * - * Copyright (c) 2004-2008 Peter Wortmann - * Copyright (c) 2001-2009, RedWolf Design GmbH, http://www.clonk.de - * - * Portions might be copyrighted by other authors who have contributed - * to OpenClonk. - * - * Permission to use, copy, modify, and/or distribute this software for any - * purpose with or without fee is hereby granted, provided that the above - * copyright notice and this permission notice appear in all copies. - * See isc_license.txt for full license and disclaimer. - * - * "Clonk" is a registered trademark of Matthes Bender. - * See clonk_trademark_license.txt for full license. - */ -#ifndef INC_C4Network2IO -#define INC_C4Network2IO - -#include "C4NetIO.h" -#include "C4Client.h" -#include "C4InteractiveThread.h" - -class C4Network2IOConnection; - -// enums & constants -enum C4Network2IOProtocol -{ - P_UDP, P_TCP, P_NONE = -1 -}; - -const int C4NetTimer = 500, // ms - C4NetPingFreq = 1000, // ms - C4NetStatisticsFreq = 1000, // ms - C4NetAcceptTimeout = 10, // s - C4NetPingTimeout = 30000;// ms - -// client count -const int C4NetMaxClients = 256; - -class C4Network2IO - : protected C4InteractiveThread::Callback, - protected C4NetIO::CBClass, - protected StdSchedulerProc -{ -public: - C4Network2IO(); - virtual ~C4Network2IO(); - -protected: - - // main traffic net i/o classes - C4NetIO *pNetIO_TCP, *pNetIO_UDP; - - // discovery net i/o - class C4Network2IODiscover *pNetIODiscover; - - // reference server - class C4Network2RefServer *pRefServer; - - // UPnP port mapping manager - class C4Network2UPnP *UPnPMgr; - - // local client core - C4ClientCore LCCore; - CStdCSec LCCoreCSec; - - // connection list - C4Network2IOConnection *pConnList; - CStdCSec ConnListCSec, BroadcastCSec; - - // next connection ID to use - uint32_t iNextConnID; - - // allow incoming connections? - bool fAllowConnect; - - // connection acceptance - struct AutoAccept - { - C4ClientCore CCore; - AutoAccept *Next; - } - *pAutoAcceptList; - CStdCSec AutoAcceptCSec; - - // make sure only one connection is established? - bool fExclusiveConn; - - // timer & ping - unsigned long iLastExecute, iLastPing; - - // statistics - unsigned long iLastStatistic; - int iTCPIRate, iTCPORate, iTCPBCRate, - iUDPIRate, iUDPORate, iUDPBCRate; - - // punching - C4NetIO::addr_t PuncherAddr; - -public: - - bool hasTCP() const { return !! pNetIO_TCP; } - bool hasUDP() const { return !! pNetIO_UDP; } - - // initialization - bool Init(int16_t iPortTCP, int16_t iPortUDP, int16_t iPortDiscovery = -1, int16_t iPortRefServer = -1, bool fBroadcast = false); // by main thread - void Clear(); // by main thread - void SetLocalCCore(const C4ClientCore &CCore); // by main thread - - // i/o types - C4NetIO *MsgIO(); // by both - C4NetIO *DataIO(); // by both - - // connections - bool Connect(const C4NetIO::addr_t &addr, C4Network2IOProtocol eProt, const C4ClientCore &nCCore, const char *szPassword = NULL); // by main thread - void SetAcceptMode(bool fAcceptAll); // by main thread - void SetExclusiveConnMode(bool fExclusiveConn); // by main thread - int getConnectionCount(); // by main thread - - void ClearAutoAccept(); // by main thread - void AddAutoAccept(const C4ClientCore &CCore); // by main thread - void RemoveAutoAccept(const C4ClientCore &CCore); // by main thread - - C4Network2IOConnection *GetMsgConnection(int iClientID); // by both (returns referenced connection!) - C4Network2IOConnection *GetDataConnection(int iClientID); // by both (returns referenced connection!) - - // broadcasting - void BeginBroadcast(bool fSelectAll = false); // by both - void EndBroadcast(); // by both - bool Broadcast(const C4NetIOPacket &rPkt); // by both - - // sending helpers - bool SendMsgToClient(C4NetIOPacket &rPkt, int iClient); // by both - bool BroadcastMsg(const C4NetIOPacket &rPkt); // by both - - // punch - bool Punch(C4NetIO::addr_t PuncherAddr); // by main thread - - // stuff - C4NetIO *getNetIO(C4Network2IOProtocol eProt); // by both - const char *getNetIOName(C4NetIO *pNetIO); - C4Network2IOProtocol getNetIOProt(C4NetIO *pNetIO); - - // statistics - int getProtIRate(C4Network2IOProtocol eProt) const { return eProt == P_TCP ? iTCPIRate : iUDPIRate; } - int getProtORate(C4Network2IOProtocol eProt) const { return eProt == P_TCP ? iTCPORate : iUDPORate; } - int getProtBCRate(C4Network2IOProtocol eProt) const { return eProt == P_TCP ? iTCPBCRate : iUDPBCRate; } - - // reference - void SetReference(class C4Network2Reference *pReference); - bool IsReferenceNeeded(); - -protected: - // *** callbacks - // C4NetIO-Callbacks - virtual bool OnConn(const C4NetIO::addr_t &addr, const C4NetIO::addr_t &AddrConnect, const C4NetIO::addr_t *pOwnAddr, C4NetIO *pNetIO); - virtual void OnDisconn(const C4NetIO::addr_t &addr, C4NetIO *pNetIO, const char *szReason); - virtual void OnPacket(const C4NetIOPacket &rPacket, C4NetIO *pNetIO); - // C4NetIOMan - virtual void OnError(const char *strError, C4NetIO *pNetIO); - // StdSchedulerProc - virtual bool Execute(int iTimeout, pollfd *); - virtual int GetNextTick(int Now); - // Event callback by C4InteractiveThread - void OnThreadEvent(C4InteractiveEventType eEvent, void *pEventData); // by main thread - - // connections list - void AddConnection(C4Network2IOConnection *pConn); // by both - void RemoveConnection(C4Network2IOConnection *pConn); // by both - C4Network2IOConnection *GetConnection(const C4NetIO::addr_t &addr, C4NetIO *pNetIO); // by both - C4Network2IOConnection *GetConnectionByConnAddr(const C4NetIO::addr_t &addr, C4NetIO *pNetIO); // by both - C4Network2IOConnection *GetConnectionByID(uint32_t iConnID); // by thread - - // network events (signals to main thread) - struct NetEvPacketData; - - // connection acceptance - bool doAutoAccept(const C4ClientCore &CCore, const C4Network2IOConnection &Conn); - - // general packet handling (= forward in most cases) - bool HandlePacket(const C4NetIOPacket &rPacket, C4Network2IOConnection *pConn, bool fThread); // by both - void CallHandlers(int iHandlers, const class C4IDPacket *pPacket, C4Network2IOConnection *pConn, bool fThread); // by both - - // packet handling (some are really handled here) - void HandlePacket(char cStatus, const C4PacketBase *pPacket, C4Network2IOConnection *pConn); - void HandleFwdReq(const class C4PacketFwd &rFwd, C4Network2IOConnection *pBy); - - // misc - bool Ping(); - void CheckTimeout(); - void GenerateStatistics(int iInterval); - void SendConnPackets(); - - // puncher - void OnPunch(C4NetIO::addr_t addr); - -}; - -enum C4Network2IOConnStatus -{ - CS_Connect, // waiting for connection - CS_Connected, // waiting for Conn - CS_HalfAccepted, // got Conn (peer identified, client class created if neccessary) - CS_Accepted, // got ConnRe (peer did accept) - CS_Closed, - CS_ConnectFail // got closed before HalfAccepted was reached -}; - -class C4Network2IOConnection // shared -{ - friend class C4Network2IO; -public: - C4Network2IOConnection(); - ~C4Network2IOConnection(); - -protected: - - // connection data - class C4NetIO *pNetClass; - C4Network2IOProtocol eProt; - C4NetIO::addr_t PeerAddr, ConnectAddr; - - // status data - C4Network2IOConnStatus Status; - uint32_t iID, iRemoteID; // connection ID for this and the remote client - bool fAutoAccept; // auto accepted by thread? - bool fBroadcastTarget; // broadcast target? - time_t iTimestamp; // timestamp of last status change - int iPingTime; // ping - unsigned long iLastPing; // if > iLastPong, it's the first ping that hasn't been answered yet - unsigned long iLastPong; // last pong received - C4ClientCore CCore; // client core (>= CS_HalfAccepted) - CStdCSec CCoreCSec; - int iIRate, iORate; // input/output rates (by C4NetIO, in b/s) - int iPacketLoss; // lost packets (in the last seconds) - StdCopyStrBuf Password; // password to use for connect - bool fConnSent; // initial connection packet send - bool fPostMortemSent; // post mortem send - - // packet backlog - uint32_t iOutPacketCounter, iInPacketCounter; - struct PacketLogEntry - { - uint32_t Number; - C4NetIOPacket Pkt; - PacketLogEntry *Next; - }; - PacketLogEntry *pPacketLog; - CStdCSec PacketLogCSec; - - // list (C4Network2IO) - C4Network2IOConnection *pNext; - - // reference counter - long iRefCnt; - -public: - C4NetIO *getNetClass() const { return pNetClass; } - C4Network2IOProtocol getProtocol() const { return eProt; } - const C4NetIO::addr_t &getPeerAddr() const { return PeerAddr.sin_port ? PeerAddr : ConnectAddr; } - const C4NetIO::addr_t &getConnectAddr() const { return ConnectAddr; } - uint32_t getID() const { return iID; } - uint32_t getRemoteID() const { return iRemoteID; } - time_t getTimestamp() const { return iTimestamp; } - const C4ClientCore &getCCore() const { return CCore; } - CStdCSec &getCCoreCSec() { return CCoreCSec; } - int getClientID() const { return CCore.getID(); } - bool isHost() const { return CCore.isHost(); } - int getPingTime() const { return iPingTime; } - int getLag() const; - int getIRate() const { return iIRate; } - int getORate() const { return iORate; } - int getPacketLoss() const { return iPacketLoss; } - const char *getPassword() const { return Password.getData(); } - bool isConnSent() const { return fConnSent; } - - uint32_t getInPacketCounter() const { return iInPacketCounter; } - uint32_t getOutPacketCounter() const { return iOutPacketCounter; } - - bool isConnecting() const { return Status == CS_Connect; } - bool isOpen() const { return Status != CS_Connect && Status != CS_Closed && Status != CS_ConnectFail; } - bool isHalfAccepted()const { return Status == CS_HalfAccepted || Status == CS_Accepted; } - bool isAccepted() const { return Status == CS_Accepted; } - bool isClosed() const { return Status == CS_Closed || Status == CS_ConnectFail; } - bool isAutoAccepted()const { return fAutoAccept; } - bool isBroadcastTarget() const { return fBroadcastTarget; } - bool isFailed() const { return Status == CS_ConnectFail; } - -protected: - // called by C4Network2IO only - void Set(C4NetIO *pnNetClass, C4Network2IOProtocol eProt, const C4NetIO::addr_t &nPeerAddr, const C4NetIO::addr_t &nConnectAddr, C4Network2IOConnStatus nStatus, const char *szPassword, uint32_t iID); - void SetRemoteID(uint32_t iRemoteID); - void SetPeerAddr(const C4NetIO::addr_t &nPeerAddr); - void OnPing(); - void SetPingTime(int iPingTime); - void SetStatus(C4Network2IOConnStatus nStatus); - void SetAutoAccepted(); - void OnPacketReceived(uint8_t iPacketType); - void ClearPacketLog(uint32_t iStartNumber = ~0); - -public: - // status changing - void SetHalfAccepted() { SetStatus(CS_HalfAccepted); } - void SetAccepted() { SetStatus(CS_Accepted); } - void SetCCore(const C4ClientCore &nCCore); - void ResetAutoAccepted() { fAutoAccept = false; } - void SetConnSent() { fConnSent = true; } - - // connection operations - bool Connect(); - void Close(); - bool Send(const C4NetIOPacket &rPkt); - void SetBroadcastTarget(bool fSet); // (only call after C4Network2IO::BeginBroadcast!) - - // statistics - void DoStatistics(int iInterval, int *pIRateSum, int *pORateSum); - - // reference counting - void AddRef(); void DelRef(); - - // post mortem - bool CreatePostMortem(class C4PacketPostMortem *pPkt); - -}; - -// * Packets * - -class C4PacketPing : public C4PacketBase -{ -public: - C4PacketPing(uint32_t iPacketCounter = 0, uint32_t iRemotePacketCounter = 0); - -protected: - uint32_t iTime; - uint32_t iPacketCounter; - -public: - uint32_t getTravelTime() const; - uint32_t getPacketCounter() const { return iPacketCounter; } - - virtual void CompileFunc(StdCompiler *pComp); -}; - -class C4PacketConn : public C4PacketBase -{ -public: - C4PacketConn(); - C4PacketConn(const class C4ClientCore &nCCore, uint32_t iConnID, const char *szPassword = NULL); - -protected: - int32_t iVer; - uint32_t iConnID; - C4ClientCore CCore; - StdCopyStrBuf Password; - -public: - int32_t getVer() const { return iVer; } - uint32_t getConnID() const { return iConnID; } - const C4ClientCore &getCCore() const { return CCore; } - const char *getPassword() const { return Password.getData(); } - - virtual void CompileFunc(StdCompiler *pComp); -}; - -class C4PacketConnRe : public C4PacketBase -{ -public: - C4PacketConnRe(); - C4PacketConnRe(bool fOK, bool fWrongPassword, const char *szMsg = NULL); - -protected: - bool fOK, fWrongPassword; - StdStrBuf szMsg; - -public: - bool isOK() const { return fOK; } - bool isPasswordWrong() const { return fWrongPassword; } - const char *getMsg() const { return szMsg.getData(); } - - virtual void CompileFunc(StdCompiler *pComp); -}; - -class C4PacketFwd : public C4PacketBase -{ -public: - C4PacketFwd(); - C4PacketFwd(const StdBuf &Pkt); - -protected: - bool fNegativeList; - int32_t iClients[C4NetMaxClients]; - int32_t iClientCnt; - StdCopyBuf Data; - -public: - const StdCopyBuf &getData() const { return Data; } - bool isNegativeList() const { return fNegativeList; } - int32_t getClient(int32_t i) const { return iClients[i]; } - int32_t getClientCnt() const { return iClientCnt; } - - bool DoFwdTo(int32_t iClient) const; - - void SetData(const StdBuf &Pkt); - void SetListType(bool fnNegativeList); - void AddClient(int32_t iClient); - - virtual void CompileFunc(StdCompiler *pComp); -}; - -class C4PacketPostMortem : public C4PacketBase -{ -public: - C4PacketPostMortem(); - ~C4PacketPostMortem(); - -private: - uint32_t iConnID; - uint32_t iPacketCounter; // last packet counter of dead connection - uint32_t iPacketCount; - struct PacketLink - { - C4NetIOPacket Pkt; - PacketLink *Next; - }; - PacketLink *pPackets; - -public: - uint32_t getConnID() const { return iConnID; } - uint32_t getPacketCount() const { return iPacketCount; } - void SetConnID(uint32_t inConnID) { iConnID = inConnID; } - - const C4NetIOPacket *getPacket(uint32_t iNumber) const; - void SetPacketCounter(uint32_t iPacketCounter); - void Add(const C4NetIOPacket &rPkt); - - virtual void CompileFunc(StdCompiler *pComp); -}; - -#endif +/* + * OpenClonk, http://www.openclonk.org + * + * Copyright (c) 2004-2008 Peter Wortmann + * Copyright (c) 2001-2009, RedWolf Design GmbH, http://www.clonk.de + * + * Portions might be copyrighted by other authors who have contributed + * to OpenClonk. + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * See isc_license.txt for full license and disclaimer. + * + * "Clonk" is a registered trademark of Matthes Bender. + * See clonk_trademark_license.txt for full license. + */ +#ifndef INC_C4Network2IO +#define INC_C4Network2IO + +#include "C4NetIO.h" +#include "C4Client.h" +#include "C4InteractiveThread.h" + +class C4Network2IOConnection; + +// enums & constants +enum C4Network2IOProtocol +{ + P_UDP, P_TCP, P_NONE = -1 +}; + +const int C4NetTimer = 500, // ms + C4NetPingFreq = 1000, // ms + C4NetStatisticsFreq = 1000, // ms + C4NetAcceptTimeout = 10, // s + C4NetPingTimeout = 30000;// ms + +// client count +const int C4NetMaxClients = 256; + +class C4Network2IO + : protected C4InteractiveThread::Callback, + protected C4NetIO::CBClass, + protected StdSchedulerProc +{ +public: + C4Network2IO(); + virtual ~C4Network2IO(); + +protected: + + // main traffic net i/o classes + C4NetIO *pNetIO_TCP, *pNetIO_UDP; + + // discovery net i/o + class C4Network2IODiscover *pNetIODiscover; + + // reference server + class C4Network2RefServer *pRefServer; + + // UPnP port mapping manager + class C4Network2UPnP *UPnPMgr; + + // local client core + C4ClientCore LCCore; + CStdCSec LCCoreCSec; + + // connection list + C4Network2IOConnection *pConnList; + CStdCSec ConnListCSec, BroadcastCSec; + + // next connection ID to use + uint32_t iNextConnID; + + // allow incoming connections? + bool fAllowConnect; + + // connection acceptance + struct AutoAccept + { + C4ClientCore CCore; + AutoAccept *Next; + } + *pAutoAcceptList; + CStdCSec AutoAcceptCSec; + + // make sure only one connection is established? + bool fExclusiveConn; + + // timer & ping + time_t tLastExecute; + time_t tLastPing; + + // statistics + time_t tLastStatistic; + int iTCPIRate, iTCPORate, iTCPBCRate, + iUDPIRate, iUDPORate, iUDPBCRate; + + // punching + C4NetIO::addr_t PuncherAddr; + +public: + + bool hasTCP() const { return !! pNetIO_TCP; } + bool hasUDP() const { return !! pNetIO_UDP; } + + // initialization + bool Init(int16_t iPortTCP, int16_t iPortUDP, int16_t iPortDiscovery = -1, int16_t iPortRefServer = -1, bool fBroadcast = false); // by main thread + void Clear(); // by main thread + void SetLocalCCore(const C4ClientCore &CCore); // by main thread + + // i/o types + C4NetIO *MsgIO(); // by both + C4NetIO *DataIO(); // by both + + // connections + bool Connect(const C4NetIO::addr_t &addr, C4Network2IOProtocol eProt, const C4ClientCore &nCCore, const char *szPassword = NULL); // by main thread + void SetAcceptMode(bool fAcceptAll); // by main thread + void SetExclusiveConnMode(bool fExclusiveConn); // by main thread + int getConnectionCount(); // by main thread + + void ClearAutoAccept(); // by main thread + void AddAutoAccept(const C4ClientCore &CCore); // by main thread + void RemoveAutoAccept(const C4ClientCore &CCore); // by main thread + + C4Network2IOConnection *GetMsgConnection(int iClientID); // by both (returns referenced connection!) + C4Network2IOConnection *GetDataConnection(int iClientID); // by both (returns referenced connection!) + + // broadcasting + void BeginBroadcast(bool fSelectAll = false); // by both + void EndBroadcast(); // by both + bool Broadcast(const C4NetIOPacket &rPkt); // by both + + // sending helpers + bool SendMsgToClient(C4NetIOPacket &rPkt, int iClient); // by both + bool BroadcastMsg(const C4NetIOPacket &rPkt); // by both + + // punch + bool Punch(C4NetIO::addr_t PuncherAddr); // by main thread + + // stuff + C4NetIO *getNetIO(C4Network2IOProtocol eProt); // by both + const char *getNetIOName(C4NetIO *pNetIO); + C4Network2IOProtocol getNetIOProt(C4NetIO *pNetIO); + + // statistics + int getProtIRate(C4Network2IOProtocol eProt) const { return eProt == P_TCP ? iTCPIRate : iUDPIRate; } + int getProtORate(C4Network2IOProtocol eProt) const { return eProt == P_TCP ? iTCPORate : iUDPORate; } + int getProtBCRate(C4Network2IOProtocol eProt) const { return eProt == P_TCP ? iTCPBCRate : iUDPBCRate; } + + // reference + void SetReference(class C4Network2Reference *pReference); + bool IsReferenceNeeded(); + +protected: + // *** callbacks + // C4NetIO-Callbacks + virtual bool OnConn(const C4NetIO::addr_t &addr, const C4NetIO::addr_t &AddrConnect, const C4NetIO::addr_t *pOwnAddr, C4NetIO *pNetIO); + virtual void OnDisconn(const C4NetIO::addr_t &addr, C4NetIO *pNetIO, const char *szReason); + virtual void OnPacket(const C4NetIOPacket &rPacket, C4NetIO *pNetIO); + // C4NetIOMan + virtual void OnError(const char *strError, C4NetIO *pNetIO); + // StdSchedulerProc + virtual bool Execute(int iTimeout, pollfd *); + virtual time_t GetNextTick(time_t Now); + virtual bool IsScheduledExecution() { return true; } + // Event callback by C4InteractiveThread + void OnThreadEvent(C4InteractiveEventType eEvent, void *pEventData); // by main thread + + // connections list + void AddConnection(C4Network2IOConnection *pConn); // by both + void RemoveConnection(C4Network2IOConnection *pConn); // by both + C4Network2IOConnection *GetConnection(const C4NetIO::addr_t &addr, C4NetIO *pNetIO); // by both + C4Network2IOConnection *GetConnectionByConnAddr(const C4NetIO::addr_t &addr, C4NetIO *pNetIO); // by both + C4Network2IOConnection *GetConnectionByID(uint32_t iConnID); // by thread + + // network events (signals to main thread) + struct NetEvPacketData; + + // connection acceptance + bool doAutoAccept(const C4ClientCore &CCore, const C4Network2IOConnection &Conn); + + // general packet handling (= forward in most cases) + bool HandlePacket(const C4NetIOPacket &rPacket, C4Network2IOConnection *pConn, bool fThread); // by both + void CallHandlers(int iHandlers, const class C4IDPacket *pPacket, C4Network2IOConnection *pConn, bool fThread); // by both + + // packet handling (some are really handled here) + void HandlePacket(char cStatus, const C4PacketBase *pPacket, C4Network2IOConnection *pConn); + void HandleFwdReq(const class C4PacketFwd &rFwd, C4Network2IOConnection *pBy); + + // misc + bool Ping(); + void CheckTimeout(); + void GenerateStatistics(int iInterval); + void SendConnPackets(); + + // puncher + void OnPunch(C4NetIO::addr_t addr); + +}; + +enum C4Network2IOConnStatus +{ + CS_Connect, // waiting for connection + CS_Connected, // waiting for Conn + CS_HalfAccepted, // got Conn (peer identified, client class created if neccessary) + CS_Accepted, // got ConnRe (peer did accept) + CS_Closed, + CS_ConnectFail // got closed before HalfAccepted was reached +}; + +class C4Network2IOConnection // shared +{ + friend class C4Network2IO; +public: + C4Network2IOConnection(); + ~C4Network2IOConnection(); + +protected: + + // connection data + class C4NetIO *pNetClass; + C4Network2IOProtocol eProt; + C4NetIO::addr_t PeerAddr, ConnectAddr; + + // status data + C4Network2IOConnStatus Status; + uint32_t iID, iRemoteID; // connection ID for this and the remote client + bool fAutoAccept; // auto accepted by thread? + bool fBroadcastTarget; // broadcast target? + time_t iTimestamp; // timestamp of last status change + int iPingTime; // ping + time_t tLastPing; // if > iLastPong, it's the first ping that hasn't been answered yet + time_t tLastPong; // last pong received + C4ClientCore CCore; // client core (>= CS_HalfAccepted) + CStdCSec CCoreCSec; + int iIRate, iORate; // input/output rates (by C4NetIO, in b/s) + int iPacketLoss; // lost packets (in the last seconds) + StdCopyStrBuf Password; // password to use for connect + bool fConnSent; // initial connection packet send + bool fPostMortemSent; // post mortem send + + // packet backlog + uint32_t iOutPacketCounter, iInPacketCounter; + struct PacketLogEntry + { + uint32_t Number; + C4NetIOPacket Pkt; + PacketLogEntry *Next; + }; + PacketLogEntry *pPacketLog; + CStdCSec PacketLogCSec; + + // list (C4Network2IO) + C4Network2IOConnection *pNext; + + // reference counter + long iRefCnt; + +public: + C4NetIO *getNetClass() const { return pNetClass; } + C4Network2IOProtocol getProtocol() const { return eProt; } + const C4NetIO::addr_t &getPeerAddr() const { return PeerAddr.sin_port ? PeerAddr : ConnectAddr; } + const C4NetIO::addr_t &getConnectAddr() const { return ConnectAddr; } + uint32_t getID() const { return iID; } + uint32_t getRemoteID() const { return iRemoteID; } + time_t getTimestamp() const { return iTimestamp; } + const C4ClientCore &getCCore() const { return CCore; } + CStdCSec &getCCoreCSec() { return CCoreCSec; } + int getClientID() const { return CCore.getID(); } + bool isHost() const { return CCore.isHost(); } + int getPingTime() const { return iPingTime; } + int getLag() const; + int getIRate() const { return iIRate; } + int getORate() const { return iORate; } + int getPacketLoss() const { return iPacketLoss; } + const char *getPassword() const { return Password.getData(); } + bool isConnSent() const { return fConnSent; } + + uint32_t getInPacketCounter() const { return iInPacketCounter; } + uint32_t getOutPacketCounter() const { return iOutPacketCounter; } + + bool isConnecting() const { return Status == CS_Connect; } + bool isOpen() const { return Status != CS_Connect && Status != CS_Closed && Status != CS_ConnectFail; } + bool isHalfAccepted()const { return Status == CS_HalfAccepted || Status == CS_Accepted; } + bool isAccepted() const { return Status == CS_Accepted; } + bool isClosed() const { return Status == CS_Closed || Status == CS_ConnectFail; } + bool isAutoAccepted()const { return fAutoAccept; } + bool isBroadcastTarget() const { return fBroadcastTarget; } + bool isFailed() const { return Status == CS_ConnectFail; } + +protected: + // called by C4Network2IO only + void Set(C4NetIO *pnNetClass, C4Network2IOProtocol eProt, const C4NetIO::addr_t &nPeerAddr, const C4NetIO::addr_t &nConnectAddr, C4Network2IOConnStatus nStatus, const char *szPassword, uint32_t iID); + void SetRemoteID(uint32_t iRemoteID); + void SetPeerAddr(const C4NetIO::addr_t &nPeerAddr); + void OnPing(); + void SetPingTime(int iPingTime); + void SetStatus(C4Network2IOConnStatus nStatus); + void SetAutoAccepted(); + void OnPacketReceived(uint8_t iPacketType); + void ClearPacketLog(uint32_t iStartNumber = ~0); + +public: + // status changing + void SetHalfAccepted() { SetStatus(CS_HalfAccepted); } + void SetAccepted() { SetStatus(CS_Accepted); } + void SetCCore(const C4ClientCore &nCCore); + void ResetAutoAccepted() { fAutoAccept = false; } + void SetConnSent() { fConnSent = true; } + + // connection operations + bool Connect(); + void Close(); + bool Send(const C4NetIOPacket &rPkt); + void SetBroadcastTarget(bool fSet); // (only call after C4Network2IO::BeginBroadcast!) + + // statistics + void DoStatistics(int iInterval, int *pIRateSum, int *pORateSum); + + // reference counting + void AddRef(); void DelRef(); + + // post mortem + bool CreatePostMortem(class C4PacketPostMortem *pPkt); + +}; + +// * Packets * + +class C4PacketPing : public C4PacketBase +{ +public: + C4PacketPing(uint32_t iPacketCounter = 0, uint32_t iRemotePacketCounter = 0); + +protected: + time_t tTime; + uint32_t iPacketCounter; + +public: + uint32_t getTravelTime() const; + uint32_t getPacketCounter() const { return iPacketCounter; } + + virtual void CompileFunc(StdCompiler *pComp); +}; + +class C4PacketConn : public C4PacketBase +{ +public: + C4PacketConn(); + C4PacketConn(const class C4ClientCore &nCCore, uint32_t iConnID, const char *szPassword = NULL); + +protected: + int32_t iVer; + uint32_t iConnID; + C4ClientCore CCore; + StdCopyStrBuf Password; + +public: + int32_t getVer() const { return iVer; } + uint32_t getConnID() const { return iConnID; } + const C4ClientCore &getCCore() const { return CCore; } + const char *getPassword() const { return Password.getData(); } + + virtual void CompileFunc(StdCompiler *pComp); +}; + +class C4PacketConnRe : public C4PacketBase +{ +public: + C4PacketConnRe(); + C4PacketConnRe(bool fOK, bool fWrongPassword, const char *szMsg = NULL); + +protected: + bool fOK, fWrongPassword; + StdStrBuf szMsg; + +public: + bool isOK() const { return fOK; } + bool isPasswordWrong() const { return fWrongPassword; } + const char *getMsg() const { return szMsg.getData(); } + + virtual void CompileFunc(StdCompiler *pComp); +}; + +class C4PacketFwd : public C4PacketBase +{ +public: + C4PacketFwd(); + C4PacketFwd(const StdBuf &Pkt); + +protected: + bool fNegativeList; + int32_t iClients[C4NetMaxClients]; + int32_t iClientCnt; + StdCopyBuf Data; + +public: + const StdCopyBuf &getData() const { return Data; } + bool isNegativeList() const { return fNegativeList; } + int32_t getClient(int32_t i) const { return iClients[i]; } + int32_t getClientCnt() const { return iClientCnt; } + + bool DoFwdTo(int32_t iClient) const; + + void SetData(const StdBuf &Pkt); + void SetListType(bool fnNegativeList); + void AddClient(int32_t iClient); + + virtual void CompileFunc(StdCompiler *pComp); +}; + +class C4PacketPostMortem : public C4PacketBase +{ +public: + C4PacketPostMortem(); + ~C4PacketPostMortem(); + +private: + uint32_t iConnID; + uint32_t iPacketCounter; // last packet counter of dead connection + uint32_t iPacketCount; + struct PacketLink + { + C4NetIOPacket Pkt; + PacketLink *Next; + }; + PacketLink *pPackets; + +public: + uint32_t getConnID() const { return iConnID; } + uint32_t getPacketCount() const { return iPacketCount; } + void SetConnID(uint32_t inConnID) { iConnID = inConnID; } + + const C4NetIOPacket *getPacket(uint32_t iNumber) const; + void SetPacketCounter(uint32_t iPacketCounter); + void Add(const C4NetIOPacket &rPkt); + + virtual void CompileFunc(StdCompiler *pComp); +}; + +#endif diff --git a/src/network/C4Network2Reference.cpp b/src/network/C4Network2Reference.cpp index cbbbf9fd8..06b56d5ed 100644 --- a/src/network/C4Network2Reference.cpp +++ b/src/network/C4Network2Reference.cpp @@ -1,640 +1,649 @@ -/* - * OpenClonk, http://www.openclonk.org - * - * Copyright (c) 2006-2008 Peter Wortmann - * Copyright (c) 2007-2009 Sven Eberhardt - * Copyright (c) 2008 Matthes Bender - * Copyright (c) 2009 Günther Brammer - * Copyright (c) 2010 Benjamin Herr - * Copyright (c) 2010 Tobias Zwick - * Copyright (c) 2001-2009, RedWolf Design GmbH, http://www.clonk.de - * - * Portions might be copyrighted by other authors who have contributed - * to OpenClonk. - * - * Permission to use, copy, modify, and/or distribute this software for any - * purpose with or without fee is hereby granted, provided that the above - * copyright notice and this permission notice appear in all copies. - * See isc_license.txt for full license and disclaimer. - * - * "Clonk" is a registered trademark of Matthes Bender. - * See clonk_trademark_license.txt for full license. - */ -#include "C4Include.h" -#include "C4Network2Reference.h" - -#include -#include -#include "C4Version.h" - -#include -#include -#include - -// *** C4Network2Reference - -C4Network2Reference::C4Network2Reference() - : Icon(0), Time(0), Frame(0), StartTime(0), LeaguePerformance(0), - JoinAllowed(true), ObservingAllowed(true), PasswordNeeded(false), OfficialServer(false), - iAddrCnt(0) -{ - -} - -C4Network2Reference::~C4Network2Reference() -{ - -} - -void C4Network2Reference::SetSourceIP(in_addr ip) -{ - for (int i = 0; i < iAddrCnt; i++) - if (Addrs[i].isIPNull()) - Addrs[i].SetIP(ip); -} - -void C4Network2Reference::InitLocal() -{ - // Copy all game parameters - Parameters = ::Game.Parameters; - - // Discard player resources (we don't want these infos in the reference) - C4ClientPlayerInfos *pClientInfos; C4PlayerInfo *pPlayerInfo; - int32_t i, j; - for (i = 0; (pClientInfos = Parameters.PlayerInfos.GetIndexedInfo(i)); i++) - for (j = 0; (pPlayerInfo = pClientInfos->GetPlayerInfo(j)); j++) - pPlayerInfo->DiscardResource(); - - // Special additional information in reference - Icon = ::Game.C4S.Head.Icon; - Title.CopyValidated(::Game.ScenarioTitle); - GameStatus = ::Network.Status; - Time = ::Game.Time; - Frame = ::Game.FrameCounter; - StartTime = ::Game.StartTime; - LeaguePerformance = ::Game.RoundResults.GetLeaguePerformance(); - Comment = Config.Network.Comment; - JoinAllowed = ::Network.isJoinAllowed(); - ObservingAllowed = ::Network.isObservingAllowed(); - PasswordNeeded = ::Network.isPassworded(); - Game.Set(); - - // Addresses - C4Network2Client *pLocalNetClient = ::Game.Clients.getLocal()->getNetClient(); - iAddrCnt = pLocalNetClient->getAddrCnt(); - for (i = 0; i < iAddrCnt; i++) - Addrs[i] = pLocalNetClient->getAddr(i); - -} - -void C4Network2Reference::SortNullIPsBack() -{ - // Sort all addresses with zero IP to back of list - int iSortAddrCnt = iAddrCnt; - for (int i = 0; i < iSortAddrCnt; i++) - if (Addrs[i].isIPNull()) - { - C4Network2Address Addr = Addrs[i]; - for (int j = i + 1; j < iAddrCnt; j++) - Addrs[j - 1] = Addrs[j]; - Addrs[iAddrCnt - 1] = Addr; - // Correct position - i--; iSortAddrCnt--; - } -} - -void C4Network2Reference::CompileFunc(StdCompiler *pComp) -{ - pComp->Value(mkNamingAdapt(Icon, "Icon", 0)); - pComp->Value(mkNamingAdapt(Title, "Title", "No title")); - pComp->Value(mkParAdapt(GameStatus, true)); - pComp->Value(mkNamingAdapt(Time, "Time", 0)); - pComp->Value(mkNamingAdapt(Frame, "Frame", 0)); - pComp->Value(mkNamingAdapt(StartTime, "StartTime", 0)); - pComp->Value(mkNamingAdapt(LeaguePerformance, "LeaguePerformance",0)); - pComp->Value(mkNamingAdapt(Comment, "Comment", "")); - pComp->Value(mkNamingAdapt(JoinAllowed, "JoinAllowed", true)); - pComp->Value(mkNamingAdapt(ObservingAllowed, "ObservingAllowed", true)); - pComp->Value(mkNamingAdapt(PasswordNeeded, "PasswordNeeded", false)); - pComp->Value(mkNamingAdapt(mkIntPackAdapt(iAddrCnt), "AddressCount", 0)); - iAddrCnt = Min(C4ClientMaxAddr, iAddrCnt); - pComp->Value(mkNamingAdapt(mkArrayAdapt(Addrs, iAddrCnt, C4Network2Address()), "Address")); - pComp->Value(mkNamingAdapt(Game.sEngineName, "Game", "None")); - pComp->Value(mkNamingAdapt(mkArrayAdaptDM(Game.iVer,0),"Version" )); - pComp->Value(mkNamingAdapt(OfficialServer, "OfficialServer", false)); - - pComp->Value(Parameters); -} - -int32_t C4Network2Reference::getSortOrder() const // Don't go over 100, because that's for the masterserver... -{ - C4GameVersion verThis; - int iOrder = 0; - // Official server - if (isOfficialServer() && !Config.Network.UseAlternateServer) iOrder += 50; - // Joinable - if (isJoinAllowed() && (getGameVersion() == verThis)) iOrder += 25; - // League game - if (Parameters.isLeague()) iOrder += 5; - // In lobby - if (getGameStatus().isLobbyActive()) iOrder += 3; - // No password needed - if (!isPasswordNeeded()) iOrder += 1; - // Done - return iOrder; -} - - -// *** C4Network2RefServer - -C4Network2RefServer::C4Network2RefServer() - : pReference(NULL) -{ -} - -C4Network2RefServer::~C4Network2RefServer() -{ - Clear(); -} - -void C4Network2RefServer::Clear() -{ - C4NetIOTCP::Close(); - delete pReference; pReference = NULL; -} - -void C4Network2RefServer::SetReference(C4Network2Reference *pNewReference) -{ - CStdLock RefLock(&RefCSec); - delete pReference; pReference = pNewReference; -} - -void C4Network2RefServer::PackPacket(const C4NetIOPacket &rPacket, StdBuf &rOutBuf) -{ - // Just append the packet - rOutBuf.Append(rPacket); -} - -size_t C4Network2RefServer::UnpackPacket(const StdBuf &rInBuf, const C4NetIO::addr_t &addr) -{ - const char *pData = getBufPtr(rInBuf); - // Check for complete header - const char *pHeaderEnd = strstr(pData, "\r\n\r\n"); - if (!pHeaderEnd) - return 0; - // Check method (only GET is allowed for now) - if (!SEqual2(pData, "GET ")) - { - RespondNotImplemented(addr, "Method not implemented"); - return rInBuf.getSize(); - } - // Check target - // TODO - RespondReference(addr); - return rInBuf.getSize(); -} - -void C4Network2RefServer::RespondNotImplemented(const C4NetIO::addr_t &addr, const char *szMessage) -{ - // Send the message - StdStrBuf Data = FormatString("HTTP/1.0 501 %s\r\n\r\n", szMessage); - Send(C4NetIOPacket(Data.getData(), Data.getLength(), false, addr)); - // Close the connection - Close(addr); -} - -void C4Network2RefServer::RespondReference(const C4NetIO::addr_t &addr) -{ - CStdLock RefLock(&RefCSec); - // Pack - StdStrBuf PacketData = DecompileToBuf(mkNamingPtrAdapt(pReference, "Reference")); - // Create header - StdStrBuf Header = FormatString( - "HTTP/1.1 200 Found\r\n" - "Content-Length: %lu\r\n" - "Content-Type: text/plain; charset=UTF-8\r\n" - "Server: " C4ENGINENAME "/" C4VERSION "\r\n" - "\r\n", - static_cast(PacketData.getLength())); - // Send back - Send(C4NetIOPacket(Header, Header.getLength(), false, addr)); - Send(C4NetIOPacket(PacketData, PacketData.getLength(), false, addr)); - // Close the connection - Close(addr); -} - -// *** C4Network2HTTPClient - -C4Network2HTTPClient::C4Network2HTTPClient() - : fBinary(false), fBusy(false), fSuccess(false), fConnected(false), iDataOffset(0), iDownloadedSize(0), iTotalSize(0), - pNotify(NULL) -{ - C4NetIOTCP::SetCallback(this); -} - -C4Network2HTTPClient::~C4Network2HTTPClient() -{ -} - -void C4Network2HTTPClient::PackPacket(const C4NetIOPacket &rPacket, StdBuf &rOutBuf) -{ - // Just append the packet - rOutBuf.Append(rPacket); -} - -size_t C4Network2HTTPClient::UnpackPacket(const StdBuf &rInBuf, const C4NetIO::addr_t &addr) -{ - // since new data arrived, increase timeout time - ResetRequestTimeout(); - // Check for complete header - if (!iDataOffset) - { - // Copy data into string buffer (terminate) - StdStrBuf Data; Data.Copy(getBufPtr(rInBuf), rInBuf.getSize()); - const char *pData = Data.getData(); - // Header complete? - const char *pContent = SSearch(pData, "\r\n\r\n"); - if (!pContent) - return 0; - // Read the header - if (!ReadHeader(Data)) - { - fBusy = fSuccess = false; - Close(addr); - return rInBuf.getSize(); - } - } - iDownloadedSize = rInBuf.getSize() - iDataOffset; - // Check if the packet is complete - if (iTotalSize > iDownloadedSize) - { - return 0; - } - // Get data, uncompress it if needed - StdBuf Data = rInBuf.getPart(iDataOffset, iTotalSize); - if (fCompressed) - if (!Decompress(&Data)) - { - fBusy = fSuccess = false; - Close(addr); - return rInBuf.getSize(); - } - // Save the result - if (fBinary) - ResultBin.Copy(Data); - else - ResultString.Copy(getBufPtr(Data), Data.getSize()); - fBusy = false; fSuccess = true; - // Callback - OnPacket(C4NetIOPacket(Data, addr), this); - // Done - Close(addr); - return rInBuf.getSize(); -} - -bool C4Network2HTTPClient::ReadHeader(StdStrBuf Data) -{ - const char *pData = Data.getData(); - const char *pContent = SSearch(pData, "\r\n\r\n"); - if (!pContent) - return 0; - // Parse header line - int iHTTPVer1, iHTTPVer2, iResponseCode, iStatusStringPtr; - if (sscanf(pData, "HTTP/%d.%d %d %n", &iHTTPVer1, &iHTTPVer2, &iResponseCode, &iStatusStringPtr) != 3) - { - Error = "Invalid status line!"; - return false; - } - // Check HTTP version - if (iHTTPVer1 != 1) - { - Error.Format("Unsupported HTTP version: %d.%d!", iHTTPVer1, iHTTPVer2); - return false; - } - // Check code - if (iResponseCode != 200) - { - // Get status string - StdStrBuf StatusString; StatusString.CopyUntil(pData + iStatusStringPtr, '\r'); - // Create error message - Error.Format("HTTP server responded %d: %s", iResponseCode, StatusString.getData()); - return false; - } - // Get content length - const char *pContentLength = SSearch(pData, "\r\nContent-Length:"); - int iContentLength; - if (!pContentLength || pContentLength > pContent || - sscanf(pContentLength, "%d", &iContentLength) != 1) - { - Error.Format("Invalid server response: Content-Length is missing!"); - return false; - } - iTotalSize = iContentLength; - iDataOffset = (pContent - pData); - // Get content encoding - const char *pContentEncoding = SSearch(pData, "\r\nContent-Encoding:"); - if (pContentEncoding) - { - while (*pContentEncoding == ' ') pContentEncoding++; - StdStrBuf Encoding; Encoding.CopyUntil(pContentEncoding, '\r'); - if (Encoding == "gzip") - fCompressed = true; - else - fCompressed = false; - } - else - fCompressed = false; - // Okay - return true; -} - -bool C4Network2HTTPClient::Decompress(StdBuf *pData) -{ - size_t iSize = pData->getSize(); - // Create buffer - uint32_t iOutSize = *getBufPtr(*pData, pData->getSize() - sizeof(uint32_t)); - iOutSize = Min(iOutSize, iSize * 1000); - StdBuf Out; Out.New(iOutSize); - // Prepare stream - z_stream zstrm; - ZeroMem(&zstrm, sizeof(zstrm)); - zstrm.next_in = const_cast(getBufPtr(*pData)); - zstrm.avail_in = pData->getSize(); - zstrm.next_out = getMBufPtr(Out); - zstrm.avail_out = Out.getSize(); - // Inflate... - if (inflateInit2(&zstrm, 15 + 16) != Z_OK) - { - Error.Format("Could not decompress data!"); - return false; - } - // Inflate! - if (inflate(&zstrm, Z_FINISH) != Z_STREAM_END) - { - inflateEnd(&zstrm); - Error.Format("Could not decompress data!"); - return false; - } - // Return the buffer - Out.SetSize(zstrm.total_out); - pData->Take(std::move(Out)); - // Okay - inflateEnd(&zstrm); - return true; -} - -bool C4Network2HTTPClient::OnConn(const C4NetIO::addr_t &AddrPeer, const C4NetIO::addr_t &AddrConnect, const C4NetIO::addr_t *pOwnAddr, C4NetIO *pNetIO) -{ - // Make sure we're actually waiting for this connection - if (!AddrEqual(AddrConnect, ServerAddr)) - return false; - // Save pack peer address - PeerAddr = AddrPeer; - // Send the request - Send(C4NetIOPacket(Request, AddrPeer)); - Request.Clear(); - fConnected = true; - return true; -} - -void C4Network2HTTPClient::OnDisconn(const C4NetIO::addr_t &AddrPeer, C4NetIO *pNetIO, const char *szReason) -{ - // Got no complete packet? Failure... - if (!fSuccess && Error.isNull()) - { - fBusy = false; - Error.Format("Unexpected disconnect: %s", szReason); - } - fConnected = false; - // Notify - if (pNotify) - pNotify->PushEvent(Ev_HTTP_Response, this); -} - -void C4Network2HTTPClient::OnPacket(const class C4NetIOPacket &rPacket, C4NetIO *pNetIO) -{ - // Everything worthwhile was already done in UnpackPacket. Only do notify callback - if (pNotify) - pNotify->PushEvent(Ev_HTTP_Response, this); -} - -bool C4Network2HTTPClient::Execute(int iMaxTime) -{ - // Check timeout - if (fBusy && time(NULL) > iRequestTimeout) - { - Cancel("Request timeout"); - return true; - } - // Execute normally - return C4NetIOTCP::Execute(iMaxTime); -} - -int C4Network2HTTPClient::GetNextTick(int Now) -{ - if (!fBusy) - return C4NetIOTCP::GetNextTick(Now); - return MaxTimeout(C4NetIOTCP::GetNextTick(Now), Now + 1000 * Max(iRequestTimeout - time(NULL), 0)); -} - -bool C4Network2HTTPClient::Query(const StdBuf &Data, bool fBinary) -{ - if (Server.isNull()) return false; - // Cancel previous request - if (fBusy) - Cancel("Cancelled"); - // No result known yet - ResultString.Clear(); - // store mode - this->fBinary = fBinary; - // Create request - StdStrBuf Header; - if (Data.getSize()) - Header.Format( - "POST %s HTTP/1.0\r\n" - "Host: %s\r\n" - "Connection: Close\r\n" - "Content-Length: %lu\r\n" - "Content-Type: text/plain; charset=utf-8\r\n" - "Accept-Charset: utf-8\r\n" - "Accept-Encoding: gzip\r\n" - "Accept-Language: %s\r\n" - "User-Agent: " C4ENGINENAME "/" C4VERSION "\r\n" - "\r\n", - RequestPath.getData(), - Server.getData(), - static_cast(Data.getSize()), - Config.General.LanguageEx); - else - Header.Format( - "GET %s HTTP/1.0\r\n" - "Host: %s\r\n" - "Connection: Close\r\n" - "Accept-Charset: utf-8\r\n" - "Accept-Encoding: gzip\r\n" - "Accept-Language: %s\r\n" - "User-Agent: " C4ENGINENAME "/" C4VERSION "\r\n" - "\r\n", - RequestPath.getData(), - Server.getData(), - Config.General.LanguageEx); - // Compose query - Request.Take(Header.GrabPointer(), Header.getLength()); - Request.Append(Data); - // Start connecting - if (!Connect(ServerAddr)) - return false; - // Okay, request will be performed when connection is complete - fBusy = true; - iDataOffset = 0; - ResetRequestTimeout(); - ResetError(); - return true; -} - -void C4Network2HTTPClient::ResetRequestTimeout() -{ - // timeout C4Network2HTTPQueryTimeout seconds from this point - iRequestTimeout = time(NULL) + C4Network2HTTPQueryTimeout; -} - -void C4Network2HTTPClient::Cancel(const char *szReason) -{ - // Close connection - and connection attempt - Close(ServerAddr); Close(PeerAddr); - // Reset flags - fBusy = fSuccess = fConnected = fBinary = false; - iDownloadedSize = iTotalSize = iDataOffset = 0; - Error = szReason; -} - -void C4Network2HTTPClient::Clear() -{ - fBusy = fSuccess = fConnected = fBinary = false; - iDownloadedSize = iTotalSize = iDataOffset = 0; - ResultBin.Clear(); - ResultString.Clear(); - Error.Clear(); -} - -bool C4Network2HTTPClient::SetServer(const char *szServerAddress) -{ - // Split address - const char *pRequestPath; - if ((pRequestPath = strchr(szServerAddress, '/'))) - { - Server.CopyUntil(szServerAddress, '/'); - RequestPath = pRequestPath; - } - else - { - Server = szServerAddress; - RequestPath = "/"; - } - // Resolve address - if (!ResolveAddress(Server.getData(), &ServerAddr, GetDefaultPort())) - { - SetError(FormatString("Could not resolve server address %s!", Server.getData()).getData()); - return false; - } - // Remove port - const char *pColon = strchr(Server.getData(), ':'); - if (pColon) - Server.SetLength(pColon - Server.getData()); - // Done - ResetError(); - return true; -} - -// *** C4Network2UpdateClient - -bool C4Network2UpdateClient::QueryUpdateURL() -{ - // Perform an Query query - return Query(NULL, false); -} - -bool C4Network2UpdateClient::GetUpdateURL(StdStrBuf *pUpdateURL) -{ - // Sanity check - if (isBusy() || !isSuccess()) return false; - // Parse response - try - { - CompileFromBuf(mkNamingAdapt( - mkNamingAdapt(mkParAdapt(*pUpdateURL, StdCompiler::RCT_All), "UpdateURL", ""), - C4ENGINENAME), ResultString); - } - catch (StdCompiler::Exception *pExc) - { - SetError(pExc->Msg.getData()); - return false; - } - // done; version OK! - return true; -} - -bool C4Network2UpdateClient::GetVersion(StdStrBuf *pVersion) -{ - // Sanity check - if (isBusy() || !isSuccess()) return false; - // Parse response - try - { - CompileFromBuf(mkNamingAdapt( - mkNamingAdapt(mkParAdapt(*pVersion, StdCompiler::RCT_All), "Version", ""), - C4ENGINENAME), ResultString); - } - catch (StdCompiler::Exception *pExc) - { - SetError(pExc->Msg.getData()); - return false; - } - // done; version OK! - return true; -} - -// *** C4Network2RefClient - -bool C4Network2RefClient::QueryReferences() -{ - // Perform an Query query - return Query(NULL, false); -} - -bool C4Network2RefClient::GetReferences(C4Network2Reference **&rpReferences, int32_t &rRefCount) -{ - // Sanity check - if (isBusy() || !isSuccess()) return false; - // local update test - try - { - // Create compiler - StdCompilerINIRead Comp; - Comp.setInput(ResultString); - Comp.Begin(); - // Read reference count - Comp.Value(mkNamingCountAdapt(rRefCount, "Reference")); - // Create reference array and initialize - rpReferences = new C4Network2Reference *[rRefCount]; - for (int i = 0; i < rRefCount; i++) - rpReferences[i] = NULL; - // Get references - Comp.Value(mkNamingAdapt(mkArrayAdaptMap(rpReferences, rRefCount, mkPtrAdaptNoNull), "Reference")); - mkPtrAdaptNoNull(*rpReferences); - // Done - Comp.End(); - } - catch (StdCompiler::Exception *pExc) - { - SetError(pExc->Msg.getData()); - return false; - } - // Set source ip - for (int i = 0; i < rRefCount; i++) - rpReferences[i]->SetSourceIP(getServerAddress().sin_addr); - // Done - ResetError(); - return true; -} - +/* + * OpenClonk, http://www.openclonk.org + * + * Copyright (c) 2006-2008 Peter Wortmann + * Copyright (c) 2007-2009 Sven Eberhardt + * Copyright (c) 2008 Matthes Bender + * Copyright (c) 2009 Günther Brammer + * Copyright (c) 2010 Benjamin Herr + * Copyright (c) 2010 Tobias Zwick + * Copyright (c) 2001-2009, RedWolf Design GmbH, http://www.clonk.de + * + * Portions might be copyrighted by other authors who have contributed + * to OpenClonk. + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * See isc_license.txt for full license and disclaimer. + * + * "Clonk" is a registered trademark of Matthes Bender. + * See clonk_trademark_license.txt for full license. + */ +#include "C4Include.h" +#include "C4Network2Reference.h" + +#include +#include +#include "C4Version.h" + +#include +#include +#include + +// *** C4Network2Reference + +C4Network2Reference::C4Network2Reference() + : Icon(0), Time(0), Frame(0), StartTime(0), LeaguePerformance(0), + JoinAllowed(true), ObservingAllowed(true), PasswordNeeded(false), OfficialServer(false), + iAddrCnt(0) +{ + +} + +C4Network2Reference::~C4Network2Reference() +{ + +} + +void C4Network2Reference::SetSourceIP(in_addr ip) +{ + for (int i = 0; i < iAddrCnt; i++) + if (Addrs[i].isIPNull()) + Addrs[i].SetIP(ip); +} + +void C4Network2Reference::InitLocal() +{ + // Copy all game parameters + Parameters = ::Game.Parameters; + + // Discard player resources (we don't want these infos in the reference) + C4ClientPlayerInfos *pClientInfos; C4PlayerInfo *pPlayerInfo; + int32_t i, j; + for (i = 0; (pClientInfos = Parameters.PlayerInfos.GetIndexedInfo(i)); i++) + for (j = 0; (pPlayerInfo = pClientInfos->GetPlayerInfo(j)); j++) + pPlayerInfo->DiscardResource(); + + // Special additional information in reference + Icon = ::Game.C4S.Head.Icon; + Title.CopyValidated(::Game.ScenarioTitle); + GameStatus = ::Network.Status; + Time = ::Game.Time; + Frame = ::Game.FrameCounter; + StartTime = ::Game.StartTime; + LeaguePerformance = ::Game.RoundResults.GetLeaguePerformance(); + Comment = Config.Network.Comment; + JoinAllowed = ::Network.isJoinAllowed(); + ObservingAllowed = ::Network.isObservingAllowed(); + PasswordNeeded = ::Network.isPassworded(); + Game.Set(); + + // Addresses + C4Network2Client *pLocalNetClient = ::Game.Clients.getLocal()->getNetClient(); + iAddrCnt = pLocalNetClient->getAddrCnt(); + for (i = 0; i < iAddrCnt; i++) + Addrs[i] = pLocalNetClient->getAddr(i); + +} + +void C4Network2Reference::SortNullIPsBack() +{ + // Sort all addresses with zero IP to back of list + int iSortAddrCnt = iAddrCnt; + for (int i = 0; i < iSortAddrCnt; i++) + if (Addrs[i].isIPNull()) + { + C4Network2Address Addr = Addrs[i]; + for (int j = i + 1; j < iAddrCnt; j++) + Addrs[j - 1] = Addrs[j]; + Addrs[iAddrCnt - 1] = Addr; + // Correct position + i--; iSortAddrCnt--; + } +} + +void C4Network2Reference::CompileFunc(StdCompiler *pComp) +{ + pComp->Value(mkNamingAdapt(Icon, "Icon", 0)); + pComp->Value(mkNamingAdapt(Title, "Title", "No title")); + pComp->Value(mkParAdapt(GameStatus, true)); + pComp->Value(mkNamingAdapt(Time, "Time", 0)); + pComp->Value(mkNamingAdapt(Frame, "Frame", 0)); + pComp->Value(mkNamingAdapt(StartTime, "StartTime", 0)); + pComp->Value(mkNamingAdapt(LeaguePerformance, "LeaguePerformance",0)); + pComp->Value(mkNamingAdapt(Comment, "Comment", "")); + pComp->Value(mkNamingAdapt(JoinAllowed, "JoinAllowed", true)); + pComp->Value(mkNamingAdapt(ObservingAllowed, "ObservingAllowed", true)); + pComp->Value(mkNamingAdapt(PasswordNeeded, "PasswordNeeded", false)); + pComp->Value(mkNamingAdapt(mkIntPackAdapt(iAddrCnt), "AddressCount", 0)); + iAddrCnt = Min(C4ClientMaxAddr, iAddrCnt); + pComp->Value(mkNamingAdapt(mkArrayAdapt(Addrs, iAddrCnt, C4Network2Address()), "Address")); + pComp->Value(mkNamingAdapt(Game.sEngineName, "Game", "None")); + pComp->Value(mkNamingAdapt(mkArrayAdaptDM(Game.iVer,0),"Version" )); + pComp->Value(mkNamingAdapt(OfficialServer, "OfficialServer", false)); + + pComp->Value(Parameters); +} + +int32_t C4Network2Reference::getSortOrder() const // Don't go over 100, because that's for the masterserver... +{ + C4GameVersion verThis; + int iOrder = 0; + // Official server + if (isOfficialServer() && !Config.Network.UseAlternateServer) iOrder += 50; + // Joinable + if (isJoinAllowed() && (getGameVersion() == verThis)) iOrder += 25; + // League game + if (Parameters.isLeague()) iOrder += 5; + // In lobby + if (getGameStatus().isLobbyActive()) iOrder += 3; + // No password needed + if (!isPasswordNeeded()) iOrder += 1; + // Done + return iOrder; +} + + +// *** C4Network2RefServer + +C4Network2RefServer::C4Network2RefServer() + : pReference(NULL) +{ +} + +C4Network2RefServer::~C4Network2RefServer() +{ + Clear(); +} + +void C4Network2RefServer::Clear() +{ + C4NetIOTCP::Close(); + delete pReference; pReference = NULL; +} + +void C4Network2RefServer::SetReference(C4Network2Reference *pNewReference) +{ + CStdLock RefLock(&RefCSec); + delete pReference; pReference = pNewReference; +} + +void C4Network2RefServer::PackPacket(const C4NetIOPacket &rPacket, StdBuf &rOutBuf) +{ + // Just append the packet + rOutBuf.Append(rPacket); +} + +size_t C4Network2RefServer::UnpackPacket(const StdBuf &rInBuf, const C4NetIO::addr_t &addr) +{ + const char *pData = getBufPtr(rInBuf); + // Check for complete header + const char *pHeaderEnd = strstr(pData, "\r\n\r\n"); + if (!pHeaderEnd) + return 0; + // Check method (only GET is allowed for now) + if (!SEqual2(pData, "GET ")) + { + RespondNotImplemented(addr, "Method not implemented"); + return rInBuf.getSize(); + } + // Check target + // TODO + RespondReference(addr); + return rInBuf.getSize(); +} + +void C4Network2RefServer::RespondNotImplemented(const C4NetIO::addr_t &addr, const char *szMessage) +{ + // Send the message + StdStrBuf Data = FormatString("HTTP/1.0 501 %s\r\n\r\n", szMessage); + Send(C4NetIOPacket(Data.getData(), Data.getLength(), false, addr)); + // Close the connection + Close(addr); +} + +void C4Network2RefServer::RespondReference(const C4NetIO::addr_t &addr) +{ + CStdLock RefLock(&RefCSec); + // Pack + StdStrBuf PacketData = DecompileToBuf(mkNamingPtrAdapt(pReference, "Reference")); + // Create header + StdStrBuf Header = FormatString( + "HTTP/1.1 200 Found\r\n" + "Content-Length: %lu\r\n" + "Content-Type: text/plain; charset=UTF-8\r\n" + "Server: " C4ENGINENAME "/" C4VERSION "\r\n" + "\r\n", + static_cast(PacketData.getLength())); + // Send back + Send(C4NetIOPacket(Header, Header.getLength(), false, addr)); + Send(C4NetIOPacket(PacketData, PacketData.getLength(), false, addr)); + // Close the connection + Close(addr); +} + +// *** C4Network2HTTPClient + +C4Network2HTTPClient::C4Network2HTTPClient() + : fBinary(false), fBusy(false), fSuccess(false), fConnected(false), iDataOffset(0), iDownloadedSize(0), iTotalSize(0), + pNotify(NULL) +{ + C4NetIOTCP::SetCallback(this); +} + +C4Network2HTTPClient::~C4Network2HTTPClient() +{ +} + +void C4Network2HTTPClient::PackPacket(const C4NetIOPacket &rPacket, StdBuf &rOutBuf) +{ + // Just append the packet + rOutBuf.Append(rPacket); +} + +size_t C4Network2HTTPClient::UnpackPacket(const StdBuf &rInBuf, const C4NetIO::addr_t &addr) +{ + // since new data arrived, increase timeout time + ResetRequestTimeout(); + // Check for complete header + if (!iDataOffset) + { + // Copy data into string buffer (terminate) + StdStrBuf Data; Data.Copy(getBufPtr(rInBuf), rInBuf.getSize()); + const char *pData = Data.getData(); + // Header complete? + const char *pContent = SSearch(pData, "\r\n\r\n"); + if (!pContent) + return 0; + // Read the header + if (!ReadHeader(Data)) + { + fBusy = fSuccess = false; + Close(addr); + return rInBuf.getSize(); + } + } + iDownloadedSize = rInBuf.getSize() - iDataOffset; + // Check if the packet is complete + if (iTotalSize > iDownloadedSize) + { + return 0; + } + // Get data, uncompress it if needed + StdBuf Data = rInBuf.getPart(iDataOffset, iTotalSize); + if (fCompressed) + if (!Decompress(&Data)) + { + fBusy = fSuccess = false; + Close(addr); + return rInBuf.getSize(); + } + // Save the result + if (fBinary) + ResultBin.Copy(Data); + else + ResultString.Copy(getBufPtr(Data), Data.getSize()); + fBusy = false; fSuccess = true; + // Callback + OnPacket(C4NetIOPacket(Data, addr), this); + // Done + Close(addr); + return rInBuf.getSize(); +} + +bool C4Network2HTTPClient::ReadHeader(StdStrBuf Data) +{ + const char *pData = Data.getData(); + const char *pContent = SSearch(pData, "\r\n\r\n"); + if (!pContent) + return 0; + // Parse header line + int iHTTPVer1, iHTTPVer2, iResponseCode, iStatusStringPtr; + if (sscanf(pData, "HTTP/%d.%d %d %n", &iHTTPVer1, &iHTTPVer2, &iResponseCode, &iStatusStringPtr) != 3) + { + Error = "Invalid status line!"; + return false; + } + // Check HTTP version + if (iHTTPVer1 != 1) + { + Error.Format("Unsupported HTTP version: %d.%d!", iHTTPVer1, iHTTPVer2); + return false; + } + // Check code + if (iResponseCode != 200) + { + // Get status string + StdStrBuf StatusString; StatusString.CopyUntil(pData + iStatusStringPtr, '\r'); + // Create error message + Error.Format("HTTP server responded %d: %s", iResponseCode, StatusString.getData()); + return false; + } + // Get content length + const char *pContentLength = SSearch(pData, "\r\nContent-Length:"); + int iContentLength; + if (!pContentLength || pContentLength > pContent || + sscanf(pContentLength, "%d", &iContentLength) != 1) + { + Error.Format("Invalid server response: Content-Length is missing!"); + return false; + } + iTotalSize = iContentLength; + iDataOffset = (pContent - pData); + // Get content encoding + const char *pContentEncoding = SSearch(pData, "\r\nContent-Encoding:"); + if (pContentEncoding) + { + while (*pContentEncoding == ' ') pContentEncoding++; + StdStrBuf Encoding; Encoding.CopyUntil(pContentEncoding, '\r'); + if (Encoding == "gzip") + fCompressed = true; + else + fCompressed = false; + } + else + fCompressed = false; + // Okay + return true; +} + +bool C4Network2HTTPClient::Decompress(StdBuf *pData) +{ + size_t iSize = pData->getSize(); + // Create buffer + uint32_t iOutSize = *getBufPtr(*pData, pData->getSize() - sizeof(uint32_t)); + iOutSize = Min(iOutSize, iSize * 1000); + StdBuf Out; Out.New(iOutSize); + // Prepare stream + z_stream zstrm; + ZeroMem(&zstrm, sizeof(zstrm)); + zstrm.next_in = const_cast(getBufPtr(*pData)); + zstrm.avail_in = pData->getSize(); + zstrm.next_out = getMBufPtr(Out); + zstrm.avail_out = Out.getSize(); + // Inflate... + if (inflateInit2(&zstrm, 15 + 16) != Z_OK) + { + Error.Format("Could not decompress data!"); + return false; + } + // Inflate! + if (inflate(&zstrm, Z_FINISH) != Z_STREAM_END) + { + inflateEnd(&zstrm); + Error.Format("Could not decompress data!"); + return false; + } + // Return the buffer + Out.SetSize(zstrm.total_out); + pData->Take(std::move(Out)); + // Okay + inflateEnd(&zstrm); + return true; +} + +bool C4Network2HTTPClient::OnConn(const C4NetIO::addr_t &AddrPeer, const C4NetIO::addr_t &AddrConnect, const C4NetIO::addr_t *pOwnAddr, C4NetIO *pNetIO) +{ + // Make sure we're actually waiting for this connection + if (!AddrEqual(AddrConnect, ServerAddr)) + return false; + // Save pack peer address + PeerAddr = AddrPeer; + // Send the request + Send(C4NetIOPacket(Request, AddrPeer)); + Request.Clear(); + fConnected = true; + return true; +} + +void C4Network2HTTPClient::OnDisconn(const C4NetIO::addr_t &AddrPeer, C4NetIO *pNetIO, const char *szReason) +{ + // Got no complete packet? Failure... + if (!fSuccess && Error.isNull()) + { + fBusy = false; + Error.Format("Unexpected disconnect: %s", szReason); + } + fConnected = false; + // Notify + if (pNotify) + pNotify->PushEvent(Ev_HTTP_Response, this); +} + +void C4Network2HTTPClient::OnPacket(const class C4NetIOPacket &rPacket, C4NetIO *pNetIO) +{ + // Everything worthwhile was already done in UnpackPacket. Only do notify callback + if (pNotify) + pNotify->PushEvent(Ev_HTTP_Response, this); +} + +bool C4Network2HTTPClient::Execute(int iMaxTime) +{ + // Check timeout + if (fBusy && time(NULL) > iRequestTimeout) + { + Cancel("Request timeout"); + return true; + } + // Execute normally + return C4NetIOTCP::Execute(iMaxTime); +} + +time_t C4Network2HTTPClient::GetNextTick(time_t tNow) +{ + time_t iNetIOTCPTick = C4NetIOTCP::GetNextTick(tNow); + if (!fBusy) + return iNetIOTCPTick; + + time_t iHTTPClientTick = tNow + 1000 * Max(iRequestTimeout - time(NULL), 0); + + return Max(iNetIOTCPTick, iHTTPClientTick); +} + +bool C4Network2HTTPClient::IsScheduledExecution() +{ + return C4NetIOTCP::IsScheduledExecution(); +} + +bool C4Network2HTTPClient::Query(const StdBuf &Data, bool fBinary) +{ + if (Server.isNull()) return false; + // Cancel previous request + if (fBusy) + Cancel("Cancelled"); + // No result known yet + ResultString.Clear(); + // store mode + this->fBinary = fBinary; + // Create request + StdStrBuf Header; + if (Data.getSize()) + Header.Format( + "POST %s HTTP/1.0\r\n" + "Host: %s\r\n" + "Connection: Close\r\n" + "Content-Length: %lu\r\n" + "Content-Type: text/plain; charset=utf-8\r\n" + "Accept-Charset: utf-8\r\n" + "Accept-Encoding: gzip\r\n" + "Accept-Language: %s\r\n" + "User-Agent: " C4ENGINENAME "/" C4VERSION "\r\n" + "\r\n", + RequestPath.getData(), + Server.getData(), + static_cast(Data.getSize()), + Config.General.LanguageEx); + else + Header.Format( + "GET %s HTTP/1.0\r\n" + "Host: %s\r\n" + "Connection: Close\r\n" + "Accept-Charset: utf-8\r\n" + "Accept-Encoding: gzip\r\n" + "Accept-Language: %s\r\n" + "User-Agent: " C4ENGINENAME "/" C4VERSION "\r\n" + "\r\n", + RequestPath.getData(), + Server.getData(), + Config.General.LanguageEx); + // Compose query + Request.Take(Header.GrabPointer(), Header.getLength()); + Request.Append(Data); + // Start connecting + if (!Connect(ServerAddr)) + return false; + // Okay, request will be performed when connection is complete + fBusy = true; + iDataOffset = 0; + ResetRequestTimeout(); + ResetError(); + return true; +} + +void C4Network2HTTPClient::ResetRequestTimeout() +{ + // timeout C4Network2HTTPQueryTimeout seconds from this point + iRequestTimeout = time(NULL) + C4Network2HTTPQueryTimeout; +} + +void C4Network2HTTPClient::Cancel(const char *szReason) +{ + // Close connection - and connection attempt + Close(ServerAddr); Close(PeerAddr); + // Reset flags + fBusy = fSuccess = fConnected = fBinary = false; + iDownloadedSize = iTotalSize = iDataOffset = 0; + Error = szReason; +} + +void C4Network2HTTPClient::Clear() +{ + fBusy = fSuccess = fConnected = fBinary = false; + iDownloadedSize = iTotalSize = iDataOffset = 0; + ResultBin.Clear(); + ResultString.Clear(); + Error.Clear(); +} + +bool C4Network2HTTPClient::SetServer(const char *szServerAddress) +{ + // Split address + const char *pRequestPath; + if ((pRequestPath = strchr(szServerAddress, '/'))) + { + Server.CopyUntil(szServerAddress, '/'); + RequestPath = pRequestPath; + } + else + { + Server = szServerAddress; + RequestPath = "/"; + } + // Resolve address + if (!ResolveAddress(Server.getData(), &ServerAddr, GetDefaultPort())) + { + SetError(FormatString("Could not resolve server address %s!", Server.getData()).getData()); + return false; + } + // Remove port + const char *pColon = strchr(Server.getData(), ':'); + if (pColon) + Server.SetLength(pColon - Server.getData()); + // Done + ResetError(); + return true; +} + +// *** C4Network2UpdateClient + +bool C4Network2UpdateClient::QueryUpdateURL() +{ + // Perform an Query query + return Query(NULL, false); +} + +bool C4Network2UpdateClient::GetUpdateURL(StdStrBuf *pUpdateURL) +{ + // Sanity check + if (isBusy() || !isSuccess()) return false; + // Parse response + try + { + CompileFromBuf(mkNamingAdapt( + mkNamingAdapt(mkParAdapt(*pUpdateURL, StdCompiler::RCT_All), "UpdateURL", ""), + C4ENGINENAME), ResultString); + } + catch (StdCompiler::Exception *pExc) + { + SetError(pExc->Msg.getData()); + return false; + } + // done; version OK! + return true; +} + +bool C4Network2UpdateClient::GetVersion(StdStrBuf *pVersion) +{ + // Sanity check + if (isBusy() || !isSuccess()) return false; + // Parse response + try + { + CompileFromBuf(mkNamingAdapt( + mkNamingAdapt(mkParAdapt(*pVersion, StdCompiler::RCT_All), "Version", ""), + C4ENGINENAME), ResultString); + } + catch (StdCompiler::Exception *pExc) + { + SetError(pExc->Msg.getData()); + return false; + } + // done; version OK! + return true; +} + +// *** C4Network2RefClient + +bool C4Network2RefClient::QueryReferences() +{ + // Perform an Query query + return Query(NULL, false); +} + +bool C4Network2RefClient::GetReferences(C4Network2Reference **&rpReferences, int32_t &rRefCount) +{ + // Sanity check + if (isBusy() || !isSuccess()) return false; + // local update test + try + { + // Create compiler + StdCompilerINIRead Comp; + Comp.setInput(ResultString); + Comp.Begin(); + // Read reference count + Comp.Value(mkNamingCountAdapt(rRefCount, "Reference")); + // Create reference array and initialize + rpReferences = new C4Network2Reference *[rRefCount]; + for (int i = 0; i < rRefCount; i++) + rpReferences[i] = NULL; + // Get references + Comp.Value(mkNamingAdapt(mkArrayAdaptMap(rpReferences, rRefCount, mkPtrAdaptNoNull), "Reference")); + mkPtrAdaptNoNull(*rpReferences); + // Done + Comp.End(); + } + catch (StdCompiler::Exception *pExc) + { + SetError(pExc->Msg.getData()); + return false; + } + // Set source ip + for (int i = 0; i < rRefCount; i++) + rpReferences[i]->SetSourceIP(getServerAddress().sin_addr); + // Done + ResetError(); + return true; +} + diff --git a/src/network/C4Network2Reference.h b/src/network/C4Network2Reference.h index 5f6f9573c..6ee008d45 100644 --- a/src/network/C4Network2Reference.h +++ b/src/network/C4Network2Reference.h @@ -1,217 +1,218 @@ -/* - * OpenClonk, http://www.openclonk.org - * - * Copyright (c) 2006-2007 Peter Wortmann - * Copyright (c) 2007-2008 Sven Eberhardt - * Copyright (c) 2010 Tobias Zwick - * Copyright (c) 2001-2009, RedWolf Design GmbH, http://www.clonk.de - * - * Portions might be copyrighted by other authors who have contributed - * to OpenClonk. - * - * Permission to use, copy, modify, and/or distribute this software for any - * purpose with or without fee is hereby granted, provided that the above - * copyright notice and this permission notice appear in all copies. - * See isc_license.txt for full license and disclaimer. - * - * "Clonk" is a registered trademark of Matthes Bender. - * See clonk_trademark_license.txt for full license. - */ -#ifndef C4NETWORK2REFERENCE_H -#define C4NETWORK2REFERENCE_H - -#include "C4Network2.h" -#include "C4Network2Client.h" -#include "C4GameParameters.h" -#include "C4Version.h" -#include "C4GameVersion.h" -#include "C4InputValidation.h" - -const int C4Network2HTTPQueryTimeout = 10; // (s) - -// Session data -class C4Network2Reference -{ -public: - C4Network2Reference(); - ~C4Network2Reference(); - - // Game parameters - C4GameParameters Parameters; - -private: - // General information - int32_t Icon; - ValidatedStdCopyStrBuf Title; - C4Network2Status GameStatus; - int32_t Time; - int32_t Frame; - int32_t StartTime; - int32_t LeaguePerformance; // custom settlement league performance if scenario doesn't use elapsed frames - ValidatedStdCopyStrBuf Comment; - bool JoinAllowed; - bool ObservingAllowed; - bool PasswordNeeded; - bool OfficialServer; - - // Engine information - C4GameVersion Game; - - // Network addresses - uint8_t iAddrCnt; - C4Network2Address Addrs[C4ClientMaxAddr]; - -public: - const C4Network2Address &getAddr(int i) const { return Addrs[i]; } - int getAddrCnt() const { return iAddrCnt; } - const char *getTitle() const { return Title.getData(); } - int32_t getIcon() const { return Icon; } - C4Network2Status getGameStatus() const { return GameStatus; } - const char *getComment() const { return Comment.getData(); } - const C4GameVersion &getGameVersion() const { return Game; } - bool isPasswordNeeded() const { return PasswordNeeded; } - bool isJoinAllowed() const { return JoinAllowed; } - bool isOfficialServer() const { return OfficialServer; } - int32_t getSortOrder() const; - int32_t getTime() const { return Time; } - int32_t getStartTime() const { return StartTime; } - - void SetSourceIP(in_addr ip); - - void InitLocal(); - - void SortNullIPsBack(); - - void CompileFunc(StdCompiler *pComp); -}; - -// Serves references (mini-HTTP-server) -class C4Network2RefServer : public C4NetIOTCP -{ -public: - C4Network2RefServer(); - virtual ~C4Network2RefServer(); - -private: - CStdCSec RefCSec; - C4Network2Reference *pReference; - -public: - void Clear(); - void SetReference(C4Network2Reference *pReference); - -protected: - // Overridden - virtual void PackPacket(const C4NetIOPacket &rPacket, StdBuf &rOutBuf); - virtual size_t UnpackPacket(const StdBuf &rInBuf, const C4NetIO::addr_t &addr); - -private: - // Responses - void RespondNotImplemented(const C4NetIO::addr_t &addr, const char *szMessage); - void RespondReference(const C4NetIO::addr_t &addr); - -}; - -// mini HTTP client -class C4Network2HTTPClient : public C4NetIOTCP, private C4NetIO::CBClass -{ -public: - C4Network2HTTPClient(); - virtual ~C4Network2HTTPClient(); - -private: - - // Address information - C4NetIO::addr_t ServerAddr, PeerAddr; - StdCopyStrBuf Server, RequestPath; - - bool fBinary; - bool fBusy, fSuccess, fConnected; - size_t iDataOffset; - StdCopyBuf Request; - time_t iRequestTimeout; - - // Response header data - size_t iDownloadedSize, iTotalSize; - bool fCompressed; - - // Event queue to use for notify when something happens - class C4InteractiveThread *pNotify; - -protected: - StdCopyBuf ResultBin; // set if fBinary - StdCopyStrBuf ResultString; // set if !fBinary - -protected: - - // Overridden - virtual void PackPacket(const C4NetIOPacket &rPacket, StdBuf &rOutBuf); - virtual size_t UnpackPacket(const StdBuf &rInBuf, const C4NetIO::addr_t &addr); - - // Callbacks - bool OnConn(const C4NetIO::addr_t &AddrPeer, const C4NetIO::addr_t &AddrConnect, const addr_t *pOwnAddr, C4NetIO *pNetIO); - void OnDisconn(const C4NetIO::addr_t &AddrPeer, C4NetIO *pNetIO, const char *szReason); - void OnPacket(const class C4NetIOPacket &rPacket, C4NetIO *pNetIO); - - void ResetRequestTimeout(); - virtual int32_t GetDefaultPort() { return 80; } - -public: - bool Query(const StdBuf &Data, bool fBinary); - bool Query(const char *szData, bool fBinary) { return Query(StdBuf(szData, SLen(szData)), fBinary); } - - bool isBusy() const { return fBusy; } - bool isSuccess() const { return fSuccess; } - bool isConnected() const { return fConnected; } - size_t getTotalSize() const { return iTotalSize; } - size_t getDownloadedSize() const { return iDownloadedSize; } - const StdBuf &getResultBin() const { assert(fBinary); return ResultBin; } - const char *getResultString() const { assert(!fBinary); return ResultString.getData(); } - const char *getServerName() const { return Server.getData(); } - const char *getRequest() const { return RequestPath.getData(); } - const C4NetIO::addr_t &getServerAddress() const { return ServerAddr; } - - void Cancel(const char *szReason); - void Clear(); - - bool SetServer(const char *szServerAddress); - - void SetNotify(class C4InteractiveThread *pnNotify) { pNotify = pnNotify; } - - // Overridden - virtual bool Execute(int iMaxTime, pollfd * readyfds) { return Execute(iMaxTime); } - virtual bool Execute(int iMaxTime = TO_INF); - virtual int GetNextTick(int Now); - -private: - bool ReadHeader(StdStrBuf Data); - bool Decompress(StdBuf *pData); - -}; - -// Loads current update url string (mini-HTTP-client) -class C4Network2UpdateClient : public C4Network2HTTPClient -{ -protected: - virtual int32_t GetDefaultPort() { return C4NetStdPortHTTP; } -public: - C4Network2UpdateClient() : C4Network2HTTPClient() {} - - bool QueryUpdateURL(); - bool GetUpdateURL(StdStrBuf *pUpdateURL); - bool GetVersion(StdStrBuf *pVersion); -}; - -// Loads references + current update url string (mini-HTTP-client) -class C4Network2RefClient : public C4Network2UpdateClient -{ -protected: - virtual int32_t GetDefaultPort() { return C4NetStdPortRefServer; } -public: - C4Network2RefClient() : C4Network2UpdateClient() {} - - bool QueryReferences(); - bool GetReferences(C4Network2Reference **&rpReferences, int32_t &rRefCount); -}; - -#endif // C4NETWORK2REFERENCE_H +/* + * OpenClonk, http://www.openclonk.org + * + * Copyright (c) 2006-2007 Peter Wortmann + * Copyright (c) 2007-2008 Sven Eberhardt + * Copyright (c) 2010 Tobias Zwick + * Copyright (c) 2001-2009, RedWolf Design GmbH, http://www.clonk.de + * + * Portions might be copyrighted by other authors who have contributed + * to OpenClonk. + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * See isc_license.txt for full license and disclaimer. + * + * "Clonk" is a registered trademark of Matthes Bender. + * See clonk_trademark_license.txt for full license. + */ +#ifndef C4NETWORK2REFERENCE_H +#define C4NETWORK2REFERENCE_H + +#include "C4Network2.h" +#include "C4Network2Client.h" +#include "C4GameParameters.h" +#include "C4Version.h" +#include "C4GameVersion.h" +#include "C4InputValidation.h" + +const int C4Network2HTTPQueryTimeout = 10; // (s) + +// Session data +class C4Network2Reference +{ +public: + C4Network2Reference(); + ~C4Network2Reference(); + + // Game parameters + C4GameParameters Parameters; + +private: + // General information + int32_t Icon; + ValidatedStdCopyStrBuf Title; + C4Network2Status GameStatus; + int32_t Time; + int32_t Frame; + int32_t StartTime; + int32_t LeaguePerformance; // custom settlement league performance if scenario doesn't use elapsed frames + ValidatedStdCopyStrBuf Comment; + bool JoinAllowed; + bool ObservingAllowed; + bool PasswordNeeded; + bool OfficialServer; + + // Engine information + C4GameVersion Game; + + // Network addresses + uint8_t iAddrCnt; + C4Network2Address Addrs[C4ClientMaxAddr]; + +public: + const C4Network2Address &getAddr(int i) const { return Addrs[i]; } + int getAddrCnt() const { return iAddrCnt; } + const char *getTitle() const { return Title.getData(); } + int32_t getIcon() const { return Icon; } + C4Network2Status getGameStatus() const { return GameStatus; } + const char *getComment() const { return Comment.getData(); } + const C4GameVersion &getGameVersion() const { return Game; } + bool isPasswordNeeded() const { return PasswordNeeded; } + bool isJoinAllowed() const { return JoinAllowed; } + bool isOfficialServer() const { return OfficialServer; } + int32_t getSortOrder() const; + int32_t getTime() const { return Time; } + int32_t getStartTime() const { return StartTime; } + + void SetSourceIP(in_addr ip); + + void InitLocal(); + + void SortNullIPsBack(); + + void CompileFunc(StdCompiler *pComp); +}; + +// Serves references (mini-HTTP-server) +class C4Network2RefServer : public C4NetIOTCP +{ +public: + C4Network2RefServer(); + virtual ~C4Network2RefServer(); + +private: + CStdCSec RefCSec; + C4Network2Reference *pReference; + +public: + void Clear(); + void SetReference(C4Network2Reference *pReference); + +protected: + // Overridden + virtual void PackPacket(const C4NetIOPacket &rPacket, StdBuf &rOutBuf); + virtual size_t UnpackPacket(const StdBuf &rInBuf, const C4NetIO::addr_t &addr); + +private: + // Responses + void RespondNotImplemented(const C4NetIO::addr_t &addr, const char *szMessage); + void RespondReference(const C4NetIO::addr_t &addr); + +}; + +// mini HTTP client +class C4Network2HTTPClient : public C4NetIOTCP, private C4NetIO::CBClass +{ +public: + C4Network2HTTPClient(); + virtual ~C4Network2HTTPClient(); + +private: + + // Address information + C4NetIO::addr_t ServerAddr, PeerAddr; + StdCopyStrBuf Server, RequestPath; + + bool fBinary; + bool fBusy, fSuccess, fConnected; + size_t iDataOffset; + StdCopyBuf Request; + time_t iRequestTimeout; + + // Response header data + size_t iDownloadedSize, iTotalSize; + bool fCompressed; + + // Event queue to use for notify when something happens + class C4InteractiveThread *pNotify; + +protected: + StdCopyBuf ResultBin; // set if fBinary + StdCopyStrBuf ResultString; // set if !fBinary + +protected: + + // Overridden + virtual void PackPacket(const C4NetIOPacket &rPacket, StdBuf &rOutBuf); + virtual size_t UnpackPacket(const StdBuf &rInBuf, const C4NetIO::addr_t &addr); + + // Callbacks + bool OnConn(const C4NetIO::addr_t &AddrPeer, const C4NetIO::addr_t &AddrConnect, const addr_t *pOwnAddr, C4NetIO *pNetIO); + void OnDisconn(const C4NetIO::addr_t &AddrPeer, C4NetIO *pNetIO, const char *szReason); + void OnPacket(const class C4NetIOPacket &rPacket, C4NetIO *pNetIO); + + void ResetRequestTimeout(); + virtual int32_t GetDefaultPort() { return 80; } + +public: + bool Query(const StdBuf &Data, bool fBinary); + bool Query(const char *szData, bool fBinary) { return Query(StdBuf(szData, SLen(szData)), fBinary); } + + bool isBusy() const { return fBusy; } + bool isSuccess() const { return fSuccess; } + bool isConnected() const { return fConnected; } + size_t getTotalSize() const { return iTotalSize; } + size_t getDownloadedSize() const { return iDownloadedSize; } + const StdBuf &getResultBin() const { assert(fBinary); return ResultBin; } + const char *getResultString() const { assert(!fBinary); return ResultString.getData(); } + const char *getServerName() const { return Server.getData(); } + const char *getRequest() const { return RequestPath.getData(); } + const C4NetIO::addr_t &getServerAddress() const { return ServerAddr; } + + void Cancel(const char *szReason); + void Clear(); + + bool SetServer(const char *szServerAddress); + + void SetNotify(class C4InteractiveThread *pnNotify) { pNotify = pnNotify; } + + // Overridden + virtual bool Execute(int iMaxTime, pollfd * readyfds) { return Execute(iMaxTime); } + virtual bool Execute(int iMaxTime = TO_INF); + virtual time_t GetNextTick(time_t tNow); + virtual bool IsScheduledExecution(); + +private: + bool ReadHeader(StdStrBuf Data); + bool Decompress(StdBuf *pData); + +}; + +// Loads current update url string (mini-HTTP-client) +class C4Network2UpdateClient : public C4Network2HTTPClient +{ +protected: + virtual int32_t GetDefaultPort() { return C4NetStdPortHTTP; } +public: + C4Network2UpdateClient() : C4Network2HTTPClient() {} + + bool QueryUpdateURL(); + bool GetUpdateURL(StdStrBuf *pUpdateURL); + bool GetVersion(StdStrBuf *pVersion); +}; + +// Loads references + current update url string (mini-HTTP-client) +class C4Network2RefClient : public C4Network2UpdateClient +{ +protected: + virtual int32_t GetDefaultPort() { return C4NetStdPortRefServer; } +public: + C4Network2RefClient() : C4Network2UpdateClient() {} + + bool QueryReferences(); + bool GetReferences(C4Network2Reference **&rpReferences, int32_t &rRefCount); +}; + +#endif // C4NETWORK2REFERENCE_H diff --git a/src/network/C4Packet2.cpp b/src/network/C4Packet2.cpp index 3a1843d91..312e1df9b 100644 --- a/src/network/C4Packet2.cpp +++ b/src/network/C4Packet2.cpp @@ -513,19 +513,23 @@ void C4PacketJoinData::CompileFunc(StdCompiler *pComp) // *** C4PacketPing C4PacketPing::C4PacketPing(uint32_t iPacketCounter, uint32_t iRemotePacketCounter) - : iTime(GetTime()), + : tTime(GetTime()), iPacketCounter(iPacketCounter) { } uint32_t C4PacketPing::getTravelTime() const { - return GetTime() - iTime; + return GetTime() - tTime; } void C4PacketPing::CompileFunc(StdCompiler *pComp) { - pComp->Value(mkNamingAdapt(iTime, "Time", 0U)); + // FIXME: the compiler can't compile 64bit integers yet, the ping will return wrong times if GetTime() returns large ints + uint32_t time; + pComp->Value(mkNamingAdapt(time, "Time", 0U)); + tTime = time; + pComp->Value(mkNamingAdapt(iPacketCounter, "PacketCounter", 0U)); } diff --git a/src/platform/GetTime.cpp b/src/platform/GetTime.cpp index 97b3a183e..904e446c6 100644 --- a/src/platform/GetTime.cpp +++ b/src/platform/GetTime.cpp @@ -24,7 +24,7 @@ #include #include -unsigned int GetTime() +time_t GetTime() { return timeGetTime(); } @@ -37,7 +37,7 @@ unsigned int GetTime() #include #endif -unsigned int GetTime() +time_t GetTime() { #ifdef __APPLE__ static time_t sec_offset; diff --git a/src/platform/PlatformAbstraction.h b/src/platform/PlatformAbstraction.h index f6cbdb819..a3175ce95 100644 --- a/src/platform/PlatformAbstraction.h +++ b/src/platform/PlatformAbstraction.h @@ -1,263 +1,263 @@ -/* - * OpenClonk, http://www.openclonk.org - * - * Copyright (c) 1998-2000, 2007 Matthes Bender - * Copyright (c) 2002, 2004-2005, 2007 Sven Eberhardt - * Copyright (c) 2004-2011 Günther Brammer - * Copyright (c) 2005, 2007, 2009 Peter Wortmann - * Copyright (c) 2009-2011 Nicolas Hake - * Copyright (c) 2010 Tobias Zwick - * Copyright (c) 2010 Martin Plicht - * Copyright (c) 2010-2011 Armin Burgmeier - * Copyright (c) 2001-2009, RedWolf Design GmbH, http://www.clonk.de - * - * Portions might be copyrighted by other authors who have contributed - * to OpenClonk. - * - * Permission to use, copy, modify, and/or distribute this software for any - * purpose with or without fee is hereby granted, provided that the above - * copyright notice and this permission notice appear in all copies. - * See isc_license.txt for full license and disclaimer. - * - * "Clonk" is a registered trademark of Matthes Bender. - * See clonk_trademark_license.txt for full license. - */ - -/* All the ifdefs in one place (Hah, I wish) */ - -#ifndef INC_PLATFORMABSTRACTION -#define INC_PLATFORMABSTRACTION - -#ifdef HAVE_CONFIG_H -#include -#endif // HAVE_CONFIG_H - -// We need to #define the target Windows version selector macros before we -// including any MinGW header. -#ifdef _WIN64 -# define WINVER 0x0501 -# define _WIN32_WINDOWS 0x0501 -# define _WIN32_WINNT 0x0501 -# define _WIN32_IE 0x0501 -# define _AMD64_ 1 -#elif defined(_WIN32) -# define WINVER 0x0500 -# define _WIN32_WINDOWS 0x0500 -# define _WIN32_WINNT 0x0501 -# define _WIN32_IE 0x0501 -# define _X86_ 1 -#endif -#ifdef _WIN32 -#define WIN32_LEAN_AND_MEAN -#define UNICODE -#define _UNICODE -#ifndef NOMINMAX -# define NOMINMAX -#endif -#endif - -#if defined(_WIN32) && !defined(USE_CONSOLE) && !defined(USE_SDL_MAINLOOP) && !defined(USE_X11) && !defined(USE_COCOA) -#define USE_WIN32_WINDOWS -#endif - -#ifdef _MSC_VER -#define DEPRECATED __declspec(deprecated) -#elif defined(__GNUC__) -#define DEPRECATED __attribute__((deprecated)) -#else -#define DEPRECATED -#endif - -#ifdef _MSC_VER -#pragma warning(disable : 4786) // long symbol names -#pragma warning(disable: 4706) -#pragma warning(disable: 4239) -#pragma warning(disable: 4521) // multiple copy constructors specified -// Get non-standard constants (M_PI etc.) -# define _USE_MATH_DEFINES -#endif - - - -// C++0x nullptr -#ifdef HAVE_NULLPTR -#undef NULL -#define NULL nullptr -#endif - - - -// Integer dataypes -#ifdef HAVE_STDINT_H -#include -#elif defined(_MSC_VER) -#include -typedef signed __int8 int8_t; -typedef signed __int16 int16_t; -typedef signed __int32 int32_t; -typedef signed __int64 int64_t; -typedef unsigned __int8 uint8_t; -typedef unsigned __int16 uint16_t; -typedef unsigned __int32 uint32_t; -typedef unsigned __int64 uint64_t; -// Copied from newer stddef.h -#ifndef _INTPTR_T_DEFINED -#ifdef _WIN64 -typedef __int64 intptr_t; -#else -typedef __int32 intptr_t; -#endif -#define _INTPTR_T_DEFINED -#endif -#else -#error Could not find integer datatypes! -#endif - - - -#ifdef HAVE_UNISTD_H -#include -#else -typedef ptrdiff_t ssize_t; -#endif - - - -#ifndef HAVE_STATIC_ASSERT -#include -#ifndef BOOST_HAS_STATIC_ASSERT -#define static_assert(x, y) BOOST_STATIC_ASSERT(x) -#endif -#endif - - - -#if defined(__GNUC__) -// Allow checks for correct printf-usage -#define GNUC_FORMAT_ATTRIBUTE __attribute__ ((format (printf, 1, 2))) -#define GNUC_FORMAT_ATTRIBUTE_O __attribute__ ((format (printf, 2, 3))) -#define ALWAYS_INLINE inline __attribute__ ((always_inline)) -#define NORETURN __attribute__ ((noreturn)) -#else -#define GNUC_FORMAT_ATTRIBUTE -#define GNUC_FORMAT_ATTRIBUTE_O -#define ALWAYS_INLINE __forceinline -#define NORETURN -#endif - - - -// Temporary-To-Reference-Fix -#if !defined(__clang__) && defined(__GNUC__) && ((__GNUC__ < 4) || (__GNUC__ == 4 && __GNUC_MINOR__ < 3)) -#define ALLOW_TEMP_TO_REF(ClassName) operator ClassName & () { return *this; } -#else -#define ALLOW_TEMP_TO_REF(ClassName) -#endif - -#ifdef HAVE_RVALUE_REF -# define RREF && -#else -# define RREF & -namespace std { template inline T &move (T &t) { return t; } } -#endif - - - -#if defined(_DEBUG) && defined(_MSC_VER) -// use inline assembler to invoke the "breakpoint exception" -# define BREAKPOINT_HERE __debugbreak() -#elif defined(_DEBUG) && defined(__GNUC__) -# define BREAKPOINT_HERE asm volatile("int $3") -#elif defined(_DEBUG) && defined(HAVE_SIGNAL_H) -# include -# if defined(SIGTRAP) -# define BREAKPOINT_HERE raise(SIGTRAP); -# else -# define BREAKPOINT_HERE -# endif -#else -# define BREAKPOINT_HERE -#endif - - - -#ifdef _WIN32 - -typedef unsigned long DWORD; -typedef unsigned char BYTE; -typedef unsigned short WORD; - -#else - -// Windows integer types -typedef uint32_t DWORD; -typedef uint8_t BYTE; -typedef uint16_t WORD; - -#include -inline int stricmp(const char *s1, const char *s2) -{ - return strcasecmp(s1, s2); -} - -#endif //_WIN32 - - - -#ifdef _WIN64 -#define C4_OS "win-x86_64" -#elif defined(_WIN32) -#define C4_OS "win-x86" -#elif defined(__linux__) -#if defined(__x86_64__) -#define C4_OS "linux-x86_64" -#else -#define C4_OS "linux-x86" -#endif -#elif defined(__APPLE__) -#define C4_OS "mac-x86" -#else -#define C4_OS "" -#endif - -// delete item to the recycle bin -bool EraseItemSafe(const char *szFilename); - -// Check whether the OS is "German" -bool IsGermanSystem(); - -// open a weblink in an external browser -bool OpenURL(const char* szURL); - -// Get a monotonically increasing timestamp in milliseconds -unsigned int GetTime(); - -#ifdef _WIN32 -#include -#define F_OK 0 -#else -#include -#include -#define _O_BINARY 0 -#define _MAX_PATH PATH_MAX -#define _MAX_FNAME NAME_MAX - -bool CopyFile(const char *szSource, const char *szTarget, bool FailIfExists); -#endif - -#include -#ifndef O_CLOEXEC -#define O_CLOEXEC 0 -#endif - -#ifdef _WIN32 -#define DirSep "\\" -#define DirectorySeparator '\\' -#define AltDirectorySeparator '/' -#else -#define DirSep "/" -#define DirectorySeparator '/' -#define AltDirectorySeparator '\\' -#endif - -#endif // INC_PLATFORMABSTRACTION +/* + * OpenClonk, http://www.openclonk.org + * + * Copyright (c) 1998-2000, 2007 Matthes Bender + * Copyright (c) 2002, 2004-2005, 2007 Sven Eberhardt + * Copyright (c) 2004-2011 Günther Brammer + * Copyright (c) 2005, 2007, 2009 Peter Wortmann + * Copyright (c) 2009-2011 Nicolas Hake + * Copyright (c) 2010 Tobias Zwick + * Copyright (c) 2010 Martin Plicht + * Copyright (c) 2010-2011 Armin Burgmeier + * Copyright (c) 2001-2009, RedWolf Design GmbH, http://www.clonk.de + * + * Portions might be copyrighted by other authors who have contributed + * to OpenClonk. + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * See isc_license.txt for full license and disclaimer. + * + * "Clonk" is a registered trademark of Matthes Bender. + * See clonk_trademark_license.txt for full license. + */ + +/* All the ifdefs in one place (Hah, I wish) */ + +#ifndef INC_PLATFORMABSTRACTION +#define INC_PLATFORMABSTRACTION + +#ifdef HAVE_CONFIG_H +#include +#endif // HAVE_CONFIG_H + +// We need to #define the target Windows version selector macros before we +// including any MinGW header. +#ifdef _WIN64 +# define WINVER 0x0501 +# define _WIN32_WINDOWS 0x0501 +# define _WIN32_WINNT 0x0501 +# define _WIN32_IE 0x0501 +# define _AMD64_ 1 +#elif defined(_WIN32) +# define WINVER 0x0500 +# define _WIN32_WINDOWS 0x0500 +# define _WIN32_WINNT 0x0501 +# define _WIN32_IE 0x0501 +# define _X86_ 1 +#endif +#ifdef _WIN32 +#define WIN32_LEAN_AND_MEAN +#define UNICODE +#define _UNICODE +#ifndef NOMINMAX +# define NOMINMAX +#endif +#endif + +#if defined(_WIN32) && !defined(USE_CONSOLE) && !defined(USE_SDL_MAINLOOP) && !defined(USE_X11) && !defined(USE_COCOA) +#define USE_WIN32_WINDOWS +#endif + +#ifdef _MSC_VER +#define DEPRECATED __declspec(deprecated) +#elif defined(__GNUC__) +#define DEPRECATED __attribute__((deprecated)) +#else +#define DEPRECATED +#endif + +#ifdef _MSC_VER +#pragma warning(disable : 4786) // long symbol names +#pragma warning(disable: 4706) +#pragma warning(disable: 4239) +#pragma warning(disable: 4521) // multiple copy constructors specified +// Get non-standard constants (M_PI etc.) +# define _USE_MATH_DEFINES +#endif + + + +// C++0x nullptr +#ifdef HAVE_NULLPTR +#undef NULL +#define NULL nullptr +#endif + + + +// Integer dataypes +#ifdef HAVE_STDINT_H +#include +#elif defined(_MSC_VER) +#include +typedef signed __int8 int8_t; +typedef signed __int16 int16_t; +typedef signed __int32 int32_t; +typedef signed __int64 int64_t; +typedef unsigned __int8 uint8_t; +typedef unsigned __int16 uint16_t; +typedef unsigned __int32 uint32_t; +typedef unsigned __int64 uint64_t; +// Copied from newer stddef.h +#ifndef _INTPTR_T_DEFINED +#ifdef _WIN64 +typedef __int64 intptr_t; +#else +typedef __int32 intptr_t; +#endif +#define _INTPTR_T_DEFINED +#endif +#else +#error Could not find integer datatypes! +#endif + + + +#ifdef HAVE_UNISTD_H +#include +#else +typedef ptrdiff_t ssize_t; +#endif + + + +#ifndef HAVE_STATIC_ASSERT +#include +#ifndef BOOST_HAS_STATIC_ASSERT +#define static_assert(x, y) BOOST_STATIC_ASSERT(x) +#endif +#endif + + + +#if defined(__GNUC__) +// Allow checks for correct printf-usage +#define GNUC_FORMAT_ATTRIBUTE __attribute__ ((format (printf, 1, 2))) +#define GNUC_FORMAT_ATTRIBUTE_O __attribute__ ((format (printf, 2, 3))) +#define ALWAYS_INLINE inline __attribute__ ((always_inline)) +#define NORETURN __attribute__ ((noreturn)) +#else +#define GNUC_FORMAT_ATTRIBUTE +#define GNUC_FORMAT_ATTRIBUTE_O +#define ALWAYS_INLINE __forceinline +#define NORETURN +#endif + + + +// Temporary-To-Reference-Fix +#if !defined(__clang__) && defined(__GNUC__) && ((__GNUC__ < 4) || (__GNUC__ == 4 && __GNUC_MINOR__ < 3)) +#define ALLOW_TEMP_TO_REF(ClassName) operator ClassName & () { return *this; } +#else +#define ALLOW_TEMP_TO_REF(ClassName) +#endif + +#ifdef HAVE_RVALUE_REF +# define RREF && +#else +# define RREF & +namespace std { template inline T &move (T &t) { return t; } } +#endif + + + +#if defined(_DEBUG) && defined(_MSC_VER) +// use inline assembler to invoke the "breakpoint exception" +# define BREAKPOINT_HERE __debugbreak() +#elif defined(_DEBUG) && defined(__GNUC__) +# define BREAKPOINT_HERE asm volatile("int $3") +#elif defined(_DEBUG) && defined(HAVE_SIGNAL_H) +# include +# if defined(SIGTRAP) +# define BREAKPOINT_HERE raise(SIGTRAP); +# else +# define BREAKPOINT_HERE +# endif +#else +# define BREAKPOINT_HERE +#endif + + + +#ifdef _WIN32 + +typedef unsigned long DWORD; +typedef unsigned char BYTE; +typedef unsigned short WORD; + +#else + +// Windows integer types +typedef uint32_t DWORD; +typedef uint8_t BYTE; +typedef uint16_t WORD; + +#include +inline int stricmp(const char *s1, const char *s2) +{ + return strcasecmp(s1, s2); +} + +#endif //_WIN32 + + + +#ifdef _WIN64 +#define C4_OS "win-x86_64" +#elif defined(_WIN32) +#define C4_OS "win-x86" +#elif defined(__linux__) +#if defined(__x86_64__) +#define C4_OS "linux-x86_64" +#else +#define C4_OS "linux-x86" +#endif +#elif defined(__APPLE__) +#define C4_OS "mac-x86" +#else +#define C4_OS "" +#endif + +// delete item to the recycle bin +bool EraseItemSafe(const char *szFilename); + +// Check whether the OS is "German" +bool IsGermanSystem(); + +// open a weblink in an external browser +bool OpenURL(const char* szURL); + +// Get a monotonically increasing timestamp in milliseconds +time_t GetTime(); + +#ifdef _WIN32 +#include +#define F_OK 0 +#else +#include +#include +#define _O_BINARY 0 +#define _MAX_PATH PATH_MAX +#define _MAX_FNAME NAME_MAX + +bool CopyFile(const char *szSource, const char *szTarget, bool FailIfExists); +#endif + +#include +#ifndef O_CLOEXEC +#define O_CLOEXEC 0 +#endif + +#ifdef _WIN32 +#define DirSep "\\" +#define DirectorySeparator '\\' +#define AltDirectorySeparator '/' +#else +#define DirSep "/" +#define DirectorySeparator '/' +#define AltDirectorySeparator '\\' +#endif + +#endif // INC_PLATFORMABSTRACTION diff --git a/src/platform/StdScheduler.cpp b/src/platform/StdScheduler.cpp index 211757aaa..22da28e6f 100644 --- a/src/platform/StdScheduler.cpp +++ b/src/platform/StdScheduler.cpp @@ -65,17 +65,17 @@ bool StdSchedulerProc::ExecuteUntil(int iTimeout) if (!Execute()) return false; // Calculate endpoint - unsigned int iStopTime = GetTime() + iTimeout; + time_t tStopTime = GetTime() + iTimeout; for (;;) { // Call execute with given timeout if (!Execute(Max(iTimeout, 0))) return false; // Calculate timeout - unsigned int iTime = GetTime(); - if (iTime >= iStopTime) + time_t tTime = GetTime(); + if (tTime >= tStopTime) break; - iTimeout = int(iStopTime - iTime); + iTimeout = int(tStopTime - tTime); } // All ok. return true; @@ -175,11 +175,18 @@ bool StdScheduler::ScheduleProcs(int iTimeout) if (!iProcCnt) return false; // Get timeout - int i; int iProcTick; int Now = GetTime(); + int i; + time_t tProcTick; + time_t tNow = GetTime(); for (i = 0; i < iProcCnt; i++) - if ((iProcTick = ppProcs[i]->GetNextTick(Now)) >= 0) - if (iTimeout == -1 || iTimeout + Now > iProcTick) - iTimeout = Max(iProcTick - Now, 0); + { + if(ppProcs[i]->IsScheduledExecution()) + { + tProcTick = ppProcs[i]->GetNextTick(tNow); + if (iTimeout == -1 || iTimeout + tNow > tProcTick) + iTimeout = Max(tProcTick - tNow, 0); + } + } #ifdef STDSCHEDULER_USE_EVENTS @@ -225,16 +232,19 @@ bool StdScheduler::ScheduleProcs(int iTimeout) } // Execute all processes with timeout - Now = GetTime(); + tNow = GetTime(); for (i = 0; i < iProcCnt; i++) { - iProcTick = ppProcs[i]->GetNextTick(Now); - if (iProcTick >= 0 && iProcTick <= Now) - if (!ppProcs[i]->Execute(0)) - { - OnError(ppProcs[i]); - fSuccess = false; - } + if(ppProcs[i]->IsScheduledExecution()) + { + tProcTick = ppProcs[i]->GetNextTick(tNow); + if (tProcTick <= tNow) + if (!ppProcs[i]->Execute(0)) + { + OnError(ppProcs[i]); + fSuccess = false; + } + } } #else @@ -260,46 +270,49 @@ bool StdScheduler::ScheduleProcs(int iTimeout) if (cnt >= 0) { bool any_executed = false; - Now = GetTime(); + tNow = GetTime(); // Which process? for (i = 0; i < iProcCnt; i++) { - iProcTick = ppProcs[i]->GetNextTick(Now); - if (iProcTick >= 0 && iProcTick <= Now) + if (ppProcs[i]->IsScheduledExecution()) { - struct pollfd * pfd = 0; - if (fds_for_proc.find(ppProcs[i]) != fds_for_proc.end()) - pfd = &fds[fds_for_proc[ppProcs[i]].first]; - if (!ppProcs[i]->Execute(0, pfd)) + tProcTick = ppProcs[i]->GetNextTick(tNow); + if (tProcTick <= tNow) { - OnError(ppProcs[i]); - fSuccess = false; - } - any_executed = true; - continue; - } - // no fds? - if (fds_for_proc.find(ppProcs[i]) == fds_for_proc.end()) - continue; - // Check intersection - unsigned int begin = fds_for_proc[ppProcs[i]].first; - unsigned int end = fds_for_proc[ppProcs[i]].second; - for (unsigned int j = begin; j < end; ++j) - { - if (fds[j].events & fds[j].revents) - { - if (any_executed && ppProcs[i]->IsLowPriority()) - break; - if (!ppProcs[i]->Execute(0, &fds[begin])) + struct pollfd * pfd = 0; + if (fds_for_proc.find(ppProcs[i]) != fds_for_proc.end()) + pfd = &fds[fds_for_proc[ppProcs[i]].first]; + if (!ppProcs[i]->Execute(0, pfd)) { OnError(ppProcs[i]); fSuccess = false; } any_executed = true; - // the list of procs might have been changed, but procs must be in both ppProcs and - // fds_for_proc to be executed, which prevents execution of any proc not polled this round - // or deleted. Some procs might be skipped or executed twice, but that should be save. - break; + continue; + } + // no fds? + if (fds_for_proc.find(ppProcs[i]) == fds_for_proc.end()) + continue; + // Check intersection + unsigned int begin = fds_for_proc[ppProcs[i]].first; + unsigned int end = fds_for_proc[ppProcs[i]].second; + for (unsigned int j = begin; j < end; ++j) + { + if (fds[j].events & fds[j].revents) + { + if (any_executed && ppProcs[i]->IsLowPriority()) + break; + if (!ppProcs[i]->Execute(0, &fds[begin])) + { + OnError(ppProcs[i]); + fSuccess = false; + } + any_executed = true; + // the list of procs might have been changed, but procs must be in both ppProcs and + // fds_for_proc to be executed, which prevents execution of any proc not polled this round + // or deleted. Some procs might be skipped or executed twice, but that should be save. + break; + } } } } diff --git a/src/platform/StdScheduler.h b/src/platform/StdScheduler.h index cc083f794..efe04363a 100644 --- a/src/platform/StdScheduler.h +++ b/src/platform/StdScheduler.h @@ -47,12 +47,6 @@ struct pollfd; #endif // HAVE_PTHREAD #endif // _WIN32 -// helper -inline int MaxTimeout(int iTimeout1, int iTimeout2) -{ - return (iTimeout1 == -1 || iTimeout2 == -1) ? -1 : Max(iTimeout1, iTimeout2); -} - typedef struct _GMainLoop GMainLoop; // Abstract class for a process @@ -76,8 +70,9 @@ public: #endif // Call Execute() after this time has elapsed - // -1 means no timeout (infinity). - virtual int GetNextTick(int Now) { return -1; } + virtual time_t GetNextTick(time_t tNow) { return 0; }; + + virtual bool IsScheduledExecution() { return false; } // Is the process signal currently set? bool IsSignaled(); @@ -91,30 +86,33 @@ public: class CStdTimerProc : public StdSchedulerProc { public: - CStdTimerProc(uint32_t iDelay) : iLastTimer(0), iDelay(iDelay) { } + CStdTimerProc(uint32_t iDelay) : tLastTimer(0), iDelay(iDelay) { } ~CStdTimerProc() { } private: - uint32_t iLastTimer, iDelay; + time_t tLastTimer; + uint32_t iDelay; public: - void Set() { iLastTimer = 0; } + void Set() { tLastTimer = 0; } void SetDelay(uint32_t inDelay) { iDelay = inDelay; } bool CheckAndReset() { - if (GetTime() < iLastTimer + iDelay) return false; + if (GetTime() < tLastTimer + iDelay) return false; // Compensate light drifting - uint32_t iTime = GetTime(); - uint32_t iDrift = iTime - iLastTimer - iDelay; // >= 0 because of Check() - iLastTimer = iTime - Min(iDrift, iDelay / 2); + time_t tTime = GetTime(); + uint32_t iDrift = tTime - tLastTimer - iDelay; // >= 0 because of Check() + tLastTimer = tTime - Min(iDrift, iDelay / 2); return true; } // StdSchedulerProc override - virtual int GetNextTick(int Now) + virtual time_t GetNextTick(time_t tNow) { - return iLastTimer + iDelay; + return tLastTimer + iDelay; } + + virtual bool IsScheduledExecution() { return true; } }; // A simple alertable proc