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