⭐ 欢迎来到虫虫下载站! | 📦 资源下载 📁 资源专辑 ℹ️ 关于我们
⭐ 虫虫下载站

📄 usersession.cpp

📁 hl2 source code. Do not use it illegal.
💻 CPP
📖 第 1 页 / 共 4 页
字号:
//=========== (C) Copyright 2000 Valve, L.L.C. All rights reserved. ===========
//
// The copyright to the contents herein is the property of Valve, L.L.C.
// The contents may be used and/or copied only with the written permission of
// Valve, L.L.C., or in accordance with the terms and conditions stipulated in
// the agreement/contract under which the contents have been supplied.
//
// Purpose: 
//=============================================================================

#include "UserSession.h"
#include "Random.h"
#include "SessionManager.h"
#include "TrackerDatabaseManager.h"
#include "../public/ITrackerUserDatabase.h"

#include "../TrackerNET/TrackerNET_Interface.h"
#include "../TrackerNET/ReceiveMessage.h"
#include "../TrackerNET/SendMessage.h"
#include "DebugConsole_Interface.h"
#include "TrackerMessageFlags.h"
#include "TrackerProtocol.h"
#include "UtlMsgBuffer.h"
#include "TopologyManager.h"
#include "MemPool.h"

#include <assert.h>
#include <stdlib.h>
#include <time.h>

extern IDebugConsole *g_pConsole;

#define ARRAYSIZE(p)	(sizeof(p)/sizeof(p[0]))

#ifndef min
#define min(a,b)            (((a) < (b)) ? (a) : (b))
#endif

static const int STATUS_AWAY = 3;
static const int STATUS_INGAME = 4;
static const int STATUS_SNOOZE = 5;
static const int STATUS_OFFLINE = 0;

void v_strncpy(char *dest, const char *src, int bufsize);

// minimum build number of Client to be allowed to connect to server
static const int MINIMUM_BUILD_NUMBER = 1957;

// build numbers used for compatibility with old clients
static const int COMPATILIBITY_SERVERID_SUPPORT_VERSION_MIN = 1920;

// the number of seconds after missed heartbeat the user will be disconnected
static const float USER_TIMEOUT_SLOP = 30.0f;

// the minimum duration a firewall must be open to be an acceptable Client
static const float MINIMUM_FIRWALL_WINDOW_DURATION = 15.0f;

enum EAuthLevels
{
	AUTHLEVEL_NONE = 0,
	AUTHLEVEL_REQUESTINGAUTH = 3,
	AUTHLEVEL_FULLAUTH = 10,
};
//-----------------------------------------------------------------------------
// Purpose: State list
//-----------------------------------------------------------------------------
enum UserState_t
{
	STATE_NORMAL,
	STATE_REQUESTINGAUTH,
	STATE_GETFRIENDINFO,
	STATE_SENDINGSTATUS,
	STATE_EXCHANGESTATUS,
	STATE_DISCONNECTINGUSER,
	STATE_ADDWATCH,
	STATE_CHECKMESSAGES,
	STATE_SENDINGOFFLINESTATUS,
};


//-----------------------------------------------------------------------------
// Purpose: Constructor
//-----------------------------------------------------------------------------
CUserSession::CUserSession()
{
	m_iSessionID = 0;
	m_pMsgQueueFirst = NULL;
	m_pMsgQueueLast = NULL;

	Clear();
}

//-----------------------------------------------------------------------------
// Purpose: Destructor
//-----------------------------------------------------------------------------
CUserSession::~CUserSession()
{
}

//-----------------------------------------------------------------------------
// Purpose: Clears the session between user connections
//-----------------------------------------------------------------------------
void CUserSession::Clear()
{ 
	m_iUserID = 0; 
	m_iStatus = 0;
	m_iUserSessionState = USERSESSION_FREE;
	m_pDB = NULL;
	m_iBuildNum = 0;
	m_iGameIP = 0;
	m_iGamePort = 0;
	m_szGameType[0] = 0;
	m_szPassword[0] = 0;
	m_szUserName[0] = 0;
	m_flLastAccess = 0.0f;
	m_flFirewallWindow = 9999999.0f;
	m_bUpdateFirewallWindowToClient = false;
	m_flTimeoutDelay = 0.0f;

	FlushMessageQueue();
}

//-----------------------------------------------------------------------------
// Purpose: Wakes up the main thread
//-----------------------------------------------------------------------------
void CUserSession::WakeUp()
{
	sessionmanager()->WakeUp();
}

//-----------------------------------------------------------------------------
// Purpose: called after a succesful login
//-----------------------------------------------------------------------------
void CUserSession::PostLogin()
{
	g_pConsole->Print(4, "Logged in: %d\n", m_iUserID);

	// set watches on all the servers that our friend resides
	// get a list of all the friends we need to watch - this will initiate setting watches
	m_pDB->User_GetFriendList(this, m_iUserID);

	// tell other users that the friend has logged on
	UpdateStatusToFriends();
	
	// check to see if we have any messages waiting
	CheckForMessages();
}

//-----------------------------------------------------------------------------
// Purpose: Checks to see if the current user session should be checked for timeout
//-----------------------------------------------------------------------------
bool CUserSession::CheckForTimeout(float currentTime)
{
	if (m_iUserSessionState == USERSESSION_ACTIVE)
	{
		if ((m_flLastAccess + m_flTimeoutDelay) < currentTime && m_iStatus > 0)
		{
			// time elapsed between received notification from user too long, log them off
			Logoff(true, true, false);
			return true;
		}
	}
	else
	{
		float timeoutTime = 0.0f;
		if (m_iUserSessionState == USERSESSION_CONNECTING)
		{
			// timeout a connection after 20 seconds
			timeoutTime = 20.0f;
		}
		/* unnecessary:
		else if (m_iUserSessionState == USERSESSION_CONNECTFAILED)
		{
			timeoutTime = 0.0f;
		}
		*/

		if (m_iUserSessionState == USERSESSION_FREE)
		{
			g_pConsole->Print(8, "!! Timing out already freed session\n");
		}

		if ((m_flLastAccess + timeoutTime) < currentTime)
		{
			// kill the user, no need to log them off since they were never logged in
			sessionmanager()->FreeUserSession(this);
			return true;
		}
	}

	return false;
}

//-----------------------------------------------------------------------------
// Purpose: Creates a message to be sent to the current user
//-----------------------------------------------------------------------------
ISendMessage *CUserSession::CreateUserMessage(int msgID)
{
	ISendMessage *msg = net()->CreateMessage(msgID);
	msg->SetSessionID(m_iSessionID);
	msg->SetNetAddress(m_NetAddress);
	msg->SetEncrypted(true);

	return msg;
}

// message dispatch table
typedef void (CUserSession::*funcPtr)( IReceiveMessage * );
struct UserMsgDispatch_t
{
	int msgName;
	funcPtr msgFunc;
};

// map of networking messages -> functions
// ordered from most called to least called
static UserMsgDispatch_t g_UserMsgDispatch[] =
{
	{ TCLS_HEARTBEAT, CUserSession::ReceivedMsg_Heartbeat },
	{ TCLS_ROUTETOFRIEND, CUserSession::ReceivedMsg_RouteToFriend },
	{ TCLS_MESSAGE, CUserSession::ReceivedMsg_Message },
	{ TCLS_RESPONSE, CUserSession::ReceivedMsg_Response },
	{ TCLS_FRIENDINFO, CUserSession::ReceivedMsg_FriendInfo },
	{ TCLS_AUTHUSER, CUserSession::ReceivedMsg_AuthUser },
	{ TCLS_REQAUTH, CUserSession::ReceivedMsg_ReqAuth },
	{ TCLS_FRIENDSEARCH, CUserSession::ReceivedMsg_FindUser },
	{ TCLS_SETINFO, CUserSession::ReceivedMsg_SetInfo },
};


// SQL DB Reply table
typedef void (CUserSession::*dbFuncPtr)(int , void *);
struct DBReplyMapItem_t
{
	int cmdID;
	int returnState;
	dbFuncPtr msgFunc;
};

static DBReplyMapItem_t g_DBMsgDispatch[] =
{
	{ CMD_LOGIN,			STATE_NORMAL,			CUserSession::DBMsg_Login },
	{ CMD_LOGOFF,			STATE_NORMAL,			CUserSession::DBMsg_Logoff },
	{ CMD_GETSESSIONINFO,	STATE_SENDINGSTATUS,	CUserSession::DBMsg_GetSessionInfo_SendingStatus },
	{ CMD_GETSESSIONINFO,	STATE_EXCHANGESTATUS,	CUserSession::DBMsg_GetSessionInfo_ExchangeStatus },
	{ CMD_GETSESSIONINFO,	STATE_SENDINGOFFLINESTATUS,	CUserSession::DBMsg_GetSessionInfo_SendingOfflineStatus },
	{ CMD_GETSESSIONINFO,	STATE_ADDWATCH,			CUserSession::DBMsg_GetSessionInfo_AddWatch },
	{ CMD_GETSESSIONINFO,	STATE_DISCONNECTINGUSER,CUserSession::DBMsg_GetSessionInfo_DisconnectingUser },
	{ CMD_GETSESSIONINFO,	STATE_CHECKMESSAGES,	CUserSession::DBMsg_GetSessionInfo_CheckMessages },
	{ CMD_FINDUSERS,		STATE_NORMAL,			CUserSession::DBMsg_FindUsers },
	{ CMD_GETINFO,			STATE_NORMAL,			CUserSession::DBMsg_GetInfo },
	{ CMD_ISAUTHED,			STATE_GETFRIENDINFO,	CUserSession::DBMsg_GetFriendInfo_IsAuthed },
	{ CMD_ISAUTHED,			STATE_REQUESTINGAUTH,	CUserSession::DBMsg_RequestAuth_IsAuthed },
	{ CMD_GETWATCHERS,		STATE_NORMAL,			CUserSession::DBMsg_GetWatchers },
	{ CMD_GETFRIENDLIST,	STATE_NORMAL,			CUserSession::DBMsg_GetFriendList },
	{ CMD_GETFRIENDSTATUS,	STATE_NORMAL,			CUserSession::DBMsg_GetFriendStatus },
	{ CMD_GETFRIENDSGAMESTATUS, STATE_NORMAL,		CUserSession::DBMsg_GetFriendsGameStatus },
	{ CMD_GETMESSAGE,		STATE_NORMAL,			CUserSession::DBMsg_GetMessage },
	{ CMD_DELETEMESSAGE,	STATE_NORMAL,			CUserSession::DBMsg_DeleteMessage },
};

//-----------------------------------------------------------------------------
// Purpose: Handles a user session receiving a network message
//-----------------------------------------------------------------------------
void CUserSession::ReceivedMessage(IReceiveMessage *userMsg, float currentTime)
{
	// mark the new access
	m_flLastAccess = currentTime;

	// update the user port
	if (userMsg->NetAddress().Port() != m_NetAddress.Port())
	{
		g_pConsole->Print(9, "++++ User port changed, %s -> %d\n", m_NetAddress.ToStaticString(), userMsg->NetAddress().Port());
		m_NetAddress.SetPort(userMsg->NetAddress().Port());
	}

	// send all the queued messages
	SendQueuedMessages();

	// simple dispatch
	// loop through the array and find the right message
	int arraySize = ARRAYSIZE(g_UserMsgDispatch);
	int msgName = userMsg->GetMsgID();
	for (int i = 0; i < arraySize; i++)
	{
		if (msgName == g_UserMsgDispatch[i].msgName)
		{
			(this->*g_UserMsgDispatch[i].msgFunc)( userMsg );
			return;
		}
	}

	// invalid message
	g_pConsole->Print(5, "* Unhandled message '%d'\n", msgName);
}

//-----------------------------------------------------------------------------
// Purpose: Increments the sessionID stamp
// Input  : baseSessionID - basic session id that occupies the top 16 bits
//			of the sessionID number
//-----------------------------------------------------------------------------
void CUserSession::CreateNewSessionID(int baseSessionID)
{
	// mask off the base id
	unsigned int numericID = (m_iSessionID & 0x0000FFFF);

	// incremement
	numericID++;

	// mask again (to remove any overflow)
	numericID &= 0x0000FFFF;

	// reset the base id
	m_iSessionID = numericID | (baseSessionID << 16);
}


//-----------------------------------------------------------------------------
// LOGGING INTO A SESSION
/*
	Client -> sends a message requesting a log in
			+ 'login'
				- 'uid'
				- 'password'
				- 'email'

	server -> allocates an new session
	       -> generates new sessionID and random challenge key
		   -> sends Client a reply challenging them to respond
			+ 'challenge'
				- 'sessionID'
				- 'challenge key'

	Client -> replies to the challenge
			+ 'response'
				- 'sessionID'
				- 'challenge key'

	server -> validates the player in the database to see if they exist
	       -> sends back a login ack if successful
			+ 'LoginOK'
				- 'sessionID'
		   -> sends status of friends to Client
		   -> sends Client status to friends
	
	Client -> enters logged in mode
		   -> displays friends status & messages

*/
//-----------------------------------------------------------------------------

//-----------------------------------------------------------------------------
// Purpose: Attempts to log a user into the server
//-----------------------------------------------------------------------------
bool CUserSession::ReceivedMsg_Login(IReceiveMessage *loginMsg)
{
	unsigned int userID;
	char szEmail[128];
	char szPassword[32];
	int iStatus;
	int iFirewallWindow = 0;

	loginMsg->ReadUInt("uid", userID);
	loginMsg->ReadString("email", szEmail, sizeof(szEmail));
	loginMsg->ReadString("password", szPassword, sizeof(szPassword));
	loginMsg->ReadInt("status", iStatus);
	loginMsg->ReadInt("FirewallWindow", iFirewallWindow);

	if (userID < 1)
	{
		g_pConsole->Print(4, "User (%s) with bad userID %d attempted to log into server\n", loginMsg->NetAddress().ToStaticString(), userID);
		return false;
	}

	// get our database handle
	m_pDB = g_pDataManager->TrackerUser(userID);

	// make sure this is a valid userID
	if (!m_pDB)
	{
		// bad user ID
		g_pConsole->Print(4, "User (%s) with bad userID %d attempted to log into server\n", loginMsg->NetAddress().ToStaticString(), m_iUserID);
		return false;
	}

	// if this user is already logged in to the server from this IP, override previous session
	CUserSession *oldUserSession = sessionmanager()->GetUserSessionByID(userID);
	if (oldUserSession)
	{
		if (oldUserSession->IP() == loginMsg->NetAddress().IP() || oldUserSession->State() != USERSESSION_ACTIVE)
		{
			// same IP address, simply kill the previous session and continue
			g_pConsole->Print(0, "Forcing off previous instance of user '%d'\n", m_iUserID);
			sessionmanager()->ForceDisconnectUser(userID);
		}
		else if (oldUserSession->State() == USERSESSION_ACTIVE)
		{
			// begin logging off the old user, quit ourselves
			oldUserSession->Logoff(true, false, true);
			return false;
		}
	}

	// save off data
	m_iUserID = userID;
	m_iStatus = iStatus;
	v_strncpy(m_szPassword, szPassword, sizeof(m_szPassword));
	m_iUserSessionState = USERSESSION_CONNECTING;
	m_NetAddress = loginMsg->NetAddress();
	m_flTimeoutDelay = (6 * 60.0f);	// 6 minute timeout by default
	if (iFirewallWindow)
	{
		m_flFirewallWindow = (float)iFirewallWindow;
	}
	else
	{
		// no firewall info sent, mark it as wide as possible
		m_flFirewallWindow = 999999.0f;
	}

	// set the current access time
	m_flLastAccess = sessionmanager()->GetCurrentTime();

	// build a reply
	ISendMessage *replyMsg = net()->CreateReply(TSVC_CHALLENGE, loginMsg );
	replyMsg->SetEncrypted(true);

	// session ID is 0 for this message, indicating a non-validated packet
	replyMsg->SetSessionID( 0 );

	// generate a random challenge key (that the Client will have to respond with correctly)
	m_iChallengeKey = RandomLong(1, MAX_RANDOM_RANGE);

	// write out the reply
	replyMsg->WriteUInt("sessionID", m_iSessionID);
	replyMsg->WriteInt("challenge", m_iChallengeKey);

	// this could be changed to unreliable for optimization
	SendMessage(replyMsg, NET_RELIABLE /*NET_UNRELIABLE*/);

//	g_pConsole->Print(4, "Sending challenge for login request from '%s'\n", szEmail);

	return true;
}

//-----------------------------------------------------------------------------
// Purpose: Response to a login challenge
//			If this response to the challenge is correct, then fully log the user
//			into the database
//			Invalid 'response' messages receive no reply (since it's then a security problem)
//-----------------------------------------------------------------------------
void CUserSession::ReceivedMsg_Response(IReceiveMessage *pMsg)
{
	unsigned int msgSessionID = pMsg->SessionID();
	unsigned int sessionID; 
	int challenge, status, buildNum, heartBeatRateMillis;
	pMsg->ReadInt("challenge", challenge);
	pMsg->ReadUInt("sessionID", sessionID);
	pMsg->ReadInt("status", status);
	pMsg->ReadInt("build", buildNum);
	pMsg->ReadInt("hrate", heartBeatRateMillis);
	
//	g_pConsole->Print(4, "Received Response from user (%d) sessionID (%d)\n", m_iUserID, m_iSessionID);

	// Validate
	if (msgSessionID != sessionID || m_iChallengeKey != challenge)
	{
		g_pConsole->Print(6, "Response ignored, invalid challenge or sessionID\n");
		m_iUserSessionState = USERSESSION_CONNECTFAILED;
		sessionmanager()->FreeUserSession(this);
		return;
	}

	// check build number

⌨️ 快捷键说明

复制代码 Ctrl + C
搜索代码 Ctrl + F
全屏模式 F11
切换主题 Ctrl + Shift + D
显示快捷键 ?
增大字号 Ctrl + =
减小字号 Ctrl + -