/*
* The ManaPlus Client
* Copyright (C) 2004-2009 The Mana World Development Team
* Copyright (C) 2009-2010 The Mana Developers
* Copyright (C) 2011-2013 The ManaPlus Developers
*
* This file is part of The ManaPlus Client.
*
* 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; either version 2 of the License, or
* any later version.
*
* 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, see <http://www.gnu.org/licenses/>.
*/
#include "net/ea/network.h"
#include "configuration.h"
#include "logger.h"
#include "net/messagehandler.h"
#include "net/eathena/protocol.h"
#include "utils/gettext.h"
#include <assert.h>
#include <sstream>
#include "debug.h"
namespace Ea
{
const unsigned int BUFFER_SIZE = 1000000;
const unsigned int BUFFER_LIMIT = 930000;
int networkThread(void *data)
{
Network *const network = static_cast<Network *const>(data);
if (!network || !network->realConnect())
return -1;
network->receive();
return 0;
}
Network::Network() :
mSocket(nullptr),
mInBuffer(new char[BUFFER_SIZE]),
mOutBuffer(new char[BUFFER_SIZE]),
mInSize(0),
mOutSize(0),
mToSkip(0),
mState(IDLE),
mWorkerThread(nullptr),
mMutex(SDL_CreateMutex()),
mSleep(config.getIntValue("networksleep"))
{
TcpNet::init();
}
Network::~Network()
{
if (mState != IDLE && mState != NET_ERROR)
disconnect();
SDL_DestroyMutex(mMutex);
mMutex = nullptr;
delete []mInBuffer;
delete []mOutBuffer;
TcpNet::quit();
}
bool Network::connect(ServerInfo server)
{
if (mState != IDLE && mState != NET_ERROR)
{
logger->log1("Tried to connect an already connected socket!");
assert(false);
return false;
}
if (server.hostname.empty())
{
setError(_("Empty address given to Network::connect()!"));
return false;
}
logger->log("Network::Connecting to %s:%i",
server.hostname.c_str(), server.port);
mServer.hostname = server.hostname;
mServer.port = server.port;
// Reset to sane values
mOutSize = 0;
mInSize = 0;
mToSkip = 0;
mState = CONNECTING;
mWorkerThread = SDL_CreateThread(networkThread, this);
if (!mWorkerThread)
{
setError("Unable to create network worker thread");
return false;
}
return true;
}
void Network::disconnect()
{
mState = IDLE;
if (mWorkerThread && SDL_GetThreadID(mWorkerThread))
{
SDL_WaitThread(mWorkerThread, nullptr);
mWorkerThread = nullptr;
}
if (mSocket)
{
TcpNet::closeSocket(mSocket);
mSocket = nullptr;
if (mSleep > 0)
SDL_Delay(mSleep);
}
}
void Network::flush()
{
if (!mOutSize || mState != CONNECTED)
return;
int ret;
SDL_mutexP(mMutex);
ret = TcpNet::send(mSocket, mOutBuffer, mOutSize);
DEBUGLOG(std::string("Send ").append(toString(mOutSize)).append(" bytes"));
if (ret < static_cast<int>(mOutSize))
{
setError("Error in TcpNet::send(): " +
std::string(TcpNet::getError()));
}
mOutSize = 0;
SDL_mutexV(mMutex);
}
void Network::skip(int len)
{
SDL_mutexP(mMutex);
mToSkip += len;
if (!mInSize)
{
SDL_mutexV(mMutex);
return;
}
if (mInSize >= mToSkip)
{
mInSize -= mToSkip;
memmove(mInBuffer, mInBuffer + mToSkip, mInSize);
mToSkip = 0;
}
else
{
mToSkip -= mInSize;
mInSize = 0;
}
SDL_mutexV(mMutex);
}
bool Network::realConnect()
{
IPaddress ipAddress;
if (TcpNet::resolveHost(&ipAddress, mServer.hostname.c_str(),
mServer.port) == -1)
{
std::string errorMessage = std::string(_("Unable to resolve host \""))
.append(mServer.hostname).append("\"");
setError(errorMessage);
logger->log("TcpNet::ResolveHost: %s", errorMessage.c_str());
return false;
}
mState = CONNECTING;
mSocket = TcpNet::open(&ipAddress);
if (!mSocket)
{
logger->log("Error in TcpNet::open(): %s", TcpNet::getError());
setError(TcpNet::getError());
return false;
}
logger->log("Network::Started session with %s:%i",
ipToString(ipAddress.host), ipAddress.port);
mState = CONNECTED;
return true;
}
void Network::receive()
{
TcpNet::SocketSet set;
if (!(set = TcpNet::allocSocketSet(1)))
{
setError("Error in TcpNet::allocSocketSet(): " +
std::string(TcpNet::getError()));
return;
}
if (TcpNet::addSocket(set, mSocket) == -1)
{
setError("Error in TcpNet::addSocket(): " +
std::string(TcpNet::getError()));
}
while (mState == CONNECTED)
{
// TODO Try to get this to block all the time while still being able
// to escape the loop
const int numReady = TcpNet::checkSockets(
set, (static_cast<uint32_t>(500)));
int ret;
switch (numReady)
{
case -1:
logger->log1("Error: TcpNet::checkSockets");
// FALLTHROUGH
case 0:
break;
case 1:
// Receive data from the socket
SDL_mutexP(mMutex);
if (mInSize > BUFFER_LIMIT)
{
SDL_mutexV(mMutex);
SDL_Delay(100);
continue;
}
ret = TcpNet::recv(mSocket, mInBuffer + mInSize,
BUFFER_SIZE - mInSize);
if (!ret)
{
// We got disconnected
mState = IDLE;
logger->log1("Disconnected.");
}
else if (ret < 0)
{
setError(_("Connection to server terminated. ") +
std::string(TcpNet::getError()));
}
else
{
// DEBUGLOG("Receive " + toString(ret) + " bytes");
mInSize += ret;
if (mToSkip)
{
if (mInSize >= mToSkip)
{
mInSize -= mToSkip;
memmove(mInBuffer, mInBuffer + mToSkip, mInSize);
mToSkip = 0;
}
else
{
mToSkip -= mInSize;
mInSize = 0;
}
}
}
SDL_mutexV(mMutex);
break;
default:
// more than one socket is ready..
// this should not happen since we only listen once socket.
std::stringstream errorStream;
errorStream << "Error in TcpNet::recv(), " << numReady
<< " sockets are ready: " << TcpNet::getError();
setError(errorStream.str());
break;
}
}
if (TcpNet::delSocket(set, mSocket) == -1)
logger->log("Error in TcpNet::delSocket(): %s", TcpNet::getError());
TcpNet::freeSocketSet(set);
}
void Network::setError(const std::string &error)
{
logger->log("Network error: %s", error.c_str());
mError = error;
mState = NET_ERROR;
}
uint16_t Network::readWord(const int pos) const
{
#if SDL_BYTEORDER == SDL_BIG_ENDIAN
return SDL_Swap16((*(uint16_t*)(mInBuffer + (pos))));
#else
return (*reinterpret_cast<uint16_t*>(mInBuffer + (pos)));
#endif
}
void Network::fixSendBuffer()
{
if (mOutSize > BUFFER_LIMIT)
{
if (mState != CONNECTED)
mOutSize = 0;
else
flush();
}
}
} // namespace EAthena