/* * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; version 2 of the License. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA * * Authors: Manuel Mausz (manuel@mausz.at) * Christian Raschko (c.raschko@netcore.at) */ // Header #include "tsconnectionthread.h" #include "wxbufferex.h" //Libraries #include #include #include #include #include #include WX_DEFINE_LIST(TSQueue); IMPLEMENT_CLASS(TSConnectionThread, wxObject) //------------------------------------------------------------------------------ // Default CTor, Initializes the object TSConnectionThread::TSConnectionThread(TSClient *client, wxMutex *mutex) : wxThread(wxTHREAD_JOINABLE) { m_pMutex = mutex; Create(); m_pClient = client; m_ppCommands = new TSpCommandArray; m_pMySock = new wxDatagramSocket(m_ClientAddr); m_InQueue = new TSQueue; m_OutQueue = new TSQueue; m_Exit = false; m_Error = false; m_Timer = false; m_pCurCmd = NULL; m_LocalCmd = true; m_Connected = false; m_pMySock = NULL; //register known commands RegisterCommand(new TSCmdSendLogin); RegisterCommand(new TSCmdRecvServer); RegisterCommand(new TSCmdSendDefault); RegisterCommand(new TSCmdRecvChannel); RegisterCommand(new TSCmdRecvUser); RegisterCommand(new TSCmdRecvAck); RegisterCommand(new TSCmdSendAck); RegisterCommand(new TSCmdRecvUrl); RegisterCommand(new TSCmdSendPing); RegisterCommand(new TSCmdRecvPing); RegisterCommand(new TSCmdSendLogout); RegisterCommand(new TSCmdRecvLogout); RegisterCommand(new TSCmdRecvAddPlayer); RegisterCommand(new TSCmdRecvPlayerFlags); RegisterCommand(new TSCmdRecvAddChannel); RegisterCommand(new TSCmdRecvMovePlayer); RegisterCommand(new TSCmdRecvMovePlayer2); RegisterCommand(new TSCmdRecvDelChannel); RegisterCommand(new TSCmdRecvServerUpdate); RegisterCommand(new TSCmdSendAddChannel); RegisterCommand(new TSCmdSendDelChannel); RegisterCommand(new TSCmdSendMovePlayer); RegisterCommand(new TSCmdSendSetChannelPassword); RegisterCommand(new TSCmdSendSetChannelName); RegisterCommand(new TSCmdSendSetChannelTopic); RegisterCommand(new TSCmdSendSetChannelDescription); RegisterCommand(new TSCmdSendSetChannelMaxPlayers); RegisterCommand(new TSCmdSendSetChannelFlagsCodec); RegisterCommand(new TSCmdSendSetChannelOrder); RegisterCommand(new TSCmdRecvSetChannelName); RegisterCommand(new TSCmdRecvSetChannelTopic); RegisterCommand(new TSCmdRecvSetChannelDescription); RegisterCommand(new TSCmdRecvSetChannelMaxPlayers); RegisterCommand(new TSCmdRecvSetChannelFlagsCodec); RegisterCommand(new TSCmdRecvSetChannelOrder); RegisterCommand(new TSCmdSendKickPlayer); RegisterCommand(new TSCmdRecvChannelPasswordChanged); //for all unknown commands RegisterCommand(new TSCmdRecvUnknown); } //------------------------------------------------------------------------------ //Default DTor TSConnectionThread::~TSConnectionThread() { /* DON'T USE THIS */ } //------------------------------------------------------------------------------ // thread execution starts here wxThread::ExitCode TSConnectionThread::Entry() { wxLogMessage(_T("TSConnection: thread started.")); m_ClientAddr.AnyAddress(); m_ClientAddr.Service(0); m_ServerAddr.Hostname(m_pClient->GetServer()->GetServerAddress()); m_ServerAddr.Service(m_pClient->GetServer()->GetPort()); //no methode to reset socket if (m_pMySock) { wxASSERT(m_pMySock != NULL); m_pMySock->Destroy(); m_pMySock = NULL; } m_pMySock = new wxDatagramSocket(m_ClientAddr); if(m_pMySock->Error()) { wxLogError(_T("creating socket, terminating thread.")); return wxThread::ExitCode(EXIT_FAILURE); } m_pMySock->SetTimeout(SOCKET_TIMEOUT); //empty dgram to initialize and remember peer //allows us to use wxSocketOutputStream afterwards m_pMySock->SendTo(m_ServerAddr, NULL, 0); if(!MainLoop()) { wxLogDebug(_T("Error: %s"), GetLastError().c_str()); return wxThread::ExitCode(EXIT_FAILURE); } return wxThread::ExitCode(EXIT_SUCCESS); } //------------------------------------------------------------------------------ // and stops here void TSConnectionThread::OnExit() { wxLogDebug(_T("TSConnection: thread terminated.")); //do the necessary cleanup for(size_t i = 0; i < m_ppCommands->GetCount(); i++) delete m_ppCommands->Item(i); m_InQueue->DeleteContents(true); m_OutQueue->DeleteContents(true); wxASSERT(m_InQueue != NULL); delete m_InQueue; m_InQueue = NULL; wxASSERT(m_OutQueue != NULL); delete m_OutQueue; m_OutQueue = NULL; wxASSERT(m_ppCommands != NULL); delete m_ppCommands; m_ppCommands = NULL; wxASSERT(m_pMySock != NULL); m_pMySock->Destroy(); m_pMySock = NULL; if(m_pCurCmd != NULL) { delete m_pCurCmd; m_pCurCmd = NULL; } } //------------------------------------------------------------------------------ // SyncSendCmdError bool TSConnectionThread::SyncSendCmdLastError() { //wxLogDebug(_T("SyncSendCmdLastError")); m_LocalCmd = true; m_SWTimeout.Start(); m_SWTimeout.Pause(); TSCmd *cmd = m_pClient->GetSync(); cmd->error = true; cmd->lasterror = m_LastError; cmd->executed = true; m_pClient->m_Lock = false; return true; } //------------------------------------------------------------------------------ // Sync with TSClient and send command bool TSConnectionThread::SyncSendCmd() { //wxLogDebug(_T("SyncSendCmd")); TSCmd *cmd = m_pClient->GetSync(); m_pCurCmd = cmd; if(cmd->id == 0) { SetLastError(_T("no command id")); SyncSendCmdLastError(); return true; } if(cmd->id != TS_CMD_SEND_LOGIN && cmd->id != TS_CMD_SEND_DEFAULT && !m_Connected) { SetLastError(_T("no connection to server")); SyncSendCmdLastError(); return true; } m_LocalCmd = false; SendCommand(cmd); return true; } //------------------------------------------------------------------------------ // Register a Command, the class takes care of this pointer. void TSConnectionThread::RegisterCommand(TSCommand *cmd) { wxASSERT(cmd != NULL); m_ppCommands->Add(cmd); } //------------------------------------------------------------------------------ // Executes a command. bool TSConnectionThread::ExecuteCommand(wxInputStream &istrm, wxOutputStream &ostrm, TSCmd *cmd) { wxLogDebug(_T("ExecuteCommand: 0x%X"), cmd->id); for(size_t i = 0; i < m_ppCommands->GetCount(); i++) { if(m_ppCommands->Item(i)->IsCommand(cmd->id)) { if(m_ppCommands->Item(i)->ProcessCommand(istrm, ostrm, cmd)) return true; else { SetLastError(m_ppCommands->Item(i)->GetLastError()); return false; } } } #ifdef IGNORE_UNKNOWN_COMMANDS //used for unknown commands cmd->id = TS_CMD_RECV_UNKNOWN; return ExecuteCommand(istrm, ostrm, cmd); #else SetLastError(_T("invalid command")); return false; #endif } //------------------------------------------------------------------------------ // Dumps object. void TSConnectionThread::Dump(wxOutputStream &ostrm) const { wxTextOutputStream out(ostrm); out << _T("Object: TSConnection (") << wxString::Format(_T("0x%X"), this) << _T(")") << endl; out << _T("-wxString m_LastError: ") << m_LastError << endl; } //------------------------------------------------------------------------------ // send command. bool TSConnectionThread::SendOutputCommand(TSCmd *cmd) { //wxLogDebug(_T("SendOutputCommand: 0x%X"), cmd->id); if(!SendCommandToSocket(cmd)) return false; /* this m_pCurCmd code possibly contains a memleak * the lines below solves ~95% of them */ /*if(m_pCurCmd != NULL) { delete m_pCurCmd; m_pCurCmd = NULL; }*/ switch(cmd->id) { case TS_CMD_SEND_LOGIN: AddInputCommand(new TSCmd(TS_CMD_RECV_SERVER, cmd)); m_pCurCmd = new TSCmd(TS_CMD_RECV_SERVER, cmd); break; case TS_CMD_SEND_DEFAULT: AddInputCommand(new TSCmd(TS_CMD_RECV_ACK, cmd)); m_pCurCmd = new TSCmd(TS_CMD_RECV_ACK, cmd); break; case TS_CMD_SEND_PING: AddInputCommand(new TSCmd(TS_CMD_RECV_PING, cmd)); m_pCurCmd = new TSCmd(TS_CMD_RECV_PING, cmd); break; case TS_CMD_SEND_LOGOUT: AddInputCommand(new TSCmd(TS_CMD_RECV_ACK, cmd)); m_pCurCmd = new TSCmd(TS_CMD_RECV_ACK, cmd); break; case TS_CMD_SEND_ADD_CHANNEL: AddInputCommand(new TSCmd(TS_CMD_RECV_ACK, cmd)); m_pCurCmd = new TSCmd(TS_CMD_RECV_ACK, cmd); break; case TS_CMD_SEND_DEL_CHANNEL: AddInputCommand(new TSCmd(TS_CMD_RECV_ACK, cmd)); m_pCurCmd = new TSCmd(TS_CMD_RECV_ACK, cmd); break; case TS_CMD_SEND_MOVE_PLAYER: AddInputCommand(new TSCmd(TS_CMD_RECV_ACK, cmd)); m_pCurCmd = new TSCmd(TS_CMD_RECV_ACK, cmd); break; case TS_CMD_SEND_SET_CHANNEL_PASSWORD: AddInputCommand(new TSCmd(TS_CMD_RECV_ACK, cmd)); m_pCurCmd = new TSCmd(TS_CMD_RECV_ACK, cmd); break; case TS_CMD_SEND_SET_CHANNEL_NAME: AddInputCommand(new TSCmd(TS_CMD_RECV_ACK, cmd)); m_pCurCmd = new TSCmd(TS_CMD_RECV_ACK, cmd); break; case TS_CMD_SEND_SET_CHANNEL_TOPIC: AddInputCommand(new TSCmd(TS_CMD_RECV_ACK, cmd)); m_pCurCmd = new TSCmd(TS_CMD_RECV_ACK, cmd); break; case TS_CMD_SEND_SET_CHANNEL_DESCRIPTION: AddInputCommand(new TSCmd(TS_CMD_RECV_ACK, cmd)); m_pCurCmd = new TSCmd(TS_CMD_RECV_ACK, cmd); break; case TS_CMD_SEND_SET_CHANNEL_MAXPLAYERS: AddInputCommand(new TSCmd(TS_CMD_RECV_ACK, cmd)); m_pCurCmd = new TSCmd(TS_CMD_RECV_ACK, cmd); break; case TS_CMD_SEND_SET_CHANNEL_FLAGSCODEC: AddInputCommand(new TSCmd(TS_CMD_RECV_ACK, cmd)); m_pCurCmd = new TSCmd(TS_CMD_RECV_ACK, cmd); break; case TS_CMD_SEND_SET_CHANNEL_ORDER: AddInputCommand(new TSCmd(TS_CMD_RECV_ACK, cmd)); m_pCurCmd = new TSCmd(TS_CMD_RECV_ACK, cmd); break; case TS_CMD_SEND_KICK_PLAYER: AddInputCommand(new TSCmd(TS_CMD_RECV_ACK, cmd)); m_pCurCmd = new TSCmd(TS_CMD_RECV_ACK, cmd); break; case TS_CMD_SEND_ACK: break; } return true; } //------------------------------------------------------------------------------ // SendQueuedCommand. bool TSConnectionThread::SendQueuedCommand() { //wxLogDebug(_T("SendQueuedCommand")); wxTSQueueNode *node = m_OutQueue->GetFirst(); TSCmd *t = node->GetData(); if(!SendOutputCommand(t)) return false; delete t; if(!m_OutQueue->DeleteNode(node)) { SetLastError(_T("deleting node")); return false; } return true; } //------------------------------------------------------------------------------ // Send command. bool TSConnectionThread::SendCommand(TSCmd *cmd) { //wxLogDebug(_T("SendCommand: 0x%X"), cmd->id); if(m_pCurCmd == NULL) m_pCurCmd = cmd; m_SWTimeout.Start(); if(!SendOutputCommand(cmd)) return false; m_Retries = 0; return true; } //------------------------------------------------------------------------------ // Send command to socket. bool TSConnectionThread::SendCommandToSocket(TSCmd *cmd) { //wxLogDebug(_T("SendCommandToSocket: 0x%X"), cmd->id); wxLogVerbose(_T("TSClient: command %#x sent"),cmd->id); wxSocketOutputStream so(*m_pMySock); wxBufferedOutputStream out(so); wxSocketInputStream si(*m_pMySock); wxBufferedInputStream in(si); if(!ExecuteCommand(in, out, cmd)) { SetLastError(_T("execute command")); return false; } out.Sync(); return true; } //------------------------------------------------------------------------------ // Add command. void TSConnectionThread::AddOutputCommand(TSCmd *cmd) { //wxLogDebug(_T("AddOutputCommand: 0x%X"), cmd->id); m_OutQueue->Append(cmd); } //------------------------------------------------------------------------------ // CheckInputQueue bool TSConnectionThread::CheckInputQueue(TSCmd *cmd) { wxLogDebug(_T("CheckInputQueue: 0x%X, 0x%X"), cmd->id, cmd->pktid); switch(cmd->id) { case TS_CMD_RECV_CHANNEL: AddOutputCommand(new TSCmd(TS_CMD_SEND_ACK, cmd)); break; case TS_CMD_RECV_USER: AddOutputCommand(new TSCmd(TS_CMD_SEND_ACK, cmd)); break; case TS_CMD_RECV_URL: AddOutputCommand(new TSCmd(TS_CMD_SEND_ACK, cmd)); wxLogVerbose(_T("TSClient: connected")); m_Connected = true; m_Timer = true; break; case TS_CMD_RECV_LOGOUT: AddOutputCommand(new TSCmd(TS_CMD_SEND_ACK, cmd)); break; case TS_CMD_RECV_UNKNOWN: AddOutputCommand(new TSCmd(TS_CMD_SEND_ACK, cmd)); break; case TS_CMD_RECV_ACK: break; case TS_CMD_RECV_SERVER: //AddOutputCommand(new TSCmd(TS_CMD_SEND_DEFAULT, cmd)); break; case TS_CMD_RECV_ADD_PLAYER: AddOutputCommand(new TSCmd(TS_CMD_SEND_ACK, cmd)); break; case TS_CMD_RECV_PLAYER_FLAGS: AddOutputCommand(new TSCmd(TS_CMD_SEND_ACK, cmd)); break; case TS_CMD_RECV_ADD_CHANNEL: AddOutputCommand(new TSCmd(TS_CMD_SEND_ACK, cmd)); break; case TS_CMD_RECV_MOVE_PLAYER: AddOutputCommand(new TSCmd(TS_CMD_SEND_ACK, cmd)); break; case TS_CMD_RECV_MOVE_PLAYER2: AddOutputCommand(new TSCmd(TS_CMD_SEND_ACK, cmd)); break; case TS_CMD_RECV_DEL_CHANNEL: AddOutputCommand(new TSCmd(TS_CMD_SEND_ACK, cmd)); break; case TS_CMD_RECV_SERVER_UPDATE: AddOutputCommand(new TSCmd(TS_CMD_SEND_ACK, cmd)); // force reconnect for the sake of consistency AddOutputCommand(new TSCmd(TS_CMD_SEND_LOGOUT, cmd)); break; case TS_CMD_RECV_SET_CHANNEL_NAME: AddOutputCommand(new TSCmd(TS_CMD_SEND_ACK, cmd)); break; case TS_CMD_RECV_SET_CHANNEL_TOPIC: AddOutputCommand(new TSCmd(TS_CMD_SEND_ACK, cmd)); break; case TS_CMD_RECV_SET_CHANNEL_DESCRIPTION: AddOutputCommand(new TSCmd(TS_CMD_SEND_ACK, cmd)); break; case TS_CMD_RECV_SET_CHANNEL_MAXPLAYERS: AddOutputCommand(new TSCmd(TS_CMD_SEND_ACK, cmd)); break; case TS_CMD_RECV_SET_CHANNEL_FLAGSCODEC: AddOutputCommand(new TSCmd(TS_CMD_SEND_ACK, cmd)); break; case TS_CMD_RECV_SET_CHANNEL_ORDER: AddOutputCommand(new TSCmd(TS_CMD_SEND_ACK, cmd)); break; case TS_CMD_RECV_CHANNEL_PASSWORD_CHANGED: AddOutputCommand(new TSCmd(TS_CMD_SEND_ACK, cmd)); break; case TS_CMD_RECV_PING: break; default: wxLogWarning(_T("input queue: invalid command %#x, ignoring"), cmd->id); break; } for(wxTSQueueNode *node = m_InQueue->GetFirst(); node != NULL; node = node->GetNext()) { TSCmd *cur = node->GetData(); //cmd found if(cur->id == cmd->id) { delete cur; if(!m_InQueue->DeleteNode(node)) { SetLastError(_T("deleting node")); return false; } //return true; } if(m_pCurCmd->id == cmd->id) { m_pMutex->Lock(); wxLogDebug(_T("found in queue: 0x%X"), cmd->id); delete m_pCurCmd; m_pCurCmd = NULL; if (!m_LocalCmd) { m_pClient->m_Lock = false; m_pClient->GetSync()->executed = true; } m_LocalCmd = true; m_SWTimeout.Pause(); m_pMutex->Unlock(); return true; } } return true; } //------------------------------------------------------------------------------ // ReadQueuedCommand. bool TSConnectionThread::RecvCommand() { wxSocketOutputStream so(*m_pMySock); wxMemoryBufferEx buf(1024); m_pMySock->RecvFrom(m_ServerAddr, buf.GetData(), 1024); buf.SetDataLen(m_pMySock->LastCount()); wxMemoryInputStream in(buf.GetData(), buf.GetDataLen()); wxDataInputStream data(in); TSCmd cmd; cmd.id = data.Read32(); //read id data.Read32(); //Session id data.Read32(); //Player id cmd.pktid = data.Read32(); //Packet count cmd.client = m_pClient; in.SeekI(0); wxLogVerbose(_T("TSClient: command %#x received"), cmd.id); // dirty workaround for crappy teamspeak protocol, which splits off pakets if(cmd.id == TS_CMD_RECV_CHANNEL) { in.SeekI(-1, wxFromEnd); if(data.Read8() != 0) { wxMemoryBuffer buf_b2(1024); m_pMySock->RecvFrom(m_ServerAddr, buf_b2.GetData(), 1024); buf_b2.SetDataLen(m_pMySock->LastCount()); wxMemoryInputStream in_b2(buf_b2.GetData(), buf_b2.GetDataLen()); wxDataInputStream data_b2(in_b2); if(data_b2.Read32() != cmd.id) { SetLastError(_T("invalid command, perhaps wrong paket order (udp?)")); return false; } data_b2.Read32(); //Session id data_b2.Read32(); //Player id cmd.pktid = data_b2.Read32(); //Packet count data_b2.Read32(); //read unknown data_b2.Read32(); //CRC // send acknowledge for 2nd packet if(!CheckInputQueue(&cmd)) return false; // append bytes while(!in_b2.Eof()) buf.AppendByte(in_b2.GetC()); } in.SeekI(0); } // update streams (dirty, but copy-operator is private) wxMemoryInputStream in2(buf.GetData(), buf.GetDataLen()); wxDataInputStream data2(in2); if(!ExecuteCommand(in2, so, &cmd)) { switch(cmd.id) { case TS_CMD_RECV_SERVER: SyncSendCmdLastError(); break; } return false; } if(!CheckInputQueue(&cmd)) return false; return true; } //------------------------------------------------------------------------------ // Add command. void TSConnectionThread::AddInputCommand(TSCmd *cmd) { //wxLogDebug(_T("AddInputCommand: 0x%X"), cmd->id); m_InQueue->Append(cmd); } //------------------------------------------------------------------------------ // StartMainLoop bool TSConnectionThread::MainLoop() { wxStopWatch sw; sw.Start(); m_Retries = 0; while(1) { //Is new command available m_pMutex->Lock(); if((m_pClient->GetSync() != NULL) && (m_pCurCmd == NULL) && (m_pClient->m_Lock) && (m_OutQueue->GetCount() == 0)) { m_SWTimeout.Start(); SyncSendCmd(); } m_pMutex->Unlock(); //Is some data in queue if(m_OutQueue->GetCount() != 0) { if(!SendQueuedCommand()) return false; } //Is new data available if(m_pMySock->IsData()) { if(!RecvCommand()) return false; } //Send ping if((sw.Time() >= PING_INTERMITTENT) && m_Timer) { sw.Start(); TSCmd tmp; tmp.client = m_pClient; if(m_pCurCmd == NULL) SendCommand(new TSCmd(TS_CMD_SEND_PING, &tmp)); } //Check for timeout if(m_SWTimeout.Time() > CMD_TIMEOUT) { SetLastError(_T("command timeout")); m_pCurCmd = NULL; if(!m_LocalCmd) { SyncSendCmdLastError(); } m_LocalCmd = true; //reset timer m_SWTimeout.Start(); m_SWTimeout.Pause(); wxLogVerbose(_T("TSClient: disconnected")); m_Connected = false; /* if(++m_Retries <= MAX_RETRIES) { m_Connected = false; } */ return false; } //Check if we should delete our self if(TestDestroy()) return true; //If an error occurred, end thread if(m_Error) return false; //If m_Exit is set, end thread if(m_Exit) return true; Sleep(10); } }