| (* |
| * 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. |
| *) |
| |
| {$SCOPEDENUMS ON} |
| |
| unit Thrift.Protocol.Compact; |
| |
| interface |
| |
| uses |
| Classes, |
| SysUtils, |
| Math, |
| Generics.Collections, |
| Thrift.Configuration, |
| Thrift.Transport, |
| Thrift.Protocol, |
| Thrift.Utils; |
| |
| type |
| ICompactProtocol = interface( IProtocol) |
| ['{C01927EC-021A-45F7-93B1-23D6A5420EDD}'] |
| end; |
| |
| // Compact protocol implementation for thrift. |
| // Adapted from the C# version. |
| TCompactProtocolImpl = class( TProtocolImpl, ICompactProtocol) |
| public |
| type |
| TFactory = class( TInterfacedObject, IProtocolFactory) |
| public |
| function GetProtocol( const trans: ITransport): IProtocol; |
| end; |
| |
| strict private const |
| |
| { TODO |
| static TStruct ANONYMOUS_STRUCT = new TStruct(""); |
| static TField TSTOP = new TField("", TType.Stop, (short)0); |
| } |
| |
| PROTOCOL_ID = Byte( $82); |
| VERSION = Byte( 1); |
| VERSION_MASK = Byte( $1F); // 0001 1111 |
| TYPE_MASK = Byte( $E0); // 1110 0000 |
| TYPE_BITS = Byte( $07); // 0000 0111 |
| TYPE_SHIFT_AMOUNT = Byte( 5); |
| |
| strict private type |
| // All of the on-wire type codes. |
| Types = ( |
| STOP = $00, |
| BOOLEAN_TRUE = $01, |
| BOOLEAN_FALSE = $02, |
| BYTE_ = $03, |
| I16 = $04, |
| I32 = $05, |
| I64 = $06, |
| DOUBLE_ = $07, |
| BINARY = $08, |
| LIST = $09, |
| SET_ = $0A, |
| MAP = $0B, |
| STRUCT = $0C |
| ); |
| |
| strict private const |
| ttypeToCompactType : array[TType] of Types = ( |
| Types.STOP, // Stop = 0, |
| Types(-1), // Void = 1, |
| Types.BOOLEAN_TRUE, // Bool_ = 2, |
| Types.BYTE_, // Byte_ = 3, |
| Types.DOUBLE_, // Double_ = 4, |
| Types(-5), // unused |
| Types.I16, // I16 = 6, |
| Types(-7), // unused |
| Types.I32, // I32 = 8, |
| Types(-9), // unused |
| Types.I64, // I64 = 10, |
| Types.BINARY, // String_ = 11, |
| Types.STRUCT, // Struct = 12, |
| Types.MAP, // Map = 13, |
| Types.SET_, // Set_ = 14, |
| Types.LIST // List = 15, |
| ); |
| |
| tcompactTypeToType : array[Types] of TType = ( |
| TType.Stop, // STOP |
| TType.Bool_, // BOOLEAN_TRUE |
| TType.Bool_, // BOOLEAN_FALSE |
| TType.Byte_, // BYTE_ |
| TType.I16, // I16 |
| TType.I32, // I32 |
| TType.I64, // I64 |
| TType.Double_, // DOUBLE_ |
| TType.String_, // BINARY |
| TType.List, // LIST |
| TType.Set_, // SET_ |
| TType.Map, // MAP |
| TType.Struct // STRUCT |
| ); |
| |
| strict private |
| // Used to keep track of the last field for the current and previous structs, |
| // so we can do the delta stuff. |
| lastField_ : TStack<Integer>; |
| lastFieldId_ : Integer; |
| |
| // If we encounter a boolean field begin, save the TField here so it can |
| // have the value incorporated. |
| strict private booleanField_ : TThriftField; |
| |
| // If we Read a field header, and it's a boolean field, save the boolean |
| // value here so that ReadBool can use it. |
| strict private boolValue_ : ( unused, bool_true, bool_false); |
| |
| public |
| constructor Create(const trans : ITransport); |
| destructor Destroy; override; |
| |
| strict private |
| procedure WriteByteDirect( const b : Byte); overload; |
| |
| // Writes a byte without any possibility of all that field header nonsense. |
| procedure WriteByteDirect( const n : Integer); overload; |
| |
| // Write an i32 as a varint. Results in 1-5 bytes on the wire. |
| // TODO: make a permanent buffer like WriteVarint64? |
| procedure WriteVarint32( n : Cardinal); |
| |
| strict private |
| // 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. |
| procedure WriteFieldBeginInternal( const field : TThriftField; typeOverride : Byte); |
| |
| public |
| procedure WriteMessageBegin( const msg: TThriftMessage); override; |
| procedure WriteMessageEnd; override; |
| procedure WriteStructBegin( const struc: TThriftStruct); override; |
| procedure WriteStructEnd; override; |
| procedure WriteFieldBegin( const field: TThriftField); override; |
| procedure WriteFieldEnd; override; |
| procedure WriteFieldStop; override; |
| procedure WriteMapBegin( const map: TThriftMap); override; |
| procedure WriteMapEnd; override; |
| procedure WriteListBegin( const list: TThriftList); override; |
| procedure WriteListEnd(); override; |
| procedure WriteSetBegin( const set_: TThriftSet ); override; |
| procedure WriteSetEnd(); override; |
| procedure WriteBool( b: Boolean); override; |
| procedure WriteByte( b: ShortInt); override; |
| procedure WriteI16( i16: SmallInt); override; |
| procedure WriteI32( i32: Integer); override; |
| procedure WriteI64( const i64: Int64); override; |
| procedure WriteDouble( const dub: Double); override; |
| procedure WriteBinary( const b: TBytes); overload; override; |
| |
| private // unit visible stuff |
| class function DoubleToInt64Bits( const db : Double) : Int64; |
| class function Int64BitsToDouble( const i64 : Int64) : Double; |
| |
| // Abstract method for writing the start of lists and sets. List and sets on |
| // the wire differ only by the type indicator. |
| procedure WriteCollectionBegin( const elemType : TType; size : Integer); |
| |
| procedure WriteVarint64( n : UInt64); |
| |
| // Convert l into a zigzag long. This allows negative numbers to be |
| // represented compactly as a varint. |
| class function longToZigzag( const n : Int64) : UInt64; |
| |
| // Convert n into a zigzag int. This allows negative numbers to be |
| // represented compactly as a varint. |
| class function intToZigZag( const n : Integer) : Cardinal; |
| |
| //Convert a Int64 into little-endian bytes in buf starting at off and going until off+7. |
| class procedure fixedLongToBytes( const n : Int64; var buf : TBytes); |
| |
| strict protected |
| function GetMinSerializedSize( const aType : TType) : Integer; override; |
| procedure Reset; override; |
| |
| public |
| function ReadMessageBegin: TThriftMessage; override; |
| procedure ReadMessageEnd(); override; |
| function ReadStructBegin: TThriftStruct; override; |
| procedure ReadStructEnd; override; |
| function ReadFieldBegin: TThriftField; override; |
| procedure ReadFieldEnd(); override; |
| function ReadMapBegin: TThriftMap; override; |
| procedure ReadMapEnd(); override; |
| function ReadListBegin: TThriftList; override; |
| procedure ReadListEnd(); override; |
| function ReadSetBegin: TThriftSet; override; |
| procedure ReadSetEnd(); override; |
| function ReadBool: Boolean; override; |
| function ReadByte: ShortInt; override; |
| function ReadI16: SmallInt; override; |
| function ReadI32: Integer; override; |
| function ReadI64: Int64; override; |
| function ReadDouble:Double; override; |
| function ReadBinary: TBytes; overload; override; |
| |
| private |
| // 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. |
| function ReadVarint32 : Cardinal; |
| |
| // 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. |
| function ReadVarint64 : UInt64; |
| |
| |
| // encoding helpers |
| |
| // Convert from zigzag Integer to Integer. |
| class function zigzagToInt( const n : Cardinal ) : Integer; |
| |
| // Convert from zigzag Int64 to Int64. |
| class function zigzagToLong( const n : UInt64) : Int64; |
| |
| // Note that it's important that the mask bytes are Int64 literals, |
| // otherwise they'll default to ints, and when you shift an Integer left 56 bits, |
| // you just get a messed up Integer. |
| class function bytesToLong( const bytes : TBytes) : Int64; |
| |
| // type testing and converting |
| class function isBoolType( const b : byte) : Boolean; |
| |
| // Given a TCompactProtocol.Types constant, convert it to its corresponding TType value. |
| class function getTType( const type_ : byte) : TType; |
| |
| // Given a TType value, find the appropriate TCompactProtocol.Types constant. |
| class function getCompactType( const ttype : TType) : Byte; |
| end; |
| |
| |
| implementation |
| |
| |
| |
| //--- TCompactProtocolImpl.TFactory ---------------------------------------- |
| |
| |
| function TCompactProtocolImpl.TFactory.GetProtocol( const trans: ITransport): IProtocol; |
| begin |
| result := TCompactProtocolImpl.Create( trans); |
| end; |
| |
| |
| //--- TCompactProtocolImpl ------------------------------------------------- |
| |
| |
| constructor TCompactProtocolImpl.Create( const trans : ITransport); |
| begin |
| inherited Create( trans); |
| |
| lastFieldId_ := 0; |
| lastField_ := TStack<Integer>.Create; |
| |
| Init( booleanField_, '', TType.Stop, 0); |
| boolValue_ := unused; |
| end; |
| |
| |
| destructor TCompactProtocolImpl.Destroy; |
| begin |
| try |
| FreeAndNil( lastField_); |
| finally |
| inherited Destroy; |
| end; |
| end; |
| |
| |
| |
| procedure TCompactProtocolImpl.Reset; |
| begin |
| inherited Reset; |
| lastField_.Clear(); |
| lastFieldId_ := 0; |
| Init( booleanField_, '', TType.Stop, 0); |
| boolValue_ := unused; |
| end; |
| |
| |
| // 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. |
| procedure TCompactProtocolImpl.WriteByteDirect( const b : Byte); |
| begin |
| Transport.Write( @b, SizeOf(b)); |
| end; |
| |
| |
| // Writes a byte without any possibility of all that field header nonsense. |
| procedure TCompactProtocolImpl.WriteByteDirect( const n : Integer); |
| begin |
| WriteByteDirect( Byte(n)); |
| end; |
| |
| |
| // Write an i32 as a varint. Results in 1-5 bytes on the wire. |
| procedure TCompactProtocolImpl.WriteVarint32( n : Cardinal); |
| var i32buf : TBytes; |
| idx : Integer; |
| begin |
| SetLength( i32buf, 5); |
| idx := 0; |
| while TRUE do begin |
| ASSERT( idx < Length(i32buf)); |
| |
| // last part? |
| if ((n and not $7F) = 0) then begin |
| i32buf[idx] := Byte(n); |
| Inc(idx); |
| Break; |
| end; |
| |
| i32buf[idx] := Byte((n and $7F) or $80); |
| Inc(idx); |
| n := n shr 7; |
| end; |
| |
| Transport.Write( i32buf, 0, idx); |
| end; |
| |
| |
| // 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. |
| procedure TCompactProtocolImpl.WriteMessageBegin( const msg: TThriftMessage); |
| var versionAndType : Byte; |
| begin |
| Reset; |
| |
| versionAndType := Byte( VERSION and VERSION_MASK) |
| or Byte( (Cardinal(msg.Type_) shl TYPE_SHIFT_AMOUNT) and TYPE_MASK); |
| |
| WriteByteDirect( PROTOCOL_ID); |
| WriteByteDirect( versionAndType); |
| WriteVarint32( Cardinal(msg.SeqID)); |
| WriteString( msg.Name); |
| end; |
| |
| |
| // 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. |
| procedure TCompactProtocolImpl.WriteStructBegin( const struc: TThriftStruct); |
| begin |
| lastField_.Push(lastFieldId_); |
| lastFieldId_ := 0; |
| end; |
| |
| |
| // 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. |
| procedure TCompactProtocolImpl.WriteStructEnd; |
| begin |
| lastFieldId_ := lastField_.Pop(); |
| end; |
| |
| |
| // 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. |
| procedure TCompactProtocolImpl.WriteFieldBegin( const field: TThriftField); |
| begin |
| case field.Type_ of |
| TType.Bool_ : booleanField_ := field; // we want to possibly include the value, so we'll wait. |
| else |
| WriteFieldBeginInternal(field, $FF); |
| end; |
| end; |
| |
| |
| // 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. |
| procedure TCompactProtocolImpl.WriteFieldBeginInternal( const field : TThriftField; typeOverride : Byte); |
| var typeToWrite : Byte; |
| begin |
| // if there's a type override, use that. |
| if typeOverride = $FF |
| then typeToWrite := getCompactType( field.Type_) |
| else typeToWrite := typeOverride; |
| |
| // check if we can use delta encoding for the field id |
| if (field.ID > lastFieldId_) and ((field.ID - lastFieldId_) <= 15) |
| then begin |
| // Write them together |
| WriteByteDirect( ((field.ID - lastFieldId_) shl 4) or typeToWrite); |
| end |
| else begin |
| // Write them separate |
| WriteByteDirect( typeToWrite); |
| WriteI16( field.ID); |
| end; |
| |
| lastFieldId_ := field.ID; |
| end; |
| |
| |
| // Write the STOP symbol so we know there are no more fields in this struct. |
| procedure TCompactProtocolImpl.WriteFieldStop; |
| begin |
| WriteByteDirect( Byte( Types.STOP)); |
| end; |
| |
| |
| // 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. |
| procedure TCompactProtocolImpl.WriteMapBegin( const map: TThriftMap); |
| var key, val : Byte; |
| begin |
| if (map.Count = 0) |
| then WriteByteDirect( 0) |
| else begin |
| WriteVarint32( Cardinal( map.Count)); |
| key := getCompactType(map.KeyType); |
| val := getCompactType(map.ValueType); |
| WriteByteDirect( (key shl 4) or val); |
| end; |
| end; |
| |
| |
| // Write a list header. |
| procedure TCompactProtocolImpl.WriteListBegin( const list: TThriftList); |
| begin |
| WriteCollectionBegin( list.ElementType, list.Count); |
| end; |
| |
| |
| // Write a set header. |
| procedure TCompactProtocolImpl.WriteSetBegin( const set_: TThriftSet ); |
| begin |
| WriteCollectionBegin( set_.ElementType, set_.Count); |
| end; |
| |
| |
| // 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. |
| procedure TCompactProtocolImpl.WriteBool( b: Boolean); |
| var bt : Types; |
| begin |
| if b |
| then bt := Types.BOOLEAN_TRUE |
| else bt := Types.BOOLEAN_FALSE; |
| |
| if booleanField_.Type_ = TType.Bool_ then begin |
| // we haven't written the field header yet |
| WriteFieldBeginInternal( booleanField_, Byte(bt)); |
| booleanField_.Type_ := TType.Stop; |
| end |
| else begin |
| // we're not part of a field, so just Write the value. |
| WriteByteDirect( Byte(bt)); |
| end; |
| end; |
| |
| |
| // Write a byte. Nothing to see here! |
| procedure TCompactProtocolImpl.WriteByte( b: ShortInt); |
| begin |
| WriteByteDirect( Byte(b)); |
| end; |
| |
| |
| // Write an I16 as a zigzag varint. |
| procedure TCompactProtocolImpl.WriteI16( i16: SmallInt); |
| begin |
| WriteVarint32( intToZigZag( i16)); |
| end; |
| |
| |
| // Write an i32 as a zigzag varint. |
| procedure TCompactProtocolImpl.WriteI32( i32: Integer); |
| begin |
| WriteVarint32( intToZigZag( i32)); |
| end; |
| |
| |
| // Write an i64 as a zigzag varint. |
| procedure TCompactProtocolImpl.WriteI64( const i64: Int64); |
| begin |
| WriteVarint64( longToZigzag( i64)); |
| end; |
| |
| |
| class function TCompactProtocolImpl.DoubleToInt64Bits( const db : Double) : Int64; |
| begin |
| ASSERT( SizeOf(db) = SizeOf(result)); |
| Move( db, result, SizeOf(result)); |
| end; |
| |
| |
| class function TCompactProtocolImpl.Int64BitsToDouble( const i64 : Int64) : Double; |
| begin |
| ASSERT( SizeOf(i64) = SizeOf(result)); |
| Move( i64, result, SizeOf(result)); |
| end; |
| |
| |
| // Write a double to the wire as 8 bytes. |
| procedure TCompactProtocolImpl.WriteDouble( const dub: Double); |
| var data : TBytes; |
| begin |
| fixedLongToBytes( DoubleToInt64Bits(dub), data); |
| Transport.Write( data); |
| end; |
| |
| |
| // Write a byte array, using a varint for the size. |
| procedure TCompactProtocolImpl.WriteBinary( const b: TBytes); |
| begin |
| WriteVarint32( Cardinal(Length(b))); |
| Transport.Write( b); |
| end; |
| |
| procedure TCompactProtocolImpl.WriteMessageEnd; |
| begin |
| // nothing to do |
| end; |
| |
| |
| procedure TCompactProtocolImpl.WriteMapEnd; |
| begin |
| // nothing to do |
| end; |
| |
| |
| procedure TCompactProtocolImpl.WriteListEnd; |
| begin |
| // nothing to do |
| end; |
| |
| |
| procedure TCompactProtocolImpl.WriteSetEnd; |
| begin |
| // nothing to do |
| end; |
| |
| |
| procedure TCompactProtocolImpl.WriteFieldEnd; |
| begin |
| // nothing to do |
| end; |
| |
| |
| // Abstract method for writing the start of lists and sets. List and sets on |
| // the wire differ only by the type indicator. |
| procedure TCompactProtocolImpl.WriteCollectionBegin( const elemType : TType; size : Integer); |
| begin |
| if size <= 14 |
| then WriteByteDirect( (size shl 4) or getCompactType(elemType)) |
| else begin |
| WriteByteDirect( $F0 or getCompactType(elemType)); |
| WriteVarint32( Cardinal(size)); |
| end; |
| end; |
| |
| |
| // Write an i64 as a varint. Results in 1-10 bytes on the wire. |
| procedure TCompactProtocolImpl.WriteVarint64( n : UInt64); |
| var varint64out : TBytes; |
| idx : Integer; |
| begin |
| SetLength( varint64out, 10); |
| idx := 0; |
| while TRUE do begin |
| ASSERT( idx < Length(varint64out)); |
| |
| // last one? |
| if (n and not UInt64($7F)) = 0 then begin |
| varint64out[idx] := Byte(n); |
| Inc(idx); |
| Break; |
| end; |
| |
| varint64out[idx] := Byte((n and $7F) or $80); |
| Inc(idx); |
| n := n shr 7; |
| end; |
| |
| Transport.Write( varint64out, 0, idx); |
| end; |
| |
| |
| // Convert l into a zigzag Int64. This allows negative numbers to be |
| // represented compactly as a varint. |
| class function TCompactProtocolImpl.longToZigzag( const n : Int64) : UInt64; |
| begin |
| // there is no arithmetic right shift in Delphi |
| if n >= 0 |
| then result := UInt64(n shl 1) |
| else result := UInt64(n shl 1) xor $FFFFFFFFFFFFFFFF; |
| end; |
| |
| |
| // Convert n into a zigzag Integer. This allows negative numbers to be |
| // represented compactly as a varint. |
| class function TCompactProtocolImpl.intToZigZag( const n : Integer) : Cardinal; |
| begin |
| // there is no arithmetic right shift in Delphi |
| if n >= 0 |
| then result := Cardinal(n shl 1) |
| else result := Cardinal(n shl 1) xor $FFFFFFFF; |
| end; |
| |
| |
| // Convert a Int64 into 8 little-endian bytes in buf |
| class procedure TCompactProtocolImpl.fixedLongToBytes( const n : Int64; var buf : TBytes); |
| begin |
| SetLength( buf, 8); |
| buf[0] := Byte( n and $FF); |
| buf[1] := Byte((n shr 8) and $FF); |
| buf[2] := Byte((n shr 16) and $FF); |
| buf[3] := Byte((n shr 24) and $FF); |
| buf[4] := Byte((n shr 32) and $FF); |
| buf[5] := Byte((n shr 40) and $FF); |
| buf[6] := Byte((n shr 48) and $FF); |
| buf[7] := Byte((n shr 56) and $FF); |
| end; |
| |
| |
| |
| // Read a message header. |
| function TCompactProtocolImpl.ReadMessageBegin : TThriftMessage; |
| var protocolId, versionAndType, version, type_ : Byte; |
| seqid : Integer; |
| msgNm : String; |
| begin |
| Reset; |
| |
| protocolId := Byte( ReadByte); |
| if (protocolId <> PROTOCOL_ID) |
| then raise TProtocolExceptionBadVersion.Create( 'Expected protocol id ' + IntToHex(PROTOCOL_ID,2) |
| + ' but got ' + IntToHex(protocolId,2)); |
| |
| versionAndType := Byte( ReadByte); |
| version := Byte( versionAndType and VERSION_MASK); |
| if (version <> VERSION) |
| then raise TProtocolExceptionBadVersion.Create( 'Expected version ' +IntToStr(VERSION) |
| + ' but got ' + IntToStr(version)); |
| |
| type_ := Byte( (versionAndType shr TYPE_SHIFT_AMOUNT) and TYPE_BITS); |
| seqid := Integer( ReadVarint32); |
| msgNm := ReadString; |
| Init( result, msgNm, TMessageType(type_), seqid); |
| end; |
| |
| |
| // 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. |
| function TCompactProtocolImpl.ReadStructBegin: TThriftStruct; |
| begin |
| lastField_.Push( lastFieldId_); |
| lastFieldId_ := 0; |
| Init( result); |
| end; |
| |
| |
| // Doesn't actually consume any wire data, just removes the last field for |
| // this struct from the field stack. |
| procedure TCompactProtocolImpl.ReadStructEnd; |
| begin |
| // consume the last field we Read off the wire. |
| lastFieldId_ := lastField_.Pop(); |
| end; |
| |
| |
| // Read a field header off the wire. |
| function TCompactProtocolImpl.ReadFieldBegin: TThriftField; |
| var type_ : Byte; |
| modifier : ShortInt; |
| fieldId : SmallInt; |
| begin |
| type_ := Byte( ReadByte); |
| |
| // if it's a stop, then we can return immediately, as the struct is over. |
| if type_ = Byte(Types.STOP) then begin |
| Init( result, '', TType.Stop, 0); |
| Exit; |
| end; |
| |
| // mask off the 4 MSB of the type header. it could contain a field id delta. |
| modifier := ShortInt( (type_ and $F0) shr 4); |
| if (modifier = 0) |
| then fieldId := ReadI16 // not a delta. look ahead for the zigzag varint field id. |
| else fieldId := SmallInt( lastFieldId_ + modifier); // add the delta to the last Read field id. |
| |
| Init( result, '', getTType(Byte(type_ and $0F)), fieldId); |
| |
| // if this happens to be a boolean field, the value is encoded in the type |
| // save the boolean value in a special instance variable. |
| if isBoolType(type_) then begin |
| if Byte(type_ and $0F) = Byte(Types.BOOLEAN_TRUE) |
| then boolValue_ := bool_true |
| else boolValue_ := bool_false; |
| end; |
| |
| // push the new field onto the field stack so we can keep the deltas going. |
| lastFieldId_ := result.ID; |
| end; |
| |
| |
| // 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. |
| function TCompactProtocolImpl.ReadMapBegin: TThriftMap; |
| var size : Integer; |
| keyAndValueType : Byte; |
| key, val : TType; |
| begin |
| size := Integer( ReadVarint32); |
| if size = 0 |
| then keyAndValueType := 0 |
| else keyAndValueType := Byte( ReadByte); |
| |
| key := getTType( Byte( keyAndValueType shr 4)); |
| val := getTType( Byte( keyAndValueType and $F)); |
| Init( result, key, val, size); |
| ASSERT( (result.KeyType = key) and (result.ValueType = val)); |
| CheckReadBytesAvailable(result); |
| end; |
| |
| |
| // 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 $F, and a varint will follow with the |
| // true size. |
| function TCompactProtocolImpl.ReadListBegin: TThriftList; |
| var size_and_type : Byte; |
| size : Integer; |
| type_ : TType; |
| begin |
| size_and_type := Byte( ReadByte); |
| |
| size := (size_and_type shr 4) and $0F; |
| if (size = 15) |
| then size := Integer( ReadVarint32); |
| |
| type_ := getTType( size_and_type); |
| Init( result, type_, size); |
| CheckReadBytesAvailable(result); |
| end; |
| |
| |
| // 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 $F, and a varint will follow with the |
| // true size. |
| function TCompactProtocolImpl.ReadSetBegin: TThriftSet; |
| var size_and_type : Byte; |
| size : Integer; |
| type_ : TType; |
| begin |
| size_and_type := Byte( ReadByte); |
| |
| size := (size_and_type shr 4) and $0F; |
| if (size = 15) |
| then size := Integer( ReadVarint32); |
| |
| type_ := getTType( size_and_type); |
| Init( result, type_, size); |
| CheckReadBytesAvailable(result); |
| end; |
| |
| |
| // 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. |
| function TCompactProtocolImpl.ReadBool: Boolean; |
| begin |
| if boolValue_ <> unused then begin |
| result := (boolValue_ = bool_true); |
| boolValue_ := unused; |
| Exit; |
| end; |
| |
| result := (Byte(ReadByte) = Byte(Types.BOOLEAN_TRUE)); |
| end; |
| |
| |
| // Read a single byte off the wire. Nothing interesting here. |
| function TCompactProtocolImpl.ReadByte: ShortInt; |
| begin |
| Transport.ReadAll( @result, SizeOf(result), 0, 1); |
| end; |
| |
| |
| // Read an i16 from the wire as a zigzag varint. |
| function TCompactProtocolImpl.ReadI16: SmallInt; |
| begin |
| result := SmallInt( zigzagToInt( ReadVarint32)); |
| end; |
| |
| |
| // Read an i32 from the wire as a zigzag varint. |
| function TCompactProtocolImpl.ReadI32: Integer; |
| begin |
| result := zigzagToInt( ReadVarint32); |
| end; |
| |
| |
| // Read an i64 from the wire as a zigzag varint. |
| function TCompactProtocolImpl.ReadI64: Int64; |
| begin |
| result := zigzagToLong( ReadVarint64); |
| end; |
| |
| |
| // No magic here - just Read a double off the wire. |
| function TCompactProtocolImpl.ReadDouble:Double; |
| var longBits : TBytes; |
| begin |
| SetLength( longBits, 8); |
| Transport.ReadAll( longBits, 0, 8); |
| result := Int64BitsToDouble( bytesToLong( longBits)); |
| end; |
| |
| |
| // Read a byte[] from the wire. |
| function TCompactProtocolImpl.ReadBinary: TBytes; |
| var length : Integer; |
| begin |
| length := Integer( ReadVarint32); |
| FTrans.CheckReadBytesAvailable(length); |
| SetLength( result, length); |
| if (length > 0) |
| then Transport.ReadAll( result, 0, length); |
| end; |
| |
| |
| procedure TCompactProtocolImpl.ReadMessageEnd; |
| begin |
| // nothing to do |
| end; |
| |
| |
| procedure TCompactProtocolImpl.ReadFieldEnd; |
| begin |
| // nothing to do |
| end; |
| |
| |
| procedure TCompactProtocolImpl.ReadMapEnd; |
| begin |
| // nothing to do |
| end; |
| |
| |
| procedure TCompactProtocolImpl.ReadListEnd; |
| begin |
| // nothing to do |
| end; |
| |
| |
| procedure TCompactProtocolImpl.ReadSetEnd; |
| begin |
| // nothing to do |
| end; |
| |
| |
| |
| // 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. |
| function TCompactProtocolImpl.ReadVarint32 : Cardinal; |
| var shift : Integer; |
| b : Byte; |
| begin |
| result := 0; |
| shift := 0; |
| while TRUE do begin |
| b := Byte( ReadByte); |
| result := result or (Cardinal(b and $7F) shl shift); |
| if ((b and $80) <> $80) |
| then Break; |
| Inc( shift, 7); |
| end; |
| end; |
| |
| |
| // 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. |
| function TCompactProtocolImpl.ReadVarint64 : UInt64; |
| var shift : Integer; |
| b : Byte; |
| begin |
| result := 0; |
| shift := 0; |
| while TRUE do begin |
| b := Byte( ReadByte); |
| result := result or (UInt64(b and $7F) shl shift); |
| if ((b and $80) <> $80) |
| then Break; |
| Inc( shift, 7); |
| end; |
| end; |
| |
| |
| // Convert from zigzag Integer to Integer. |
| class function TCompactProtocolImpl.zigzagToInt( const n : Cardinal ) : Integer; |
| begin |
| result := Integer(n shr 1) xor (-Integer(n and 1)); |
| end; |
| |
| |
| // Convert from zigzag Int64 to Int64. |
| class function TCompactProtocolImpl.zigzagToLong( const n : UInt64) : Int64; |
| begin |
| result := Int64(n shr 1) xor (-Int64(n and 1)); |
| end; |
| |
| |
| // Note that it's important that the mask bytes are Int64 literals, |
| // otherwise they'll default to ints, and when you shift an Integer left 56 bits, |
| // you just get a messed up Integer. |
| class function TCompactProtocolImpl.bytesToLong( const bytes : TBytes) : Int64; |
| begin |
| ASSERT( Length(bytes) >= 8); |
| result := (Int64(bytes[7] and $FF) shl 56) or |
| (Int64(bytes[6] and $FF) shl 48) or |
| (Int64(bytes[5] and $FF) shl 40) or |
| (Int64(bytes[4] and $FF) shl 32) or |
| (Int64(bytes[3] and $FF) shl 24) or |
| (Int64(bytes[2] and $FF) shl 16) or |
| (Int64(bytes[1] and $FF) shl 8) or |
| (Int64(bytes[0] and $FF)); |
| end; |
| |
| |
| class function TCompactProtocolImpl.isBoolType( const b : byte) : Boolean; |
| var lowerNibble : Byte; |
| begin |
| lowerNibble := b and $0f; |
| result := (Types(lowerNibble) in [Types.BOOLEAN_TRUE, Types.BOOLEAN_FALSE]); |
| end; |
| |
| |
| // Given a TCompactProtocol.Types constant, convert it to its corresponding TType value. |
| class function TCompactProtocolImpl.getTType( const type_ : byte) : TType; |
| var tct : Types; |
| begin |
| tct := Types( type_ and $0F); |
| if tct in [Low(Types)..High(Types)] |
| then result := tcompactTypeToType[tct] |
| else raise TProtocolExceptionInvalidData.Create('don''t know what type: '+IntToStr(Ord(tct))); |
| end; |
| |
| |
| // Given a TType value, find the appropriate TCompactProtocol.Types constant. |
| class function TCompactProtocolImpl.getCompactType( const ttype : TType) : Byte; |
| begin |
| if ttype in VALID_TTYPES |
| then result := Byte( ttypeToCompactType[ttype]) |
| else raise TProtocolExceptionInvalidData.Create('don''t know what type: '+IntToStr(Ord(ttype))); |
| end; |
| |
| |
| function TCompactProtocolImpl.GetMinSerializedSize( const aType : TType) : Integer; |
| // Return the minimum number of bytes a type will consume on the wire |
| begin |
| case aType of |
| TType.Stop: result := 0; |
| TType.Void: result := 0; |
| TType.Bool_: result := SizeOf(Byte); |
| TType.Byte_: result := SizeOf(Byte); |
| TType.Double_: result := 8; // uses fixedLongToBytes() which always writes 8 bytes |
| TType.I16: result := SizeOf(Byte); |
| TType.I32: result := SizeOf(Byte); |
| TType.I64: result := SizeOf(Byte); |
| TType.String_: result := SizeOf(Byte); // string length |
| TType.Struct: result := 0; // empty struct |
| TType.Map: result := SizeOf(Byte); // element count |
| TType.Set_: result := SizeOf(Byte); // element count |
| TType.List: result := SizeOf(Byte); // element count |
| else |
| raise TTransportExceptionBadArgs.Create('Unhandled type code'); |
| end; |
| end; |
| |
| |
| |
| |
| |
| //--- unit tests ------------------------------------------- |
| |
| {$IFDEF Debug} |
| procedure TestDoubleToInt64Bits; |
| |
| procedure TestPair( const a : Double; const b : Int64); |
| begin |
| ASSERT( TCompactProtocolImpl.DoubleToInt64Bits(a) = b); |
| ASSERT( TCompactProtocolImpl.Int64BitsToDouble(b) = a); |
| end; |
| |
| begin |
| TestPair( 1.0000000000000000E+000, Int64($3FF0000000000000)); |
| TestPair( 1.5000000000000000E+001, Int64($402E000000000000)); |
| TestPair( 2.5500000000000000E+002, Int64($406FE00000000000)); |
| TestPair( 4.2949672950000000E+009, Int64($41EFFFFFFFE00000)); |
| TestPair( 3.9062500000000000E-003, Int64($3F70000000000000)); |
| TestPair( 2.3283064365386963E-010, Int64($3DF0000000000000)); |
| TestPair( 1.2345678901230000E-300, Int64($01AA74FE1C1E7E45)); |
| TestPair( 1.2345678901234500E-150, Int64($20D02A36586DB4BB)); |
| TestPair( 1.2345678901234565E+000, Int64($3FF3C0CA428C59FA)); |
| TestPair( 1.2345678901234567E+000, Int64($3FF3C0CA428C59FB)); |
| TestPair( 1.2345678901234569E+000, Int64($3FF3C0CA428C59FC)); |
| TestPair( 1.2345678901234569E+150, Int64($5F182344CD3CDF9F)); |
| TestPair( 1.2345678901234569E+300, Int64($7E3D7EE8BCBBD352)); |
| TestPair( -1.7976931348623157E+308, Int64($FFEFFFFFFFFFFFFF)); |
| TestPair( 1.7976931348623157E+308, Int64($7FEFFFFFFFFFFFFF)); |
| TestPair( 4.9406564584124654E-324, Int64($0000000000000001)); |
| TestPair( 0.0000000000000000E+000, Int64($0000000000000000)); |
| TestPair( 4.94065645841247E-324, Int64($0000000000000001)); |
| TestPair( 3.2378592100206092E-319, Int64($000000000000FFFF)); |
| TestPair( 1.3906711615669959E-309, Int64($0000FFFFFFFFFFFF)); |
| TestPair( NegInfinity, Int64($FFF0000000000000)); |
| TestPair( Infinity, Int64($7FF0000000000000)); |
| |
| // NaN is special |
| ASSERT( TCompactProtocolImpl.DoubleToInt64Bits( NaN) = Int64($FFF8000000000000)); |
| ASSERT( IsNan( TCompactProtocolImpl.Int64BitsToDouble( Int64($FFF8000000000000)))); |
| end; |
| {$ENDIF} |
| |
| |
| {$IFDEF Debug} |
| procedure TestZigZag; |
| |
| procedure Test32( const test : Integer); |
| var zz : Cardinal; |
| begin |
| zz := TCompactProtocolImpl.intToZigZag(test); |
| ASSERT( TCompactProtocolImpl.zigzagToInt(zz) = test, IntToStr(test)); |
| end; |
| |
| procedure Test64( const test : Int64); |
| var zz : UInt64; |
| begin |
| zz := TCompactProtocolImpl.longToZigzag(test); |
| ASSERT( TCompactProtocolImpl.zigzagToLong(zz) = test, IntToStr(test)); |
| end; |
| |
| var i : Integer; |
| begin |
| // protobuf testcases |
| ASSERT( TCompactProtocolImpl.intToZigZag(0) = 0, 'pb #1 to ZigZag'); |
| ASSERT( TCompactProtocolImpl.intToZigZag(-1) = 1, 'pb #2 to ZigZag'); |
| ASSERT( TCompactProtocolImpl.intToZigZag(1) = 2, 'pb #3 to ZigZag'); |
| ASSERT( TCompactProtocolImpl.intToZigZag(-2) = 3, 'pb #4 to ZigZag'); |
| ASSERT( TCompactProtocolImpl.intToZigZag(+2147483647) = 4294967294, 'pb #5 to ZigZag'); |
| ASSERT( TCompactProtocolImpl.intToZigZag(-2147483648) = 4294967295, 'pb #6 to ZigZag'); |
| |
| // protobuf testcases |
| ASSERT( TCompactProtocolImpl.zigzagToInt(0) = 0, 'pb #1 from ZigZag'); |
| ASSERT( TCompactProtocolImpl.zigzagToInt(1) = -1, 'pb #2 from ZigZag'); |
| ASSERT( TCompactProtocolImpl.zigzagToInt(2) = 1, 'pb #3 from ZigZag'); |
| ASSERT( TCompactProtocolImpl.zigzagToInt(3) = -2, 'pb #4 from ZigZag'); |
| ASSERT( TCompactProtocolImpl.zigzagToInt(4294967294) = +2147483647, 'pb #5 from ZigZag'); |
| ASSERT( TCompactProtocolImpl.zigzagToInt(4294967295) = -2147483648, 'pb #6 from ZigZag'); |
| |
| // back and forth 32 |
| Test32( 0); |
| for i := 0 to 30 do begin |
| Test32( +(Integer(1) shl i)); |
| Test32( -(Integer(1) shl i)); |
| end; |
| Test32( Integer($7FFFFFFF)); |
| Test32( Integer($80000000)); |
| |
| // back and forth 64 |
| Test64( 0); |
| for i := 0 to 62 do begin |
| Test64( +(Int64(1) shl i)); |
| Test64( -(Int64(1) shl i)); |
| end; |
| Test64( Int64($7FFFFFFFFFFFFFFF)); |
| Test64( Int64($8000000000000000)); |
| end; |
| {$ENDIF} |
| |
| |
| {$IFDEF Debug} |
| procedure TestLongBytes; |
| |
| procedure Test( const test : Int64); |
| var buf : TBytes; |
| begin |
| TCompactProtocolImpl.fixedLongToBytes( test, buf); |
| ASSERT( TCompactProtocolImpl.bytesToLong( buf) = test, IntToStr(test)); |
| end; |
| |
| var i : Integer; |
| begin |
| Test( 0); |
| for i := 0 to 62 do begin |
| Test( +(Int64(1) shl i)); |
| Test( -(Int64(1) shl i)); |
| end; |
| Test( Int64($7FFFFFFFFFFFFFFF)); |
| Test( Int64($8000000000000000)); |
| end; |
| {$ENDIF} |
| |
| |
| {$IFDEF Debug} |
| procedure UnitTest; |
| var w : WORD; |
| const FPU_CW_DENORMALIZED = $0002; |
| begin |
| w := Get8087CW; |
| try |
| Set8087CW( w or FPU_CW_DENORMALIZED); |
| |
| TestDoubleToInt64Bits; |
| TestZigZag; |
| TestLongBytes; |
| |
| finally |
| Set8087CW( w); |
| end; |
| end; |
| {$ENDIF} |
| |
| |
| initialization |
| {$IFDEF Debug} |
| UnitTest; |
| {$ENDIF} |
| |
| end. |
| |