blob: 8b80066b6c5d22dbe2df7da18d2ad0f8b0134999 [file] [log] [blame]
// Copyright 2010-2015, Google Inc.
// All rights reserved.
//
// 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 Google Inc. 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.
#ifdef OS_WIN
// Do not change the order of the following headers required for Windows.
#include <winsock2.h>
#include <windows.h>
#include <ws2tcpip.h>
#pragma comment(lib, "ws2_32.lib")
#define ssize_t SSIZE_T
#else
#include <fcntl.h>
#include <netdb.h>
#include <netinet/in.h>
#include <sys/socket.h>
#include <unistd.h>
#endif // OS_WIN
#include <cstddef>
#include <cstring>
#include <vector>
#include <string>
#include "base/number_util.h"
#include "base/singleton.h"
#include "base/scoped_ptr.h"
#include "base/system_util.h"
#include "engine/engine_factory.h"
#include "session/commands.pb.h"
#include "session/random_keyevents_generator.h"
#include "session/session_handler.h"
#include "session/session_usage_observer.h"
DEFINE_string(host, "localhost", "server host name");
DEFINE_bool(server, true, "server mode");
DEFINE_bool(client, false, "client mode");
DEFINE_int32(client_test_size, 100, "client test size");
DEFINE_int32(port, 8000, "port of RPC server");
DEFINE_int32(rpc_timeout, 60000, "timeout");
DEFINE_string(user_profile_directory, "", "user profile directory");
namespace mozc {
namespace {
const size_t kMaxRequestSize = 32 * 32 * 8192;
const size_t kMaxOutputSize = 32 * 32 * 8192;
const int kInvalidSocket = -1;
// TODO(taku): timeout should be handled.
bool Recv(int socket, char *buf,
size_t buf_size, int timeout) {
ssize_t buf_left = buf_size;
while (buf_left > 0) {
const ssize_t read_size = ::recv(socket, buf, buf_left, 0);
if (read_size < 0) {
LOG(ERROR) << "an error occurred during recv()";
return false;
}
buf += read_size;
buf_left -= read_size;
}
return buf_left == 0;
}
// TODO(taku): timeout should be handled.
bool Send(int socket, const char *buf,
size_t buf_size, int timeout) {
ssize_t buf_left = buf_size;
while (buf_left > 0) {
#if defined(OS_WIN)
const int kFlag = 0;
#elif defined(OS_MACOSX)
const int kFlag = SO_NOSIGPIPE;
#else
const int kFlag = MSG_NOSIGNAL;
#endif
const ssize_t read_size = ::send(socket, buf, buf_left, kFlag);
if (read_size < 0) {
LOG(ERROR) << "an error occurred during sending";
return false;
}
buf += read_size;
buf_left -= read_size;
}
return buf_left == 0;
}
void CloseSocket(int client_socket) {
#ifdef OS_WIN
::closesocket(client_socket);
::shutdown(client_socket, SD_BOTH);
#else
::close(client_socket);
::shutdown(client_socket, SHUT_RDWR);
#endif
}
// Standalone RPCServer.
// TODO(taku): Make a RPC class inherited from IPCInterface.
// This allows us to reuse client::Session library and SessionServer.
class RPCServer {
public:
RPCServer() : server_socket_(kInvalidSocket),
engine_(EngineFactory::Create()),
handler_(new SessionHandler(engine_.get())) {
struct sockaddr_in sin;
server_socket_ = ::socket(AF_INET, SOCK_STREAM, 0);
CHECK_NE(server_socket_, kInvalidSocket) << "socket failed";
#ifndef OS_WIN
int flags = ::fcntl(server_socket_, F_GETFD, 0);
CHECK_GE(flags, 0) << "fcntl(F_GETFD) failed";
flags |= FD_CLOEXEC;
CHECK_EQ(::fcntl(server_socket_, F_SETFD, flags), 0)
<< "fctl(F_SETFD) failed";
#endif
::memset(&sin, 0, sizeof(sin));
sin.sin_port = htons(FLAGS_port);
sin.sin_family = AF_INET;
sin.sin_addr.s_addr = htonl(INADDR_ANY);
int on = 1;
::setsockopt(server_socket_,
SOL_SOCKET,
SO_REUSEADDR, reinterpret_cast<char * >(&on),
sizeof(on));
CHECK_GE(::bind(server_socket_,
reinterpret_cast<struct sockaddr *>(&sin),
sizeof(sin)), 0) << "bind failed";
CHECK_GE(::listen(server_socket_, SOMAXCONN), 0) << "listen failed";
CHECK_NE(server_socket_, 0);
handler_->AddObserver(Singleton<session::SessionUsageObserver>::get());
}
~RPCServer() {
CloseSocket(server_socket_);
server_socket_ = kInvalidSocket;
}
void Loop() {
LOG(INFO) << "Start Mozc RPCServer";
while (true) {
const int client_socket = ::accept(server_socket_, NULL, NULL);
if (client_socket == kInvalidSocket) {
LOG(ERROR) << "accept failed";
continue;
}
uint32 request_size = 0;
// Receive the size of data.
if (!Recv(client_socket, reinterpret_cast<char *>(&request_size),
sizeof(request_size), FLAGS_rpc_timeout)) {
LOG(ERROR) << "Cannot receive request_size header.";
CloseSocket(client_socket);
continue;
}
request_size = ntohl(request_size);
CHECK_GT(request_size, 0);
CHECK_LT(request_size, kMaxRequestSize);
// Receive the body of serialized protobuf.
scoped_ptr<char[]> request_str(new char[request_size]);
if (!Recv(client_socket,
request_str.get(), request_size, FLAGS_rpc_timeout)) {
LOG(ERROR) << "cannot receive body of request.";
CloseSocket(client_socket);
continue;
}
commands::Command command;
if (!command.mutable_input()->ParseFromArray(request_str.get(),
request_size)) {
LOG(ERROR) << "ParseFromArray failed";
CloseSocket(client_socket);
continue;
}
CHECK(handler_->EvalCommand(&command));
string output_str;
// Return the result.
CHECK(command.output().SerializeToString(&output_str));
uint32 output_size = output_str.size();
CHECK_GT(output_size, 0);
CHECK_LT(output_size, kMaxOutputSize);
output_size = htonl(output_size);
if (!Send(client_socket, reinterpret_cast<char *>(&output_size),
sizeof(output_size), FLAGS_rpc_timeout) ||
!Send(client_socket, output_str.data(), output_str.size(),
FLAGS_rpc_timeout)) {
LOG(ERROR) << "Cannot send reply.";
}
CloseSocket(client_socket);
}
}
private:
int server_socket_;
scoped_ptr<EngineInterface> engine_;
scoped_ptr<SessionHandler> handler_;
};
// Standalone RPCClient.
// TODO(taku): Make a RPC class inherited from IPCInterface.
// This allows us to reuse client::Session library and SessionServer.
class RPCClient {
public:
RPCClient() : id_(0) {}
bool CreateSession() {
id_ = 0;
commands::Input input;
commands::Output output;
input.set_type(commands::Input::CREATE_SESSION);
if (!Call(input, &output) ||
output.error_code() != commands::Output::SESSION_SUCCESS) {
return false;
}
id_ = output.id();
return true;
}
bool DeleteSession() {
commands::Input input;
commands::Output output;
id_ = 0;
input.set_type(commands::Input::DELETE_SESSION);
return (Call(input, &output) &&
output.error_code() == commands::Output::SESSION_SUCCESS);
}
bool SendKey(const mozc::commands::KeyEvent &key,
mozc::commands::Output *output) const {
if (id_ == 0) {
return false;
}
commands::Input input;
input.set_type(commands::Input::SEND_KEY);
input.set_id(id_);
input.mutable_key()->CopyFrom(key);
return (Call(input, output) &&
output->error_code() == commands::Output::SESSION_SUCCESS);
}
private:
bool Call(const commands::Input &input,
commands::Output *output) const {
struct addrinfo hints, *res;
::memset(&hints, 0, sizeof(hints));
hints.ai_socktype = SOCK_STREAM;
hints.ai_family = AF_INET;
const string port_str = NumberUtil::SimpleItoa(FLAGS_port);
CHECK_EQ(::getaddrinfo(FLAGS_host.c_str(), port_str.c_str(),
&hints, &res), 0)
<< "getaddrinfo failed";
const int client_socket = ::socket(res->ai_family,
res->ai_socktype,
res->ai_protocol);
CHECK_NE(client_socket, kInvalidSocket) << "socket failed";
CHECK_GE(::connect(client_socket,
res->ai_addr, res->ai_addrlen), 0)
<< "connect failed";
string request_str;
CHECK(input.SerializeToString(&request_str));
uint32 request_size = request_str.size();
CHECK_GT(request_size, 0);
CHECK_LT(request_size, kMaxRequestSize);
request_size = htonl(request_size);
CHECK(Send(client_socket, reinterpret_cast<char *>(&request_size),
sizeof(request_size), FLAGS_rpc_timeout));
CHECK(Send(client_socket, request_str.data(), request_str.size(),
FLAGS_rpc_timeout));
uint32 output_size = 0;
CHECK(Recv(client_socket, reinterpret_cast<char *>(&output_size),
sizeof(output_size), FLAGS_rpc_timeout));
output_size = ntohl(output_size);
CHECK_GT(output_size, 0);
CHECK_LT(output_size, kMaxOutputSize);
scoped_ptr<char[]> output_str(new char[output_size]);
CHECK(Recv(client_socket,
output_str.get(), output_size, FLAGS_rpc_timeout));
CHECK(output->ParseFromArray(output_str.get(), output_size));
::freeaddrinfo(res);
CloseSocket(client_socket);
return true;
}
uint64 id_;
};
// Wrapper class for WSAStartup on Windows.
class ScopedWSAData {
public:
ScopedWSAData() {
#ifdef OS_WIN
WSADATA wsaData;
CHECK_EQ(::WSAStartup(MAKEWORD(2, 1), &wsaData), 0)
<< "WSAStartup failed";
#endif
}
~ScopedWSAData() {
#ifdef OS_WIN
::WSACleanup();
#endif
}
};
} // namespace
} // namespace mozc
int main(int argc, char *argv[]) {
InitGoogle(argv[0], &argc, &argv, false);
mozc::ScopedWSAData wsadata;
if (!FLAGS_user_profile_directory.empty()) {
LOG(INFO) << "Setting user profile directory to "
<< FLAGS_user_profile_directory;
mozc::SystemUtil::SetUserProfileDirectory(FLAGS_user_profile_directory);
}
if (FLAGS_client) {
mozc::RPCClient client;
CHECK(client.CreateSession());
for (int n = 0; n < FLAGS_client_test_size; ++n) {
vector<mozc::commands::KeyEvent> keys;
mozc::session::RandomKeyEventsGenerator::GenerateSequence(&keys);
for (size_t i = 0; i < keys.size(); ++i) {
LOG(INFO) << "Sending to Server: " << keys[i].Utf8DebugString();
mozc::commands::Output output;
CHECK(client.SendKey(keys[i], &output));
LOG(INFO) << "Output of SendKey: " << output.Utf8DebugString();
}
}
CHECK(client.DeleteSession());
return 0;
} else if (FLAGS_server) {
mozc::RPCServer server;
server.Loop();
} else {
LOG(ERROR) << "use --server or --client option";
return -1;
}
return 0;
}