| /* |
| * 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) { |
| 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; |
| } |