blob: 03b13e2f6c4bf3d2dbcbf716894526b7e0ee8581 [file] [log] [blame]
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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.
*/
package org.apache.thrift.protocol;
import haxe.io.Bytes;
import haxe.io.BytesInput;
import haxe.io.BytesOutput;
import haxe.io.BytesBuffer;
import haxe.ds.GenericStack;
import haxe.Int32;
import haxe.Int64;
import haxe.Utf8;
import org.apache.thrift.TException;
import org.apache.thrift.transport.TTransport;
import org.apache.thrift.helper.ZigZag;
import org.apache.thrift.helper.BitConverter;
/**
* Compact protocol implementation for thrift.
*/
class TCompactProtocol extends TRecursionTracker implements TProtocol {
private static var ANONYMOUS_STRUCT : TStruct = new TStruct("");
private static var TSTOP : TField = new TField("", TType.STOP, 0);
private static inline var PROTOCOL_ID : Int = 0x82;
private static inline var VERSION : Int = 1;
private static inline var VERSION_MASK : Int = 0x1f; // 0001 1111
private static inline var TYPE_MASK : Int = 0xE0; // 1110 0000
private static inline var TYPE_BITS : Int = 0x07; // 0000 0111
private static inline var TYPE_SHIFT_AMOUNT : Int = 5;
private static var ttypeToCompactType = [
TType.STOP => TCompactTypes.STOP,
TType.BOOL => TCompactTypes.BOOLEAN_TRUE,
TType.BYTE => TCompactTypes.BYTE,
TType.DOUBLE => TCompactTypes.DOUBLE,
TType.I16 => TCompactTypes.I16,
TType.I32 => TCompactTypes.I32,
TType.I64 => TCompactTypes.I64,
TType.STRING => TCompactTypes.BINARY,
TType.STRUCT => TCompactTypes.STRUCT,
TType.MAP => TCompactTypes.MAP,
TType.SET => TCompactTypes.SET,
TType.LIST => TCompactTypes.LIST
];
private static var tcompactTypeToType = [
TCompactTypes.STOP => TType.STOP,
TCompactTypes.BOOLEAN_TRUE => TType.BOOL,
TCompactTypes.BOOLEAN_FALSE => TType.BOOL,
TCompactTypes.BYTE => TType.BYTE,
TCompactTypes.I16 => TType.I16,
TCompactTypes.I32 => TType.I32,
TCompactTypes.I64 => TType.I64,
TCompactTypes.DOUBLE => TType.DOUBLE,
TCompactTypes.BINARY => TType.STRING,
TCompactTypes.LIST => TType.LIST,
TCompactTypes.SET => TType.SET,
TCompactTypes.MAP => TType.MAP,
TCompactTypes.STRUCT => TType.STRUCT
];
/**
* Used to keep track of the last field for the current and previous structs,
* so we can do the delta stuff.
*/
private var lastField_ : GenericStack<Int> = new GenericStack<Int>();
private var lastFieldId_ : Int = 0;
/**
* If we encounter a boolean field begin, save the TField here so it can
* have the value incorporated.
*/
private var booleanField_ : Null<TField>;
/**
* If we Read a field header, and it's a boolean field, save the boolean
* value here so that ReadBool can use it.
*/
private var boolValue_ : Null<Bool>;
// whether the underlying system holds Strings as UTF-8
// http://old.haxe.org/manual/encoding
private static var utf8Strings = haxe.Utf8.validate("Ç-ß-Æ-Ю-Ш");
// the transport used
public var trans(default,null) : TTransport;
// TCompactProtocol Constructor
public function new( trans : TTransport) {
this.trans = trans;
}
public function getTransport() : TTransport {
return trans;
}
public function Reset() : Void{
while ( ! lastField_.isEmpty()) {
lastField_.pop();
}
lastFieldId_ = 0;
}
/**
* Writes a byte without any possibility of all that field header nonsense.
* Used internally by other writing methods that know they need to Write a byte.
*/
private function WriteByteDirect( b : Int) : Void {
var buf = Bytes.alloc(1);
buf.set( 0, b);
trans.write( buf, 0, 1);
}
/**
* Write an i32 as a varint. Results in 1-5 bytes on the wire.
*/
private function WriteVarint32( n : UInt) : Void {
var i32buf = new BytesBuffer();
while (true)
{
if ((n & ~0x7F) == 0)
{
i32buf.addByte( n & 0xFF);
break;
}
else
{
i32buf.addByte( (n & 0x7F) | 0x80);
n >>= 7;
}
}
var tmp = i32buf.getBytes();
trans.write( tmp, 0, tmp.length);
}
/**
* Write a message header to the wire. Compact Protocol messages contain the
* protocol version so we can migrate forwards in the future if need be.
*/
public function writeMessageBegin( message : TMessage) : Void {
Reset();
var versionAndType : Int = (VERSION & VERSION_MASK) | ((message.type << TYPE_SHIFT_AMOUNT) & TYPE_MASK);
WriteByteDirect( PROTOCOL_ID);
WriteByteDirect( versionAndType);
WriteVarint32( cast( message.seqid, UInt));
writeString( message.name);
}
/**
* Write a struct begin. This doesn't actually put anything on the wire. We
* use it as an opportunity to put special placeholder markers on the field
* stack so we can get the field id deltas correct.
*/
public function writeStructBegin(struct:TStruct) : Void {
lastField_.add( lastFieldId_);
lastFieldId_ = 0;
}
/**
* Write a struct end. This doesn't actually put anything on the wire. We use
* this as an opportunity to pop the last field from the current struct off
* of the field stack.
*/
public function writeStructEnd() : Void {
lastFieldId_ = lastField_.pop();
}
/**
* Write a field header containing the field id and field type. If the
* difference between the current field id and the last one is small (< 15),
* then the field id will be encoded in the 4 MSB as a delta. Otherwise, the
* field id will follow the type header as a zigzag varint.
*/
public function writeFieldBegin(field:TField) : Void {
if (field.type == TType.BOOL)
booleanField_ = field; // we want to possibly include the value, so we'll wait.
else
WriteFieldBeginInternal(field, 0xFF);
}
/**
* The workhorse of WriteFieldBegin. It has the option of doing a
* 'type override' of the type header. This is used specifically in the
* boolean field case.
*/
private function WriteFieldBeginInternal( field : TField, typeOverride : Int) : Void {
// if there's a type override, use that.
var typeToWrite : Int;
if ( typeOverride == 0xFF)
typeToWrite = getCompactType( field.type);
else
typeToWrite = typeOverride;
// check if we can use delta encoding for the field id
if (field.id > lastFieldId_ && field.id - lastFieldId_ <= 15)
{
// Write them together
WriteByteDirect((field.id - lastFieldId_) << 4 | typeToWrite);
}
else
{
// Write them separate
WriteByteDirect(typeToWrite);
writeI16(field.id);
}
lastFieldId_ = field.id;
}
/**
* Write the STOP symbol so we know there are no more fields in this struct.
*/
public function writeFieldStop() : Void {
WriteByteDirect( cast(TCompactTypes.STOP, Int));
}
/**
* Write a map header. If the map is empty, omit the key and value type
* headers, as we don't need any additional information to skip it.
*/
public function writeMapBegin(map:TMap) : Void {
if (map.size == 0)
{
WriteByteDirect(0);
}
else
{
var kvtype = (getCompactType(map.keyType) << 4) | getCompactType(map.valueType);
WriteVarint32( cast( map.size, UInt));
WriteByteDirect( kvtype);
}
}
/**
* Write a list header.
*/
public function writeListBegin( list : TList) : Void {
WriteCollectionBegin( list.elemType, list.size);
}
/**
* Write a set header.
*/
public function writeSetBegin( set : TSet) : Void {
WriteCollectionBegin( set.elemType, set.size);
}
/**
* Write a boolean value. Potentially, this could be a boolean field, in
* which case the field header info isn't written yet. If so, decide what the
* right type header is for the value and then Write the field header.
* Otherwise, Write a single byte.
*/
public function writeBool(b : Bool) : Void {
var bct : Int = b ? TCompactTypes.BOOLEAN_TRUE : TCompactTypes.BOOLEAN_FALSE;
if (booleanField_ != null)
{
// we haven't written the field header yet
WriteFieldBeginInternal( booleanField_, bct);
booleanField_ = null;
}
else
{
// we're not part of a field, so just Write the value.
WriteByteDirect( bct);
}
}
/**
* Write a byte. Nothing to see here!
*/
public function writeByte( b : Int) : Void {
WriteByteDirect( b);
}
/**
* Write an I16 as a zigzag varint.
*/
public function writeI16( i16 : Int) : Void {
WriteVarint32( ZigZag.FromInt( i16));
}
/**
* Write an i32 as a zigzag varint.
*/
public function writeI32( i32 : Int) : Void {
WriteVarint32( ZigZag.FromInt( i32));
}
/**
* Write an i64 as a zigzag varint.
*/
public function writeI64( i64 : haxe.Int64) : Void {
WriteVarint64( ZigZag.FromLong( i64));
}
/**
* Write a double to the wire as 8 bytes.
*/
public function writeDouble( dub : Float) : Void {
var data = BitConverter.fixedLongToBytes( BitConverter.DoubleToInt64Bits(dub));
trans.write( data, 0, data.length);
}
/**
* Write a string to the wire with a varint size preceding.
*/
public function writeString(str : String) : Void {
var buf = new BytesBuffer();
if( utf8Strings)
buf.addString( str); // no need to encode on UTF8 targets, the string is just fine
else
buf.addString( Utf8.encode( str));
var tmp = buf.getBytes();
writeBinary( tmp);
}
/**
* Write a byte array, using a varint for the size.
*/
public function writeBinary( bin : Bytes) : Void {
WriteVarint32( cast(bin.length,UInt));
trans.write( bin, 0, bin.length);
}
// These methods are called by structs, but don't actually have any wire
// output or purpose.
public function writeMessageEnd() : Void { }
public function writeMapEnd() : Void { }
public function writeListEnd() : Void { }
public function writeSetEnd() : Void { }
public function writeFieldEnd() : Void { }
//
// Internal writing methods
//
/**
* Abstract method for writing the start of lists and sets. List and sets on
* the wire differ only by the type indicator.
*/
private function WriteCollectionBegin( elemType : Int, size : Int) : Void {
if (size <= 14) {
WriteByteDirect( size << 4 | getCompactType(elemType));
}
else {
WriteByteDirect( 0xf0 | getCompactType(elemType));
WriteVarint32( cast(size, UInt));
}
}
/**
* Write an i64 as a varint. Results in 1-10 bytes on the wire.
*/
private function WriteVarint64(n : haxe.Int64) : Void {
var varint64out = new BytesBuffer();
while (true)
{
if( Int64.isZero( Int64.and( n, Int64.neg(Int64.make(0,0x7F)))))
{
#if( haxe_ver < 3.2)
varint64out.addByte( Int64.getLow(n));
#else
varint64out.addByte( n.low);
#end
break;
}
else
{
#if ( haxe_ver < 3.2)
varint64out.addByte( (Int64.getLow(n) & 0x7F) | 0x80);
#else
varint64out.addByte( (n.low & 0x7F) | 0x80);
#end
n = Int64.shr( n, 7);
n = Int64.and( n, Int64.make(0x01FFFFFF,0xFFFFFFFF)); // clean out the shifted 7 bits
}
}
var tmp = varint64out.getBytes();
trans.write( tmp, 0, tmp.length);
}
/**
* Read a message header.
*/
public function readMessageBegin():TMessage {
Reset();
var protocolId : Int = readByte();
if (protocolId != PROTOCOL_ID) {
throw new TProtocolException( TProtocolException.INVALID_DATA, "Expected protocol id " + StringTools.hex(PROTOCOL_ID,2) + " but got " + StringTools.hex(protocolId));
}
var versionAndType : Int = readByte();
var version : Int = (versionAndType & VERSION_MASK);
if (version != VERSION) {
throw new TProtocolException( TProtocolException.INVALID_DATA, "Expected version " + VERSION + " but got " + version);
}
var type : Int = ((versionAndType >> TYPE_SHIFT_AMOUNT) & TYPE_BITS);
var seqid : Int = cast( ReadVarint32(), Int);
var msgNm : String = readString();
return new TMessage( msgNm, type, seqid);
}
/**
* Read a struct begin. There's nothing on the wire for this, but it is our
* opportunity to push a new struct begin marker onto the field stack.
*/
public function readStructBegin():TStruct {
lastField_.add(lastFieldId_);
lastFieldId_ = 0;
return ANONYMOUS_STRUCT;
}
/**
* Doesn't actually consume any wire data, just removes the last field for
* this struct from the field stack.
*/
public function readStructEnd() : Void {
// consume the last field we Read off the wire.
lastFieldId_ = lastField_.pop();
}
/**
* Read a field header off the wire.
*/
public function readFieldBegin() : TField {
var type : Int = readByte();
// if it's a stop, then we can return immediately, as the struct is over.
if (type == cast(TCompactTypes.STOP,Int)) {
return TSTOP;
}
var fieldId : Int;
// mask off the 4 MSB of the type header. it could contain a field id delta.
var modifier : Int = ((type & 0xf0) >> 4);
if (modifier == 0)
fieldId = readI16(); // not a delta. look ahead for the zigzag varint field id.
else
fieldId = lastFieldId_ + modifier; // add the delta to the last Read field id.
var field : TField = new TField( "", cast(getTType(type & 0x0f),Int), fieldId);
// if this happens to be a boolean field, the value is encoded in the type
if (isBoolType(type)) {
// save the boolean value in a special instance variable.
boolValue_ = ((type & 0x0f) == cast(TCompactTypes.BOOLEAN_TRUE,Int));
}
// push the new field onto the field stack so we can keep the deltas going.
lastFieldId_ = field.id;
return field;
}
/**
* Read a map header off the wire. If the size is zero, skip Reading the key
* and value type. This means that 0-length maps will yield TMaps without the
* "correct" types.
*/
public function readMapBegin() : TMap {
var size : Int = cast( ReadVarint32(), Int);
var keyAndValueType : Int = ((size == 0) ? 0 : readByte());
var key : Int = cast( getTType( (keyAndValueType & 0xF0) >> 4), Int);
var val : Int = cast( getTType( keyAndValueType & 0x0F), Int);
return new TMap( key, val, size);
}
/**
* Read a list header off the wire. If the list size is 0-14, the size will
* be packed into the element type header. If it's a longer list, the 4 MSB
* of the element type header will be 0xF, and a varint will follow with the
* true size.
*/
public function readListBegin():TList {
var size_and_type : Int = readByte();
var size : Int = ((size_and_type & 0xF0) >> 4) & 0x0F;
if (size == 15) {
size = cast( ReadVarint32(), Int);
}
var type = getTType(size_and_type);
return new TList( type, size);
}
/**
* Read a set header off the wire. If the set size is 0-14, the size will
* be packed into the element type header. If it's a longer set, the 4 MSB
* of the element type header will be 0xF, and a varint will follow with the
* true size.
*/
public function readSetBegin() : TSet {
var size_and_type : Int = readByte();
var size : Int = ((size_and_type & 0xF0) >> 4) & 0x0F;
if (size == 15) {
size = cast( ReadVarint32(), Int);
}
var type = getTType(size_and_type);
return new TSet( type, size);
}
/**
* Read a boolean off the wire. If this is a boolean field, the value should
* already have been Read during ReadFieldBegin, so we'll just consume the
* pre-stored value. Otherwise, Read a byte.
*/
public function readBool() : Bool {
if (boolValue_ != null) {
var result : Bool = boolValue_;
boolValue_ = null;
return result;
}
return (readByte() == cast(TCompactTypes.BOOLEAN_TRUE,Int));
}
/**
* Read a single byte off the wire. Nothing interesting here.
*/
public function readByte() : Int {
var byteRawBuf = new BytesBuffer();
trans.readAll( byteRawBuf, 0, 1);
return byteRawBuf.getBytes().get(0);
}
/**
* Read an i16 from the wire as a zigzag varint.
*/
public function readI16() : Int {
return ZigZag.ToInt( ReadVarint32());
}
/**
* Read an i32 from the wire as a zigzag varint.
*/
public function readI32() : Int {
return ZigZag.ToInt( ReadVarint32());
}
/**
* Read an i64 from the wire as a zigzag varint.
*/
public function readI64() : haxe.Int64 {
return ZigZag.ToLong( ReadVarint64());
}
/**
* No magic here - just Read a double off the wire.
*/
public function readDouble():Float {
var longBits = new BytesBuffer();
trans.readAll( longBits, 0, 8);
return BitConverter.Int64BitsToDouble( BitConverter.bytesToLong( longBits.getBytes()));
}
/**
* Reads a byte[] (via ReadBinary), and then UTF-8 decodes it.
*/
public function readString() : String {
var length : Int = cast( ReadVarint32(), Int);
if (length == 0) {
return "";
}
var buf = new BytesBuffer();
trans.readAll( buf, 0, length);
length = buf.length;
var inp = new BytesInput( buf.getBytes());
var str = inp.readString( length);
if( utf8Strings)
return str; // no need to decode on UTF8 targets, the string is just fine
else
return Utf8.decode( str);
}
/**
* Read a byte[] from the wire.
*/
public function readBinary() : Bytes {
var length : Int = cast( ReadVarint32(), Int);
if (length == 0) {
return Bytes.alloc(0);
}
var buf = new BytesBuffer();
trans.readAll( buf, 0, length);
return buf.getBytes();
}
// These methods are here for the struct to call, but don't have any wire
// encoding.
public function readMessageEnd() : Void { }
public function readFieldEnd() : Void { }
public function readMapEnd() : Void { }
public function readListEnd() : Void { }
public function readSetEnd() : Void { }
//
// Internal Reading methods
//
/**
* Read an i32 from the wire as a varint. The MSB of each byte is set
* if there is another byte to follow. This can Read up to 5 bytes.
*/
private function ReadVarint32() : UInt {
var result : UInt = 0;
var shift : Int = 0;
while (true) {
var b : Int = readByte();
result |= cast((b & 0x7f) << shift, UInt);
if ((b & 0x80) != 0x80) {
break;
}
shift += 7;
}
return result;
}
/**
* Read an i64 from the wire as a proper varint. The MSB of each byte is set
* if there is another byte to follow. This can Read up to 10 bytes.
*/
private function ReadVarint64() : Int64 {
var shift : Int = 0;
var result : Int64 = Int64.make(0,0);
while (true) {
var b : Int = readByte();
result = Int64.or( result, Int64.shl( Int64.make(0,b & 0x7f), shift));
if ((b & 0x80) != 0x80) {
break;
}
shift += 7;
}
return result;
}
//
// type testing and converting
//
private function isBoolType( b : Int) : Bool {
var lowerNibble : Int = b & 0x0f;
switch(lowerNibble)
{
case TCompactTypes.BOOLEAN_TRUE: return true;
case TCompactTypes.BOOLEAN_FALSE: return true;
default: return false;
}
}
/**
* Given a TCompactProtocol.TCompactTypes constant, convert it to its corresponding
* TType value.
*/
private function getTType( type : Int) : Int {
try
{
return tcompactTypeToType[type];
}
catch ( e : Dynamic)
{
var tt : Int = (type & 0x0f);
throw new TProtocolException( TProtocolException.UNKNOWN, 'don\'t know what type: $tt ($e)');
}
}
/**
* Given a TType value, find the appropriate TCompactProtocol.TCompactTypes constant.
*/
private function getCompactType( ttype : Int) : Int
{
return cast( ttypeToCompactType[ttype], Int);
}
}