Arquivos
hhvm/hphp/runtime/ext/thrift/compact.cpp
T
Misha Shneerson 2e72a2aba5 Do not suppress exceptions thrown by PHP transport
Currerntly, if TServiceRouterTransport::flush throws an exception,
this exception is swallowed non-chalantly by Thrift's extension layer.
This should not happen.
2013-05-30 17:33:00 -07:00

941 linhas
24 KiB
C++

/*
+----------------------------------------------------------------------+
| HipHop for PHP |
+----------------------------------------------------------------------+
| Copyright (c) 2010- Facebook, Inc. (http://www.facebook.com) |
| Copyright (c) 1997-2010 The PHP Group |
+----------------------------------------------------------------------+
| This source file is subject to version 3.01 of the PHP license, |
| that is bundled with this package in the file LICENSE, and is |
| available through the world-wide-web at the following url: |
| http://www.php.net/license/3_01.txt |
| If you did not receive a copy of the PHP license and are unable to |
| obtain it through the world-wide-web, please send a note to |
| license@php.net so we can mail you a copy immediately. |
+----------------------------------------------------------------------+
*/
#include "hphp/runtime/base/util/request_local.h"
#include "hphp/runtime/ext/thrift/transport.h"
#include "hphp/runtime/ext/ext_reflection.h"
#include "hphp/runtime/ext/ext_thrift.h"
#include <stack>
#include <utility>
namespace HPHP {
static const uint8_t VERSION_MASK = 0x1f;
static const uint8_t VERSION = 2;
static const uint8_t VERSION_LOW = 1;
static const uint8_t VERSION_DOUBLE_BE = 2;
static const uint8_t PROTOCOL_ID = 0x82;
static const uint8_t TYPE_MASK = 0xe0;
static const uint8_t TYPE_SHIFT_AMOUNT = 5;
enum CState {
STATE_CLEAR,
STATE_FIELD_WRITE,
STATE_VALUE_WRITE,
STATE_CONTAINER_WRITE,
STATE_BOOL_WRITE,
STATE_FIELD_READ,
STATE_CONTAINER_READ,
STATE_VALUE_READ,
STATE_BOOL_READ
};
enum CType {
C_STOP = 0x00,
C_TRUE = 0x01,
C_FALSE = 0x02,
C_BYTE = 0x03,
C_I16 = 0x04,
C_I32 = 0x05,
C_I64 = 0x06,
C_DOUBLE = 0x07,
C_BINARY = 0x08,
C_LIST = 0x09,
C_SET = 0x0A,
C_MAP = 0x0B,
C_STRUCT = 0x0C
};
enum CListType {
C_LIST_LIST,
C_LIST_SET
};
enum TResponseType {
T_REPLY = 2,
T_EXCEPTION = 3
};
static CType ttype_to_ctype(TType x) {
switch (x) {
case T_STOP:
return C_STOP;
case T_BOOL:
return C_TRUE;
case T_BYTE:
return C_BYTE;
case T_I16:
return C_I16;
case T_I32:
return C_I32;
case T_I64:
return C_I64;
case T_DOUBLE:
return C_DOUBLE;
case T_STRING:
return C_BINARY;
case T_STRUCT:
return C_STRUCT;
case T_LIST:
return C_LIST;
case T_SET:
return C_SET;
case T_MAP:
return C_MAP;
default:
throw InvalidArgumentException("unknown TType", x);
}
}
static TType ctype_to_ttype(CType x) {
switch (x) {
case C_STOP:
return T_STOP;
case C_TRUE:
case C_FALSE:
return T_BOOL;
case C_BYTE:
return T_BYTE;
case C_I16:
return T_I16;
case C_I32:
return T_I32;
case C_I64:
return T_I64;
case C_DOUBLE:
return T_DOUBLE;
case C_BINARY:
return T_STRING;
case C_STRUCT:
return T_STRUCT;
case C_LIST:
return T_LIST;
case C_SET:
return T_SET;
case C_MAP:
return T_MAP;
default:
throw InvalidArgumentException("unknown CType", x);
}
}
enum TError {
ERR_UNKNOWN = 0,
ERR_INVALID_DATA = 1,
ERR_BAD_VERSION = 4
};
class CompactRequestData : public RequestEventHandler {
public:
CompactRequestData() : version(VERSION) { }
void clear() { version = VERSION; }
virtual void requestInit() {
clear();
}
virtual void requestShutdown() {
clear();
}
uint8_t version;
};
IMPLEMENT_STATIC_REQUEST_LOCAL(CompactRequestData, s_compact_request_data);
static void thrift_error(CStrRef what, TError why) ATTRIBUTE_NORETURN;
static void thrift_error(CStrRef what, TError why) {
throw create_object("TProtocolException", CREATE_VECTOR2(what, why));
}
class CompactWriter {
public:
explicit CompactWriter(PHPOutputTransport *transport) :
transport(transport),
version(VERSION),
state(STATE_CLEAR),
lastFieldNum(0),
boolFieldNum(0),
structHistory(),
containerHistory() {
}
void setWriteVersion(uint8_t _version) {
version = _version;
}
void writeHeader(CStrRef name, uint8_t msgtype, uint32_t seqid) {
writeUByte(PROTOCOL_ID);
writeUByte(version | (msgtype << TYPE_SHIFT_AMOUNT));
writeVarint(seqid);
writeString(name);
state = STATE_VALUE_WRITE;
}
void write(CObjRef obj) {
writeStruct(obj);
}
private:
PHPOutputTransport* transport;
uint8_t version;
CState state;
uint16_t lastFieldNum;
uint16_t boolFieldNum;
std::stack<std::pair<CState, uint16_t> > structHistory;
std::stack<CState> containerHistory;
void writeStruct(CObjRef obj) {
// Save state
structHistory.push(std::make_pair(state, lastFieldNum));
state = STATE_FIELD_WRITE;
lastFieldNum = 0;
// Get field specification
CArrRef spec = f_hphp_get_static_property(obj->o_getClassName(), "_TSPEC")
.toArray();
// Write each member
for (ArrayIter specIter = spec.begin(); !specIter.end(); ++specIter) {
Variant key = specIter.first();
if (!key.isInteger()) {
thrift_error("Bad keytype in TSPEC (expected 'long')",
ERR_INVALID_DATA);
}
uint16_t fieldNo = key.toInt16();
Array fieldSpec = specIter.second().toArray();
String fieldName = fieldSpec
.rvalAt(s_var, AccessFlags::Error_Key).toString();
TType fieldType = (TType)fieldSpec
.rvalAt(s_type, AccessFlags::Error_Key).toByte();
Variant fieldVal = obj->o_get(fieldName, true, obj->o_getClassName());
if (!fieldVal.isNull()) {
writeFieldBegin(fieldNo, fieldType);
writeField(fieldVal, fieldSpec, fieldType);
writeFieldEnd();
}
}
// Write stop
writeUByte(0);
// Restore state
std::pair<CState, uint16_t> prev = structHistory.top();
state = prev.first;
lastFieldNum = prev.second;
structHistory.pop();
}
void writeFieldBegin(uint16_t fieldNum, TType fieldType) {
if (fieldType == T_BOOL) {
state = STATE_BOOL_WRITE;
boolFieldNum = fieldNum;
// the value and header are written together in writeField
} else {
state = STATE_VALUE_WRITE;
writeFieldHeader(fieldNum, ttype_to_ctype(fieldType));
}
}
void writeFieldEnd(void) {
state = STATE_FIELD_WRITE;
}
void writeFieldHeader(uint16_t fieldNum, CType fieldType) {
int delta = fieldNum - lastFieldNum;
if (0 < delta && delta <= 15) {
writeUByte((delta << 4) | fieldType);
} else {
writeUByte(fieldType);
writeI(fieldNum);
}
lastFieldNum = fieldNum;
}
void writeField(CVarRef value,
CArrRef valueSpec,
TType type) {
switch (type) {
case T_STOP:
case T_VOID:
break;
case T_STRUCT:
if (!value.is(KindOfObject)) {
thrift_error("Attempt to send non-object type as T_STRUCT",
ERR_INVALID_DATA);
}
writeStruct(value);
break;
case T_BOOL: {
bool b = value.toBoolean();
if (state == STATE_BOOL_WRITE) {
CType t = b ? C_TRUE : C_FALSE;
writeFieldHeader(boolFieldNum, t);
} else if (state == STATE_CONTAINER_WRITE) {
writeUByte(b ? C_TRUE : C_FALSE);
} else {
thrift_error("Invalid state in compact protocol", ERR_UNKNOWN);
}
}
break;
case T_BYTE:
writeUByte(value.toByte());
break;
case T_I16:
case T_I32:
case T_I64:
case T_U64:
writeI(value.toInt64());
break;
case T_DOUBLE: {
union {
uint64_t i;
double d;
} u;
u.d = value.toDouble();
uint64_t bits;
if (version >= VERSION_DOUBLE_BE) {
bits = htonll(u.i);
} else {
bits = htolell(u.i);
}
transport->write((char*)&bits, 8);
}
break;
case T_UTF8:
case T_UTF16:
case T_STRING: {
String s = value.toString();
auto slice = s.slice();
writeVarint(slice.len);
transport->write(slice.ptr, slice.len);
break;
}
case T_MAP:
writeMap(value.toArray(), valueSpec);
break;
case T_LIST:
writeList(value.toArray(), valueSpec, C_LIST_LIST);
break;
case T_SET:
writeList(value.toArray(), valueSpec, C_LIST_SET);
break;
default:
throw InvalidArgumentException("unknown TType", type);
}
}
void writeMap(Array arr, CArrRef spec) {
TType keyType = (TType)spec
.rvalAt(s_ktype, AccessFlags::Error_Key).toByte();
TType valueType = (TType)spec
.rvalAt(s_vtype, AccessFlags::Error_Key).toByte();
Array keySpec = spec.rvalAt(s_key, AccessFlags::Error_Key).toArray();
Array valueSpec = spec.rvalAt(s_val, AccessFlags::Error_Key).toArray();
writeMapBegin(keyType, valueType, arr.size());
for (ArrayIter arrIter = arr.begin(); !arrIter.end(); ++arrIter) {
writeField(arrIter.first(), keySpec, keyType);
writeField(arrIter.second(), valueSpec, valueType);
}
writeCollectionEnd();
}
void writeList(Array arr, CArrRef spec, CListType listType) {
TType valueType = (TType)spec
.rvalAt(s_etype, AccessFlags::Error_Key).toByte();
Array valueSpec = spec
.rvalAt(s_elem, AccessFlags::Error_Key).toArray();
writeListBegin(valueType, arr.size());
for (ArrayIter arrIter = arr.begin(); !arrIter.end(); ++arrIter) {
Variant x;
if (listType == C_LIST_LIST) {
x = arrIter.second();
} else if (listType == C_LIST_SET) {
x = arrIter.first();
}
writeField(x, valueSpec, valueType);
}
writeCollectionEnd();
}
void writeMapBegin(TType keyType, TType valueType, uint32_t size) {
if (size == 0) {
writeUByte(0);
} else {
writeVarint(size);
CType keyCType = ttype_to_ctype(keyType);
CType valueCType = ttype_to_ctype(valueType);
writeUByte((keyCType << 4) | valueCType);
}
containerHistory.push(state);
state = STATE_CONTAINER_WRITE;
}
void writeListBegin(TType elemType, uint32_t size) {
if (size <= 14) {
writeUByte((size << 4) | ttype_to_ctype(elemType));
} else {
writeUByte(0xf0 | ttype_to_ctype(elemType));
writeVarint(size);
}
containerHistory.push(state);
state = STATE_CONTAINER_WRITE;
}
void writeCollectionEnd(void) {
state = containerHistory.top();
containerHistory.pop();
}
void writeUByte(uint8_t n) {
transport->writeI8(n);
}
void writeI(int64_t n) {
writeVarint(i64ToZigzag(n));
}
void writeVarint(uint64_t n) {
uint8_t buf[10];
uint8_t wsize = 0;
while (true) {
if ((n & ~0x7FL) == 0) {
buf[wsize++] = (int8_t)n;
break;
} else {
buf[wsize++] = (int8_t)((n & 0x7F) | 0x80);
n >>= 7;
}
}
transport->write((char*)buf, wsize);
}
void writeString(CStrRef s) {
auto slice = s.slice();
writeVarint(slice.len);
transport->write(slice.ptr, slice.len);
}
uint64_t i64ToZigzag(int64_t n) {
return (n << 1) ^ (n >> 63);
}
};
class CompactReader {
public:
explicit CompactReader(CObjRef _transportobj) :
transport(_transportobj),
version(VERSION),
state(STATE_CLEAR),
lastFieldNum(0),
boolValue(true),
structHistory(),
containerHistory() {
}
Variant read(CStrRef resultClassName) {
uint8_t protoId = readUByte();
if (protoId != PROTOCOL_ID) {
thrift_error("Bad protocol id in TCompact message", ERR_BAD_VERSION);
}
uint8_t versionAndType = readUByte();
uint8_t type = (versionAndType & TYPE_MASK) >> TYPE_SHIFT_AMOUNT;
version = versionAndType & VERSION_MASK;
if (version < VERSION_LOW || version > VERSION) {
thrift_error("Bad version in TCompact message", ERR_BAD_VERSION);
}
// TODO: we eventually want to return seqid to the caller
/*uint32_t seqid =*/ readVarint(); // unused
/*Variant name =*/ readString(); // unused
if (type == T_REPLY) {
Object ret = create_object(resultClassName, Array());
Variant spec = f_hphp_get_static_property(resultClassName, "_TSPEC");
readStruct(ret, spec);
return ret;
} else if (type == T_EXCEPTION) {
Object exn = create_object("TApplicationException", Array());
Variant spec = f_hphp_get_static_property("TApplicationException", "_TSPEC");
readStruct(exn, spec);
throw exn;
} else {
thrift_error("Invalid response type", ERR_INVALID_DATA);
}
}
private:
PHPInputTransport transport;
uint8_t version;
CState state;
uint16_t lastFieldNum;
bool boolValue;
std::stack<std::pair<CState, uint16_t> > structHistory;
std::stack<CState> containerHistory;
void readStruct(CObjRef dest, CArrRef spec) {
readStructBegin();
while (true) {
int16_t fieldNum;
TType fieldType;
readFieldBegin(fieldNum, fieldType);
if (fieldType == T_STOP) {
break;
}
bool readComplete = false;
Variant fieldSpecVariant = spec.rvalAt(fieldNum);
if (!fieldSpecVariant.isNull()) {
Array fieldSpec = fieldSpecVariant.toArray();
String fieldName = fieldSpec.rvalAt(s_var).toString();
TType expectedType = (TType)fieldSpec.rvalAt(s_type).toInt64();
if (typesAreCompatible(fieldType, expectedType)) {
readComplete = true;
Variant fieldValue = readField(fieldSpec, fieldType);
dest->o_set(fieldName, fieldValue, dest->o_getClassName());
}
}
if (!readComplete) {
skip(fieldType);
}
readFieldEnd();
}
readStructEnd();
}
void readStructBegin(void) {
structHistory.push(std::make_pair(state, lastFieldNum));
state = STATE_FIELD_READ;
lastFieldNum = 0;
}
void readStructEnd(void) {
std::pair<CState, uint16_t> prev = structHistory.top();
state = prev.first;
lastFieldNum = prev.second;
structHistory.pop();
}
void readFieldBegin(int16_t &fieldNum, TType &fieldType) {
uint8_t fieldTypeAndDelta = readUByte();
int delta = fieldTypeAndDelta >> 4;
CType fieldCType = (CType)(fieldTypeAndDelta & 0x0f);
fieldType = ctype_to_ttype(fieldCType);
if (fieldCType == C_STOP) {
fieldNum = 0;
return;
}
if (delta == 0) {
fieldNum = readI();
} else {
fieldNum = lastFieldNum + delta;
}
lastFieldNum = fieldNum;
if (fieldCType == C_TRUE) {
state = STATE_BOOL_READ;
boolValue = true;
} else if (fieldCType == C_FALSE) {
state = STATE_BOOL_READ;
boolValue = false;
} else {
state = STATE_VALUE_READ;
}
}
void readFieldEnd(void) {
state = STATE_FIELD_READ;
}
Variant readField(CArrRef spec, TType type) {
switch (type) {
case T_STOP:
case T_VOID:
return uninit_null();
case T_STRUCT: {
Variant className = spec.rvalAt(s_class);
if (className.isNull()) {
thrift_error("no class type in spec", ERR_INVALID_DATA);
}
String classNameString = className.toString();
Variant newStruct = create_object(classNameString, Array());
if (newStruct.isNull()) {
thrift_error("invalid class type in spec", ERR_INVALID_DATA);
}
Variant newStructSpec =
f_hphp_get_static_property(classNameString, "_TSPEC");
if (!newStructSpec.is(KindOfArray)) {
thrift_error("invalid type of spec", ERR_INVALID_DATA);
}
readStruct(newStruct, newStructSpec);
return newStruct;
}
case T_BOOL:
if (state == STATE_BOOL_READ) {
return boolValue;
} else if (state == STATE_CONTAINER_READ) {
return (readUByte() == C_TRUE);
} else {
thrift_error("Invalid state in compact protocol", ERR_UNKNOWN);
}
case T_BYTE:
return readUByte();
case T_I16:
case T_I32:
case T_I64:
case T_U64:
return readI();
case T_DOUBLE: {
union {
uint64_t i;
double d;
} u;
transport.readBytes(&(u.i), 8);
if (version >= VERSION_DOUBLE_BE) {
u.i = ntohll(u.i);
} else {
u.i = letohll(u.i);
}
return u.d;
}
case T_UTF8:
case T_UTF16:
case T_STRING:
return readString();
case T_MAP:
return readMap(spec);
case T_LIST:
return readList(spec, C_LIST_LIST);
case T_SET:
return readList(spec, C_LIST_SET);
default:
throw InvalidArgumentException("unknown TType", type);
}
}
void skip(TType type) {
switch (type) {
case T_STOP:
case T_VOID:
break;
case T_STRUCT: {
readStructBegin();
while (true) {
int16_t fieldNum;
TType fieldType;
readFieldBegin(fieldNum, fieldType);
if (fieldType == T_STOP) {
break;
}
skip(fieldType);
readFieldEnd();
}
readStructEnd();
}
break;
case T_BOOL:
if (state == STATE_BOOL_READ) {
// don't need to do anything
} else if (state == STATE_CONTAINER_READ) {
readUByte();
} else {
thrift_error("Invalid state in compact protocol", ERR_UNKNOWN);
}
break;
case T_BYTE:
readUByte();
break;
case T_I16:
case T_I32:
case T_I64:
case T_U64:
readI();
break;
case T_DOUBLE:
transport.skip(8);
break;
case T_UTF8:
case T_UTF16:
case T_STRING:
transport.skip(readVarint());
break;
case T_MAP: {
TType keyType, valueType;
uint32_t size;
readMapBegin(keyType, valueType, size);
for (uint32_t i = 0; i < size; i++) {
skip(keyType);
skip(valueType);
}
readCollectionEnd();
}
break;
case T_LIST:
case T_SET: {
TType valueType;
uint32_t size;
readListBegin(valueType, size);
for (uint32_t i = 0; i < size; i++) {
skip(valueType);
}
readCollectionEnd();
}
break;
default:
throw InvalidArgumentException("unknown TType", type);
}
}
Variant readMap(CArrRef spec) {
TType keyType, valueType;
uint32_t size;
readMapBegin(keyType, valueType, size);
Array keySpec = spec.rvalAt(s_key, AccessFlags::Error);
Array valueSpec = spec.rvalAt(s_val, AccessFlags::Error);
Variant ret = Array::Create();
for (uint32_t i = 0; i < size; i++) {
Variant key = readField(keySpec, keyType);
Variant value = readField(valueSpec, valueType);
ret.set(key, value);
}
readCollectionEnd();
return ret;
}
Variant readList(CArrRef spec, CListType listType) {
TType valueType;
uint32_t size;
readListBegin(valueType, size);
Array valueSpec = spec.rvalAt(s_elem, AccessFlags::Error_Key);
Variant ret = Array::Create();
for (uint32_t i = 0; i < size; i++) {
Variant value = readField(valueSpec, valueType);
if (listType == C_LIST_LIST) {
ret.append(value);
} else {
ret.set(value, true);
}
}
readCollectionEnd();
return ret;
}
void readMapBegin(TType &keyType, TType &valueType, uint32_t &size) {
size = readVarint();
uint8_t types = 0;
if (size > 0) {
types = readUByte();
}
valueType = ctype_to_ttype((CType)(types & 0x0f));
keyType = ctype_to_ttype((CType)(types >> 4));
containerHistory.push(state);
state = STATE_CONTAINER_READ;
}
void readListBegin(TType &elemType, uint32_t &size) {
uint8_t sizeAndType = readUByte();
size = sizeAndType >> 4;
elemType = ctype_to_ttype((CType)(sizeAndType & 0x0f));
if (size == 15) {
size = readVarint();
}
containerHistory.push(state);
state = STATE_CONTAINER_READ;
}
void readCollectionEnd(void) {
state = containerHistory.top();
containerHistory.pop();
}
uint8_t readUByte(void) {
return transport.readI8();
}
int64_t readI(void) {
return zigzagToI64(readVarint());
}
uint64_t readVarint(void) {
uint64_t result = 0;
uint8_t shift = 0;
while (true) {
uint8_t byte = readUByte();
result |= (uint64_t)(byte & 0x7f) << shift;
shift += 7;
if (!(byte & 0x80)) {
return result;
}
// Should never read more than 10 bytes, which is the max for a 64-bit
// int
if (shift >= 10 * 7) {
thrift_error("Variable-length int over 10 bytes", ERR_INVALID_DATA);
}
}
}
Variant readString(void) {
uint32_t size = readVarint();
if (size && (size + 1)) {
String s = String(size, ReserveString);
char* buf = s.mutableSlice().ptr;
transport.readBytes(buf, size);
return s.setSize(size);
} else {
transport.skip(size);
return "";
}
}
int64_t zigzagToI64(uint64_t n) {
return (n >> 1) ^ -(n & 1);
}
bool typeIsInt(TType t) {
return (t == T_BYTE) || ((t >= T_I16) && (t <= T_I64));
}
bool typesAreCompatible(TType t1, TType t2) {
return (t1 == t2) || (typeIsInt(t1) && (typeIsInt(t2)));
}
};
int f_thrift_protocol_set_compact_version(int version) {
int result = s_compact_request_data->version;
s_compact_request_data->version = (uint8_t)version;
return result;
}
void f_thrift_protocol_write_compact(CObjRef transportobj,
CStrRef method_name,
int64_t msgtype,
CObjRef request_struct,
int seqid) {
PHPOutputTransport transport(transportobj);
CompactWriter writer(&transport);
writer.setWriteVersion(s_compact_request_data->version);
writer.writeHeader(method_name, (uint8_t)msgtype, (uint32_t)seqid);
writer.write(request_struct);
transport.flush();
}
Variant f_thrift_protocol_read_compact(CObjRef transportobj,
CStrRef obj_typename) {
CompactReader reader(transportobj);
return reader.read(obj_typename);
}
}