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