blob: bff1b21df79c026c07bdddd594886c3eec1bff9e [file] [log] [blame]
/*
* Copyright 2008 Google Inc.
*
* 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 <cstdio>
#include <cstdlib>
#include <cstring>
#include <cerrno>
#include "Debug.h"
#ifdef _WINDOWS
#include <winsock2.h>
#include <ws2tcpip.h>
#else
#include <netdb.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <sys/time.h>
#endif
#include <time.h>
#include "Platform.h"
#include "ByteOrder.h"
#include "CheckVersionsMessage.h"
#include "ProtocolVersionMessage.h"
#include "ChooseTransportMessage.h"
#include "SwitchTransportMessage.h"
#include "FatalErrorMessage.h"
#include "FreeValueMessage.h"
#include "HostChannel.h"
#include "LoadJsniMessage.h"
#include "InvokeMessage.h"
#include "InvokeSpecialMessage.h"
#include "QuitMessage.h"
#include "ReturnMessage.h"
#include "Value.h"
#include "scoped_ptr/scoped_ptr.h"
using namespace std;
ByteOrder HostChannel::byteOrder;
bool HostChannel::connectToHost(const char* host, unsigned port) {
if (!port) {
port = 9997;
}
Debug::log(Debug::Info)
<< "Initiating GWT Development Mode connection to host " << host
<< ", port " << port << Debug::flush;
return sock.connect(host, port);
}
bool HostChannel::init(SessionHandler* handler, int minProtoVers,
int maxProtoVers, const std::string& hostedHtmlVers) {
this->handler = handler;
Debug::log(Debug::Debugging)
<< " negotiating versions - we support protocol " << minProtoVers
<< " through " << maxProtoVers << ", hostedHtmlVersion=" << hostedHtmlVers
<< Debug::flush;
// TODO(jat): support transport selection
CheckVersionsMessage::send(*this, minProtoVers, maxProtoVers, hostedHtmlVers);
flush();
char type;
if (!readByte(type)) {
handler->fatalError(*this, "Failed to receive message type");
Debug::log(Debug::Error) << "Failed to receive message type" << Debug::flush;
disconnectFromHost();
return false;
}
switch (type) {
case MESSAGE_TYPE_PROTOCOL_VERSION:
{
scoped_ptr<ProtocolVersionMessage> imsg(ProtocolVersionMessage
::receive(*this));
if (!imsg.get()) {
Debug::log(Debug::Error) << "Failed to receive protocol version message"
<< Debug::flush;
return false;
}
// TODO(jat): save selected protocol version when we support a range
break;
}
case MESSAGE_TYPE_FATAL_ERROR:
{
scoped_ptr<FatalErrorMessage> imsg(FatalErrorMessage::receive(*this));
if (!imsg.get()) {
Debug::log(Debug::Error) << "Failed to receive fatal error message"
<< Debug::flush;
return false;
}
handler->fatalError(*this, imsg.get()->getError());
return false;
}
default:
return false;
}
return true;
}
bool HostChannel::disconnectFromHost() {
Debug::log(Debug::Debugging) << "Disconnecting channel" << Debug::flush;
if (!isConnected()) {
Debug::log(Debug::Error) << "Disconnecting already disconnected channel" << Debug::flush;
return false;
}
QuitMessage::send(*this);
flush();
sock.disconnect();
return true;
}
bool HostChannel::readInt(int32_t& data) {
int32_t d;
if (!readBytes(&d, sizeof(d))) return false;
data = ntohl(d);
return true;
}
bool HostChannel::sendInt(int32_t data) {
uint32_t d = htonl(data);
return sendBytes(&d, sizeof(d));
}
bool HostChannel::readShort(short& data) {
int16_t d;
if (!readBytes(&d, sizeof(d))) return false;
data = ntohs(d);
return true;
}
bool HostChannel::sendShort(const short data) {
uint16_t d = htons(data);
return sendBytes(&d, sizeof(d));
}
bool HostChannel::readLong(int64_t& data) {
// network is big-endian
int32_t d[2];
if (!readInt(d[0])) return false;
if (!readInt(d[1])) return false;
data = (static_cast<int64_t>(d[0]) << 32) | ntohl(d[1]);
return true;
}
bool HostChannel::sendLong(const int64_t data) {
if (!sendInt(static_cast<int32_t>(data >> 32))) {
return false;
}
return sendInt(static_cast<int32_t>(data));
}
bool HostChannel::readFloat(float& data) {
char bytes[sizeof(data)];
if (!readBytes(bytes, sizeof(bytes))) {
return false;
}
data = byteOrder.floatFromBytes(bytes);
return true;
}
bool HostChannel::sendFloat(const float data) {
char bytes[sizeof(data)];
byteOrder.bytesFromFloat(data, bytes);
return sendBytes(bytes, sizeof(bytes));
}
bool HostChannel::readDouble(double& data) {
char bytes[sizeof(data)];
if (!readBytes(bytes, sizeof(bytes))) {
return false;
}
data = byteOrder.doubleFromBytes(bytes);
return true;
}
bool HostChannel::sendDouble(const double data) {
char bytes[sizeof(data)];
byteOrder.bytesFromDouble(data, bytes);
return sendBytes(bytes, sizeof(bytes));
}
bool HostChannel::readStringLength(uint32_t& data) {
int32_t val;
if (!readInt(val)) return false;
// TODO: assert positive?
data = val;
return true;
}
bool HostChannel::readStringBytes(char* data, const uint32_t len) {
return readBytes(data, len);
}
bool HostChannel::readString(std::string& strRef) {
uint32_t len;
if (!readStringLength(len)) {
Debug::log(Debug::Error) << "readString: failed to read length"
<< Debug::flush;
return false;
}
// Allocating variable-length arrays on the stack is a GCC feature,
// and is vulnerable to stack overflow attacks, so we allocate on the heap.
scoped_array<char> buf(new char[len]);
if (!readStringBytes(buf.get(), len)) {
Debug::log(Debug::Error) << "readString: failed to read " << len
<< " bytes" << Debug::flush;
return false;
}
strRef.assign(buf.get(), len);
return true;
}
static inline double operator-(const struct timeval& end,
const struct timeval& begin) {
double us = end.tv_sec * 1000000.0 + end.tv_usec - begin.tv_sec * 1000000.0
- begin.tv_usec;
return us;
}
ReturnMessage* HostChannel::reactToMessages(SessionHandler* handler, bool expectReturn) {
char type;
while (true) {
flush();
Debug::log(Debug::Spam) << "Waiting for response, flushed output"
<< Debug::flush;
if (!readByte(type)) {
if (isConnected()) {
Debug::log(Debug::Error) << "Failed to receive message type"
<< Debug::flush;
}
return 0;
}
switch (type) {
case MESSAGE_TYPE_INVOKE:
{
scoped_ptr<InvokeMessage> imsg(InvokeMessage::receive(*this));
if (!imsg.get()) {
Debug::log(Debug::Error) << "Failed to receive invoke message" << Debug::flush;
return 0;
}
Value returnValue;
bool exception = handler->invoke(*this, imsg->getThis(), imsg->getMethodName(),
imsg->getNumArgs(), imsg->getArgs(), &returnValue);
handler->sendFreeValues(*this);
ReturnMessage::send(*this, exception, returnValue);
}
break;
case MESSAGE_TYPE_INVOKESPECIAL:
{
// scottb: I think this is never used; I think server never sends invokeSpecial
scoped_ptr<InvokeSpecialMessage> imsg(InvokeSpecialMessage::receive(*this));
if (!imsg.get()) {
Debug::log(Debug::Error) << "Failed to receive invoke special message" << Debug::flush;
return 0;
}
Value returnValue;
bool exception = handler->invokeSpecial(*this, imsg->getDispatchId(),
imsg->getNumArgs(), imsg->getArgs(), &returnValue);
handler->sendFreeValues(*this);
ReturnMessage::send(*this, exception, returnValue);
}
break;
case MESSAGE_TYPE_FREEVALUE:
{
scoped_ptr<FreeValueMessage> freeMsg(FreeValueMessage::receive(*this));
if (!freeMsg.get()) {
Debug::log(Debug::Error) << "Failed to receive free value message" << Debug::flush;
return 0;
}
handler->freeValue(*this, freeMsg->getIdCount(), freeMsg->getIds());
}
// do not send a response
break;
case MESSAGE_TYPE_LOADJSNI:
{
scoped_ptr<LoadJsniMessage> loadMsg(LoadJsniMessage::receive(*this));
if (!loadMsg.get()) {
Debug::log(Debug::Error) << "Failed to receive load JSNI message" << Debug::flush;
return 0;
}
handler->loadJsni(*this, loadMsg->getJs());
}
// do not send a response
break;
case MESSAGE_TYPE_RETURN:
if (!expectReturn) {
Debug::log(Debug::Error) << "Received unexpected RETURN" << Debug::flush;
}
return ReturnMessage::receive(*this);
case MESSAGE_TYPE_QUIT:
if (expectReturn) {
Debug::log(Debug::Error) << "Received QUIT while waiting for return" << Debug::flush;
}
disconnectFromHost();
return 0;
default:
// TODO(jat): error handling
Debug::log(Debug::Error) << "Unexpected message type " << type
<< ", expectReturn=" << expectReturn << Debug::flush;
disconnectFromHost();
return 0;
}
}
}
bool HostChannel::readValue(Value& valueRef) {
char typeBuf;
if (!readByte(typeBuf)) return false;
Value::ValueType type = Value::ValueType(typeBuf);
switch (type) {
case Value::NULL_TYPE:
valueRef.setNull();
return true;
case Value::UNDEFINED:
valueRef.setUndefined();
return true;
case Value::BOOLEAN:
{
char val;
if (!readByte(val)) return false;
valueRef.setBoolean(val != 0);
}
return true;
case Value::BYTE:
{
char val;
if (!readByte(val)) return false;
valueRef.setByte(val);
}
return true;
case Value::CHAR:
{
short val;
if (!readShort(val)) return false;
valueRef.setChar(val);
}
return true;
case Value::SHORT:
{
short val;
if (!readShort(val)) return false;
valueRef.setShort(val);
}
return true;
case Value::STRING:
{
std::string val;
if (!readString(val)) return false;
valueRef.setString(val);
}
return true;
case Value::INT:
{
int val;
if (!readInt(val)) return false;
valueRef.setInt(val);
}
return true;
case Value::LONG:
{
int64_t val;
if (!readLong(val)) return false;
valueRef.setLong(val);
}
return true;
case Value::DOUBLE:
{
double val;
if (!readDouble(val)) return false;
valueRef.setDouble(val);
}
return true;
case Value::JAVA_OBJECT:
{
int objId;
if (!readInt(objId)) return false;
valueRef.setJavaObject(objId);
}
return true;
case Value::JS_OBJECT:
{
int val;
if (!readInt(val)) return false;
valueRef.setJsObjectId(val);
}
return true;
default:
Debug::log(Debug::Error) << "Unhandled value type sent from server: " << type << Debug::flush;
break;
}
return false;
}
bool HostChannel::sendValue(const Value& value) {
Value::ValueType type = value.getType();
if (!sendByte(type)) return false;
switch (type) {
case Value::NULL_TYPE:
case Value::UNDEFINED:
// Null and Undefined only have the tag byte, no data
return true;
case Value::BOOLEAN:
return sendByte(value.getBoolean() ? 1 : 0);
case Value::BYTE:
return sendByte(value.getByte());
case Value::CHAR:
return sendShort(short(value.getChar()));
case Value::SHORT:
return sendShort(value.getShort());
case Value::INT:
return sendInt(value.getInt());
case Value::LONG:
return sendLong(value.getLong());
case Value::STRING:
return sendString(value.getString());
case Value::DOUBLE:
return sendDouble(value.getDouble());
case Value::FLOAT:
return sendFloat(value.getFloat());
case Value::JS_OBJECT:
return sendInt(value.getJsObjectId());
case Value::JAVA_OBJECT:
return sendInt(value.getJavaObjectId());
default:
Debug::log(Debug::Error) << "Unhandled value type sent to server: " << type << Debug::flush;
break;
}
return false;
}