cyphesis/common/AtlasStreamClient.cpp
2015-05-24 11:25:08 +02:00

628 lines
16 KiB
C++

// Cyphesis Online RPG Server and AI Engine
// Copyright (C) 2009 Alistair Riddoch
//
// 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
// (at your option) 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, write to the Free Software Foundation,
// Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif // HAVE_CONFIG_H
#include "common/AtlasStreamClient.h"
#include "common/ClientTask.h"
#include "common/system.h"
#include "common/debug.h"
#include <Atlas/Codec.h>
#include <Atlas/Objects/Anonymous.h>
#include <Atlas/Objects/Encoder.h>
#include <Atlas/Objects/SmartPtr.h>
#include <Atlas/Net/Stream.h>
#ifdef _WIN32
#undef DATADIR
#endif // _WIN32
#include <boost/asio/deadline_timer.hpp>
#include <cstdio>
#include <iostream>
using Atlas::Message::Element;
using Atlas::Message::ListType;
using Atlas::Message::MapType;
using Atlas::Objects::Root;
using Atlas::Objects::Entity::Anonymous;
using Atlas::Objects::Operation::Create;
using Atlas::Objects::Operation::Login;
using Atlas::Objects::Operation::RootOperation;
using namespace boost::asio;
StreamClientSocketBase::StreamClientSocketBase(boost::asio::io_service& io_service, std::function<void()>& dispatcher)
: m_io_service(io_service), mDispatcher(dispatcher), m_ios(&mBuffer), m_codec(nullptr), m_encoder(nullptr), m_is_connected(false)
{
}
StreamClientSocketBase::~StreamClientSocketBase()
{
delete m_encoder;
delete m_codec;
}
std::iostream& StreamClientSocketBase::getIos()
{
return m_ios;
}
int StreamClientSocketBase::negotiate(Atlas::Objects::ObjectsDecoder& decoder)
{
Atlas::Net::StreamConnect conn("cyphesis_client", m_ios);
while (conn.getState() == Atlas::Net::StreamConnect::IN_PROGRESS) {
write();
auto dataReceived = read_blocking();
conn.poll(dataReceived > 0);
}
if (conn.getState() == Atlas::Net::StreamConnect::FAILED) {
std::cerr << "Failed to negotiate" << std::endl;
return -1;
}
m_codec = conn.getCodec(decoder);
if (!m_codec) {
return -1;
}
m_encoder = new Atlas::Objects::ObjectsEncoder(*m_codec);
m_codec->streamBegin();
do_read();
return 0;
}
Atlas::Codec& StreamClientSocketBase::getCodec()
{
return *m_codec;
}
Atlas::Objects::ObjectsEncoder& StreamClientSocketBase::getEncoder()
{
return *m_encoder;
}
int StreamClientSocketBase::poll(const boost::posix_time::time_duration& duration) {
return poll(duration, []()->bool{return false;});
}
int StreamClientSocketBase::poll(const boost::posix_time::time_duration& duration, const std::function<bool()> exitCheckerFn)
{
bool hasExpired = false;
bool isCancelled = false;
deadline_timer timer(m_io_service);
timer.expires_from_now(duration);
timer.async_wait([&](boost::system::error_code ec){
if (!ec) {
hasExpired = true;
} else {
isCancelled = true;
}
});
//We'll try to only run one handler each polling. Either our timer gets called, or one of the network handlers.
//The reason for this loop is that when we cancel the timer we need to poll run handlers until the timer handler
//has been run, since it references locally scoped variables.
while (!hasExpired && !isCancelled && !exitCheckerFn()) {
m_io_service.run_one();
//Check if we didn't run the timer handler; if so we should cancel it and then keep on polling until
//it's been run.
if (!hasExpired && !isCancelled) {
timer.cancel();
}
}
if (!m_is_connected) {
return -1;
}
if (hasExpired) {
return 1;
}
return 0;
}
TcpStreamClientSocket::TcpStreamClientSocket(boost::asio::io_service& io_service, std::function<void()>& dispatcher, boost::asio::ip::tcp::endpoint endpoint)
: StreamClientSocketBase(io_service, dispatcher), m_socket(io_service)
{
m_socket.connect(endpoint);
m_is_connected = true;
}
void TcpStreamClientSocket::do_read()
{
m_socket.async_read_some(mReadBuffer.prepare(read_buffer_size),
[this](boost::system::error_code ec, std::size_t length)
{
if (!ec)
{
mReadBuffer.commit(length);
this->m_ios.rdbuf(&mReadBuffer);
m_codec->poll();
this->m_ios.rdbuf(&mBuffer);
mDispatcher();
this->do_read();
} else {
m_is_connected = false;
}
});
}
int TcpStreamClientSocket::read_blocking()
{
if (!m_socket.is_open()) {
return -1;
}
if (m_socket.available() > 0) {
auto received = m_socket.read_some(mBuffer.prepare(m_socket.available()));
if (received > 0) {
mBuffer.commit(received);
}
return received;
}
return 0;
}
int TcpStreamClientSocket::write()
{
if (!m_socket.is_open()) {
return -1;
}
if (mBuffer.size() == 0) {
return 0;
}
auto size = boost::asio::write(m_socket, mBuffer.data(),
boost::asio::transfer_all());
mBuffer.consume(size);
return size;
}
LocalStreamClientSocket::LocalStreamClientSocket(boost::asio::io_service& io_service, std::function<void()>& dispatcher, boost::asio::local::stream_protocol::endpoint endpoint)
: StreamClientSocketBase(io_service, dispatcher), m_socket(io_service)
{
m_socket.connect(endpoint);
m_is_connected = true;
}
void LocalStreamClientSocket::do_read()
{
m_socket.async_read_some(mReadBuffer.prepare(read_buffer_size),
[this](boost::system::error_code ec, std::size_t length)
{
if (!ec)
{
mReadBuffer.commit(length);
this->m_ios.rdbuf(&mReadBuffer);
m_codec->poll();
this->m_ios.rdbuf(&mBuffer);
mDispatcher();
this->do_read();
} else {
m_is_connected = false;
}
});
}
int LocalStreamClientSocket::read_blocking()
{
if (!m_socket.is_open()) {
return -1;
}
if (m_socket.available() > 0) {
int received = m_socket.read_some(mBuffer.prepare(m_socket.available()));
if (received > 0) {
mBuffer.commit(received);
}
return received;
}
return 0;
}
int LocalStreamClientSocket::write()
{
if (!m_socket.is_open()) {
return -1;
}
if (mBuffer.size() == 0) {
return 0;
}
auto size = boost::asio::write(m_socket, mBuffer.data(),
boost::asio::transfer_all());
mBuffer.consume(size);
return size;
}
void AtlasStreamClient::output(const Element & item, int depth) const
{
output_element(std::cout, item, depth);
}
void AtlasStreamClient::output(const Root & ent) const
{
MapType entmap = ent->asMessage();
MapType::const_iterator Iend = entmap.end();
for (MapType::const_iterator I = entmap.begin(); I != Iend; ++I) {
const Element & item = I->second;
std::cout << std::string(spacing(), ' ') << I->first << ": ";
output(item, 1);
std::cout << std::endl;
}
}
/// \brief Function call from the base class when an object arrives from the
/// server
///
/// @param obj Object that has arrived from the server
void AtlasStreamClient::objectArrived(const Root & obj)
{
RootOperation op = Atlas::Objects::smart_dynamic_cast<RootOperation>(obj);
if (!op.isValid()) {
std::cerr << "ERROR: Non op object received from server"
<< std::endl << std::flush;;
if (!obj->isDefaultParents() && !obj->getParents().empty()) {
std::cerr << "NOTICE: Unexpected object has parent "
<< obj->getParents().front()
<< std::endl << std::flush;
}
if (!obj->isDefaultObjtype()) {
std::cerr << "NOTICE: Unexpected object has objtype "
<< obj->getObjtype()
<< std::endl << std::flush;
}
return;
}
mOps.push_back(op);
}
void AtlasStreamClient::dispatch()
{
for (auto& op : mOps) {
operation(op);
}
mOps.clear();
}
void AtlasStreamClient::operation(const RootOperation & op)
{
if (m_currentTask != 0) {
OpVector res;
m_currentTask->operation(op, res);
OpVector::const_iterator Iend = res.end();
for (OpVector::const_iterator I = res.begin(); I != Iend; ++I) {
send(*I);
}
if (m_currentTask->isComplete()) {
delete m_currentTask;
m_currentTask = 0;
}
}
switch (op->getClassNo()) {
case Atlas::Objects::Operation::APPEARANCE_NO:
appearanceArrived(op);
break;
case Atlas::Objects::Operation::DISAPPEARANCE_NO:
disappearanceArrived(op);
break;
case Atlas::Objects::Operation::INFO_NO:
infoArrived(op);
break;
case Atlas::Objects::Operation::ERROR_NO:
errorArrived(op);
break;
case Atlas::Objects::Operation::SIGHT_NO:
sightArrived(op);
break;
case Atlas::Objects::Operation::SOUND_NO:
soundArrived(op);
break;
default:
break;
}
}
void AtlasStreamClient::infoArrived(const RootOperation & op)
{
reply_flag = true;
if (!op->isDefaultFrom()) {
return;
}
if (op->isDefaultArgs() || op->getArgs().empty()) {
std::cerr << "WARNING: Malformed account from server" << std::endl << std::flush;
return;
}
if (op->isDefaultRefno()) {
return;
}
if (op->getRefno() != serialNo) {
return;
}
m_infoReply = op->getArgs().front();
}
void AtlasStreamClient::appearanceArrived(const RootOperation & op)
{
}
void AtlasStreamClient::disappearanceArrived(const RootOperation & op)
{
}
void AtlasStreamClient::sightArrived(const RootOperation & op)
{
}
void AtlasStreamClient::soundArrived(const RootOperation & op)
{
}
void AtlasStreamClient::loginSuccess(const Atlas::Objects::Root & arg)
{
}
/// \brief Called when an Error operation arrives
///
/// @param op Operation to be processed
void AtlasStreamClient::errorArrived(const RootOperation & op)
{
reply_flag = true;
error_flag = true;
const std::vector<Root> & args = op->getArgs();
if (args.empty()) {
return;
}
const Root & arg = args.front();
Element message_attr;
if (arg->copyAttr("message", message_attr) == 0 && message_attr.isString()) {
m_errorMessage = message_attr.String();
}
}
AtlasStreamClient::AtlasStreamClient() : m_io_work(m_io_service), reply_flag(false), error_flag(false),
serialNo(512), m_socket(nullptr),
m_currentTask(0), m_spacing(2)
{
}
AtlasStreamClient::~AtlasStreamClient()
{
delete m_socket;
}
void AtlasStreamClient::send(const RootOperation & op)
{
if (m_socket == 0) {
return;
}
reply_flag = false;
error_flag = false;
m_socket->getEncoder().streamObjectsMessage(op);
m_socket->write();
}
int AtlasStreamClient::connect(const std::string & host, int port)
{
delete m_socket;
m_socket = nullptr;
try {
std::function<void()> dispatcher = [&]{this->dispatch();};
m_socket = new TcpStreamClientSocket(m_io_service, dispatcher, ip::tcp::endpoint(boost::asio::ip::address::from_string(host), port));
} catch (const std::exception& e) {
return -1;
}
return m_socket->negotiate(*this);
}
int AtlasStreamClient::connectLocal(const std::string & filename)
{
delete m_socket;
m_socket = nullptr;
try {
std::function<void()> dispatcher = [&]{this->dispatch();};
m_socket = new LocalStreamClientSocket(m_io_service, dispatcher, local::stream_protocol::endpoint(filename));
} catch (const std::exception& e) {
return -1;
}
return m_socket->negotiate(*this);
}
int AtlasStreamClient::cleanDisconnect()
{
delete m_socket;
m_socket = nullptr;
// // Shutting down our write side will cause the server to get a HUP once
// // it has consumed all we have left for it
// m_ios->shutdown(true);
// // The server will then close the socket once we have all the responses
// while (this->poll(20, 0) == 0);
return 0;
}
int AtlasStreamClient::login(const std::string & username,
const std::string & password)
{
m_username = username;
Login l;
Anonymous account;
account->setAttr("username", username);
account->setAttr("password", password);
l->setArgs1(account);
l->setSerialno(newSerialNo());
send(l);
return waitForLoginResponse();
}
int AtlasStreamClient::create(const std::string & type,
const std::string & username,
const std::string & password)
{
m_username = username;
Create c;
Anonymous account;
account->setAttr("username", username);
account->setAttr("password", password);
account->setParents(std::list<std::string>(1, type));
c->setArgs1(account);
c->setSerialno(newSerialNo());
send(c);
return waitForLoginResponse();
}
int AtlasStreamClient::waitForLoginResponse()
{
while (poll(boost::posix_time::seconds(10)) == 0) {
if (reply_flag && !error_flag) {
if (m_infoReply->isDefaultId()) {
std::cerr << "Malformed reply" << std::endl << std::flush;
} else {
m_accountId = m_infoReply->getId();
if (!m_infoReply->getParents().empty()) {
m_accountType = m_infoReply->getParents().front();
}
loginSuccess(m_infoReply);
return 0;
}
reply_flag = false;
}
}
return -1;
}
int AtlasStreamClient::pollOne(const boost::posix_time::time_duration& duration)
{
if (!m_socket) {
return -1;
}
int result = m_socket->poll(duration, [&]()->bool{return !mOps.empty();});
if (result == -1) {
std::cerr << "Server disconnected" << std::endl << std::flush;
}
return result;
}
int AtlasStreamClient::poll(const boost::posix_time::time_duration& duration)
{
if (!m_socket) {
return -1;
}
int result = m_socket->poll(duration, [](){return false;});
if (result == -1) {
std::cerr << "Server disconnected" << std::endl << std::flush;
}
return result;
}
int AtlasStreamClient::poll(int seconds, int microseconds)
{
return poll(boost::posix_time::seconds(seconds) + boost::posix_time::microseconds(microseconds));
}
int AtlasStreamClient::runTask(ClientTask * task, const std::string & arg)
{
assert(task != 0);
if (m_currentTask != 0) {
std::cout << "Busy" << std::endl << std::flush;
return -1;
}
m_currentTask = task;
OpVector res;
m_currentTask->setup(arg, res);
if (m_currentTask->isComplete()) {
delete task;
m_currentTask = 0;
return -1;
}
OpVector::const_iterator Iend = res.end();
for (OpVector::const_iterator I = res.begin(); I != Iend; ++I) {
send(*I);
}
return 0;
}
int AtlasStreamClient::endTask()
{
if (m_currentTask == 0) {
return -1;
}
delete m_currentTask;
m_currentTask = 0;
return 0;
}
bool AtlasStreamClient::hasTask() const {
return m_currentTask != nullptr;
}
int AtlasStreamClient::pollUntilTaskComplete()
{
if (m_currentTask == nullptr) {
return 1;
}
while (m_currentTask != nullptr) {
if (poll(boost::posix_time::milliseconds(100)) == -1) {
return -1;
}
}
return 0;
}