blob: 109e660644c022b46e63ea5f1a4fe0b15d3f93f5 [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
Jens Geyerfad7fd32019-11-09 23:24:52 +010050 strict private const
Jens Geyerf0e63312015-03-01 18:47:49 +010051
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
Jens Geyerfad7fd32019-11-09 23:24:52 +010064 strict private type
Jens Geyerf0e63312015-03-01 18:47:49 +010065 // 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
Jens Geyerfad7fd32019-11-09 23:24:52 +010082 strict private const
Jens Geyerf0e63312015-03-01 18:47:49 +010083 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
Jens Geyerfad7fd32019-11-09 23:24:52 +0100118 strict private
Jens Geyerf0e63312015-03-01 18:47:49 +0100119 // 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.
Jens Geyerfad7fd32019-11-09 23:24:52 +0100126 strict private booleanField_ : TThriftField;
Jens Geyerf0e63312015-03-01 18:47:49 +0100127
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.
Jens Geyerfad7fd32019-11-09 23:24:52 +0100130 strict private boolValue_ : ( unused, bool_true, bool_false);
Jens Geyerf0e63312015-03-01 18:47:49 +0100131
132 public
133 constructor Create(const trans : ITransport);
134 destructor Destroy; override;
135
Jens Geyerfad7fd32019-11-09 23:24:52 +0100136 strict private
Jens Geyerf0e63312015-03-01 18:47:49 +0100137 procedure WriteByteDirect( const b : Byte); overload;
138
139 // Writes a byte without any possibility of all that field header nonsense.
140 procedure WriteByteDirect( const n : Integer); overload;
141
142 // Write an i32 as a varint. Results in 1-5 bytes on the wire.
143 // TODO: make a permanent buffer like WriteVarint64?
144 procedure WriteVarint32( n : Cardinal);
145
Jens Geyerfad7fd32019-11-09 23:24:52 +0100146 strict private
Jens Geyerf0e63312015-03-01 18:47:49 +0100147 // The workhorse of WriteFieldBegin. It has the option of doing a 'type override'
148 // of the type header. This is used specifically in the boolean field case.
Jens Geyer17c3ad92017-09-05 20:31:27 +0200149 procedure WriteFieldBeginInternal( const field : TThriftField; typeOverride : Byte);
Jens Geyerf0e63312015-03-01 18:47:49 +0100150
151 public
Jens Geyer17c3ad92017-09-05 20:31:27 +0200152 procedure WriteMessageBegin( const msg: TThriftMessage); override;
Jens Geyerf0e63312015-03-01 18:47:49 +0100153 procedure WriteMessageEnd; override;
Jens Geyer17c3ad92017-09-05 20:31:27 +0200154 procedure WriteStructBegin( const struc: TThriftStruct); override;
Jens Geyerf0e63312015-03-01 18:47:49 +0100155 procedure WriteStructEnd; override;
Jens Geyer17c3ad92017-09-05 20:31:27 +0200156 procedure WriteFieldBegin( const field: TThriftField); override;
Jens Geyerf0e63312015-03-01 18:47:49 +0100157 procedure WriteFieldEnd; override;
158 procedure WriteFieldStop; override;
Jens Geyer17c3ad92017-09-05 20:31:27 +0200159 procedure WriteMapBegin( const map: TThriftMap); override;
Jens Geyerf0e63312015-03-01 18:47:49 +0100160 procedure WriteMapEnd; override;
Jens Geyer17c3ad92017-09-05 20:31:27 +0200161 procedure WriteListBegin( const list: TThriftList); override;
Jens Geyerf0e63312015-03-01 18:47:49 +0100162 procedure WriteListEnd(); override;
Jens Geyer17c3ad92017-09-05 20:31:27 +0200163 procedure WriteSetBegin( const set_: TThriftSet ); override;
Jens Geyerf0e63312015-03-01 18:47:49 +0100164 procedure WriteSetEnd(); override;
165 procedure WriteBool( b: Boolean); override;
166 procedure WriteByte( b: ShortInt); override;
167 procedure WriteI16( i16: SmallInt); override;
168 procedure WriteI32( i32: Integer); override;
169 procedure WriteI64( const i64: Int64); override;
170 procedure WriteDouble( const dub: Double); override;
171 procedure WriteBinary( const b: TBytes); overload; override;
172
Jens Geyer41f47af2019-11-09 23:24:52 +0100173 private // unit visible stuff
Jens Geyerf0e63312015-03-01 18:47:49 +0100174 class function DoubleToInt64Bits( const db : Double) : Int64;
175 class function Int64BitsToDouble( const i64 : Int64) : Double;
176
177 // Abstract method for writing the start of lists and sets. List and sets on
178 // the wire differ only by the type indicator.
179 procedure WriteCollectionBegin( const elemType : TType; size : Integer);
180
181 procedure WriteVarint64( n : UInt64);
182
183 // Convert l into a zigzag long. This allows negative numbers to be
184 // represented compactly as a varint.
185 class function longToZigzag( const n : Int64) : UInt64;
186
187 // Convert n into a zigzag int. This allows negative numbers to be
188 // represented compactly as a varint.
189 class function intToZigZag( const n : Integer) : Cardinal;
190
191 //Convert a Int64 into little-endian bytes in buf starting at off and going until off+7.
192 class procedure fixedLongToBytes( const n : Int64; var buf : TBytes);
193
Jens Geyer41f47af2019-11-09 23:24:52 +0100194 strict protected
195 function GetMinSerializedSize( const aType : TType) : Integer; override;
196 procedure Reset; override;
197
Jens Geyerf0e63312015-03-01 18:47:49 +0100198 public
Jens Geyer17c3ad92017-09-05 20:31:27 +0200199 function ReadMessageBegin: TThriftMessage; override;
Jens Geyerf0e63312015-03-01 18:47:49 +0100200 procedure ReadMessageEnd(); override;
Jens Geyer17c3ad92017-09-05 20:31:27 +0200201 function ReadStructBegin: TThriftStruct; override;
Jens Geyerf0e63312015-03-01 18:47:49 +0100202 procedure ReadStructEnd; override;
Jens Geyer17c3ad92017-09-05 20:31:27 +0200203 function ReadFieldBegin: TThriftField; override;
Jens Geyerf0e63312015-03-01 18:47:49 +0100204 procedure ReadFieldEnd(); override;
Jens Geyer17c3ad92017-09-05 20:31:27 +0200205 function ReadMapBegin: TThriftMap; override;
Jens Geyerf0e63312015-03-01 18:47:49 +0100206 procedure ReadMapEnd(); override;
Jens Geyer17c3ad92017-09-05 20:31:27 +0200207 function ReadListBegin: TThriftList; override;
Jens Geyerf0e63312015-03-01 18:47:49 +0100208 procedure ReadListEnd(); override;
Jens Geyer17c3ad92017-09-05 20:31:27 +0200209 function ReadSetBegin: TThriftSet; override;
Jens Geyerf0e63312015-03-01 18:47:49 +0100210 procedure ReadSetEnd(); override;
211 function ReadBool: Boolean; override;
212 function ReadByte: ShortInt; override;
213 function ReadI16: SmallInt; override;
214 function ReadI32: Integer; override;
215 function ReadI64: Int64; override;
216 function ReadDouble:Double; override;
217 function ReadBinary: TBytes; overload; override;
218
219 private
220 // Internal Reading methods
221
222 // Read an i32 from the wire as a varint. The MSB of each byte is set
223 // if there is another byte to follow. This can Read up to 5 bytes.
224 function ReadVarint32 : Cardinal;
225
226 // Read an i64 from the wire as a proper varint. The MSB of each byte is set
227 // if there is another byte to follow. This can Read up to 10 bytes.
228 function ReadVarint64 : UInt64;
229
230
231 // encoding helpers
232
233 // Convert from zigzag Integer to Integer.
234 class function zigzagToInt( const n : Cardinal ) : Integer;
235
236 // Convert from zigzag Int64 to Int64.
237 class function zigzagToLong( const n : UInt64) : Int64;
238
239 // Note that it's important that the mask bytes are Int64 literals,
240 // otherwise they'll default to ints, and when you shift an Integer left 56 bits,
241 // you just get a messed up Integer.
242 class function bytesToLong( const bytes : TBytes) : Int64;
243
244 // type testing and converting
245 class function isBoolType( const b : byte) : Boolean;
246
247 // Given a TCompactProtocol.Types constant, convert it to its corresponding TType value.
248 class function getTType( const type_ : byte) : TType;
249
250 // Given a TType value, find the appropriate TCompactProtocol.Types constant.
251 class function getCompactType( const ttype : TType) : Byte;
252 end;
253
254
255implementation
256
257
258
259//--- TCompactProtocolImpl.TFactory ----------------------------------------
260
261
262function TCompactProtocolImpl.TFactory.GetProtocol( const trans: ITransport): IProtocol;
263begin
264 result := TCompactProtocolImpl.Create( trans);
265end;
266
267
268//--- TCompactProtocolImpl -------------------------------------------------
269
270
271constructor TCompactProtocolImpl.Create(const trans: ITransport);
272begin
273 inherited Create( trans);
274
275 lastFieldId_ := 0;
276 lastField_ := TStack<Integer>.Create;
277
Jens Geyer17c3ad92017-09-05 20:31:27 +0200278 Init( booleanField_, '', TType.Stop, 0);
Jens Geyerf0e63312015-03-01 18:47:49 +0100279 boolValue_ := unused;
280end;
281
282
283destructor TCompactProtocolImpl.Destroy;
284begin
285 try
286 FreeAndNil( lastField_);
287 finally
288 inherited Destroy;
289 end;
290end;
291
292
293
294procedure TCompactProtocolImpl.Reset;
295begin
Jens Geyer41f47af2019-11-09 23:24:52 +0100296 inherited Reset;
Jens Geyerf0e63312015-03-01 18:47:49 +0100297 lastField_.Clear();
298 lastFieldId_ := 0;
Jens Geyer17c3ad92017-09-05 20:31:27 +0200299 Init( booleanField_, '', TType.Stop, 0);
Jens Geyerf0e63312015-03-01 18:47:49 +0100300 boolValue_ := unused;
301end;
302
303
304// Writes a byte without any possibility of all that field header nonsense.
305// Used internally by other writing methods that know they need to Write a byte.
306procedure TCompactProtocolImpl.WriteByteDirect( const b : Byte);
Jens Geyerf0e63312015-03-01 18:47:49 +0100307begin
Jens Geyer17c3ad92017-09-05 20:31:27 +0200308 Transport.Write( @b, SizeOf(b));
Jens Geyerf0e63312015-03-01 18:47:49 +0100309end;
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.
Jens Geyer17c3ad92017-09-05 20:31:27 +0200347procedure TCompactProtocolImpl.WriteMessageBegin( const msg: TThriftMessage);
Jens Geyerf0e63312015-03-01 18:47:49 +0100348var 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.
Jens Geyer17c3ad92017-09-05 20:31:27 +0200365procedure TCompactProtocolImpl.WriteStructBegin( const struc: TThriftStruct);
Jens Geyerf0e63312015-03-01 18:47:49 +0100366begin
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.
Jens Geyer17c3ad92017-09-05 20:31:27 +0200383procedure TCompactProtocolImpl.WriteFieldBegin( const field: TThriftField);
Jens Geyerf0e63312015-03-01 18:47:49 +0100384begin
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.
Jens Geyer17c3ad92017-09-05 20:31:27 +0200395procedure TCompactProtocolImpl.WriteFieldBeginInternal( const field : TThriftField; typeOverride : Byte);
Jens Geyerf0e63312015-03-01 18:47:49 +0100396var 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.
Jens Geyer17c3ad92017-09-05 20:31:27 +0200428procedure TCompactProtocolImpl.WriteMapBegin( const map: TThriftMap);
Jens Geyerf0e63312015-03-01 18:47:49 +0100429var 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.
Jens Geyer17c3ad92017-09-05 20:31:27 +0200443procedure TCompactProtocolImpl.WriteListBegin( const list: TThriftList);
Jens Geyerf0e63312015-03-01 18:47:49 +0100444begin
445 WriteCollectionBegin( list.ElementType, list.Count);
446end;
447
448
449// Write a set header.
Jens Geyer17c3ad92017-09-05 20:31:27 +0200450procedure TCompactProtocolImpl.WriteSetBegin( const set_: TThriftSet );
Jens Geyerf0e63312015-03-01 18:47:49 +0100451begin
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
Jens Geyer17c3ad92017-09-05 20:31:27 +0200467 if booleanField_.Type_ = TType.Bool_ then begin
Jens Geyerf0e63312015-03-01 18:47:49 +0100468 // we haven't written the field header yet
469 WriteFieldBeginInternal( booleanField_, Byte(bt));
Jens Geyer17c3ad92017-09-05 20:31:27 +0200470 booleanField_.Type_ := TType.Stop;
Jens Geyerf0e63312015-03-01 18:47:49 +0100471 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.
Jens Geyer17c3ad92017-09-05 20:31:27 +0200645function TCompactProtocolImpl.ReadMessageBegin : TThriftMessage;
Jens Geyerf0e63312015-03-01 18:47:49 +0100646var 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;
Jens Geyer17c3ad92017-09-05 20:31:27 +0200666 Init( result, msgNm, TMessageType(type_), seqid);
Jens Geyerf0e63312015-03-01 18:47:49 +0100667end;
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.
Jens Geyer17c3ad92017-09-05 20:31:27 +0200672function TCompactProtocolImpl.ReadStructBegin: TThriftStruct;
Jens Geyerf0e63312015-03-01 18:47:49 +0100673begin
674 lastField_.Push( lastFieldId_);
675 lastFieldId_ := 0;
Jens Geyer17c3ad92017-09-05 20:31:27 +0200676 Init( result);
Jens Geyerf0e63312015-03-01 18:47:49 +0100677end;
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.
Jens Geyer17c3ad92017-09-05 20:31:27 +0200690function TCompactProtocolImpl.ReadFieldBegin: TThriftField;
Jens Geyerf0e63312015-03-01 18:47:49 +0100691var type_ : Byte;
Jens Geyera715f702019-08-28 22:56:13 +0200692 modifier : ShortInt;
693 fieldId : SmallInt;
Jens Geyerf0e63312015-03-01 18:47:49 +0100694begin
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
Jens Geyer17c3ad92017-09-05 20:31:27 +0200699 Init( result, '', TType.Stop, 0);
Jens Geyerf0e63312015-03-01 18:47:49 +0100700 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.
Jens Geyera715f702019-08-28 22:56:13 +0200707 else fieldId := SmallInt( lastFieldId_ + modifier); // add the delta to the last Read field id.
Jens Geyerf0e63312015-03-01 18:47:49 +0100708
Jens Geyer17c3ad92017-09-05 20:31:27 +0200709 Init( result, '', getTType(Byte(type_ and $0F)), fieldId);
Jens Geyerf0e63312015-03-01 18:47:49 +0100710
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.
Jens Geyer17c3ad92017-09-05 20:31:27 +0200727function TCompactProtocolImpl.ReadMapBegin: TThriftMap;
Jens Geyerf0e63312015-03-01 18:47:49 +0100728var 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));
Jens Geyer17c3ad92017-09-05 20:31:27 +0200739 Init( result, key, val, size);
Jens Geyerf0e63312015-03-01 18:47:49 +0100740 ASSERT( (result.KeyType = key) and (result.ValueType = val));
Jens Geyer41f47af2019-11-09 23:24:52 +0100741 CheckReadBytesAvailable(result);
Jens Geyerf0e63312015-03-01 18:47:49 +0100742end;
743
744
745// Read a list header off the wire. If the list size is 0-14, the size will
746// be packed into the element type header. If it's a longer list, the 4 MSB
747// of the element type header will be $F, and a varint will follow with the
748// true size.
Jens Geyer17c3ad92017-09-05 20:31:27 +0200749function TCompactProtocolImpl.ReadListBegin: TThriftList;
Jens Geyerf0e63312015-03-01 18:47:49 +0100750var size_and_type : Byte;
751 size : Integer;
752 type_ : TType;
753begin
754 size_and_type := Byte( ReadByte);
755
756 size := (size_and_type shr 4) and $0F;
757 if (size = 15)
758 then size := Integer( ReadVarint32);
759
760 type_ := getTType( size_and_type);
Jens Geyer17c3ad92017-09-05 20:31:27 +0200761 Init( result, type_, size);
Jens Geyer41f47af2019-11-09 23:24:52 +0100762 CheckReadBytesAvailable(result);
Jens Geyerf0e63312015-03-01 18:47:49 +0100763end;
764
765
766// Read a set header off the wire. If the set size is 0-14, the size will
767// be packed into the element type header. If it's a longer set, the 4 MSB
768// of the element type header will be $F, and a varint will follow with the
769// true size.
Jens Geyer17c3ad92017-09-05 20:31:27 +0200770function TCompactProtocolImpl.ReadSetBegin: TThriftSet;
Jens Geyerf0e63312015-03-01 18:47:49 +0100771var size_and_type : Byte;
772 size : Integer;
773 type_ : TType;
774begin
775 size_and_type := Byte( ReadByte);
776
777 size := (size_and_type shr 4) and $0F;
778 if (size = 15)
779 then size := Integer( ReadVarint32);
780
781 type_ := getTType( size_and_type);
Jens Geyer17c3ad92017-09-05 20:31:27 +0200782 Init( result, type_, size);
Jens Geyer41f47af2019-11-09 23:24:52 +0100783 CheckReadBytesAvailable(result);
Jens Geyerf0e63312015-03-01 18:47:49 +0100784end;
785
786
787// Read a boolean off the wire. If this is a boolean field, the value should
788// already have been Read during ReadFieldBegin, so we'll just consume the
789// pre-stored value. Otherwise, Read a byte.
790function TCompactProtocolImpl.ReadBool: Boolean;
791begin
792 if boolValue_ <> unused then begin
793 result := (boolValue_ = bool_true);
794 boolValue_ := unused;
795 Exit;
796 end;
797
798 result := (Byte(ReadByte) = Byte(Types.BOOLEAN_TRUE));
799end;
800
801
802// Read a single byte off the wire. Nothing interesting here.
803function TCompactProtocolImpl.ReadByte: ShortInt;
Jens Geyerf0e63312015-03-01 18:47:49 +0100804begin
Jens Geyer17c3ad92017-09-05 20:31:27 +0200805 Transport.ReadAll( @result, SizeOf(result), 0, 1);
Jens Geyerf0e63312015-03-01 18:47:49 +0100806end;
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);
Jens Geyer41f47af2019-11-09 23:24:52 +0100845 FTrans.CheckReadBytesAvailable(length);
Jens Geyerf0e63312015-03-01 18:47:49 +0100846 SetLength( result, length);
847 if (length > 0)
848 then Transport.ReadAll( result, 0, length);
849end;
850
851
852procedure TCompactProtocolImpl.ReadMessageEnd;
853begin
854 // nothing to do
855end;
856
857
858procedure TCompactProtocolImpl.ReadFieldEnd;
859begin
860 // nothing to do
861end;
862
863
864procedure TCompactProtocolImpl.ReadMapEnd;
865begin
866 // nothing to do
867end;
868
869
870procedure TCompactProtocolImpl.ReadListEnd;
871begin
872 // nothing to do
873end;
874
875
876procedure TCompactProtocolImpl.ReadSetEnd;
877begin
878 // nothing to do
879end;
880
881
882
883// Read an i32 from the wire as a varint. The MSB of each byte is set
884// if there is another byte to follow. This can Read up to 5 bytes.
885function TCompactProtocolImpl.ReadVarint32 : Cardinal;
886var shift : Integer;
887 b : Byte;
888begin
889 result := 0;
890 shift := 0;
891 while TRUE do begin
892 b := Byte( ReadByte);
893 result := result or (Cardinal(b and $7F) shl shift);
894 if ((b and $80) <> $80)
895 then Break;
896 Inc( shift, 7);
897 end;
898end;
899
900
901// Read an i64 from the wire as a proper varint. The MSB of each byte is set
902// if there is another byte to follow. This can Read up to 10 bytes.
903function TCompactProtocolImpl.ReadVarint64 : UInt64;
904var shift : Integer;
905 b : Byte;
906begin
907 result := 0;
908 shift := 0;
909 while TRUE do begin
910 b := Byte( ReadByte);
911 result := result or (UInt64(b and $7F) shl shift);
912 if ((b and $80) <> $80)
913 then Break;
914 Inc( shift, 7);
915 end;
916end;
917
918
919// Convert from zigzag Integer to Integer.
920class function TCompactProtocolImpl.zigzagToInt( const n : Cardinal ) : Integer;
921begin
922 result := Integer(n shr 1) xor (-Integer(n and 1));
923end;
924
925
926// Convert from zigzag Int64 to Int64.
927class function TCompactProtocolImpl.zigzagToLong( const n : UInt64) : Int64;
928begin
929 result := Int64(n shr 1) xor (-Int64(n and 1));
930end;
931
932
933// Note that it's important that the mask bytes are Int64 literals,
934// otherwise they'll default to ints, and when you shift an Integer left 56 bits,
935// you just get a messed up Integer.
936class function TCompactProtocolImpl.bytesToLong( const bytes : TBytes) : Int64;
937begin
938 ASSERT( Length(bytes) >= 8);
939 result := (Int64(bytes[7] and $FF) shl 56) or
940 (Int64(bytes[6] and $FF) shl 48) or
941 (Int64(bytes[5] and $FF) shl 40) or
942 (Int64(bytes[4] and $FF) shl 32) or
943 (Int64(bytes[3] and $FF) shl 24) or
944 (Int64(bytes[2] and $FF) shl 16) or
945 (Int64(bytes[1] and $FF) shl 8) or
946 (Int64(bytes[0] and $FF));
947end;
948
949
950class function TCompactProtocolImpl.isBoolType( const b : byte) : Boolean;
951var lowerNibble : Byte;
952begin
953 lowerNibble := b and $0f;
954 result := (Types(lowerNibble) in [Types.BOOLEAN_TRUE, Types.BOOLEAN_FALSE]);
955end;
956
957
958// Given a TCompactProtocol.Types constant, convert it to its corresponding TType value.
959class function TCompactProtocolImpl.getTType( const type_ : byte) : TType;
960var tct : Types;
961begin
962 tct := Types( type_ and $0F);
963 if tct in [Low(Types)..High(Types)]
964 then result := tcompactTypeToType[tct]
Jens Geyere0e32402016-04-20 21:50:48 +0200965 else raise TProtocolExceptionInvalidData.Create('don''t know what type: '+IntToStr(Ord(tct)));
Jens Geyerf0e63312015-03-01 18:47:49 +0100966end;
967
968
969// Given a TType value, find the appropriate TCompactProtocol.Types constant.
970class function TCompactProtocolImpl.getCompactType( const ttype : TType) : Byte;
971begin
972 if ttype in VALID_TTYPES
973 then result := Byte( ttypeToCompactType[ttype])
Jens Geyere0e32402016-04-20 21:50:48 +0200974 else raise TProtocolExceptionInvalidData.Create('don''t know what type: '+IntToStr(Ord(ttype)));
Jens Geyerf0e63312015-03-01 18:47:49 +0100975end;
976
977
Jens Geyer41f47af2019-11-09 23:24:52 +0100978function TCompactProtocolImpl.GetMinSerializedSize( const aType : TType) : Integer;
979// Return the minimum number of bytes a type will consume on the wire
980begin
981 case aType of
982 TType.Stop: result := 0;
983 TType.Void: result := 0;
984 TType.Bool_: result := SizeOf(Byte);
985 TType.Byte_: result := SizeOf(Byte);
986 TType.Double_: result := 8; // uses fixedLongToBytes() which always writes 8 bytes
987 TType.I16: result := SizeOf(Byte);
988 TType.I32: result := SizeOf(Byte);
989 TType.I64: result := SizeOf(Byte);
990 TType.String_: result := SizeOf(Byte); // string length
991 TType.Struct: result := 0; // empty struct
992 TType.Map: result := SizeOf(Byte); // element count
993 TType.Set_: result := SizeOf(Byte); // element count
994 TType.List: result := SizeOf(Byte); // element count
995 else
996 raise TTransportExceptionBadArgs.Create('Unhandled type code');
997 end;
998end;
999
1000
1001
1002
1003
Jens Geyerf0e63312015-03-01 18:47:49 +01001004//--- unit tests -------------------------------------------
1005
1006{$IFDEF Debug}
1007procedure TestDoubleToInt64Bits;
1008
1009 procedure TestPair( const a : Double; const b : Int64);
1010 begin
1011 ASSERT( TCompactProtocolImpl.DoubleToInt64Bits(a) = b);
1012 ASSERT( TCompactProtocolImpl.Int64BitsToDouble(b) = a);
1013 end;
1014
1015begin
1016 TestPair( 1.0000000000000000E+000, Int64($3FF0000000000000));
1017 TestPair( 1.5000000000000000E+001, Int64($402E000000000000));
1018 TestPair( 2.5500000000000000E+002, Int64($406FE00000000000));
1019 TestPair( 4.2949672950000000E+009, Int64($41EFFFFFFFE00000));
1020 TestPair( 3.9062500000000000E-003, Int64($3F70000000000000));
1021 TestPair( 2.3283064365386963E-010, Int64($3DF0000000000000));
1022 TestPair( 1.2345678901230000E-300, Int64($01AA74FE1C1E7E45));
1023 TestPair( 1.2345678901234500E-150, Int64($20D02A36586DB4BB));
1024 TestPair( 1.2345678901234565E+000, Int64($3FF3C0CA428C59FA));
1025 TestPair( 1.2345678901234567E+000, Int64($3FF3C0CA428C59FB));
1026 TestPair( 1.2345678901234569E+000, Int64($3FF3C0CA428C59FC));
1027 TestPair( 1.2345678901234569E+150, Int64($5F182344CD3CDF9F));
1028 TestPair( 1.2345678901234569E+300, Int64($7E3D7EE8BCBBD352));
1029 TestPair( -1.7976931348623157E+308, Int64($FFEFFFFFFFFFFFFF));
1030 TestPair( 1.7976931348623157E+308, Int64($7FEFFFFFFFFFFFFF));
1031 TestPair( 4.9406564584124654E-324, Int64($0000000000000001));
1032 TestPair( 0.0000000000000000E+000, Int64($0000000000000000));
1033 TestPair( 4.94065645841247E-324, Int64($0000000000000001));
1034 TestPair( 3.2378592100206092E-319, Int64($000000000000FFFF));
1035 TestPair( 1.3906711615669959E-309, Int64($0000FFFFFFFFFFFF));
1036 TestPair( NegInfinity, Int64($FFF0000000000000));
1037 TestPair( Infinity, Int64($7FF0000000000000));
1038
1039 // NaN is special
1040 ASSERT( TCompactProtocolImpl.DoubleToInt64Bits( NaN) = Int64($FFF8000000000000));
1041 ASSERT( IsNan( TCompactProtocolImpl.Int64BitsToDouble( Int64($FFF8000000000000))));
1042end;
1043{$ENDIF}
1044
1045
1046{$IFDEF Debug}
1047procedure TestZigZag;
1048
1049 procedure Test32( const test : Integer);
1050 var zz : Cardinal;
1051 begin
1052 zz := TCompactProtocolImpl.intToZigZag(test);
1053 ASSERT( TCompactProtocolImpl.zigzagToInt(zz) = test, IntToStr(test));
1054 end;
1055
1056 procedure Test64( const test : Int64);
1057 var zz : UInt64;
1058 begin
1059 zz := TCompactProtocolImpl.longToZigzag(test);
1060 ASSERT( TCompactProtocolImpl.zigzagToLong(zz) = test, IntToStr(test));
1061 end;
1062
1063var i : Integer;
1064begin
1065 // protobuf testcases
Jens Geyerd6834402015-03-07 13:16:34 +01001066 ASSERT( TCompactProtocolImpl.intToZigZag(0) = 0, 'pb #1 to ZigZag');
1067 ASSERT( TCompactProtocolImpl.intToZigZag(-1) = 1, 'pb #2 to ZigZag');
1068 ASSERT( TCompactProtocolImpl.intToZigZag(1) = 2, 'pb #3 to ZigZag');
1069 ASSERT( TCompactProtocolImpl.intToZigZag(-2) = 3, 'pb #4 to ZigZag');
1070 ASSERT( TCompactProtocolImpl.intToZigZag(+2147483647) = 4294967294, 'pb #5 to ZigZag');
1071 ASSERT( TCompactProtocolImpl.intToZigZag(-2147483648) = 4294967295, 'pb #6 to ZigZag');
1072
1073 // protobuf testcases
1074 ASSERT( TCompactProtocolImpl.zigzagToInt(0) = 0, 'pb #1 from ZigZag');
1075 ASSERT( TCompactProtocolImpl.zigzagToInt(1) = -1, 'pb #2 from ZigZag');
1076 ASSERT( TCompactProtocolImpl.zigzagToInt(2) = 1, 'pb #3 from ZigZag');
1077 ASSERT( TCompactProtocolImpl.zigzagToInt(3) = -2, 'pb #4 from ZigZag');
1078 ASSERT( TCompactProtocolImpl.zigzagToInt(4294967294) = +2147483647, 'pb #5 from ZigZag');
1079 ASSERT( TCompactProtocolImpl.zigzagToInt(4294967295) = -2147483648, 'pb #6 from ZigZag');
Jens Geyerf0e63312015-03-01 18:47:49 +01001080
1081 // back and forth 32
1082 Test32( 0);
1083 for i := 0 to 30 do begin
1084 Test32( +(Integer(1) shl i));
1085 Test32( -(Integer(1) shl i));
1086 end;
1087 Test32( Integer($7FFFFFFF));
1088 Test32( Integer($80000000));
1089
1090 // back and forth 64
1091 Test64( 0);
1092 for i := 0 to 62 do begin
1093 Test64( +(Int64(1) shl i));
1094 Test64( -(Int64(1) shl i));
1095 end;
1096 Test64( Int64($7FFFFFFFFFFFFFFF));
1097 Test64( Int64($8000000000000000));
1098end;
1099{$ENDIF}
1100
1101
Jens Geyera6ea4442015-03-02 23:06:57 +01001102{$IFDEF Debug}
1103procedure TestLongBytes;
1104
1105 procedure Test( const test : Int64);
1106 var buf : TBytes;
1107 begin
1108 TCompactProtocolImpl.fixedLongToBytes( test, buf);
1109 ASSERT( TCompactProtocolImpl.bytesToLong( buf) = test, IntToStr(test));
1110 end;
1111
1112var i : Integer;
1113begin
1114 Test( 0);
1115 for i := 0 to 62 do begin
1116 Test( +(Int64(1) shl i));
1117 Test( -(Int64(1) shl i));
1118 end;
1119 Test( Int64($7FFFFFFFFFFFFFFF));
1120 Test( Int64($8000000000000000));
1121end;
1122{$ENDIF}
1123
1124
Jens Geyera9235802018-09-25 00:21:12 +02001125{$IFDEF Debug}
1126procedure UnitTest;
1127var w : WORD;
1128const FPU_CW_DENORMALIZED = $0002;
1129begin
1130 w := Get8087CW;
1131 try
1132 Set8087CW( w or FPU_CW_DENORMALIZED);
1133
1134 TestDoubleToInt64Bits;
1135 TestZigZag;
1136 TestLongBytes;
1137
1138 finally
1139 Set8087CW( w);
1140 end;
1141end;
1142{$ENDIF}
1143
1144
Jens Geyerf0e63312015-03-01 18:47:49 +01001145initialization
1146 {$IFDEF Debug}
Jens Geyera9235802018-09-25 00:21:12 +02001147 UnitTest;
Jens Geyerf0e63312015-03-01 18:47:49 +01001148 {$ENDIF}
1149
1150end.
1151