summaryrefslogtreecommitdiff
path: root/library/RemoteClient.cpp
diff options
context:
space:
mode:
authorAlexander Gavrilov2012-03-14 19:57:29 +0400
committerAlexander Gavrilov2012-03-14 19:57:29 +0400
commit560e977f0589ac1c0feb6ea825d20d351e325826 (patch)
treee32bf2135261fa1e9129fae4c2c7ae068b6ef4cc /library/RemoteClient.cpp
parentc42e2ff053bc3acbded353112cd6412c8211f279 (diff)
downloaddfhack-560e977f0589ac1c0feb6ea825d20d351e325826.tar.gz
dfhack-560e977f0589ac1c0feb6ea825d20d351e325826.tar.bz2
dfhack-560e977f0589ac1c0feb6ea825d20d351e325826.tar.xz
Implement trivial RPC interface for dfhack via TCP & protobufs.
Use it to make an executable capable of calling commands remotely.
Diffstat (limited to 'library/RemoteClient.cpp')
-rw-r--r--library/RemoteClient.cpp359
1 files changed, 359 insertions, 0 deletions
diff --git a/library/RemoteClient.cpp b/library/RemoteClient.cpp
new file mode 100644
index 00000000..a8c80466
--- /dev/null
+++ b/library/RemoteClient.cpp
@@ -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;
+ }
+ }
+}