| /* | |
| * 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::Debugging) << "Waiting for response, flushed output" << Debug::flush; | |
| if (!readByte(type)) { | |
| if (isConnected()) { | |
| Debug::log(Debug::Debugging) << "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; | |
| } | |
| gwt::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; | |
| } | |
| gwt::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(gwt::Value& valueRef) { | |
| char typeBuf; | |
| if (!readByte(typeBuf)) return false; | |
| gwt::Value::ValueType type = gwt::Value::ValueType(typeBuf); | |
| switch (type) { | |
| case gwt::Value::NULL_TYPE: | |
| valueRef.setNull(); | |
| return true; | |
| case gwt::Value::UNDEFINED: | |
| valueRef.setUndefined(); | |
| return true; | |
| case gwt::Value::BOOLEAN: | |
| { | |
| char val; | |
| if (!readByte(val)) return false; | |
| valueRef.setBoolean(val != 0); | |
| } | |
| return true; | |
| case gwt::Value::BYTE: | |
| { | |
| char val; | |
| if (!readByte(val)) return false; | |
| valueRef.setByte(val); | |
| } | |
| return true; | |
| case gwt::Value::CHAR: | |
| { | |
| short val; | |
| if (!readShort(val)) return false; | |
| valueRef.setChar(val); | |
| } | |
| return true; | |
| case gwt::Value::SHORT: | |
| { | |
| short val; | |
| if (!readShort(val)) return false; | |
| valueRef.setShort(val); | |
| } | |
| return true; | |
| case gwt::Value::STRING: | |
| { | |
| std::string val; | |
| if (!readString(val)) return false; | |
| valueRef.setString(val); | |
| } | |
| return true; | |
| case gwt::Value::INT: | |
| { | |
| int val; | |
| if (!readInt(val)) return false; | |
| valueRef.setInt(val); | |
| } | |
| return true; | |
| case gwt::Value::LONG: | |
| { | |
| int64_t val; | |
| if (!readLong(val)) return false; | |
| valueRef.setLong(val); | |
| } | |
| return true; | |
| case gwt::Value::DOUBLE: | |
| { | |
| double val; | |
| if (!readDouble(val)) return false; | |
| valueRef.setDouble(val); | |
| } | |
| return true; | |
| case gwt::Value::JAVA_OBJECT: | |
| { | |
| int objId; | |
| if (!readInt(objId)) return false; | |
| valueRef.setJavaObject(objId); | |
| } | |
| return true; | |
| case gwt::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 gwt::Value& value) { | |
| gwt::Value::ValueType type = value.getType(); | |
| if (!sendByte(type)) return false; | |
| switch (type) { | |
| case gwt::Value::NULL_TYPE: | |
| case gwt::Value::UNDEFINED: | |
| // Null and Undefined only have the tag byte, no data | |
| return true; | |
| case gwt::Value::BOOLEAN: | |
| return sendByte(value.getBoolean() ? 1 : 0); | |
| case gwt::Value::BYTE: | |
| return sendByte(value.getByte()); | |
| case gwt::Value::CHAR: | |
| return sendShort(short(value.getChar())); | |
| case gwt::Value::SHORT: | |
| return sendShort(value.getShort()); | |
| case gwt::Value::INT: | |
| return sendInt(value.getInt()); | |
| case gwt::Value::LONG: | |
| return sendLong(value.getLong()); | |
| case gwt::Value::STRING: | |
| return sendString(value.getString()); | |
| case gwt::Value::DOUBLE: | |
| return sendDouble(value.getDouble()); | |
| case gwt::Value::FLOAT: | |
| return sendFloat(value.getFloat()); | |
| case gwt::Value::JS_OBJECT: | |
| return sendInt(value.getJsObjectId()); | |
| case gwt::Value::JAVA_OBJECT: | |
| return sendInt(value.getJavaObjectId()); | |
| default: | |
| Debug::log(Debug::Error) << "Unhandled value type sent to server: " << type << Debug::flush; | |
| break; | |
| } | |
| return false; | |
| } |