blob: 665cfc4ba34535b902571606a6c97c0989aa5958 [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.
*)
{$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.