Implement trivial RPC interface for dfhack via TCP & protobufs.
Use it to make an executable capable of calling commands remotely.develop
parent
c42e2ff053
commit
560e977f05
@ -0,0 +1,359 @@
|
|||||||
|
/*
|
||||||
|
https://github.com/peterix/dfhack
|
||||||
|
Copyright (c) 2011 Petr Mrázek <peterix@gmail.com>
|
||||||
|
|
||||||
|
A thread-safe logging console with a line editor for windows.
|
||||||
|
|
||||||
|
Based on linenoise win32 port,
|
||||||
|
copyright 2010, Jon Griffiths <jon_p_griffiths at yahoo dot com>.
|
||||||
|
All rights reserved.
|
||||||
|
Based on linenoise, copyright 2010, Salvatore Sanfilippo <antirez at gmail dot com>.
|
||||||
|
The original linenoise can be found at: http://github.com/antirez/linenoise
|
||||||
|
|
||||||
|
Redistribution and use in source and binary forms, with or without
|
||||||
|
modification, are permitted provided that the following conditions are met:
|
||||||
|
|
||||||
|
* Redistributions of source code must retain the above copyright notice,
|
||||||
|
this list of conditions and the following disclaimer.
|
||||||
|
* Redistributions in binary form must reproduce the above copyright
|
||||||
|
notice, this list of conditions and the following disclaimer in the
|
||||||
|
documentation and/or other materials provided with the distribution.
|
||||||
|
* Neither the name of Redis nor the names of its contributors may be used
|
||||||
|
to endorse or promote products derived from this software without
|
||||||
|
specific prior written permission.
|
||||||
|
|
||||||
|
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||||
|
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||||
|
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||||
|
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
||||||
|
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||||
|
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||||
|
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||||
|
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||||
|
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||||
|
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||||
|
POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
#include <stdarg.h>
|
||||||
|
#include <errno.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <iostream>
|
||||||
|
#include <fstream>
|
||||||
|
#include <istream>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
#include "RemoteClient.h"
|
||||||
|
#include "MiscUtils.h"
|
||||||
|
|
||||||
|
#include <cstdio>
|
||||||
|
#include <cstdlib>
|
||||||
|
#include <sstream>
|
||||||
|
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
|
using namespace DFHack;
|
||||||
|
|
||||||
|
#include "tinythread.h"
|
||||||
|
using namespace tthread;
|
||||||
|
|
||||||
|
using dfproto::CoreTextNotification;
|
||||||
|
|
||||||
|
using google::protobuf::MessageLite;
|
||||||
|
|
||||||
|
const char RPCHandshakeHeader::REQUEST_MAGIC[9] = "DFHack?\n";
|
||||||
|
const char RPCHandshakeHeader::RESPONSE_MAGIC[9] = "DFHack!\n";
|
||||||
|
|
||||||
|
void color_ostream_proxy::decode(CoreTextNotification *data)
|
||||||
|
{
|
||||||
|
flush_proxy();
|
||||||
|
|
||||||
|
int cnt = data->fragments_size();
|
||||||
|
if (cnt > 0) {
|
||||||
|
target->begin_batch();
|
||||||
|
|
||||||
|
for (int i = 0; i < cnt; i++)
|
||||||
|
{
|
||||||
|
auto &frag = data->fragments(i);
|
||||||
|
|
||||||
|
color_value color = frag.has_color() ? color_value(frag.color()) : COLOR_RESET;
|
||||||
|
target->add_text(color, frag.text());
|
||||||
|
}
|
||||||
|
|
||||||
|
target->end_batch();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
RemoteClient::RemoteClient()
|
||||||
|
{
|
||||||
|
active = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
RemoteClient::~RemoteClient()
|
||||||
|
{
|
||||||
|
disconnect();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool DFHack::readFullBuffer(CSimpleSocket &socket, void *buf, int size)
|
||||||
|
{
|
||||||
|
if (!socket.IsSocketValid())
|
||||||
|
return false;
|
||||||
|
|
||||||
|
char *ptr = (char*)buf;
|
||||||
|
while (size > 0) {
|
||||||
|
int cnt = socket.Receive(size);
|
||||||
|
if (cnt <= 0)
|
||||||
|
return false;
|
||||||
|
memcpy(ptr, socket.GetData(), cnt);
|
||||||
|
ptr += cnt;
|
||||||
|
size -= cnt;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
int RemoteClient::GetDefaultPort()
|
||||||
|
{
|
||||||
|
const char *port = getenv("DFHACK_PORT");
|
||||||
|
if (!port) port = "0";
|
||||||
|
|
||||||
|
int portval = atoi(port);
|
||||||
|
if (portval <= 0)
|
||||||
|
return 5000;
|
||||||
|
else
|
||||||
|
return portval;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool RemoteClient::connect(int port)
|
||||||
|
{
|
||||||
|
assert(!active);
|
||||||
|
|
||||||
|
if (port <= 0)
|
||||||
|
port = GetDefaultPort();
|
||||||
|
|
||||||
|
if (!socket.Initialize())
|
||||||
|
{
|
||||||
|
std::cerr << "Socket init failed." << endl;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!socket.Open((const uint8 *)"localhost", port))
|
||||||
|
{
|
||||||
|
std::cerr << "Could not connect to localhost:" << port << endl;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
active = true;
|
||||||
|
|
||||||
|
RPCHandshakeHeader header;
|
||||||
|
memcpy(header.magic, RPCHandshakeHeader::REQUEST_MAGIC, sizeof(header.magic));
|
||||||
|
header.version = 1;
|
||||||
|
|
||||||
|
if (socket.Send((uint8*)&header, sizeof(header)) != sizeof(header))
|
||||||
|
{
|
||||||
|
std::cerr << "Could not send header." << endl;
|
||||||
|
socket.Close();
|
||||||
|
return active = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!readFullBuffer(socket, &header, sizeof(header)))
|
||||||
|
{
|
||||||
|
std::cerr << "Could not read header." << endl;
|
||||||
|
socket.Close();
|
||||||
|
return active = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (memcmp(header.magic, RPCHandshakeHeader::RESPONSE_MAGIC, sizeof(header.magic)) ||
|
||||||
|
header.version != 1)
|
||||||
|
{
|
||||||
|
std::cerr << "Invalid handshake response." << endl;
|
||||||
|
socket.Close();
|
||||||
|
return active = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bind_call.name = "BindMethod";
|
||||||
|
bind_call.p_client = this;
|
||||||
|
bind_call.id = 0;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void RemoteClient::disconnect()
|
||||||
|
{
|
||||||
|
if (active && socket.IsSocketValid())
|
||||||
|
{
|
||||||
|
RPCMessageHeader header;
|
||||||
|
header.id = RPC_REQUEST_QUIT;
|
||||||
|
header.size = 0;
|
||||||
|
if (socket.Send((uint8_t*)&header, sizeof(header)) != sizeof(header))
|
||||||
|
std::cerr << "Could not send the disconnect message." << endl;
|
||||||
|
}
|
||||||
|
|
||||||
|
socket.Close();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool RemoteClient::bind(color_ostream &out, RemoteFunctionBase *function,
|
||||||
|
const std::string &name, const std::string &proto)
|
||||||
|
{
|
||||||
|
if (!active || !socket.IsSocketValid())
|
||||||
|
return false;
|
||||||
|
|
||||||
|
bind_call.reset();
|
||||||
|
|
||||||
|
{
|
||||||
|
auto in = bind_call.in();
|
||||||
|
|
||||||
|
in->set_method(name);
|
||||||
|
if (!proto.empty())
|
||||||
|
in->set_plugin(proto);
|
||||||
|
in->set_input_msg(function->p_in_template->GetTypeName());
|
||||||
|
in->set_output_msg(function->p_out_template->GetTypeName());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (bind_call.execute(out) != CR_OK)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
function->p_client = this;
|
||||||
|
function->id = bind_call.out()->assigned_id();
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void RPCFunctionBase::reset(bool free)
|
||||||
|
{
|
||||||
|
if (free)
|
||||||
|
{
|
||||||
|
delete p_in;
|
||||||
|
delete p_out;
|
||||||
|
p_in = p_out = NULL;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (p_in)
|
||||||
|
p_in->Clear();
|
||||||
|
if (p_out)
|
||||||
|
p_out->Clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool RemoteFunctionBase::bind(color_ostream &out, RemoteClient *client,
|
||||||
|
const std::string &name, const std::string &proto)
|
||||||
|
{
|
||||||
|
if (p_client == client)
|
||||||
|
return true;
|
||||||
|
|
||||||
|
if (p_client)
|
||||||
|
{
|
||||||
|
out.printerr("Function already bound to %s::%s\n",
|
||||||
|
this->proto.c_str(), this->name.c_str());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
this->name = name;
|
||||||
|
this->proto = proto;
|
||||||
|
|
||||||
|
return client->bind(out, this, name, proto);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool DFHack::sendRemoteMessage(CSimpleSocket &socket, int16_t id, const MessageLite *msg)
|
||||||
|
{
|
||||||
|
int size = msg->ByteSize();
|
||||||
|
int fullsz = size + sizeof(RPCMessageHeader);
|
||||||
|
|
||||||
|
std::auto_ptr<uint8_t> data(new uint8_t[fullsz]);
|
||||||
|
RPCMessageHeader *hdr = (RPCMessageHeader*)data.get();
|
||||||
|
|
||||||
|
hdr->id = id;
|
||||||
|
hdr->size = size;
|
||||||
|
|
||||||
|
if (!msg->SerializeToArray(data.get() + sizeof(RPCMessageHeader), size))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
return (socket.Send(data.get(), fullsz) == fullsz);
|
||||||
|
}
|
||||||
|
|
||||||
|
command_result RemoteFunctionBase::execute(color_ostream &out,
|
||||||
|
const message_type *input, message_type *output)
|
||||||
|
{
|
||||||
|
if (!p_client)
|
||||||
|
{
|
||||||
|
out.printerr("Calling an unbound RPC function.\n");
|
||||||
|
return CR_NOT_IMPLEMENTED;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!p_client->socket.IsSocketValid())
|
||||||
|
{
|
||||||
|
out.printerr("In call to %s::%s: invalid socket.\n",
|
||||||
|
this->proto.c_str(), this->name.c_str());
|
||||||
|
return CR_FAILURE;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!sendRemoteMessage(p_client->socket, id, input))
|
||||||
|
{
|
||||||
|
out.printerr("In call to %s::%s: I/O error in send.\n",
|
||||||
|
this->proto.c_str(), this->name.c_str());
|
||||||
|
return CR_FAILURE;
|
||||||
|
}
|
||||||
|
|
||||||
|
color_ostream_proxy text_decoder(out);
|
||||||
|
CoreTextNotification text_data;
|
||||||
|
|
||||||
|
output->Clear();
|
||||||
|
|
||||||
|
for (;;) {
|
||||||
|
RPCMessageHeader header;
|
||||||
|
|
||||||
|
if (!readFullBuffer(p_client->socket, &header, sizeof(header)))
|
||||||
|
{
|
||||||
|
out.printerr("In call to %s::%s: I/O error in receive header.\n",
|
||||||
|
this->proto.c_str(), this->name.c_str());
|
||||||
|
return CR_FAILURE;
|
||||||
|
}
|
||||||
|
|
||||||
|
//out.print("Received %d:%d\n", header.id, header.size);
|
||||||
|
|
||||||
|
if (header.id == RPC_REPLY_FAIL)
|
||||||
|
return header.size == CR_OK ? CR_FAILURE : command_result(header.size);
|
||||||
|
|
||||||
|
if (header.size < 0 || header.size > 2*1048576)
|
||||||
|
{
|
||||||
|
out.printerr("In call to %s::%s: invalid received size %d.\n",
|
||||||
|
this->proto.c_str(), this->name.c_str(), header.size);
|
||||||
|
return CR_FAILURE;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::auto_ptr<uint8_t> buf(new uint8_t[header.size]);
|
||||||
|
|
||||||
|
if (!readFullBuffer(p_client->socket, buf.get(), header.size))
|
||||||
|
{
|
||||||
|
out.printerr("In call to %s::%s: I/O error in receive %d bytes of data.\n",
|
||||||
|
this->proto.c_str(), this->name.c_str(), header.size);
|
||||||
|
return CR_FAILURE;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (header.id) {
|
||||||
|
case RPC_REPLY_RESULT:
|
||||||
|
if (!output->ParseFromArray(buf.get(), header.size))
|
||||||
|
{
|
||||||
|
out.printerr("In call to %s::%s: error parsing received result.\n",
|
||||||
|
this->proto.c_str(), this->name.c_str());
|
||||||
|
return CR_FAILURE;
|
||||||
|
}
|
||||||
|
|
||||||
|
return CR_OK;
|
||||||
|
|
||||||
|
case RPC_REPLY_TEXT:
|
||||||
|
text_data.Clear();
|
||||||
|
if (text_data.ParseFromArray(buf.get(), header.size))
|
||||||
|
text_decoder.decode(&text_data);
|
||||||
|
else
|
||||||
|
out.printerr("In call to %s::%s: received invalid text data.\n",
|
||||||
|
this->proto.c_str(), this->name.c_str());
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,359 @@
|
|||||||
|
/*
|
||||||
|
https://github.com/peterix/dfhack
|
||||||
|
Copyright (c) 2011 Petr Mrázek <peterix@gmail.com>
|
||||||
|
|
||||||
|
A thread-safe logging console with a line editor for windows.
|
||||||
|
|
||||||
|
Based on linenoise win32 port,
|
||||||
|
copyright 2010, Jon Griffiths <jon_p_griffiths at yahoo dot com>.
|
||||||
|
All rights reserved.
|
||||||
|
Based on linenoise, copyright 2010, Salvatore Sanfilippo <antirez at gmail dot com>.
|
||||||
|
The original linenoise can be found at: http://github.com/antirez/linenoise
|
||||||
|
|
||||||
|
Redistribution and use in source and binary forms, with or without
|
||||||
|
modification, are permitted provided that the following conditions are met:
|
||||||
|
|
||||||
|
* Redistributions of source code must retain the above copyright notice,
|
||||||
|
this list of conditions and the following disclaimer.
|
||||||
|
* Redistributions in binary form must reproduce the above copyright
|
||||||
|
notice, this list of conditions and the following disclaimer in the
|
||||||
|
documentation and/or other materials provided with the distribution.
|
||||||
|
* Neither the name of Redis nor the names of its contributors may be used
|
||||||
|
to endorse or promote products derived from this software without
|
||||||
|
specific prior written permission.
|
||||||
|
|
||||||
|
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||||
|
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||||
|
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||||
|
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
||||||
|
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||||
|
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||||
|
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||||
|
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||||
|
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||||
|
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||||
|
POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
#include <stdarg.h>
|
||||||
|
#include <errno.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <iostream>
|
||||||
|
#include <fstream>
|
||||||
|
#include <istream>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
#include "RemoteServer.h"
|
||||||
|
#include "PluginManager.h"
|
||||||
|
#include "MiscUtils.h"
|
||||||
|
|
||||||
|
#include <cstdio>
|
||||||
|
#include <cstdlib>
|
||||||
|
#include <sstream>
|
||||||
|
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
|
using namespace DFHack;
|
||||||
|
|
||||||
|
#include "tinythread.h"
|
||||||
|
using namespace tthread;
|
||||||
|
|
||||||
|
using dfproto::CoreTextNotification;
|
||||||
|
using dfproto::CoreTextFragment;
|
||||||
|
using google::protobuf::MessageLite;
|
||||||
|
|
||||||
|
CoreService::CoreService() {
|
||||||
|
// This must be the first method, so that it gets id 0
|
||||||
|
addMethod("BindMethod", &CoreService::BindMethod);
|
||||||
|
|
||||||
|
addMethod("RunCommand", &CoreService::RunCommand);
|
||||||
|
}
|
||||||
|
|
||||||
|
command_result CoreService::BindMethod(color_ostream &stream,
|
||||||
|
const dfproto::CoreBindRequest *in,
|
||||||
|
dfproto::CoreBindReply *out)
|
||||||
|
{
|
||||||
|
ServerFunctionBase *fn = connection()->findFunction(in->plugin(), in->method());
|
||||||
|
|
||||||
|
if (!fn)
|
||||||
|
{
|
||||||
|
stream.printerr("RPC method not found: %s::%s\n",
|
||||||
|
in->plugin().c_str(), in->method().c_str());
|
||||||
|
return CR_FAILURE;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (fn->p_in_template->GetTypeName() != in->input_msg() ||
|
||||||
|
fn->p_out_template->GetTypeName() != in->output_msg())
|
||||||
|
{
|
||||||
|
stream.printerr("Requested wrong signature for RPC method: %s::%s\n",
|
||||||
|
in->plugin().c_str(), in->method().c_str());
|
||||||
|
return CR_FAILURE;
|
||||||
|
}
|
||||||
|
|
||||||
|
out->set_assigned_id(fn->getId());
|
||||||
|
return CR_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
command_result CoreService::RunCommand(color_ostream &stream,
|
||||||
|
const dfproto::CoreRunCommandRequest *in,
|
||||||
|
CoreVoidReply*)
|
||||||
|
{
|
||||||
|
std::string cmd = in->command();
|
||||||
|
std::vector<std::string> args;
|
||||||
|
for (int i = 0; i < in->arguments_size(); i++)
|
||||||
|
args.push_back(in->arguments(i));
|
||||||
|
|
||||||
|
return Core::getInstance().plug_mgr->InvokeCommand(stream, cmd, args, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
RPCService::RPCService()
|
||||||
|
{
|
||||||
|
owner = NULL;
|
||||||
|
holder = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
RPCService::~RPCService()
|
||||||
|
{
|
||||||
|
for (size_t i = 0; i < functions.size(); i++)
|
||||||
|
delete functions[i];
|
||||||
|
}
|
||||||
|
|
||||||
|
ServerFunctionBase *RPCService::getFunction(const std::string &name)
|
||||||
|
{
|
||||||
|
assert(owner);
|
||||||
|
return lookup[name];
|
||||||
|
}
|
||||||
|
|
||||||
|
void RPCService::finalize(ServerConnection *owner, std::vector<ServerFunctionBase*> *ftable)
|
||||||
|
{
|
||||||
|
this->owner = owner;
|
||||||
|
|
||||||
|
for (size_t i = 0; i < functions.size(); i++)
|
||||||
|
{
|
||||||
|
auto fn = functions[i];
|
||||||
|
|
||||||
|
fn->id = (int16_t)ftable->size();
|
||||||
|
ftable->push_back(fn);
|
||||||
|
|
||||||
|
lookup[fn->name] = fn;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ServerConnection::ServerConnection(CActiveSocket *socket)
|
||||||
|
: socket(socket), stream(this)
|
||||||
|
{
|
||||||
|
in_error = false;
|
||||||
|
|
||||||
|
core_service = new CoreService();
|
||||||
|
core_service->finalize(this, &functions);
|
||||||
|
|
||||||
|
thread = new tthread::thread(threadFn, (void*)this);
|
||||||
|
}
|
||||||
|
|
||||||
|
ServerConnection::~ServerConnection()
|
||||||
|
{
|
||||||
|
in_error = true;
|
||||||
|
socket->Close();
|
||||||
|
delete socket;
|
||||||
|
|
||||||
|
for (auto it = plugin_services.begin(); it != plugin_services.end(); ++it)
|
||||||
|
delete it->second;
|
||||||
|
|
||||||
|
delete core_service;
|
||||||
|
}
|
||||||
|
|
||||||
|
ServerFunctionBase *ServerConnection::findFunction(const std::string &plugin, const std::string &name)
|
||||||
|
{
|
||||||
|
if (plugin.empty())
|
||||||
|
return core_service->getFunction(name);
|
||||||
|
else
|
||||||
|
return NULL; // todo: add plugin api support
|
||||||
|
}
|
||||||
|
|
||||||
|
void ServerConnection::connection_ostream::flush_proxy()
|
||||||
|
{
|
||||||
|
if (owner->in_error)
|
||||||
|
{
|
||||||
|
buffer.clear();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (buffer.empty())
|
||||||
|
return;
|
||||||
|
|
||||||
|
CoreTextNotification msg;
|
||||||
|
|
||||||
|
for (auto it = buffer.begin(); it != buffer.end(); ++it)
|
||||||
|
{
|
||||||
|
auto frag = msg.add_fragments();
|
||||||
|
frag->set_text(it->second);
|
||||||
|
if (it->first >= 0)
|
||||||
|
frag->set_color(CoreTextFragment::Color(it->first));
|
||||||
|
}
|
||||||
|
|
||||||
|
buffer.clear();
|
||||||
|
|
||||||
|
if (!sendRemoteMessage(*owner->socket, RPC_REPLY_TEXT, &msg))
|
||||||
|
{
|
||||||
|
owner->in_error = true;
|
||||||
|
Core::printerr("Error writing text into client socket.\n");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ServerConnection::threadFn(void *arg)
|
||||||
|
{
|
||||||
|
ServerConnection *me = (ServerConnection*)arg;
|
||||||
|
color_ostream_proxy out(Core::getInstance().getConsole());
|
||||||
|
|
||||||
|
/* Handshake */
|
||||||
|
|
||||||
|
{
|
||||||
|
RPCHandshakeHeader header;
|
||||||
|
|
||||||
|
if (!readFullBuffer(*me->socket, &header, sizeof(header)))
|
||||||
|
{
|
||||||
|
out << "In RPC server: could not read handshake header." << endl;
|
||||||
|
delete me;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (memcmp(header.magic, RPCHandshakeHeader::REQUEST_MAGIC, sizeof(header.magic)) ||
|
||||||
|
header.version != 1)
|
||||||
|
{
|
||||||
|
out << "In RPC server: invalid handshake header." << endl;
|
||||||
|
delete me;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
memcpy(header.magic, RPCHandshakeHeader::RESPONSE_MAGIC, sizeof(header.magic));
|
||||||
|
header.version = 1;
|
||||||
|
|
||||||
|
if (me->socket->Send((uint8*)&header, sizeof(header)) != sizeof(header))
|
||||||
|
{
|
||||||
|
out << "In RPC server: could not send handshake response." << endl;
|
||||||
|
delete me;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Processing */
|
||||||
|
|
||||||
|
std::cerr << "Client connection established." << endl;
|
||||||
|
|
||||||
|
while (!me->in_error) {
|
||||||
|
RPCMessageHeader header;
|
||||||
|
|
||||||
|
if (!readFullBuffer(*me->socket, &header, sizeof(header)))
|
||||||
|
{
|
||||||
|
out.printerr("In RPC server: I/O error in receive header.\n");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (header.id == RPC_REQUEST_QUIT)
|
||||||
|
break;
|
||||||
|
|
||||||
|
if (header.size < 0 || header.size > 2*1048576)
|
||||||
|
{
|
||||||
|
out.printerr("In RPC server: invalid received size %d.\n", header.size);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::auto_ptr<uint8_t> buf(new uint8_t[header.size]);
|
||||||
|
|
||||||
|
if (!readFullBuffer(*me->socket, buf.get(), header.size))
|
||||||
|
{
|
||||||
|
out.printerr("In RPC server: I/O error in receive %d bytes of data.\n", header.size);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
//out.print("Handling %d:%d\n", header.id, header.size);
|
||||||
|
|
||||||
|
ServerFunctionBase *fn = vector_get(me->functions, header.id);
|
||||||
|
MessageLite *reply = NULL;
|
||||||
|
command_result res = CR_FAILURE;
|
||||||
|
|
||||||
|
if (!fn)
|
||||||
|
{
|
||||||
|
me->stream.printerr("RPC call of invalid id %d\n", header.id);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (!fn->in()->ParseFromArray(buf.get(), header.size))
|
||||||
|
{
|
||||||
|
me->stream.printerr("In call to %s: could not decode input args.\n", fn->name);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
reply = fn->out();
|
||||||
|
res = fn->execute(me->stream);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (me->in_error)
|
||||||
|
break;
|
||||||
|
|
||||||
|
me->stream.flush();
|
||||||
|
|
||||||
|
//out.print("Answer %d:%d\n", res, reply);
|
||||||
|
|
||||||
|
if (res == CR_OK && reply)
|
||||||
|
{
|
||||||
|
if (!sendRemoteMessage(*me->socket, RPC_REPLY_RESULT, reply))
|
||||||
|
{
|
||||||
|
out.printerr("In RPC server: I/O error in send result.\n");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
header.id = RPC_REPLY_FAIL;
|
||||||
|
header.size = res;
|
||||||
|
|
||||||
|
if (me->socket->Send((uint8_t*)&header, sizeof(header)) != sizeof(header))
|
||||||
|
{
|
||||||
|
out.printerr("In RPC server: I/O error in send failure code.\n");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std::cerr << "Shutting down client connection." << endl;
|
||||||
|
|
||||||
|
delete me;
|
||||||
|
}
|
||||||
|
|
||||||
|
ServerMain::ServerMain()
|
||||||
|
{
|
||||||
|
thread = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
ServerMain::~ServerMain()
|
||||||
|
{
|
||||||
|
socket.Close();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ServerMain::listen(int port)
|
||||||
|
{
|
||||||
|
if (thread)
|
||||||
|
return true;
|
||||||
|
|
||||||
|
socket.Initialize();
|
||||||
|
|
||||||
|
if (!socket.Listen((const uint8 *)"127.0.0.1", port))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
thread = new tthread::thread(threadFn, this);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ServerMain::threadFn(void *arg)
|
||||||
|
{
|
||||||
|
ServerMain *me = (ServerMain*)arg;
|
||||||
|
CActiveSocket *client;
|
||||||
|
|
||||||
|
while ((client = me->socket.Accept()) != NULL)
|
||||||
|
{
|
||||||
|
new ServerConnection(client);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,90 @@
|
|||||||
|
/*
|
||||||
|
https://github.com/peterix/dfhack
|
||||||
|
Copyright (c) 2011 Petr Mrázek <peterix@gmail.com>
|
||||||
|
|
||||||
|
A thread-safe logging console with a line editor for windows.
|
||||||
|
|
||||||
|
Based on linenoise win32 port,
|
||||||
|
copyright 2010, Jon Griffiths <jon_p_griffiths at yahoo dot com>.
|
||||||
|
All rights reserved.
|
||||||
|
Based on linenoise, copyright 2010, Salvatore Sanfilippo <antirez at gmail dot com>.
|
||||||
|
The original linenoise can be found at: http://github.com/antirez/linenoise
|
||||||
|
|
||||||
|
Redistribution and use in source and binary forms, with or without
|
||||||
|
modification, are permitted provided that the following conditions are met:
|
||||||
|
|
||||||
|
* Redistributions of source code must retain the above copyright notice,
|
||||||
|
this list of conditions and the following disclaimer.
|
||||||
|
* Redistributions in binary form must reproduce the above copyright
|
||||||
|
notice, this list of conditions and the following disclaimer in the
|
||||||
|
documentation and/or other materials provided with the distribution.
|
||||||
|
* Neither the name of Redis nor the names of its contributors may be used
|
||||||
|
to endorse or promote products derived from this software without
|
||||||
|
specific prior written permission.
|
||||||
|
|
||||||
|
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||||
|
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||||
|
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||||
|
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
||||||
|
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||||
|
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||||
|
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||||
|
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||||
|
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||||
|
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||||
|
POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
#include <stdarg.h>
|
||||||
|
#include <errno.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <iostream>
|
||||||
|
#include <fstream>
|
||||||
|
#include <istream>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
#include "RemoteClient.h"
|
||||||
|
|
||||||
|
#include <cstdio>
|
||||||
|
#include <cstdlib>
|
||||||
|
#include <sstream>
|
||||||
|
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
|
using namespace DFHack;
|
||||||
|
using namespace dfproto;
|
||||||
|
using std::cout;
|
||||||
|
|
||||||
|
int main (int argc, char *argv[])
|
||||||
|
{
|
||||||
|
color_ostream_wrapper out(cout);
|
||||||
|
|
||||||
|
if (argc <= 1)
|
||||||
|
{
|
||||||
|
fprintf(stderr, "Usage: dfhack-run <command> [args...]\n");
|
||||||
|
return 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Connect to DFHack
|
||||||
|
RemoteClient client;
|
||||||
|
if (!client.connect())
|
||||||
|
return 2;
|
||||||
|
|
||||||
|
// Bind to RunCommand
|
||||||
|
RemoteFunction<CoreRunCommandRequest,CoreVoidReply> command;
|
||||||
|
|
||||||
|
if (!command.bind(out, &client, "RunCommand"))
|
||||||
|
return 2;
|
||||||
|
|
||||||
|
// Execute it
|
||||||
|
command.in()->set_command(argv[1]);
|
||||||
|
for (int i = 2; i < argc; i++)
|
||||||
|
command.in()->add_arguments(argv[i]);
|
||||||
|
|
||||||
|
if (command.execute(out) != CR_OK)
|
||||||
|
return 1;
|
||||||
|
|
||||||
|
out.flush();
|
||||||
|
return 0;
|
||||||
|
}
|
@ -0,0 +1,169 @@
|
|||||||
|
/*
|
||||||
|
https://github.com/peterix/dfhack
|
||||||
|
Copyright (c) 2009-2011 Petr Mrázek (peterix@gmail.com)
|
||||||
|
|
||||||
|
This software is provided 'as-is', without any express or implied
|
||||||
|
warranty. In no event will the authors be held liable for any
|
||||||
|
damages arising from the use of this software.
|
||||||
|
|
||||||
|
Permission is granted to anyone to use this software for any
|
||||||
|
purpose, including commercial applications, and to alter it and
|
||||||
|
redistribute it freely, subject to the following restrictions:
|
||||||
|
|
||||||
|
1. The origin of this software must not be misrepresented; you must
|
||||||
|
not claim that you wrote the original software. If you use this
|
||||||
|
software in a product, an acknowledgment in the product documentation
|
||||||
|
would be appreciated but is not required.
|
||||||
|
|
||||||
|
2. Altered source versions must be plainly marked as such, and
|
||||||
|
must not be misrepresented as being the original software.
|
||||||
|
|
||||||
|
3. This notice may not be removed or altered from any source
|
||||||
|
distribution.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
#include "Pragma.h"
|
||||||
|
#include "Export.h"
|
||||||
|
#include "ColorText.h"
|
||||||
|
|
||||||
|
#include "ActiveSocket.h"
|
||||||
|
|
||||||
|
#include "CoreProtocol.pb.h"
|
||||||
|
|
||||||
|
namespace DFHack
|
||||||
|
{
|
||||||
|
using dfproto::CoreVoidReply;
|
||||||
|
|
||||||
|
enum command_result
|
||||||
|
{
|
||||||
|
CR_WOULD_BREAK = -2,
|
||||||
|
CR_NOT_IMPLEMENTED = -1,
|
||||||
|
CR_OK = 0,
|
||||||
|
CR_FAILURE = 1,
|
||||||
|
CR_WRONG_USAGE = 2
|
||||||
|
};
|
||||||
|
|
||||||
|
enum DFHackReplyCode : int16_t {
|
||||||
|
RPC_REPLY_RESULT = -1,
|
||||||
|
RPC_REPLY_FAIL = -2,
|
||||||
|
RPC_REPLY_TEXT = -3,
|
||||||
|
RPC_REQUEST_QUIT = -4
|
||||||
|
};
|
||||||
|
|
||||||
|
struct RPCHandshakeHeader {
|
||||||
|
char magic[8];
|
||||||
|
int version;
|
||||||
|
|
||||||
|
static const char REQUEST_MAGIC[9];
|
||||||
|
static const char RESPONSE_MAGIC[9];
|
||||||
|
};
|
||||||
|
|
||||||
|
struct RPCMessageHeader {
|
||||||
|
int16_t id;
|
||||||
|
int32_t size;
|
||||||
|
};
|
||||||
|
|
||||||
|
class DFHACK_EXPORT RemoteClient;
|
||||||
|
|
||||||
|
class DFHACK_EXPORT RPCFunctionBase {
|
||||||
|
public:
|
||||||
|
typedef ::google::protobuf::MessageLite message_type;
|
||||||
|
|
||||||
|
const message_type *const p_in_template;
|
||||||
|
const message_type *const p_out_template;
|
||||||
|
|
||||||
|
message_type *make_in() const {
|
||||||
|
return p_in_template->New();
|
||||||
|
}
|
||||||
|
|
||||||
|
message_type *in() {
|
||||||
|
if (!p_in) p_in = make_in();
|
||||||
|
return p_in;
|
||||||
|
}
|
||||||
|
|
||||||
|
message_type *make_out() const {
|
||||||
|
return p_out_template->New();
|
||||||
|
}
|
||||||
|
|
||||||
|
message_type *out() {
|
||||||
|
if (!p_out) p_out = make_out();
|
||||||
|
return p_out;
|
||||||
|
}
|
||||||
|
|
||||||
|
void reset(bool free = false);
|
||||||
|
|
||||||
|
protected:
|
||||||
|
RPCFunctionBase(const message_type *in, const message_type *out)
|
||||||
|
: p_in_template(in), p_out_template(out), p_in(NULL), p_out(NULL)
|
||||||
|
{}
|
||||||
|
~RPCFunctionBase() { delete p_in; delete p_out; }
|
||||||
|
|
||||||
|
message_type *p_in, *p_out;
|
||||||
|
};
|
||||||
|
|
||||||
|
class DFHACK_EXPORT RemoteFunctionBase : public RPCFunctionBase {
|
||||||
|
public:
|
||||||
|
bool bind(color_ostream &out,
|
||||||
|
RemoteClient *client, const std::string &name,
|
||||||
|
const std::string &proto = std::string());
|
||||||
|
bool isValid() { return (p_client != NULL); }
|
||||||
|
|
||||||
|
protected:
|
||||||
|
friend class RemoteClient;
|
||||||
|
|
||||||
|
RemoteFunctionBase(const message_type *in, const message_type *out)
|
||||||
|
: RPCFunctionBase(in, out), p_client(NULL), id(-1)
|
||||||
|
{}
|
||||||
|
|
||||||
|
command_result execute(color_ostream &out, const message_type *input, message_type *output);
|
||||||
|
|
||||||
|
std::string name, proto;
|
||||||
|
RemoteClient *p_client;
|
||||||
|
int16_t id;
|
||||||
|
};
|
||||||
|
|
||||||
|
template<typename In, typename Out>
|
||||||
|
class RemoteFunction : public RemoteFunctionBase {
|
||||||
|
public:
|
||||||
|
In *make_in() const { return static_cast<In*>(RemoteFunctionBase::make_in()); }
|
||||||
|
In *in() { return static_cast<In*>(RemoteFunctionBase::in()); }
|
||||||
|
Out *make_out() const { return static_cast<Out*>(RemoteFunctionBase::make_out()); }
|
||||||
|
Out *out() { return static_cast<Out*>(RemoteFunctionBase::out()); }
|
||||||
|
|
||||||
|
RemoteFunction() : RemoteFunctionBase(&In::default_instance(), &Out::default_instance()) {}
|
||||||
|
|
||||||
|
command_result execute(color_ostream &out) {
|
||||||
|
return RemoteFunctionBase::execute(out, this->in(), this->out());
|
||||||
|
}
|
||||||
|
command_result operator() (color_ostream &out, const In *input, Out *output) {
|
||||||
|
return RemoteFunctionBase::execute(out, input, output);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
bool readFullBuffer(CSimpleSocket &socket, void *buf, int size);
|
||||||
|
bool sendRemoteMessage(CSimpleSocket &socket, int16_t id, const ::google::protobuf::MessageLite *msg);
|
||||||
|
|
||||||
|
class DFHACK_EXPORT RemoteClient
|
||||||
|
{
|
||||||
|
friend class RemoteFunctionBase;
|
||||||
|
|
||||||
|
bool bind(color_ostream &out, RemoteFunctionBase *function,
|
||||||
|
const std::string &name, const std::string &proto);
|
||||||
|
|
||||||
|
public:
|
||||||
|
RemoteClient();
|
||||||
|
~RemoteClient();
|
||||||
|
|
||||||
|
static int GetDefaultPort();
|
||||||
|
|
||||||
|
bool connect(int port = -1);
|
||||||
|
void disconnect();
|
||||||
|
|
||||||
|
private:
|
||||||
|
bool active;
|
||||||
|
CActiveSocket socket;
|
||||||
|
|
||||||
|
RemoteFunction<dfproto::CoreBindRequest,dfproto::CoreBindReply> bind_call;
|
||||||
|
};
|
||||||
|
}
|
@ -0,0 +1,189 @@
|
|||||||
|
/*
|
||||||
|
https://github.com/peterix/dfhack
|
||||||
|
Copyright (c) 2009-2011 Petr Mrázek (peterix@gmail.com)
|
||||||
|
|
||||||
|
This software is provided 'as-is', without any express or implied
|
||||||
|
warranty. In no event will the authors be held liable for any
|
||||||
|
damages arising from the use of this software.
|
||||||
|
|
||||||
|
Permission is granted to anyone to use this software for any
|
||||||
|
purpose, including commercial applications, and to alter it and
|
||||||
|
redistribute it freely, subject to the following restrictions:
|
||||||
|
|
||||||
|
1. The origin of this software must not be misrepresented; you must
|
||||||
|
not claim that you wrote the original software. If you use this
|
||||||
|
software in a product, an acknowledgment in the product documentation
|
||||||
|
would be appreciated but is not required.
|
||||||
|
|
||||||
|
2. Altered source versions must be plainly marked as such, and
|
||||||
|
must not be misrepresented as being the original software.
|
||||||
|
|
||||||
|
3. This notice may not be removed or altered from any source
|
||||||
|
distribution.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
#include "Pragma.h"
|
||||||
|
#include "Export.h"
|
||||||
|
#include "RemoteClient.h"
|
||||||
|
#include "Core.h"
|
||||||
|
|
||||||
|
#include "PassiveSocket.h"
|
||||||
|
|
||||||
|
namespace DFHack
|
||||||
|
{
|
||||||
|
class DFHACK_EXPORT ServerConnection;
|
||||||
|
class DFHACK_EXPORT RPCService;
|
||||||
|
|
||||||
|
class DFHACK_EXPORT ServerFunctionBase : public RPCFunctionBase {
|
||||||
|
public:
|
||||||
|
const char *const name;
|
||||||
|
|
||||||
|
virtual command_result execute(color_ostream &stream) = 0;
|
||||||
|
|
||||||
|
int16_t getId() { return id; }
|
||||||
|
|
||||||
|
protected:
|
||||||
|
friend class RPCService;
|
||||||
|
|
||||||
|
ServerFunctionBase(const message_type *in, const message_type *out, RPCService *owner, const char *name)
|
||||||
|
: RPCFunctionBase(in, out), name(name), owner(owner), id(-1)
|
||||||
|
{}
|
||||||
|
|
||||||
|
RPCService *owner;
|
||||||
|
int16_t id;
|
||||||
|
};
|
||||||
|
|
||||||
|
template<typename In, typename Out>
|
||||||
|
class ServerFunction : public ServerFunctionBase {
|
||||||
|
public:
|
||||||
|
typedef command_result (*function_type)(color_ostream &out, const In *input, Out *output);
|
||||||
|
|
||||||
|
In *in() { return static_cast<In*>(RPCFunctionBase::in()); }
|
||||||
|
Out *out() { return static_cast<Out*>(RPCFunctionBase::out()); }
|
||||||
|
|
||||||
|
ServerFunction(RPCService *owner, const char *name, function_type fptr)
|
||||||
|
: ServerFunctionBase(&In::default_instance(), &Out::default_instance(), owner, name),
|
||||||
|
fptr(fptr) {}
|
||||||
|
|
||||||
|
virtual command_result execute(color_ostream &stream) { return fptr(stream, in(), out()); }
|
||||||
|
|
||||||
|
private:
|
||||||
|
function_type fptr;
|
||||||
|
};
|
||||||
|
|
||||||
|
template<typename Svc, typename In, typename Out>
|
||||||
|
class ServerMethod : public ServerFunctionBase {
|
||||||
|
public:
|
||||||
|
typedef command_result (Svc::*function_type)(color_ostream &out, const In *input, Out *output);
|
||||||
|
|
||||||
|
In *in() { return static_cast<In*>(RPCFunctionBase::in()); }
|
||||||
|
Out *out() { return static_cast<Out*>(RPCFunctionBase::out()); }
|
||||||
|
|
||||||
|
ServerMethod(RPCService *owner, const char *name, function_type fptr)
|
||||||
|
: ServerFunctionBase(&In::default_instance(), &Out::default_instance(), owner, name),
|
||||||
|
fptr(fptr) {}
|
||||||
|
|
||||||
|
virtual command_result execute(color_ostream &stream) {
|
||||||
|
return (static_cast<Svc*>(owner)->*fptr)(stream, in(), out());
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
function_type fptr;
|
||||||
|
};
|
||||||
|
|
||||||
|
class Plugin;
|
||||||
|
|
||||||
|
class DFHACK_EXPORT RPCService {
|
||||||
|
friend class ServerConnection;
|
||||||
|
|
||||||
|
std::vector<ServerFunctionBase*> functions;
|
||||||
|
std::map<std::string, ServerFunctionBase*> lookup;
|
||||||
|
ServerConnection *owner;
|
||||||
|
|
||||||
|
Plugin *holder;
|
||||||
|
|
||||||
|
void finalize(ServerConnection *owner, std::vector<ServerFunctionBase*> *ftable);
|
||||||
|
|
||||||
|
public:
|
||||||
|
RPCService();
|
||||||
|
virtual ~RPCService();
|
||||||
|
|
||||||
|
ServerFunctionBase *getFunction(const std::string &name);
|
||||||
|
|
||||||
|
template<typename In, typename Out>
|
||||||
|
void addFunction(
|
||||||
|
const char *name,
|
||||||
|
command_result (*fptr)(color_ostream &out, const In *input, Out *output)
|
||||||
|
) {
|
||||||
|
assert(!owner);
|
||||||
|
functions.push_back(new ServerFunction<In,Out>(this, name, fptr));
|
||||||
|
}
|
||||||
|
|
||||||
|
protected:
|
||||||
|
ServerConnection *connection() { return owner; }
|
||||||
|
|
||||||
|
template<typename Svc, typename In, typename Out>
|
||||||
|
void addMethod(
|
||||||
|
const char *name,
|
||||||
|
command_result (Svc::*fptr)(color_ostream &out, const In *input, Out *output)
|
||||||
|
) {
|
||||||
|
assert(!owner);
|
||||||
|
functions.push_back(new ServerMethod<Svc,In,Out>(this, name, fptr));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
class CoreService : public RPCService {
|
||||||
|
public:
|
||||||
|
CoreService();
|
||||||
|
|
||||||
|
command_result BindMethod(color_ostream &stream,
|
||||||
|
const dfproto::CoreBindRequest *in,
|
||||||
|
dfproto::CoreBindReply *out);
|
||||||
|
command_result RunCommand(color_ostream &stream,
|
||||||
|
const dfproto::CoreRunCommandRequest *in,
|
||||||
|
CoreVoidReply*);
|
||||||
|
};
|
||||||
|
|
||||||
|
class DFHACK_EXPORT ServerConnection {
|
||||||
|
class connection_ostream : public buffered_color_ostream {
|
||||||
|
ServerConnection *owner;
|
||||||
|
|
||||||
|
protected:
|
||||||
|
virtual void flush_proxy();
|
||||||
|
|
||||||
|
public:
|
||||||
|
connection_ostream(ServerConnection *owner) : owner(owner) {}
|
||||||
|
};
|
||||||
|
|
||||||
|
bool in_error;
|
||||||
|
CActiveSocket *socket;
|
||||||
|
connection_ostream stream;
|
||||||
|
|
||||||
|
std::vector<ServerFunctionBase*> functions;
|
||||||
|
|
||||||
|
CoreService *core_service;
|
||||||
|
std::map<std::string, RPCService*> plugin_services;
|
||||||
|
|
||||||
|
tthread::thread *thread;
|
||||||
|
static void threadFn(void *);
|
||||||
|
|
||||||
|
public:
|
||||||
|
ServerConnection(CActiveSocket *socket);
|
||||||
|
~ServerConnection();
|
||||||
|
|
||||||
|
ServerFunctionBase *findFunction(const std::string &plugin, const std::string &name);
|
||||||
|
};
|
||||||
|
|
||||||
|
class DFHACK_EXPORT ServerMain {
|
||||||
|
CPassiveSocket socket;
|
||||||
|
|
||||||
|
tthread::thread *thread;
|
||||||
|
static void threadFn(void *);
|
||||||
|
public:
|
||||||
|
ServerMain();
|
||||||
|
~ServerMain();
|
||||||
|
|
||||||
|
bool listen(int port);
|
||||||
|
};
|
||||||
|
}
|
Loading…
Reference in New Issue