blob: e9944d66446a03c734bf6dde8d538b1ff3fbc6cc [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
Jens Geyerf0e63312015-03-01 18:47:49 +0100525 fixedLongToBytes( DoubleToInt64Bits(dub), data);
526 Transport.Write( data);
527end;
528
529
530// Write a byte array, using a varint for the size.
531procedure TCompactProtocolImpl.WriteBinary( const b: TBytes);
532begin
533 WriteVarint32( Cardinal(Length(b)));
534 Transport.Write( b);
535end;
536
537procedure TCompactProtocolImpl.WriteMessageEnd;
538begin
539 // nothing to do
540end;
541
542
543procedure TCompactProtocolImpl.WriteMapEnd;
544begin
545 // nothing to do
546end;
547
548
549procedure TCompactProtocolImpl.WriteListEnd;
550begin
551 // nothing to do
552end;
553
554
555procedure TCompactProtocolImpl.WriteSetEnd;
556begin
557 // nothing to do
558end;
559
560
561procedure TCompactProtocolImpl.WriteFieldEnd;
562begin
563 // nothing to do
564end;
565
566
567// Abstract method for writing the start of lists and sets. List and sets on
568// the wire differ only by the type indicator.
569procedure TCompactProtocolImpl.WriteCollectionBegin( const elemType : TType; size : Integer);
570begin
571 if size <= 14
572 then WriteByteDirect( (size shl 4) or getCompactType(elemType))
573 else begin
574 WriteByteDirect( $F0 or getCompactType(elemType));
575 WriteVarint32( Cardinal(size));
576 end;
577end;
578
579
580// Write an i64 as a varint. Results in 1-10 bytes on the wire.
581procedure TCompactProtocolImpl.WriteVarint64( n : UInt64);
582var varint64out : TBytes;
583 idx : Integer;
584begin
585 SetLength( varint64out, 10);
586 idx := 0;
587 while TRUE do begin
588 ASSERT( idx < Length(varint64out));
589
590 // last one?
591 if (n and not UInt64($7F)) = 0 then begin
592 varint64out[idx] := Byte(n);
593 Inc(idx);
594 Break;
595 end;
596
597 varint64out[idx] := Byte((n and $7F) or $80);
598 Inc(idx);
599 n := n shr 7;
600 end;
601
602 Transport.Write( varint64out, 0, idx);
603end;
604
605
606// Convert l into a zigzag Int64. This allows negative numbers to be
607// represented compactly as a varint.
608class function TCompactProtocolImpl.longToZigzag( const n : Int64) : UInt64;
609begin
610 // there is no arithmetic right shift in Delphi
611 if n >= 0
612 then result := UInt64(n shl 1)
613 else result := UInt64(n shl 1) xor $FFFFFFFFFFFFFFFF;
614end;
615
616
617// Convert n into a zigzag Integer. This allows negative numbers to be
618// represented compactly as a varint.
619class function TCompactProtocolImpl.intToZigZag( const n : Integer) : Cardinal;
620begin
621 // there is no arithmetic right shift in Delphi
622 if n >= 0
623 then result := Cardinal(n shl 1)
624 else result := Cardinal(n shl 1) xor $FFFFFFFF;
625end;
626
627
628// Convert a Int64 into 8 little-endian bytes in buf
629class procedure TCompactProtocolImpl.fixedLongToBytes( const n : Int64; var buf : TBytes);
630begin
Jens Geyera6ea4442015-03-02 23:06:57 +0100631 SetLength( buf, 8);
Jens Geyerf0e63312015-03-01 18:47:49 +0100632 buf[0] := Byte( n and $FF);
633 buf[1] := Byte((n shr 8) and $FF);
634 buf[2] := Byte((n shr 16) and $FF);
635 buf[3] := Byte((n shr 24) and $FF);
636 buf[4] := Byte((n shr 32) and $FF);
637 buf[5] := Byte((n shr 40) and $FF);
638 buf[6] := Byte((n shr 48) and $FF);
639 buf[7] := Byte((n shr 56) and $FF);
640end;
641
642
643
644// Read a message header.
645function TCompactProtocolImpl.ReadMessageBegin : IMessage;
646var protocolId, versionAndType, version, type_ : Byte;
647 seqid : Integer;
648 msgNm : String;
649begin
650 Reset;
651
652 protocolId := Byte( ReadByte);
653 if (protocolId <> PROTOCOL_ID)
Jens Geyere0e32402016-04-20 21:50:48 +0200654 then raise TProtocolExceptionBadVersion.Create( 'Expected protocol id ' + IntToHex(PROTOCOL_ID,2)
655 + ' but got ' + IntToHex(protocolId,2));
Jens Geyerf0e63312015-03-01 18:47:49 +0100656
657 versionAndType := Byte( ReadByte);
658 version := Byte( versionAndType and VERSION_MASK);
659 if (version <> VERSION)
Jens Geyere0e32402016-04-20 21:50:48 +0200660 then raise TProtocolExceptionBadVersion.Create( 'Expected version ' +IntToStr(VERSION)
661 + ' but got ' + IntToStr(version));
Jens Geyerf0e63312015-03-01 18:47:49 +0100662
663 type_ := Byte( (versionAndType shr TYPE_SHIFT_AMOUNT) and TYPE_BITS);
664 seqid := Integer( ReadVarint32);
665 msgNm := ReadString;
666 result := TMessageImpl.Create( msgNm, TMessageType(type_), seqid);
667end;
668
669
670// Read a struct begin. There's nothing on the wire for this, but it is our
671// opportunity to push a new struct begin marker onto the field stack.
672function TCompactProtocolImpl.ReadStructBegin: IStruct;
673begin
674 lastField_.Push( lastFieldId_);
675 lastFieldId_ := 0;
676 result := TStructImpl.Create('');
677end;
678
679
680// Doesn't actually consume any wire data, just removes the last field for
681// this struct from the field stack.
682procedure TCompactProtocolImpl.ReadStructEnd;
683begin
684 // consume the last field we Read off the wire.
685 lastFieldId_ := lastField_.Pop();
686end;
687
688
689// Read a field header off the wire.
690function TCompactProtocolImpl.ReadFieldBegin: IField;
691var type_ : Byte;
692 fieldId, modifier : ShortInt;
693begin
694 type_ := Byte( ReadByte);
695
696 // if it's a stop, then we can return immediately, as the struct is over.
697 if type_ = Byte(Types.STOP) then begin
698 result := TFieldImpl.Create( '', TType.Stop, 0);
699 Exit;
700 end;
701
702 // mask off the 4 MSB of the type header. it could contain a field id delta.
703 modifier := ShortInt( (type_ and $F0) shr 4);
704 if (modifier = 0)
705 then fieldId := ReadI16 // not a delta. look ahead for the zigzag varint field id.
706 else fieldId := ShortInt( lastFieldId_ + modifier); // add the delta to the last Read field id.
707
708 result := TFieldImpl.Create( '', getTType(Byte(type_ and $0F)), fieldId);
709
710 // if this happens to be a boolean field, the value is encoded in the type
711 // save the boolean value in a special instance variable.
712 if isBoolType(type_) then begin
713 if Byte(type_ and $0F) = Byte(Types.BOOLEAN_TRUE)
714 then boolValue_ := bool_true
715 else boolValue_ := bool_false;
716 end;
717
718 // push the new field onto the field stack so we can keep the deltas going.
719 lastFieldId_ := result.ID;
720end;
721
722
723// Read a map header off the wire. If the size is zero, skip Reading the key
724// and value type. This means that 0-length maps will yield TMaps without the
725// "correct" types.
726function TCompactProtocolImpl.ReadMapBegin: IMap;
727var size : Integer;
728 keyAndValueType : Byte;
729 key, val : TType;
730begin
731 size := Integer( ReadVarint32);
732 if size = 0
733 then keyAndValueType := 0
734 else keyAndValueType := Byte( ReadByte);
735
736 key := getTType( Byte( keyAndValueType shr 4));
737 val := getTType( Byte( keyAndValueType and $F));
738 result := TMapImpl.Create( key, val, size);
739 ASSERT( (result.KeyType = key) and (result.ValueType = val));
740end;
741
742
743// Read a list header off the wire. If the list size is 0-14, the size will
744// be packed into the element type header. If it's a longer list, the 4 MSB
745// of the element type header will be $F, and a varint will follow with the
746// true size.
747function TCompactProtocolImpl.ReadListBegin: IList;
748var size_and_type : Byte;
749 size : Integer;
750 type_ : TType;
751begin
752 size_and_type := Byte( ReadByte);
753
754 size := (size_and_type shr 4) and $0F;
755 if (size = 15)
756 then size := Integer( ReadVarint32);
757
758 type_ := getTType( size_and_type);
759 result := TListImpl.Create( type_, size);
760end;
761
762
763// Read a set header off the wire. If the set size is 0-14, the size will
764// be packed into the element type header. If it's a longer set, the 4 MSB
765// of the element type header will be $F, and a varint will follow with the
766// true size.
767function TCompactProtocolImpl.ReadSetBegin: ISet;
768var size_and_type : Byte;
769 size : Integer;
770 type_ : TType;
771begin
772 size_and_type := Byte( ReadByte);
773
774 size := (size_and_type shr 4) and $0F;
775 if (size = 15)
776 then size := Integer( ReadVarint32);
777
778 type_ := getTType( size_and_type);
779 result := TSetImpl.Create( type_, size);
780end;
781
782
783// Read a boolean off the wire. If this is a boolean field, the value should
784// already have been Read during ReadFieldBegin, so we'll just consume the
785// pre-stored value. Otherwise, Read a byte.
786function TCompactProtocolImpl.ReadBool: Boolean;
787begin
788 if boolValue_ <> unused then begin
789 result := (boolValue_ = bool_true);
790 boolValue_ := unused;
791 Exit;
792 end;
793
794 result := (Byte(ReadByte) = Byte(Types.BOOLEAN_TRUE));
795end;
796
797
798// Read a single byte off the wire. Nothing interesting here.
799function TCompactProtocolImpl.ReadByte: ShortInt;
800var data : TBytes;
801begin
802 SetLength( data, 1);
803 Transport.ReadAll( data, 0, 1);
Jens Geyera61e5052016-02-23 16:32:55 +0100804 result := ShortInt(data[0]);
Jens Geyerf0e63312015-03-01 18:47:49 +0100805end;
806
807
808// Read an i16 from the wire as a zigzag varint.
809function TCompactProtocolImpl.ReadI16: SmallInt;
810begin
811 result := SmallInt( zigzagToInt( ReadVarint32));
812end;
813
814
815// Read an i32 from the wire as a zigzag varint.
816function TCompactProtocolImpl.ReadI32: Integer;
817begin
818 result := zigzagToInt( ReadVarint32);
819end;
820
821
822// Read an i64 from the wire as a zigzag varint.
823function TCompactProtocolImpl.ReadI64: Int64;
824begin
825 result := zigzagToLong( ReadVarint64);
826end;
827
828
829// No magic here - just Read a double off the wire.
830function TCompactProtocolImpl.ReadDouble:Double;
831var longBits : TBytes;
832begin
833 SetLength( longBits, 8);
834 Transport.ReadAll( longBits, 0, 8);
835 result := Int64BitsToDouble( bytesToLong( longBits));
836end;
837
838
839// Read a byte[] from the wire.
840function TCompactProtocolImpl.ReadBinary: TBytes;
841var length : Integer;
842begin
843 length := Integer( ReadVarint32);
844 SetLength( result, length);
845 if (length > 0)
846 then Transport.ReadAll( result, 0, length);
847end;
848
849
850procedure TCompactProtocolImpl.ReadMessageEnd;
851begin
852 // nothing to do
853end;
854
855
856procedure TCompactProtocolImpl.ReadFieldEnd;
857begin
858 // nothing to do
859end;
860
861
862procedure TCompactProtocolImpl.ReadMapEnd;
863begin
864 // nothing to do
865end;
866
867
868procedure TCompactProtocolImpl.ReadListEnd;
869begin
870 // nothing to do
871end;
872
873
874procedure TCompactProtocolImpl.ReadSetEnd;
875begin
876 // nothing to do
877end;
878
879
880
881// Read an i32 from the wire as a varint. The MSB of each byte is set
882// if there is another byte to follow. This can Read up to 5 bytes.
883function TCompactProtocolImpl.ReadVarint32 : Cardinal;
884var shift : Integer;
885 b : Byte;
886begin
887 result := 0;
888 shift := 0;
889 while TRUE do begin
890 b := Byte( ReadByte);
891 result := result or (Cardinal(b and $7F) shl shift);
892 if ((b and $80) <> $80)
893 then Break;
894 Inc( shift, 7);
895 end;
896end;
897
898
899// Read an i64 from the wire as a proper varint. The MSB of each byte is set
900// if there is another byte to follow. This can Read up to 10 bytes.
901function TCompactProtocolImpl.ReadVarint64 : UInt64;
902var shift : Integer;
903 b : Byte;
904begin
905 result := 0;
906 shift := 0;
907 while TRUE do begin
908 b := Byte( ReadByte);
909 result := result or (UInt64(b and $7F) shl shift);
910 if ((b and $80) <> $80)
911 then Break;
912 Inc( shift, 7);
913 end;
914end;
915
916
917// Convert from zigzag Integer to Integer.
918class function TCompactProtocolImpl.zigzagToInt( const n : Cardinal ) : Integer;
919begin
920 result := Integer(n shr 1) xor (-Integer(n and 1));
921end;
922
923
924// Convert from zigzag Int64 to Int64.
925class function TCompactProtocolImpl.zigzagToLong( const n : UInt64) : Int64;
926begin
927 result := Int64(n shr 1) xor (-Int64(n and 1));
928end;
929
930
931// Note that it's important that the mask bytes are Int64 literals,
932// otherwise they'll default to ints, and when you shift an Integer left 56 bits,
933// you just get a messed up Integer.
934class function TCompactProtocolImpl.bytesToLong( const bytes : TBytes) : Int64;
935begin
936 ASSERT( Length(bytes) >= 8);
937 result := (Int64(bytes[7] and $FF) shl 56) or
938 (Int64(bytes[6] and $FF) shl 48) or
939 (Int64(bytes[5] and $FF) shl 40) or
940 (Int64(bytes[4] and $FF) shl 32) or
941 (Int64(bytes[3] and $FF) shl 24) or
942 (Int64(bytes[2] and $FF) shl 16) or
943 (Int64(bytes[1] and $FF) shl 8) or
944 (Int64(bytes[0] and $FF));
945end;
946
947
948class function TCompactProtocolImpl.isBoolType( const b : byte) : Boolean;
949var lowerNibble : Byte;
950begin
951 lowerNibble := b and $0f;
952 result := (Types(lowerNibble) in [Types.BOOLEAN_TRUE, Types.BOOLEAN_FALSE]);
953end;
954
955
956// Given a TCompactProtocol.Types constant, convert it to its corresponding TType value.
957class function TCompactProtocolImpl.getTType( const type_ : byte) : TType;
958var tct : Types;
959begin
960 tct := Types( type_ and $0F);
961 if tct in [Low(Types)..High(Types)]
962 then result := tcompactTypeToType[tct]
Jens Geyere0e32402016-04-20 21:50:48 +0200963 else raise TProtocolExceptionInvalidData.Create('don''t know what type: '+IntToStr(Ord(tct)));
Jens Geyerf0e63312015-03-01 18:47:49 +0100964end;
965
966
967// Given a TType value, find the appropriate TCompactProtocol.Types constant.
968class function TCompactProtocolImpl.getCompactType( const ttype : TType) : Byte;
969begin
970 if ttype in VALID_TTYPES
971 then result := Byte( ttypeToCompactType[ttype])
Jens Geyere0e32402016-04-20 21:50:48 +0200972 else raise TProtocolExceptionInvalidData.Create('don''t know what type: '+IntToStr(Ord(ttype)));
Jens Geyerf0e63312015-03-01 18:47:49 +0100973end;
974
975
976//--- unit tests -------------------------------------------
977
978{$IFDEF Debug}
979procedure TestDoubleToInt64Bits;
980
981 procedure TestPair( const a : Double; const b : Int64);
982 begin
983 ASSERT( TCompactProtocolImpl.DoubleToInt64Bits(a) = b);
984 ASSERT( TCompactProtocolImpl.Int64BitsToDouble(b) = a);
985 end;
986
987begin
988 TestPair( 1.0000000000000000E+000, Int64($3FF0000000000000));
989 TestPair( 1.5000000000000000E+001, Int64($402E000000000000));
990 TestPair( 2.5500000000000000E+002, Int64($406FE00000000000));
991 TestPair( 4.2949672950000000E+009, Int64($41EFFFFFFFE00000));
992 TestPair( 3.9062500000000000E-003, Int64($3F70000000000000));
993 TestPair( 2.3283064365386963E-010, Int64($3DF0000000000000));
994 TestPair( 1.2345678901230000E-300, Int64($01AA74FE1C1E7E45));
995 TestPair( 1.2345678901234500E-150, Int64($20D02A36586DB4BB));
996 TestPair( 1.2345678901234565E+000, Int64($3FF3C0CA428C59FA));
997 TestPair( 1.2345678901234567E+000, Int64($3FF3C0CA428C59FB));
998 TestPair( 1.2345678901234569E+000, Int64($3FF3C0CA428C59FC));
999 TestPair( 1.2345678901234569E+150, Int64($5F182344CD3CDF9F));
1000 TestPair( 1.2345678901234569E+300, Int64($7E3D7EE8BCBBD352));
1001 TestPair( -1.7976931348623157E+308, Int64($FFEFFFFFFFFFFFFF));
1002 TestPair( 1.7976931348623157E+308, Int64($7FEFFFFFFFFFFFFF));
1003 TestPair( 4.9406564584124654E-324, Int64($0000000000000001));
1004 TestPair( 0.0000000000000000E+000, Int64($0000000000000000));
1005 TestPair( 4.94065645841247E-324, Int64($0000000000000001));
1006 TestPair( 3.2378592100206092E-319, Int64($000000000000FFFF));
1007 TestPair( 1.3906711615669959E-309, Int64($0000FFFFFFFFFFFF));
1008 TestPair( NegInfinity, Int64($FFF0000000000000));
1009 TestPair( Infinity, Int64($7FF0000000000000));
1010
1011 // NaN is special
1012 ASSERT( TCompactProtocolImpl.DoubleToInt64Bits( NaN) = Int64($FFF8000000000000));
1013 ASSERT( IsNan( TCompactProtocolImpl.Int64BitsToDouble( Int64($FFF8000000000000))));
1014end;
1015{$ENDIF}
1016
1017
1018{$IFDEF Debug}
1019procedure TestZigZag;
1020
1021 procedure Test32( const test : Integer);
1022 var zz : Cardinal;
1023 begin
1024 zz := TCompactProtocolImpl.intToZigZag(test);
1025 ASSERT( TCompactProtocolImpl.zigzagToInt(zz) = test, IntToStr(test));
1026 end;
1027
1028 procedure Test64( const test : Int64);
1029 var zz : UInt64;
1030 begin
1031 zz := TCompactProtocolImpl.longToZigzag(test);
1032 ASSERT( TCompactProtocolImpl.zigzagToLong(zz) = test, IntToStr(test));
1033 end;
1034
1035var i : Integer;
1036begin
1037 // protobuf testcases
Jens Geyerd6834402015-03-07 13:16:34 +01001038 ASSERT( TCompactProtocolImpl.intToZigZag(0) = 0, 'pb #1 to ZigZag');
1039 ASSERT( TCompactProtocolImpl.intToZigZag(-1) = 1, 'pb #2 to ZigZag');
1040 ASSERT( TCompactProtocolImpl.intToZigZag(1) = 2, 'pb #3 to ZigZag');
1041 ASSERT( TCompactProtocolImpl.intToZigZag(-2) = 3, 'pb #4 to ZigZag');
1042 ASSERT( TCompactProtocolImpl.intToZigZag(+2147483647) = 4294967294, 'pb #5 to ZigZag');
1043 ASSERT( TCompactProtocolImpl.intToZigZag(-2147483648) = 4294967295, 'pb #6 to ZigZag');
1044
1045 // protobuf testcases
1046 ASSERT( TCompactProtocolImpl.zigzagToInt(0) = 0, 'pb #1 from ZigZag');
1047 ASSERT( TCompactProtocolImpl.zigzagToInt(1) = -1, 'pb #2 from ZigZag');
1048 ASSERT( TCompactProtocolImpl.zigzagToInt(2) = 1, 'pb #3 from ZigZag');
1049 ASSERT( TCompactProtocolImpl.zigzagToInt(3) = -2, 'pb #4 from ZigZag');
1050 ASSERT( TCompactProtocolImpl.zigzagToInt(4294967294) = +2147483647, 'pb #5 from ZigZag');
1051 ASSERT( TCompactProtocolImpl.zigzagToInt(4294967295) = -2147483648, 'pb #6 from ZigZag');
Jens Geyerf0e63312015-03-01 18:47:49 +01001052
1053 // back and forth 32
1054 Test32( 0);
1055 for i := 0 to 30 do begin
1056 Test32( +(Integer(1) shl i));
1057 Test32( -(Integer(1) shl i));
1058 end;
1059 Test32( Integer($7FFFFFFF));
1060 Test32( Integer($80000000));
1061
1062 // back and forth 64
1063 Test64( 0);
1064 for i := 0 to 62 do begin
1065 Test64( +(Int64(1) shl i));
1066 Test64( -(Int64(1) shl i));
1067 end;
1068 Test64( Int64($7FFFFFFFFFFFFFFF));
1069 Test64( Int64($8000000000000000));
1070end;
1071{$ENDIF}
1072
1073
Jens Geyera6ea4442015-03-02 23:06:57 +01001074{$IFDEF Debug}
1075procedure TestLongBytes;
1076
1077 procedure Test( const test : Int64);
1078 var buf : TBytes;
1079 begin
1080 TCompactProtocolImpl.fixedLongToBytes( test, buf);
1081 ASSERT( TCompactProtocolImpl.bytesToLong( buf) = test, IntToStr(test));
1082 end;
1083
1084var i : Integer;
1085begin
1086 Test( 0);
1087 for i := 0 to 62 do begin
1088 Test( +(Int64(1) shl i));
1089 Test( -(Int64(1) shl i));
1090 end;
1091 Test( Int64($7FFFFFFFFFFFFFFF));
1092 Test( Int64($8000000000000000));
1093end;
1094{$ENDIF}
1095
1096
Jens Geyerf0e63312015-03-01 18:47:49 +01001097initialization
1098 {$IFDEF Debug}
1099 TestDoubleToInt64Bits;
1100 TestZigZag;
Jens Geyera6ea4442015-03-02 23:06:57 +01001101 TestLongBytes;
Jens Geyerf0e63312015-03-01 18:47:49 +01001102 {$ENDIF}
1103
1104end.
1105