/**
* Copyright (C) 2010 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include <alloca.h>
#include <pthread.h>
#include <stdio.h>
#include <string.h>
#include <sys/endian.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <unistd.h>
#include <cutils/sockets.h>
#include "logging.h"
#include "node_buffer.h"
#include "status.h"
#include "util.h"
#include "worker.h"
#include "msgheader.pb.h"
#include "ctrl.pb.h"
#include "ctrl_server.h"
//#define CONTROL_SERVER_DEBUG
#ifdef CONTROL_SERVER_DEBUG
#define DBG(...) LOGD(__VA_ARGS__)
#else
#define DBG(...)
#endif
#define MOCK_RIL_CONTROL_SERVER_STOPPING_SOCKET 54311
#define MOCK_RIL_CONTROL_SERVER_SOCKET 54312
using communication::MsgHeader;
class CtrlServerThread;
static CtrlServerThread *g_ctrl_server;
class CtrlServerThread : public WorkerThread {
private:
#define SOCKET_NAME_MOCK_RIL_CST_STOPPER "mock-ril-cst-stopper"
v8::Handle<v8::Context> context_;
int server_accept_socket_;
int server_to_client_socket_;
int stop_server_fd_;
int stop_client_fd_;
int stopper_fd_;
fd_set rd_fds_;
fd_set wr_fds_;
bool done_;
Buffer *ObtainBuffer(int length) {
Buffer *b = Buffer::New(length);
return b;
}
int WriteAll(int s, void *data, int length) {
int ret_value;
uint8_t *bytes = (uint8_t *)data;
int count = length;
while (length > 0) {
ret_value = send(s, bytes, length, 0);
if (ret_value < 0) {
return STATUS_ERR;
}
if (ret_value == 0) {
return STATUS_CLIENT_CLOSED_CONNECTION;
}
bytes += ret_value;
length -= ret_value;
}
return STATUS_OK;
}
int ReadAll(int s, void *data, int length) {
int ret_value;
uint8_t *bytes = (uint8_t *)data;
int count = length;
while (length != 0) {
ret_value = recv(s, bytes, length, 0);
if (ret_value < 0) {
return STATUS_ERR;
}
if (ret_value == 0) {
return STATUS_CLIENT_CLOSED_CONNECTION;
}
bytes += ret_value;
length -= ret_value;
}
return STATUS_OK;
}
int ReadMessage(MsgHeader *mh, Buffer **pBuffer) {
int status;
int32_t len_msg_header;
// Reader header length
status = ReadAll(server_to_client_socket_, &len_msg_header, sizeof(len_msg_header));
len_msg_header = letoh32(len_msg_header);
DBG("rm: read len_msg_header=%d status=%d", len_msg_header, status);
if (status != STATUS_OK) return status;
// Read header into an array allocated on the stack and unmarshall
uint8_t *msg_header_raw = (uint8_t *)alloca(len_msg_header);
status = ReadAll(server_to_client_socket_, msg_header_raw, len_msg_header);
DBG("rm: read msg_header_raw=%p status=%d", msg_header_raw, status);
if (status != STATUS_OK) return status;
mh->ParseFromArray(msg_header_raw, len_msg_header);
// Read auxillary data
Buffer *buffer;
if (mh->length_data() > 0) {
buffer = ObtainBuffer(mh->length_data());
status = ReadAll(server_to_client_socket_, buffer->data(), buffer->length());
DBG("rm: read protobuf status=%d", status);
if (status != STATUS_OK) return status;
} else {
DBG("rm: NO protobuf");
buffer = NULL;
}
*pBuffer = buffer;
return STATUS_OK;
}
public:
int WriteMessage(MsgHeader *mh, Buffer *buffer) {
int status;
uint32_t i;
uint64_t l;
// Set length of data
if (buffer == NULL) {
mh->set_length_data(0);
} else {
mh->set_length_data(buffer->length());
}
// Serialize header
uint32_t len_msg_header = mh->ByteSize();
uint8_t *msg_header_raw = (uint8_t *)alloca(len_msg_header);
mh->SerializeToArray(msg_header_raw, len_msg_header);
// Write length in little endian followed by the header
i = htole32(len_msg_header);
status = WriteAll(server_to_client_socket_, &i, 4);
DBG("wm: write len_msg_header=%d status=%d", len_msg_header, status);
if (status != 0) return status;
status = WriteAll(server_to_client_socket_, msg_header_raw, len_msg_header);
DBG("wm: write msg_header_raw=%p status=%d", msg_header_raw, status);
if (status != 0) return status;
// Write data
if (mh->length_data() > 0) {
status = WriteAll(server_to_client_socket_, buffer->data(), buffer->length());
DBG("wm: protobuf data=%p len=%d status=%d",
buffer->data(), buffer->length(), status);
if (status != 0) return status;
}
return STATUS_OK;
}
CtrlServerThread(v8::Handle<v8::Context> context) :
context_(context),
server_accept_socket_(-1),
server_to_client_socket_(-1),
done_(false) {
}
virtual int Run() {
DBG("CtrlServerThread::Run E");
// Create a server socket.
server_accept_socket_ = socket_inaddr_any_server(
MOCK_RIL_CONTROL_SERVER_SOCKET, SOCK_STREAM);
if (server_accept_socket_ < 0) {
LOGE("CtrlServerThread::Run error creating server_accept_socket_ '%s'",
strerror(errno));
return STATUS_ERR;
}
// Create a server socket that will be used for stopping
stop_server_fd_ = socket_loopback_server(
MOCK_RIL_CONTROL_SERVER_STOPPING_SOCKET, SOCK_STREAM);
if (stop_server_fd_ < 0) {
LOGE("CtrlServerThread::Run error creating stop_server_fd_ '%s'",
strerror(errno));
return STATUS_ERR;
}
// Create a client socket that will be used for sending a stop
stop_client_fd_ = socket_loopback_client(
MOCK_RIL_CONTROL_SERVER_STOPPING_SOCKET, SOCK_STREAM);
if (stop_client_fd_ < 0) {
LOGE("CtrlServerThread::Run error creating stop_client_fd_ '%s'",
strerror(errno));
return STATUS_ERR;
}
// Accept the connection of the stop_client_fd_
stopper_fd_ = accept(stop_server_fd_, NULL, NULL);
if (stopper_fd_ < 0) {
LOGE("CtrlServerThread::Run error accepting stop_client_fd '%s'",
strerror(errno));
return STATUS_ERR;
}
// Run the new thread
int ret_value = WorkerThread::Run(NULL);
DBG("CtrlServerThread::Run X");
return ret_value;
}
virtual void Stop() {
DBG("CtrlServerThread::Stop E");
if (BeginStopping()) {
done_ = true;
int rv = send(stop_client_fd_, &done_, sizeof(done_), 0);
if (rv <= 0) {
LOGE("CtrlServerThread::Stop could not send stop"
"WE WILL PROBABLY HANG");
}
WaitUntilStopped();
}
DBG("CtrlServerThread::Stop X");
}
virtual bool isRunning() {
bool rv = done_ || WorkerThread::isRunning();
return rv;
}
int WaitOnSocketOrStopping(fd_set *rfds, int s) {
DBG("WaitOnSocketOrStopping E s=%d stopper_fd_=%d", s, stopper_fd_);
FD_ZERO(rfds);
FD_SET(s, rfds);
FD_SET(stopper_fd_, rfds);
int fd_number = s > stopper_fd_ ? s + 1 : stopper_fd_ + 1;
v8::Unlocker unlocker;
int rv = select(fd_number, rfds, NULL, NULL, NULL);
v8::Locker locker;
DBG("WaitOnSocketOrStopping X rv=%d s=%d stopper_fd_=%d", rv, s, stopper_fd_);
return rv;
}
int sendToCtrlServer(MsgHeader *mh, Buffer *buffer) {
DBG("sendToCtrlServer E: cmd=%d token=%lld", mh->cmd(), mh->token());
int status = STATUS_OK;
v8::HandleScope handle_scope;
v8::TryCatch try_catch;
try_catch.SetVerbose(true);
// Get the onRilRequest Function
v8::Handle<v8::String> name = v8::String::New("onCtrlServerCmd");
v8::Handle<v8::Value> onCtrlServerCmdFunctionValue =
context_->Global()->Get(name);
v8::Handle<v8::Function> onCtrlServerCmdFunction =
v8::Handle<v8::Function>::Cast(onCtrlServerCmdFunctionValue);
// Create the CmdValue and TokenValue
v8::Handle<v8::Value> v8CmdValue = v8::Number::New(mh->cmd());
v8::Handle<v8::Value> v8TokenValue = v8::Number::New(mh->token());
// Invoke onRilRequest
const int argc = 3;
v8::Handle<v8::Value> buf;
if (mh->length_data() == 0) {
buf = v8::Undefined();
} else {
buf = buffer->handle_;
}
v8::Handle<v8::Value> argv[argc] = {
v8CmdValue, v8TokenValue, buf };
v8::Handle<v8::Value> result =
onCtrlServerCmdFunction->Call(context_->Global(), argc, argv);
if (try_catch.HasCaught()) {
ReportException(&try_catch);
status = STATUS_ERR;
} else {
v8::String::Utf8Value result_string(result);
DBG("sendToCtrlServer result=%s", ToCString(result_string));
status = STATUS_OK;
}
if (status != STATUS_OK) {
LOGE("sendToCtrlServer Error: status=%d", status);
// An error report complete now
mh->set_length_data(0);
mh->set_status(ril_proto::CTRL_STATUS_ERR);
g_ctrl_server->WriteMessage(mh, NULL);
}
DBG("sendToCtrlServer X: status=%d", status);
return status;
}
virtual void * Worker(void *param) {
DBG("CtrlServerThread::Worker E param=%p stopper_fd_=%d",
param, stopper_fd_);
v8::Locker locker;
v8::HandleScope handle_scope;
v8::Context::Scope context_scope(context_);
while (isRunning()) {
int ret_value;
// Wait on either server_accept_socket_ or stopping
DBG("CtrlServerThread::Worker wait on server for a client");
WaitOnSocketOrStopping(&rd_fds_, server_accept_socket_);
if (isRunning() != true) {
break;
}
if (FD_ISSET(server_accept_socket_, &rd_fds_)) {
server_to_client_socket_ = accept(server_accept_socket_, NULL, NULL);
DBG("CtrlServerThread::Worker accepted server_to_client_socket_=%d isRunning()=%d",
server_to_client_socket_, isRunning());
int status;
Buffer *buffer;
MsgHeader mh;
while ((server_to_client_socket_ > 0) && isRunning()) {
DBG("CtrlServerThread::Worker wait on client for message");
WaitOnSocketOrStopping(&rd_fds_, server_to_client_socket_);
if (isRunning() != true) {
break;
}
status = ReadMessage(&mh, &buffer);
if (status != STATUS_OK) break;
if (mh.cmd() == ril_proto::CTRL_CMD_ECHO) {
LOGD("CtrlServerThread::Worker echo");
status = WriteMessage(&mh, buffer);
if (status != STATUS_OK) break;
} else {
DBG("CtrlServerThread::Worker sendToCtrlServer");
status = sendToCtrlServer(&mh, buffer);
if (status != STATUS_OK) break;
}
}
close(server_to_client_socket_);
server_to_client_socket_ = -1;
}
}
close(stop_server_fd_);
stop_server_fd_ = -1;
close(stop_client_fd_);
stop_client_fd_ = -1;
close(stopper_fd_);
stopper_fd_ = -1;
close(server_accept_socket_);
server_accept_socket_ = -1;
DBG("CtrlServerThread::Worker X param=%p", param);
return NULL;
}
};
/**
* Send a control request complete response.
*/
v8::Handle<v8::Value> SendCtrlRequestComplete(const v8::Arguments& args) {
DBG("SendCtrlRequestComplete E:");
v8::HandleScope handle_scope;
v8::Handle<v8::Value> retValue;
void *data;
size_t datalen;
Buffer* buffer;
MsgHeader mh;
/**
* Get the arguments. There should be at least 3, reqNum,
* ril error code and token. Optionally a Buffer containing
* the protobuf representation of the data to return.
*/
if (args.Length() < 3) {
// Expecting a reqNum, ERROR and token
LOGE("SendCtrlRequestComplete X %d parameters"
" expecting at least 3: status, reqNum, and token",
args.Length());
return v8::Undefined();
}
v8::Handle<v8::Value> v8CtrlStatus(args[0]->ToObject());
mh.set_status(ril_proto::CtrlStatus(v8CtrlStatus->NumberValue()));
DBG("SendCtrlRequestComplete: status=%d", mh.status());
v8::Handle<v8::Value> v8ReqNum(args[1]->ToObject());
mh.set_cmd(int(v8ReqNum->NumberValue()));
DBG("SendCtrlRequestComplete: cmd=%d", mh.cmd());
v8::Handle<v8::Value> v8Token(args[2]->ToObject());
mh.set_token(int64_t(v8Token->NumberValue()));
DBG("SendCtrlRequestComplete: token=%lld", mh.token());
if (args.Length() >= 4) {
buffer = ObjectWrap::Unwrap<Buffer>(args[3]->ToObject());
mh.set_length_data(buffer->length());
DBG("SendCtrlRequestComplete: mh.length_data=%d",
mh.length_data());
} else {
mh.set_length_data(0);
buffer = NULL;
DBG("SendCtrlRequestComplete: NO PROTOBUF");
}
DBG("SendCtrlRequestComplete: WriteMessage");
int status = g_ctrl_server->WriteMessage(&mh, buffer);
DBG("SendCtrlRequestComplete E:");
return v8::Undefined();
}
void ctrlServerInit(v8::Handle<v8::Context> context) {
int status;
g_ctrl_server = new CtrlServerThread(context);
status = g_ctrl_server->Run();
if (status != STATUS_OK) {
LOGE("mock_ril control server could not start");
} else {
LOGD("CtrlServer started");
}
#if 0
LOGD("Test CtrlServerThread stop sleeping 10 seconds...");
v8::Unlocker unlocker;
sleep(10);
LOGD("Test CtrlServerThread call Stop");
g_ctrl_server->Stop();
v8::Locker locker;
// Restart
g_ctrl_server = new CtrlServerThread(context);
status = g_ctrl_server->Run();
if (status != STATUS_OK) {
LOGE("mock_ril control server could not start");
} else {
DBG("mock_ril control server started");
}
#endif
}