blob: 89bf9fb2d7f36d5b11b5dd6d507ba0654beac143 [file] [log] [blame]
Jens Geyerf0e63312015-03-01 18:47:49 +01001(*
2 * Licensed to the Apache Software Foundation (ASF) under one
3 * or more contributor license agreements. See the NOTICE file
4 * distributed with this work for additional information
5 * regarding copyright ownership. The ASF licenses this file
6 * to you under the Apache License, Version 2.0 (the
7 * "License"); you may not use this file except in compliance
8 * with the License. You may obtain a copy of the License at
9 *
10 * http://www.apache.org/licenses/LICENSE-2.0
11 *
12 * Unless required by applicable law or agreed to in writing,
13 * software distributed under the License is distributed on an
14 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15 * KIND, either express or implied. See the License for the
16 * specific language governing permissions and limitations
17 * under the License.
18 *)
19
20{$SCOPEDENUMS ON}
21
22unit Thrift.Protocol.Compact;
23
24interface
25
26uses
27 Classes,
28 SysUtils,
29 Math,
30 Generics.Collections,
31 Thrift.Transport,
32 Thrift.Protocol,
33 Thrift.Utils;
34
35type
36 ICompactProtocol = interface( IProtocol)
37 ['{C01927EC-021A-45F7-93B1-23D6A5420EDD}']
38 end;
39
40 // Compact protocol implementation for thrift.
41 // Adapted from the C# version.
42 TCompactProtocolImpl = class( TProtocolImpl, ICompactProtocol)
43 public
44 type
45 TFactory = class( TInterfacedObject, IProtocolFactory)
46 public
47 function GetProtocol( const trans: ITransport): IProtocol;
48 end;
49
50 private const
51
52 { TODO
53 static TStruct ANONYMOUS_STRUCT = new TStruct("");
54 static TField TSTOP = new TField("", TType.Stop, (short)0);
55 }
56
57 PROTOCOL_ID = Byte( $82);
58 VERSION = Byte( 1);
59 VERSION_MASK = Byte( $1F); // 0001 1111
60 TYPE_MASK = Byte( $E0); // 1110 0000
61 TYPE_BITS = Byte( $07); // 0000 0111
62 TYPE_SHIFT_AMOUNT = Byte( 5);
63
64 private type
65 // All of the on-wire type codes.
66 Types = (
67 STOP = $00,
68 BOOLEAN_TRUE = $01,
69 BOOLEAN_FALSE = $02,
70 BYTE_ = $03,
71 I16 = $04,
72 I32 = $05,
73 I64 = $06,
74 DOUBLE_ = $07,
75 BINARY = $08,
76 LIST = $09,
77 SET_ = $0A,
78 MAP = $0B,
79 STRUCT = $0C
80 );
81
82 private const
83 ttypeToCompactType : array[TType] of Types = (
84 Types.STOP, // Stop = 0,
85 Types(-1), // Void = 1,
86 Types.BOOLEAN_TRUE, // Bool_ = 2,
87 Types.BYTE_, // Byte_ = 3,
88 Types.DOUBLE_, // Double_ = 4,
89 Types(-5), // unused
90 Types.I16, // I16 = 6,
91 Types(-7), // unused
92 Types.I32, // I32 = 8,
93 Types(-9), // unused
94 Types.I64, // I64 = 10,
95 Types.BINARY, // String_ = 11,
96 Types.STRUCT, // Struct = 12,
97 Types.MAP, // Map = 13,
98 Types.SET_, // Set_ = 14,
99 Types.LIST // List = 15,
100 );
101
102 tcompactTypeToType : array[Types] of TType = (
103 TType.Stop, // STOP
104 TType.Bool_, // BOOLEAN_TRUE
105 TType.Bool_, // BOOLEAN_FALSE
106 TType.Byte_, // BYTE_
107 TType.I16, // I16
108 TType.I32, // I32
109 TType.I64, // I64
110 TType.Double_, // DOUBLE_
111 TType.String_, // BINARY
112 TType.List, // LIST
113 TType.Set_, // SET_
114 TType.Map, // MAP
115 TType.Struct // STRUCT
116 );
117
118 private
119 // Used to keep track of the last field for the current and previous structs,
120 // so we can do the delta stuff.
121 lastField_ : TStack<Integer>;
122 lastFieldId_ : Integer;
123
124 // If we encounter a boolean field begin, save the TField here so it can
125 // have the value incorporated.
126 private booleanField_ : IField;
127
128 // If we Read a field header, and it's a boolean field, save the boolean
129 // value here so that ReadBool can use it.
130 private boolValue_ : ( unused, bool_true, bool_false);
131
132 public
133 constructor Create(const trans : ITransport);
134 destructor Destroy; override;
135
136 procedure Reset;
137
138 private
139 procedure WriteByteDirect( const b : Byte); overload;
140
141 // Writes a byte without any possibility of all that field header nonsense.
142 procedure WriteByteDirect( const n : Integer); overload;
143
144 // Write an i32 as a varint. Results in 1-5 bytes on the wire.
145 // TODO: make a permanent buffer like WriteVarint64?
146 procedure WriteVarint32( n : Cardinal);
147
148 private
149 // The workhorse of WriteFieldBegin. It has the option of doing a 'type override'
150 // of the type header. This is used specifically in the boolean field case.
151 procedure WriteFieldBeginInternal( const field : IField; typeOverride : Byte);
152
153 public
154 procedure WriteMessageBegin( const msg: IMessage); override;
155 procedure WriteMessageEnd; override;
156 procedure WriteStructBegin( const struc: IStruct); override;
157 procedure WriteStructEnd; override;
158 procedure WriteFieldBegin( const field: IField); override;
159 procedure WriteFieldEnd; override;
160 procedure WriteFieldStop; override;
161 procedure WriteMapBegin( const map: IMap); override;
162 procedure WriteMapEnd; override;
163 procedure WriteListBegin( const list: IList); override;
164 procedure WriteListEnd(); override;
165 procedure WriteSetBegin( const set_: ISet ); override;
166 procedure WriteSetEnd(); override;
167 procedure WriteBool( b: Boolean); override;
168 procedure WriteByte( b: ShortInt); override;
169 procedure WriteI16( i16: SmallInt); override;
170 procedure WriteI32( i32: Integer); override;
171 procedure WriteI64( const i64: Int64); override;
172 procedure WriteDouble( const dub: Double); override;
173 procedure WriteBinary( const b: TBytes); overload; override;
174
175 private
176 class function DoubleToInt64Bits( const db : Double) : Int64;
177 class function Int64BitsToDouble( const i64 : Int64) : Double;
178
179 // Abstract method for writing the start of lists and sets. List and sets on
180 // the wire differ only by the type indicator.
181 procedure WriteCollectionBegin( const elemType : TType; size : Integer);
182
183 procedure WriteVarint64( n : UInt64);
184
185 // Convert l into a zigzag long. This allows negative numbers to be
186 // represented compactly as a varint.
187 class function longToZigzag( const n : Int64) : UInt64;
188
189 // Convert n into a zigzag int. This allows negative numbers to be
190 // represented compactly as a varint.
191 class function intToZigZag( const n : Integer) : Cardinal;
192
193 //Convert a Int64 into little-endian bytes in buf starting at off and going until off+7.
194 class procedure fixedLongToBytes( const n : Int64; var buf : TBytes);
195
196 public
197 function ReadMessageBegin: IMessage; override;
198 procedure ReadMessageEnd(); override;
199 function ReadStructBegin: IStruct; override;
200 procedure ReadStructEnd; override;
201 function ReadFieldBegin: IField; override;
202 procedure ReadFieldEnd(); override;
203 function ReadMapBegin: IMap; override;
204 procedure ReadMapEnd(); override;
205 function ReadListBegin: IList; override;
206 procedure ReadListEnd(); override;
207 function ReadSetBegin: ISet; override;
208 procedure ReadSetEnd(); override;
209 function ReadBool: Boolean; override;
210 function ReadByte: ShortInt; override;
211 function ReadI16: SmallInt; override;
212 function ReadI32: Integer; override;
213 function ReadI64: Int64; override;
214 function ReadDouble:Double; override;
215 function ReadBinary: TBytes; overload; override;
216
217 private
218 // Internal Reading methods
219
220 // Read an i32 from the wire as a varint. The MSB of each byte is set
221 // if there is another byte to follow. This can Read up to 5 bytes.
222 function ReadVarint32 : Cardinal;
223
224 // Read an i64 from the wire as a proper varint. The MSB of each byte is set
225 // if there is another byte to follow. This can Read up to 10 bytes.
226 function ReadVarint64 : UInt64;
227
228
229 // encoding helpers
230
231 // Convert from zigzag Integer to Integer.
232 class function zigzagToInt( const n : Cardinal ) : Integer;
233
234 // Convert from zigzag Int64 to Int64.
235 class function zigzagToLong( const n : UInt64) : Int64;
236
237 // Note that it's important that the mask bytes are Int64 literals,
238 // otherwise they'll default to ints, and when you shift an Integer left 56 bits,
239 // you just get a messed up Integer.
240 class function bytesToLong( const bytes : TBytes) : Int64;
241
242 // type testing and converting
243 class function isBoolType( const b : byte) : Boolean;
244
245 // Given a TCompactProtocol.Types constant, convert it to its corresponding TType value.
246 class function getTType( const type_ : byte) : TType;
247
248 // Given a TType value, find the appropriate TCompactProtocol.Types constant.
249 class function getCompactType( const ttype : TType) : Byte;
250 end;
251
252
253implementation
254
255
256
257//--- TCompactProtocolImpl.TFactory ----------------------------------------
258
259
260function TCompactProtocolImpl.TFactory.GetProtocol( const trans: ITransport): IProtocol;
261begin
262 result := TCompactProtocolImpl.Create( trans);
263end;
264
265
266//--- TCompactProtocolImpl -------------------------------------------------
267
268
269constructor TCompactProtocolImpl.Create(const trans: ITransport);
270begin
271 inherited Create( trans);
272
273 lastFieldId_ := 0;
274 lastField_ := TStack<Integer>.Create;
275
276 booleanField_ := nil;
277 boolValue_ := unused;
278end;
279
280
281destructor TCompactProtocolImpl.Destroy;
282begin
283 try
284 FreeAndNil( lastField_);
285 finally
286 inherited Destroy;
287 end;
288end;
289
290
291
292procedure TCompactProtocolImpl.Reset;
293begin
294 lastField_.Clear();
295 lastFieldId_ := 0;
296 booleanField_ := nil;
297 boolValue_ := unused;
298end;
299
300
301// Writes a byte without any possibility of all that field header nonsense.
302// Used internally by other writing methods that know they need to Write a byte.
303procedure TCompactProtocolImpl.WriteByteDirect( const b : Byte);
304var data : TBytes;
305begin
306 SetLength( data, 1);
307 data[0] := b;
308 Transport.Write( data);
309end;
310
311
312// Writes a byte without any possibility of all that field header nonsense.
313procedure TCompactProtocolImpl.WriteByteDirect( const n : Integer);
314begin
315 WriteByteDirect( Byte(n));
316end;
317
318
319// Write an i32 as a varint. Results in 1-5 bytes on the wire.
320procedure TCompactProtocolImpl.WriteVarint32( n : Cardinal);
321var i32buf : TBytes;
322 idx : Integer;
323begin
324 SetLength( i32buf, 5);
325 idx := 0;
326 while TRUE do begin
327 ASSERT( idx < Length(i32buf));
328
329 // last part?
330 if ((n and not $7F) = 0) then begin
331 i32buf[idx] := Byte(n);
332 Inc(idx);
333 Break;
334 end;
335
336 i32buf[idx] := Byte((n and $7F) or $80);
337 Inc(idx);
338 n := n shr 7;
339 end;
340
341 Transport.Write( i32buf, 0, idx);
342end;
343
344
345// Write a message header to the wire. Compact Protocol messages contain the
346// protocol version so we can migrate forwards in the future if need be.
347procedure TCompactProtocolImpl.WriteMessageBegin( const msg: IMessage);
348var versionAndType : Byte;
349begin
350 Reset;
351
352 versionAndType := Byte( VERSION and VERSION_MASK)
353 or Byte( (Cardinal(msg.Type_) shl TYPE_SHIFT_AMOUNT) and TYPE_MASK);
354
355 WriteByteDirect( PROTOCOL_ID);
356 WriteByteDirect( versionAndType);
357 WriteVarint32( Cardinal(msg.SeqID));
358 WriteString( msg.Name);
359end;
360
361
362// Write a struct begin. This doesn't actually put anything on the wire. We use it as an
363// opportunity to put special placeholder markers on the field stack so we can get the
364// field id deltas correct.
365procedure TCompactProtocolImpl.WriteStructBegin( const struc: IStruct);
366begin
367 lastField_.Push(lastFieldId_);
368 lastFieldId_ := 0;
369end;
370
371
372// Write a struct end. This doesn't actually put anything on the wire. We use this as an
373// opportunity to pop the last field from the current struct off of the field stack.
374procedure TCompactProtocolImpl.WriteStructEnd;
375begin
376 lastFieldId_ := lastField_.Pop();
377end;
378
379
380// Write a field header containing the field id and field type. If the difference between the
381// current field id and the last one is small (< 15), then the field id will be encoded in
382// the 4 MSB as a delta. Otherwise, the field id will follow the type header as a zigzag varint.
383procedure TCompactProtocolImpl.WriteFieldBegin( const field: IField);
384begin
385 case field.Type_ of
386 TType.Bool_ : booleanField_ := field; // we want to possibly include the value, so we'll wait.
387 else
388 WriteFieldBeginInternal(field, $FF);
389 end;
390end;
391
392
393// The workhorse of WriteFieldBegin. It has the option of doing a 'type override'
394// of the type header. This is used specifically in the boolean field case.
395procedure TCompactProtocolImpl.WriteFieldBeginInternal( const field : IField; typeOverride : Byte);
396var typeToWrite : Byte;
397begin
398 // if there's a type override, use that.
399 if typeOverride = $FF
400 then typeToWrite := getCompactType( field.Type_)
401 else typeToWrite := typeOverride;
402
403 // check if we can use delta encoding for the field id
404 if (field.ID > lastFieldId_) and ((field.ID - lastFieldId_) <= 15)
405 then begin
406 // Write them together
407 WriteByteDirect( ((field.ID - lastFieldId_) shl 4) or typeToWrite);
408 end
409 else begin
410 // Write them separate
411 WriteByteDirect( typeToWrite);
412 WriteI16( field.ID);
413 end;
414
415 lastFieldId_ := field.ID;
416end;
417
418
419// Write the STOP symbol so we know there are no more fields in this struct.
420procedure TCompactProtocolImpl.WriteFieldStop;
421begin
422 WriteByteDirect( Byte( Types.STOP));
423end;
424
425
426// Write a map header. If the map is empty, omit the key and value type
427// headers, as we don't need any additional information to skip it.
428procedure TCompactProtocolImpl.WriteMapBegin( const map: IMap);
429var key, val : Byte;
430begin
431 if (map.Count = 0)
432 then WriteByteDirect( 0)
433 else begin
434 WriteVarint32( Cardinal( map.Count));
435 key := getCompactType(map.KeyType);
436 val := getCompactType(map.ValueType);
437 WriteByteDirect( (key shl 4) or val);
438 end;
439end;
440
441
442// Write a list header.
443procedure TCompactProtocolImpl.WriteListBegin( const list: IList);
444begin
445 WriteCollectionBegin( list.ElementType, list.Count);
446end;
447
448
449// Write a set header.
450procedure TCompactProtocolImpl.WriteSetBegin( const set_: ISet );
451begin
452 WriteCollectionBegin( set_.ElementType, set_.Count);
453end;
454
455
456// Write a boolean value. Potentially, this could be a boolean field, in
457// which case the field header info isn't written yet. If so, decide what the
458// right type header is for the value and then Write the field header.
459// Otherwise, Write a single byte.
460procedure TCompactProtocolImpl.WriteBool( b: Boolean);
461var bt : Types;
462begin
463 if b
464 then bt := Types.BOOLEAN_TRUE
465 else bt := Types.BOOLEAN_FALSE;
466
467 if booleanField_ <> nil then begin
468 // we haven't written the field header yet
469 WriteFieldBeginInternal( booleanField_, Byte(bt));
470 booleanField_ := nil;
471 end
472 else begin
473 // we're not part of a field, so just Write the value.
474 WriteByteDirect( Byte(bt));
475 end;
476end;
477
478
479// Write a byte. Nothing to see here!
480procedure TCompactProtocolImpl.WriteByte( b: ShortInt);
481begin
482 WriteByteDirect( Byte(b));
483end;
484
485
486// Write an I16 as a zigzag varint.
487procedure TCompactProtocolImpl.WriteI16( i16: SmallInt);
488begin
489 WriteVarint32( intToZigZag( i16));
490end;
491
492
493// Write an i32 as a zigzag varint.
494procedure TCompactProtocolImpl.WriteI32( i32: Integer);
495begin
496 WriteVarint32( intToZigZag( i32));
497end;
498
499
500// Write an i64 as a zigzag varint.
501procedure TCompactProtocolImpl.WriteI64( const i64: Int64);
502begin
503 WriteVarint64( longToZigzag( i64));
504end;
505
506
507class function TCompactProtocolImpl.DoubleToInt64Bits( const db : Double) : Int64;
508begin
509 ASSERT( SizeOf(db) = SizeOf(result));
510 Move( db, result, SizeOf(result));
511end;
512
513
514class function TCompactProtocolImpl.Int64BitsToDouble( const i64 : Int64) : Double;
515begin
516 ASSERT( SizeOf(i64) = SizeOf(result));
517 Move( i64, result, SizeOf(result));
518end;
519
520
521// Write a double to the wire as 8 bytes.
522procedure TCompactProtocolImpl.WriteDouble( const dub: Double);
523var data : TBytes;
524begin
525 SetLength( data, 8);
526 fixedLongToBytes( DoubleToInt64Bits(dub), data);
527 Transport.Write( data);
528end;
529
530
531// Write a byte array, using a varint for the size.
532procedure TCompactProtocolImpl.WriteBinary( const b: TBytes);
533begin
534 WriteVarint32( Cardinal(Length(b)));
535 Transport.Write( b);
536end;
537
538procedure TCompactProtocolImpl.WriteMessageEnd;
539begin
540 // nothing to do
541end;
542
543
544procedure TCompactProtocolImpl.WriteMapEnd;
545begin
546 // nothing to do
547end;
548
549
550procedure TCompactProtocolImpl.WriteListEnd;
551begin
552 // nothing to do
553end;
554
555
556procedure TCompactProtocolImpl.WriteSetEnd;
557begin
558 // nothing to do
559end;
560
561
562procedure TCompactProtocolImpl.WriteFieldEnd;
563begin
564 // nothing to do
565end;
566
567
568// Abstract method for writing the start of lists and sets. List and sets on
569// the wire differ only by the type indicator.
570procedure TCompactProtocolImpl.WriteCollectionBegin( const elemType : TType; size : Integer);
571begin
572 if size <= 14
573 then WriteByteDirect( (size shl 4) or getCompactType(elemType))
574 else begin
575 WriteByteDirect( $F0 or getCompactType(elemType));
576 WriteVarint32( Cardinal(size));
577 end;
578end;
579
580
581// Write an i64 as a varint. Results in 1-10 bytes on the wire.
582procedure TCompactProtocolImpl.WriteVarint64( n : UInt64);
583var varint64out : TBytes;
584 idx : Integer;
585begin
586 SetLength( varint64out, 10);
587 idx := 0;
588 while TRUE do begin
589 ASSERT( idx < Length(varint64out));
590
591 // last one?
592 if (n and not UInt64($7F)) = 0 then begin
593 varint64out[idx] := Byte(n);
594 Inc(idx);
595 Break;
596 end;
597
598 varint64out[idx] := Byte((n and $7F) or $80);
599 Inc(idx);
600 n := n shr 7;
601 end;
602
603 Transport.Write( varint64out, 0, idx);
604end;
605
606
607// Convert l into a zigzag Int64. This allows negative numbers to be
608// represented compactly as a varint.
609class function TCompactProtocolImpl.longToZigzag( const n : Int64) : UInt64;
610begin
611 // there is no arithmetic right shift in Delphi
612 if n >= 0
613 then result := UInt64(n shl 1)
614 else result := UInt64(n shl 1) xor $FFFFFFFFFFFFFFFF;
615end;
616
617
618// Convert n into a zigzag Integer. This allows negative numbers to be
619// represented compactly as a varint.
620class function TCompactProtocolImpl.intToZigZag( const n : Integer) : Cardinal;
621begin
622 // there is no arithmetic right shift in Delphi
623 if n >= 0
624 then result := Cardinal(n shl 1)
625 else result := Cardinal(n shl 1) xor $FFFFFFFF;
626end;
627
628
629// Convert a Int64 into 8 little-endian bytes in buf
630class procedure TCompactProtocolImpl.fixedLongToBytes( const n : Int64; var buf : TBytes);
631begin
632 ASSERT( Length(buf) >= 8);
633 buf[0] := Byte( n and $FF);
634 buf[1] := Byte((n shr 8) and $FF);
635 buf[2] := Byte((n shr 16) and $FF);
636 buf[3] := Byte((n shr 24) and $FF);
637 buf[4] := Byte((n shr 32) and $FF);
638 buf[5] := Byte((n shr 40) and $FF);
639 buf[6] := Byte((n shr 48) and $FF);
640 buf[7] := Byte((n shr 56) and $FF);
641end;
642
643
644
645// Read a message header.
646function TCompactProtocolImpl.ReadMessageBegin : IMessage;
647var protocolId, versionAndType, version, type_ : Byte;
648 seqid : Integer;
649 msgNm : String;
650begin
651 Reset;
652
653 protocolId := Byte( ReadByte);
654 if (protocolId <> PROTOCOL_ID)
655 then raise TProtocolException.Create( 'Expected protocol id ' + IntToHex(PROTOCOL_ID,2)
656 + ' but got ' + IntToHex(protocolId,2));
657
658 versionAndType := Byte( ReadByte);
659 version := Byte( versionAndType and VERSION_MASK);
660 if (version <> VERSION)
661 then raise TProtocolException.Create( 'Expected version ' +IntToStr(VERSION)
662 + ' but got ' + IntToStr(version));
663
664 type_ := Byte( (versionAndType shr TYPE_SHIFT_AMOUNT) and TYPE_BITS);
665 seqid := Integer( ReadVarint32);
666 msgNm := ReadString;
667 result := TMessageImpl.Create( msgNm, TMessageType(type_), seqid);
668end;
669
670
671// Read a struct begin. There's nothing on the wire for this, but it is our
672// opportunity to push a new struct begin marker onto the field stack.
673function TCompactProtocolImpl.ReadStructBegin: IStruct;
674begin
675 lastField_.Push( lastFieldId_);
676 lastFieldId_ := 0;
677 result := TStructImpl.Create('');
678end;
679
680
681// Doesn't actually consume any wire data, just removes the last field for
682// this struct from the field stack.
683procedure TCompactProtocolImpl.ReadStructEnd;
684begin
685 // consume the last field we Read off the wire.
686 lastFieldId_ := lastField_.Pop();
687end;
688
689
690// Read a field header off the wire.
691function TCompactProtocolImpl.ReadFieldBegin: IField;
692var type_ : Byte;
693 fieldId, modifier : ShortInt;
694begin
695 type_ := Byte( ReadByte);
696
697 // if it's a stop, then we can return immediately, as the struct is over.
698 if type_ = Byte(Types.STOP) then begin
699 result := TFieldImpl.Create( '', TType.Stop, 0);
700 Exit;
701 end;
702
703 // mask off the 4 MSB of the type header. it could contain a field id delta.
704 modifier := ShortInt( (type_ and $F0) shr 4);
705 if (modifier = 0)
706 then fieldId := ReadI16 // not a delta. look ahead for the zigzag varint field id.
707 else fieldId := ShortInt( lastFieldId_ + modifier); // add the delta to the last Read field id.
708
709 result := TFieldImpl.Create( '', getTType(Byte(type_ and $0F)), fieldId);
710
711 // if this happens to be a boolean field, the value is encoded in the type
712 // save the boolean value in a special instance variable.
713 if isBoolType(type_) then begin
714 if Byte(type_ and $0F) = Byte(Types.BOOLEAN_TRUE)
715 then boolValue_ := bool_true
716 else boolValue_ := bool_false;
717 end;
718
719 // push the new field onto the field stack so we can keep the deltas going.
720 lastFieldId_ := result.ID;
721end;
722
723
724// Read a map header off the wire. If the size is zero, skip Reading the key
725// and value type. This means that 0-length maps will yield TMaps without the
726// "correct" types.
727function TCompactProtocolImpl.ReadMapBegin: IMap;
728var size : Integer;
729 keyAndValueType : Byte;
730 key, val : TType;
731begin
732 size := Integer( ReadVarint32);
733 if size = 0
734 then keyAndValueType := 0
735 else keyAndValueType := Byte( ReadByte);
736
737 key := getTType( Byte( keyAndValueType shr 4));
738 val := getTType( Byte( keyAndValueType and $F));
739 result := TMapImpl.Create( key, val, size);
740 ASSERT( (result.KeyType = key) and (result.ValueType = val));
741end;
742
743
744// Read a list header off the wire. If the list size is 0-14, the size will
745// be packed into the element type header. If it's a longer list, the 4 MSB
746// of the element type header will be $F, and a varint will follow with the
747// true size.
748function TCompactProtocolImpl.ReadListBegin: IList;
749var size_and_type : Byte;
750 size : Integer;
751 type_ : TType;
752begin
753 size_and_type := Byte( ReadByte);
754
755 size := (size_and_type shr 4) and $0F;
756 if (size = 15)
757 then size := Integer( ReadVarint32);
758
759 type_ := getTType( size_and_type);
760 result := TListImpl.Create( type_, size);
761end;
762
763
764// Read a set header off the wire. If the set size is 0-14, the size will
765// be packed into the element type header. If it's a longer set, the 4 MSB
766// of the element type header will be $F, and a varint will follow with the
767// true size.
768function TCompactProtocolImpl.ReadSetBegin: ISet;
769var size_and_type : Byte;
770 size : Integer;
771 type_ : TType;
772begin
773 size_and_type := Byte( ReadByte);
774
775 size := (size_and_type shr 4) and $0F;
776 if (size = 15)
777 then size := Integer( ReadVarint32);
778
779 type_ := getTType( size_and_type);
780 result := TSetImpl.Create( type_, size);
781end;
782
783
784// Read a boolean off the wire. If this is a boolean field, the value should
785// already have been Read during ReadFieldBegin, so we'll just consume the
786// pre-stored value. Otherwise, Read a byte.
787function TCompactProtocolImpl.ReadBool: Boolean;
788begin
789 if boolValue_ <> unused then begin
790 result := (boolValue_ = bool_true);
791 boolValue_ := unused;
792 Exit;
793 end;
794
795 result := (Byte(ReadByte) = Byte(Types.BOOLEAN_TRUE));
796end;
797
798
799// Read a single byte off the wire. Nothing interesting here.
800function TCompactProtocolImpl.ReadByte: ShortInt;
801var data : TBytes;
802begin
803 SetLength( data, 1);
804 Transport.ReadAll( data, 0, 1);
805 result := data[0];
806end;
807
808
809// Read an i16 from the wire as a zigzag varint.
810function TCompactProtocolImpl.ReadI16: SmallInt;
811begin
812 result := SmallInt( zigzagToInt( ReadVarint32));
813end;
814
815
816// Read an i32 from the wire as a zigzag varint.
817function TCompactProtocolImpl.ReadI32: Integer;
818begin
819 result := zigzagToInt( ReadVarint32);
820end;
821
822
823// Read an i64 from the wire as a zigzag varint.
824function TCompactProtocolImpl.ReadI64: Int64;
825begin
826 result := zigzagToLong( ReadVarint64);
827end;
828
829
830// No magic here - just Read a double off the wire.
831function TCompactProtocolImpl.ReadDouble:Double;
832var longBits : TBytes;
833begin
834 SetLength( longBits, 8);
835 Transport.ReadAll( longBits, 0, 8);
836 result := Int64BitsToDouble( bytesToLong( longBits));
837end;
838
839
840// Read a byte[] from the wire.
841function TCompactProtocolImpl.ReadBinary: TBytes;
842var length : Integer;
843begin
844 length := Integer( ReadVarint32);
845 SetLength( result, length);
846 if (length > 0)
847 then Transport.ReadAll( result, 0, length);
848end;
849
850
851procedure TCompactProtocolImpl.ReadMessageEnd;
852begin
853 // nothing to do
854end;
855
856
857procedure TCompactProtocolImpl.ReadFieldEnd;
858begin
859 // nothing to do
860end;
861
862
863procedure TCompactProtocolImpl.ReadMapEnd;
864begin
865 // nothing to do
866end;
867
868
869procedure TCompactProtocolImpl.ReadListEnd;
870begin
871 // nothing to do
872end;
873
874
875procedure TCompactProtocolImpl.ReadSetEnd;
876begin
877 // nothing to do
878end;
879
880
881
882// Read an i32 from the wire as a varint. The MSB of each byte is set
883// if there is another byte to follow. This can Read up to 5 bytes.
884function TCompactProtocolImpl.ReadVarint32 : Cardinal;
885var shift : Integer;
886 b : Byte;
887begin
888 result := 0;
889 shift := 0;
890 while TRUE do begin
891 b := Byte( ReadByte);
892 result := result or (Cardinal(b and $7F) shl shift);
893 if ((b and $80) <> $80)
894 then Break;
895 Inc( shift, 7);
896 end;
897end;
898
899
900// Read an i64 from the wire as a proper varint. The MSB of each byte is set
901// if there is another byte to follow. This can Read up to 10 bytes.
902function TCompactProtocolImpl.ReadVarint64 : UInt64;
903var shift : Integer;
904 b : Byte;
905begin
906 result := 0;
907 shift := 0;
908 while TRUE do begin
909 b := Byte( ReadByte);
910 result := result or (UInt64(b and $7F) shl shift);
911 if ((b and $80) <> $80)
912 then Break;
913 Inc( shift, 7);
914 end;
915end;
916
917
918// Convert from zigzag Integer to Integer.
919class function TCompactProtocolImpl.zigzagToInt( const n : Cardinal ) : Integer;
920begin
921 result := Integer(n shr 1) xor (-Integer(n and 1));
922end;
923
924
925// Convert from zigzag Int64 to Int64.
926class function TCompactProtocolImpl.zigzagToLong( const n : UInt64) : Int64;
927begin
928 result := Int64(n shr 1) xor (-Int64(n and 1));
929end;
930
931
932// Note that it's important that the mask bytes are Int64 literals,
933// otherwise they'll default to ints, and when you shift an Integer left 56 bits,
934// you just get a messed up Integer.
935class function TCompactProtocolImpl.bytesToLong( const bytes : TBytes) : Int64;
936begin
937 ASSERT( Length(bytes) >= 8);
938 result := (Int64(bytes[7] and $FF) shl 56) or
939 (Int64(bytes[6] and $FF) shl 48) or
940 (Int64(bytes[5] and $FF) shl 40) or
941 (Int64(bytes[4] and $FF) shl 32) or
942 (Int64(bytes[3] and $FF) shl 24) or
943 (Int64(bytes[2] and $FF) shl 16) or
944 (Int64(bytes[1] and $FF) shl 8) or
945 (Int64(bytes[0] and $FF));
946end;
947
948
949class function TCompactProtocolImpl.isBoolType( const b : byte) : Boolean;
950var lowerNibble : Byte;
951begin
952 lowerNibble := b and $0f;
953 result := (Types(lowerNibble) in [Types.BOOLEAN_TRUE, Types.BOOLEAN_FALSE]);
954end;
955
956
957// Given a TCompactProtocol.Types constant, convert it to its corresponding TType value.
958class function TCompactProtocolImpl.getTType( const type_ : byte) : TType;
959var tct : Types;
960begin
961 tct := Types( type_ and $0F);
962 if tct in [Low(Types)..High(Types)]
963 then result := tcompactTypeToType[tct]
964 else raise TProtocolException.Create('don''t know what type: '+IntToStr(Ord(tct)));
965end;
966
967
968// Given a TType value, find the appropriate TCompactProtocol.Types constant.
969class function TCompactProtocolImpl.getCompactType( const ttype : TType) : Byte;
970begin
971 if ttype in VALID_TTYPES
972 then result := Byte( ttypeToCompactType[ttype])
973 else raise TProtocolException.Create('don''t know what type: '+IntToStr(Ord(ttype)));
974end;
975
976
977//--- unit tests -------------------------------------------
978
979{$IFDEF Debug}
980procedure TestDoubleToInt64Bits;
981
982 procedure TestPair( const a : Double; const b : Int64);
983 begin
984 ASSERT( TCompactProtocolImpl.DoubleToInt64Bits(a) = b);
985 ASSERT( TCompactProtocolImpl.Int64BitsToDouble(b) = a);
986 end;
987
988begin
989 TestPair( 1.0000000000000000E+000, Int64($3FF0000000000000));
990 TestPair( 1.5000000000000000E+001, Int64($402E000000000000));
991 TestPair( 2.5500000000000000E+002, Int64($406FE00000000000));
992 TestPair( 4.2949672950000000E+009, Int64($41EFFFFFFFE00000));
993 TestPair( 3.9062500000000000E-003, Int64($3F70000000000000));
994 TestPair( 2.3283064365386963E-010, Int64($3DF0000000000000));
995 TestPair( 1.2345678901230000E-300, Int64($01AA74FE1C1E7E45));
996 TestPair( 1.2345678901234500E-150, Int64($20D02A36586DB4BB));
997 TestPair( 1.2345678901234565E+000, Int64($3FF3C0CA428C59FA));
998 TestPair( 1.2345678901234567E+000, Int64($3FF3C0CA428C59FB));
999 TestPair( 1.2345678901234569E+000, Int64($3FF3C0CA428C59FC));
1000 TestPair( 1.2345678901234569E+150, Int64($5F182344CD3CDF9F));
1001 TestPair( 1.2345678901234569E+300, Int64($7E3D7EE8BCBBD352));
1002 TestPair( -1.7976931348623157E+308, Int64($FFEFFFFFFFFFFFFF));
1003 TestPair( 1.7976931348623157E+308, Int64($7FEFFFFFFFFFFFFF));
1004 TestPair( 4.9406564584124654E-324, Int64($0000000000000001));
1005 TestPair( 0.0000000000000000E+000, Int64($0000000000000000));
1006 TestPair( 4.94065645841247E-324, Int64($0000000000000001));
1007 TestPair( 3.2378592100206092E-319, Int64($000000000000FFFF));
1008 TestPair( 1.3906711615669959E-309, Int64($0000FFFFFFFFFFFF));
1009 TestPair( NegInfinity, Int64($FFF0000000000000));
1010 TestPair( Infinity, Int64($7FF0000000000000));
1011
1012 // NaN is special
1013 ASSERT( TCompactProtocolImpl.DoubleToInt64Bits( NaN) = Int64($FFF8000000000000));
1014 ASSERT( IsNan( TCompactProtocolImpl.Int64BitsToDouble( Int64($FFF8000000000000))));
1015end;
1016{$ENDIF}
1017
1018
1019{$IFDEF Debug}
1020procedure TestZigZag;
1021
1022 procedure Test32( const test : Integer);
1023 var zz : Cardinal;
1024 begin
1025 zz := TCompactProtocolImpl.intToZigZag(test);
1026 ASSERT( TCompactProtocolImpl.zigzagToInt(zz) = test, IntToStr(test));
1027 end;
1028
1029 procedure Test64( const test : Int64);
1030 var zz : UInt64;
1031 begin
1032 zz := TCompactProtocolImpl.longToZigzag(test);
1033 ASSERT( TCompactProtocolImpl.zigzagToLong(zz) = test, IntToStr(test));
1034 end;
1035
1036var i : Integer;
1037begin
1038 // protobuf testcases
1039 ASSERT( TCompactProtocolImpl.intToZigZag(0) = 0, 'pb #1');
1040 ASSERT( TCompactProtocolImpl.intToZigZag(-1) = 1, 'pb #2');
1041 ASSERT( TCompactProtocolImpl.intToZigZag(1) = 2, 'pb #3');
1042 ASSERT( TCompactProtocolImpl.intToZigZag(-2) = 3, 'pb #4');
1043 ASSERT( TCompactProtocolImpl.intToZigZag(+2147483647) = 4294967294, 'pb #5');
1044 ASSERT( TCompactProtocolImpl.intToZigZag(-2147483648) = 4294967295, 'pb #6');
1045
1046 // back and forth 32
1047 Test32( 0);
1048 for i := 0 to 30 do begin
1049 Test32( +(Integer(1) shl i));
1050 Test32( -(Integer(1) shl i));
1051 end;
1052 Test32( Integer($7FFFFFFF));
1053 Test32( Integer($80000000));
1054
1055 // back and forth 64
1056 Test64( 0);
1057 for i := 0 to 62 do begin
1058 Test64( +(Int64(1) shl i));
1059 Test64( -(Int64(1) shl i));
1060 end;
1061 Test64( Int64($7FFFFFFFFFFFFFFF));
1062 Test64( Int64($8000000000000000));
1063end;
1064{$ENDIF}
1065
1066
1067initialization
1068 {$IFDEF Debug}
1069 TestDoubleToInt64Bits;
1070 TestZigZag;
1071 {$ENDIF}
1072
1073end.
1074