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