diff options
| author | Alexander Gavrilov | 2012-03-14 19:57:29 +0400 |
|---|---|---|
| committer | Alexander Gavrilov | 2012-03-14 19:57:29 +0400 |
| commit | 560e977f0589ac1c0feb6ea825d20d351e325826 (patch) | |
| tree | e32bf2135261fa1e9129fae4c2c7ae068b6ef4cc /library/RemoteClient.cpp | |
| parent | c42e2ff053bc3acbded353112cd6412c8211f279 (diff) | |
| download | dfhack-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.cpp | 359 |
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; + } + } +} |
