blob: fff6b86c0de98801c3a23390744147e43d8d86af [file] [log] [blame]
Jens Geyerd5436f52014-10-03 19:50:38 +02001(*
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
20unit Thrift.Utils;
21
22interface
23
Jens Geyer9f7f11e2016-04-14 21:37:11 +020024{$I Thrift.Defines.inc}
Nick4f5229e2016-04-14 16:43:22 +030025
Jens Geyerd5436f52014-10-03 19:50:38 +020026uses
Jens Geyer9f7f11e2016-04-14 21:37:11 +020027 {$IFDEF OLD_UNIT_NAMES}
Jens Geyer8f7487e2019-05-09 22:21:32 +020028 Classes, Windows, SysUtils, Character, SyncObjs, TypInfo, Rtti;
Jens Geyer9f7f11e2016-04-14 21:37:11 +020029 {$ELSE}
Jens Geyer8f7487e2019-05-09 22:21:32 +020030 System.Classes, Winapi.Windows, System.SysUtils, System.Character,
31 System.SyncObjs, System.TypInfo, System.Rtti;
Jens Geyer9f7f11e2016-04-14 21:37:11 +020032 {$ENDIF}
Jens Geyerd5436f52014-10-03 19:50:38 +020033
34type
Jens Geyer8f7487e2019-05-09 22:21:32 +020035 ISupportsToString = interface
36 ['{AF71C350-E0CD-4E94-B77C-0310DC8227FF}']
37 function ToString : string;
38 end;
39
40
Jens Geyerd5436f52014-10-03 19:50:38 +020041 IOverlappedHelper = interface
42 ['{A1832EFA-2E02-4884-8F09-F0A0277157FA}']
43 function Overlapped : TOverlapped;
44 function OverlappedPtr : POverlapped;
45 function WaitHandle : THandle;
46 function WaitFor(dwTimeout: DWORD) : DWORD;
47 end;
48
49 TOverlappedHelperImpl = class( TInterfacedObject, IOverlappedHelper)
50 strict protected
51 FOverlapped : TOverlapped;
52 FEvent : TEvent;
53
54 // IOverlappedHelper
55 function Overlapped : TOverlapped;
56 function OverlappedPtr : POverlapped;
57 function WaitHandle : THandle;
58 function WaitFor(dwTimeout: DWORD) : DWORD;
59 public
60 constructor Create;
61 destructor Destroy; override;
62 end;
63
64
Jens Geyer8f7487e2019-05-09 22:21:32 +020065 TThriftStringBuilder = class( TStringBuilder)
66 public
67 function Append(const Value: TBytes): TStringBuilder; overload;
68 function Append(const Value: ISupportsToString): TStringBuilder; overload;
69 end;
70
71
Jens Geyerd8bddbc2014-12-14 00:41:33 +010072 Base64Utils = class sealed
73 public
74 class function Encode( const src : TBytes; srcOff, len : Integer; dst : TBytes; dstOff : Integer) : Integer; static;
75 class function Decode( const src : TBytes; srcOff, len : Integer; dst : TBytes; dstOff : Integer) : Integer; static;
76 end;
77
78
Jens Geyer71070432016-01-29 10:08:39 +010079 CharUtils = class sealed
80 public
81 class function IsHighSurrogate( const c : Char) : Boolean; static; inline;
82 class function IsLowSurrogate( const c : Char) : Boolean; static; inline;
Jens Geyer589ee5b2021-03-29 21:40:55 +020083
84 class function IsHtmlDoctype( const fourBytes : Integer) : Boolean; static;
Jens Geyer71070432016-01-29 10:08:39 +010085 end;
86
Jens Geyer62445c12022-06-29 00:00:00 +020087
88 IntegerUtils = class sealed
89 strict private
90 class procedure SwapBytes( var one, two : Byte); static; inline;
91 class procedure Swap2( const pValue : Pointer); static;
92 class procedure Swap4( const pValue : Pointer); static;
93 class procedure Swap8( const pValue : Pointer); static;
94 public
95 class procedure SwapByteOrder( const pValue : Pointer; const size : Integer); overload; static;
96 end;
97
98
Jens Geyerf8f62782022-09-10 00:55:02 +020099 // problem: inheritance possible for class helpers ONLY but not with record helpers
100 // workaround: use static class method instead of record helper :-(
101 GuidUtils = class sealed
Jens Geyer62445c12022-06-29 00:00:00 +0200102 public
Jens Geyerf8f62782022-09-10 00:55:02 +0200103 // new stuff
104 class function SwapByteOrder( const aGuid : TGuid) : TGuid; static;
Jens Geyer62445c12022-06-29 00:00:00 +0200105
106 {$IFDEF Debug}
107 class procedure SelfTest; static;
108 {$ENDIF}
109 end;
110
111
Jens Geyer8f7487e2019-05-09 22:21:32 +0200112 EnumUtils<T> = class sealed
113 public
114 class function ToString(const value : Integer) : string; reintroduce; static; inline;
115 end;
116
Jens Geyer62445c12022-06-29 00:00:00 +0200117
Jens Geyer8f7487e2019-05-09 22:21:32 +0200118 StringUtils<T> = class sealed
119 public
120 class function ToString(const value : T) : string; reintroduce; static; inline;
121 end;
122
Jens Geyer71070432016-01-29 10:08:39 +0100123
Jens Geyer83ff7532019-06-06 22:46:03 +0200124const
125 THRIFT_MIMETYPE = 'application/x-thrift';
126
Jens Geyerf7904452017-07-26 15:02:12 +0200127{$IFDEF Win64}
Jens Geyer6e2a6982019-12-12 23:07:49 +0100128function InterlockedExchangeAdd64( var Addend : Int64; Value : Int64) : Int64;
Jens Geyerf7904452017-07-26 15:02:12 +0200129{$ENDIF}
Jens Geyer9f7f11e2016-04-14 21:37:11 +0200130
Jens Geyer71070432016-01-29 10:08:39 +0100131
Jens Geyerd5436f52014-10-03 19:50:38 +0200132implementation
133
Jens Geyerd5436f52014-10-03 19:50:38 +0200134{ TOverlappedHelperImpl }
135
136constructor TOverlappedHelperImpl.Create;
137begin
138 inherited Create;
139 FillChar( FOverlapped, SizeOf(FOverlapped), 0);
140 FEvent := TEvent.Create( nil, TRUE, FALSE, ''); // always ManualReset, see MSDN
141 FOverlapped.hEvent := FEvent.Handle;
142end;
143
144
145
146destructor TOverlappedHelperImpl.Destroy;
147begin
148 try
149 FOverlapped.hEvent := 0;
150 FreeAndNil( FEvent);
151
152 finally
153 inherited Destroy;
154 end;
155
156end;
157
158
159function TOverlappedHelperImpl.Overlapped : TOverlapped;
160begin
161 result := FOverlapped;
162end;
163
164
165function TOverlappedHelperImpl.OverlappedPtr : POverlapped;
166begin
167 result := @FOverlapped;
168end;
169
170
171function TOverlappedHelperImpl.WaitHandle : THandle;
172begin
173 result := FOverlapped.hEvent;
174end;
175
176
177function TOverlappedHelperImpl.WaitFor( dwTimeout : DWORD) : DWORD;
178begin
179 result := WaitForSingleObject( FOverlapped.hEvent, dwTimeout);
180end;
181
182
Jens Geyerd8bddbc2014-12-14 00:41:33 +0100183{ Base64Utils }
184
185class function Base64Utils.Encode( const src : TBytes; srcOff, len : Integer; dst : TBytes; dstOff : Integer) : Integer;
186const ENCODE_TABLE : PAnsiChar = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/';
187begin
188 ASSERT( len in [1..3]);
189 dst[dstOff] := Byte( ENCODE_TABLE[ (src[srcOff] shr 2) and $3F]);
190 case len of
191 3 : begin
192 Inc(dstOff);
193 dst[dstOff] := Byte( ENCODE_TABLE[ ((src[srcOff] shl 4) and $30) or ((src[srcOff + 1] shr 4) and $0F)]);
194 Inc(dstOff);
195 dst[dstOff] := Byte( ENCODE_TABLE[ ((src[srcOff + 1] shl 2) and $3C) or ((src[srcOff + 2] shr 6) and $03)]);
196 Inc(dstOff);
197 dst[dstOff] := Byte( ENCODE_TABLE[ src[srcOff + 2] and $3F]);
198 result := 4;
199 end;
200
201 2 : begin
202 Inc(dstOff);
203 dst[dstOff] := Byte( ENCODE_TABLE[ ((src[srcOff] shl 4) and $30) or ((src[srcOff + 1] shr 4) and $0F)]);
204 Inc(dstOff);
205 dst[dstOff] := Byte( ENCODE_TABLE[ (src[srcOff + 1] shl 2) and $3C]);
206 result := 3;
207 end;
208
209 1 : begin
210 Inc(dstOff);
211 dst[dstOff] := Byte( ENCODE_TABLE[ (src[srcOff] shl 4) and $30]);
212 result := 2;
213 end;
214
215 else
216 ASSERT( FALSE);
Jens Geyer9f9535c2014-12-14 04:16:05 +0100217 result := 0; // because invalid call
Jens Geyerd8bddbc2014-12-14 00:41:33 +0100218 end;
219end;
220
221
222class function Base64Utils.Decode( const src : TBytes; srcOff, len : Integer; dst : TBytes; dstOff : Integer) : Integer;
223const DECODE_TABLE : array[0..$FF] of Integer
224 = ( -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,
225 -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,
226 -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,62,-1,-1,-1,63,
227 52,53,54,55,56,57,58,59,60,61,-1,-1,-1,-1,-1,-1,
228 -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,10,11,12,13,14,
229 15,16,17,18,19,20,21,22,23,24,25,-1,-1,-1,-1,-1,
230 -1,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,
231 41,42,43,44,45,46,47,48,49,50,51,-1,-1,-1,-1,-1,
232 -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,
233 -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,
234 -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,
235 -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,
236 -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,
237 -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,
238 -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,
239 -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1 );
240begin
241 ASSERT( len in [1..4]);
242 result := 1;
243 dst[dstOff] := ((DECODE_TABLE[src[srcOff] and $0FF] shl 2)
244 or (DECODE_TABLE[src[srcOff + 1] and $0FF] shr 4));
245
246 if (len > 2) then begin
247 Inc( result);
248 Inc( dstOff);
249 dst[dstOff] := (((DECODE_TABLE[src[srcOff + 1] and $0FF] shl 4) and $F0)
250 or (DECODE_TABLE[src[srcOff + 2] and $0FF] shr 2));
251
252 if (len > 3) then begin
253 Inc( result);
254 Inc( dstOff);
255 dst[dstOff] := (((DECODE_TABLE[src[srcOff + 2] and $0FF] shl 6) and $C0)
256 or DECODE_TABLE[src[srcOff + 3] and $0FF]);
257 end;
258 end;
259end;
Jens Geyerd5436f52014-10-03 19:50:38 +0200260
261
Jens Geyer71070432016-01-29 10:08:39 +0100262class function CharUtils.IsHighSurrogate( const c : Char) : Boolean;
263begin
Jens Geyer36c0b342018-01-19 19:17:33 +0100264 {$IF CompilerVersion < 25.0}
265 {$IFDEF OLD_UNIT_NAMES}
266 result := Character.IsHighSurrogate(c);
267 {$ELSE}
268 result := System.Character.IsHighSurrogate(c);
269 {$ENDIF}
Jens Geyer71070432016-01-29 10:08:39 +0100270 {$ELSE}
Jens Geyer9f7f11e2016-04-14 21:37:11 +0200271 result := c.IsHighSurrogate();
Jens Geyer71070432016-01-29 10:08:39 +0100272 {$IFEND}
273end;
274
275
276class function CharUtils.IsLowSurrogate( const c : Char) : Boolean;
277begin
Jens Geyer36c0b342018-01-19 19:17:33 +0100278 {$IF CompilerVersion < 25.0}
279 {$IFDEF OLD_UNIT_NAMES}
280 result := Character.IsLowSurrogate(c);
281 {$ELSE}
282 result := System.Character.IsLowSurrogate(c);
283 {$ENDIF}
Jens Geyer71070432016-01-29 10:08:39 +0100284 {$ELSE}
Jens Geyer36c0b342018-01-19 19:17:33 +0100285 result := c.IsLowSurrogate();
Jens Geyer71070432016-01-29 10:08:39 +0100286 {$IFEND}
287end;
288
289
Jens Geyer589ee5b2021-03-29 21:40:55 +0200290class function CharUtils.IsHtmlDoctype( const fourBytes : Integer) : Boolean;
291var pc : PAnsiChar;
292const HTML_BEGIN : PAnsiChar = 'OD!<'; // first 4 bytes of '<!DOCTYPE ' in LE byte order
293begin
294 pc := @fourBytes;
295
296 if UpCase(pc^) <> HTML_BEGIN[0]
297 then Exit(FALSE);
298
299 Inc( pc);
300 if UpCase(pc^) <> HTML_BEGIN[1]
301 then Exit(FALSE);
302
303
304 Inc( pc);
305 if UpCase(pc^) <> HTML_BEGIN[2]
306 then Exit(FALSE);
307
308 Inc( pc);
309 result := (UpCase(pc^) = HTML_BEGIN[3]);
310end;
311
Jens Geyer62445c12022-06-29 00:00:00 +0200312{ IntegerUtils }
313
314
315class procedure IntegerUtils.SwapBytes( var one, two : Byte);
316var tmp : Byte;
317begin
318 tmp := one;
319 one := two;
320 two := tmp;
321end;
322
323
324class procedure IntegerUtils.Swap2( const pValue : Pointer);
325var pData : PByteArray absolute pValue;
326begin
327 SwapBytes( pData^[0], pData^[1]);
328end;
329
330
331class procedure IntegerUtils.Swap4( const pValue : Pointer);
332var pData : PByteArray absolute pValue;
333begin
334 SwapBytes( pData^[0], pData^[3]);
335 SwapBytes( pData^[1], pData^[2]);
336end;
337
338
339class procedure IntegerUtils.Swap8( const pValue : Pointer);
340var pData : PByteArray absolute pValue;
341begin
342 SwapBytes( pData^[0], pData^[7]);
343 SwapBytes( pData^[1], pData^[6]);
344 SwapBytes( pData^[2], pData^[5]);
345 SwapBytes( pData^[3], pData^[4]);
346end;
347
348
349class procedure IntegerUtils.SwapByteOrder( const pValue : Pointer; const size : Integer);
350begin
351 case size of
352 2 : Swap2( pValue);
353 4 : Swap4( pValue);
354 8 : Swap8( pValue);
355 else
356 raise EArgumentException.Create('Unexpected size');
357 end;
358end;
359
360
Jens Geyerf8f62782022-09-10 00:55:02 +0200361{ GuidUtils }
Jens Geyer62445c12022-06-29 00:00:00 +0200362
363
Jens Geyerf8f62782022-09-10 00:55:02 +0200364class function GuidUtils.SwapByteOrder( const aGuid : TGuid) : TGuid;
Jens Geyer62445c12022-06-29 00:00:00 +0200365// convert to/from network byte order
366// - https://www.ietf.org/rfc/rfc4122.txt
367// - https://stackoverflow.com/questions/10850075/guid-uuid-compatibility-issue-between-net-and-linux
368// - https://lists.gnu.org/archive/html/bug-parted/2002-01/msg00099.html
369begin
Jens Geyerf8f62782022-09-10 00:55:02 +0200370 result := aGuid;
Jens Geyer62445c12022-06-29 00:00:00 +0200371
372 IntegerUtils.SwapByteOrder( @result.D1, SizeOf(result.D1));
373 IntegerUtils.SwapByteOrder( @result.D2, SizeOf(result.D2));
374 IntegerUtils.SwapByteOrder( @result.D3, SizeOf(result.D3));
375 //result.D4 = array of byte -> implicitly correct
376end;
377
378
379{$IFDEF Debug}
Jens Geyerf8f62782022-09-10 00:55:02 +0200380class procedure GuidUtils.SelfTest;
Jens Geyer62445c12022-06-29 00:00:00 +0200381var guid : TGuid;
382 pBytes : PByteArray;
383 i, expected : Integer;
384const TEST_GUID : TGuid = '{00112233-4455-6677-8899-aabbccddeeff}';
385begin
386 // host to network
387 guid := TEST_GUID;
Jens Geyerf8f62782022-09-10 00:55:02 +0200388 guid := GuidUtils.SwapByteOrder(guid);
Jens Geyer62445c12022-06-29 00:00:00 +0200389
390 // validate network order
391 pBytes := @guid;
392 for i := 0 to $F do begin
393 expected := i * $11;
394 ASSERT( pBytes^[i] = expected);
395 end;
396
397 // network to host and final validation
Jens Geyerf8f62782022-09-10 00:55:02 +0200398 guid := GuidUtils.SwapByteOrder(guid);
Jens Geyer62445c12022-06-29 00:00:00 +0200399 ASSERT( IsEqualGuid( guid, TEST_GUID));
Jens Geyerf8f62782022-09-10 00:55:02 +0200400
401 // prevent collisions with SysUtils.TGuidHelper
402 guid := TGuid.NewGuid;
Jens Geyer62445c12022-06-29 00:00:00 +0200403end;
404{$ENDIF}
405
Jens Geyer589ee5b2021-03-29 21:40:55 +0200406
407
Jens Geyerf7904452017-07-26 15:02:12 +0200408{$IFDEF Win64}
409
410function InterlockedCompareExchange64( var Target : Int64; Exchange, Comparand : Int64) : Int64; inline;
411begin
412 {$IFDEF OLD_UNIT_NAMES}
413 result := Windows.InterlockedCompareExchange64( Target, Exchange, Comparand);
414 {$ELSE}
415 result := WinApi.Windows.InterlockedCompareExchange64( Target, Exchange, Comparand);
416 {$ENDIF}
417end;
Jens Geyer9f7f11e2016-04-14 21:37:11 +0200418
419
Jens Geyerf7904452017-07-26 15:02:12 +0200420function InterlockedExchangeAdd64( var Addend : Int64; Value : Int64) : Int64;
Jens Geyer9f7f11e2016-04-14 21:37:11 +0200421var old : Int64;
422begin
423 repeat
424 Old := Addend;
425 until (InterlockedCompareExchange64( Addend, Old + Value, Old) = Old);
426 result := Old;
427end;
428
Jens Geyerf7904452017-07-26 15:02:12 +0200429{$ENDIF}
Jens Geyer71070432016-01-29 10:08:39 +0100430
431
Jens Geyer8f7487e2019-05-09 22:21:32 +0200432{ EnumUtils<T> }
433
434class function EnumUtils<T>.ToString(const value : Integer) : string;
435var pType : PTypeInfo;
436begin
437 pType := PTypeInfo(TypeInfo(T));
Jens Geyer6e2a6982019-12-12 23:07:49 +0100438 if Assigned(pType)
439 and (pType^.Kind = tkEnumeration)
440 {$IF CompilerVersion >= 23.0} // TODO: Range correct? What we know is that XE does not offer it, but Rio has it
441 and (pType^.TypeData^.MaxValue >= value)
442 and (pType^.TypeData^.MinValue <= value)
443 {$ELSE}
444 and FALSE // THRIFT-5048: pType^.TypeData^ member not supported -> prevent GetEnumName() from reading outside the legal range
445 {$IFEND}
446 then result := GetEnumName( PTypeInfo(pType), value)
Jens Geyer8f7487e2019-05-09 22:21:32 +0200447 else result := IntToStr(Ord(value));
448end;
449
450
451{ StringUtils<T> }
452
453class function StringUtils<T>.ToString(const value : T) : string;
Jens Geyer85431d92019-05-10 21:17:00 +0200454type PInterface = ^IInterface;
Jens Geyer8f7487e2019-05-09 22:21:32 +0200455var pType : PTypeInfo;
Jens Geyer85431d92019-05-10 21:17:00 +0200456 stos : ISupportsToString;
457 pIntf : PInterface; // Workaround: Rio does not allow the direct typecast
Jens Geyer8f7487e2019-05-09 22:21:32 +0200458begin
459 pType := PTypeInfo(TypeInfo(T));
460 if Assigned(pType) then begin
461 case pType^.Kind of
Jens Geyerec572712020-07-13 21:15:31 +0200462
Jens Geyer8f7487e2019-05-09 22:21:32 +0200463 tkInterface : begin
Jens Geyer85431d92019-05-10 21:17:00 +0200464 pIntf := PInterface(@value);
465 if Supports( pIntf^, ISupportsToString, stos) then begin
466 result := stos.toString;
Jens Geyer8f7487e2019-05-09 22:21:32 +0200467 Exit;
468 end;
469 end;
Jens Geyerec572712020-07-13 21:15:31 +0200470
471 tkEnumeration : begin
472 case SizeOf(value) of
473 1 : begin result := EnumUtils<T>.ToString( PShortInt(@value)^); Exit; end;
474 2 : begin result := EnumUtils<T>.ToString( PSmallInt(@value)^); Exit; end;
475 4 : begin result := EnumUtils<T>.ToString( PLongInt(@value)^); Exit; end;
476 else
477 ASSERT(FALSE); // in theory, this should not happen
478 end;
479 end;
480
Jens Geyer8f7487e2019-05-09 22:21:32 +0200481 end;
482 end;
483
484 result := TValue.From<T>(value).ToString;
485end;
486
487
488{ TThriftStringBuilder }
489
490function TThriftStringBuilder.Append(const Value: TBytes): TStringBuilder;
491begin
492 Result := Append( string( RawByteString(Value)) );
493end;
494
495function TThriftStringBuilder.Append( const Value: ISupportsToString): TStringBuilder;
496begin
497 Result := Append( Value.ToString );
498end;
499
500
Jens Geyer62445c12022-06-29 00:00:00 +0200501begin
502 {$IFDEF Debug}
Jens Geyerf8f62782022-09-10 00:55:02 +0200503 GuidUtils.SelfTest;
Jens Geyer62445c12022-06-29 00:00:00 +0200504 {$ENDIF}
Jens Geyerd5436f52014-10-03 19:50:38 +0200505end.