blob: 665cfc4ba34535b902571606a6c97c0989aa5958 [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,
Jens Geyera019cda2019-11-09 23:24:52 +010031 Thrift.Configuration,
Jens Geyerf0e63312015-03-01 18:47:49 +010032 Thrift.Transport,
33 Thrift.Protocol,
34 Thrift.Utils;
35
36type
37 ICompactProtocol = interface( IProtocol)
38 ['{C01927EC-021A-45F7-93B1-23D6A5420EDD}']
39 end;
40
41 // Compact protocol implementation for thrift.
42 // Adapted from the C# version.
43 TCompactProtocolImpl = class( TProtocolImpl, ICompactProtocol)
44 public
45 type
46 TFactory = class( TInterfacedObject, IProtocolFactory)
47 public
48 function GetProtocol( const trans: ITransport): IProtocol;
49 end;
50
Jens Geyerfad7fd32019-11-09 23:24:52 +010051 strict private const
Jens Geyerf0e63312015-03-01 18:47:49 +010052
53 { TODO
54 static TStruct ANONYMOUS_STRUCT = new TStruct("");
55 static TField TSTOP = new TField("", TType.Stop, (short)0);
56 }
57
58 PROTOCOL_ID = Byte( $82);
59 VERSION = Byte( 1);
60 VERSION_MASK = Byte( $1F); // 0001 1111
61 TYPE_MASK = Byte( $E0); // 1110 0000
62 TYPE_BITS = Byte( $07); // 0000 0111
63 TYPE_SHIFT_AMOUNT = Byte( 5);
64
Jens Geyerfad7fd32019-11-09 23:24:52 +010065 strict private type
Jens Geyerf0e63312015-03-01 18:47:49 +010066 // All of the on-wire type codes.
67 Types = (
68 STOP = $00,
69 BOOLEAN_TRUE = $01,
70 BOOLEAN_FALSE = $02,
71 BYTE_ = $03,
72 I16 = $04,
73 I32 = $05,
74 I64 = $06,
75 DOUBLE_ = $07,
76 BINARY = $08,
77 LIST = $09,
78 SET_ = $0A,
79 MAP = $0B,
80 STRUCT = $0C
81 );
82
Jens Geyerfad7fd32019-11-09 23:24:52 +010083 strict private const
Jens Geyerf0e63312015-03-01 18:47:49 +010084 ttypeToCompactType : array[TType] of Types = (
85 Types.STOP, // Stop = 0,
86 Types(-1), // Void = 1,
87 Types.BOOLEAN_TRUE, // Bool_ = 2,
88 Types.BYTE_, // Byte_ = 3,
89 Types.DOUBLE_, // Double_ = 4,
90 Types(-5), // unused
91 Types.I16, // I16 = 6,
92 Types(-7), // unused
93 Types.I32, // I32 = 8,
94 Types(-9), // unused
95 Types.I64, // I64 = 10,
96 Types.BINARY, // String_ = 11,
97 Types.STRUCT, // Struct = 12,
98 Types.MAP, // Map = 13,
99 Types.SET_, // Set_ = 14,
100 Types.LIST // List = 15,
101 );
102
103 tcompactTypeToType : array[Types] of TType = (
104 TType.Stop, // STOP
105 TType.Bool_, // BOOLEAN_TRUE
106 TType.Bool_, // BOOLEAN_FALSE
107 TType.Byte_, // BYTE_
108 TType.I16, // I16
109 TType.I32, // I32
110 TType.I64, // I64
111 TType.Double_, // DOUBLE_
112 TType.String_, // BINARY
113 TType.List, // LIST
114 TType.Set_, // SET_
115 TType.Map, // MAP
116 TType.Struct // STRUCT
117 );
118
Jens Geyerfad7fd32019-11-09 23:24:52 +0100119 strict private
Jens Geyerf0e63312015-03-01 18:47:49 +0100120 // Used to keep track of the last field for the current and previous structs,
121 // so we can do the delta stuff.
122 lastField_ : TStack<Integer>;
123 lastFieldId_ : Integer;
124
125 // If we encounter a boolean field begin, save the TField here so it can
126 // have the value incorporated.
Jens Geyerfad7fd32019-11-09 23:24:52 +0100127 strict private booleanField_ : TThriftField;
Jens Geyerf0e63312015-03-01 18:47:49 +0100128
129 // If we Read a field header, and it's a boolean field, save the boolean
130 // value here so that ReadBool can use it.
Jens Geyerfad7fd32019-11-09 23:24:52 +0100131 strict private boolValue_ : ( unused, bool_true, bool_false);
Jens Geyerf0e63312015-03-01 18:47:49 +0100132
133 public
134 constructor Create(const trans : ITransport);
135 destructor Destroy; override;
136
Jens Geyerfad7fd32019-11-09 23:24:52 +0100137 strict private
Jens Geyerf0e63312015-03-01 18:47:49 +0100138 procedure WriteByteDirect( const b : Byte); overload;
139
140 // Writes a byte without any possibility of all that field header nonsense.
141 procedure WriteByteDirect( const n : Integer); overload;
142
143 // Write an i32 as a varint. Results in 1-5 bytes on the wire.
144 // TODO: make a permanent buffer like WriteVarint64?
145 procedure WriteVarint32( n : Cardinal);
146
Jens Geyerfad7fd32019-11-09 23:24:52 +0100147 strict private
Jens Geyerf0e63312015-03-01 18:47:49 +0100148 // The workhorse of WriteFieldBegin. It has the option of doing a 'type override'
149 // of the type header. This is used specifically in the boolean field case.
Jens Geyer17c3ad92017-09-05 20:31:27 +0200150 procedure WriteFieldBeginInternal( const field : TThriftField; typeOverride : Byte);
Jens Geyerf0e63312015-03-01 18:47:49 +0100151
152 public
Jens Geyer17c3ad92017-09-05 20:31:27 +0200153 procedure WriteMessageBegin( const msg: TThriftMessage); override;
Jens Geyerf0e63312015-03-01 18:47:49 +0100154 procedure WriteMessageEnd; override;
Jens Geyer17c3ad92017-09-05 20:31:27 +0200155 procedure WriteStructBegin( const struc: TThriftStruct); override;
Jens Geyerf0e63312015-03-01 18:47:49 +0100156 procedure WriteStructEnd; override;
Jens Geyer17c3ad92017-09-05 20:31:27 +0200157 procedure WriteFieldBegin( const field: TThriftField); override;
Jens Geyerf0e63312015-03-01 18:47:49 +0100158 procedure WriteFieldEnd; override;
159 procedure WriteFieldStop; override;
Jens Geyer17c3ad92017-09-05 20:31:27 +0200160 procedure WriteMapBegin( const map: TThriftMap); override;
Jens Geyerf0e63312015-03-01 18:47:49 +0100161 procedure WriteMapEnd; override;
Jens Geyer17c3ad92017-09-05 20:31:27 +0200162 procedure WriteListBegin( const list: TThriftList); override;
Jens Geyerf0e63312015-03-01 18:47:49 +0100163 procedure WriteListEnd(); override;
Jens Geyer17c3ad92017-09-05 20:31:27 +0200164 procedure WriteSetBegin( const set_: TThriftSet ); override;
Jens Geyerf0e63312015-03-01 18:47:49 +0100165 procedure WriteSetEnd(); override;
166 procedure WriteBool( b: Boolean); override;
167 procedure WriteByte( b: ShortInt); override;
168 procedure WriteI16( i16: SmallInt); override;
169 procedure WriteI32( i32: Integer); override;
170 procedure WriteI64( const i64: Int64); override;
171 procedure WriteDouble( const dub: Double); override;
172 procedure WriteBinary( const b: TBytes); overload; override;
173
Jens Geyer41f47af2019-11-09 23:24:52 +0100174 private // unit visible stuff
Jens Geyerf0e63312015-03-01 18:47:49 +0100175 class function DoubleToInt64Bits( const db : Double) : Int64;
176 class function Int64BitsToDouble( const i64 : Int64) : Double;
177
178 // Abstract method for writing the start of lists and sets. List and sets on
179 // the wire differ only by the type indicator.
180 procedure WriteCollectionBegin( const elemType : TType; size : Integer);
181
182 procedure WriteVarint64( n : UInt64);
183
184 // Convert l into a zigzag long. This allows negative numbers to be
185 // represented compactly as a varint.
186 class function longToZigzag( const n : Int64) : UInt64;
187
188 // Convert n into a zigzag int. This allows negative numbers to be
189 // represented compactly as a varint.
190 class function intToZigZag( const n : Integer) : Cardinal;
191
192 //Convert a Int64 into little-endian bytes in buf starting at off and going until off+7.
193 class procedure fixedLongToBytes( const n : Int64; var buf : TBytes);
194
Jens Geyer41f47af2019-11-09 23:24:52 +0100195 strict protected
196 function GetMinSerializedSize( const aType : TType) : Integer; override;
197 procedure Reset; override;
198
Jens Geyerf0e63312015-03-01 18:47:49 +0100199 public
Jens Geyer17c3ad92017-09-05 20:31:27 +0200200 function ReadMessageBegin: TThriftMessage; override;
Jens Geyerf0e63312015-03-01 18:47:49 +0100201 procedure ReadMessageEnd(); override;
Jens Geyer17c3ad92017-09-05 20:31:27 +0200202 function ReadStructBegin: TThriftStruct; override;
Jens Geyerf0e63312015-03-01 18:47:49 +0100203 procedure ReadStructEnd; override;
Jens Geyer17c3ad92017-09-05 20:31:27 +0200204 function ReadFieldBegin: TThriftField; override;
Jens Geyerf0e63312015-03-01 18:47:49 +0100205 procedure ReadFieldEnd(); override;
Jens Geyer17c3ad92017-09-05 20:31:27 +0200206 function ReadMapBegin: TThriftMap; override;
Jens Geyerf0e63312015-03-01 18:47:49 +0100207 procedure ReadMapEnd(); override;
Jens Geyer17c3ad92017-09-05 20:31:27 +0200208 function ReadListBegin: TThriftList; override;
Jens Geyerf0e63312015-03-01 18:47:49 +0100209 procedure ReadListEnd(); override;
Jens Geyer17c3ad92017-09-05 20:31:27 +0200210 function ReadSetBegin: TThriftSet; override;
Jens Geyerf0e63312015-03-01 18:47:49 +0100211 procedure ReadSetEnd(); override;
212 function ReadBool: Boolean; override;
213 function ReadByte: ShortInt; override;
214 function ReadI16: SmallInt; override;
215 function ReadI32: Integer; override;
216 function ReadI64: Int64; override;
217 function ReadDouble:Double; override;
218 function ReadBinary: TBytes; overload; override;
219
220 private
221 // Internal Reading methods
222
223 // Read an i32 from the wire as a varint. The MSB of each byte is set
224 // if there is another byte to follow. This can Read up to 5 bytes.
225 function ReadVarint32 : Cardinal;
226
227 // Read an i64 from the wire as a proper varint. The MSB of each byte is set
228 // if there is another byte to follow. This can Read up to 10 bytes.
229 function ReadVarint64 : UInt64;
230
231
232 // encoding helpers
233
234 // Convert from zigzag Integer to Integer.
235 class function zigzagToInt( const n : Cardinal ) : Integer;
236
237 // Convert from zigzag Int64 to Int64.
238 class function zigzagToLong( const n : UInt64) : Int64;
239
240 // Note that it's important that the mask bytes are Int64 literals,
241 // otherwise they'll default to ints, and when you shift an Integer left 56 bits,
242 // you just get a messed up Integer.
243 class function bytesToLong( const bytes : TBytes) : Int64;
244
245 // type testing and converting
246 class function isBoolType( const b : byte) : Boolean;
247
248 // Given a TCompactProtocol.Types constant, convert it to its corresponding TType value.
249 class function getTType( const type_ : byte) : TType;
250
251 // Given a TType value, find the appropriate TCompactProtocol.Types constant.
252 class function getCompactType( const ttype : TType) : Byte;
253 end;
254
255
256implementation
257
258
259
260//--- TCompactProtocolImpl.TFactory ----------------------------------------
261
262
263function TCompactProtocolImpl.TFactory.GetProtocol( const trans: ITransport): IProtocol;
264begin
265 result := TCompactProtocolImpl.Create( trans);
266end;
267
268
269//--- TCompactProtocolImpl -------------------------------------------------
270
271
Jens Geyera019cda2019-11-09 23:24:52 +0100272constructor TCompactProtocolImpl.Create( const trans : ITransport);
Jens Geyerf0e63312015-03-01 18:47:49 +0100273begin
274 inherited Create( trans);
275
276 lastFieldId_ := 0;
277 lastField_ := TStack<Integer>.Create;
278
Jens Geyer17c3ad92017-09-05 20:31:27 +0200279 Init( booleanField_, '', TType.Stop, 0);
Jens Geyerf0e63312015-03-01 18:47:49 +0100280 boolValue_ := unused;
281end;
282
283
284destructor TCompactProtocolImpl.Destroy;
285begin
286 try
287 FreeAndNil( lastField_);
288 finally
289 inherited Destroy;
290 end;
291end;
292
293
294
295procedure TCompactProtocolImpl.Reset;
296begin
Jens Geyer41f47af2019-11-09 23:24:52 +0100297 inherited Reset;
Jens Geyerf0e63312015-03-01 18:47:49 +0100298 lastField_.Clear();
299 lastFieldId_ := 0;
Jens Geyer17c3ad92017-09-05 20:31:27 +0200300 Init( booleanField_, '', TType.Stop, 0);
Jens Geyerf0e63312015-03-01 18:47:49 +0100301 boolValue_ := unused;
302end;
303
304
305// Writes a byte without any possibility of all that field header nonsense.
306// Used internally by other writing methods that know they need to Write a byte.
307procedure TCompactProtocolImpl.WriteByteDirect( const b : Byte);
Jens Geyerf0e63312015-03-01 18:47:49 +0100308begin
Jens Geyer17c3ad92017-09-05 20:31:27 +0200309 Transport.Write( @b, SizeOf(b));
Jens Geyerf0e63312015-03-01 18:47:49 +0100310end;
311
312
313// Writes a byte without any possibility of all that field header nonsense.
314procedure TCompactProtocolImpl.WriteByteDirect( const n : Integer);
315begin
316 WriteByteDirect( Byte(n));
317end;
318
319
320// Write an i32 as a varint. Results in 1-5 bytes on the wire.
321procedure TCompactProtocolImpl.WriteVarint32( n : Cardinal);
322var i32buf : TBytes;
323 idx : Integer;
324begin
325 SetLength( i32buf, 5);
326 idx := 0;
327 while TRUE do begin
328 ASSERT( idx < Length(i32buf));
329
330 // last part?
331 if ((n and not $7F) = 0) then begin
332 i32buf[idx] := Byte(n);
333 Inc(idx);
334 Break;
335 end;
336
337 i32buf[idx] := Byte((n and $7F) or $80);
338 Inc(idx);
339 n := n shr 7;
340 end;
341
342 Transport.Write( i32buf, 0, idx);
343end;
344
345
346// Write a message header to the wire. Compact Protocol messages contain the
347// protocol version so we can migrate forwards in the future if need be.
Jens Geyer17c3ad92017-09-05 20:31:27 +0200348procedure TCompactProtocolImpl.WriteMessageBegin( const msg: TThriftMessage);
Jens Geyerf0e63312015-03-01 18:47:49 +0100349var versionAndType : Byte;
350begin
351 Reset;
352
353 versionAndType := Byte( VERSION and VERSION_MASK)
354 or Byte( (Cardinal(msg.Type_) shl TYPE_SHIFT_AMOUNT) and TYPE_MASK);
355
356 WriteByteDirect( PROTOCOL_ID);
357 WriteByteDirect( versionAndType);
358 WriteVarint32( Cardinal(msg.SeqID));
359 WriteString( msg.Name);
360end;
361
362
363// Write a struct begin. This doesn't actually put anything on the wire. We use it as an
364// opportunity to put special placeholder markers on the field stack so we can get the
365// field id deltas correct.
Jens Geyer17c3ad92017-09-05 20:31:27 +0200366procedure TCompactProtocolImpl.WriteStructBegin( const struc: TThriftStruct);
Jens Geyerf0e63312015-03-01 18:47:49 +0100367begin
368 lastField_.Push(lastFieldId_);
369 lastFieldId_ := 0;
370end;
371
372
373// Write a struct end. This doesn't actually put anything on the wire. We use this as an
374// opportunity to pop the last field from the current struct off of the field stack.
375procedure TCompactProtocolImpl.WriteStructEnd;
376begin
377 lastFieldId_ := lastField_.Pop();
378end;
379
380
381// Write a field header containing the field id and field type. If the difference between the
382// current field id and the last one is small (< 15), then the field id will be encoded in
383// 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 +0200384procedure TCompactProtocolImpl.WriteFieldBegin( const field: TThriftField);
Jens Geyerf0e63312015-03-01 18:47:49 +0100385begin
386 case field.Type_ of
387 TType.Bool_ : booleanField_ := field; // we want to possibly include the value, so we'll wait.
388 else
389 WriteFieldBeginInternal(field, $FF);
390 end;
391end;
392
393
394// The workhorse of WriteFieldBegin. It has the option of doing a 'type override'
395// of the type header. This is used specifically in the boolean field case.
Jens Geyer17c3ad92017-09-05 20:31:27 +0200396procedure TCompactProtocolImpl.WriteFieldBeginInternal( const field : TThriftField; typeOverride : Byte);
Jens Geyerf0e63312015-03-01 18:47:49 +0100397var typeToWrite : Byte;
398begin
399 // if there's a type override, use that.
400 if typeOverride = $FF
401 then typeToWrite := getCompactType( field.Type_)
402 else typeToWrite := typeOverride;
403
404 // check if we can use delta encoding for the field id
405 if (field.ID > lastFieldId_) and ((field.ID - lastFieldId_) <= 15)
406 then begin
407 // Write them together
408 WriteByteDirect( ((field.ID - lastFieldId_) shl 4) or typeToWrite);
409 end
410 else begin
411 // Write them separate
412 WriteByteDirect( typeToWrite);
413 WriteI16( field.ID);
414 end;
415
416 lastFieldId_ := field.ID;
417end;
418
419
420// Write the STOP symbol so we know there are no more fields in this struct.
421procedure TCompactProtocolImpl.WriteFieldStop;
422begin
423 WriteByteDirect( Byte( Types.STOP));
424end;
425
426
427// Write a map header. If the map is empty, omit the key and value type
428// headers, as we don't need any additional information to skip it.
Jens Geyer17c3ad92017-09-05 20:31:27 +0200429procedure TCompactProtocolImpl.WriteMapBegin( const map: TThriftMap);
Jens Geyerf0e63312015-03-01 18:47:49 +0100430var key, val : Byte;
431begin
432 if (map.Count = 0)
433 then WriteByteDirect( 0)
434 else begin
435 WriteVarint32( Cardinal( map.Count));
436 key := getCompactType(map.KeyType);
437 val := getCompactType(map.ValueType);
438 WriteByteDirect( (key shl 4) or val);
439 end;
440end;
441
442
443// Write a list header.
Jens Geyer17c3ad92017-09-05 20:31:27 +0200444procedure TCompactProtocolImpl.WriteListBegin( const list: TThriftList);
Jens Geyerf0e63312015-03-01 18:47:49 +0100445begin
446 WriteCollectionBegin( list.ElementType, list.Count);
447end;
448
449
450// Write a set header.
Jens Geyer17c3ad92017-09-05 20:31:27 +0200451procedure TCompactProtocolImpl.WriteSetBegin( const set_: TThriftSet );
Jens Geyerf0e63312015-03-01 18:47:49 +0100452begin
453 WriteCollectionBegin( set_.ElementType, set_.Count);
454end;
455
456
457// Write a boolean value. Potentially, this could be a boolean field, in
458// which case the field header info isn't written yet. If so, decide what the
459// right type header is for the value and then Write the field header.
460// Otherwise, Write a single byte.
461procedure TCompactProtocolImpl.WriteBool( b: Boolean);
462var bt : Types;
463begin
464 if b
465 then bt := Types.BOOLEAN_TRUE
466 else bt := Types.BOOLEAN_FALSE;
467
Jens Geyer17c3ad92017-09-05 20:31:27 +0200468 if booleanField_.Type_ = TType.Bool_ then begin
Jens Geyerf0e63312015-03-01 18:47:49 +0100469 // we haven't written the field header yet
470 WriteFieldBeginInternal( booleanField_, Byte(bt));
Jens Geyer17c3ad92017-09-05 20:31:27 +0200471 booleanField_.Type_ := TType.Stop;
Jens Geyerf0e63312015-03-01 18:47:49 +0100472 end
473 else begin
474 // we're not part of a field, so just Write the value.
475 WriteByteDirect( Byte(bt));
476 end;
477end;
478
479
480// Write a byte. Nothing to see here!
481procedure TCompactProtocolImpl.WriteByte( b: ShortInt);
482begin
483 WriteByteDirect( Byte(b));
484end;
485
486
487// Write an I16 as a zigzag varint.
488procedure TCompactProtocolImpl.WriteI16( i16: SmallInt);
489begin
490 WriteVarint32( intToZigZag( i16));
491end;
492
493
494// Write an i32 as a zigzag varint.
495procedure TCompactProtocolImpl.WriteI32( i32: Integer);
496begin
497 WriteVarint32( intToZigZag( i32));
498end;
499
500
501// Write an i64 as a zigzag varint.
502procedure TCompactProtocolImpl.WriteI64( const i64: Int64);
503begin
504 WriteVarint64( longToZigzag( i64));
505end;
506
507
508class function TCompactProtocolImpl.DoubleToInt64Bits( const db : Double) : Int64;
509begin
510 ASSERT( SizeOf(db) = SizeOf(result));
511 Move( db, result, SizeOf(result));
512end;
513
514
515class function TCompactProtocolImpl.Int64BitsToDouble( const i64 : Int64) : Double;
516begin
517 ASSERT( SizeOf(i64) = SizeOf(result));
518 Move( i64, result, SizeOf(result));
519end;
520
521
522// Write a double to the wire as 8 bytes.
523procedure TCompactProtocolImpl.WriteDouble( const dub: Double);
524var data : TBytes;
525begin
Jens Geyerf0e63312015-03-01 18:47:49 +0100526 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
Jens Geyera6ea4442015-03-02 23:06:57 +0100632 SetLength( buf, 8);
Jens Geyerf0e63312015-03-01 18:47:49 +0100633 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.
Jens Geyer17c3ad92017-09-05 20:31:27 +0200646function TCompactProtocolImpl.ReadMessageBegin : TThriftMessage;
Jens Geyerf0e63312015-03-01 18:47:49 +0100647var protocolId, versionAndType, version, type_ : Byte;
648 seqid : Integer;
649 msgNm : String;
650begin
651 Reset;
652
653 protocolId := Byte( ReadByte);
654 if (protocolId <> PROTOCOL_ID)
Jens Geyere0e32402016-04-20 21:50:48 +0200655 then raise TProtocolExceptionBadVersion.Create( 'Expected protocol id ' + IntToHex(PROTOCOL_ID,2)
656 + ' but got ' + IntToHex(protocolId,2));
Jens Geyerf0e63312015-03-01 18:47:49 +0100657
658 versionAndType := Byte( ReadByte);
659 version := Byte( versionAndType and VERSION_MASK);
660 if (version <> VERSION)
Jens Geyere0e32402016-04-20 21:50:48 +0200661 then raise TProtocolExceptionBadVersion.Create( 'Expected version ' +IntToStr(VERSION)
662 + ' but got ' + IntToStr(version));
Jens Geyerf0e63312015-03-01 18:47:49 +0100663
664 type_ := Byte( (versionAndType shr TYPE_SHIFT_AMOUNT) and TYPE_BITS);
665 seqid := Integer( ReadVarint32);
666 msgNm := ReadString;
Jens Geyer17c3ad92017-09-05 20:31:27 +0200667 Init( result, msgNm, TMessageType(type_), seqid);
Jens Geyerf0e63312015-03-01 18:47:49 +0100668end;
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.
Jens Geyer17c3ad92017-09-05 20:31:27 +0200673function TCompactProtocolImpl.ReadStructBegin: TThriftStruct;
Jens Geyerf0e63312015-03-01 18:47:49 +0100674begin
675 lastField_.Push( lastFieldId_);
676 lastFieldId_ := 0;
Jens Geyer17c3ad92017-09-05 20:31:27 +0200677 Init( result);
Jens Geyerf0e63312015-03-01 18:47:49 +0100678end;
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.
Jens Geyer17c3ad92017-09-05 20:31:27 +0200691function TCompactProtocolImpl.ReadFieldBegin: TThriftField;
Jens Geyerf0e63312015-03-01 18:47:49 +0100692var type_ : Byte;
Jens Geyera715f702019-08-28 22:56:13 +0200693 modifier : ShortInt;
694 fieldId : SmallInt;
Jens Geyerf0e63312015-03-01 18:47:49 +0100695begin
696 type_ := Byte( ReadByte);
697
698 // if it's a stop, then we can return immediately, as the struct is over.
699 if type_ = Byte(Types.STOP) then begin
Jens Geyer17c3ad92017-09-05 20:31:27 +0200700 Init( result, '', TType.Stop, 0);
Jens Geyerf0e63312015-03-01 18:47:49 +0100701 Exit;
702 end;
703
704 // mask off the 4 MSB of the type header. it could contain a field id delta.
705 modifier := ShortInt( (type_ and $F0) shr 4);
706 if (modifier = 0)
707 then fieldId := ReadI16 // not a delta. look ahead for the zigzag varint field id.
Jens Geyera715f702019-08-28 22:56:13 +0200708 else fieldId := SmallInt( lastFieldId_ + modifier); // add the delta to the last Read field id.
Jens Geyerf0e63312015-03-01 18:47:49 +0100709
Jens Geyer17c3ad92017-09-05 20:31:27 +0200710 Init( result, '', getTType(Byte(type_ and $0F)), fieldId);
Jens Geyerf0e63312015-03-01 18:47:49 +0100711
712 // if this happens to be a boolean field, the value is encoded in the type
713 // save the boolean value in a special instance variable.
714 if isBoolType(type_) then begin
715 if Byte(type_ and $0F) = Byte(Types.BOOLEAN_TRUE)
716 then boolValue_ := bool_true
717 else boolValue_ := bool_false;
718 end;
719
720 // push the new field onto the field stack so we can keep the deltas going.
721 lastFieldId_ := result.ID;
722end;
723
724
725// Read a map header off the wire. If the size is zero, skip Reading the key
726// and value type. This means that 0-length maps will yield TMaps without the
727// "correct" types.
Jens Geyer17c3ad92017-09-05 20:31:27 +0200728function TCompactProtocolImpl.ReadMapBegin: TThriftMap;
Jens Geyerf0e63312015-03-01 18:47:49 +0100729var size : Integer;
730 keyAndValueType : Byte;
731 key, val : TType;
732begin
733 size := Integer( ReadVarint32);
734 if size = 0
735 then keyAndValueType := 0
736 else keyAndValueType := Byte( ReadByte);
737
738 key := getTType( Byte( keyAndValueType shr 4));
739 val := getTType( Byte( keyAndValueType and $F));
Jens Geyer17c3ad92017-09-05 20:31:27 +0200740 Init( result, key, val, size);
Jens Geyerf0e63312015-03-01 18:47:49 +0100741 ASSERT( (result.KeyType = key) and (result.ValueType = val));
Jens Geyer41f47af2019-11-09 23:24:52 +0100742 CheckReadBytesAvailable(result);
Jens Geyerf0e63312015-03-01 18:47:49 +0100743end;
744
745
746// Read a list header off the wire. If the list size is 0-14, the size will
747// be packed into the element type header. If it's a longer list, the 4 MSB
748// of the element type header will be $F, and a varint will follow with the
749// true size.
Jens Geyer17c3ad92017-09-05 20:31:27 +0200750function TCompactProtocolImpl.ReadListBegin: TThriftList;
Jens Geyerf0e63312015-03-01 18:47:49 +0100751var size_and_type : Byte;
752 size : Integer;
753 type_ : TType;
754begin
755 size_and_type := Byte( ReadByte);
756
757 size := (size_and_type shr 4) and $0F;
758 if (size = 15)
759 then size := Integer( ReadVarint32);
760
761 type_ := getTType( size_and_type);
Jens Geyer17c3ad92017-09-05 20:31:27 +0200762 Init( result, type_, size);
Jens Geyer41f47af2019-11-09 23:24:52 +0100763 CheckReadBytesAvailable(result);
Jens Geyerf0e63312015-03-01 18:47:49 +0100764end;
765
766
767// Read a set header off the wire. If the set size is 0-14, the size will
768// be packed into the element type header. If it's a longer set, the 4 MSB
769// of the element type header will be $F, and a varint will follow with the
770// true size.
Jens Geyer17c3ad92017-09-05 20:31:27 +0200771function TCompactProtocolImpl.ReadSetBegin: TThriftSet;
Jens Geyerf0e63312015-03-01 18:47:49 +0100772var size_and_type : Byte;
773 size : Integer;
774 type_ : TType;
775begin
776 size_and_type := Byte( ReadByte);
777
778 size := (size_and_type shr 4) and $0F;
779 if (size = 15)
780 then size := Integer( ReadVarint32);
781
782 type_ := getTType( size_and_type);
Jens Geyer17c3ad92017-09-05 20:31:27 +0200783 Init( result, type_, size);
Jens Geyer41f47af2019-11-09 23:24:52 +0100784 CheckReadBytesAvailable(result);
Jens Geyerf0e63312015-03-01 18:47:49 +0100785end;
786
787
788// Read a boolean off the wire. If this is a boolean field, the value should
789// already have been Read during ReadFieldBegin, so we'll just consume the
790// pre-stored value. Otherwise, Read a byte.
791function TCompactProtocolImpl.ReadBool: Boolean;
792begin
793 if boolValue_ <> unused then begin
794 result := (boolValue_ = bool_true);
795 boolValue_ := unused;
796 Exit;
797 end;
798
799 result := (Byte(ReadByte) = Byte(Types.BOOLEAN_TRUE));
800end;
801
802
803// Read a single byte off the wire. Nothing interesting here.
804function TCompactProtocolImpl.ReadByte: ShortInt;
Jens Geyerf0e63312015-03-01 18:47:49 +0100805begin
Jens Geyer17c3ad92017-09-05 20:31:27 +0200806 Transport.ReadAll( @result, SizeOf(result), 0, 1);
Jens Geyerf0e63312015-03-01 18:47:49 +0100807end;
808
809
810// Read an i16 from the wire as a zigzag varint.
811function TCompactProtocolImpl.ReadI16: SmallInt;
812begin
813 result := SmallInt( zigzagToInt( ReadVarint32));
814end;
815
816
817// Read an i32 from the wire as a zigzag varint.
818function TCompactProtocolImpl.ReadI32: Integer;
819begin
820 result := zigzagToInt( ReadVarint32);
821end;
822
823
824// Read an i64 from the wire as a zigzag varint.
825function TCompactProtocolImpl.ReadI64: Int64;
826begin
827 result := zigzagToLong( ReadVarint64);
828end;
829
830
831// No magic here - just Read a double off the wire.
832function TCompactProtocolImpl.ReadDouble:Double;
833var longBits : TBytes;
834begin
835 SetLength( longBits, 8);
836 Transport.ReadAll( longBits, 0, 8);
837 result := Int64BitsToDouble( bytesToLong( longBits));
838end;
839
840
841// Read a byte[] from the wire.
842function TCompactProtocolImpl.ReadBinary: TBytes;
843var length : Integer;
844begin
845 length := Integer( ReadVarint32);
Jens Geyer41f47af2019-11-09 23:24:52 +0100846 FTrans.CheckReadBytesAvailable(length);
Jens Geyerf0e63312015-03-01 18:47:49 +0100847 SetLength( result, length);
848 if (length > 0)
849 then Transport.ReadAll( result, 0, length);
850end;
851
852
853procedure TCompactProtocolImpl.ReadMessageEnd;
854begin
855 // nothing to do
856end;
857
858
859procedure TCompactProtocolImpl.ReadFieldEnd;
860begin
861 // nothing to do
862end;
863
864
865procedure TCompactProtocolImpl.ReadMapEnd;
866begin
867 // nothing to do
868end;
869
870
871procedure TCompactProtocolImpl.ReadListEnd;
872begin
873 // nothing to do
874end;
875
876
877procedure TCompactProtocolImpl.ReadSetEnd;
878begin
879 // nothing to do
880end;
881
882
883
884// Read an i32 from the wire as a varint. The MSB of each byte is set
885// if there is another byte to follow. This can Read up to 5 bytes.
886function TCompactProtocolImpl.ReadVarint32 : Cardinal;
887var shift : Integer;
888 b : Byte;
889begin
890 result := 0;
891 shift := 0;
892 while TRUE do begin
893 b := Byte( ReadByte);
894 result := result or (Cardinal(b and $7F) shl shift);
895 if ((b and $80) <> $80)
896 then Break;
897 Inc( shift, 7);
898 end;
899end;
900
901
902// Read an i64 from the wire as a proper varint. The MSB of each byte is set
903// if there is another byte to follow. This can Read up to 10 bytes.
904function TCompactProtocolImpl.ReadVarint64 : UInt64;
905var shift : Integer;
906 b : Byte;
907begin
908 result := 0;
909 shift := 0;
910 while TRUE do begin
911 b := Byte( ReadByte);
912 result := result or (UInt64(b and $7F) shl shift);
913 if ((b and $80) <> $80)
914 then Break;
915 Inc( shift, 7);
916 end;
917end;
918
919
920// Convert from zigzag Integer to Integer.
921class function TCompactProtocolImpl.zigzagToInt( const n : Cardinal ) : Integer;
922begin
923 result := Integer(n shr 1) xor (-Integer(n and 1));
924end;
925
926
927// Convert from zigzag Int64 to Int64.
928class function TCompactProtocolImpl.zigzagToLong( const n : UInt64) : Int64;
929begin
930 result := Int64(n shr 1) xor (-Int64(n and 1));
931end;
932
933
934// Note that it's important that the mask bytes are Int64 literals,
935// otherwise they'll default to ints, and when you shift an Integer left 56 bits,
936// you just get a messed up Integer.
937class function TCompactProtocolImpl.bytesToLong( const bytes : TBytes) : Int64;
938begin
939 ASSERT( Length(bytes) >= 8);
940 result := (Int64(bytes[7] and $FF) shl 56) or
941 (Int64(bytes[6] and $FF) shl 48) or
942 (Int64(bytes[5] and $FF) shl 40) or
943 (Int64(bytes[4] and $FF) shl 32) or
944 (Int64(bytes[3] and $FF) shl 24) or
945 (Int64(bytes[2] and $FF) shl 16) or
946 (Int64(bytes[1] and $FF) shl 8) or
947 (Int64(bytes[0] and $FF));
948end;
949
950
951class function TCompactProtocolImpl.isBoolType( const b : byte) : Boolean;
952var lowerNibble : Byte;
953begin
954 lowerNibble := b and $0f;
955 result := (Types(lowerNibble) in [Types.BOOLEAN_TRUE, Types.BOOLEAN_FALSE]);
956end;
957
958
959// Given a TCompactProtocol.Types constant, convert it to its corresponding TType value.
960class function TCompactProtocolImpl.getTType( const type_ : byte) : TType;
961var tct : Types;
962begin
963 tct := Types( type_ and $0F);
964 if tct in [Low(Types)..High(Types)]
965 then result := tcompactTypeToType[tct]
Jens Geyere0e32402016-04-20 21:50:48 +0200966 else raise TProtocolExceptionInvalidData.Create('don''t know what type: '+IntToStr(Ord(tct)));
Jens Geyerf0e63312015-03-01 18:47:49 +0100967end;
968
969
970// Given a TType value, find the appropriate TCompactProtocol.Types constant.
971class function TCompactProtocolImpl.getCompactType( const ttype : TType) : Byte;
972begin
973 if ttype in VALID_TTYPES
974 then result := Byte( ttypeToCompactType[ttype])
Jens Geyere0e32402016-04-20 21:50:48 +0200975 else raise TProtocolExceptionInvalidData.Create('don''t know what type: '+IntToStr(Ord(ttype)));
Jens Geyerf0e63312015-03-01 18:47:49 +0100976end;
977
978
Jens Geyer41f47af2019-11-09 23:24:52 +0100979function TCompactProtocolImpl.GetMinSerializedSize( const aType : TType) : Integer;
980// Return the minimum number of bytes a type will consume on the wire
981begin
982 case aType of
983 TType.Stop: result := 0;
984 TType.Void: result := 0;
985 TType.Bool_: result := SizeOf(Byte);
986 TType.Byte_: result := SizeOf(Byte);
987 TType.Double_: result := 8; // uses fixedLongToBytes() which always writes 8 bytes
988 TType.I16: result := SizeOf(Byte);
989 TType.I32: result := SizeOf(Byte);
990 TType.I64: result := SizeOf(Byte);
991 TType.String_: result := SizeOf(Byte); // string length
992 TType.Struct: result := 0; // empty struct
993 TType.Map: result := SizeOf(Byte); // element count
994 TType.Set_: result := SizeOf(Byte); // element count
995 TType.List: result := SizeOf(Byte); // element count
996 else
997 raise TTransportExceptionBadArgs.Create('Unhandled type code');
998 end;
999end;
1000
1001
1002
1003
1004
Jens Geyerf0e63312015-03-01 18:47:49 +01001005//--- unit tests -------------------------------------------
1006
1007{$IFDEF Debug}
1008procedure TestDoubleToInt64Bits;
1009
1010 procedure TestPair( const a : Double; const b : Int64);
1011 begin
1012 ASSERT( TCompactProtocolImpl.DoubleToInt64Bits(a) = b);
1013 ASSERT( TCompactProtocolImpl.Int64BitsToDouble(b) = a);
1014 end;
1015
1016begin
1017 TestPair( 1.0000000000000000E+000, Int64($3FF0000000000000));
1018 TestPair( 1.5000000000000000E+001, Int64($402E000000000000));
1019 TestPair( 2.5500000000000000E+002, Int64($406FE00000000000));
1020 TestPair( 4.2949672950000000E+009, Int64($41EFFFFFFFE00000));
1021 TestPair( 3.9062500000000000E-003, Int64($3F70000000000000));
1022 TestPair( 2.3283064365386963E-010, Int64($3DF0000000000000));
1023 TestPair( 1.2345678901230000E-300, Int64($01AA74FE1C1E7E45));
1024 TestPair( 1.2345678901234500E-150, Int64($20D02A36586DB4BB));
1025 TestPair( 1.2345678901234565E+000, Int64($3FF3C0CA428C59FA));
1026 TestPair( 1.2345678901234567E+000, Int64($3FF3C0CA428C59FB));
1027 TestPair( 1.2345678901234569E+000, Int64($3FF3C0CA428C59FC));
1028 TestPair( 1.2345678901234569E+150, Int64($5F182344CD3CDF9F));
1029 TestPair( 1.2345678901234569E+300, Int64($7E3D7EE8BCBBD352));
1030 TestPair( -1.7976931348623157E+308, Int64($FFEFFFFFFFFFFFFF));
1031 TestPair( 1.7976931348623157E+308, Int64($7FEFFFFFFFFFFFFF));
1032 TestPair( 4.9406564584124654E-324, Int64($0000000000000001));
1033 TestPair( 0.0000000000000000E+000, Int64($0000000000000000));
1034 TestPair( 4.94065645841247E-324, Int64($0000000000000001));
1035 TestPair( 3.2378592100206092E-319, Int64($000000000000FFFF));
1036 TestPair( 1.3906711615669959E-309, Int64($0000FFFFFFFFFFFF));
1037 TestPair( NegInfinity, Int64($FFF0000000000000));
1038 TestPair( Infinity, Int64($7FF0000000000000));
1039
1040 // NaN is special
1041 ASSERT( TCompactProtocolImpl.DoubleToInt64Bits( NaN) = Int64($FFF8000000000000));
1042 ASSERT( IsNan( TCompactProtocolImpl.Int64BitsToDouble( Int64($FFF8000000000000))));
1043end;
1044{$ENDIF}
1045
1046
1047{$IFDEF Debug}
1048procedure TestZigZag;
1049
1050 procedure Test32( const test : Integer);
1051 var zz : Cardinal;
1052 begin
1053 zz := TCompactProtocolImpl.intToZigZag(test);
1054 ASSERT( TCompactProtocolImpl.zigzagToInt(zz) = test, IntToStr(test));
1055 end;
1056
1057 procedure Test64( const test : Int64);
1058 var zz : UInt64;
1059 begin
1060 zz := TCompactProtocolImpl.longToZigzag(test);
1061 ASSERT( TCompactProtocolImpl.zigzagToLong(zz) = test, IntToStr(test));
1062 end;
1063
1064var i : Integer;
1065begin
1066 // protobuf testcases
Jens Geyerd6834402015-03-07 13:16:34 +01001067 ASSERT( TCompactProtocolImpl.intToZigZag(0) = 0, 'pb #1 to ZigZag');
1068 ASSERT( TCompactProtocolImpl.intToZigZag(-1) = 1, 'pb #2 to ZigZag');
1069 ASSERT( TCompactProtocolImpl.intToZigZag(1) = 2, 'pb #3 to ZigZag');
1070 ASSERT( TCompactProtocolImpl.intToZigZag(-2) = 3, 'pb #4 to ZigZag');
1071 ASSERT( TCompactProtocolImpl.intToZigZag(+2147483647) = 4294967294, 'pb #5 to ZigZag');
1072 ASSERT( TCompactProtocolImpl.intToZigZag(-2147483648) = 4294967295, 'pb #6 to ZigZag');
1073
1074 // protobuf testcases
1075 ASSERT( TCompactProtocolImpl.zigzagToInt(0) = 0, 'pb #1 from ZigZag');
1076 ASSERT( TCompactProtocolImpl.zigzagToInt(1) = -1, 'pb #2 from ZigZag');
1077 ASSERT( TCompactProtocolImpl.zigzagToInt(2) = 1, 'pb #3 from ZigZag');
1078 ASSERT( TCompactProtocolImpl.zigzagToInt(3) = -2, 'pb #4 from ZigZag');
1079 ASSERT( TCompactProtocolImpl.zigzagToInt(4294967294) = +2147483647, 'pb #5 from ZigZag');
1080 ASSERT( TCompactProtocolImpl.zigzagToInt(4294967295) = -2147483648, 'pb #6 from ZigZag');
Jens Geyerf0e63312015-03-01 18:47:49 +01001081
1082 // back and forth 32
1083 Test32( 0);
1084 for i := 0 to 30 do begin
1085 Test32( +(Integer(1) shl i));
1086 Test32( -(Integer(1) shl i));
1087 end;
1088 Test32( Integer($7FFFFFFF));
1089 Test32( Integer($80000000));
1090
1091 // back and forth 64
1092 Test64( 0);
1093 for i := 0 to 62 do begin
1094 Test64( +(Int64(1) shl i));
1095 Test64( -(Int64(1) shl i));
1096 end;
1097 Test64( Int64($7FFFFFFFFFFFFFFF));
1098 Test64( Int64($8000000000000000));
1099end;
1100{$ENDIF}
1101
1102
Jens Geyera6ea4442015-03-02 23:06:57 +01001103{$IFDEF Debug}
1104procedure TestLongBytes;
1105
1106 procedure Test( const test : Int64);
1107 var buf : TBytes;
1108 begin
1109 TCompactProtocolImpl.fixedLongToBytes( test, buf);
1110 ASSERT( TCompactProtocolImpl.bytesToLong( buf) = test, IntToStr(test));
1111 end;
1112
1113var i : Integer;
1114begin
1115 Test( 0);
1116 for i := 0 to 62 do begin
1117 Test( +(Int64(1) shl i));
1118 Test( -(Int64(1) shl i));
1119 end;
1120 Test( Int64($7FFFFFFFFFFFFFFF));
1121 Test( Int64($8000000000000000));
1122end;
1123{$ENDIF}
1124
1125
Jens Geyera9235802018-09-25 00:21:12 +02001126{$IFDEF Debug}
1127procedure UnitTest;
1128var w : WORD;
1129const FPU_CW_DENORMALIZED = $0002;
1130begin
1131 w := Get8087CW;
1132 try
1133 Set8087CW( w or FPU_CW_DENORMALIZED);
1134
1135 TestDoubleToInt64Bits;
1136 TestZigZag;
1137 TestLongBytes;
1138
1139 finally
1140 Set8087CW( w);
1141 end;
1142end;
1143{$ENDIF}
1144
1145
Jens Geyerf0e63312015-03-01 18:47:49 +01001146initialization
1147 {$IFDEF Debug}
Jens Geyera9235802018-09-25 00:21:12 +02001148 UnitTest;
Jens Geyerf0e63312015-03-01 18:47:49 +01001149 {$ENDIF}
1150
1151end.
1152