From 39fb5ae47910de0b596682758c7599929e5759dd Mon Sep 17 00:00:00 2001 From: Razor12911 Date: Fri, 13 May 2022 13:05:10 +0200 Subject: [PATCH] support for delphi 11.1 --- .../mORMot/CrossPlatform/SynCrossPlatform.inc | 4 +- .../CrossPlatform/SynCrossPlatformCrypto.pas | 4 +- .../CrossPlatform/SynCrossPlatformJSON.pas | 8 +- .../CrossPlatform/SynCrossPlatformREST.pas | 21 +- .../SynCrossPlatformSpecific.pas | 4 +- .../CrossPlatform/SynCrossPlatformSynLZ.pas | 10 +- .../CrossPlatform/SynCrossPlatformTests.pas | 4 +- contrib/mORMot/PasZip.pas | 733 ++++--- .../SQLite3/DDD/dom/dddDomAuthInterfaces.pas | 4 +- .../mORMot/SQLite3/DDD/dom/dddDomCountry.pas | 4 +- .../SQLite3/DDD/dom/dddDomEmailInterfaces.pas | 2 +- .../mORMot/SQLite3/DDD/dom/dddDomUserCQRS.pas | 4 +- .../SQLite3/DDD/dom/dddDomUserInterfaces.pas | 4 +- .../SQLite3/DDD/dom/dddDomUserTypes.pas | 4 +- .../mORMot/SQLite3/DDD/infra/dddInfraApps.pas | 7 +- .../SQLite3/DDD/infra/dddInfraAuthRest.pas | 4 +- .../SQLite3/DDD/infra/dddInfraEmail.pas | 4 +- .../SQLite3/DDD/infra/dddInfraEmailer.pas | 4 +- .../SQLite3/DDD/infra/dddInfraRepoUser.pas | 4 +- .../SQLite3/DDD/infra/dddInfraSettings.pas | 4 +- .../Synopse SQLite3 Framework.pro | 89 +- .../11 - Exception logging/LogViewMain.lfm | 57 +- .../Samples/24 - MongoDB/MongoDBTestCases.pas | 9 +- .../Samples/30 - MVC Server/MVCModel.pas | 81 +- .../Samples/30 - MVC Server/MVCServer.dpr | 10 +- .../Samples/30 - MVC Server/MVCViewModel.pas | 171 +- .../30 - MVC Server/Views/ArticleView.html | 96 +- .../30 - MVC Server/Views/AuthorView.html | 32 +- .../30 - MVC Server/Views/footer.partial | 17 +- .../30 - MVC Server/Views/masthead.partial | 55 +- .../31 - WebSockets/Project31ChatClient.dpr | 1 + .../SQLite3/Samples/33 - ECC/ECCProcess.pas | 4 +- .../RESTBenchmark.dpr | 11 +- .../RESTBenchmark.lpi | 9 + .../SynJSONTreeview/SynJSONTreeView.pas | 4 +- .../TSynRestDataset/SynRestMidasVCL.pas | 4 +- .../EMartin/TSynRestDataset/SynRestVCL.pas | 4 +- .../REST-tester/mORMotRESTFPCInterfaces.pas | 2 +- .../SynTaskDialog4Lazarus/FMXUtil.inc.pas | 49 + .../SynTaskDialog4Lazarus/SynTaskDialog.pas | 12 +- contrib/mORMot/SQLite3/TestSQL3.dpr | 3 + .../mORMot/SQLite3/TestSQL3FPCInterfaces.pas | 2 +- .../amalgamation/ciphers/cipher_common.c | 666 ++++++ .../amalgamation/ciphers/cipher_common.h | 233 ++ .../amalgamation/compile-delphi-win32.bat | 7 +- .../amalgamation/compile-delphi-win64.bat | 10 +- .../compile-fpc-aarch64-android.sh | 25 + .../amalgamation/compile-fpc-aarch64-linux.sh | 8 +- .../amalgamation/compile-fpc-arm-android.sh | 24 + .../amalgamation/compile-fpc-arm-linux.sh | 8 +- .../amalgamation/compile-fpc-i386-android.sh | 24 + .../amalgamation/compile-fpc-i386-darwin.sh | 10 +- .../amalgamation/compile-fpc-i386-freebsd.sh | 8 +- .../amalgamation/compile-fpc-i386-linux.bat | 4 + .../amalgamation/compile-fpc-i386-linux.sh | 8 +- .../amalgamation/compile-fpc-i386-openbsd.sh | 8 +- .../amalgamation/compile-fpc-i386-win32.sh | 7 +- .../compile-fpc-x86_64-android.sh | 24 + .../amalgamation/compile-fpc-x86_64-darwin.sh | 10 +- .../compile-fpc-x86_64-freebsd.sh | 8 +- .../amalgamation/compile-fpc-x86_64-linux.bat | 8 +- .../amalgamation/compile-fpc-x86_64-linux.sh | 7 +- .../compile-fpc-x86_64-openbsd.sh | 8 +- .../amalgamation/compile-fpc-x86_64-win64.sh | 19 +- .../SQLite3/amalgamation/patch-and-compile.sh | 8 + contrib/mORMot/SQLite3/amalgamation/regexp.c | 760 +++++++ .../mORMot/SQLite3/amalgamation/sqlite3mc.c | 121 +- contrib/mORMot/SQLite3/mORMot.pas | 501 +++-- contrib/mORMot/SQLite3/mORMotBigTable.pas | 4 +- contrib/mORMot/SQLite3/mORMotDB.pas | 59 +- contrib/mORMot/SQLite3/mORMotDDD.pas | 6 +- .../mORMot/SQLite3/mORMotFastCgiServer.pas | 4 +- contrib/mORMot/SQLite3/mORMotHttpClient.pas | 16 +- contrib/mORMot/SQLite3/mORMotHttpServer.pas | 9 +- contrib/mORMot/SQLite3/mORMotMVC.pas | 40 +- contrib/mORMot/SQLite3/mORMotMidasVCL.pas | 4 +- contrib/mORMot/SQLite3/mORMotMongoDB.pas | 38 +- contrib/mORMot/SQLite3/mORMotReport.pas | 16 +- contrib/mORMot/SQLite3/mORMotSQLite3.pas | 14 +- contrib/mORMot/SQLite3/mORMotSelfTests.pas | 8 +- contrib/mORMot/SQLite3/mORMotService.pas | 31 +- contrib/mORMot/SQLite3/mORMotToolBar.pas | 4 +- contrib/mORMot/SQLite3/mORMotUI.pas | 17 +- contrib/mORMot/SQLite3/mORMotUIEdit.pas | 4 +- contrib/mORMot/SQLite3/mORMotUILogin.pas | 4 +- contrib/mORMot/SQLite3/mORMotUIOptions.pas | 4 +- contrib/mORMot/SQLite3/mORMotUIQuery.pas | 4 +- contrib/mORMot/SQLite3/mORMotVCL.pas | 4 +- contrib/mORMot/SQLite3/mORMotWrappers.pas | 4 +- contrib/mORMot/SQLite3/mORMoti18n.pas | 4 +- contrib/mORMot/SQLite3/sqlite3.obj | Bin 823471 -> 923856 bytes contrib/mORMot/SyNode/SpiderMonkey.pas | 6 +- contrib/mORMot/SyNode/SyNode.inc | 6 +- contrib/mORMot/SyNode/SyNode.pas | 20 +- contrib/mORMot/SyNode/SyNodeBinding_fs.pas | 2 +- contrib/mORMot/SyNode/SyNodeNewProto.pas | 6 +- contrib/mORMot/SyNode/SyNodeProto.pas | 4 +- .../mORMot/SyNode/SyNodeRemoteDebugger.pas | 28 +- contrib/mORMot/SyNode/SyNodeSimpleProto.pas | 6 +- contrib/mORMot/SynBidirSock.pas | 10 +- contrib/mORMot/SynBigTable.pas | 6 +- contrib/mORMot/SynBz.pas | 4 +- contrib/mORMot/SynBzPas.pas | 4 +- contrib/mORMot/SynCommons.pas | 1174 +++++----- contrib/mORMot/SynCrtSock.pas | 291 ++- contrib/mORMot/SynCrypto.pas | 667 +++++- contrib/mORMot/SynCurl.pas | 147 +- contrib/mORMot/SynDB.pas | 141 +- contrib/mORMot/SynDBDataset.pas | 6 +- contrib/mORMot/SynDBDataset/SynDBBDE.pas | 4 +- contrib/mORMot/SynDBDataset/SynDBFireDAC.pas | 7 +- contrib/mORMot/SynDBDataset/SynDBNexusDB.pas | 5 +- contrib/mORMot/SynDBDataset/SynDBUniDAC.pas | 254 ++- contrib/mORMot/SynDBFirebird.pas | 4 +- contrib/mORMot/SynDBMidasVCL.pas | 4 +- contrib/mORMot/SynDBODBC.pas | 40 +- contrib/mORMot/SynDBOracle.pas | 46 +- contrib/mORMot/SynDBPostgres.pas | 21 +- contrib/mORMot/SynDBRemote.pas | 4 +- contrib/mORMot/SynDBSQLite3.pas | 4 +- contrib/mORMot/SynDBVCL.pas | 67 +- contrib/mORMot/SynDBZeos.pas | 20 +- contrib/mORMot/SynDprUses.inc | 5 +- contrib/mORMot/SynEcc.pas | 99 +- contrib/mORMot/SynEcc32asm.inc | 2 +- contrib/mORMot/SynFPCCMemAligned.pas | 4 +- contrib/mORMot/SynFPCLinux.pas | 363 +++- contrib/mORMot/SynFPCSock.pas | 4 +- contrib/mORMot/SynFPCTypInfo.pas | 4 +- contrib/mORMot/SynFPCx64MM.pas | 1899 +++++++++++------ contrib/mORMot/SynFastWideString.pas | 4 +- contrib/mORMot/SynGSSAPI.pas | 76 +- contrib/mORMot/SynGSSAPIAuth.pas | 8 +- contrib/mORMot/SynGdiPlus.pas | 8 +- contrib/mORMot/SynKylix.pas | 4 +- contrib/mORMot/SynLZ.pas | 4 +- contrib/mORMot/SynLZO.pas | 4 +- contrib/mORMot/SynLizard.pas | 4 +- contrib/mORMot/SynLog.pas | 340 +-- contrib/mORMot/SynMemoEx.pas | 14 +- contrib/mORMot/SynMongoDB.pas | 263 +-- contrib/mORMot/SynMustache.pas | 4 +- contrib/mORMot/SynOleDB.pas | 7 +- contrib/mORMot/SynOpenSSL.pas | 4 +- contrib/mORMot/SynPdf.pas | 411 +++- contrib/mORMot/SynProtoRTSPHTTP.pas | 4 +- contrib/mORMot/SynProtoRelay.pas | 4 +- contrib/mORMot/SynSM.inc | 4 +- contrib/mORMot/SynSM.pas | 6 +- contrib/mORMot/SynSMAPI.pas | 6 +- contrib/mORMot/SynSQLite3.pas | 23 +- contrib/mORMot/SynSQLite3RegEx.pas | 4 +- contrib/mORMot/SynSQLite3Static.pas | 43 +- contrib/mORMot/SynSSPI.pas | 16 +- contrib/mORMot/SynSSPIAuth.pas | 4 +- contrib/mORMot/SynScaleMM.pas | 4 +- contrib/mORMot/SynSelfTests.pas | 411 +++- contrib/mORMot/SynTable.pas | 375 +++- contrib/mORMot/SynTaskDialog.pas | 6 +- contrib/mORMot/SynTests.pas | 4 +- contrib/mORMot/SynVirtualDataSet.pas | 8 +- contrib/mORMot/SynWinSock.pas | 130 +- contrib/mORMot/SynZLibSSE.pas | 4 +- contrib/mORMot/SynZip.pas | 81 +- contrib/mORMot/SynZipFiles.pas | 24 +- contrib/mORMot/Synopse.inc | 32 +- contrib/mORMot/SynopseCommit.inc | 2 +- 167 files changed, 8914 insertions(+), 3205 deletions(-) create mode 100644 contrib/mORMot/SQLite3/amalgamation/ciphers/cipher_common.c create mode 100644 contrib/mORMot/SQLite3/amalgamation/ciphers/cipher_common.h create mode 100644 contrib/mORMot/SQLite3/amalgamation/compile-fpc-aarch64-android.sh create mode 100644 contrib/mORMot/SQLite3/amalgamation/compile-fpc-arm-android.sh create mode 100644 contrib/mORMot/SQLite3/amalgamation/compile-fpc-i386-android.sh create mode 100644 contrib/mORMot/SQLite3/amalgamation/compile-fpc-x86_64-android.sh create mode 100644 contrib/mORMot/SQLite3/amalgamation/regexp.c diff --git a/contrib/mORMot/CrossPlatform/SynCrossPlatform.inc b/contrib/mORMot/CrossPlatform/SynCrossPlatform.inc index 984cc9f..045e293 100644 --- a/contrib/mORMot/CrossPlatform/SynCrossPlatform.inc +++ b/contrib/mORMot/CrossPlatform/SynCrossPlatform.inc @@ -2,7 +2,7 @@ { This file is part of Synopse mORMot framework. - Synopse mORMot framework. Copyright (C) 2020 Arnaud Bouchez + Synopse mORMot framework. Copyright (C) 2022 Arnaud Bouchez Synopse Informatique - https://synopse.info *** BEGIN LICENSE BLOCK ***** @@ -21,7 +21,7 @@ The Initial Developer of the Original Code is Arnaud Bouchez. - Portions created by the Initial Developer are Copyright (C) 2020 + Portions created by the Initial Developer are Copyright (C) 2022 the Initial Developer. All Rights Reserved. Contributor(s): diff --git a/contrib/mORMot/CrossPlatform/SynCrossPlatformCrypto.pas b/contrib/mORMot/CrossPlatform/SynCrossPlatformCrypto.pas index 89c03db..1f6b40e 100644 --- a/contrib/mORMot/CrossPlatform/SynCrossPlatformCrypto.pas +++ b/contrib/mORMot/CrossPlatform/SynCrossPlatformCrypto.pas @@ -6,7 +6,7 @@ unit SynCrossPlatformCrypto; { This file is part of Synopse mORMot framework. - Synopse mORMot framework. Copyright (C) 2020 Arnaud Bouchez + Synopse mORMot framework. Copyright (C) 2022 Arnaud Bouchez Synopse Informatique - https://synopse.info *** BEGIN LICENSE BLOCK ***** @@ -25,7 +25,7 @@ unit SynCrossPlatformCrypto; The Initial Developer of the Original Code is Arnaud Bouchez. - Portions created by the Initial Developer are Copyright (C) 2020 + Portions created by the Initial Developer are Copyright (C) 2022 the Initial Developer. All Rights Reserved. Contributor(s): diff --git a/contrib/mORMot/CrossPlatform/SynCrossPlatformJSON.pas b/contrib/mORMot/CrossPlatform/SynCrossPlatformJSON.pas index 2570650..ea14163 100644 --- a/contrib/mORMot/CrossPlatform/SynCrossPlatformJSON.pas +++ b/contrib/mORMot/CrossPlatform/SynCrossPlatformJSON.pas @@ -6,7 +6,7 @@ unit SynCrossPlatformJSON; { This file is part of Synopse mORMot framework. - Synopse mORMot framework. Copyright (C) 2020 Arnaud Bouchez + Synopse mORMot framework. Copyright (C) 2022 Arnaud Bouchez Synopse Informatique - https://synopse.info *** BEGIN LICENSE BLOCK ***** @@ -25,7 +25,7 @@ unit SynCrossPlatformJSON; The Initial Developer of the Original Code is Arnaud Bouchez. - Portions created by the Initial Developer are Copyright (C) 2020 + Portions created by the Initial Developer are Copyright (C) 2022 the Initial Developer. All Rights Reserved. Contributor(s): @@ -1682,7 +1682,7 @@ begin VKind := jvObject else if VKind<>jvObject then raise EJSONException.CreateFmt('AddNameValue(%s) over array',[aName]); - if VCount<=length(Values) then begin + if VCount=length(Values) then begin SetLength(Values,VCount+VCount shr 3+32); SetLength(Names,VCount+VCount shr 3+32); end; @@ -1697,7 +1697,7 @@ begin VKind := jvArray else if VKind<>jvArray then raise EJSONException.Create('AddValue() over object'); - if VCount<=length(Values) then + if VCount=length(Values) then SetLength(Values,VCount+VCount shr 3+32); Values[VCount] := aValue; inc(VCount); diff --git a/contrib/mORMot/CrossPlatform/SynCrossPlatformREST.pas b/contrib/mORMot/CrossPlatform/SynCrossPlatformREST.pas index 5acb124..19f66f9 100644 --- a/contrib/mORMot/CrossPlatform/SynCrossPlatformREST.pas +++ b/contrib/mORMot/CrossPlatform/SynCrossPlatformREST.pas @@ -6,7 +6,7 @@ unit SynCrossPlatformREST; { This file is part of Synopse mORMot framework. - Synopse mORMot framework. Copyright (C) 2020 Arnaud Bouchez + Synopse mORMot framework. Copyright (C) 2022 Arnaud Bouchez Synopse Informatique - https://synopse.info *** BEGIN LICENSE BLOCK ***** @@ -25,7 +25,7 @@ unit SynCrossPlatformREST; The Initial Developer of the Original Code is Arnaud Bouchez. - Portions created by the Initial Developer are Copyright (C) 2020 + Portions created by the Initial Developer are Copyright (C) 2022 the Initial Developer. All Rights Reserved. Contributor(s): @@ -253,8 +253,8 @@ type /// a set of published property Kind TSQLFieldKinds = set of TSQLFieldKind; - { TODO: TID should be a string since number is limited to 53-bit in JavaScript - -> define and use an explicit Int52 type for SMS } + { Should TID be a string since number is limited to 53-bit in JavaScript? + -> or define and use an explicit Int52 type for SMS? } /// the TSQLRecord primary key is a 64 bit integer TID = {$ifndef ISDWS}type{$endif} Int64; @@ -2895,7 +2895,7 @@ begin end; /// marshall {result:...,id:...} and {result:...} body answers -function CallGetResult(const aCall: TSQLRestURIParams; var outID: integer): variant; +function CallGetResult(const aCall: TSQLRestURIParams; var outID: TID): variant; {$ifndef ISSMS} var doc: TJSONVariantData; jsonres: string; @@ -2922,7 +2922,7 @@ end; function TSQLRestClientURI.CallBackGetResult(const aMethodName: string; const aNameValueParameters: array of const; aTable: TSQLRecordClass; aID: TID): string; var Call: TSQLRestURIParams; - dummyID: integer; + dummyID: TID; begin CallBackGet(aMethodName,aNameValueParameters,Call,aTable,aID); result := CallGetResult(Call,dummyID); @@ -3038,7 +3038,7 @@ begin onError(self); exit; end; - var outID: integer; + var outID: TID; var result := CallGetResult(Call,outID); // from {result:...,id:...} if VarIsValidRef(result) then begin if (aCaller.fInstanceImplementation=sicClientDriven) and (outID<>0) then @@ -3067,7 +3067,7 @@ function TSQLRestClientURI.CallRemoteServiceSynch(aCaller: TServiceClientAbstrac const aInputParams: array of variant; aReturnsCustomAnswer: boolean): TVariantDynArray; var Call: TSQLRestURIParams; outResult: variant; - outID: integer; + outID: TID; procedure RaiseError; begin raise EServiceException.CreateFmt('Error calling %s.%s - returned status %d', @@ -3119,7 +3119,8 @@ var Call: TSQLRestURIParams; result: variant; bodyerror: string; arr: PJSONVariantData; - i,outID: integer; + i: integer; + outID: TID; begin params.Init; for i := 0 to high(aInputParams) do @@ -3671,7 +3672,7 @@ end; constructor TServiceClientAbstract.Create(aClient: TSQLRestClientURI); var Call: TSQLRestURIParams; // manual synchronous call - dummyID: integer; + dummyID: TID; result: variant; contract: string; begin diff --git a/contrib/mORMot/CrossPlatform/SynCrossPlatformSpecific.pas b/contrib/mORMot/CrossPlatform/SynCrossPlatformSpecific.pas index 1f0d3c9..1cc4dfb 100644 --- a/contrib/mORMot/CrossPlatform/SynCrossPlatformSpecific.pas +++ b/contrib/mORMot/CrossPlatform/SynCrossPlatformSpecific.pas @@ -6,7 +6,7 @@ unit SynCrossPlatformSpecific; { This file is part of Synopse mORMot framework. - Synopse mORMot framework. Copyright (C) 2020 Arnaud Bouchez + Synopse mORMot framework. Copyright (C) 2022 Arnaud Bouchez Synopse Informatique - https://synopse.info *** BEGIN LICENSE BLOCK ***** @@ -25,7 +25,7 @@ unit SynCrossPlatformSpecific; The Initial Developer of the Original Code is Arnaud Bouchez. - Portions created by the Initial Developer are Copyright (C) 2020 + Portions created by the Initial Developer are Copyright (C) 2022 the Initial Developer. All Rights Reserved. Contributor(s): diff --git a/contrib/mORMot/CrossPlatform/SynCrossPlatformSynLZ.pas b/contrib/mORMot/CrossPlatform/SynCrossPlatformSynLZ.pas index d038c6f..8a69505 100644 --- a/contrib/mORMot/CrossPlatform/SynCrossPlatformSynLZ.pas +++ b/contrib/mORMot/CrossPlatform/SynCrossPlatformSynLZ.pas @@ -8,7 +8,7 @@ interface { This file is part of Synopse mORMot framework. - Synopse mORMot framework. Copyright (C) 2020 Arnaud Bouchez + Synopse mORMot framework. Copyright (C) 2022 Arnaud Bouchez Synopse Informatique - https://synopse.info *** BEGIN LICENSE BLOCK ***** @@ -27,7 +27,7 @@ interface The Initial Developer of the Original Code is Arnaud Bouchez. - Portions created by the Initial Developer are Copyright (C) 2020 + Portions created by the Initial Developer are Copyright (C) 2022 the Initial Developer. All Rights Reserved. Contributor(s): @@ -86,10 +86,10 @@ type {$ifdef FPC} PBytes = PAnsiChar; {$else} - PtrUInt = {$ifdef CPUX64} NativeUInt {$else} cardinal {$endif}; + PtrUInt = {$ifdef UNICODE} NativeUInt {$else} cardinal {$endif}; TBytes = array[0..maxInt-1] of byte; PBytes = ^TBytes; -{$endif} +{$endif FPC} function SynLZcomp(src: pointer; size: cardinal; dst: pointer): cardinal; var dst_beg, // initial dst value @@ -190,7 +190,7 @@ function SynLZdecomp(src: pointer; size: cardinal; dst: pointer): cardinal; var last_hashed, // initial src and dst value src_end: PtrUInt; CW, CWbit: cardinal; - v, t, h, o: cardinal; + v, t, h, o: PtrUInt; i: integer; offset: array[0..4095] of PtrUInt; // 16KB hashing code label nextCW; diff --git a/contrib/mORMot/CrossPlatform/SynCrossPlatformTests.pas b/contrib/mORMot/CrossPlatform/SynCrossPlatformTests.pas index 49aec4c..b3e5eca 100644 --- a/contrib/mORMot/CrossPlatform/SynCrossPlatformTests.pas +++ b/contrib/mORMot/CrossPlatform/SynCrossPlatformTests.pas @@ -6,7 +6,7 @@ unit SynCrossPlatformTests; { This file is part of Synopse mORMot framework. - Synopse mORMot framework. Copyright (C) 2020 Arnaud Bouchez + Synopse mORMot framework. Copyright (C) 2022 Arnaud Bouchez Synopse Informatique - https://synopse.info *** BEGIN LICENSE BLOCK ***** @@ -25,7 +25,7 @@ unit SynCrossPlatformTests; The Initial Developer of the Original Code is Arnaud Bouchez. - Portions created by the Initial Developer are Copyright (C) 2020 + Portions created by the Initial Developer are Copyright (C) 2022 the Initial Developer. All Rights Reserved. Contributor(s): diff --git a/contrib/mORMot/PasZip.pas b/contrib/mORMot/PasZip.pas index 5eaf4d2..0fb1ee8 100644 --- a/contrib/mORMot/PasZip.pas +++ b/contrib/mORMot/PasZip.pas @@ -6,7 +6,7 @@ unit PasZip; { This file is part of Synopse framework. - Synopse framework. Copyright (C) 2020 Arnaud Bouchez + Synopse framework. Copyright (C) 2022 Arnaud Bouchez Synopse Informatique - https://synopse.info This library is free software; you can redistribute it and/or modify it @@ -52,7 +52,7 @@ uses Windows, {$else} Types, -{$endif} +{$endif MSWINDOWS} SysUtils; type @@ -62,10 +62,11 @@ type {$else} RawByteZip = AnsiString; TZipName = AnsiString; -{$endif} +{$endif HASCODEPAGE} + {$ifdef DELPHI5OROLDER} PCardinal = ^cardinal; -{$endif} +{$endif DELPHI5OROLDER} /// compress memory using the ZLib DEFLATE algorithm function CompressMem(src, dst: pointer; srcLen, dstLen: integer): integer; @@ -81,6 +82,7 @@ function UncompressString(const data: RawByteZip): RawByteZip; {$ifdef MSWINDOWS} { use Windows MapFile } + function CompressFile(const srcFile, dstFile: TFileName; failIfGrow: boolean = false): boolean; function UncompressFile(const srcFile, dstFile: TFileName; @@ -169,7 +171,7 @@ const $cabac28a, $53b39330, $24b4a3a6, $bad03605, $cdd70693, $54de5729, $23d967bf, $b3667a2e, $c4614ab8, $5d681b02, $2a6f2b94, $b40bbe37, $c30c8ea1, $5a05df1b, $2d02ef8d); -{$endif} +{$endif DYNAMIC_CRC_TABLE} {$ifdef MSWINDOWS} @@ -304,9 +306,9 @@ type constructor Create(const aFileName: TFileName); overload; /// compress (using the deflate method) a memory buffer, and add it to the zip file // - by default, the 1st of January, 2010 is used if not date is supplied - procedure AddDeflated(const aZipName: TZipName; Buf: pointer; Size: - integer; CompressLevel: integer = 6; FileAge: integer = 1 + 1 shl 5 + 30 - shl 9); overload; + procedure AddDeflated(const aZipName: TZipName; + Buf: pointer; Size: integer; CompressLevel: integer = 6; + FileAge: integer = 1 + 1 shl 5 + 30 shl 9); overload; /// compress (using the deflate method) a file, and add it to the zip file procedure AddDeflated(const aFileName: TFileName; RemovePath: boolean = true; CompressLevel: integer = 6); overload; @@ -345,10 +347,10 @@ begin // should be fast enough in practice, especially inlined repeat Dst^ := PByteArray(Src)[PtrUInt(Dst)]; inc(Dst); - until PtrUInt(Dst)=Count; + until PtrUInt(Dst) = Count; end; {$else} -procedure MoveWithOverlap(Src: PByte; Dst: PByte; Count: Integer); +procedure MoveWithOverlap(Src: PByte; Dst: PByte; Count: integer); {$ifdef FPC} nostackframe; assembler; {$endif} asm // eax=source edx=dest ecx=count push edx @@ -370,7 +372,7 @@ asm // eax=source edx=dest ecx=count pop edi @exit: end; -{$endif} +{$endif PUREPASCAL} //----------------- general library stuff @@ -415,20 +417,19 @@ type ); // inflate codes private state - PInflateCodesState = ^TInflateCodesState; TInflateCodesState = record Mode: TInflateCodesMode; // current inflate codes mode // mode dependent information Len: Cardinal; - Sub: record // submode + Sub: record // submode case Byte of - 0:(Code: record // if Len or Distance, where in tree + 0:(Code: record // if Len or Distance, where in tree Tree: PInflateHuft; // pointer into tree - need: Cardinal; // bits needed + need: Cardinal; // bits needed end); - 1:(lit: Cardinal); // if icmLit, literal - 2:(copy: record // if EXT or icmCopy, where and how much - get: Cardinal; // bits to get for extra + 1:(lit: Cardinal); // if icmLit, literal + 2:(copy: record // if EXT or icmCopy, where and how much + get: Cardinal; // bits to get for extra Distance: Cardinal; // distance back to copy from end); end; @@ -438,8 +439,10 @@ type LiteralTree: PInflateHuft; // literal/length/eob tree DistanceTree: PInflateHuft; // distance tree end; + PInflateCodesState = ^TInflateCodesState; - TInflateBlockMode = (ibmZType, // get type bits (3, including end bit) + TInflateBlockMode = ( + ibmZType, // get type bits (3, including end bit) ibmLens, // get lengths for stored ibmStored, // processing stored block ibmTable, // get table lengths @@ -452,27 +455,26 @@ type ); // inflate blocks semi-private state - PInflateBlocksState = ^TInflateBlocksState; TInflateBlocksState = record - Mode: TInflateBlockMode; // current inflate block mode + Mode: TInflateBlockMode; // current inflate block mode // mode dependent information - Sub: record // submode + Sub: record // submode case Byte of - 0: (left: Cardinal); // if ibmStored, bytes left to copy - 1: (Trees: record // if DistanceTree, decoding info for trees + 0: (left: Cardinal); // if ibmStored, bytes left to copy + 1: (Trees: record // if DistanceTree, decoding info for trees Table: Cardinal; // table lengths (14 Bits) Index: Cardinal; // index into blens (or BitOrder) blens: TPACardinal; // bit lengths of codes BB: Cardinal; // bit length tree depth TB: PInflateHuft; // bit length decoding tree end); - 2: (decode: record // if ibmCodes, current state + 2: (decode: record // if ibmCodes, current state TL: PInflateHuft; TD: PInflateHuft; // trees to free codes: PInflateCodesState; end); end; - Last: Boolean; // True if this block is the last block + Last: boolean; // True if this block is the last block // mode independent information bitk: Cardinal; // bits in bit buffer bitb: Cardinal; // bit buffer @@ -482,6 +484,7 @@ type read: PByte; // window read pointer write: PByte; // window write pointer end; + PInflateBlocksState = ^TInflateBlocksState; // The application must update NextInput and AvailableInput when AvailableInput has dropped to zero. It must update // NextOutput and AvailableOutput when AvailableOutput has dropped to zero. All other fields are set by the @@ -540,21 +543,21 @@ type fc: record case Byte of 0: - (Frequency: Word); // frequency count + (Frequency: word); // frequency count 1: - (Code: Word); // bit string + (Code: word); // bit string end; dl: record case Byte of 0: - (dad: Word); // father node in Huffman tree + (dad: word); // father node in Huffman tree 1: - (Len: Word); // length of bit string + (Len: word); // length of bit string end; end; TLiteralTree = array[0..HEAP_SIZE - 1] of TTreeEntry; // literal and length tree - TDistanceTree = array[0..2 * D_CODES] of TTreeEntry; // distance tree - THuffmanTree = array[0..2 * BL_CODES] of TTreeEntry; // Huffman tree for bit lengths + TDistanceTree = array[0..2 * D_CODES] of TTreeEntry; // distance tree + THuffmanTree = array[0..2 * BL_CODES] of TTreeEntry; // Huffman tree for bit lengths PTree = ^TTree; TTree = array[0..(MaxInt div SizeOf(TTreeEntry)) - 1] of TTreeEntry; // generic tree type @@ -562,15 +565,15 @@ type TStaticTreeDescriptor = record StaticTree: PTree; // static tree or nil ExtraBits: TPAInteger; // extra bits for each code or nil - ExtraBase: Integer; // base index for ExtraBits - Elements: Integer; // max number of elements in the tree - MaxLength: Integer; // max bit length for the codes + ExtraBase: integer; // base index for ExtraBits + Elements: integer; // max number of elements in the tree + MaxLength: integer; // max bit length for the codes end; PTreeDescriptor = ^TTreeDescriptor; TTreeDescriptor = record DynamicTree: PTree; - MaxCode: Integer; // largest code with non zero frequency + MaxCode: integer; // largest code with non zero frequency StaticDescriptor: PStaticTreeDescriptor; // the corresponding static tree end; @@ -578,9 +581,9 @@ type TDeflateState = record ZState: PZState; // pointer back to this zlib stream PendingBuffer: TPAByte; // output still pending - PendingBufferSize: Integer; + PendingBufferSize: integer; PendingOutput: PByte; // next pending byte to output to the stream - Pending: Integer; // nb of bytes in the pending buffer + Pending: integer; // nb of bytes in the pending buffer WindowSize: Cardinal; // LZ77 window size (32K by default) WindowBits: Cardinal; // log2(WindowSize) (8..16) WindowMask: Cardinal; // WindowSize - 1 @@ -596,7 +599,7 @@ type // Actual size of Window: 2 * WSize, except when the user input buffer // is directly used as sliding window. - CurrentWindowSize: Integer; + CurrentWindowSize: integer; // Link to older string with same hash index. to limit the size of this // array to 64K, this link is maintained only for the last 32K strings. @@ -616,10 +619,10 @@ type // Window position at the beginning of the current output block. Gets // negative when the window is moved backwards. - BlockStart: Integer; + BlockStart: integer; MatchLength: Cardinal; // length of best match PreviousMatch: Cardinal; // previous match - MatchAvailable: Boolean; // set if previous match exists + MatchAvailable: boolean; // set if previous match exists StringStart: Cardinal; // start of string to insert MatchStart: Cardinal; // start of matching string Lookahead: Cardinal; // number of valid bytes ahead in window @@ -635,11 +638,11 @@ type DistanceDescriptor: TTreeDescriptor; // Descriptor for distance tree BitLengthDescriptor: TTreeDescriptor; // Descriptor for bit length tree - BitLengthCounts: array[0..MAX_BITS] of Word; // number of codes at each bit length for an optimal tree + BitLengthCounts: array[0..MAX_BITS] of word; // number of codes at each bit length for an optimal tree - Heap: array[0..2 * L_CODES] of Integer; // heap used to build the Huffman trees - HeapLength: Integer; // number of elements in the heap - HeapMaximum: Integer; // element of largest frequency + Heap: array[0..2 * L_CODES] of integer; // heap used to build the Huffman trees + HeapLength: integer; // number of elements in the heap + HeapMaximum: integer; // element of largest frequency // The sons of Heap[N] are Heap[2 * N] and Heap[2 * N + 1]. Heap[0] is not used. // The same heap array is used to build all trees. @@ -669,13 +672,13 @@ type // Buffer for distances. To simplify the code, DistanceBuffer and LiteralBuffer have // the same number of elements. To use different lengths, an extra flag array would be necessary. DistanceBuffer: TPAWord; - OptimalLength: Integer; // bit length of current block with optimal trees - StaticLength: Integer; // bit length of current block with static trees - CompressedLength: Integer; // total bit length of compressed file + OptimalLength: integer; // bit length of current block with optimal trees + StaticLength: integer; // bit length of current block with static trees + CompressedLength: integer; // total bit length of compressed file Matches: Cardinal; // number of string matches in current block - LastEOBLength: Integer; // bit length of EOB code for last block - BitsBuffer: Word; // Output buffer. Bits are inserted starting at the bottom (least significant bits). - ValidBits: Integer; // Number of valid bits in BitsBuffer. All Bits above the last valid bit are always zero. + LastEOBLength: integer; // bit length of EOB code for last block + BitsBuffer: word; // Output buffer. Bits are inserted starting at the bottom (least significant bits). + ValidBits: integer; // Number of valid bits in BitsBuffer. All Bits above the last valid bit are always zero. end; //----------------- Huffmann trees @@ -853,7 +856,7 @@ const ); // first normalized distance for each code (0 = distance of 1) - BaseDistance: array[0..D_CODES - 1] of Integer = ( + BaseDistance: array[0..D_CODES - 1] of integer = ( 0, 1, 2, 3, 4, 6, 8, 12, 16, 24, 32, 48, 64, 96, 128, 192, 256, 384, 512, 768, 1024, 1536, 2048, 3072, 4096, 6144, 8192, 12288, 16384, 24576 @@ -867,17 +870,19 @@ const REPZ_11_138 = 18; // repeat a zero length 11-138 times (7 Bits of repeat count) // extra bits for each length code - ExtraLengthBits: array[0..LENGTH_CODES - 1] of Integer = ( - 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5, 0 + ExtraLengthBits: array[0..LENGTH_CODES - 1] of integer = ( + 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, + 4, 4, 4, 4, 5, 5, 5, 5, 0 ); // extra bits for each distance code - ExtraDistanceBits: array[0..D_CODES - 1] of Integer = ( - 0, 0, 0, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, 7, 7, 8, 8, 9, 9, 10 ,10, 11, 11, 12, 12, 13, 13 + ExtraDistanceBits: array[0..D_CODES - 1] of integer = ( + 0, 0, 0, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, 7, 7, 8, 8, + 9, 9, 10 ,10, 11, 11, 12, 12, 13, 13 ); // extra bits for each bit length code - ExtraBitLengthBits: array[0..BL_CODES - 1] of Integer = ( + ExtraBitLengthBits: array[0..BL_CODES - 1] of integer = ( 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 3, 7 ); @@ -918,10 +923,11 @@ const //----------------- Inflate support const - InflateMask: array[0..16] of Cardinal = ($0000, $0001, $0003, $0007, $000F, - $001F, $003F, $007F, $00FF, $01FF, $03FF, $07FF, $0FFF, $1FFF, $3FFF, $7FFF, $FFFF); + InflateMask: array[0..16] of Cardinal = ( + $0000, $0001, $0003, $0007, $000F, $001F, $003F, $007F, + $00FF, $01FF, $03FF, $07FF, $0FFF, $1FFF, $3FFF, $7FFF, $FFFF); -function InflateFlush(var S: TInflateBlocksState; var Z: TZState; R: Integer): Integer; +function InflateFlush(var S: TInflateBlocksState; var Z: TZState; R: integer): integer; // copies as much as possible from the sliding window to the output area var N: Cardinal; @@ -952,7 +958,8 @@ begin Inc(Q, N); // see if more to copy at beginning of window - if Q = S.zend then begin + if Q = S.zend then + begin // wrap pointers Q := S.Window; if S.write = S.zend then @@ -976,11 +983,11 @@ begin Z.NextOutput := P; S.Read := Q; - Result := R; + result := R; end; function InflateFast(LiteralBits, DistanceBits: Cardinal; TL, TD: PInflateHuft; - var S: TInflateBlocksState; var Z: TZState): Integer; + var S: TInflateBlocksState; var Z: TZState): integer; // Called with number of bytes left to write in window at least 258 (the maximum string length) and number of input // bytes available at least ten. The ten bytes are six bytes for the longest length/distance pair plus four bytes for // overloading the bit buffer. @@ -1017,7 +1024,8 @@ begin // assume called with (M >= 258) and (N >= 10) repeat // get literal/length Code - while K < 20 do begin + while K < 20 do + begin Dec(N); BitsBuffer := BitsBuffer or (cardinal(P^) shl K); Inc(K, 8); @@ -1027,7 +1035,8 @@ begin Temp := @PHuftField(TL)[BitsBuffer and ml]; Extra := Temp.exop; - if Extra = 0 then begin + if Extra = 0 then + begin BitsBuffer := BitsBuffer shr Temp.Bits; Dec(K, Temp.Bits); Q^ := Temp.Base; @@ -1043,14 +1052,16 @@ begin BitsBuffer := BitsBuffer shr Temp.Bits; Dec(K, Temp.Bits); - if (Extra and 16) <> 0 then begin + if (Extra and 16) <> 0 then + begin // get extra bits for length Extra := Extra and 15; C := Temp.Base + (BitsBuffer and InflateMask[Extra]); BitsBuffer := BitsBuffer shr Extra; Dec(K, Extra); // decode distance base of block to copy - while K < 15 do begin + while K < 15 do + begin Dec(N); BitsBuffer := BitsBuffer or (Cardinal(P^) shl K); Inc(P); @@ -1062,10 +1073,12 @@ begin repeat BitsBuffer := BitsBuffer shr Temp.Bits; Dec(K, Temp.Bits); - if (Extra and 16) <> 0 then begin + if (Extra and 16) <> 0 then + begin // get extra bits to add to distance base Extra := Extra and 15; - while K < Extra do begin + while K < Extra do + begin Dec(N); BitsBuffer := BitsBuffer or (Cardinal(P^) shl K); Inc(P); @@ -1077,19 +1090,22 @@ begin // do the copy Dec(M, C); // offset before Dest - if (PtrUInt(Q) - PtrUInt(S.Window)) >= D then begin + if (PtrUInt(Q) - PtrUInt(S.Window)) >= D then + begin // copy without extra R := Q; Dec(R, D); end - else begin + else + begin // offset after destination, // bytes from offset to end Extra := D - (PtrUInt(Q) - PtrUInt(S.Window)); R := S.zend; // pointer to offset Dec(R, Extra); - if C > Extra then begin + if C > Extra then + begin // copy to end of window Dec(C, Extra); MoveWithOverlap(R, Q, Extra); @@ -1104,11 +1120,13 @@ begin inc(Q,Extra); Break; end - else if (Extra and 64) = 0 then begin + else if (Extra and 64) = 0 then + begin Inc(Temp, Temp.Base + (BitsBuffer and InflateMask[Extra])); Extra := Temp.exop; end - else begin + else + begin C := Z.AvailableInput - N; if (K shr 3) < C then C := K shr 3; @@ -1121,17 +1139,19 @@ begin Inc(Z.TotalInput, PtrUInt(P) - PtrUInt(Z.NextInput)); Z.NextInput := P; S.write := Q; - Result := Z_DATA_ERROR; - Exit; + result := Z_DATA_ERROR; + exit; end; until False; Break; end; - if (Extra and 64) = 0 then begin + if (Extra and 64) = 0 then + begin Inc(Temp, Temp.Base + (BitsBuffer and InflateMask[Extra])); Extra := Temp.exop; - if Extra = 0 then begin + if Extra = 0 then + begin BitsBuffer := BitsBuffer shr Temp.Bits; Dec(K, Temp.Bits); Q^ := Temp.Base; @@ -1140,7 +1160,8 @@ begin Break; end; end - else if (Extra and 32) <> 0 then begin + else if (Extra and 32) <> 0 then + begin C := Z.AvailableInput - N; if (K shr 3) < C then C := K shr 3; @@ -1153,10 +1174,11 @@ begin Inc(Z.TotalInput, PtrUInt(P) - PtrUInt(Z.NextInput)); Z.NextInput := P; S.write := Q; - Result := Z_STREAM_END; - Exit; + result := Z_STREAM_END; + exit; end - else begin + else + begin C := Z.AvailableInput - N; if (K shr 3) < C then C := K shr 3; @@ -1169,8 +1191,8 @@ begin Inc(Z.TotalInput, PtrUInt(P) - PtrUInt(Z.NextInput)); Z.NextInput := P; S.write := Q; - Result := Z_DATA_ERROR; - Exit; + result := Z_DATA_ERROR; + exit; end; until False; if (M < 258) or (N < 10) then @@ -1190,21 +1212,21 @@ begin Inc(Z.TotalInput, PtrUInt(P) - PtrUInt(Z.NextInput)); Z.NextInput := P; S.write := Q; - Result := Z_OK; + result := Z_OK; end; function InflateCodesNew(LiteralBits: Cardinal; DistanceBits: Cardinal; TL, TD: PInflateHuft; var Z: TZState): PInflateCodesState; begin GetMem(result, SizeOf(TInflateCodesState)); - Result.Mode := icmStart; - Result.LiteralTreeBits := LiteralBits; - Result.DistanceTreeBits := DistanceBits; - Result.LiteralTree := TL; - Result.DistanceTree := TD; + result.Mode := icmStart; + result.LiteralTreeBits := LiteralBits; + result.DistanceTreeBits := DistanceBits; + result.LiteralTree := TL; + result.DistanceTree := TD; end; -function InflateCodes(var S: TInflateBlocksState; var Z: TZState; R: Integer): Integer; +function InflateCodes(var S: TInflateBlocksState; var Z: TZState; R: integer): integer; var J: Cardinal; // temporary storage Temp: PInflateHuft; @@ -1232,11 +1254,13 @@ begin M := PtrUInt(S.zend) - PtrUInt(Q); // process input and output based on current state - while True do begin + while True do + begin case C.Mode of icmStart: begin - if (M >= 258) and (N >= 10) then begin + if (M >= 258) and (N >= 10) then + begin S.bitb := BitsBuffer; S.bitk := K; Z.AvailableInput := N; @@ -1255,7 +1279,8 @@ begin M := PtrUInt(S.read) - PtrUInt(Q) - 1 else M := PtrUInt(S.zend) - PtrUInt(Q); - if R <> Z_OK then begin + if R <> Z_OK then + begin if R = Z_STREAM_END then C.mode := icmWash else @@ -1270,18 +1295,20 @@ begin icmLen: // I: get length/literal/eob next begin J := C.sub.Code.need; - while K < J do begin + while K < J do + begin if N <> 0 then R := Z_OK - else begin + else + begin S.bitb := BitsBuffer; S.bitk := K; Z.AvailableInput := N; Inc(Z.TotalInput, PtrUInt(P) - PtrUInt(Z.NextInput)); Z.NextInput := P; S.write := Q; - Result := InflateFlush(S, Z, R); - Exit; + result := InflateFlush(S, Z, R); + exit; end; Dec(N); BitsBuffer := BitsBuffer or (Cardinal(P^) shl K); @@ -1295,26 +1322,30 @@ begin Extra := Temp.exop; // literal - if Extra = 0 then begin + if Extra = 0 then + begin C.sub.lit := Temp.Base; C.mode := icmLit; Continue; end; // length - if (Extra and 16) <> 0 then begin + if (Extra and 16) <> 0 then + begin C.sub.copy.get := Extra and 15; C.Len := Temp.Base; C.mode := icmLenNext; Continue; end; // next table - if (Extra and 64) = 0 then begin + if (Extra and 64) = 0 then + begin C.sub.Code.need := Extra; C.sub.Code.Tree := @PHuftField(Temp)[Temp.Base]; Continue; end; // end of block - if (Extra and 32) <> 0 then begin + if (Extra and 32) <> 0 then + begin C.mode := icmWash; Continue; end; @@ -1327,24 +1358,26 @@ begin Inc(Z.TotalInput, PtrUInt(P) - PtrUInt(Z.NextInput)); Z.NextInput := P; S.write := Q; - Result := InflateFlush(S, Z, R); - Exit; + result := InflateFlush(S, Z, R); + exit; end; icmLenNext: // I: getting length extra (have base) begin J := C.sub.copy.get; - while K < J do begin + while K < J do + begin if N <> 0 then R := Z_OK - else begin + else + begin S.bitb := BitsBuffer; S.bitk := K; Z.AvailableInput := N; Inc(Z.TotalInput, PtrUInt(P) - PtrUInt(Z.NextInput)); Z.NextInput := P; S.write := Q; - Result := InflateFlush(S, Z, R); - Exit; + result := InflateFlush(S, Z, R); + exit; end; Dec(N); BitsBuffer := BitsBuffer or (Cardinal(P^) shl K); @@ -1361,18 +1394,20 @@ begin icmDistance: // I: get distance next begin J := C.sub.Code.need; - while K < J do begin + while K < J do + begin if N <> 0 then R := Z_OK - else begin + else + begin S.bitb := BitsBuffer; S.bitk := K; Z.AvailableInput := N; Inc(Z.TotalInput, PtrUInt(P) - PtrUInt(Z.NextInput)); Z.NextInput := P; S.write := Q; - Result := InflateFlush(S, Z, R); - Exit; + result := InflateFlush(S, Z, R); + exit; end; Dec(N); BitsBuffer := BitsBuffer or (PtrUInt(P^) shl K); @@ -1385,14 +1420,16 @@ begin Extra := Temp.exop; // distance - if (Extra and 16) <> 0 then begin + if (Extra and 16) <> 0 then + begin C.sub.copy.get := Extra and 15; C.sub.copy.Distance := Temp.Base; C.mode := icmDistExt; Continue; end; // next table - if (Extra and 64) = 0 then begin + if (Extra and 64) = 0 then + begin C.sub.Code.need := Extra; C.sub.Code.Tree := @PHuftField(Temp)[Temp.Base]; Continue; @@ -1406,24 +1443,26 @@ begin Inc(Z.TotalInput, PtrUInt(P) - PtrUInt(Z.NextInput)); Z.NextInput := P; S.write := Q; - Result := InflateFlush(S, Z, R); - Exit; + result := InflateFlush(S, Z, R); + exit; end; icmDistExt: // I: getting distance extra begin J := C.sub.copy.get; - while K < J do begin + while K < J do + begin if N <> 0 then R := Z_OK - else begin + else + begin S.bitb := BitsBuffer; S.bitk := K; Z.AvailableInput := N; Inc(Z.TotalInput, PtrUInt(P) - PtrUInt(Z.NextInput)); Z.NextInput := P; S.write := Q; - Result := InflateFlush(S, Z, R); - Exit; + result := InflateFlush(S, Z, R); + exit; end; Dec(N); BitsBuffer := BitsBuffer or (Cardinal(P^) shl K); @@ -1439,21 +1478,26 @@ begin begin F := Q; Dec(F, C.sub.copy.Distance); - if (PtrUInt(Q) - PtrUInt(S.Window)) < C.sub.copy.Distance then begin + if (PtrUInt(Q) - PtrUInt(S.Window)) < C.sub.copy.Distance then + begin F := S.zend; Dec(F, C.sub.copy.Distance - (PtrUInt(Q) - PtrUInt(S.Window))); end; - while C.Len <> 0 do begin - if M = 0 then begin - if (Q = S.zend) and (S.read <> S.Window) then begin + while C.Len <> 0 do + begin + if M = 0 then + begin + if (Q = S.zend) and (S.read <> S.Window) then + begin Q := S.Window; if PtrUInt(Q) < PtrUInt(S.read) then M := PtrUInt(S.read) - PtrUInt(Q) - 1 else M := PtrUInt(S.zend) - PtrUInt(Q); end; - if M = 0 then begin + if M = 0 then + begin S.write := Q; R := InflateFlush(S, Z, R); Q := S.write; @@ -1461,22 +1505,24 @@ begin M := PtrUInt(S.read) - PtrUInt(Q) - 1 else M := PtrUInt(S.zend) - PtrUInt(Q); - if (Q = S.zend) and (S.read <> S.Window) then begin + if (Q = S.zend) and (S.read <> S.Window) then + begin Q := S.Window; if PtrUInt(Q) < PtrUInt(S.read) then M := PtrUInt(S.read) - PtrUInt(Q) - 1 else M := PtrUInt(S.zend) - PtrUInt(Q); end; - if M = 0 then begin + if M = 0 then + begin S.bitb := BitsBuffer; S.bitk := K; Z.AvailableInput := N; Inc(Z.TotalInput, PtrUInt(P) - PtrUInt(Z.NextInput)); Z.NextInput := P; S.write := Q; - Result := InflateFlush(S, Z, R); - Exit; + result := InflateFlush(S, Z, R); + exit; end; end; end; @@ -1493,15 +1539,18 @@ begin end; icmLit: // O: got literal, waiting for output space begin - if M = 0 then begin - if (Q = S.zend) and (S.read <> S.Window) then begin + if M = 0 then + begin + if (Q = S.zend) and (S.read <> S.Window) then + begin Q := S.Window; if PtrUInt(Q) < PtrUInt(S.read) then M := PtrUInt(S.read) - PtrUInt(Q) - 1 else M := PtrUInt(S.zend) - PtrUInt(Q); end; - if M = 0 then begin + if M = 0 then + begin S.write := Q; R := InflateFlush(S, Z, R); Q := S.write; @@ -1509,22 +1558,24 @@ begin M := PtrUInt(S.read) - PtrUInt(Q) - 1 else M := PtrUInt(S.zend) - PtrUInt(Q); - if (Q = S.zend) and (S.read <> S.Window) then begin + if (Q = S.zend) and (S.read <> S.Window) then + begin Q := S.Window; if PtrUInt(Q) < PtrUInt(S.read) then M := PtrUInt(S.read) - PtrUInt(Q) - 1 else M := PtrUInt(S.zend) - PtrUInt(Q); end; - if M = 0 then begin + if M = 0 then + begin S.bitb := BitsBuffer; S.bitk := K; Z.AvailableInput := N; Inc(Z.TotalInput, PtrUInt(P) - PtrUInt(Z.NextInput)); Z.NextInput := P; S.write := Q; - Result := InflateFlush(S, Z, R); - Exit; + result := InflateFlush(S, Z, R); + exit; end; end; end; @@ -1537,7 +1588,8 @@ begin icmWash: // O: got eob, possibly More output begin // return unused byte, if any - if K > 7 then begin + if K > 7 then + begin Dec(K, 8); Inc(N); Dec(P); @@ -1550,15 +1602,16 @@ begin M := PtrUInt(S.read) - PtrUInt(Q) - 1 else M := PtrUInt(S.zend) - PtrUInt(Q); - if S.read <> S.write then begin + if S.read <> S.write then + begin S.bitb := BitsBuffer; S.bitk := K; Z.AvailableInput := N; Inc(Z.TotalInput, PtrUInt(P) - PtrUInt(Z.NextInput)); Z.NextInput := P; S.write := Q; - Result := InflateFlush(S, Z, R); - Exit; + result := InflateFlush(S, Z, R); + exit; end; C.mode := icmZEnd; end; @@ -1571,8 +1624,8 @@ begin Inc(Z.TotalInput, PtrUInt(P) - PtrUInt(Z.NextInput)); Z.NextInput := P; S.write := Q; - Result := InflateFlush(S, Z, R); - Exit; + result := InflateFlush(S, Z, R); + exit; end; icmBadCode: // X: got error begin @@ -1583,8 +1636,8 @@ begin Inc(Z.TotalInput, PtrUInt(P) - PtrUInt(Z.NextInput)); Z.NextInput := P; S.write := Q; - Result := InflateFlush(S, Z, R); - Exit; + result := InflateFlush(S, Z, R); + exit; end; else begin @@ -1595,13 +1648,11 @@ begin Inc(Z.TotalInput, PtrUInt(P) - PtrUInt(Z.NextInput)); Z.NextInput := P; S.write := Q; - Result := InflateFlush(S, Z, R); - Exit; + result := InflateFlush(S, Z, R); + exit; end; end; end; - - Result := Z_STREAM_ERROR; end; type @@ -1617,7 +1668,8 @@ const // Tables for deflate from PKZIP'S appnote.txt // copy lengths for literal codes 257..285 (actually lengths - 2; also see note #13 above about 258) CopyLengths: TDeflateLengths = (3, 4, 5, 6, 7, 8, 9, 10, 11, 13, 15, - 17, 19, 23, 27, 31, 35, 43, 51, 59, 67, 83, 99, 115, 131, 163, 195, 227, 258, 0, 0); + 17, 19, 23, 27, 31, 35, 43, 51, 59, 67, 83, 99, 115, 131, 163, 195, + 227, 258, 0, 0); INVALID_CODE = 112; // extra bits for literal codes 257..285 CopyLiteralExtra: TDeflateLengths = (0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, @@ -1667,7 +1719,7 @@ const function BuildHuffmanTables(const B: TACardinal; N, S: Cardinal; const D, Extra: TDeflateLengths; Temp: PPInflateHuft; var M: Cardinal; HP: PHuftField; - var HN: Cardinal; var V: TDeflateWorkArea): Integer; + var HN: Cardinal; var V: TDeflateWorkArea): integer; // Given a list of code lengths and a maximum table size, make a set of tables to decode that set of codes. Returns Z_OK // on success, Z_BUF_ERROR if the given code set is incomplete (the tables are still built in this case), Z_DATA_ERROR @@ -1690,20 +1742,20 @@ function BuildHuffmanTables(const B: TACardinal; N, S: Cardinal; const D, var A: Cardinal; // counter for codes of length K F: Cardinal; // I repeats in table every F entries - G: Integer; // maximum code Length - H: Integer; // table Level + G: integer; // maximum code Length + H: integer; // table Level I: Cardinal; // counter, current code J: Cardinal; // counter - K: Integer; // number of bits in current code - L: Integer; // bits per table (returned in M) + K: integer; // number of bits in current code + L: integer; // bits per table (returned in M) Mask: Cardinal; // (1 shl W) - 1, to avoid cc - O bug on HP P: TPCardinal; // pointer into C[], B[], or V[] Q: PInflateHuft; // points to current table R: TInflateHuft; // table entry for structure assignment XP: TPCardinal; // pointer into X - Y: Integer; // number of dummy codes added + Y: integer; // number of dummy codes added Z: Cardinal; // number of entries in current table - W: Integer; // bits before this table = (L * H) + W: integer; // bits before this table = (L * H) C: array[0..BMAX] of Cardinal; // bit length count table U: array[0..BMAX - 1] of PInflateHuft; // table stack X: array[0..BMAX] of Cardinal; // bit offsets, then code stack @@ -1716,11 +1768,12 @@ begin Inc(C[B[I]]); // nil input -> all zero length codes - if C[0] = N then begin + if C[0] = N then + begin Temp^ := nil; M := 0; - Result := Z_OK; - Exit; + result := Z_OK; + exit; end; // find minimum and maximum length, bound [M] by those @@ -1743,28 +1796,32 @@ begin // adjust last length count to fill out codes if needed Y := 1 shl J; - while J < I do begin + while J < I do + begin Dec(Y, C[J]); - if Y < 0 then begin + if Y < 0 then + begin // bad input: more codes than bits - Result := Z_DATA_ERROR; - Exit; + result := Z_DATA_ERROR; + exit; end; Inc(J); Y := Y shl 1; end; Dec(Y, C[I]); - if Y < 0 then begin + if Y < 0 then + begin // bad input: more codes than bits - Result := Z_DATA_ERROR; - Exit; + result := Z_DATA_ERROR; + exit; end; Inc(C[I], Y); // generate starting offsets into the value table for each length X[1] := 0; J := 0; - for I := 1 to G - 1 do begin + for I := 1 to G - 1 do + begin inc(J, C[I]); X[I + 1] := J; end; @@ -1839,8 +1896,8 @@ begin Z := 1 shl J; // allocate new table (note: doesn't matter for fixed) if HN + Z > MANY then begin - Result := Z_MEM_ERROR; - Exit; + result := Z_MEM_ERROR; + exit; end; Q := @HP[HN]; @@ -1919,20 +1976,20 @@ begin // Return Z_BUF_ERROR if we were given an incomplete table if (Y <> 0) and (G <> 1) then - Result := Z_BUF_ERROR + result := Z_BUF_ERROR else - Result := Z_OK; + result := Z_OK; end; function InflateTreesBits(var C: TACardinal; var BB: Cardinal; var TB: - PInflateHuft; HP: PHuftField; var Z: TZState): Integer; + PInflateHuft; HP: PHuftField; var Z: TZState): integer; // C holds 19 code lengths // BB - bits tree desired/actual depth // TB - bits tree result // HP - space for trees // Z - for messages var - R: Integer; + R: integer; HN: Cardinal; // hufts used in space V: TDeflateWorkArea; // work area for BuildHuffmanTables begin @@ -1940,12 +1997,12 @@ begin R := BuildHuffmanTables(C, 19, 19, CopyLengths, CopyLiteralExtra, @TB, BB, HP, HN, V); if (R = Z_BUF_ERROR) or (BB = 0) then R := Z_DATA_ERROR; - Result := R; + result := R; end; function InflateTreesDynamic(NL: Cardinal; ND: Cardinal; var C: TACardinal; var LiteralBits: Cardinal; var DistanceBits: Cardinal; var TL: PInflateHuft; - var TD: PInflateHuft; HP: PHuftField; var Z: TZState): Integer; + var TD: PInflateHuft; HP: PHuftField; var Z: TZState): integer; // NL - number of literal/length codes // ND - number of distance codes // C - code lengths @@ -1956,19 +2013,19 @@ function InflateTreesDynamic(NL: Cardinal; ND: Cardinal; var C: TACardinal; // HP - space for trees // Z - for messages var - R: Integer; + R: integer; HN: Cardinal; // hufts used in space V: TDeflateWorkArea; // work area for BuildHuffmanTables begin HN := 0; // allocate work area - Result := Z_OK; + result := Z_OK; // build literal/length tree R := BuildHuffmanTables(C, NL, 257, CopyLengths, CopyLiteralExtra, @TL, LiteralBits, HP, HN, V); if (R <> Z_OK) or (LiteralBits = 0) then begin - Result := R; - Exit; + result := R; + exit; end; // build distance tree R := BuildHuffmanTables(TPACardinal(@C[NL])^, ND, 0, CopyOffsets, CopyExtra, @TD, @@ -1978,7 +2035,7 @@ begin R := Z_DATA_ERROR else if R <> Z_MEM_ERROR then R := Z_DATA_ERROR; - Result := R; + result := R; end; end; @@ -1988,7 +2045,7 @@ const var // build fixed tables only once -> keep them here - FixedBuild: Boolean; + FixedBuild: boolean; FixedTablesMemory: array[0..FIXEDH - 1] of TInflateHuft; FixedLiteralBits: Cardinal; FixedDistanceBits: Cardinal; @@ -1996,9 +2053,9 @@ var FixedDistanceTable: array[0..32 - 1] of TInflateHuft; function InflateTreesFixed(var LiteralBits: Cardinal; var DistanceBits: Cardinal; - var TL, TD: PInflateHuft; var Z: TZState): Integer; + var TL, TD: PInflateHuft; var Z: TZState): integer; var - K: Integer; // temporary variable + K: integer; // temporary variable C: TDeflateWorkArea; // length list for BuildHuffmanTables V: TDeflateWorkArea; // work area for BuildHuffmanTables F: Cardinal; // number of hufts used in FixedTablesMemory @@ -2030,7 +2087,7 @@ begin DistanceBits := FixedDistanceBits; TL := @FixedLiteralTable; TD := @FixedDistanceTable; - Result := Z_OK; + result := Z_OK; end; @@ -2091,7 +2148,7 @@ var begin GetMem(S, SizeOf(TInflateBlocksState)); if S = nil then - Result := S + result := S else try GetMem(S.hufts, SizeOf(TInflateHuft) * MANY); @@ -2100,7 +2157,7 @@ begin Inc(S.zend, W); S.mode := ibmZType; InflateBlockReset(S^, Z); - Result := S; + result := S; except if Assigned(S.Window) then FreeMem(S.Window); @@ -2111,7 +2168,7 @@ begin end; end; -function InflateBlocks(var S: TInflateBlocksState; var Z: TZState; R: Integer): Integer; +function InflateBlocks(var S: TInflateBlocksState; var Z: TZState; R: integer): integer; // R contains the initial return code var Temp: Cardinal; @@ -2128,7 +2185,7 @@ var I, J, C: Cardinal; CodeState: PInflateCodesState; - function UpdatePointers: Integer; + function UpdatePointers: integer; begin S.bitb := B; S.bitk := K; @@ -2136,7 +2193,7 @@ var Inc(Z.TotalInput, PtrUInt(P) - PtrUInt(Z.NextInput)); Z.NextInput := P; S.write := Q; - Result := InflateFlush(S, Z, R); + result := InflateFlush(S, Z, R); end; begin @@ -2160,8 +2217,8 @@ begin if N <> 0 then R := Z_OK else begin - Result := UpdatePointers; - Exit; + result := UpdatePointers; + exit; end; Dec(N); B := B or (Cardinal(P^) shl K); @@ -2170,7 +2227,7 @@ begin end; Temp := B and 7; - S.last := Boolean(Temp and 1); + S.last := boolean(Temp and 1); case Temp shr 1 of 0: // stored begin @@ -2190,8 +2247,8 @@ begin TL, TD, Z); if S.sub.decode.codes = nil then begin R := Z_MEM_ERROR; - Result := UpdatePointers; - Exit; + result := UpdatePointers; + exit; end; B := B shr 3; Dec(K, 3); @@ -2209,8 +2266,8 @@ begin Dec(K, 3); S.mode := ibmBlockBad; R := Z_DATA_ERROR; - Result := UpdatePointers; - Exit; + result := UpdatePointers; + exit; end; end; end; @@ -2220,8 +2277,8 @@ begin if N <> 0 then R := Z_OK else begin - Result := UpdatePointers; - Exit; + result := UpdatePointers; + exit; end; Dec(N); B := B or (Cardinal(P^) shl K); @@ -2232,8 +2289,8 @@ begin if (((not B) shr 16) and $FFFF) <> (B and $FFFF) then begin S.mode := ibmBlockBad; R := Z_DATA_ERROR; - Result := UpdatePointers; - Exit; + result := UpdatePointers; + exit; end; S.sub.left := B and $FFFF; K := 0; @@ -2248,8 +2305,8 @@ begin ibmStored: begin if N = 0 then begin - Result := UpdatePointers; - Exit; + result := UpdatePointers; + exit; end; if M = 0 then begin @@ -2278,8 +2335,8 @@ begin end; if M = 0 then begin - Result := UpdatePointers; - Exit; + result := UpdatePointers; + exit; end; end; end; @@ -2309,8 +2366,8 @@ begin if N <> 0 then R := Z_OK else begin - Result := UpdatePointers; - Exit; + result := UpdatePointers; + exit; end; Dec(N); B := B or (Cardinal(P^) shl K); @@ -2323,8 +2380,8 @@ begin if ((Temp and $1F) > 29) or (((Temp shr 5) and $1F) > 29) then begin S.mode := ibmBlockBad; R := Z_DATA_ERROR; - Result := UpdatePointers; - Exit; + result := UpdatePointers; + exit; end; Temp := 258 + (Temp and $1F) + ((Temp shr 5) and $1F); GetMem(S.sub.trees.blens, Temp * SizeOf(Cardinal)); @@ -2341,8 +2398,8 @@ begin if N <> 0 then R := Z_OK else begin - Result := UpdatePointers; - Exit; + result := UpdatePointers; + exit; end; Dec(N); B := B or (Cardinal(P^) shl K); @@ -2367,8 +2424,8 @@ begin R := Temp; if R = Z_DATA_ERROR then S.mode := ibmBlockBad; - Result := UpdatePointers; - Exit; + result := UpdatePointers; + exit; end; S.sub.trees.Index := 0; S.mode := ibmDistTree; @@ -2384,8 +2441,8 @@ begin if N <> 0 then R := Z_OK else begin - Result := UpdatePointers; - Exit; + result := UpdatePointers; + exit; end; Dec(N); B := B or (Cardinal(P^) shl K); @@ -2419,8 +2476,8 @@ begin if N <> 0 then R := Z_OK else begin - Result := UpdatePointers; - Exit; + result := UpdatePointers; + exit; end; Dec(N); B := B or (Cardinal(P^) shl K); @@ -2442,8 +2499,8 @@ begin FreeMem(S.sub.trees.blens); S.mode := ibmBlockBad; R := Z_DATA_ERROR; - Result := UpdatePointers; - Exit; + result := UpdatePointers; + exit; end; if C = 16 then @@ -2467,17 +2524,17 @@ begin S.sub.trees.blens^, LiteralBits, DistanceBits, TL, TD, S.hufts, Z); FreeMem(S.sub.trees.blens); if Temp <> Z_OK then begin - if Integer(Temp) = Z_DATA_ERROR then + if integer(Temp) = Z_DATA_ERROR then S.mode := ibmBlockBad; R := Temp; - Result := UpdatePointers; - Exit; + result := UpdatePointers; + exit; end; CodeState := InflateCodesNew(LiteralBits, DistanceBits, TL, TD, Z); if CodeState = nil then begin R := Z_MEM_ERROR; - Result := UpdatePointers; - Exit; + result := UpdatePointers; + exit; end; S.sub.decode.codes := CodeState; S.mode := ibmCodes; @@ -2494,8 +2551,8 @@ begin R := InflateCodes(S, Z, R); if R <> Z_STREAM_END then begin - Result := InflateFlush(S, Z, R); - Exit; + result := InflateFlush(S, Z, R); + exit; end; R := Z_OK; Freemem(S.sub.decode.codes); @@ -2521,27 +2578,27 @@ begin R := InflateFlush(S, Z, R); Q := S.write; if S.read <> S.write then begin - Result := UpdatePointers; - Exit; + result := UpdatePointers; + exit; end; S.mode := ibmBlockDone; end; ibmBlockDone: begin R := Z_STREAM_END; - Result := UpdatePointers; - Exit; + result := UpdatePointers; + exit; end; ibmBlockBad: begin R := Z_DATA_ERROR; - Result := UpdatePointers; - Exit; + result := UpdatePointers; + exit; end; else R := Z_STREAM_ERROR; - Result := UpdatePointers; - Exit; + result := UpdatePointers; + exit; end; // case S.mode of end; end; @@ -2596,7 +2653,7 @@ function CompressMem(src, dst: pointer; srcLen, dstLen: integer): integer; if (Scan^ <> Match^) then Break; until (PtrUInt(Scan) >= PtrUInt(StrEnd)); - result := MAX_MATCH - Integer(PtrUInt(StrEnd) - PtrUInt(Scan)); + result := MAX_MATCH - integer(PtrUInt(StrEnd) - PtrUInt(Scan)); end; const @@ -2676,9 +2733,9 @@ function CompressMem(src, dst: pointer; srcLen, dstLen: integer): integer; until (CurrentMatch <= Limit) or (ChainLength = 0); if BestLen <= S.Lookahead then - Result := BestLen + result := BestLen else - Result := S.Lookahead; + result := S.Lookahead; end; procedure FillWindow(var S: TDeflateState); @@ -2688,7 +2745,7 @@ function CompressMem(src, dst: pointer; srcLen, dstLen: integer): integer; // On exit at least one byte has been read, or AvailableInput = 0. Reads are performed for at least two bytes (required // for the zip translate_eol option -> not supported here). - function ReadBuffer(ZState: PZState; Buffer: PByte; Size: Cardinal): Integer; + function ReadBuffer(ZState: PZState; Buffer: PByte; Size: Cardinal): integer; // Reads a new buffer from the current input stream, updates the Adler32 and total number of bytes read. All Deflate // input goes through this function so some applications may wish to modify it to avoid allocating a large // ZState.NextInput buffer and copying from it (see also FlushPending). @@ -2699,14 +2756,14 @@ function CompressMem(src, dst: pointer; srcLen, dstLen: integer): integer; if Len > Size then Len := Size; if Len = 0 then begin - Result := 0; - Exit; + result := 0; + exit; end; Dec(ZState.AvailableInput, Len); Move(ZState.NextInput^, Buffer^, Len); Inc(ZState.NextInput, Len); Inc(ZState.TotalInput, Len); - Result := Len; + result := Len; end; var @@ -2715,7 +2772,7 @@ function CompressMem(src, dst: pointer; srcLen, dstLen: integer): integer; More: Cardinal; // amount of free space at the end of the window begin repeat - More := S.CurrentWindowSize - Integer(S.Lookahead) - Integer(S.StringStart); + More := S.CurrentWindowSize - integer(S.Lookahead) - integer(S.StringStart); if (More = 0) and (S.StringStart = 0) and (S.Lookahead = 0) then More := S.WindowSize else if More = Cardinal(-1) then begin @@ -2729,7 +2786,7 @@ function CompressMem(src, dst: pointer; srcLen, dstLen: integer): integer; Dec(S.MatchStart, S.WindowSize); Dec(S.StringStart, S.WindowSize); // we now have StringStart >= MaxDistance - Dec(S.BlockStart, Integer(S.WindowSize)); + Dec(S.BlockStart, integer(S.WindowSize)); // Slide the hash table (could be avoided with 32 bit values at the expense of memory usage). We slide even when // Level = 0 to keep the hash table consistent if we switch back to Level > 0 later. (Using Level 0 permanently @@ -2757,7 +2814,7 @@ function CompressMem(src, dst: pointer; srcLen, dstLen: integer): integer; end; if S.ZState.AvailableInput = 0 then - Exit; + exit; // If there was no sliding: // StringStart <= S.WindowSize + MaxDistance - 1 and Lookahead <= MIN_LOOKAHEAD - 1 and @@ -2785,7 +2842,7 @@ function CompressMem(src, dst: pointer; srcLen, dstLen: integer): integer; procedure InitializeBlock(var S: TDeflateState); var - N: Integer; + N: integer; begin // initialize the trees for N := 0 to L_CODES - 1 do @@ -2801,7 +2858,7 @@ function CompressMem(src, dst: pointer; srcLen, dstLen: integer): integer; S.LastLiteral := 0; end; - procedure FlushBlockOnly(var S: TDeflateState; EOF: Boolean); + procedure FlushBlockOnly(var S: TDeflateState; EOF: boolean); // Flushs the current block with given end-of-file flag. // StringStart must be set to the end of the current match. @@ -2832,7 +2889,7 @@ function CompressMem(src, dst: pointer; srcLen, dstLen: integer): integer; end; function TreeFlushBlock(var S: TDeflateState; Buffer: PByte; StoredLength: - Integer; EOF: Boolean): Integer; + integer; EOF: boolean): integer; // Determines the best encoding for the current block: dynamic trees, static trees or store, and outputs the encoded // block. Buffer contains the input block (or nil if too old), StoredLength the length of this block and EOF if this // is the last block. @@ -2841,37 +2898,37 @@ function CompressMem(src, dst: pointer; srcLen, dstLen: integer): integer; procedure BuildTree(var S: TDeflateState; var Descriptor: TTreeDescriptor); // Constructs a Huffman tree and assigns the code bit strings and lengths. // Updates the total bit length for the current block. The field Frequency must be set for all tree elements on entry. - // Result: the fields Len and Code are set to the optimal bit length and corresponding Code. The length OptimalLength + // result: the fields Len and Code are set to the optimal bit length and corresponding Code. The length OptimalLength // is updated; StaticLength is also updated if STree is not nil. The field MaxCode is set. - procedure GenerateCodes(Tree: PTree; MaxCode: Integer; const - BitLengthCounts: array of Word); + procedure GenerateCodes(Tree: PTree; MaxCode: integer; const + BitLengthCounts: array of word); // Generates the codes for a given tree and bit counts (which need not be optimal). // The array BitLengthCounts contains the bit length statistics for the given tree and the field Len is set for all // Tree elements. MaxCode is the largest code with non zero frequency and BitLengthCounts are the number of codes at // each bit length. // On exit the field code is set for all tree elements of non zero code length. - function BitReverse(Code: Word; Len: Integer): Word; + function BitReverse(Code: word; Len: integer): word; // Reverses the first Len bits of Code, using straightforward code (a faster // imMethod would use a table) begin - Result := 0; + result := 0; repeat - Result := Result or (Code and 1); + result := result or (Code and 1); Code := Code shr 1; - Result := Result shl 1; + result := result shl 1; Dec(Len); until Len <= 0; - Result := Result shr 1; + result := result shr 1; end; var - NextCode: array[0..MAX_BITS] of Word; // next code value for each bit length - Code: Word; // running code value - Bits: Integer; // bit Index - N: Integer; // code Index - Len: Integer; + NextCode: array[0..MAX_BITS] of word; // next code value for each bit length + Code: word; // running code value + Bits: integer; // bit Index + N: integer; // code Index + Len: integer; begin Code := 0; // The distribution counts are first used to generate the code values without bit reversal. @@ -2889,12 +2946,12 @@ function CompressMem(src, dst: pointer; srcLen, dstLen: integer): integer; end; end; - procedure RestoreHeap(var S: TDeflateState; const Tree: TTree; K: Integer); + procedure RestoreHeap(var S: TDeflateState; const Tree: TTree; K: integer); // Restores the heap property by moving down tree starting at node K, // exchanging a Node with the smallest of its two sons if necessary, stopping // when the heap property is re-established (each father smaller than its two sons). var - V, J: Integer; + V, J: integer; begin V := S.Heap[K]; J := K shl 1; // left son of K @@ -2927,21 +2984,21 @@ function CompressMem(src, dst: pointer; srcLen, dstLen: integer): integer; TTreeDescriptor); // Computes the optimal bit lengths for a tree and update the total bit length for the current block. // The fields Frequency and dad are set, Heap[HeapMaximum] and above are the tree nodes sorted by increasing frequency. - // Result: The field Len is set to the optimal bit length, the array BitLengthCounts contains the frequencies for each + // result: The field Len is set to the optimal bit length, the array BitLengthCounts contains the frequencies for each // bit length. The length OptimalLength is updated. StaticLength is also updated if STree is not nil. var Tree: PTree; - MaxCode: Integer; + MaxCode: integer; STree: PTree; Extra: TPAInteger; - Base: Integer; - MaxLength: Integer; - H: Integer; // heap Index - N, M: Integer; // iterate over the tree elements - Bits: Word; // bit length - ExtraBits: Integer; - F: Word; // frequency - Overflow: Integer; // number of elements with bit length too large + Base: integer; + MaxLength: integer; + H: integer; // heap Index + N, M: integer; // iterate over the tree elements + Bits: word; // bit length + ExtraBits: integer; + F: word; // frequency + Overflow: integer; // number of elements with bit length too large begin Tree := Descriptor.DynamicTree; MaxCode := Descriptor.MaxCode; @@ -2974,13 +3031,13 @@ function CompressMem(src, dst: pointer; srcLen, dstLen: integer): integer; if N >= Base then ExtraBits := Extra[N - Base]; F := Tree[N].fc.Frequency; - Inc(S.OptimalLength, Integer(F) * (Bits + ExtraBits)); + Inc(S.OptimalLength, integer(F) * (Bits + ExtraBits)); if Assigned(STree) then - Inc(S.StaticLength, Integer(F) * (STree[N].dl.Len + ExtraBits)); + Inc(S.StaticLength, integer(F) * (STree[N].dl.Len + ExtraBits)); end; // This happens for example on obj2 and pic of the Calgary corpus if Overflow = 0 then - Exit; + exit; // find the first bit length which could increase repeat @@ -3011,7 +3068,7 @@ function CompressMem(src, dst: pointer; srcLen, dstLen: integer): integer; Continue; if Tree[M].dl.Len <> Bits then begin Inc(S.OptimalLength, (Bits - Tree[M].dl.Len) * Tree[M].fc.Frequency); - Tree[M].dl.Len := Word(Bits); + Tree[M].dl.Len := word(Bits); end; Dec(N); end; @@ -3021,10 +3078,10 @@ function CompressMem(src, dst: pointer; srcLen, dstLen: integer): integer; var Tree: PTree; STree: PTree; - Elements: Integer; - N, M: Integer; // iterate over heap elements - MaxCode: Integer; // largest code with non zero frequency - Node: Integer; // new node being created + Elements: integer; + N, M: integer; // iterate over heap elements + MaxCode: integer; // largest code with non zero frequency + Node: integer; // new node being created begin Tree := Descriptor.DynamicTree; @@ -3100,8 +3157,8 @@ function CompressMem(src, dst: pointer; srcLen, dstLen: integer): integer; else S.Depth[Node] := Byte(S.Depth[M] + 1); - Tree[M].dl.Dad := Word(Node); - Tree[N].dl.Dad := Word(Node); + Tree[M].dl.Dad := word(Node); + Tree[N].dl.Dad := word(Node); // and insert the new node in the heap S.Heap[1] := Node; Inc(Node); @@ -3125,7 +3182,7 @@ function CompressMem(src, dst: pointer; srcLen, dstLen: integer): integer; if S.ValidBits > 8 then begin S.PendingBuffer[S.Pending] := Byte(S.BitsBuffer and $FF); Inc(S.Pending); - S.PendingBuffer[S.Pending] := Byte(Word(S.BitsBuffer) shr 8); + S.PendingBuffer[S.Pending] := Byte(word(S.BitsBuffer) shr 8); Inc(S.Pending); end else if S.ValidBits > 0 then begin @@ -3136,13 +3193,13 @@ function CompressMem(src, dst: pointer; srcLen, dstLen: integer): integer; S.ValidBits := 0; end; - procedure SendBits(var S: TDeflateState; Value: Word; Length: Integer); + procedure SendBits(var S: TDeflateState; Value: word; Length: integer); // Value contains what is to be sent // Length is the number of bits to send begin // If there's not enough room in BitsBuffer use (valid) bits from BitsBuffer and // (16 - ValidBits) bits from Value, leaving (width - (16 - ValidBits)) unused bits in Value. - if (S.ValidBits > Integer(BufferSize) - Length) then begin + if (S.ValidBits > integer(BufferSize) - Length) then begin S.BitsBuffer := S.BitsBuffer or (Value shl S.ValidBits); S.PendingBuffer[S.Pending] := S.BitsBuffer and $FF; Inc(S.Pending); @@ -3157,23 +3214,23 @@ function CompressMem(src, dst: pointer; srcLen, dstLen: integer): integer; end; end; - procedure SendAllTrees(var S: TDeflateState; lcodes, dcodes, blcodes: Integer); + procedure SendAllTrees(var S: TDeflateState; lcodes, dcodes, blcodes: integer); // Sends the header for a block using dynamic Huffman trees: the counts, the // lengths of the bit length codes, the literal tree and the distance tree. // lcodes must be >= 257, dcodes >= 1 and blcodes >= 4 procedure SendTree(var S: TDeflateState; const Tree: array of TTreeEntry; - MaxCode: Integer); + MaxCode: integer); // Sends the given tree in compressed form using the codes in BitLengthTree. // MaxCode is the tree's largest code of non zero frequency. var - N: Integer; // iterates over all tree elements - PreviousLen: Integer; // last emitted length - CurrentLen: Integer; // length of current code - NextLen: Integer; // length of next code - Count: Integer; // repeat count of the current code - MaxCount: Integer; // max repeat count - MinCount: Integer; // min repeat count + N: integer; // iterates over all tree elements + PreviousLen: integer; // last emitted length + CurrentLen: integer; // length of current code + NextLen: integer; // length of next code + Count: integer; // repeat count of the current code + MaxCount: integer; // max repeat count + MinCount: integer; // min repeat count begin PreviousLen := -1; NextLen := Tree[0].dl.Len; @@ -3231,7 +3288,7 @@ function CompressMem(src, dst: pointer; srcLen, dstLen: integer): integer; end; var - Rank: Integer; + Rank: integer; begin SendBits(S, lcodes - 257, 5); // not +255 as stated in appnote.txt SendBits(S, dcodes - 1, 5); @@ -3242,22 +3299,22 @@ function CompressMem(src, dst: pointer; srcLen, dstLen: integer): integer; SendTree(S, S.DistanceTree, dcodes - 1); end; - function BuildBitLengthTree(var S: TDeflateState): Integer; + function BuildBitLengthTree(var S: TDeflateState): integer; // Constructs the Huffman tree for the bit lengths and returns the Index in BitLengthOrder // of the last bit length code to send. procedure ScanTree(var S: TDeflateState; var Tree: array of TTreeEntry; - MaxCode: Integer); + MaxCode: integer); // Scans a given tree to determine the frequencies of the codes in the bit length tree. // MaxCode is the tree's largest code of non zero frequency. var - N: Integer; // iterates over all tree elements - PreviousLen: Integer; // last emitted length - CurrentLen: Integer; // Length of current code - NextLen: Integer; // length of next code - Count: Integer; // repeat count of the current xode - MaxCount: Integer; // max repeat count - MinCount: Integer; // min repeat count + N: integer; // iterates over all tree elements + PreviousLen: integer; // last emitted length + CurrentLen: integer; // Length of current code + NextLen: integer; // length of next code + Count: integer; // repeat count of the current xode + MaxCount: integer; // max repeat count + MinCount: integer; // min repeat count begin PreviousLen := -1; NextLen := Tree[0].dl.Len; @@ -3269,7 +3326,7 @@ function CompressMem(src, dst: pointer; srcLen, dstLen: integer): integer; MaxCount := 138; MinCount := 3; end; - Tree[MaxCode + 1].dl.Len := Word($FFFF); // guard + Tree[MaxCode + 1].dl.Len := word($FFFF); // guard for N := 0 to MaxCode do begin CurrentLen := NextLen; @@ -3317,21 +3374,21 @@ function CompressMem(src, dst: pointer; srcLen, dstLen: integer): integer; // Determine the number of bit length codes to send. The pkzip format requires that at least 4 bit length codes // be sent. (appnote.txt says 3 but the actual value used is 4.) - for Result := BL_CODES - 1 downto 3 do - if S.BitLengthTree[BitLengthOrder[Result]].dl.Len <> 0 then + for result := BL_CODES - 1 downto 3 do + if S.BitLengthTree[BitLengthOrder[result]].dl.Len <> 0 then Break; // update OptimalLength to include the bit length tree and counts - Inc(S.OptimalLength, 3 * (Result + 1) + 14); + Inc(S.OptimalLength, 3 * (result + 1) + 14); end; procedure TreeStroredBlock(var S: TDeflateState; Buffer: PByte; - StoredLength: Integer; EOF: Boolean); + StoredLength: integer; EOF: boolean); // sends a stored block // Buffer contains the input data, Len the buffer length and EOF is True if this is the last block for a file. procedure CopyBlock(var S: TDeflateState; Buffer: PByte; Len: Cardinal; - Header: Boolean); + Header: boolean); // copies a stored block, storing first the length and its one's complement if requested // Buffer contains the input data, Len the buffer length and Header is True if the block Header must be written too. begin @@ -3339,13 +3396,13 @@ function CompressMem(src, dst: pointer; srcLen, dstLen: integer): integer; S.LastEOBLength := 8; // enough lookahead for Inflate if Header then begin - S.PendingBuffer[S.Pending] := Byte(Word(Len) and $FF); + S.PendingBuffer[S.Pending] := Byte(word(Len) and $FF); Inc(S.Pending); - S.PendingBuffer[S.Pending] := Byte(Word(Len) shr 8); + S.PendingBuffer[S.Pending] := Byte(word(Len) shr 8); Inc(S.Pending); - S.PendingBuffer[S.Pending] := Byte(Word(not Len) and $FF); + S.PendingBuffer[S.Pending] := Byte(word(not Len) and $FF); Inc(S.Pending); - S.PendingBuffer[S.Pending] := Byte(Word(not Len) shr 8); + S.PendingBuffer[S.Pending] := Byte(word(not Len) shr 8); Inc(S.Pending); end; @@ -3359,7 +3416,7 @@ function CompressMem(src, dst: pointer; srcLen, dstLen: integer): integer; begin SendBits(S, (STORED_BLOCK shl 1) + Ord(EOF), 3); // send block type - S.CompressedLength := (S.CompressedLength + 10) and Integer(not 7); + S.CompressedLength := (S.CompressedLength + 10) and integer(not 7); Inc(S.CompressedLength, (StoredLength + 4) shl 3); // copy with header @@ -3371,10 +3428,10 @@ function CompressMem(src, dst: pointer; srcLen, dstLen: integer): integer; // sends the block data compressed using the given Huffman trees var Distance: Cardinal; // distance of matched string - lc: Integer; // match length or unmatched char (if Distance = 0) + lc: integer; // match length or unmatched char (if Distance = 0) I: Cardinal; Code: Cardinal; // the code to send - Extra: Integer; // number of extra bits to send + Extra: integer; // number of extra bits to send begin I := 0; @@ -3422,8 +3479,8 @@ function CompressMem(src, dst: pointer; srcLen, dstLen: integer): integer; end; var - OptimalByteLength, StaticByteLength: Integer; // OptimalLength and StaticLength in bytes - MacBLIndex: Integer; // index of last bit length code of non zero frequency + OptimalByteLength, StaticByteLength: integer; // OptimalLength and StaticLength in bytes + MacBLIndex: integer; // index of last bit length code of non zero frequency begin // construct the literal and distance trees // After this, OptimalLength and StaticLength are the total bit lengths of @@ -3473,27 +3530,27 @@ function CompressMem(src, dst: pointer; srcLen, dstLen: integer): integer; Inc(S.CompressedLength, 7); end; - Result := S.CompressedLength shr 3; + result := S.CompressedLength shr 3; end; begin if S.BlockStart >= 0 then TreeFlushBlock(S, @S.Window[Cardinal(S.BlockStart)], - Integer(S.StringStart) - S.BlockStart, EOF) + integer(S.StringStart) - S.BlockStart, EOF) else - TreeFlushBlock(S, nil, Integer(S.StringStart) - S.BlockStart, EOF); + TreeFlushBlock(S, nil, integer(S.StringStart) - S.BlockStart, EOF); S.BlockStart := S.StringStart; FlushPending(S.ZState^); end; - function TreeTally(var S: TDeflateState; Distance: Cardinal; lc: Cardinal): Boolean; + function TreeTally(var S: TDeflateState; Distance: Cardinal; lc: Cardinal): boolean; // Saves the match info and tallies the frequency counts. Returns True if the current block must be flushed. // Distance is the distance of the matched string and lc either match length minus MIN_MATCH or the unmatch character // (if Distance = 0). var - Code: Word; + Code: word; begin - S.DistanceBuffer[S.LastLiteral] := Word(Distance); + S.DistanceBuffer[S.LastLiteral] := word(Distance); S.LiteralBuffer[S.LastLiteral] := Byte(lc); Inc(S.LastLiteral); if (Distance = 0) then begin @@ -3512,7 +3569,7 @@ function CompressMem(src, dst: pointer; srcLen, dstLen: integer): integer; Inc(S.DistanceTree[Code].fc.Frequency); end; - Result := (S.LastLiteral = S.LiteralBufferSize - 1); + result := (S.LastLiteral = S.LiteralBufferSize - 1); // We avoid equality with LiteralBufferSize because stored blocks are restricted to 64K - 1 bytes. end; @@ -3526,7 +3583,7 @@ function CompressMem(src, dst: pointer; srcLen, dstLen: integer): integer; and S.HashMask; MatchHead := S.Head[S.InsertHash]; S.Previous[(Str) and S.WindowMask] := MatchHead; - S.Head[S.InsertHash] := Word(Str); + S.Head[S.InsertHash] := word(Str); end; const @@ -3537,7 +3594,7 @@ var // We overlay PendingBuffer and DistanceBuffer + LiteralBuffer. This works since the average // output size for (length, distance) codes is <= 24 Bits. HashHead: Cardinal; // head of the hash chain - BlockFlush: Boolean; // set if current block must be flushed + BlockFlush: boolean; // set if current block must be flushed S: TDeflateState; begin result := 0; @@ -3558,14 +3615,14 @@ begin S.HashMask := S.HashSize - 1; S.HashShift := (S.HashBits + MIN_MATCH - 1) div MIN_MATCH; GetMem(S.Window, S.WindowSize * (2 * SizeOf(Byte))); - GetMem(S.Previous, S.WindowSize * SizeOf(Word)); - GetMem(S.Head, S.HashSize * SizeOf(Word)); + GetMem(S.Previous, S.WindowSize * SizeOf(word)); + GetMem(S.Head, S.HashSize * SizeOf(word)); S.LiteralBufferSize := 1 shl (CMemLevel + 6); // 16K elements by default - GetMem(Overlay, S.LiteralBufferSize * (SizeOf(Word) + 2)); + GetMem(Overlay, S.LiteralBufferSize * (SizeOf(word) + 2)); S.PendingBuffer := TPAByte(Overlay); - S.PendingBufferSize := S.LiteralBufferSize * (SizeOf(Word) + 2); + S.PendingBufferSize := S.LiteralBufferSize * (SizeOf(word) + 2); S.DistanceBuffer := @Overlay[S.LiteralBufferSize shr 1]; - S.LiteralBuffer := @S.PendingBuffer[(1 + SizeOf(Word)) * S.LiteralBufferSize]; + S.LiteralBuffer := @S.PendingBuffer[(1 + SizeOf(word)) * S.LiteralBufferSize]; S.PendingOutput := PByte(S.PendingBuffer); S.LiteralDescriptor.DynamicTree := @S.LiteralTree; S.LiteralDescriptor.StaticDescriptor := @StaticLiteralDescriptor; @@ -4323,7 +4380,7 @@ begin inc(Str1); inc(Str2); until false; - Result := ord(C1) - ord(C2); + result := ord(C1) - ord(C2); end else result := 1 // Str2='' @@ -4366,20 +4423,20 @@ end; {$ifdef DELPHI5OROLDER} function DirectoryExists(const Directory: string): boolean; var - Code: Integer; + Code: integer; begin Code := GetFileAttributes(pointer(Directory)); result := (Code <> -1) and (FILE_ATTRIBUTE_DIRECTORY and Code <> 0); end; {$endif} -function ForceDirectories(const Dir: TFileName): Boolean; +function ForceDirectories(const Dir: TFileName): boolean; begin if (Length(Dir) < 3) or DirectoryExists(Dir) or (ExtractFileDir(Dir) = Dir) then // avoid 'x:\' problem. - Result := true + result := true else - Result := ForceDirectories(ExtractFileDir(Dir)) and CreateDir(Dir); + result := ForceDirectories(ExtractFileDir(Dir)) and CreateDir(Dir); end; function TZipRead.CheckFile(aIndex: integer; DestPath: TFileName): boolean; @@ -4678,7 +4735,7 @@ end; initialization {$ifdef DYNAMIC_CRC_TABLE} InitCrc32Tab; -{$endif} +{$endif DYNAMIC_CRC_TABLE} end. diff --git a/contrib/mORMot/SQLite3/DDD/dom/dddDomAuthInterfaces.pas b/contrib/mORMot/SQLite3/DDD/dom/dddDomAuthInterfaces.pas index 0f10edc..e64fe5b 100644 --- a/contrib/mORMot/SQLite3/DDD/dom/dddDomAuthInterfaces.pas +++ b/contrib/mORMot/SQLite3/DDD/dom/dddDomAuthInterfaces.pas @@ -6,7 +6,7 @@ unit dddDomAuthInterfaces; { This file is part of Synopse mORMot framework. - Synopse mORMot framework. Copyright (C) 2020 Arnaud Bouchez + Synopse mORMot framework. Copyright (C) 2022 Arnaud Bouchez Synopse Informatique - https://synopse.info *** BEGIN LICENSE BLOCK ***** @@ -25,7 +25,7 @@ unit dddDomAuthInterfaces; The Initial Developer of the Original Code is Arnaud Bouchez. - Portions created by the Initial Developer are Copyright (C) 2020 + Portions created by the Initial Developer are Copyright (C) 2022 the Initial Developer. All Rights Reserved. Contributor(s): diff --git a/contrib/mORMot/SQLite3/DDD/dom/dddDomCountry.pas b/contrib/mORMot/SQLite3/DDD/dom/dddDomCountry.pas index 3d202ae..ad8353e 100644 --- a/contrib/mORMot/SQLite3/DDD/dom/dddDomCountry.pas +++ b/contrib/mORMot/SQLite3/DDD/dom/dddDomCountry.pas @@ -6,7 +6,7 @@ unit dddDomCountry; { This file is part of Synopse mORMot framework. - Synopse mORMot framework. Copyright (C) 2020 Arnaud Bouchez + Synopse mORMot framework. Copyright (C) 2022 Arnaud Bouchez Synopse Informatique - https://synopse.info *** BEGIN LICENSE BLOCK ***** @@ -25,7 +25,7 @@ unit dddDomCountry; The Initial Developer of the Original Code is Arnaud Bouchez. - Portions created by the Initial Developer are Copyright (C) 2020 + Portions created by the Initial Developer are Copyright (C) 2022 the Initial Developer. All Rights Reserved. Contributor(s): diff --git a/contrib/mORMot/SQLite3/DDD/dom/dddDomEmailInterfaces.pas b/contrib/mORMot/SQLite3/DDD/dom/dddDomEmailInterfaces.pas index 10aea0e..f5cc91d 100644 --- a/contrib/mORMot/SQLite3/DDD/dom/dddDomEmailInterfaces.pas +++ b/contrib/mORMot/SQLite3/DDD/dom/dddDomEmailInterfaces.pas @@ -1,4 +1,4 @@ -unit DomUserInterfaces; +unit dddDomEmailInterfaces; interface diff --git a/contrib/mORMot/SQLite3/DDD/dom/dddDomUserCQRS.pas b/contrib/mORMot/SQLite3/DDD/dom/dddDomUserCQRS.pas index e610f54..c351b43 100644 --- a/contrib/mORMot/SQLite3/DDD/dom/dddDomUserCQRS.pas +++ b/contrib/mORMot/SQLite3/DDD/dom/dddDomUserCQRS.pas @@ -6,7 +6,7 @@ unit dddDomUserCQRS; { This file is part of Synopse mORMot framework. - Synopse mORMot framework. Copyright (C) 2020 Arnaud Bouchez + Synopse mORMot framework. Copyright (C) 2022 Arnaud Bouchez Synopse Informatique - https://synopse.info *** BEGIN LICENSE BLOCK ***** @@ -25,7 +25,7 @@ unit dddDomUserCQRS; The Initial Developer of the Original Code is Arnaud Bouchez. - Portions created by the Initial Developer are Copyright (C) 2020 + Portions created by the Initial Developer are Copyright (C) 2022 the Initial Developer. All Rights Reserved. Contributor(s): diff --git a/contrib/mORMot/SQLite3/DDD/dom/dddDomUserInterfaces.pas b/contrib/mORMot/SQLite3/DDD/dom/dddDomUserInterfaces.pas index 3039138..0bdb15c 100644 --- a/contrib/mORMot/SQLite3/DDD/dom/dddDomUserInterfaces.pas +++ b/contrib/mORMot/SQLite3/DDD/dom/dddDomUserInterfaces.pas @@ -6,7 +6,7 @@ unit dddDomUserInterfaces; { This file is part of Synopse mORMot framework. - Synopse mORMot framework. Copyright (C) 2020 Arnaud Bouchez + Synopse mORMot framework. Copyright (C) 2022 Arnaud Bouchez Synopse Informatique - https://synopse.info *** BEGIN LICENSE BLOCK ***** @@ -25,7 +25,7 @@ unit dddDomUserInterfaces; The Initial Developer of the Original Code is Arnaud Bouchez. - Portions created by the Initial Developer are Copyright (C) 2020 + Portions created by the Initial Developer are Copyright (C) 2022 the Initial Developer. All Rights Reserved. Contributor(s): diff --git a/contrib/mORMot/SQLite3/DDD/dom/dddDomUserTypes.pas b/contrib/mORMot/SQLite3/DDD/dom/dddDomUserTypes.pas index 62e91b4..822f075 100644 --- a/contrib/mORMot/SQLite3/DDD/dom/dddDomUserTypes.pas +++ b/contrib/mORMot/SQLite3/DDD/dom/dddDomUserTypes.pas @@ -6,7 +6,7 @@ unit dddDomUserTypes; { This file is part of Synopse mORMot framework. - Synopse mORMot framework. Copyright (C) 2020 Arnaud Bouchez + Synopse mORMot framework. Copyright (C) 2022 Arnaud Bouchez Synopse Informatique - https://synopse.info *** BEGIN LICENSE BLOCK ***** @@ -25,7 +25,7 @@ unit dddDomUserTypes; The Initial Developer of the Original Code is Arnaud Bouchez. - Portions created by the Initial Developer are Copyright (C) 2020 + Portions created by the Initial Developer are Copyright (C) 2022 the Initial Developer. All Rights Reserved. Contributor(s): diff --git a/contrib/mORMot/SQLite3/DDD/infra/dddInfraApps.pas b/contrib/mORMot/SQLite3/DDD/infra/dddInfraApps.pas index 29afa87..63e5d41 100644 --- a/contrib/mORMot/SQLite3/DDD/infra/dddInfraApps.pas +++ b/contrib/mORMot/SQLite3/DDD/infra/dddInfraApps.pas @@ -6,7 +6,7 @@ unit dddInfraApps; { This file is part of Synopse mORMot framework. - Synopse mORMot framework. Copyright (C) 2020 Arnaud Bouchez + Synopse mORMot framework. Copyright (C) 2022 Arnaud Bouchez Synopse Informatique - https://synopse.info *** BEGIN LICENSE BLOCK ***** @@ -25,7 +25,7 @@ unit dddInfraApps; The Initial Developer of the Original Code is Arnaud Bouchez. - Portions created by the Initial Developer are Copyright (C) 2020 + Portions created by the Initial Developer are Copyright (C) 2022 the Initial Developer. All Rights Reserved. Contributor(s): @@ -595,7 +595,8 @@ type procedure ExecuteDisconnect; procedure ExecuteDisconnectAfterError; procedure ExecuteSocket; - function TrySend(const aFrame: RawByteString; ImmediateDisconnectAfterError: boolean = true): Boolean; virtual; + function TrySend(const aFrame: RawByteString; + ImmediateDisconnectAfterError: boolean = true): Boolean; virtual; // inherited classes could override those methods for process customization procedure InternalExecuteConnected; virtual; procedure InternalExecuteDisconnect; virtual; diff --git a/contrib/mORMot/SQLite3/DDD/infra/dddInfraAuthRest.pas b/contrib/mORMot/SQLite3/DDD/infra/dddInfraAuthRest.pas index d72747d..cfe359a 100644 --- a/contrib/mORMot/SQLite3/DDD/infra/dddInfraAuthRest.pas +++ b/contrib/mORMot/SQLite3/DDD/infra/dddInfraAuthRest.pas @@ -6,7 +6,7 @@ unit dddInfraAuthRest; { This file is part of Synopse mORMot framework. - Synopse mORMot framework. Copyright (C) 2020 Arnaud Bouchez + Synopse mORMot framework. Copyright (C) 2022 Arnaud Bouchez Synopse Informatique - https://synopse.info *** BEGIN LICENSE BLOCK ***** @@ -25,7 +25,7 @@ unit dddInfraAuthRest; The Initial Developer of the Original Code is Arnaud Bouchez. - Portions created by the Initial Developer are Copyright (C) 2020 + Portions created by the Initial Developer are Copyright (C) 2022 the Initial Developer. All Rights Reserved. Contributor(s): diff --git a/contrib/mORMot/SQLite3/DDD/infra/dddInfraEmail.pas b/contrib/mORMot/SQLite3/DDD/infra/dddInfraEmail.pas index d107f62..f3dd27a 100644 --- a/contrib/mORMot/SQLite3/DDD/infra/dddInfraEmail.pas +++ b/contrib/mORMot/SQLite3/DDD/infra/dddInfraEmail.pas @@ -6,7 +6,7 @@ unit dddInfraEmail; { This file is part of Synopse mORMot framework. - Synopse mORMot framework. Copyright (C) 2020 Arnaud Bouchez + Synopse mORMot framework. Copyright (C) 2022 Arnaud Bouchez Synopse Informatique - https://synopse.info *** BEGIN LICENSE BLOCK ***** @@ -25,7 +25,7 @@ unit dddInfraEmail; The Initial Developer of the Original Code is Arnaud Bouchez. - Portions created by the Initial Developer are Copyright (C) 2020 + Portions created by the Initial Developer are Copyright (C) 2022 the Initial Developer. All Rights Reserved. Contributor(s): diff --git a/contrib/mORMot/SQLite3/DDD/infra/dddInfraEmailer.pas b/contrib/mORMot/SQLite3/DDD/infra/dddInfraEmailer.pas index c5923ef..be11199 100644 --- a/contrib/mORMot/SQLite3/DDD/infra/dddInfraEmailer.pas +++ b/contrib/mORMot/SQLite3/DDD/infra/dddInfraEmailer.pas @@ -6,7 +6,7 @@ unit dddInfraEmailer; { This file is part of Synopse mORMot framework. - Synopse mORMot framework. Copyright (C) 2020 Arnaud Bouchez + Synopse mORMot framework. Copyright (C) 2022 Arnaud Bouchez Synopse Informatique - https://synopse.info *** BEGIN LICENSE BLOCK ***** @@ -25,7 +25,7 @@ unit dddInfraEmailer; The Initial Developer of the Original Code is Arnaud Bouchez. - Portions created by the Initial Developer are Copyright (C) 2020 + Portions created by the Initial Developer are Copyright (C) 2022 the Initial Developer. All Rights Reserved. Contributor(s): diff --git a/contrib/mORMot/SQLite3/DDD/infra/dddInfraRepoUser.pas b/contrib/mORMot/SQLite3/DDD/infra/dddInfraRepoUser.pas index 2f10739..b5b8a8a 100644 --- a/contrib/mORMot/SQLite3/DDD/infra/dddInfraRepoUser.pas +++ b/contrib/mORMot/SQLite3/DDD/infra/dddInfraRepoUser.pas @@ -6,7 +6,7 @@ unit dddInfraRepoUser; { This file is part of Synopse mORMot framework. - Synopse mORMot framework. Copyright (C) 2020 Arnaud Bouchez + Synopse mORMot framework. Copyright (C) 2022 Arnaud Bouchez Synopse Informatique - https://synopse.info *** BEGIN LICENSE BLOCK ***** @@ -25,7 +25,7 @@ unit dddInfraRepoUser; The Initial Developer of the Original Code is Arnaud Bouchez. - Portions created by the Initial Developer are Copyright (C) 2020 + Portions created by the Initial Developer are Copyright (C) 2022 the Initial Developer. All Rights Reserved. Contributor(s): diff --git a/contrib/mORMot/SQLite3/DDD/infra/dddInfraSettings.pas b/contrib/mORMot/SQLite3/DDD/infra/dddInfraSettings.pas index 053efd8..0c20842 100644 --- a/contrib/mORMot/SQLite3/DDD/infra/dddInfraSettings.pas +++ b/contrib/mORMot/SQLite3/DDD/infra/dddInfraSettings.pas @@ -6,7 +6,7 @@ unit dddInfraSettings; { This file is part of Synopse mORMot framework. - Synopse mORMot framework. Copyright (C) 2020 Arnaud Bouchez + Synopse mORMot framework. Copyright (C) 2022 Arnaud Bouchez Synopse Informatique - https://synopse.info *** BEGIN LICENSE BLOCK ***** @@ -25,7 +25,7 @@ unit dddInfraSettings; The Initial Developer of the Original Code is Arnaud Bouchez. - Portions created by the Initial Developer are Copyright (C) 2020 + Portions created by the Initial Developer are Copyright (C) 2022 the Initial Developer. All Rights Reserved. Contributor(s): diff --git a/contrib/mORMot/SQLite3/Documentation/Synopse SQLite3 Framework.pro b/contrib/mORMot/SQLite3/Documentation/Synopse SQLite3 Framework.pro index faa7ec5..3d8423b 100644 --- a/contrib/mORMot/SQLite3/Documentation/Synopse SQLite3 Framework.pro +++ b/contrib/mORMot/SQLite3/Documentation/Synopse SQLite3 Framework.pro @@ -28,7 +28,7 @@ HtmlSideBar=Overview/Meet the mORMot:SOURCE,Download/How to install:TITL_113,API ; the sidebar first links, for html export {\b Document License} -{\i Synopse mORMot Framework Documentation}.\line Copyright (C) 2008-2020 Arnaud Bouchez.\line Synopse Informatique - @https://synopse.info +{\i Synopse mORMot Framework Documentation}.\line Copyright (C) 2008-2022 Arnaud Bouchez.\line Synopse Informatique - @https://synopse.info The {\i Synopse mORMot Framework Source Code} is licensed under GPL / LGPL / MPL licensing terms, free to be included in any application. ;This documentation has been generated using {\i Synopse SynProject} - @https://synopse.info/fossil/wiki?name=SynProject ;This document is a free document; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version. @@ -136,7 +136,7 @@ The main approach of this framework is to avoid @*RAD@ in the development of pro : Expected Use Any application which need moderate database usage (up to some GB of data) with easy setup and administration, together with a secure @*ACID@ behavior in a Client-Server environment should consider using the {\i Synopse mORMot Framework}. : Requirement Exceptions -This framework was developed in order to run mainly under any {\i Delphi} compiler, from version {\i Delphi} 6 to version {\i Delphi 10.3 Rio}. +This framework was developed in order to run mainly under any {\i Delphi} compiler, from version {\i Delphi} 6 to the latest Delphi version ({\i Delphi 11 Alexandria} at time of this writing). On the {\i server side}, it targets both {\i Win32} and {\i Win64} platforms (using the 64-bit compiler included in latest {\i Delphi} XE2 and up). For clients, in addition to those {\i Win32} / {\i Win64} platforms, you have cross-platform code generation abilities, for any {\i Delphi} or {\i @*FreePascal@} target (including {\i @*OSX@} and mobile {\i iOS} or {\i Android}), or AJAX / HTML5 clients via {\i @*Smart Mobile Studio@} - see @90@. =[License] @@ -464,7 +464,7 @@ The {\i Synopse mORMot Framework} shall provide User Interface and Report genera Such a ribbon-oriented interface shall be made available, in a per-table approach, and associated reports. Here is a sample of screen content, using proprietary TMS components: %synfiletms.png -And here is the same application compiled using only VCL components, available from {\i Delphi} 6 up to {\i Delphi 10.3 Rio}: +And here is the same application compiled using only VCL components, available from {\i Delphi} 6 up to the latest available {\i Delphi} version: %synfilevcl.png [SRS-DI-2.3.1] @@ -570,7 +570,7 @@ DisplayName=mORMot Framework Overview :Synopse mORMot Overview %IamLost.png -{\i Synopse mORMot} is an Open Source @*Client-Server@ @*ORM@ @*SOA@ @*MVC@ framework for {\i Delphi} 6 up to {\i Delphi 10.3 Rio} and @*FPC@, targeting {\i Win/@*Linux@} for the server, and any platform for clients (including mobile or AJAX). +{\i Synopse mORMot} is an Open Source @*Client-Server@ @*ORM@ @*SOA@ @*MVC@ framework for {\i Delphi} 6 up to the latest available {\i Delphi} version and @*FPC@ 3.2, targeting {\i Win/@*Linux@} for the server, and any platform for clients (including mobile or AJAX). The main features of {\i mORMot} are therefore: - {\i ORM/ODM}: objects persistence on almost any database (SQL or NoSQL); - {\i SOA}: organize your business logic into @*REST@ services; @@ -708,7 +708,7 @@ At first, some points can be highlighted, which make this framework distinct to - More than 1800 pages of documentation; - {\i Delphi}, {\i FreePascal}, mobile and @*AJAX@ clients can share the same server, and ORM/SOA client access code can be generated on request for any kind of application - see @86@; - Full source code provided - so you can enhance it to fulfill any need; -- Works from {\i Delphi} 6 up to {\i Delphi 10.3 Rio} and FPC 2.6.4/2.7.1/3.x, truly Unicode (uses @*UTF-8@ encoding in its kernel, just like JSON), with any version of {\i Delphi} (no need to upgrade your IDE). +- Works from {\i Delphi} 6 up to the latest available {\i Delphi} version and FPC 3.2.x, truly Unicode (uses @*UTF-8@ encoding in its kernel, just like JSON), with any version of {\i Delphi} (no need to upgrade your IDE). \page : Benefits As you can see from the previous section, {\i mORMot} provides a comprehensive set of features that can help you to manage your crosscutting concerns though a reusable set of components and core functionality. @@ -1364,7 +1364,7 @@ In the following next paragraphs, we'll comment some main features of the lowest - {\f1\fs20 @*TDocVariant@} custom {\f1\fs20 variant} type for dynamic schema-less {\i object} or {\i array} storage. Other shared features available in {\f1\fs20 SynTests.pas} and {\f1\fs20 SynLog.pas} will be detailed later, i.e. @*Test@ing and @*Log@ging - see @12@. :32 Unicode and UTF-8 -Our {\i mORMot} Framework has 100% UNICODE compatibility, that is compilation under {\i Delphi} 2009 and up (including latest {\i Delphi 10.3 Rio} revision). The code has been deeply rewritten and @*test@ed, in order to provide compatibility with the {\f1\fs20 String=UnicodeString} paradigm of these compilers. But the code will also handle safely Unicode for older versions, i.e. from {\i Delphi} 6 up to {\i Delphi} 2007. +Our {\i mORMot} Framework has 100% UNICODE compatibility, that is compilation under {\i Delphi} 2009 and up (including the latest available {\i Delphi} version). The code has been deeply rewritten and @*test@ed, in order to provide compatibility with the {\f1\fs20 String=UnicodeString} paradigm of these compilers. But the code will also handle safely Unicode for older versions, i.e. from {\i Delphi} 6 up to {\i Delphi} 2007. From its core to its uppermost features, our framework is natively @**UTF-8@, which is the de-facto character encoding for @*JSON@, {\i @*SQLite3@}, and most supported database engines. This allows our code to offer fast streaming/parsing in a @*SAX@-like mode, avoiding any conversion between encodings from the storage layer to your business logic. We also needed to establish a secure way to use strings, in order to handle all versions of {\i Delphi} (even pre-Unicode versions, especially the {\i Delphi} 7 version we like so much), and provide compatibility with the {\i @*FreePascal@ Compiler}. This consistency allows to circumvent any RTL bug or limitation, and ease long-term support of your project. Some string types have been defined, and used in the code for best cross-compiler efficiency: - {\f1\fs20 @**RawUTF8@} is used for every internal data usage, since both {\i SQLite3} and JSON do expect UTF-8 encoding; @@ -1463,7 +1463,7 @@ Here is how those new methods work: ! if GroupA.Find(v)<0 then // fast binary search ! ShowMessage('Error: 1500 not found!'); Some unique methods like {\f1\fs20 Slice, Reverse} or {\f1\fs20 AddArray} are also available, and mimic well-known Python methods. -Still closer to the generic paradigm, working for {\i Delphi} 6 up to {\i Delphi 10.3 Rio}, without the need of the slow enhanced RTTI, nor the executable size overhead and compilation issues of generics... +Still closer to the generic paradigm, working for {\i Delphi} 6 up to the latest available {\i Delphi} version, without the need of the slow enhanced RTTI, nor the executable size overhead and compilation issues of generics... : Capacity handling via an external Count One common speed issue with the default usage of {\f1\fs20 TDynArray} is that the internal memory buffer is reallocated when you change its length, just like a regular {\i Delphi} {\i dynamic array}. That is, whenever you call {\f1\fs20 Add} or {\f1\fs20 Delete} methods, an internal call to {\f1\fs20 SetLength(DynArrayVariable)} is performed. This could be slow, because it always executes some extra code, including a call to {\f1\fs20 ReallocMem}. @@ -1829,7 +1829,7 @@ When working with complex documents, e.g. with @*BSON@ / {\i @*MongoDB@} documen : Advanced TDocVariant process :194 Number values options By default, {\f1\fs20 TDocVariantData} will only recognize {\f1\fs20 integer}, {\f1\fs20 Int64} and {\f1\fs20 currency} - see @33@ - as number values. Any floating point value which may not be translated to/from @*JSON@ textual representation safely will be stored as a JSON string, i.e. if it does match an integer or up to 4 fixed decimals, with 64-bit precision. We stated that JSON serialization should be conservative, i.e. serializing then unserializing (or the other way round) should return the very same value; parsing JSON is a matter of (difficult) choices - see @http://seriot.ch/parsing_json.php#5 - and we choose to be paranoid and not loose information by default. -You can set the {\f1\fs20 @*dvoAllowDoubleValue@} option to {\f1\fs20 TDocVariantData}, so that such floating-point numbers will be recognized and stored as {\f1\fs20 @*double@}. In this case, only {\f1\fs20 varDouble} storage will be used for the {\f1\fs20 variant} values, i.e. 64-bit IEEE 754 {\f1\fs20 double} values, handling 5.0 x 10^-324 .. 1.7 x 10^308 range. With such floating-point values, you may loose precision and digits during the JSON serialization process: this is why it is not enabled by default. +You can use the {\f1\fs20 @*_JsonFastFloat()@} wrapper or set the {\f1\fs20 @**dvoAllowDoubleValue@} option to {\f1\fs20 TDocVariantData}, so that such floating-point numbers will be recognized and stored as {\f1\fs20 @**double@}. In this case, only {\f1\fs20 varDouble} storage will be used for the {\f1\fs20 variant} values, i.e. 64-bit IEEE 754 {\f1\fs20 double} values, handling 5.0 x 10^-324 .. 1.7 x 10^308 range. With such floating-point values, you may loose precision and digits during the JSON serialization process: this is why it is not enabled by default. Also note that some JSON engines do not support 64-bit integer numbers. For instance, {\f1\fs20 @*JavaScript@} engines handle only up to @*53-bit@ of information without precision loss (called the {\i significand} bits), due to their internal storage as a 8 bytes IEEE 754 container. In some cases, it is safest to use JSON string representation of such numbers, as is done with the {\f1\fs20 woIDAsIDstr} value of {\f1\fs20 TTextWriterWriteObjectOption} for safe serialization of {\f1\fs20 TSQLRecord.ID} ORM values. If you want to work with high-precision floating point numbers, consider using {\f1\fs20 @*TDecimal128@} values, as implemented in {\f1\fs20 SynMongoDB.pas}, which supports 128-bit high precision decimal, as defined by the {\i IEEE 754-2008 128-bit decimal floating point} standard, and handled in {\i MongoDB} 3.4+. Their conversion to/from text - therefore to/from JSON - won't loose nor round any digit, as soon as the value fits in its 128-bit storage. : Object or array document creation options @@ -4610,7 +4610,7 @@ In the above query expression, the {\f1\fs20 rank()} function is used over the d In any database, there is a need to define how column data is to be compared. It is needed for proper search and ordering of the data. This is the purpose of so-called {\i @**collation@s}. By default, when {\i SQLite} compares two strings, it uses a collating sequence or collating function (two words for the same thing) to determine which string is greater or if the two strings are equal. {\i SQLite} has three built-in collating functions: BINARY, NOCASE, and RTRIM: - BINARY - Compares string data using {\f1\fs20 memcmp()}, regardless of text encoding. -- NOCASE - The same as binary, except the 26 upper case characters of ASCII are folded to their lower case equivalents before the comparison is performed. Note that only ASCII characters are case folded. Plain {\i SQLite} does not attempt to do full @*Unicode@ case folding due to the size of the tables required - but you could use {\i mORMot}'s SYSTEMNOCASE or WIN32CASE/WIN32NOCASE custom collations for enhanced case folding support (see below); +- NOCASE - The same as binary, except the 26 upper case characters of ASCII are folded to their lower case equivalents before the comparison is performed. Note that only ASCII characters are case folded. Plain {\i SQLite} does not attempt to do full @*Unicode@ case folding due to the size of the tables required - but you could use {\i mORMot}'s SYSTEMNOCASE, or WIN32CASE/WIN32NOCASE custom collations for enhanced case folding support (see below); - RTRIM - The same as binary, except that trailing space characters are ignored. In the {\i mORMot} ORM, we defined some additional kind of collations, via some internal calls to the {\f1\fs20 sqlite3_create_collation()} API: |%25%60 @@ -4632,6 +4632,7 @@ The following collations are therefore available when using {\i SQLite3} within |NOCASE|Default ASCII 7 bit comparison |RTRIM|Default {\f1\fs20 memcmp()} comparison with right trim |SYSTEMNOCASE|{\i mORMot}'s Win-1252 8 bit comparison +;|UNICODENOCASE|{\i mORMot}'s Unicode 10.0 comparison |ISO8601|{\i mORMot}'s date/time comparison |WIN32CASE|{\i mORMot}'s comparison using case-insensitive Windows API |WIN32NOCASE|{\i mORMot}'s comparison using not case-insensitive Windows API @@ -4639,17 +4640,17 @@ The following collations are therefore available when using {\i SQLite3} within Note that WIN32CASE/WIN32NOCASE will be slower than the others, but will handle properly any kind of complex scripting. For instance, if you want to use the Unicode-ready Windows API at database level, you can set for each database model: ! aModel.SetCustomCollationForAll(sftUTF8Text,'WIN32CASE'); ! aModel.SetCustomCollationForAll(sftDateTime,'NOCASE'); +On non-Windows platform, it will either use the system ICU library (if available), or fallback to the FPC RTL with temporary {\f1\fs20 UnicodeString} values - which requires to include `cwstrings` in your project uses clause. Note that depending on the library used, the results may not be consistent: so if you move a {\i SQLite3} database file e.g. from a Windows system to a Linux system with WIN32CASE collation, you should better regenerate all your indexes! If you use non-default collations (i.e. SYSTEMNOCASE/ISO8601/WIN32CASE/WIN32NOCASE), you may have trouble running requests with "plain" {\i SQLite3} tools. But you can use our {\f1\fs20 @*SynDBExplorer@} safely, since it will declare all the above collations. When using external databases - see @27@, if the content is retrieved directly from the database driver and by-passes the virtual table mechanism - see @20@, returned data may not match your expectations according to the custom collations: you will need to customize the external tables definition by hand, with the proper SQL statement of each external DB engine. +Note that {\i @*mORMot 2@} offers a new UNICODENOCASE collation, which follows Unicode 10.0 without any Windows or ICU API call, so is consistent on all systems - and is also faster. : REGEXP operator -Our {\i SQLite3} engine can use {\i @**regular expression@} within its SQL queries, by enabling the {\f1\fs20 @**REGEXP@} operator in addition to standard SQL operators ({\f1\fs20 = == != <> IS IN LIKE GLOB MATCH}). It will use the Open Source PCRE library to perform the queries. -In order to enable the operator, you should include unit {\f1\fs20 SynSQLite3RegEx.pas} to your uses clause, and register the {\f1\fs20 RegExp()} SQL function to a given {\i SQLite3} database instance, as such: -!uses SynCommons, mORmot, mORMotSQLite3, -!! SynSQLite3RegEx; -! ... +Our {\i SQLite3} engine can use {\i @**regular expression@} within its SQL queries, by enabling the {\f1\fs20 @**REGEXP@} operator in addition to standard SQL operators ({\f1\fs20 = == != <> IS IN LIKE GLOB MATCH}). +: Default REGEXP Engine +By default, and since mORMot 1.18.6218 (25 January 2021), our static {\i SQlite3} engine includes a compact and efficient enough C extension, as available from the official {\i SQLite3} project source code tree. It is included with the official amalgamation file during our compilation phase. +So you don't need to do anything to be able to use the REGEX operator in your queries: !Server := TSQLRestServerDB.Create(Model,'test.db3'); !try -!! CreateRegExpFunction(Server.DB.DB); ! with TSQLRecordPeople.CreateAndFillPrepare(Client, !! 'FirstName REGEXP ?',['\bFinley\b']) do ! try @@ -4667,6 +4668,20 @@ The above code will execute the following SQL statement (with a prepared paramet ! SELECT * from People WHERE Firstname REGEXP '\bFinley\b'; That is, it will find all objects where {\f1\fs20 TSQLRecordPeople.FirstName} will contain the {\f1\fs20 'Finley'} word - in a regular expression, {\f1\fs20 \\b} defines a word {\f1\fs20 b}oundary search. In fact, the {\f1\fs20 REGEXP} operator is a special syntax for the {\f1\fs20 regexp()} user function. No {\f1\fs20 regexp()} user function is defined by default and so use of the {\f1\fs20 REGEXP} operator will normally result in an error message. Calling {\f1\fs20 CreateRegExFunction()} for a given connection will add a SQL function named "{\f1\fs20 regexp()}" at run-time, which will be called in order to implement the {\f1\fs20 REGEXP} operator. +: PCRE REGEXP Engine +If you want to use the Open Source PCRE library to perform the searches, instead of this default C extension, you should include the {\f1\fs20 SynSQLite3RegEx.pas} unit to your uses clause, and register the {\f1\fs20 RegExp()} SQL function to a given {\i SQLite3} database instance, as such: +!uses SynCommons, mORmot, mORMotSQLite3, +!! SynSQLite3RegEx; +! ... +!Server := TSQLRestServerDB.Create(Model,'test.db3'); +!try +!! CreateRegExpFunction(Server.DB.DB); +! with TSQLRecordPeople.CreateAndFillPrepare(Client, +!! 'FirstName REGEXP ?',['\bFinley\b']) do +! try +! while FillOne do begin +! Check(LastName='Morse'); +! ... It will use the statically linked PCRE library as available since {\i Delphi} XE, or will rely on the {\f1\fs20 PCRE.pas} wrapper unit as published at @http://www.regular-expressions.info/download/TPerlRegEx.zip for older versions of {\i Delphi}. This unit will call directly the @*UTF-8@ API of the PCRE library, and maintain a per-connection cache of compiled regular expressions to ensure the best performance possible. :60 ACID and speed @@ -4752,7 +4767,8 @@ Note that the virtual table module name is retrieved from the class name. For in To handle external databases, two dedicated classes, named {\f1\fs20 TSQLVirtualTableExternal} and {\f1\fs20 TSQLVirtualTableCursorExternal} will be defined in a similar manner - see @%%HierExternalTables@ @30@. As you probably have already stated, all those Virtual Table mechanism is implemented in {\f1\fs20 mORMot.pas}. Therefore, it is independent from the {\i @*SQLite3@} engine, even if, to my knowledge, there is no other SQL database engine around able to implement this pretty nice feature. : Defining a Virtual Table module -Here is how the {\f1\fs20 TSQLVirtualTableLog} class type is defined, which will implement a @*Virtual Table@ module named "{\f1\fs20 Log}". Adding a new module is just made by overriding some {\i Delphi} methods: +Here is how the {\f1\fs20 TSQLVirtualTableLog} class type is defined, which will implement a @*Virtual Table@ module named "{\f1\fs20 Log}". Note that the {\i SQLite3} virtual table module name will be computed from the class name, trimming its first characters, e.g. {\f1\fs20 TSQLVirtualTable{\b Log}} will trim trailing {\f1\fs20 TSQLVirtualTable} and define a {\f1\fs20 'Log'} virtual module. +Adding a new module is just made by overriding some {\i Delphi} methods: ! TSQLVirtualTableLog = class(TSQLVirtualTable) ! protected ! fLogFile: TSynLogFile; @@ -7431,7 +7447,7 @@ In practice, for your project, you will have several possibilities to create a C |SOA Interfaces|RPC REST|RPC |SOA Methods|Full REST/HTTP|Verbose |MVC Web|Web site + AJAX|HTML-oriented -|ORM REST|Tests or internal use|Security/design flows +|ORM REST|Tests or internal use|Security/design flaws |% In a nutshell, - {\i SOA Interfaces} - see @63@ - is the preferred way to build both public and private services: both client and server code will be defined from {\f1\fs20 interface} types, including sessions management, stubbing/mocking, documentation generation, and security features. @@ -9668,7 +9684,7 @@ If you compare with existing mocking frameworks, even in other languages / platf - Most common parameters and results can be defined as simple {\f1\fs20 array of const} in the {\i Delphi} code, or by supplying JSON arrays (needed e.g. for more complex structures like {\f1\fs20 record} values); - Execution trace retrieval in easy to read or write text format (and not via complex "fluent" interface e.g. with {\f1\fs20 When} clauses); - Auto-release of the {\f1\fs20 TInterfaceStub TInterfaceMock TInterfaceMockSpy} generator instance, when the interface is no longer required, to minimize the code to type, and avoid potential memory leaks; -- Works from {\i Delphi} 6 up to {\i Delphi 10.3 Rio} - since no use of syntax sugar like generics, nor the {\f1\fs20 RTTI.pas} features; +- Works from {\i Delphi} 6 up to the latest available {\i Delphi} version - since no use of syntax sugar like generics, nor the {\f1\fs20 RTTI.pas} features; - Very good performance (the faster {\i Delphi} mocking framework, for sure), due to very low overhead and its reuse of {\i mORMot}'s low-level interface-based services kernel using JSON serialization, which does not rely on the slow and limited {\f1\fs20 TVirtualInterface}. : Stubbing complex return values Just imagine that the {\f1\fs20 ForgotMyPassword} method does perform an internal test: @@ -9881,7 +9897,7 @@ Here are the key features of the current implementation of services using interf |Server factory|You can get an implementation on the server side |Client factory|You can get a "fake" implementation on the client side, remotely calling the server to execute the process |Cross-platform clients|A {\i mORMot} server is able to generate cross-platform client code via a set of templates - see @86@ -|Auto marshalling|The contract is transparently implemented: no additional code is needed e.g. on the client side, and will handle simple types (strings, numbers, dates, sets and enumerations) and high-level types (objects, collections, records, dynamic arrays, variants) from {\i Delphi} 6 up to {\i Delphi 10.3 Rio} +|Auto marshalling|The contract is transparently implemented: no additional code is needed e.g. on the client side, and will handle simple types (strings, numbers, dates, sets and enumerations) and high-level types (objects, collections, records, dynamic arrays, variants) from {\i Delphi} 6 up to the latest available {\i Delphi} version |Flexible|Methods accept per-value or per-reference parameters |Instance lifetime|An implementation class can be:\line - Created on every call,\line - Shared among all calls,\line - Shared for a particular user or group,\line - Dedicated to the thread it runs on,\line - Alive as long as the client-side interface is not released,\line - Or as long as an @*authentication@ session exists |@*Stateless@|Following a standard request/reply pattern @@ -9983,7 +9999,7 @@ You can therefore define complex {\f1\fs20 interface} types, as such: ! /// validates ArgsInputIsOctetStream raw binary upload ! function DirectCall(const Data: TSQLRawBlob): integer; ! end; -Note how {\f1\fs20 SpecialCall} and {\f1\fs20 ComplexCall} methods have quite complex parameters definitions, including dynamic arrays, sets and records. DirectCall will use binary POST, by-passing @*Base64@ JSON encoding - see @197@. The framework will handle {\f1\fs20 const} and {\f1\fs20 var} parameters as expected, i.e. as input/output parameters, also on the client side. Any simple types of dynamic arrays (like {\f1\fs20 TIntegerDynArray}, {\f1\fs20 TRawUTF8DynArray}, or {\f1\fs20 TWideStringDynArray}) will be serialized as plain JSON arrays - the framework is able to handle any dynamic array definition, but will serialize those simple types in a more AJAX compatible way, thanks to the enhanced RTTI available since to {\i Delphi} 2010. +Note how {\f1\fs20 SpecialCall} and {\f1\fs20 ComplexCall} methods have quite complex parameters definitions, including dynamic arrays, sets and records. {\f1\fs20 DirectCall} will use binary POST, by-passing @*Base64@ JSON encoding - see @197@. The framework will handle {\f1\fs20 const} and {\f1\fs20 var} parameters as expected, i.e. as input/output parameters, also on the client side. Any simple types of dynamic arrays (like {\f1\fs20 TIntegerDynArray}, {\f1\fs20 TRawUTF8DynArray}, or {\f1\fs20 TWideStringDynArray}) will be serialized as plain JSON arrays - the framework is able to handle any dynamic array definition, but will serialize those simple types in a more AJAX compatible way, thanks to the enhanced RTTI available since to {\i Delphi} 2010. : TPersistent / TSQLRecord parameters As stated above, {\i mORMot} does not allow a method {\f1\fs20 function} to return a {\f1\fs20 class} instance. That is, you can't define such a method: @@ -13659,7 +13675,7 @@ By default, the following security groups are created on a void database: |%14%12%14%11%11%12%12%12 |\b Group|POST SQL|SELECT SQL|Auth R|Auth W|Tables R|Tables W|Services\b0 |Admin|Yes|Yes|Yes|Yes|Yes|Yes|Yes -|Supervisor|Yes|No|Yes|No|Yes|Yes|Yes +|Supervisor|No|Yes|Yes|No|Yes|Yes|Yes |User|No|No|No|No|Yes|Yes|Yes |Guest|No|No|No|No|Yes|No|No |% @@ -15736,7 +15752,7 @@ And even better, testing-driven coding can be encouraged: It could sounds like a waste of time, but such coding improve your code quality a lot, and, at least, it help you write and optimize every implementation feature. The framework has been implemented using this approach, and provide all the tools to write tests. In addition to what other {\i Delphi} frameworks offer (e.g. {\i DUnit / DUnitX}), the {\f1\fs20 SynTests.pas} unit is very much integrated with other elements of the framework (like logging), is cross-platform and cross-compiler, and provides a complete {\i stubbing / mocking} mechanism to cover @62@. : Involved classes in Unitary testing -The @!TSynTest,TSynTestCase,TSynTests!Lib\SynTests.pas@ unit defines two classes (both inheriting from {\f1\fs20 TSynTest}), implementing a complete Unitary testing mechanism similar to {\i DUnit}, with less code overhead, and direct interface with the framework units and requirements (@*UTF-8@ ready, code compilation from {\i Delphi} 6 up to {\i Delphi 10.3 Rio} and FPC, no external dependency). +The @!TSynTest,TSynTestCase,TSynTests!Lib\SynTests.pas@ unit defines two classes (both inheriting from {\f1\fs20 TSynTest}), implementing a complete Unitary testing mechanism similar to {\i DUnit}, with less code overhead, and direct interface with the framework units and requirements (@*UTF-8@ ready, code compilation from {\i Delphi} 6 up to the latest available {\i Delphi} version and FPC, no external dependency). The following diagram defines this class hierarchy: \graph HierTSynTest TSynTest classes hierarchy \TSynTests\TSynTest @@ -15850,12 +15866,10 @@ Before any release all unitary regression tests are performed with the following - {\i Delphi} 2007; - {\i Delphi} 2010 (we assume that if it works with {\i Delphi} 2010, it will work with {\i Delphi} 2009, with the exception of {\f1\fs20 generic} compilation); - {\i Delphi} XE4; -- {\i Delphi} XE6; - {\i Delphi} XE7; -- {\i Delphi} 10 Seattle; -- {\i Delphi} 10.1 Berlin; -- {\i Delphi} 10.2 Tokyo; +- {\i Delphi} XE8; - {\i Delphi} 10.3 Rio; +- {\i Delphi} 10.4 Sidney; - {\i @*CrossKylix@} 3.0; - {\i @*FPC@} 3.x - preferred is {\i 3.2 fixes}. Target platforms are {\i Win32} and {\i Win64} for {\i Delphi} and {\i FPC}, plus {\i Linux 32/64} for {\i FPC} and {\i CrossKylix}. @@ -16223,17 +16237,17 @@ Follow these steps: - Finally, click on the "{\i Zip Archive}" link, available at the end of the "{\i Overview}" header, right ahead to the "{\i Other Links}" title. This link will build a {\f1\fs20 .zip} archive of the complete source code and download it to your browser. : Expected compilation platform The framework source code tree will compile and is tested for the following platforms: -- {\i Delphi} 6 up to {\i Delphi 10.3 Rio} compiler and IDE, with {\i @*FreePascal@ Compiler} (FPC) 3.x and {\i @*Lazarus@} support; +- {\i Delphi} 6 up to the latest {\i Delphi} compiler and IDE version, with {\i @*FreePascal@ Compiler} (FPC) 3.x and {\i @*Lazarus@} support; - Server side on Windows 32-bit and @**64-bit@ platforms (FPC or {\i Delphi} XE2 and up expected when targeting {\i Win64}); - {\i @*Linux@} 32-bit and 64-bit platform for servers using the FPC 3.2 fixes branch - now stable and tested in production since years (especially {\i @*Debian@/@*Ubuntu@} on {\f1\fs20 x86_64}); - VCL client on Win32/Win64 - GUI may be compiled optionally with third-party non Open-Source @*TMS@ Components, instead of default VCL components - see @http://www.tmssoftware.com/site/tmspack.asp - @69@ clients on any supported platforms; - @90@ startup with 2.1, for creating @*AJAX@ / @*JavaScript@ / HTML5 / Mobile clients. Some part of the library (e.g. {\f1\fs20 SynCommons.pas}, {\f1\fs20 SynTests.pas}, {\f1\fs20 SynLog.pas} {\f1\fs20 SynPDF.pas} or the @27@ units) are also compatible with {\i Delphi} 5. -If you want to compile {\i mORMot} unit into @*packages@, to avoid an obfuscated {\i [DCC Error] @*E2201@ Need imported data reference ($G) to access 'VarCopyProc'} error at compilation, you should defined the {\f1\fs20 USEPACKAGES} conditional in your project's options. Open {\f1\fs20 SynCommons.inc} for a description of this conditional, and all over definitions global to all {\i mORMot} units - see @45@. +If you want to compile {\i mORMot} unit into @**packages@, to avoid an obfuscated {\i [DCC Error] @*E2201@ Need imported data reference ($G) to access 'VarCopyProc'} error at compilation, you should defined the {\f1\fs20 USEPACKAGES} conditional in your project's options. Open {\f1\fs20 SynCommons.inc} for a description of this conditional, and all over definitions global to all {\i mORMot} units - see @45@. To avoid related {\i @*E1025@ Unsupported language feature: 'Object'} compilation error, you should probably also set "{\i Generate DCUs only}" in project's options "{\i C/C++ output file generator}". The framework source code implementation and design tried to be as cross-platform and cross-compiler as possible, since the beginning. It is a lot of work to maintain compatibility towards so many tools and platforms, but we think it is always worth it - especially if you try not depend on {\i Delphi} only, which as shown some backward compatibility issues during its lifetime. For HTML5 and Mobile clients, our main platform is {\i Smart Mobile Studio}, which is a great combination of ease of use, a powerful {\i SmartPascal} dialect, small applications (much smaller than FMX), with potential packaging as native iOS or {\i Android} applications (via {\i @*PhoneGap@}). -The latest versions of the {\i FreePascal Compiler} together with its great {\i Lazarus} IDE, are now very stable and easy to work with. We don't support {\i CodeTyphon}, since we found some licensing issue with some part of it (e.g. {\i Orca} GUI library origin is doubtful). So we recommend using {\i @*fpcupdeluxe@} - see @203@ - which is maintained by Alfred, a {\i mORMot} contributor. This is amazing to build the whole set of compilers and IDE, with a lot of components, for several platforms (this is a cross-platform project), just from the sources. I like {\i Lazarus} stability and speed much more than {\i Delphi} (did you ever tried to browse and debug {\i included} {\f1\fs20 $I ...} files in the {\i Delphi} IDE? with Lazarus, it is painless), even if the compiler is slower than {\i Delphi}'s, and if the debugger is less integrated and even more unstable than {\i Delphi}'s under Windows (yes, it is possible!). At least, it works, and the {\i Lazarus} IDE is small and efficient. Official {\i @*Linux@} support is available for {\i mORMot} servers, with full features in the {\i FPC} 3.2 branch - we use it on producing on {\i Linux} 64-bit since years. +The latest versions of the {\i FreePascal Compiler} together with its great {\i Lazarus} IDE, are now very stable and easy to work with. We don't support {\i CodeTyphon}, since we found some licensing issue with some part of it (e.g. {\i Orca} GUI library origin is doubtful). So we recommend using {\i @*fpcupdeluxe@} - see @203@ - which is maintained by Alfred, a {\i mORMot} contributor. This is amazing to build the whole set of compilers and IDE, with a lot of components, for several platforms (this is a cross-platform project), just from the sources. I like {\i Lazarus} stability and speed much more than {\i Delphi} (did you ever tried to browse and debug {\i included} {\f1\fs20 $I ...} files in the {\i Delphi} IDE? with Lazarus, it is painless), even if the compiler is slower than {\i Delphi}'s, and if the debugger is less integrated and even more unstable than {\i Delphi}'s under Windows (yes, it is possible!). At least, it works, and the {\i Lazarus} IDE is small and efficient. Official {\i @*Linux@} support is available for {\i mORMot} servers, with full features in the {\i FPC} 3.2 branch - we use it on production with {\i Linux} 64-bit since years. : SQLite3 static linking for Delphi and FPC {\i Preliminary note}: if you retrieved the source code from @https://github.com/synopse/mORMot you will have all the needed {\f1\fs20 .obj/.o} static files available in the expected folders. Just ignore this chapter. In order to maintain our @https://synopse.info/fossil/timeline source code repository in a decent size, we excluded the {\f1\fs20 sqlite3.obj/.o} storage in it, but provide the full source code of the {\i @*SQlite3@} engine in a custom {\f1\fs20 sqlite3.c} file, ready to be compiled with all conditional defined as expected by {\f1\fs20 SynSQlite3Static.pas}. You need to add the official {\i SQlite3} amalgamation file from @https://www.sqlite.org/download.html and put its content into a {\f1\fs20 SQLite3\\amalgamation} sub-folder, for proper compilation. Our custom {\f1\fs20 sqlite3.c} file will add encryption feature to the engine. Also look into {\f1\fs20 SynSQlite3Static.pas} comments if there is any manual patch needed for proper compilation of the amalgamation sourece. @@ -16374,7 +16388,7 @@ See @86@ for more information. \page :113 Delphi Installation {\i Note: for FPC setup, see @125@.} -To setup mORMot for {\i Delphi 6} up to {\i Delphi 10.3 Rio}, you have two ways: either download the framework from archives, or clone our {\i GitHub} repository at @https://github.com/synopse/mORMot +To setup mORMot for {\i Delphi 6} up to the latest {\i Delphi} version, you have two ways: either download the framework from archives, or clone our {\i GitHub} repository at @https://github.com/synopse/mORMot : Manual download Download and uncompress the framework archives, including all sub-folders, into a local directory of your computer (for instance, {\f1\fs20 D:\\Dev\\mORMot}). |%70 @@ -16429,11 +16443,12 @@ But since the FPC trunk may be unstable, we will propose to put in place a stabl For this task, don't download an existing binary release of FPC / Lazarus, but use the {\i @**fpcupdeluxe@} tool, as published at @http://wiki.freepascal.org/fpcupdeluxe - it will allow to build your environment directly from the sources, and install it in a dedicated folder. Several FPC / Lazarus installations, with dedicated revision numbers, may coexist on the same computer: just ensure you run Lazarus from the shortcut created by {\i fpcupdeluxe}. - Download the latest release of the tool from @https://github.com/LongDirtyAnimAlf/fpcupdeluxe/releases - Unpack it in a dedicated folder, and run its executable. -- On the main screen, locate on the left the two versions listboxes. Select "fixes" for both {\i FPC version} and {\i Lazarus version}. +- On the main screen, locate on the left the two versions listboxes. Select "{\f1\fs20 3.2}" for {\i FPC version} and "{\f1\fs20 2.1.0}" for {\i Lazarus version}. +- Important note: if you want to cross-compile from Windows to other systems, e.g. install a Linux cross-compiler on Windows, ensure you installed the {\i Win32} FPC compiler and Lazarus, {\i not the Win64} version, which is known to have troubles with {\f1\fs20 currency} support; - Then build the FPC and Lazarus binaries directly from the latest sources, by clicking on "Install/update FPC+Laz". -Those "fixes" branches are currently used for building our production projects, so are expected to be properly tested and supported. \line At the time of the writing of this documentation, our Lazarus IDE (on Linux) reports using: -- FPC fixes SVN 45428 (3.2.0) -- Lazarus fixes SVN 63179 (2.0.9). +Those branches are currently used for building our production projects, so are expected to be properly tested and supported. \line At the time of the writing of this documentation, our Lazarus IDE (on Linux) reports using: +- FPC SVN 45643 (3.2.0) +- Lazarus SVN 64940 (2.1.0). One big advantage of {\i fpcupdeluxe} is that you can very easily install cross-compilers for the CPU / OS combinations enumerated at @202@.\line Just go to the "Cross" tab, then select the target systems, and click on "Install compiler".\line It may be needed to download the cross-compiler binaries (once): just select "Yes" when prompted. You could install {\i mORMot} using {\i fpcupdeluxe}, but we recommend you clone our @https://github.com/synopse/mORMot repository, and setup the expected project paths, as detailed above at @113@. If you don't want to define a given version, the current {\i trunk} should/could work, if it didn't include any regression at the time you get it - this is why we provide "supported" branches.\line If you want to use the {\i FPC trunk}, please modify line #262 in {\f1\fs20 Synopse.inc} to enable the {\f1\fs20 FPC_PROVIDE_ATTR_TABLE} conditional and support the latest trunk RTTI changes: @@ -16839,7 +16854,7 @@ The {\i Office UI licensing program} was designed by {\i Microsoft} for software If you want to design your user interface using a Office 2007/2010 ribbon look, please take a look at those official guidelines: @http://msdn.microsoft.com/en-us/library/cc872782.aspx Here is the screen content, using the TMS components: %synfiletms.png -And here is the same application compiled using only VCL components, available from {\i Delphi} 6 up to {\i Delphi 10.3 Rio}: +And here is the same application compiled using only VCL components, available from {\i Delphi} 6 up to the latest {\i Delphi} version: %synfilevcl.png We did not use yet the Ribbon component as was introduced in {\i Delphi} 2009. Its action-driven design won't make it easy to interface with the event-driven design of our User Interface handling, and we have to confess that this component has rather bad reputation (at least in the {\i Delphi} 2009 version). Feel free to adapt our Open Source code to use it - we'll be very pleased to release a new version supporting it, but we don't have time nor necessity to do it by ourself. : Enumeration types @@ -18465,7 +18480,7 @@ But please do not forget to put somewhere in your credit window or documentation For instance, if you select the MPL license, here are the requirements: - You accept the license terms with no restriction - see @http://www.mozilla.org/MPL/2.0/FAQ.html for additional information; - You have to publish any modified unit (e.g. {\f1\fs20 SynTaskDialog.pas}) in a public web site (e.g. {\f1\fs20 http://SoftwareCompany.com/MPL}), with a description of applied modifications, and no removal of the original license header in source code; -- You make appear some notice available in the program (About box, documentation, online help), stating e.g.\line {\i This software uses some third-party code of the Synopse mORMot framework (C) 2020 Arnaud Bouchez - {\f1\fs20 https://synopse.info} - under Mozilla Public License 1.1; modified source code is available at {\f1\fs20 http://SoftwareCompany.com/MPL}.} +- You make appear some notice available in the program (About box, documentation, online help), stating e.g.\line {\i This software uses some third-party code of the Synopse mORMot framework (C) 2022 Arnaud Bouchez - {\f1\fs20 https://synopse.info} - under Mozilla Public License 1.1; modified source code is available at {\f1\fs20 http://SoftwareCompany.com/MPL}.} : Derivate Open Source works If you want to include part of the framework source code in your own open-source project, you may publish it with a comment similar to this one (as included in the great {\i DelphiWebScript} project by Eric Grange - @http://code.google.com/p/dwscript ): ${ @@ -18478,7 +18493,7 @@ $ $ Sample based on official mORMot's sample $ "SQLite3\Samples\09 - HttpApi web server\HttpApiServer.dpr" $ -$ Synopse mORMot framework. Copyright (C) 2020 Arnaud Bouchez +$ Synopse mORMot framework. Copyright (C) 2022 Arnaud Bouchez $ Synopse Informatique - https://synopse.info $ $ Original tri-license: MPL 1.1/GPL 2.0/LGPL 2.1 diff --git a/contrib/mORMot/SQLite3/Samples/11 - Exception logging/LogViewMain.lfm b/contrib/mORMot/SQLite3/Samples/11 - Exception logging/LogViewMain.lfm index 77b5835..f7fb22a 100644 --- a/contrib/mORMot/SQLite3/Samples/11 - Exception logging/LogViewMain.lfm +++ b/contrib/mORMot/SQLite3/Samples/11 - Exception logging/LogViewMain.lfm @@ -14,51 +14,42 @@ object MainLogView: TMainLogView OnCreate = FormCreate OnKeyDown = FormKeyDown OnShow = FormShow - LCLVersion = '2.0.8.0' - object Splitter2: TSplitter - Cursor = crVSplit - Left = 0 - Height = 4 - Top = 635 - Width = 860 - Align = alBottom - ResizeAnchor = akBottom - end + LCLVersion = '2.0.11.0' object Splitter3: TSplitter Left = 837 - Height = 635 + Height = 583 Top = 0 Width = 4 Visible = False end object Splitter1: TSplitter Left = 829 - Height = 635 + Height = 583 Top = 0 Width = 4 Visible = False end object Splitter4: TSplitter Left = 833 - Height = 635 + Height = 583 Top = 0 Width = 4 Visible = False end object PanelLeft: TPanel Left = 257 - Height = 635 + Height = 583 Top = 0 Width = 150 Align = alLeft - ClientHeight = 635 + ClientHeight = 583 ClientWidth = 150 Constraints.MinWidth = 150 TabOrder = 0 object ImageLogo: TImage Left = 8 Height = 32 - Top = 591 + Top = 539 Width = 137 Anchors = [akLeft, akRight, akBottom] Center = True @@ -421,9 +412,27 @@ object MainLogView: TMainLogView TopIndex = -1 end end + object Splitter2: TSplitter + Cursor = crVSplit + Left = 0 + Height = 4 + Top = 583 + Width = 860 + Align = alBottom + ResizeAnchor = akBottom + end + object PanelBottom: TPanel + Left = 0 + Height = 52 + Top = 587 + Width = 860 + Align = alBottom + TabOrder = 9 + OnResize = PanelBottomResize + end object List: TDrawGrid Left = 841 - Height = 635 + Height = 583 Top = 0 Width = 19 Align = alClient @@ -447,7 +456,7 @@ object MainLogView: TMainLogView end object ProfileList: TDrawGrid Left = 407 - Height = 635 + Height = 583 Top = 0 Width = 274 Align = alLeft @@ -468,17 +477,17 @@ object MainLogView: TMainLogView end object PanelThread: TPanel Left = 681 - Height = 635 + Height = 583 Top = 0 Width = 148 Align = alLeft - ClientHeight = 635 + ClientHeight = 583 ClientWidth = 148 TabOrder = 3 Visible = False object ThreadListBox: TCheckListBox Left = 1 - Height = 593 + Height = 541 Top = 1 Width = 146 Align = alClient @@ -492,7 +501,7 @@ object MainLogView: TMainLogView object pnlThreadBottom: TPanel Left = 1 Height = 40 - Top = 594 + Top = 542 Width = 146 Align = alBottom ClientHeight = 40 @@ -512,11 +521,11 @@ object MainLogView: TMainLogView end object PanelBrowse: TPanel Left = 0 - Height = 635 + Height = 583 Top = 0 Width = 257 Align = alLeft - ClientHeight = 635 + ClientHeight = 583 ClientWidth = 257 Constraints.MinWidth = 80 TabOrder = 4 diff --git a/contrib/mORMot/SQLite3/Samples/24 - MongoDB/MongoDBTestCases.pas b/contrib/mORMot/SQLite3/Samples/24 - MongoDB/MongoDBTestCases.pas index 8c425c6..a5fc2c7 100644 --- a/contrib/mORMot/SQLite3/Samples/24 - MongoDB/MongoDBTestCases.pas +++ b/contrib/mORMot/SQLite3/Samples/24 - MongoDB/MongoDBTestCases.pas @@ -57,6 +57,7 @@ type fInts: TIntegerDynArray; fCreateTime: TCreateTime; fData: TSQLRawBlob; + fFP: double; published property Name: RawUTF8 read fName write fName stored AS_UNIQUE; property Age: integer read fAge write fAge; @@ -65,6 +66,7 @@ type property Ints: TIntegerDynArray index 1 read fInts write fInts; property Data: TSQLRawBlob read fData write fData; property CreateTime: TCreateTime read fCreateTime write fCreateTime; + property FP: double read fFP write fFP; end; TTestORM = class(TSynTestCase) @@ -183,8 +185,9 @@ begin Check(serverTime<>0); CheckSame(Now,serverTime,0.5); if System.Pos('MongoDB',Owner.CustomVersions)=0 then - Owner.CustomVersions := Owner.CustomVersions+'Using '+ - string(fClient.ServerBuildInfoText); + Owner.CustomVersions := format('%sUsing %s'#13#10'Running on %s'#13#10+ + 'Compiled with mORMot '+SYNOPSE_FRAMEWORK_VERSION, + [Owner.CustomVersions,fClient.ServerBuildInfoText,OSVersionText]); fExpectedCount := COLL_COUNT; end; @@ -452,6 +455,7 @@ begin R.Value := _ObjFast(['num',i]); R.Ints := nil; R.DynArray(1).Add(i); + R.FP := i*7.3445; Check(fClient.BatchAdd(R,True)>=0); end; finally @@ -473,6 +477,7 @@ begin Check(Length(R.Ints)=1); Check(R.Ints[0]=aID); Check(R.CreateTime>=fStartTimeStamp); + CheckSame(R.FP,aID*7.3445); end; procedure TTestORM.Retrieve; diff --git a/contrib/mORMot/SQLite3/Samples/30 - MVC Server/MVCModel.pas b/contrib/mORMot/SQLite3/Samples/30 - MVC Server/MVCModel.pas index ca5b357..57cc4a1 100644 --- a/contrib/mORMot/SQLite3/Samples/30 - MVC Server/MVCModel.pas +++ b/contrib/mORMot/SQLite3/Samples/30 - MVC Server/MVCModel.pas @@ -19,12 +19,14 @@ type fTitle: RawUTF8; fLanguage: RawUTF8; fAbout: RawUTF8; + fLink: RawUTF8; published property Title: RawUTF8 index 80 read fTitle write fTitle; property Language: RawUTF8 index 3 read fLanguage write fLanguage; property Description: RawUTF8 index 120 read fDescription write fDescription; property Copyright: RawUTF8 index 80 read fCopyright write fCopyright; property About: RawUTF8 read fAbout write fAbout; + property Link: RawUTF8 index 60 read fLink write fLink; end; TSQLRecordTimeStamped = class(TSQLRecord) @@ -46,6 +48,7 @@ type fHashedPassword: RawUTF8; fLogonName: RawUTF8; public + function ComputeHash(const PlainPassword: RawUTF8): RawUTF8; virtual; procedure SetPlainPassword(const PlainPassword: RawUTF8); function CheckPlainPassword(const PlainPassword: RawUTF8): boolean; function Name: RawUTF8; @@ -96,6 +99,7 @@ type OrderID: TIntegerDynArray; procedure Init(aRest: TSQLRest); function Get(tagID: integer): RawUTF8; + function GetIDFromIdent(const Ident: RawUTF8): integer; procedure SaveOccurence(aRest: TSQLRest); procedure SortTagsByIdent(var Tags: TIntegerDynArray); function GetAsDocVariantArray: Variant; @@ -106,6 +110,7 @@ type fAbstract: RawUTF8; fPublishedMonth: Integer; fTags: TIntegerDynArray; + fLegacyHash: Int64; public class function CurrentPublishedMonth: Integer; class procedure InitializeTable(Server: TSQLRestServer; const FieldName: RawUTF8; @@ -118,6 +123,8 @@ type property Abstract: RawUTF8 read fAbstract write fAbstract; // "index 1" below to allow writing e.g. aArticle.DynArray(1).Delete(aIndex) property Tags: TIntegerDynArray index 1 read fTags write fTags; + // xxhash32 of legacy post_url + property LegacyHash: Int64 read fLegacyHash write fLegacyHash; end; TSQLArticleSearch = class(TSQLRecordFTS4Porter) @@ -157,6 +164,8 @@ procedure DotClearFlatImport(Rest: TSQLRest; const aFlatFile: RawUTF8; var aTagsLookup: TSQLTags; const aDotClearRoot: RawUTF8; const aStaticFolder: TFileName); +function ComputeLegacyHash(url: PUTF8Char): cardinal; + implementation @@ -176,12 +185,16 @@ end; { TSQLSomeone } -const - SALT = 'mORMot'; +function TSQLSomeone.ComputeHash(const PlainPassword: RawUTF8): RawUTF8; +var dig: THash256; +begin + PBKDF2_SHA3(SHA3_224,PlainPassword,LogonName+'@mORMot',30,@dig); + BinToHexLower(@dig,28,result); +end; function TSQLSomeone.CheckPlainPassword(const PlainPassword: RawUTF8): boolean; begin - result := fHashedPassword=SHA256(SALT+LogonName+PlainPassword); + result := fHashedPassword=ComputeHash(PlainPassword); end; function TSQLSomeone.Name: RawUTF8; @@ -191,7 +204,7 @@ end; procedure TSQLSomeone.SetPlainPassword(const PlainPassword: RawUTF8); begin - fHashedPassword := SHA256(SALT+LogonName+PlainPassword); + fHashedPassword := ComputeHash(PlainPassword); end; @@ -233,6 +246,8 @@ begin inherited; if (FieldName='') or (FieldName='PublishedMonth') then Server.CreateSQLIndex(TSQLArticle,'PublishedMonth',false); + if (FieldName='') or (FieldName='LegacyHash') then + Server.CreateSQLIndex(TSQLArticle,'LegacyHash',false); end; procedure TSQLArticle.SetPublishedMonth(FromTime: TTimeLog); @@ -260,8 +275,20 @@ begin result := ''; end; +function TSQLTags.GetIDFromIdent(const Ident: RawUTF8): integer; +var i: PtrInt; +begin + if Ident<>'' then + for i := 0 to length(Lookup)-1 do + if IdemPropNameU(Lookup[i].Ident,Ident) then begin + result := i+1; + exit; + end; + result := 0; +end; + function TSQLTags.GetAsDocVariantArray: Variant; -var i,ndx: Integer; +var i,ndx: PtrInt; begin TDocVariant.NewFast(result); with Lock.ProtectMethod do @@ -438,21 +465,46 @@ begin until P=nil; end; +function HttpGet(const aURI: SockString; outHeaders: PSockString=nil; + forceNotSocket: boolean=false; outStatus: PInteger=nil): SockString; +begin + result := ''; + if outStatus<>nil then + outStatus^ := 404; +end; + +function ComputeLegacyHash(url: PUTF8Char): cardinal; +var c: ansichar; +begin + result := 0; + if url<>nil then + repeat + case url^ of + #0: exit; + 'a'..'z', 'A'..'Z', '0'..'9': begin + c := upcase(url^); + result := crc32c(result, @c, 1); + end; + end; + inc(url); + until false; +end; + procedure DotClearFlatImport(Rest: TSQLRest; const aFlatFile: RawUTF8; var aTagsLookup: TSQLTags; const aDotClearRoot: RawUTF8; const aStaticFolder: TFileName); var T,tagTable,postTable: TDotClearTable; - data,urls: TRawUTF8List; + data: TRawUTF8List; + urls: TIntegerDynArray; info: TSQLBlogInfo; article: TSQLArticle; comment: TSQLComment; tag: TSQLTag; - tags: TRawUTF8DynArray; + tags, notfound: TRawUTF8DynArray; tagID: TIDDynArray; tagsCount: integer; batch: TSQLRestBatch; PublicFolder: TFileName; - notfound: TRawUTF8DynArray; r,ndx,post_url,meta_id,meta_type,tag_post_id,postID,post_id: integer; function FixLinks(P: PUTF8Char): RawUTF8; @@ -501,13 +553,20 @@ var T,tagTable,postTable: TDotClearTable; continue; AddNoJSONEscape(B,H-B); P := H; + if IdemPChar(P,'HTTP://BLOG.SYNOPSE.INFO/') then + inc(P,24) + else if IdemPChar(P,'HTTPS://BLOG.SYNOPSE.INFO/') then + inc(P,25); if IdemPChar(P,'HTTP://SYNOPSE.INFO') then begin AddShort('https://synopse.info'); inc(P,19); end else if P^='/' then begin + if P[1]='?' then + inc(P); if IdemPChar(P+1,'POST/') then begin GetUrl(P+6); - i := urls.IndexOf(urlnoparam); + i := IntegerScanIndex(pointer(urls),length(urls), + ComputeLegacyHash(pointer(urlnoparam))); if i>=0 then begin AddShort('articleView?id='); Add(i+1); @@ -582,7 +641,6 @@ begin end; auto1 := TAutoFree.Several([ @data,TDotClearTable.Parse(aFlatFile), - @urls,TRawUTF8ListHashed.Create, @batch,TSQLRestBatch.Create(Rest,TSQLTag,5000)]); auto2 := TSQLRecord.AutoFree([ // avoid several try..finally @info,TSQLBlogInfo, @article,TSQLArticle, @comment,TSQLComment, @tag,TSQLTag]); @@ -614,7 +672,7 @@ begin post_url := postTable.FieldIndexExisting('post_url'); if postTable.Step(true) then repeat - urls.Add(postTable.FieldBuffer(post_url)); + AddInteger(urls,ComputeLegacyHash(postTable.FieldBuffer(post_url))); until not postTable.Step; article.Author := TSQLAuthor(1); article.AuthorName := 'synopse'; @@ -631,6 +689,7 @@ begin article.ModifiedAt := Iso8601ToTimeLog(postTable.GetU(r,'post_upddt')); article.SetPublishedMonth(article.CreatedAt); postID := postTable.GetAsInteger(r,post_id); + article.LegacyHash := ComputeLegacyHash(postTable.Get(r,post_url)); article.Tags := nil; if tagTable.Step(true) then repeat diff --git a/contrib/mORMot/SQLite3/Samples/30 - MVC Server/MVCServer.dpr b/contrib/mORMot/SQLite3/Samples/30 - MVC Server/MVCServer.dpr index 9262703..bc05d6f 100644 --- a/contrib/mORMot/SQLite3/Samples/30 - MVC Server/MVCServer.dpr +++ b/contrib/mORMot/SQLite3/Samples/30 - MVC Server/MVCServer.dpr @@ -38,17 +38,25 @@ var aModel: TSQLModel; aApplication: TBlogApplication; aHTTPServer: TSQLHttpServer; begin - //with TSQLLog.Family do Level := LOG_VERBOSE; + with TSQLLog.Family do begin + Level := LOG_VERBOSE; + PerThreadLog := ptIdentifiedInOnFile; + RotateFileCount := 10; + RotateFileSizeKB := 20 shl 10; + FileExistsAction := acAppend; // as expected by rotation + end; aModel := CreateModel; try aServer := TSQLRestServerDB.Create(aModel,ChangeFileExt(ExeVersion.ProgramFileName,'.db')); try aServer.DB.Synchronous := smNormal; aServer.DB.LockingMode := lmExclusive; + aServer.Options := aServer.Options+[rsoNoTableURI]; aServer.CreateMissingTables; aApplication := TBlogApplication.Create; try aApplication.Start(aServer); + aServer.ServiceMethodRegisterPublishedMethods('', aApplication); aHTTPServer := TSQLHttpServer.Create('8092',aServer {$ifndef ONLYUSEHTTPSOCKET},'+',useHttpApiRegisteringURI{$endif}); try diff --git a/contrib/mORMot/SQLite3/Samples/30 - MVC Server/MVCViewModel.pas b/contrib/mORMot/SQLite3/Samples/30 - MVC Server/MVCViewModel.pas index 9b04a97..dfc2678 100644 --- a/contrib/mORMot/SQLite3/Samples/30 - MVC Server/MVCViewModel.pas +++ b/contrib/mORMot/SQLite3/Samples/30 - MVC Server/MVCViewModel.pas @@ -12,6 +12,7 @@ uses SynCommons, SynLog, SynTests, + SynCrtSock, mORMot, mORMotMVC, MVCModel; @@ -31,8 +32,9 @@ type out Comments: TObjectList); procedure AuthorView( var ID: TID; out Author: TSQLAuthor; out Articles: variant); - function Login( - const LogonName,PlainPassword: RawUTF8): TMVCAction; + procedure LoginView; + function Login(const LogonName,PlainPassword, + NewPlainPassword1,NewPlainPassword2: RawUTF8): TMVCAction; function Logout: TMVCAction; function ArticleComment(ID: TID; const Title,Comment: RawUTF8): TMVCAction; function ArticleMatch(const Match: RawUTF8): TMVCAction; @@ -71,7 +73,13 @@ type procedure TagToText(const Value: variant; out result: variant); public procedure Start(aServer: TSQLRestServer); reintroduce; + published + // low-level blog/post blog/tag blog/rss endpoints + procedure Post(Ctxt: TSQLRestServerURIContext); + procedure Tag(Ctxt: TSQLRestServerURIContext); + procedure Rss(Ctxt: TSQLRestServerURIContext); public + // IBlogApplication implemented methods procedure Default(var Scope: variant); procedure ArticleView(ID: TID; var WithComments: boolean; Direction: integer; var Scope: variant; @@ -79,7 +87,9 @@ type out Comments: TObjectList); procedure AuthorView( var ID: TID; out Author: TSQLAuthor; out Articles: variant); - function Login(const LogonName,PlainPassword: RawUTF8): TMVCAction; + procedure LoginView; + function Login(const LogonName,PlainPassword, + NewPlainPassword1,NewPlainPassword2: RawUTF8): TMVCAction; function Logout: TMVCAction; function ArticleComment(ID: TID; const Title,Comment: RawUTF8): TMVCAction; function ArticleMatch(const Match: RawUTF8): TMVCAction; @@ -130,6 +140,10 @@ begin finally Free; end; + _Safe(fBlogMainInfo)^.AddValue('engine',RawUTF8ToVariant( + 'Website powered by mORMot MVC '+SYNOPSE_FRAMEWORK_VERSION+ + ', compiled with '+GetDelphiCompilerVersion+ + ', running on '+RawUTF8(ToText(OSVersion32))+'.')); end; procedure TBlogApplication.MonthToText(const Value: variant; @@ -172,29 +186,28 @@ begin auto := TSQLRecord.AutoFree([ // avoid several try..finally @info,TSQLBlogInfo, @article,TSQLArticle, @comment,TSQLComment, @tag,TSQLTag]); if not RestModel.Retrieve('',info) then begin // retrieve first item - tmp := StringFromFile('/home/ab/Downloads/2020-06-16-a8003957c2ae6bde5be6ea279c9c9ce4-backup.txt'); + tmp := StringFromFile(ExeVersion.ProgramFilePath+'2021-01-20-16-37-default-backup.txt'); info.Language := 'en'; if tmp<>'' then begin info.Title := 'Synopse Blog'; info.Description := 'Articles, announcements, news, updates and more '+ - 'about our Open Source projects'; + 'about Synopse Open Source projects'; info.About := 'Latest information about Synopse Open Source librairies, '+ 'mainly the mORMot ORM/SOA/MVC framework, and SynPDF.'; + info.Link := 'https://blog.synopse.info'; end else begin info.Title := 'mORMot BLOG'; info.Description := 'Sample Blog Web Application using Synopse mORMot MVC'; info.About := TSynTestCase.RandomTextParagraph(10,'!'); + info.Link := 'http://localhost:8092'; end; - info.About := info.About+#13#10'Website powered by mORMot MVC '+ - SYNOPSE_FRAMEWORK_VERSION+', compiled with '+GetDelphiCompilerVersion+ - ', running on '+ToText(OSVersion32)+'.'; info.Copyright := '©'+ToUTF8(CurrentYear)+'Synopse Informatique'; RestModel.Add(info,true); end; if RestModel.TableHasRows(TSQLArticle) then exit; if tmp<>'' then begin - DotClearFlatImport(RestModel,tmp,fTagsLookup,'http://blog.synopse.info', + DotClearFlatImport(RestModel,tmp,fTagsLookup,'https://blog.synopse.info', (TMVCRunOnRestServer(fMainRunner).Views as TMVCViewsMustache).ViewStaticFolder); exit; end; @@ -271,20 +284,24 @@ begin fDefaultData.AddNewProp('tags',fTagsLookup.GetAsDocVariantArray,info); end; -procedure TBlogApplication.FlushAnyCache; -begin - inherited FlushAnyCache; // call fMainRunner.NotifyContentChanged - fDefaultData.Clear; -end; - - -{ TBlogApplication - Commands } - const ARTICLE_FIELDS = 'RowID,Title,Tags,Abstract,ContentHtml,Author,AuthorName,CreatedAt'; ARTICLE_DEFAULT_LIMIT = ' limit 20'; ARTICLE_DEFAULT_ORDER: RawUTF8 = 'order by RowID desc'+ARTICLE_DEFAULT_LIMIT; +procedure TBlogApplication.FlushAnyCache; +begin + inherited FlushAnyCache; // call fMainRunner.NotifyContentChanged + fDefaultData.Clear; + // get last 20 articles + fDefaultData.SetValue('Articles', + RestModel.RetrieveDocVariantArray(TSQLArticle,'', + ARTICLE_DEFAULT_ORDER,[],ARTICLE_FIELDS,nil,@fDefaultLastID)); +end; + + +{ TBlogApplication - Commands } + procedure TBlogApplication.Default(var Scope: variant); var scop: PDocVariantData; lastID: TID; @@ -320,14 +337,12 @@ begin whereClause := whereClause+' and IntegerDynArrayContains(Tags,?)'; end; SetVariantNull(Scope); - if (lastID=0) and (tag=0) then begin // use simple cache if no parameters - if not fDefaultData.AddExistingProp('Articles',Scope) then begin - articles := RestModel.RetrieveDocVariantArray(TSQLArticle,'', - ARTICLE_DEFAULT_ORDER,[],ARTICLE_FIELDS,nil,@fDefaultLastID); - fDefaultData.AddNewProp('Articles',articles,Scope); - end; + if (lastID=0) and (tag=0) then begin + // use simple cache if no parameters + fDefaultData.AddExistingProp('Articles',Scope); // set by FlushAnyCache lastID := fDefaultLastID; - end else begin // use more complex request using lastID + tag parameters + end else begin + // use more complex request using lastID + tag parameters articles := RestModel.RetrieveDocVariantArray(TSQLArticle,'', whereClause+ARTICLE_DEFAULT_ORDER,[lastID,tag],ARTICLE_FIELDS,nil,@lastID); scope := _ObjFast(['Articles',articles]); @@ -370,12 +385,31 @@ begin raise EMVCApplication.CreateGotoError(HTTP_NOTFOUND); end; -function TBlogApplication.Login(const LogonName, PlainPassword: RawUTF8): TMVCAction; +procedure TBlogApplication.LoginView; +begin +end; + +function TBlogApplication.Login(const LogonName, PlainPassword, + NewPlainPassword1, NewPlainPassword2: RawUTF8): TMVCAction; var Author: TSQLAuthor; SessionInfo: TCookieData; + newpwd: RawUTF8; begin - if CurrentSession.CheckAndRetrieve<>0 then begin - GotoError(result,HTTP_BADREQUEST); + if LogonName='' then begin + GotoView(result,'LoginView',[]); + exit; + end; + newpwd := Trim(NewPlainPassword1); + if newpwd<>'' then begin + if (newpwd<>NewPlainPassword2) or + (newpwd=PlainPassword) or + (CurrentSession.CheckAndRetrieve(@SessionInfo,TypeInfo(TCookieData))=0) or + (SessionInfo.AuthorName<>LogonName) then begin + GotoError(result,HTTP_NOTACCEPTABLE); + exit; + end; + end else if CurrentSession.CheckAndRetrieve<>0 then begin + GotoError(result,'Already Logged In',HTTP_BADREQUEST); exit; end; Author := TSQLAuthor.Create(RestModel,'LogonName=?',[LogonName]); @@ -385,6 +419,10 @@ begin SessionInfo.AuthorID := Author.ID; SessionInfo.AuthorRights := Author.Rights; CurrentSession.Initialize(@SessionInfo,TypeInfo(TCookieData)); + if newpwd<>'' then begin + Author.SetPlainPassword(newpwd); + RestModel.Update(Author,'HashedPassword'); + end; GotoDefault(result); end else GotoError(result,sErrorInvalidLogin); @@ -481,6 +519,83 @@ begin end; end; +procedure TBlogApplication.Post(Ctxt: TSQLRestServerURIContext); +var hash, id: Int64; +begin + hash := ComputeLegacyHash(pointer(UrlDecode(Ctxt.URIAfterRoot,5,-1))); + id := RestModel.OneFieldValueInt64(TSQLArticle,'ID', + FormatUTF8('LegacyHash=:(%):', [hash])); + Ctxt.Redirect(FormatUTF8('/%/articleview?id=%',[RestModel.Model.Root,id])); +end; + +procedure TBlogApplication.Tag(Ctxt: TSQLRestServerURIContext); +var + id: integer; +begin + id := fTagsLookup.GetIDFromIdent(copy(Ctxt.UriAfterRoot, 5, 100)); + Ctxt.Redirect(FormatUTF8('/%/default?scope={tag:%}',[RestModel.Model.Root,id])); +end; + +function Esc(const Msg: RawUTF8): RawUTF8; +var i: integer; + ins: RawUTF8; +begin + // fast enough for our purpose to compute some RSS cache + result := Msg; + for i := length(Msg) downto 1 do begin + case Msg[i] of + '"': ins := '"'; + '&': ins := '&'; + '<': ins := '<'; + '>': ins := '>'; + else Continue; + end; + result[i] := ';'; + insert(ins,result,i); + end; +end; + +procedure TBlogApplication.Rss(Ctxt: TSQLRestServerURIContext); + function ComputeRss: variant; + var xml, lng, link: RawUTF8; + art: integer; + begin + with _Safe(fBlogMainInfo)^ do + begin + link := U['Link']; + if (link<>'') and (link[length(link)]='/') then + SetLength(link,length(link)-1); + lng := U['Language']; + if lng='' then + lng := 'en_US'; + FormatUTF8(''+ + '%'+ + '%%'+ + '%%', + [Esc(U['Title']),link,Esc(U['Description']), + DateTimeToHTTPDate(NowUTC,'+0000'),lng],xml); + end; + with _Safe(fDefaultData.GetValue('Articles'))^ do + for art := 0 to Count-1 do + with _Safe(Values[art])^ do + xml := FormatUTF8('%'#13'%'+ + '%/articleview?id=%%blog'+ + '%'+ + '', + [xml,Esc(U['Title']),link,I['ID'], + DateTimeToHTTPDate(TimeLogToDateTime(I['CreatedAt']),'+0000'), + Esc(U['AuthorName']),U['Abstract'],U['Content']]); + RawUTF8ToVariant(xml+'',result); + end; +var + rss: variant; +begin + if not fDefaultData.ExistsOrLock('rss',rss) then + fDefaultData.ReplaceAndUnlock('rss',ComputeRss,rss); + Ctxt.Returns(ToUTF8(rss),HTTP_SUCCESS, + HEADER_CONTENT_TYPE+'application/rss+xml; charset=UTF-8',{handle304=}true); +end; + initialization {$ifndef DELPHI2010} // manual definition mandatory only if Delphi 2010 RTTI is not available diff --git a/contrib/mORMot/SQLite3/Samples/30 - MVC Server/Views/ArticleView.html b/contrib/mORMot/SQLite3/Samples/30 - MVC Server/Views/ArticleView.html index c4536ff..210a0da 100644 --- a/contrib/mORMot/SQLite3/Samples/30 - MVC Server/Views/ArticleView.html +++ b/contrib/mORMot/SQLite3/Samples/30 - MVC Server/Views/ArticleView.html @@ -1,51 +1,51 @@ {{>header}} {{>masthead}} -
-

{{article.title}}

-
- Written by {{article.AuthorName}} ({{author.FirstName}} {{author.FamilyName}}) on {{TimeLogToText article.CreatedAt}}
- {{#article.tags}}{{TagToText .}} {{/article.tags}} -
-
-
-
- {{#article}} - {{#ContentHtml}}{{{abstract}}}{{/ContentHtml}}{{^ContentHtml}}{{{WikiToHtml abstract}}}{{/ContentHtml}} -
- {{#ContentHtml}}{{{content}}}{{/ContentHtml}}{{^ContentHtml}}{{{WikiToHtml content}}}{{/ContentHtml}} - {{/article}} -
- - - {{#WithComments}} - {{#Comments}} -
-

{{Title}}

-

{{#ContentHtml}}{{{Content}}}{{/ContentHtml}}{{^ContentHtml}}{{{WikiToHtml Content}}}{{/ContentHtml}}

-
-
- {{/Comments}} - {{^Comments}}
No comment yet.
{{/Comments}} -

Hide Comments

- {{#main.session.AuthorRights.Comment}} - -
-
- {{#Scope}}
{{CommentError}}
{{/Scope}} - - - -
-
- {{/main.session.AuthorRights.Comment}} -

- {{/WithComments}} - {{^WithComments}} -

Show Comments

- {{/WithComments}} +
+

{{article.title}}

+
+ Written by {{article.AuthorName}} ({{author.FirstName}} {{author.FamilyName}}) on {{TimeLogToText article.CreatedAt}}
+ {{#article.tags}}{{TagToText .}} {{/article.tags}} +
+
+
+
+ {{#article}} + {{#ContentHtml}}{{{abstract}}}{{/ContentHtml}}{{^ContentHtml}}{{{WikiToHtml abstract}}}{{/ContentHtml}} +
+ {{#ContentHtml}}{{{content}}}{{/ContentHtml}}{{^ContentHtml}}{{{WikiToHtml content}}}{{/ContentHtml}} + {{/article}} +
+ + + {{#WithComments}} + {{#Comments}} +
+

{{Title}}

+

{{#ContentHtml}}{{{Content}}}{{/ContentHtml}}{{^ContentHtml}}{{{WikiToHtml Content}}}{{/ContentHtml}}

+
+
+ {{/Comments}} + {{^Comments}}
No comment yet.
{{/Comments}} +

Hide Comments

+ {{#main.session.AuthorRights.Comment}} + +
+
+ {{#Scope}}
{{CommentError}}
{{/Scope}} + + + +
+
+ {{/main.session.AuthorRights.Comment}} +

+ {{/WithComments}} + {{^WithComments}} +

Show Comments

+ {{/WithComments}} {{>footer}} \ No newline at end of file diff --git a/contrib/mORMot/SQLite3/Samples/30 - MVC Server/Views/AuthorView.html b/contrib/mORMot/SQLite3/Samples/30 - MVC Server/Views/AuthorView.html index 0b0dca9..2f8dfee 100644 --- a/contrib/mORMot/SQLite3/Samples/30 - MVC Server/Views/AuthorView.html +++ b/contrib/mORMot/SQLite3/Samples/30 - MVC Server/Views/AuthorView.html @@ -1,15 +1,25 @@ {{>header}} {{>masthead}} -
-

User {{Author.LogonName}}

-
{{Author.FirstName}} {{Author.FamilyName}} -
-
-
-
-
Information about {{Author.LogonName}}
-
- {{{TSQLAuthor.HtmlTable Author}}} -
+
+

User {{Author.LogonName}}

+
{{Author.FirstName}} {{Author.FamilyName}} +
+
+
+
+
Information about {{Author.LogonName}}
+
+ {{{TSQLAuthor.HtmlTable Author}}} + {{#main.session}} + + {{/main.session}} +
{{>articlerow}} {{>footer}} \ No newline at end of file diff --git a/contrib/mORMot/SQLite3/Samples/30 - MVC Server/Views/footer.partial b/contrib/mORMot/SQLite3/Samples/30 - MVC Server/Views/footer.partial index 48b5f19..2480572 100644 --- a/contrib/mORMot/SQLite3/Samples/30 - MVC Server/Views/footer.partial +++ b/contrib/mORMot/SQLite3/Samples/30 - MVC Server/Views/footer.partial @@ -22,8 +22,23 @@
\ No newline at end of file diff --git a/contrib/mORMot/SQLite3/Samples/30 - MVC Server/Views/masthead.partial b/contrib/mORMot/SQLite3/Samples/30 - MVC Server/Views/masthead.partial index a56a4bf..9343fe5 100644 --- a/contrib/mORMot/SQLite3/Samples/30 - MVC Server/Views/masthead.partial +++ b/contrib/mORMot/SQLite3/Samples/30 - MVC Server/Views/masthead.partial @@ -3,44 +3,23 @@
diff --git a/contrib/mORMot/SQLite3/Samples/31 - WebSockets/Project31ChatClient.dpr b/contrib/mORMot/SQLite3/Samples/31 - WebSockets/Project31ChatClient.dpr index 799d71b..349943c 100644 --- a/contrib/mORMot/SQLite3/Samples/31 - WebSockets/Project31ChatClient.dpr +++ b/contrib/mORMot/SQLite3/Samples/31 - WebSockets/Project31ChatClient.dpr @@ -8,6 +8,7 @@ uses SysUtils, Classes, SynCommons, + SynTable, mORMot, mORMotHttpClient, Project31ChatCallbackInterface in 'Project31ChatCallbackInterface.pas'; diff --git a/contrib/mORMot/SQLite3/Samples/33 - ECC/ECCProcess.pas b/contrib/mORMot/SQLite3/Samples/33 - ECC/ECCProcess.pas index 7088813..7068bfc 100644 --- a/contrib/mORMot/SQLite3/Samples/33 - ECC/ECCProcess.pas +++ b/contrib/mORMot/SQLite3/Samples/33 - ECC/ECCProcess.pas @@ -3,7 +3,7 @@ unit ECCProcess; (* This file is part of Synopse framework. - Synopse framework. Copyright (C) 2020 Arnaud Bouchez + Synopse framework. Copyright (C) 2022 Arnaud Bouchez Synopse Informatique - https://synopse.info *** BEGIN LICENSE BLOCK ***** @@ -22,7 +22,7 @@ unit ECCProcess; The Initial Developer of the Original Code is Arnaud Bouchez. - Portions created by the Initial Developer are Copyright (C) 2020 + Portions created by the Initial Developer are Copyright (C) 2022 the Initial Developer. All Rights Reserved. Contributor(s): diff --git a/contrib/mORMot/SQLite3/Samples/36 - Simple REST Benchmark/RESTBenchmark.dpr b/contrib/mORMot/SQLite3/Samples/36 - Simple REST Benchmark/RESTBenchmark.dpr index 0a82d99..ddad0a9 100644 --- a/contrib/mORMot/SQLite3/Samples/36 - Simple REST Benchmark/RESTBenchmark.dpr +++ b/contrib/mORMot/SQLite3/Samples/36 - Simple REST Benchmark/RESTBenchmark.dpr @@ -28,7 +28,7 @@ uses BaseUnix, {$endif} {$ifdef LINUXNOTBSD} - SynSystemd, + SynFPCLinux, mORMotService, {$endif} mORMotHttpServer; // HTTP server for RESTful server @@ -163,12 +163,12 @@ const UNIX_SOCK_PATH = '/tmp/rest-bench.socket'; {$ifdef LINUX} -/// killa process after X second without GEt requests +/// killa process after X second without Get requests function inactivityWatchdog(p: pointer): ptrint; var currentRC: TSynMonitorCount64; begin repeat - sleep(10000); /// once per 10 second + sleep(10000); // once per 10 second if aRestServer = nil then // not initialized continue; currentRC := aRestServer.Stats.Read; @@ -182,6 +182,7 @@ begin Result := 0; end; {$endif} + begin // set logging abilities SQLite3Log.Family.Level := LOG_VERBOSE; @@ -190,10 +191,10 @@ begin SQLite3Log.Family.NoFile := true; // do not create log files for benchmark {$ifdef UNIX} {$ifdef LINUXNOTBSD} - if SynSystemd.ProcessIsStartedBySystemd then begin + if ProcessIsStartedBySystemd then begin SQLite3Log.Family.EchoToConsole := SQLite3Log.Family.Level; SQLite3Log.Family.EchoToConsoleUseJournal := true; - if sd.listen_fds(0) = 1 then + if ExternalLibraries.sd_listen_fds(0) = 1 then url := '' // force to use socket passed by systemd else url := '8888'; diff --git a/contrib/mORMot/SQLite3/Samples/36 - Simple REST Benchmark/RESTBenchmark.lpi b/contrib/mORMot/SQLite3/Samples/36 - Simple REST Benchmark/RESTBenchmark.lpi index 3661fca..2d9fc39 100644 --- a/contrib/mORMot/SQLite3/Samples/36 - Simple REST Benchmark/RESTBenchmark.lpi +++ b/contrib/mORMot/SQLite3/Samples/36 - Simple REST Benchmark/RESTBenchmark.lpi @@ -43,6 +43,15 @@ + + + + + + + + + diff --git a/contrib/mORMot/SQLite3/Samples/ThirdPartyDemos/EMartin/SynJSONTreeview/SynJSONTreeView.pas b/contrib/mORMot/SQLite3/Samples/ThirdPartyDemos/EMartin/SynJSONTreeview/SynJSONTreeView.pas index b76bca2..3324d2f 100644 --- a/contrib/mORMot/SQLite3/Samples/ThirdPartyDemos/EMartin/SynJSONTreeview/SynJSONTreeView.pas +++ b/contrib/mORMot/SQLite3/Samples/ThirdPartyDemos/EMartin/SynJSONTreeview/SynJSONTreeView.pas @@ -7,7 +7,7 @@ unit SynJSONTreeView; { This file is part of Synopse framework. - Synopse framework. Copyright (C) 2020 Arnaud Bouchez + Synopse framework. Copyright (C) 2022 Arnaud Bouchez Synopse Informatique - https://synopse.info *** BEGIN LICENSE BLOCK ***** @@ -26,7 +26,7 @@ unit SynJSONTreeView; The Initial Developer of the Original Code is Arnaud Bouchez. - Portions created by the Initial Developer are Copyright (C) 2020 + Portions created by the Initial Developer are Copyright (C) 2022 the Initial Developer. All Rights Reserved. Contributor(s): diff --git a/contrib/mORMot/SQLite3/Samples/ThirdPartyDemos/EMartin/TSynRestDataset/SynRestMidasVCL.pas b/contrib/mORMot/SQLite3/Samples/ThirdPartyDemos/EMartin/TSynRestDataset/SynRestMidasVCL.pas index e0ca5a9..c2b9dbe 100644 --- a/contrib/mORMot/SQLite3/Samples/ThirdPartyDemos/EMartin/TSynRestDataset/SynRestMidasVCL.pas +++ b/contrib/mORMot/SQLite3/Samples/ThirdPartyDemos/EMartin/TSynRestDataset/SynRestMidasVCL.pas @@ -6,7 +6,7 @@ unit SynRestMidasVCL; { This file is part of Synopse framework. - Synopse framework. Copyright (C) 2020 Arnaud Bouchez + Synopse framework. Copyright (C) 2022 Arnaud Bouchez Synopse Informatique - https://synopse.info *** BEGIN LICENSE BLOCK ***** @@ -25,7 +25,7 @@ unit SynRestMidasVCL; The Initial Developer of the Original Code is Arnaud Bouchez. - Portions created by the Initial Developer are Copyright (C) 2020 + Portions created by the Initial Developer are Copyright (C) 2022 the Initial Developer. All Rights Reserved. Contributor(s): diff --git a/contrib/mORMot/SQLite3/Samples/ThirdPartyDemos/EMartin/TSynRestDataset/SynRestVCL.pas b/contrib/mORMot/SQLite3/Samples/ThirdPartyDemos/EMartin/TSynRestDataset/SynRestVCL.pas index 11547b1..6fe61cf 100644 --- a/contrib/mORMot/SQLite3/Samples/ThirdPartyDemos/EMartin/TSynRestDataset/SynRestVCL.pas +++ b/contrib/mORMot/SQLite3/Samples/ThirdPartyDemos/EMartin/TSynRestDataset/SynRestVCL.pas @@ -6,7 +6,7 @@ unit SynRestVCL; { This file is part of Synopse framework. - Synopse framework. Copyright (C) 2020 Arnaud Bouchez + Synopse framework. Copyright (C) 2022 Arnaud Bouchez Synopse Informatique - https://synopse.info *** BEGIN LICENSE BLOCK ***** @@ -25,7 +25,7 @@ unit SynRestVCL; The Initial Developer of the Original Code is Arnaud Bouchez. - Portions created by the Initial Developer are Copyright (C) 2020 + Portions created by the Initial Developer are Copyright (C) 2022 the Initial Developer. All Rights Reserved. Contributor(s): diff --git a/contrib/mORMot/SQLite3/Samples/ThirdPartyDemos/George/REST-tester/mORMotRESTFPCInterfaces.pas b/contrib/mORMot/SQLite3/Samples/ThirdPartyDemos/George/REST-tester/mORMotRESTFPCInterfaces.pas index b4107fc..313d9f8 100644 --- a/contrib/mORMot/SQLite3/Samples/ThirdPartyDemos/George/REST-tester/mORMotRESTFPCInterfaces.pas +++ b/contrib/mORMot/SQLite3/Samples/ThirdPartyDemos/George/REST-tester/mORMotRESTFPCInterfaces.pas @@ -7,7 +7,7 @@ unit mORMotRESTFPCInterfaces; This unit has been generated by a mORMot 1.18.2797 server. Any manual modification of this file may be lost after regeneration. - Synopse mORMot framework. Copyright (C) 2020 Arnaud Bouchez + Synopse mORMot framework. Copyright (C) 2022 Arnaud Bouchez Synopse Informatique - https://synopse.info This unit is released under a MPL/GPL/LGPL tri-license, diff --git a/contrib/mORMot/SQLite3/Samples/ThirdPartyDemos/Ondrej/SynTaskDialog4Lazarus/FMXUtil.inc.pas b/contrib/mORMot/SQLite3/Samples/ThirdPartyDemos/Ondrej/SynTaskDialog4Lazarus/FMXUtil.inc.pas index 9f6bf27..324c72a 100644 --- a/contrib/mORMot/SQLite3/Samples/ThirdPartyDemos/Ondrej/SynTaskDialog4Lazarus/FMXUtil.inc.pas +++ b/contrib/mORMot/SQLite3/Samples/ThirdPartyDemos/Ondrej/SynTaskDialog4Lazarus/FMXUtil.inc.pas @@ -1,10 +1,57 @@ +{$IF (CompilerVersion <= 25)} +type + //other name of constants in XE4 + TStyledSettingHelper = record helper for TStyledSetting + const Family = TStyledSetting.ssFamily; + const Size = TStyledSetting.ssSize; + const Style = TStyledSetting.ssStyle; + const FontColor = TStyledSetting.ssFontColor; + const Other = TStyledSetting.ssOther; + end; + + TTextAlignHelper = record helper for TTextAlign + const Center = TTextAlign.taCenter; + const Leading = TTextAlign.taLeading; + const Trailing = TTextAlign.taTrailing; + end; + + TFmxFormBorderStyleHelper = record helper for TFmxFormBorderStyle + const None = TFmxFormBorderStyle.bsNone; + const Single = TFmxFormBorderStyle.bsSingle; + const Sizeable = TFmxFormBorderStyle.bsSizeable; + const ToolWindow = TFmxFormBorderStyle.bsToolWindow; + const SizeToolWin = TFmxFormBorderStyle.bsSizeToolWin; + end; + + TFormPositionHelper = record helper for TFormPosition + //(poDesigned, poDefault, poDefaultPosOnly, poDefaultSizeOnly, + //poScreenCenter, poDesktopCenter, poMainFormCenter, poOwnerFormCenter); + const OwnerFormCenter = TFormPosition.poOwnerFormCenter; + const ScreenCenter = TFormPosition.poScreenCenter; + end; + + TBrushKindHelper = record helper for TBrushKind + //(bkNone, bkSolid, bkGradient, bkBitmap, bkResource); + const None = TBrushKind.bkNone; + end; + + TAlignLayoutHelper = record helper for TAlignLayout + //(alNone, alTop, alLeft, alRight, alBottom, alMostTop, alMostBottom, alMostLeft, alMostRight, alClient, + //alContents, alCenter, alVertCenter, alHorzCenter, alHorizontal, alVertical, alScale, alFit, alFitLeft, alFitRight); + const Top = TAlignLayout.alTop; + end; + +{$IFEND} + var _ScreenDPI_X : Single = 0; function ScalingByScreenDPI_N( F:TForm = NIL ):Single; var p : TPointF; + {$IF (CompilerVersion >= 28)} M : TDeviceDisplayMetrics; + {$IFEND} i : integer; h : THandle; begin @@ -29,6 +76,7 @@ begin end; {$ENDIF} + {$IF (CompilerVersion >= 28)} //TDeviceDisplayMetrics is available since XE8 if TPlatformServices.Current.SupportsPlatformService( IFMXDeviceMetricsService ) then begin M := (TPlatformServices.Current.GetPlatformService( @@ -38,6 +86,7 @@ begin {$IFDEF MSWINDOWS}96{$ENDIF} ; end; + {$IFEND} end; function ScalingByScreenDPI( F:TForm = NIL ):TPointF; diff --git a/contrib/mORMot/SQLite3/Samples/ThirdPartyDemos/Ondrej/SynTaskDialog4Lazarus/SynTaskDialog.pas b/contrib/mORMot/SQLite3/Samples/ThirdPartyDemos/Ondrej/SynTaskDialog4Lazarus/SynTaskDialog.pas index d9e6c62..2b6a1c4 100644 --- a/contrib/mORMot/SQLite3/Samples/ThirdPartyDemos/Ondrej/SynTaskDialog4Lazarus/SynTaskDialog.pas +++ b/contrib/mORMot/SQLite3/Samples/ThirdPartyDemos/Ondrej/SynTaskDialog4Lazarus/SynTaskDialog.pas @@ -6,7 +6,7 @@ unit SynTaskDialog; { This file is part of Synopse framework. - Synopse framework. Copyright (C) 2020 Arnaud Bouchez + Synopse framework. Copyright (C) 2022 Arnaud Bouchez Synopse Informatique - https://synopse.info *** BEGIN LICENSE BLOCK ***** @@ -25,7 +25,7 @@ unit SynTaskDialog; The Initial Developer of the Original Code is Arnaud Bouchez. - Portions created by the Initial Developer are Copyright (C) 2020 + Portions created by the Initial Developer are Copyright (C) 2022 the Initial Developer. All Rights Reserved. Contributor(s): @@ -139,8 +139,12 @@ uses {$endif} {$IFDEF FMX} System.UITypes, System.Types, System.UIConsts, - FMX.Menus, FMX.Types, FMX.Layouts, FMX.ComboEdit, - FMX.Graphics, FMX.Forms, FMX.Controls, FMX.StdCtrls, FMX.ExtCtrls, + FMX.Menus, FMX.Types, FMX.Layouts, + {$IF (CompilerVersion >= 26.0)}// Delphi XE5 UP + FMX.ComboEdit, + FMX.Graphics, + {$IFEND} + FMX.Forms, FMX.Controls, FMX.StdCtrls, FMX.ExtCtrls, FMX.ListBox, FMX.Edit, FMX.Objects, FMX.Platform, {$IFDEF MSWINDOWS} FMX.Platform.Win diff --git a/contrib/mORMot/SQLite3/TestSQL3.dpr b/contrib/mORMot/SQLite3/TestSQL3.dpr index 7c203ba..39afa90 100644 --- a/contrib/mORMot/SQLite3/TestSQL3.dpr +++ b/contrib/mORMot/SQLite3/TestSQL3.dpr @@ -127,6 +127,7 @@ uses SynZipFiles in '..\SynZipFiles.pas', {$endif LVCL} {$ifdef MSWINDOWS} + Windows, {$ifndef CPU64} SynSMAPI in '..\SynSMAPI.pas', SynSM in '..\SynSM.pas', @@ -164,10 +165,12 @@ uses // SynDBFirebird in '..\SynDBFirebird.pas', // SynDBDataSet in '..\SynDBDataSet.pas', // SynDBFireDAC in '..\SynDBDataSet\SynDBFireDAC.pas', + // SynDBUniDAC in '..\SynDBDataSet\SynDBUniDAC.pas', {$ifdef USEZEOS} SynDBZeos in '..\SynDBZeos.pas', {$endif} {$ifndef DELPHI5OROLDER} + // SynDBVCL in '..\SynDBVCL.pas', SynDBRemote in '..\SynDBRemote.pas', mORMotDB in 'mORMotDB.pas', mORMotMongoDB in 'mORMotMongoDB.pas', diff --git a/contrib/mORMot/SQLite3/TestSQL3FPCInterfaces.pas b/contrib/mORMot/SQLite3/TestSQL3FPCInterfaces.pas index 280b329..089cbde 100644 --- a/contrib/mORMot/SQLite3/TestSQL3FPCInterfaces.pas +++ b/contrib/mORMot/SQLite3/TestSQL3FPCInterfaces.pas @@ -7,7 +7,7 @@ unit TestSQL3FPCInterfaces; This unit has been generated by a mORMot 1.18.2718 server. Any manual modification of this file may be lost after regeneration. - Synopse mORMot framework. Copyright (C) 2020 Arnaud Bouchez + Synopse mORMot framework. Copyright (C) 2022 Arnaud Bouchez Synopse Informatique - https://synopse.info This unit is released under a MPL/GPL/LGPL tri-license, diff --git a/contrib/mORMot/SQLite3/amalgamation/ciphers/cipher_common.c b/contrib/mORMot/SQLite3/amalgamation/ciphers/cipher_common.c new file mode 100644 index 0000000..3b8d574 --- /dev/null +++ b/contrib/mORMot/SQLite3/amalgamation/ciphers/cipher_common.c @@ -0,0 +1,666 @@ +/* +** Name: cipher_common.c +** Purpose: Implementation of SQLite codecs +** Author: Ulrich Telle +** Created: 2020-02-02 +** Copyright: (c) 2006-2020 Ulrich Telle +** License: MIT +*/ + +#include "cipher_common.h" + +static unsigned char padding[] = +"\x28\xBF\x4E\x5E\x4E\x75\x8A\x41\x64\x00\x4E\x56\xFF\xFA\x01\x08\x2E\x2E\x00\xB6\xD0\x68\x3E\x80\x2F\x0C\xA9\xFE\x64\x53\x69\x7A"; + +/* --- Codec Descriptor Table --- */ + +/* +** Common configuration parameters +** +** - cipher : default cipher type +** - hmac_check : flag whether page hmac should be verified on read +*/ + +static CipherParams commonParams[] = +{ + { "cipher", CODEC_TYPE, CODEC_TYPE, 1, CODEC_TYPE_MAX }, + { "hmac_check", 1, 1, 0, 1 }, + CIPHER_PARAMS_SENTINEL +}; + +SQLITE_PRIVATE int +sqlite3mcGetCipherParameter(CipherParams* cipherParams, const char* paramName) +{ + int value = -1; + for (; strlen(cipherParams->m_name) > 0; ++cipherParams) + { + if (sqlite3_stricmp(paramName, cipherParams->m_name) == 0) break; + } + if (strlen(cipherParams->m_name) > 0) + { + value = cipherParams->m_value; + cipherParams->m_value = cipherParams->m_default; + } + return value; +} + +static CodecParameter globalCodecParameterTable[] = +{ + { "global", CODEC_TYPE_UNKNOWN, commonParams }, +#if HAVE_CIPHER_AES_128_CBC + { "aes128cbc", CODEC_TYPE_AES128, mcAES128Params }, +#endif +#if HAVE_CIPHER_AES_256_CBC + { "aes256cbc", CODEC_TYPE_AES256, mcAES256Params }, +#endif +#if HAVE_CIPHER_CHACHA20 + { "chacha20", CODEC_TYPE_CHACHA20, mcChaCha20Params }, +#endif +#if HAVE_CIPHER_SQLCIPHER + { "sqlcipher", CODEC_TYPE_SQLCIPHER, mcSQLCipherParams }, +#endif +#if HAVE_CIPHER_RC4 + { "rc4", CODEC_TYPE_RC4, mcRC4Params }, +#endif + { "", CODEC_TYPE_UNKNOWN, NULL } +}; + +#define CODEC_TYPE_UNKNOWN 0 +#define CODEC_TYPE_AES128 1 +#define CODEC_TYPE_AES256 2 +#define CODEC_TYPE_CHACHA20 3 +#define CODEC_TYPE_SQLCIPHER 4 +#define CODEC_TYPE_RC4 5 + +SQLITE_PRIVATE CodecParameter* +sqlite3mcCloneCodecParameterTable() +{ + /* Count number of codecs and cipher parameters */ + int nTables = 0; + int nParams = 0; + int j, k, n; + CipherParams* cloneCipherParams; + CodecParameter* cloneCodecParams; + + for (j = 0; strlen(globalCodecParameterTable[j].m_name) > 0; ++j) + { + CipherParams* params = globalCodecParameterTable[j].m_params; + for (k = 0; strlen(params[k].m_name) > 0; ++k); + nParams += k; + } + nTables = j; + + /* Allocate memory for cloned codec parameter tables (including sentinel for each table) */ + cloneCipherParams = (CipherParams*) sqlite3_malloc((nParams + nTables) * sizeof(CipherParams)); + cloneCodecParams = (CodecParameter*) sqlite3_malloc((nTables + 1) * sizeof(CodecParameter)); + + /* Create copy of tables */ + if (cloneCodecParams != NULL) + { + int offset = 0; + for (j = 0; j < nTables; ++j) + { + CipherParams* params = globalCodecParameterTable[j].m_params; + cloneCodecParams[j].m_name = globalCodecParameterTable[j].m_name; + cloneCodecParams[j].m_params = &cloneCipherParams[offset]; + for (n = 0; strlen(params[n].m_name) > 0; ++n); + /* Copy all parameters of the current table (including sentinel) */ + for (k = 0; k <= n; ++k) + { + cloneCipherParams[offset + k].m_name = params[k].m_name; + cloneCipherParams[offset + k].m_value = params[k].m_value; + cloneCipherParams[offset + k].m_default = params[k].m_default; + cloneCipherParams[offset + k].m_minValue = params[k].m_minValue; + cloneCipherParams[offset + k].m_maxValue = params[k].m_maxValue; + } + offset += (n + 1); + } + cloneCodecParams[nTables].m_name = globalCodecParameterTable[nTables].m_name; + cloneCodecParams[nTables].m_params = NULL; + } + else + { + sqlite3_free(cloneCipherParams); + } + return cloneCodecParams; +} + +SQLITE_PRIVATE void +sqlite3mcFreeCodecParameterTable(CodecParameter* codecParams) +{ + sqlite3_free(codecParams[0].m_params); + sqlite3_free(codecParams); +} + +static const CipherDescriptor mcSentinelDescriptor = +{ + "", NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL +}; + +static const CipherDescriptor mcDummyDescriptor = +{ + "@dummy@", NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL +}; + +static const CipherDescriptor* codecDescriptorTable[] = +{ +#if HAVE_CIPHER_AES_128_CBC + &mcAES128Descriptor, +#else + &mcDummyDescriptor, +#endif +#if HAVE_CIPHER_AES_256_CBC + &mcAES256Descriptor, +#else + &mcDummyDescriptor, +#endif +#if HAVE_CIPHER_CHACHA20 + &mcChaCha20Descriptor, +#else + &mcDummyDescriptor, +#endif +#if HAVE_CIPHER_SQLCIPHER + &mcSQLCipherDescriptor, +#else + &mcDummyDescriptor, +#endif +#if HAVE_CIPHER_RC4 + &mcRC4Descriptor, +#else + &mcDummyDescriptor, +#endif + &mcSentinelDescriptor +}; + +/* --- Codec --- */ + +SQLITE_PRIVATE CodecParameter* +sqlite3mcGetCodecParams(sqlite3* db); + +SQLITE_PRIVATE int +sqlite3mcGetCipherType(sqlite3* db) +{ + CodecParameter* codecParams = (db != NULL) ? sqlite3mcGetCodecParams(db) : globalCodecParameterTable; + CipherParams* cipherParamTable = (codecParams != NULL) ? codecParams[0].m_params : commonParams; + int cipherType = CODEC_TYPE; + CipherParams* cipher = cipherParamTable; + for (; strlen(cipher->m_name) > 0; ++cipher) + { + if (sqlite3_stricmp("cipher", cipher->m_name) == 0) break; + } + if (strlen(cipher->m_name) > 0) + { + cipherType = cipher->m_value; + cipher->m_value = cipher->m_default; + } + return cipherType; +} + +SQLITE_PRIVATE CipherParams* +sqlite3mcGetCipherParams(sqlite3* db, int cypherType) +{ + CodecParameter* codecParams = (db != NULL) ? sqlite3mcGetCodecParams(db) : globalCodecParameterTable; + CipherParams* cipherParamTable = (codecParams != NULL) ? codecParams[cypherType].m_params : globalCodecParameterTable[cypherType].m_params; + return cipherParamTable; +} + +SQLITE_PRIVATE int +sqlite3mcCodecInit(Codec* codec) +{ + int rc = SQLITE_OK; + if (codec != NULL) + { + codec->m_isEncrypted = 0; + codec->m_hmacCheck = 1; + + codec->m_hasReadCipher = 0; + codec->m_readCipherType = CODEC_TYPE_UNKNOWN; + codec->m_readCipher = NULL; + codec->m_readReserved = -1; + + codec->m_hasWriteCipher = 0; + codec->m_writeCipherType = CODEC_TYPE_UNKNOWN; + codec->m_writeCipher = NULL; + codec->m_writeReserved = -1; + + codec->m_db = NULL; + codec->m_bt = NULL; + codec->m_btShared = NULL; + memset(codec->m_page, 0, sizeof(codec->m_page)); + codec->m_pageSize = 0; + codec->m_reserved = 0; + codec->m_hasKeySalt = 0; + memset(codec->m_keySalt, 0, sizeof(codec->m_keySalt)); + } + else + { + rc = SQLITE_NOMEM; + } + return rc; +} + +SQLITE_PRIVATE void +sqlite3mcCodecTerm(Codec* codec) +{ + if (codec->m_readCipher != NULL) + { + codecDescriptorTable[codec->m_readCipherType - 1]->m_freeCipher(codec->m_readCipher); + codec->m_readCipher = NULL; + } + if (codec->m_writeCipher != NULL) + { + codecDescriptorTable[codec->m_writeCipherType - 1]->m_freeCipher(codec->m_writeCipher); + codec->m_writeCipher = NULL; + } + memset(codec, 0, sizeof(Codec)); +} + +SQLITE_PRIVATE void +sqlite3mcClearKeySalt(Codec* codec) +{ + codec->m_hasKeySalt = 0; + memset(codec->m_keySalt, 0, sizeof(codec->m_keySalt)); +} + +SQLITE_PRIVATE int +sqlite3mcCodecSetup(Codec* codec, int cipherType, char* userPassword, int passwordLength) +{ + int rc = SQLITE_OK; + CipherParams* globalParams = sqlite3mcGetCipherParams(codec->m_db, 0); + codec->m_isEncrypted = 1; + codec->m_hmacCheck = sqlite3mcGetCipherParameter(globalParams, "hmac_check"); + codec->m_hasReadCipher = 1; + codec->m_hasWriteCipher = 1; + codec->m_readCipherType = cipherType; + codec->m_readCipher = codecDescriptorTable[codec->m_readCipherType-1]->m_allocateCipher(codec->m_db); + if (codec->m_readCipher != NULL) + { + unsigned char* keySalt = (codec->m_hasKeySalt != 0) ? codec->m_keySalt : NULL; + sqlite3mcGenerateReadKey(codec, userPassword, passwordLength, keySalt); + rc = sqlite3mcCopyCipher(codec, 1); + } + else + { + rc = SQLITE_NOMEM; + } + return rc; +} + +SQLITE_PRIVATE int +sqlite3mcSetupWriteCipher(Codec* codec, int cipherType, char* userPassword, int passwordLength) +{ + int rc = SQLITE_OK; + CipherParams* globalParams = sqlite3mcGetCipherParams(codec->m_db, 0); + if (codec->m_writeCipher != NULL) + { + codecDescriptorTable[codec->m_writeCipherType-1]->m_freeCipher(codec->m_writeCipher); + } + codec->m_isEncrypted = 1; + codec->m_hmacCheck = sqlite3mcGetCipherParameter(globalParams, "hmac_check"); + codec->m_hasWriteCipher = 1; + codec->m_writeCipherType = cipherType; + codec->m_writeCipher = codecDescriptorTable[codec->m_writeCipherType-1]->m_allocateCipher(codec->m_db); + if (codec->m_writeCipher != NULL) + { + unsigned char* keySalt = (codec->m_hasKeySalt != 0) ? codec->m_keySalt : NULL; + sqlite3mcGenerateWriteKey(codec, userPassword, passwordLength, keySalt); + } + else + { + rc = SQLITE_NOMEM; + } + return rc; +} + +SQLITE_PRIVATE void +sqlite3mcSetIsEncrypted(Codec* codec, int isEncrypted) +{ + codec->m_isEncrypted = isEncrypted; +} + +SQLITE_PRIVATE void +sqlite3mcSetReadCipherType(Codec* codec, int cipherType) +{ + codec->m_readCipherType = cipherType; +} + +SQLITE_PRIVATE void +sqlite3mcSetWriteCipherType(Codec* codec, int cipherType) +{ + codec->m_writeCipherType = cipherType; +} + +SQLITE_PRIVATE void +sqlite3mcSetHasReadCipher(Codec* codec, int hasReadCipher) +{ + codec->m_hasReadCipher = hasReadCipher; +} + +SQLITE_PRIVATE void +sqlite3mcSetHasWriteCipher(Codec* codec, int hasWriteCipher) +{ + codec->m_hasWriteCipher = hasWriteCipher; +} + +SQLITE_PRIVATE void +sqlite3mcSetDb(Codec* codec, sqlite3* db) +{ + codec->m_db = db; +} + +SQLITE_PRIVATE void +sqlite3mcSetBtree(Codec* codec, Btree* bt) +{ + codec->m_bt = bt; + codec->m_btShared = bt->pBt; +} + +SQLITE_PRIVATE void +sqlite3mcSetReadReserved(Codec* codec, int reserved) +{ + codec->m_readReserved = reserved; +} + +SQLITE_PRIVATE void +sqlite3mcSetWriteReserved(Codec* codec, int reserved) +{ + codec->m_writeReserved = reserved; +} + +SQLITE_PRIVATE int +sqlite3mcIsEncrypted(Codec* codec) +{ + return codec->m_isEncrypted; +} + +SQLITE_PRIVATE int +sqlite3mcHasReadCipher(Codec* codec) +{ + return codec->m_hasReadCipher; +} + +SQLITE_PRIVATE int +sqlite3mcHasWriteCipher(Codec* codec) +{ + return codec->m_hasWriteCipher; +} + +SQLITE_PRIVATE Btree* +sqlite3mcGetBtree(Codec* codec) +{ + return codec->m_bt; +} + +SQLITE_PRIVATE BtShared* +sqlite3mcGetBtShared(Codec* codec) +{ + return codec->m_btShared; +} + +SQLITE_PRIVATE int +sqlite3mcGetPageSize(Codec* codec) +{ + return codec->m_btShared->pageSize; +} + +SQLITE_PRIVATE int +sqlite3mcGetReadReserved(Codec* codec) +{ + return codec->m_readReserved; +} + +SQLITE_PRIVATE int +sqlite3mcGetWriteReserved(Codec* codec) +{ + return codec->m_writeReserved; +} + +SQLITE_PRIVATE unsigned char* +sqlite3mcGetPageBuffer(Codec* codec) +{ + return &codec->m_page[4]; +} + +SQLITE_PRIVATE int +sqlite3mcGetLegacyReadCipher(Codec* codec) +{ + int legacy = (codec->m_hasReadCipher && codec->m_readCipher != NULL) ? codecDescriptorTable[codec->m_readCipherType - 1]->m_getLegacy(codec->m_readCipher) : 0; + return legacy; +} + +SQLITE_PRIVATE int +sqlite3mcGetLegacyWriteCipher(Codec* codec) +{ + int legacy = (codec->m_hasWriteCipher && codec->m_writeCipher != NULL) ? codecDescriptorTable[codec->m_writeCipherType - 1]->m_getLegacy(codec->m_writeCipher) : -1; + return legacy; +} + +SQLITE_PRIVATE int +sqlite3mcGetPageSizeReadCipher(Codec* codec) +{ + int pageSize = (codec->m_hasReadCipher && codec->m_readCipher != NULL) ? codecDescriptorTable[codec->m_readCipherType - 1]->m_getPageSize(codec->m_readCipher) : 0; + return pageSize; +} + +SQLITE_PRIVATE int +sqlite3mcGetPageSizeWriteCipher(Codec* codec) +{ + int pageSize = (codec->m_hasWriteCipher && codec->m_writeCipher != NULL) ? codecDescriptorTable[codec->m_writeCipherType - 1]->m_getPageSize(codec->m_writeCipher) : -1; + return pageSize; +} + +SQLITE_PRIVATE int +sqlite3mcGetReservedReadCipher(Codec* codec) +{ + int reserved = (codec->m_hasReadCipher && codec->m_readCipher != NULL) ? codecDescriptorTable[codec->m_readCipherType-1]->m_getReserved(codec->m_readCipher) : -1; + return reserved; +} + +SQLITE_PRIVATE int +sqlite3mcGetReservedWriteCipher(Codec* codec) +{ + int reserved = (codec->m_hasWriteCipher && codec->m_writeCipher != NULL) ? codecDescriptorTable[codec->m_writeCipherType-1]->m_getReserved(codec->m_writeCipher) : -1; + return reserved; +} + +SQLITE_PRIVATE int +sqlite3mcReservedEqual(Codec* codec) +{ + int readReserved = (codec->m_hasReadCipher && codec->m_readCipher != NULL) ? codecDescriptorTable[codec->m_readCipherType-1]->m_getReserved(codec->m_readCipher) : -1; + int writeReserved = (codec->m_hasWriteCipher && codec->m_writeCipher != NULL) ? codecDescriptorTable[codec->m_writeCipherType-1]->m_getReserved(codec->m_writeCipher) : -1; + return (readReserved == writeReserved); +} + +SQLITE_PRIVATE unsigned char* +sqlite3mcGetSaltWriteCipher(Codec* codec) +{ + unsigned char* salt = (codec->m_hasWriteCipher && codec->m_writeCipher != NULL) ? codecDescriptorTable[codec->m_writeCipherType - 1]->m_getSalt(codec->m_writeCipher) : NULL; + return salt; +} + +SQLITE_PRIVATE int +sqlite3mcCodecCopy(Codec* codec, Codec* other) +{ + int rc = SQLITE_OK; + codec->m_isEncrypted = other->m_isEncrypted; + codec->m_hmacCheck = other->m_hmacCheck; + codec->m_hasReadCipher = other->m_hasReadCipher; + codec->m_hasWriteCipher = other->m_hasWriteCipher; + codec->m_readCipherType = other->m_readCipherType; + codec->m_writeCipherType = other->m_writeCipherType; + codec->m_readCipher = NULL; + codec->m_writeCipher = NULL; + codec->m_readReserved = other->m_readReserved; + codec->m_writeReserved = other->m_writeReserved; + + if (codec->m_hasReadCipher) + { + codec->m_readCipher = codecDescriptorTable[codec->m_readCipherType - 1]->m_allocateCipher(codec->m_db); + if (codec->m_readCipher != NULL) + { + codecDescriptorTable[codec->m_readCipherType - 1]->m_cloneCipher(codec->m_readCipher, other->m_readCipher); + } + else + { + rc = SQLITE_NOMEM; + } + } + + if (codec->m_hasWriteCipher) + { + codec->m_writeCipher = codecDescriptorTable[codec->m_writeCipherType - 1]->m_allocateCipher(codec->m_db); + if (codec->m_writeCipher != NULL) + { + codecDescriptorTable[codec->m_writeCipherType - 1]->m_cloneCipher(codec->m_writeCipher, other->m_writeCipher); + } + else + { + rc = SQLITE_NOMEM; + } + } + codec->m_db = other->m_db; + codec->m_bt = other->m_bt; + codec->m_btShared = other->m_btShared; + return rc; +} + +SQLITE_PRIVATE int +sqlite3mcCodecCompare(Codec* codec1, Codec* codec2) +{ + int equal = 0; + if (codec1->m_hasReadCipher == codec2->m_hasReadCipher && + codec1->m_hasWriteCipher == codec2->m_hasWriteCipher) + { + int eqRead = (codec1->m_hasReadCipher) ? codec1->m_readCipherType == codec2->m_readCipherType : 1; + int eqWrite = (codec1->m_hasWriteCipher) ? codec1->m_writeCipherType == codec2->m_writeCipherType : 1; + if (eqRead && eqWrite) + { + eqRead = (codec1->m_hasReadCipher) ? codecDescriptorTable[codec1->m_readCipherType - 1]->m_compareCipher(codec1->m_readCipher, codec2->m_readCipher) : 1; + eqWrite = (codec1->m_hasWriteCipher) ? codecDescriptorTable[codec1->m_writeCipherType - 1]->m_compareCipher(codec1->m_writeCipher, codec2->m_writeCipher) : 1; + equal = eqRead && eqWrite; + } + } + return equal; +} + +SQLITE_PRIVATE int +sqlite3mcCopyCipher(Codec* codec, int read2write) +{ + int rc = SQLITE_OK; + if (read2write) + { + if (codec->m_writeCipher != NULL && codec->m_writeCipherType != codec->m_readCipherType) + { + codecDescriptorTable[codec->m_writeCipherType-1]->m_freeCipher(codec->m_writeCipher); + codec->m_writeCipher = NULL; + } + if (codec->m_writeCipher == NULL) + { + codec->m_writeCipherType = codec->m_readCipherType; + codec->m_writeCipher = codecDescriptorTable[codec->m_writeCipherType-1]->m_allocateCipher(codec->m_db); + } + if (codec->m_writeCipher != NULL) + { + codecDescriptorTable[codec->m_writeCipherType-1]->m_cloneCipher(codec->m_writeCipher, codec->m_readCipher); + } + else + { + rc = SQLITE_NOMEM; + } + } + else + { + if (codec->m_readCipher != NULL && codec->m_readCipherType != codec->m_writeCipherType) + { + codecDescriptorTable[codec->m_readCipherType-1]->m_freeCipher(codec->m_readCipher); + codec->m_readCipher = NULL; + } + if (codec->m_readCipher == NULL) + { + codec->m_readCipherType = codec->m_writeCipherType; + codec->m_readCipher = codecDescriptorTable[codec->m_readCipherType-1]->m_allocateCipher(codec->m_db); + } + if (codec->m_readCipher != NULL) + { + codecDescriptorTable[codec->m_readCipherType-1]->m_cloneCipher(codec->m_readCipher, codec->m_writeCipher); + } + else + { + rc = SQLITE_NOMEM; + } + } + return rc; +} + +SQLITE_PRIVATE void +sqlite3mcPadPassword(char* password, int pswdlen, unsigned char pswd[32]) +{ + int j; + int p = 0; + int m = pswdlen; + if (m > 32) m = 32; + + for (j = 0; j < m; j++) + { + pswd[p++] = (unsigned char) password[j]; + } + for (j = 0; p < 32 && j < 32; j++) + { + pswd[p++] = padding[j]; + } +} + +SQLITE_PRIVATE void +sqlite3mcGenerateReadKey(Codec* codec, char* userPassword, int passwordLength, unsigned char* cipherSalt) +{ + codecDescriptorTable[codec->m_readCipherType-1]->m_generateKey(codec->m_readCipher, codec->m_btShared, userPassword, passwordLength, 0, cipherSalt); +} + +SQLITE_PRIVATE void +sqlite3mcGenerateWriteKey(Codec* codec, char* userPassword, int passwordLength, unsigned char* cipherSalt) +{ + codecDescriptorTable[codec->m_writeCipherType-1]->m_generateKey(codec->m_writeCipher, codec->m_btShared, userPassword, passwordLength, 1, cipherSalt); +} + +SQLITE_PRIVATE int +sqlite3mcEncrypt(Codec* codec, int page, unsigned char* data, int len, int useWriteKey) +{ + int cipherType = (useWriteKey) ? codec->m_writeCipherType : codec->m_readCipherType; + void* cipher = (useWriteKey) ? codec->m_writeCipher : codec->m_readCipher; + int reserved = (useWriteKey) ? (codec->m_writeReserved >= 0) ? codec->m_writeReserved : codec->m_reserved + : (codec->m_readReserved >= 0) ? codec->m_readReserved : codec->m_reserved; + return codecDescriptorTable[cipherType-1]->m_encryptPage(cipher, page, data, len, reserved); +} + +SQLITE_PRIVATE int +sqlite3mcDecrypt(Codec* codec, int page, unsigned char* data, int len) +{ + int cipherType = codec->m_readCipherType; + void* cipher = codec->m_readCipher; + int reserved = (codec->m_readReserved >= 0) ? codec->m_readReserved : codec->m_reserved; + return codecDescriptorTable[cipherType-1]->m_decryptPage(cipher, page, data, len, reserved, codec->m_hmacCheck); +} + +SQLITE_PRIVATE void +sqlite3mcConfigureSQLCipherVersion(sqlite3* db, int configDefault, int legacyVersion) +{ + static char* stdNames[] = { "legacy_page_size", "kdf_iter", "hmac_use", "kdf_algorithm", "hmac_algorithm", NULL }; + static char* defNames[] = { "default:legacy_page_size", "default:kdf_iter", "default:hmac_use", "default:kdf_algorithm", "default:hmac_algorithm", NULL }; + static int versionParams[SQLCIPHER_VERSION_MAX][5] = + { + { 1024, 4000, 0, SQLCIPHER_KDF_ALGORITHM_SHA1, SQLCIPHER_HMAC_ALGORITHM_SHA1 }, + { 1024, 4000, 1, SQLCIPHER_KDF_ALGORITHM_SHA1, SQLCIPHER_HMAC_ALGORITHM_SHA1 }, + { 1024, 64000, 1, SQLCIPHER_KDF_ALGORITHM_SHA1, SQLCIPHER_HMAC_ALGORITHM_SHA1 }, + { 4096, 256000, 1, SQLCIPHER_KDF_ALGORITHM_SHA512, SQLCIPHER_HMAC_ALGORITHM_SHA512 } + }; + if (legacyVersion > 0 && legacyVersion <= SQLCIPHER_VERSION_MAX) + { + char** names = (configDefault != 0) ? defNames : stdNames; + int* values = &versionParams[legacyVersion - 1][0]; + int j; + for (j = 0; names[j] != NULL; ++j) + { + sqlite3mc_config_cipher(db, "sqlcipher", names[j], values[j]); + } + } +} diff --git a/contrib/mORMot/SQLite3/amalgamation/ciphers/cipher_common.h b/contrib/mORMot/SQLite3/amalgamation/ciphers/cipher_common.h new file mode 100644 index 0000000..75f1e5b --- /dev/null +++ b/contrib/mORMot/SQLite3/amalgamation/ciphers/cipher_common.h @@ -0,0 +1,233 @@ +/* +** Name: cipher_common.h +** Purpose: Header for the ciphers of SQLite3 Multiple Ciphers +** Author: Ulrich Telle +** Created: 2020-02-02 +** Copyright: (c) 2006-2020 Ulrich Telle +** License: MIT +*/ + +#ifndef CIPHER_COMMON_H_ +#define CIPHER_COMMON_H_ + +#include "sqlite3mc.h" + +/* +// ATTENTION: Macro similar to that in pager.c +// TODO: Check in case of new version of SQLite +*/ +#define WX_PAGER_MJ_PGNO(x) ((PENDING_BYTE/(x))+1) + +#define CODEC_TYPE_DEFAULT CODEC_TYPE_CHACHA20 + +#ifndef CODEC_TYPE +#define CODEC_TYPE CODEC_TYPE_DEFAULT +#endif + +#if CODEC_TYPE < 1 || CODEC_TYPE > CODEC_TYPE_MAX +#error "Invalid codec type selected" +#endif + +#define MAXKEYLENGTH 32 +#define KEYLENGTH_AES128 16 +#define KEYLENGTH_AES256 32 +#define KEYSALT_LENGTH 16 + +#define CODEC_SHA_ITER 4001 + +typedef struct _Codec +{ + int m_isEncrypted; + int m_hmacCheck; + /* Read cipher */ + int m_hasReadCipher; + int m_readCipherType; + void* m_readCipher; + int m_readReserved; + /* Write cipher */ + int m_hasWriteCipher; + int m_writeCipherType; + void* m_writeCipher; + int m_writeReserved; + + sqlite3* m_db; /* Pointer to DB */ + Btree* m_bt; /* Pointer to B-tree used by DB */ + BtShared* m_btShared; /* Pointer to shared B-tree used by DB */ + unsigned char m_page[SQLITE_MAX_PAGE_SIZE + 24]; + int m_pageSize; + int m_reserved; + int m_hasKeySalt; + unsigned char m_keySalt[KEYSALT_LENGTH]; +} Codec; + +#define CIPHER_PARAMS_SENTINEL { "", 0, 0, 0, 0 } +#define CIPHER_PAGE1_OFFSET 24 + +typedef struct _CipherParams +{ + char* m_name; + int m_value; + int m_default; + int m_minValue; + int m_maxValue; +} CipherParams; + +typedef struct _CodecParameter +{ + char* m_name; + int m_id; + CipherParams* m_params; +} CodecParameter; + +typedef void* (*AllocateCipher_t)(sqlite3* db); +typedef void (*FreeCipher_t)(void* cipher); +typedef void (*CloneCipher_t)(void* cipherTo, void* cipherFrom); +typedef int (*CompareCipher_t)(void* cipher1, void* cipher2); +typedef int (*GetLegacy_t)(void* cipher); +typedef int (*GetPageSize_t)(void* cipher); +typedef int (*GetReserved_t)(void* cipher); +typedef unsigned char* (*GetSalt_t)(void* cipher); +typedef void (*GenerateKey_t)(void* cipher, BtShared* pBt, char* userPassword, int passwordLength, int rekey, unsigned char* cipherSalt); +typedef int (*EncryptPage_t)(void* cipher, int page, unsigned char* data, int len, int reserved); +typedef int (*DecryptPage_t)(void* cipher, int page, unsigned char* data, int len, int reserved, int hmacCheck); + +typedef struct _CodecDescriptor +{ + char m_name[32]; + AllocateCipher_t m_allocateCipher; + FreeCipher_t m_freeCipher; + CloneCipher_t m_cloneCipher; + CompareCipher_t m_compareCipher; + GetLegacy_t m_getLegacy; + GetPageSize_t m_getPageSize; + GetReserved_t m_getReserved; + GetSalt_t m_getSalt; + GenerateKey_t m_generateKey; + EncryptPage_t m_encryptPage; + DecryptPage_t m_decryptPage; +} CipherDescriptor; + +SQLITE_PRIVATE int sqlite3mcGetCipherParameter(CipherParams* cipherParams, const char* paramName); +SQLITE_PRIVATE int sqlite3mcGetCipherType(sqlite3* db); +SQLITE_PRIVATE CipherParams* sqlite3mcGetCipherParams(sqlite3* db, int cypherType); +SQLITE_PRIVATE int sqlite3mcCodecInit(Codec* codec); +SQLITE_PRIVATE void sqlite3mcCodecTerm(Codec* codec); +SQLITE_PRIVATE void sqlite3mcClearKeySalt(Codec* codec); +SQLITE_PRIVATE int sqlite3mcCodecSetup(Codec* codec, int cipherType, char* userPassword, int passwordLength); +SQLITE_PRIVATE int sqlite3mcSetupWriteCipher(Codec* codec, int cipherType, char* userPassword, int passwordLength); +SQLITE_PRIVATE void sqlite3mcSetIsEncrypted(Codec* codec, int isEncrypted); +SQLITE_PRIVATE void sqlite3mcSetReadCipherType(Codec* codec, int cipherType); +SQLITE_PRIVATE void sqlite3mcSetWriteCipherType(Codec* codec, int cipherType); +SQLITE_PRIVATE void sqlite3mcSetHasReadCipher(Codec* codec, int hasReadCipher); +SQLITE_PRIVATE void sqlite3mcSetHasWriteCipher(Codec* codec, int hasWriteCipher); +SQLITE_PRIVATE void sqlite3mcSetDb(Codec* codec, sqlite3* db); +SQLITE_PRIVATE void sqlite3mcSetBtree(Codec* codec, Btree* bt); +SQLITE_PRIVATE void sqlite3mcSetReadReserved(Codec* codec, int reserved); +SQLITE_PRIVATE void sqlite3mcSetWriteReserved(Codec* codec, int reserved); +SQLITE_PRIVATE int sqlite3mcIsEncrypted(Codec* codec); +SQLITE_PRIVATE int sqlite3mcHasReadCipher(Codec* codec); +SQLITE_PRIVATE int sqlite3mcHasWriteCipher(Codec* codec); +SQLITE_PRIVATE Btree* sqlite3mcGetBtree(Codec* codec); +SQLITE_PRIVATE BtShared* sqlite3mcGetBtShared(Codec* codec); +SQLITE_PRIVATE int sqlite3mcGetPageSize(Codec* codec); +SQLITE_PRIVATE int sqlite3mcGetReadReserved(Codec* codec); +SQLITE_PRIVATE int sqlite3mcGetWriteReserved(Codec* codec); +SQLITE_PRIVATE unsigned char* sqlite3mcGetPageBuffer(Codec* codec); +SQLITE_PRIVATE int sqlite3mcGetLegacyReadCipher(Codec* codec); +SQLITE_PRIVATE int sqlite3mcGetLegacyWriteCipher(Codec* codec); +SQLITE_PRIVATE int sqlite3mcGetPageSizeReadCipher(Codec* codec); +SQLITE_PRIVATE int sqlite3mcGetPageSizeWriteCipher(Codec* codec); +SQLITE_PRIVATE int sqlite3mcGetReservedReadCipher(Codec* codec); +SQLITE_PRIVATE int sqlite3mcGetReservedWriteCipher(Codec* codec); +SQLITE_PRIVATE int sqlite3mcReservedEqual(Codec* codec); +SQLITE_PRIVATE unsigned char* sqlite3mcGetSaltWriteCipher(Codec* codec); +SQLITE_PRIVATE int sqlite3mcCodecCopy(Codec* codec, Codec* other); +SQLITE_PRIVATE int sqlite3mcCodecCompare(Codec* codec1, Codec* codec2); + +SQLITE_PRIVATE void sqlite3mcGenerateReadKey(Codec* codec, char* userPassword, int passwordLength, unsigned char* cipherSalt); + +SQLITE_PRIVATE void sqlite3mcGenerateWriteKey(Codec* codec, char* userPassword, int passwordLength, unsigned char* cipherSalt); + +SQLITE_PRIVATE int sqlite3mcEncrypt(Codec* codec, int page, unsigned char* data, int len, int useWriteKey); + +SQLITE_PRIVATE int sqlite3mcDecrypt(Codec* codec, int page, unsigned char* data, int len); + +SQLITE_PRIVATE int sqlite3mcCopyCipher(Codec* codec, int read2write); + +SQLITE_PRIVATE int sqlite3mcCodecSetup(Codec* codec, int cipherType, char* userPassword, int passwordLength); +SQLITE_PRIVATE int sqlite3mcSetupWriteCipher(Codec* codec, int cipherType, char* userPassword, int passwordLength); + +SQLITE_PRIVATE void sqlite3mcSetIsEncrypted(Codec* codec, int isEncrypted); +SQLITE_PRIVATE void sqlite3mcSetReadCipherType(Codec* codec, int cipherType); +SQLITE_PRIVATE void sqlite3mcSetWriteCipherType(Codec* codec, int cipherType); +SQLITE_PRIVATE void sqlite3mcSetHasReadCipher(Codec* codec, int hasReadCipher); +SQLITE_PRIVATE void sqlite3mcSetHasWriteCipher(Codec* codec, int hasWriteCipher); +SQLITE_PRIVATE void sqlite3mcSetDb(Codec* codec, sqlite3* db); +SQLITE_PRIVATE void sqlite3mcSetBtree(Codec* codec, Btree* bt); +SQLITE_PRIVATE void sqlite3mcSetReadReserved(Codec* codec, int reserved); +SQLITE_PRIVATE void sqlite3mcSetWriteReserved(Codec* codec, int reserved); + +SQLITE_PRIVATE int sqlite3mcIsEncrypted(Codec* codec); +SQLITE_PRIVATE int sqlite3mcHasReadCipher(Codec* codec); +SQLITE_PRIVATE int sqlite3mcHasWriteCipher(Codec* codec); +SQLITE_PRIVATE Btree* sqlite3mcGetBtree(Codec* codec); +SQLITE_PRIVATE BtShared* sqlite3mcGetBtShared(Codec* codec); +SQLITE_PRIVATE int sqlite3mcGetPageSize(Codec* codec); +SQLITE_PRIVATE int sqlite3mcGetReadReserved(Codec* codec); +SQLITE_PRIVATE int sqlite3mcGetWriteReserved(Codec* codec); +SQLITE_PRIVATE unsigned char* sqlite3mcGetPageBuffer(Codec* codec); +SQLITE_PRIVATE int sqlite3mcGetLegacyReadCipher(Codec* codec); +SQLITE_PRIVATE int sqlite3mcGetLegacyWriteCipher(Codec* codec); +SQLITE_PRIVATE int sqlite3mcGetPageSizeReadCipher(Codec* codec); +SQLITE_PRIVATE int sqlite3mcGetPageSizeWriteCipher(Codec* codec); +SQLITE_PRIVATE int sqlite3mcGetReservedReadCipher(Codec* codec); +SQLITE_PRIVATE int sqlite3mcGetReservedWriteCipher(Codec* codec); +SQLITE_PRIVATE int sqlite3mcReservedEqual(Codec* codec); + +SQLITE_PRIVATE void sqlite3mcPadPassword(char* password, int pswdlen, unsigned char pswd[32]); +SQLITE_PRIVATE void sqlite3mcRC4(unsigned char* key, int keylen, unsigned char* textin, int textlen, unsigned char* textout); +SQLITE_PRIVATE void sqlite3mcGetMD5Binary(unsigned char* data, int length, unsigned char* digest); +SQLITE_PRIVATE void sqlite3mcGetSHABinary(unsigned char* data, int length, unsigned char* digest); +SQLITE_PRIVATE void sqlite3mcGenerateInitialVector(int seed, unsigned char iv[16]); + +SQLITE_PRIVATE int sqlite3mcIsHexKey(const unsigned char* hex, int len); +SQLITE_PRIVATE int sqlite3mcConvertHex2Int(char c); +SQLITE_PRIVATE void sqlite3mcConvertHex2Bin(const unsigned char* hex, int len, unsigned char* bin); + +SQLITE_PRIVATE int sqlite3mcConfigureFromUri(sqlite3* db, const char *zDbName, int configDefault); +SQLITE_PRIVATE void sqlite3mcConfigureSQLCipherVersion(sqlite3* db, int configDefault, int legacyVersion); + +SQLITE_PRIVATE int sqlite3mcCodecAttach(sqlite3* db, int nDb, const char* zPath, const void* zKey, int nKey); +SQLITE_PRIVATE void sqlite3mcCodecGetKey(sqlite3* db, int nDb, void** zKey, int* nKey); + +/* Debugging */ + +#if 0 +#define SQLITE3MC_DEBUG +#define SQLITE3MC_DEBUG_DATA +#endif + +#ifdef SQLITE3MC_DEBUG +#define SQLITE3MC_DEBUG_LOG(...) { fprintf(stdout, __VA_ARGS__); fflush(stdout); } +#else +#define SQLITE3MC_DEBUG_LOG(...) +#endif + +#ifdef SQLITE3MC_DEBUG_DATA +#define SQLITE3MC_DEBUG_HEX(DESC,BUFFER,LEN) \ + { \ + int count; \ + printf(DESC); \ + for (count = 0; count < LEN; ++count) \ + { \ + if (count % 16 == 0) printf("\n%05x: ", count); \ + printf("%02x ", ((unsigned char*) BUFFER)[count]); \ + } \ + printf("\n"); \ + fflush(stdout); \ + } +#else +#define SQLITE3MC_DEBUG_HEX(DESC,BUFFER,LEN) +#endif + +#endif diff --git a/contrib/mORMot/SQLite3/amalgamation/compile-delphi-win32.bat b/contrib/mORMot/SQLite3/amalgamation/compile-delphi-win32.bat index 2719928..00031dd 100644 --- a/contrib/mORMot/SQLite3/amalgamation/compile-delphi-win32.bat +++ b/contrib/mORMot/SQLite3/amalgamation/compile-delphi-win32.bat @@ -1,7 +1,10 @@ @echo off +set DST2=..\..\..\lib2\static\delphi\sqlite3.obj + attrib -r ..\sqlite3.obj del ..\sqlite3.obj +del %DST2% set bcc=d:\dev\DelphiXE7 rem set bcc=d:\dev\bcc @@ -10,9 +13,9 @@ echo --------------------------------------------------- echo Compiling for Delphi Win32 using %bcc% %bcc%\bin\bcc32 -6 -Oi -O2 -c -d -u- sqlite3mc.c -copy sqlite3mc.obj ..\sqlite3.obj -del sqlite3mc.obj +copy sqlite3mc.obj ..\sqlite3.obj +copy sqlite3mc.obj %DST2% attrib +r ..\sqlite3.obj rem pause diff --git a/contrib/mORMot/SQLite3/amalgamation/compile-delphi-win64.bat b/contrib/mORMot/SQLite3/amalgamation/compile-delphi-win64.bat index d99980b..190a41c 100644 --- a/contrib/mORMot/SQLite3/amalgamation/compile-delphi-win64.bat +++ b/contrib/mORMot/SQLite3/amalgamation/compile-delphi-win64.bat @@ -1,17 +1,21 @@ @echo off +set DST2=..\..\..\lib2\static\delphi\sqlite3.o + attrib -r ..\sqlite3.o del ..\sqlite3.o +del %DST2% set bcc=d:\dev\DelphiXE7 -rem set bcc=d:\Dev\bcc64ce +rem set bcc=d:\Dev\bcc\bcc64ce echo --------------------------------------------------- echo Compiling for Delphi Win64 using %bcc% -%bcc%\bin\bcc64 -isystem "%bcc%\include" -isystem "%bcc%\include\windows\sdk" -isystem "%bcc%\include\dinkumware64" -isystem "%bcc%\include\windows\crtl" -O2 -c -DWIN64 sqlite3mc.c +%bcc%\bin\bcc64 -Wno-pointer-sign -isystem "%bcc%\include" -isystem "%bcc%\include\windows\sdk" -isystem "%bcc%\include\dinkumware64" -isystem "%bcc%\include\windows\crtl" -O2 -c -DWIN64 sqlite3mc.c + copy sqlite3mc.o ..\sqlite3.o -del sqlite3mc.o +copy sqlite3mc.o %DST2% attrib +r ..\sqlite3.o rem pause diff --git a/contrib/mORMot/SQLite3/amalgamation/compile-fpc-aarch64-android.sh b/contrib/mORMot/SQLite3/amalgamation/compile-fpc-aarch64-android.sh new file mode 100644 index 0000000..d6e3b6d --- /dev/null +++ b/contrib/mORMot/SQLite3/amalgamation/compile-fpc-aarch64-android.sh @@ -0,0 +1,25 @@ +#!/bin/sh + +ARCH=aarch64-android + +CROSS=/home/ab/fpcup/cross/bin/all-android/bin +GCC=$CROSS/clang +DST=../../static/$ARCH/sqlite3.o +DST2=../../../lib2/static/$ARCH/sqlite3.o + +rm $DST +rm $DST2 +rm sqlite3-$ARCH.o + +echo +echo --------------------------------------------------- +echo Compiling for FPC on $ARCH using $GCC +$GCC --target=aarch64-linux-android21 -static -fPIC -Wno-pointer-sign -O2 -DNDEBUG -DNO_TCL -D_CRT_SECURE_NO_DEPRECATE -c sqlite3mc.c -o sqlite3-$ARCH.o +# -fPIC is needed for proper linking + +#$CROSS/llvm-strip sqlite3-$ARCH.o +# striping remove all exported symbols :( + +cp sqlite3-$ARCH.o $DST +cp sqlite3-$ARCH.o $DST2 + diff --git a/contrib/mORMot/SQLite3/amalgamation/compile-fpc-aarch64-linux.sh b/contrib/mORMot/SQLite3/amalgamation/compile-fpc-aarch64-linux.sh index ffe3383..eb1c5f3 100644 --- a/contrib/mORMot/SQLite3/amalgamation/compile-fpc-aarch64-linux.sh +++ b/contrib/mORMot/SQLite3/amalgamation/compile-fpc-aarch64-linux.sh @@ -5,13 +5,19 @@ ARCH=aarch64-linux CROSS=/home/ab/fpcup/cross/bin/$ARCH GCC=$CROSS/$ARCH-gcc DST=../../static/$ARCH/sqlite3.o +DST2=../../../lib2/static/$ARCH/sqlite3.o rm $DST +rm $DST2 rm sqlite3-$ARCH.o echo echo --------------------------------------------------- echo Compiling for FPC on $ARCH using $GCC $GCC -static -O1 -DNDEBUG -DNO_TCL -D_CRT_SECURE_NO_DEPRECATE -c sqlite3mc.c -o sqlite3-$ARCH.o -cp sqlite3-$ARCH.o $DST + +$CROSS/$ARCH-strip -d -x sqlite3-$ARCH.o + +cp sqlite3-$ARCH.o $DST +cp sqlite3-$ARCH.o $DST2 diff --git a/contrib/mORMot/SQLite3/amalgamation/compile-fpc-arm-android.sh b/contrib/mORMot/SQLite3/amalgamation/compile-fpc-arm-android.sh new file mode 100644 index 0000000..0553e61 --- /dev/null +++ b/contrib/mORMot/SQLite3/amalgamation/compile-fpc-arm-android.sh @@ -0,0 +1,24 @@ +#!/bin/sh + +ARCH=arm-android + +CROSS=/home/ab/fpcup/cross/bin/all-android +GCC=$CROSS/bin/clang +DST=../../static/$ARCH/sqlite3.o +DST2=../../../lib2/static/$ARCH/sqlite3.o + +rm $DST +rm $DST2 +rm sqlite3-$ARCH.o + +echo +echo --------------------------------------------------- +echo Compiling for FPC on $ARCH using $GCC +$GCC --target=armv7a-linux-androideabi21 -static -O2 -Wno-pointer-sign -DNDEBUG -DNO_TCL -D_CRT_SECURE_NO_DEPRECATE -D__ARM_PCS_VFP -c sqlite3mc.c -o sqlite3-$ARCH.o + +#$CROSS/bin/llvm-strip sqlite3-$ARCH.o +# striping remove all exported symbols :( + +cp sqlite3-$ARCH.o $DST +cp sqlite3-$ARCH.o $DST2 + diff --git a/contrib/mORMot/SQLite3/amalgamation/compile-fpc-arm-linux.sh b/contrib/mORMot/SQLite3/amalgamation/compile-fpc-arm-linux.sh index 660364d..34e268a 100644 --- a/contrib/mORMot/SQLite3/amalgamation/compile-fpc-arm-linux.sh +++ b/contrib/mORMot/SQLite3/amalgamation/compile-fpc-arm-linux.sh @@ -5,13 +5,19 @@ ARCH=arm-linux CROSS=/home/ab/fpcup/cross/bin/$ARCH GCC=$CROSS/arm-linux-gnueabihf-gcc DST=../../static/$ARCH/sqlite3.o +DST2=../../../lib2/static/$ARCH/sqlite3.o rm $DST +rm $DST2 rm sqlite3-$ARCH.o echo echo --------------------------------------------------- echo Compiling for FPC on $ARCH using $GCC $GCC -static -O1 -marm -march=armv7-a+fp -I$CROSS/include -DNDEBUG -DNO_TCL -D_CRT_SECURE_NO_DEPRECATE -D__ARM_PCS_VFP -mfloat-abi=hard -c sqlite3mc.c -o sqlite3-$ARCH.o -cp sqlite3-$ARCH.o $DST + +$CROSS/arm-linux-gnueabihf-strip -d -x sqlite3-$ARCH.o + +cp sqlite3-$ARCH.o $DST +cp sqlite3-$ARCH.o $DST2 diff --git a/contrib/mORMot/SQLite3/amalgamation/compile-fpc-i386-android.sh b/contrib/mORMot/SQLite3/amalgamation/compile-fpc-i386-android.sh new file mode 100644 index 0000000..b7b616e --- /dev/null +++ b/contrib/mORMot/SQLite3/amalgamation/compile-fpc-i386-android.sh @@ -0,0 +1,24 @@ +#!/bin/sh + +ARCH=i386-android + +CROSS=/home/ab/fpcup/cross/bin/all-android +GCC=$CROSS/bin/clang +DST=../../static/$ARCH/sqlite3.o +DST2=../../../lib2/static/$ARCH/sqlite3.o + +rm $DST +rm $DST2 +rm sqlite3-$ARCH.o + +echo +echo --------------------------------------------------- +echo Compiling for FPC on $ARCH using $GCC +$GCC --target=i686-linux-androideabi21 -static -Wno-pointer-sign -O2 -DNDEBUG -DNO_TCL -D_CRT_SECURE_NO_DEPRECATE -D__ARM_PCS_VFP -c sqlite3mc.c -o sqlite3-$ARCH.o + +#$CROSS/bin/llvm-strip sqlite3-$ARCH.o +# strip blows all external symbols + +cp sqlite3-$ARCH.o $DST +cp sqlite3-$ARCH.o $DST2 + diff --git a/contrib/mORMot/SQLite3/amalgamation/compile-fpc-i386-darwin.sh b/contrib/mORMot/SQLite3/amalgamation/compile-fpc-i386-darwin.sh index 2347f73..e25138d 100644 --- a/contrib/mORMot/SQLite3/amalgamation/compile-fpc-i386-darwin.sh +++ b/contrib/mORMot/SQLite3/amalgamation/compile-fpc-i386-darwin.sh @@ -2,18 +2,24 @@ ARCH=i386-darwin DST=../../static/$ARCH/sqlite3.o +DST2=../../../lib2/static/$ARCH/sqlite3.o -CROSS=/home/ab/fpcup/cross +#CROSS=/home/ab/fpcup/cross +# use older but working fpcupdeluxe cross compiler +CROSS=/home/abouchez/fpcupdeluxe/__darwin SDK=$CROSS/lib/x86-darwin/MacOSX10.11.sdk\usr GCC=$CROSS/bin/x86-darwin/i386-apple-darwin15 rm $DST +rm $DST2 rm sqlite3-$ARCH.o echo echo --------------------------------------------------- echo Compiling for FPC on $ARCH using $GCC -$GCC-clang -static -target i386-apple-darwin15 -O2 -m32 -DNDEBUG -DNO_TCL -D_CRT_SECURE_NO_DEPRECATE -I$SDK/include -c sqlite3mc.c -o sqlite3-$ARCH.o +$GCC-clang -static -target i386-apple-darwin15 -Wno-pointer-sign -O2 -m32 -DNDEBUG -DNO_TCL -D_CRT_SECURE_NO_DEPRECATE -I$SDK/include -c sqlite3mc.c -o sqlite3-$ARCH.o cp sqlite3-$ARCH.o $DST +cp sqlite3-$ARCH.o $DST2 $GCC-libtool -static sqlite3-$ARCH.o -o ../../static/$ARCH/libsqlite3.a +$GCC-libtool -static sqlite3-$ARCH.o -o ../../../lib2/static/$ARCH/libsqlite3.a diff --git a/contrib/mORMot/SQLite3/amalgamation/compile-fpc-i386-freebsd.sh b/contrib/mORMot/SQLite3/amalgamation/compile-fpc-i386-freebsd.sh index 8241870..0ccb9cf 100644 --- a/contrib/mORMot/SQLite3/amalgamation/compile-fpc-i386-freebsd.sh +++ b/contrib/mORMot/SQLite3/amalgamation/compile-fpc-i386-freebsd.sh @@ -5,13 +5,19 @@ FPCARCHVERSION=12 CROSS=/home/ab/fpcup/cross/bin/$FPCARCH GCC=$CROSS/$FPCARCH$FPCARCHVERSION-gcc DST=../../static/$FPCARCH/sqlite3.o +DST2=../../../lib2/static/$FPCARCH/sqlite3.o rm $DST +rm $DST2 rm sqlite3-$FPCARCH.o echo echo --------------------------------------------------- echo Compiling for FPC on $FPCARCH using $GCC $GCC -static -O2 -m32 -DNDEBUG -DNO_TCL -D_CRT_SECURE_NO_DEPRECATE -c sqlite3mc.c -o sqlite3-$FPCARCH.o -cp sqlite3-$FPCARCH.o $DST + +$CROSS/$FPCARCH$FPCARCHVERSION-strip -d -x sqlite3-$FPCARCH.o + +cp sqlite3-$FPCARCH.o $DST +cp sqlite3-$FPCARCH.o $DST2 diff --git a/contrib/mORMot/SQLite3/amalgamation/compile-fpc-i386-linux.bat b/contrib/mORMot/SQLite3/amalgamation/compile-fpc-i386-linux.bat index 98c1141..ec36e97 100644 --- a/contrib/mORMot/SQLite3/amalgamation/compile-fpc-i386-linux.bat +++ b/contrib/mORMot/SQLite3/amalgamation/compile-fpc-i386-linux.bat @@ -6,15 +6,19 @@ set GCCPATH=d:\fpcup\__win\bin\%FPCARCH% set GCC=%FPCARCH%-gcc set DST=..\..\static\%FPCARCH%\sqlite3.o +set DST2=..\..\..\lib2\static\%FPCARCH%\sqlite3.o set path=%path%;%GCCPATH% del %DST% +del %DST2% del sqlite3-%FPCARCH%.o echo. echo --------------------------------------------------- echo Compiling for FPC on %FPCARCH% using %GCC% %GCC% -static -w -O2 -m32 -DNDEBUG -DNO_TCL -D_CRT_SECURE_NO_DEPRECATE -c sqlite3mc.c -o sqlite3-%FPCARCH%.o + copy sqlite3-%FPCARCH%.o %DST% +copy sqlite3-%FPCARCH%.o %DST2% rem pause \ No newline at end of file diff --git a/contrib/mORMot/SQLite3/amalgamation/compile-fpc-i386-linux.sh b/contrib/mORMot/SQLite3/amalgamation/compile-fpc-i386-linux.sh index 998241f..4014e99 100644 --- a/contrib/mORMot/SQLite3/amalgamation/compile-fpc-i386-linux.sh +++ b/contrib/mORMot/SQLite3/amalgamation/compile-fpc-i386-linux.sh @@ -3,13 +3,19 @@ FPCARCH=i386-linux GCC=gcc-7 DST=../../static/$FPCARCH/sqlite3.o +DST2=../../../lib2/static/$FPCARCH/sqlite3.o rm $DST +rm $DST2 rm sqlite3-$FPCARCH.o echo echo --------------------------------------------------- echo Compiling for FPC on $FPCARCH using $GCC $GCC -static -O2 -m32 -DNDEBUG -DNO_TCL -D_CRT_SECURE_NO_DEPRECATE -c sqlite3mc.c -o sqlite3-$FPCARCH.o -cp sqlite3-$FPCARCH.o $DST + +strip -d -x sqlite3-$FPCARCH.o + +cp sqlite3-$FPCARCH.o $DST +cp sqlite3-$FPCARCH.o $DST2 diff --git a/contrib/mORMot/SQLite3/amalgamation/compile-fpc-i386-openbsd.sh b/contrib/mORMot/SQLite3/amalgamation/compile-fpc-i386-openbsd.sh index 3a44d9a..921cde1 100644 --- a/contrib/mORMot/SQLite3/amalgamation/compile-fpc-i386-openbsd.sh +++ b/contrib/mORMot/SQLite3/amalgamation/compile-fpc-i386-openbsd.sh @@ -5,13 +5,19 @@ FPCARCHVERSION= CROSS=/home/ab/fpcup/cross/bin/$FPCARCH GCC=$CROSS/$FPCARCH$FPCARCHVERSION-gcc DST=../../static/$FPCARCH/sqlite3.o +DST2=../../../lib2/static/$FPCARCH/sqlite3.o rm $DST +rm $DST2 rm sqlite3-$FPCARCH.o echo echo --------------------------------------------------- echo Compiling for FPC on $FPCARCH using $GCC $GCC -static -O2 -m32 -DNDEBUG -DNO_TCL -D_CRT_SECURE_NO_DEPRECATE -c sqlite3mc.c -o sqlite3-$FPCARCH.o -cp sqlite3-$FPCARCH.o $DST + +$CROSS/$FPCARCH$FPCARCHVERSION-strip -d -x sqlite3-$FPCARCH.o + +cp sqlite3-$FPCARCH.o $DST +cp sqlite3-$FPCARCH.o $DST2 diff --git a/contrib/mORMot/SQLite3/amalgamation/compile-fpc-i386-win32.sh b/contrib/mORMot/SQLite3/amalgamation/compile-fpc-i386-win32.sh index 0923dd9..ff9f48c 100644 --- a/contrib/mORMot/SQLite3/amalgamation/compile-fpc-i386-win32.sh +++ b/contrib/mORMot/SQLite3/amalgamation/compile-fpc-i386-win32.sh @@ -3,13 +3,18 @@ ARCH=i386-win32 GCC=i686-w64-mingw32-gcc DST=../../static/$ARCH/sqlite3.o +DST2=../../../lib2/static/$ARCH/sqlite3.o rm $DST +rm $DST2 rm sqlite3-$ARCH.o echo echo --------------------------------------------------- echo Compiling for FPC on $ARCH using $GCC $GCC -O2 -m32 -DWIN32 -DNDEBUG -D_WINDOWS -c sqlite3mc.c -o sqlite3-$ARCH.o -cp sqlite3-$ARCH.o $DST +i686-w64-mingw32-strip -d -x sqlite3-$ARCH.o + +cp sqlite3-$ARCH.o $DST +cp sqlite3-$ARCH.o $DST2 diff --git a/contrib/mORMot/SQLite3/amalgamation/compile-fpc-x86_64-android.sh b/contrib/mORMot/SQLite3/amalgamation/compile-fpc-x86_64-android.sh new file mode 100644 index 0000000..0af678e --- /dev/null +++ b/contrib/mORMot/SQLite3/amalgamation/compile-fpc-x86_64-android.sh @@ -0,0 +1,24 @@ +#!/bin/sh + +ARCH=x86_64-android + +CROSS=/home/ab/fpcup/cross/bin/all-android/bin +GCC=$CROSS/clang +DST=../../static/$ARCH/sqlite3.o +DST2=../../../lib2/static/$ARCH/sqlite3.o + +rm $DST +rm $DST2 +rm sqlite3-$ARCH.o + +echo +echo --------------------------------------------------- +echo Compiling for FPC on $ARCH using $GCC +$GCC --target=x86_64-linux-android21 -static -O2 -Wno-pointer-sign -DNDEBUG -DNO_TCL -D_CRT_SECURE_NO_DEPRECATE -c sqlite3mc.c -o sqlite3-$ARCH.o + +#$CROSS/llvm-strip sqlite3-$ARCH.o +# striping remove all exported symbols :( + +cp sqlite3-$ARCH.o $DST +cp sqlite3-$ARCH.o $DST2 + diff --git a/contrib/mORMot/SQLite3/amalgamation/compile-fpc-x86_64-darwin.sh b/contrib/mORMot/SQLite3/amalgamation/compile-fpc-x86_64-darwin.sh index 6d94959..8ef63e0 100644 --- a/contrib/mORMot/SQLite3/amalgamation/compile-fpc-x86_64-darwin.sh +++ b/contrib/mORMot/SQLite3/amalgamation/compile-fpc-x86_64-darwin.sh @@ -2,18 +2,24 @@ ARCH=x86_64-darwin DST=../../static/$ARCH/sqlite3.o +DST2=../../../lib2/static/$ARCH/sqlite3.o -CROSS=/home/ab/fpcup/cross +#CROSS=/home/ab/fpcup/cross +# use older but working fpcupdeluxe cross compiler +CROSS=/home/abouchez/fpcupdeluxe/__darwin SDK=$CROSS/lib/x86-darwin/MacOSX10.11.sdk\usr GCC=$CROSS/bin/x86-darwin/x86_64-apple-darwin15 rm $DST +rm $DST2 rm sqlite3-$ARCH.o echo echo --------------------------------------------------- echo Compiling for FPC on $ARCH using $GCC -$GCC-clang -static -target x86_64-apple-darwin15 -O2 -m64 -DNDEBUG -DNO_TCL -D_CRT_SECURE_NO_DEPRECATE -I$SDK/include -c sqlite3mc.c -o sqlite3-$ARCH.o +$GCC-clang -static -target x86_64-apple-darwin15 -O2 -m64 -Wno-pointer-sign -DNDEBUG -DNO_TCL -D_CRT_SECURE_NO_DEPRECATE -I$SDK/include -c sqlite3mc.c -o sqlite3-$ARCH.o cp sqlite3-$ARCH.o $DST +cp sqlite3-$ARCH.o $DST2 $GCC-libtool -static sqlite3-$ARCH.o -o ../../static/$ARCH/libsqlite3.a +$GCC-libtool -static sqlite3-$ARCH.o -o ../../../lib2/static/$ARCH/libsqlite3.a diff --git a/contrib/mORMot/SQLite3/amalgamation/compile-fpc-x86_64-freebsd.sh b/contrib/mORMot/SQLite3/amalgamation/compile-fpc-x86_64-freebsd.sh index 490d2fc..4ed01a7 100644 --- a/contrib/mORMot/SQLite3/amalgamation/compile-fpc-x86_64-freebsd.sh +++ b/contrib/mORMot/SQLite3/amalgamation/compile-fpc-x86_64-freebsd.sh @@ -5,13 +5,19 @@ FPCARCHVERSION=12 CROSS=/home/ab/fpcup/cross/bin/$FPCARCH GCC=$CROSS/$FPCARCH$FPCARCHVERSION-gcc DST=../../static/$FPCARCH/sqlite3.o +DST2=../../../lib2/static/$FPCARCH/sqlite3.o rm $DST +rm $DST2 rm sqlite3-$FPCARCH.o echo echo --------------------------------------------------- echo Compiling for FPC on $FPCARCH using $GCC $GCC -static -O2 -m64 -DNDEBUG -DNO_TCL -D_CRT_SECURE_NO_DEPRECATE -c sqlite3mc.c -o sqlite3-$FPCARCH.o -cp sqlite3-$FPCARCH.o $DST + +$CROSS/$FPCARCH$FPCARCHVERSION-strip -d -x sqlite3-$FPCARCH.o + +cp sqlite3-$FPCARCH.o $DST +cp sqlite3-$FPCARCH.o $DST2 diff --git a/contrib/mORMot/SQLite3/amalgamation/compile-fpc-x86_64-linux.bat b/contrib/mORMot/SQLite3/amalgamation/compile-fpc-x86_64-linux.bat index ac13b61..75f8495 100644 --- a/contrib/mORMot/SQLite3/amalgamation/compile-fpc-x86_64-linux.bat +++ b/contrib/mORMot/SQLite3/amalgamation/compile-fpc-x86_64-linux.bat @@ -6,15 +6,21 @@ set GCCPATH=d:\fpcup\__win\bin\%FPCARCH% set GCC=%FPCARCH%-gcc set DST=..\..\static\%FPCARCH%\sqlite3.o +set DST2=..\..\..\lib2\static\%FPCARCH%\sqlite3.o set path=%path%;%GCCPATH% del %DST% +del %DST2% del sqlite3-%FPCARCH%.o echo. echo --------------------------------------------------- echo Compiling for FPC on %FPCARCH% using %GCC% -%GCC% -static -w -O2 -fno-pic -fno-stack-protector -m64 -DNDEBUG -DNO_TCL -D_CRT_SECURE_NO_DEPRECATE -c sqlite3mc.c -o sqlite3-%FPCARCH%.o +%GCC% -static -w -O2 -fno-pic -fno-stack-protector -fomit-frame-pointer -fno-exceptions -fno-asynchronous-unwind-tables -fno-unwind-tables -m64 -DNDEBUG -DNO_TCL -D_CRT_SECURE_NO_DEPRECATE -c sqlite3mc.c -o sqlite3-%FPCARCH%.o + +%FPCARCH%-strip -x sqlite3-%FPCARCH%.o + copy sqlite3-%FPCARCH%.o %DST% +copy sqlite3-%FPCARCH%.o %DST2% rem pause \ No newline at end of file diff --git a/contrib/mORMot/SQLite3/amalgamation/compile-fpc-x86_64-linux.sh b/contrib/mORMot/SQLite3/amalgamation/compile-fpc-x86_64-linux.sh index 4fcbbe6..c0e954d 100644 --- a/contrib/mORMot/SQLite3/amalgamation/compile-fpc-x86_64-linux.sh +++ b/contrib/mORMot/SQLite3/amalgamation/compile-fpc-x86_64-linux.sh @@ -3,13 +3,18 @@ FPCARCH=x86_64-linux GCC=gcc-7 DST=../../static/$FPCARCH/sqlite3.o +DST2=../../../lib2/static/$FPCARCH/sqlite3.o rm $DST +rm $DST2 rm sqlite3-$FPCARCH.o echo echo --------------------------------------------------- echo Compiling for FPC on $FPCARCH using $GCC $GCC -static -fno-pic -fno-stack-protector -O2 -m64 -DNDEBUG -DNO_TCL -D_CRT_SECURE_NO_DEPRECATE -c sqlite3mc.c -o sqlite3-$FPCARCH.o -cp sqlite3-$FPCARCH.o $DST +strip -d -x sqlite3-$FPCARCH.o + +cp sqlite3-$FPCARCH.o $DST +cp sqlite3-$FPCARCH.o $DST2 diff --git a/contrib/mORMot/SQLite3/amalgamation/compile-fpc-x86_64-openbsd.sh b/contrib/mORMot/SQLite3/amalgamation/compile-fpc-x86_64-openbsd.sh index b36b56d..f271a65 100644 --- a/contrib/mORMot/SQLite3/amalgamation/compile-fpc-x86_64-openbsd.sh +++ b/contrib/mORMot/SQLite3/amalgamation/compile-fpc-x86_64-openbsd.sh @@ -5,13 +5,19 @@ FPCARCHVERSION= CROSS=/home/ab/fpcup/cross/bin/$FPCARCH GCC=$CROSS/$FPCARCH$FPCARCHVERSION-gcc DST=../../static/$FPCARCH/sqlite3.o +DST2=../../../lib2/static/$FPCARCH/sqlite3.o rm $DST +rm $DST2 rm sqlite3-$FPCARCH.o echo echo --------------------------------------------------- echo Compiling for FPC on $FPCARCH using $GCC $GCC -static -O2 -m64 -DNDEBUG -DNO_TCL -D_CRT_SECURE_NO_DEPRECATE -c sqlite3mc.c -o sqlite3-$FPCARCH.o -cp sqlite3-$FPCARCH.o $DST + +$CROSS/$FPCARCH$FPCARCHVERSION-strip -d -x sqlite3-$FPCARCH.o + +cp sqlite3-$FPCARCH.o $DST +cp sqlite3-$FPCARCH.o $DST2 diff --git a/contrib/mORMot/SQLite3/amalgamation/compile-fpc-x86_64-win64.sh b/contrib/mORMot/SQLite3/amalgamation/compile-fpc-x86_64-win64.sh index 65325da..1f5d6af 100644 --- a/contrib/mORMot/SQLite3/amalgamation/compile-fpc-x86_64-win64.sh +++ b/contrib/mORMot/SQLite3/amalgamation/compile-fpc-x86_64-win64.sh @@ -3,25 +3,40 @@ ARCH=x86_64-win64 GCC=x86_64-w64-mingw32-gcc STATIC=../../static/$ARCH +LIB2=../../../lib2/static +STATIC2=$LIB2/$ARCH +STATIC2DELPHI=$LIB2/delphi DST=$STATIC/sqlite3.o +DST2=$STATIC2/sqlite3.o rm $DST +rm $DST2 rm sqlite3-$ARCH.o echo echo --------------------------------------------------- echo Compiling static for FPC on $ARCH using $GCC $GCC -O2 -static -DWIN64 -DNO_TCL -D_CRT_SECURE_NO_DEPRECATE -m64 -DNDEBUG -D_WINDOWS -c sqlite3mc.c -o sqlite3-$ARCH.o + +x86_64-w64-mingw32-strip -d -x sqlite3-$ARCH.o + cp sqlite3-$ARCH.o $DST +cp sqlite3-$ARCH.o $DST2 DLL=sqlite3-64.dll rm $DLL rm $STATIC/$DLL +rm $STATIC2DELPHI/$DLL +A=libsqlite3-64.a +rm $STATIC/$A +rm $STATIC2/$A echo echo --------------------------------------------------- echo Compiling $DLL using $GCC -$GCC -O2 -shared -DSQLITE_MMAP_READWRITE -DSQLITE_ENABLE_RTREE=1 -DSQLITE_ENABLE_FTS3 -DSQLITE_ENABLE_FTS4 -DDSQLITE_ENABLE_FTS5 -DSQLITE_ENABLE_FTS3_PARENTHESIS -DSQLITE_ENABLE_JSON1 -DSQLITE_ENABLE_DESERIALIZE -DWIN64 -DNDEBUG -D_WINDOWS -D_USRDLL -DNO_TCL -D_CRT_SECURE_NO_DEPRECATE -DSQLITE_MAX_EXPR_DEPTH=0 -DSQLITE_THREADSAFE=1 -DTEMP_STORE=1 -m64 sqlite3.c -o $DLL -Wl,--out-implib,libsqlite3-64.a +$GCC -O2 -shared -DSQLITE_MMAP_READWRITE -DSQLITE_ENABLE_RTREE=1 -DSQLITE_ENABLE_FTS3 -DSQLITE_ENABLE_FTS4 -DSQLITE_ENABLE_FTS5 -DSQLITE_ENABLE_FTS3_PARENTHESIS -DSQLITE_ENABLE_RBU -DSQLITE_ENABLE_JSON1 -DSQLITE_ENABLE_DESERIALIZE -DWIN64 -DNDEBUG -D_WINDOWS -D_USRDLL -DNO_TCL -D_CRT_SECURE_NO_DEPRECATE -DSQLITE_MAX_EXPR_DEPTH=0 -DSQLITE_THREADSAFE=1 -DTEMP_STORE=1 -m64 sqlite3.c -o $DLL -Wl,--out-implib,libsqlite3-64.a cp $DLL $STATIC -cp libsqlite3-64.a $STATIC +cp $DLL $STATIC2DELPHI +cp $A $STATIC +cp $A $STATIC2 diff --git a/contrib/mORMot/SQLite3/amalgamation/patch-and-compile.sh b/contrib/mORMot/SQLite3/amalgamation/patch-and-compile.sh index ab1f063..8690996 100644 --- a/contrib/mORMot/SQLite3/amalgamation/patch-and-compile.sh +++ b/contrib/mORMot/SQLite3/amalgamation/patch-and-compile.sh @@ -49,3 +49,11 @@ echo Use Native fpcupdeluxe cross-compilers for FPC OpenBSD i386/x64 ./compile-fpc-i386-openbsd.sh ./compile-fpc-x86_64-openbsd.sh + +echo +echo Use Native fpcupdeluxe cross-compilers for FPC Android arm/aarch64/i386/x64-android + +./compile-fpc-arm-android.sh +./compile-fpc-aarch64-android.sh +./compile-fpc-i386-android.sh +./compile-fpc-x86_64-android.sh diff --git a/contrib/mORMot/SQLite3/amalgamation/regexp.c b/contrib/mORMot/SQLite3/amalgamation/regexp.c new file mode 100644 index 0000000..03bbeb9 --- /dev/null +++ b/contrib/mORMot/SQLite3/amalgamation/regexp.c @@ -0,0 +1,760 @@ +/* +** 2012-11-13 +** +** The author disclaims copyright to this source code. In place of +** a legal notice, here is a blessing: +** +** May you do good and not evil. +** May you find forgiveness for yourself and forgive others. +** May you share freely, never taking more than you give. +** +****************************************************************************** +** +** The code in this file implements a compact but reasonably +** efficient regular-expression matcher for posix extended regular +** expressions against UTF8 text. +** +** This file is an SQLite extension. It registers a single function +** named "regexp(A,B)" where A is the regular expression and B is the +** string to be matched. By registering this function, SQLite will also +** then implement the "B regexp A" operator. Note that with the function +** the regular expression comes first, but with the operator it comes +** second. +** +** The following regular expression syntax is supported: +** +** X* zero or more occurrences of X +** X+ one or more occurrences of X +** X? zero or one occurrences of X +** X{p,q} between p and q occurrences of X +** (X) match X +** X|Y X or Y +** ^X X occurring at the beginning of the string +** X$ X occurring at the end of the string +** . Match any single character +** \c Character c where c is one of \{}()[]|*+?. +** \c C-language escapes for c in afnrtv. ex: \t or \n +** \uXXXX Where XXXX is exactly 4 hex digits, unicode value XXXX +** \xXX Where XX is exactly 2 hex digits, unicode value XX +** [abc] Any single character from the set abc +** [^abc] Any single character not in the set abc +** [a-z] Any single character in the range a-z +** [^a-z] Any single character not in the range a-z +** \b Word boundary +** \w Word character. [A-Za-z0-9_] +** \W Non-word character +** \d Digit +** \D Non-digit +** \s Whitespace character +** \S Non-whitespace character +** +** A nondeterministic finite automaton (NFA) is used for matching, so the +** performance is bounded by O(N*M) where N is the size of the regular +** expression and M is the size of the input string. The matcher never +** exhibits exponential behavior. Note that the X{p,q} operator expands +** to p copies of X following by q-p copies of X? and that the size of the +** regular expression in the O(N*M) performance bound is computed after +** this expansion. +*/ +#include +#include +#include "sqlite3ext.h" +SQLITE_EXTENSION_INIT1 + +/* +** The following #defines change the names of some functions implemented in +** this file to prevent name collisions with C-library functions of the +** same name. +*/ +#define re_match sqlite3re_match +#define re_compile sqlite3re_compile +#define re_free sqlite3re_free + +/* The end-of-input character */ +#define RE_EOF 0 /* End of input */ + +/* The NFA is implemented as sequence of opcodes taken from the following +** set. Each opcode has a single integer argument. +*/ +#define RE_OP_MATCH 1 /* Match the one character in the argument */ +#define RE_OP_ANY 2 /* Match any one character. (Implements ".") */ +#define RE_OP_ANYSTAR 3 /* Special optimized version of .* */ +#define RE_OP_FORK 4 /* Continue to both next and opcode at iArg */ +#define RE_OP_GOTO 5 /* Jump to opcode at iArg */ +#define RE_OP_ACCEPT 6 /* Halt and indicate a successful match */ +#define RE_OP_CC_INC 7 /* Beginning of a [...] character class */ +#define RE_OP_CC_EXC 8 /* Beginning of a [^...] character class */ +#define RE_OP_CC_VALUE 9 /* Single value in a character class */ +#define RE_OP_CC_RANGE 10 /* Range of values in a character class */ +#define RE_OP_WORD 11 /* Perl word character [A-Za-z0-9_] */ +#define RE_OP_NOTWORD 12 /* Not a perl word character */ +#define RE_OP_DIGIT 13 /* digit: [0-9] */ +#define RE_OP_NOTDIGIT 14 /* Not a digit */ +#define RE_OP_SPACE 15 /* space: [ \t\n\r\v\f] */ +#define RE_OP_NOTSPACE 16 /* Not a digit */ +#define RE_OP_BOUNDARY 17 /* Boundary between word and non-word */ + +/* Each opcode is a "state" in the NFA */ +typedef unsigned short ReStateNumber; + +/* Because this is an NFA and not a DFA, multiple states can be active at +** once. An instance of the following object records all active states in +** the NFA. The implementation is optimized for the common case where the +** number of actives states is small. +*/ +typedef struct ReStateSet { + unsigned nState; /* Number of current states */ + ReStateNumber *aState; /* Current states */ +} ReStateSet; + +/* An input string read one character at a time. +*/ +typedef struct ReInput ReInput; +struct ReInput { + const unsigned char *z; /* All text */ + int i; /* Next byte to read */ + int mx; /* EOF when i>=mx */ +}; + +/* A compiled NFA (or an NFA that is in the process of being compiled) is +** an instance of the following object. +*/ +typedef struct ReCompiled ReCompiled; +struct ReCompiled { + ReInput sIn; /* Regular expression text */ + const char *zErr; /* Error message to return */ + char *aOp; /* Operators for the virtual machine */ + int *aArg; /* Arguments to each operator */ + unsigned (*xNextChar)(ReInput*); /* Next character function */ + unsigned char zInit[12]; /* Initial text to match */ + int nInit; /* Number of characters in zInit */ + unsigned nState; /* Number of entries in aOp[] and aArg[] */ + unsigned nAlloc; /* Slots allocated for aOp[] and aArg[] */ +}; + +/* Add a state to the given state set if it is not already there */ +static void re_add_state(ReStateSet *pSet, int newState){ + unsigned i; + for(i=0; inState; i++) if( pSet->aState[i]==newState ) return; + pSet->aState[pSet->nState++] = (ReStateNumber)newState; +} + +/* Extract the next unicode character from *pzIn and return it. Advance +** *pzIn to the first byte past the end of the character returned. To +** be clear: this routine converts utf8 to unicode. This routine is +** optimized for the common case where the next character is a single byte. +*/ +static unsigned re_next_char(ReInput *p){ + unsigned c; + if( p->i>=p->mx ) return 0; + c = p->z[p->i++]; + if( c>=0x80 ){ + if( (c&0xe0)==0xc0 && p->imx && (p->z[p->i]&0xc0)==0x80 ){ + c = (c&0x1f)<<6 | (p->z[p->i++]&0x3f); + if( c<0x80 ) c = 0xfffd; + }else if( (c&0xf0)==0xe0 && p->i+1mx && (p->z[p->i]&0xc0)==0x80 + && (p->z[p->i+1]&0xc0)==0x80 ){ + c = (c&0x0f)<<12 | ((p->z[p->i]&0x3f)<<6) | (p->z[p->i+1]&0x3f); + p->i += 2; + if( c<=0x7ff || (c>=0xd800 && c<=0xdfff) ) c = 0xfffd; + }else if( (c&0xf8)==0xf0 && p->i+3mx && (p->z[p->i]&0xc0)==0x80 + && (p->z[p->i+1]&0xc0)==0x80 && (p->z[p->i+2]&0xc0)==0x80 ){ + c = (c&0x07)<<18 | ((p->z[p->i]&0x3f)<<12) | ((p->z[p->i+1]&0x3f)<<6) + | (p->z[p->i+2]&0x3f); + p->i += 3; + if( c<=0xffff || c>0x10ffff ) c = 0xfffd; + }else{ + c = 0xfffd; + } + } + return c; +} +static unsigned re_next_char_nocase(ReInput *p){ + unsigned c = re_next_char(p); + if( c>='A' && c<='Z' ) c += 'a' - 'A'; + return c; +} + +/* Return true if c is a perl "word" character: [A-Za-z0-9_] */ +static int re_word_char(int c){ + return (c>='0' && c<='9') || (c>='a' && c<='z') + || (c>='A' && c<='Z') || c=='_'; +} + +/* Return true if c is a "digit" character: [0-9] */ +static int re_digit_char(int c){ + return (c>='0' && c<='9'); +} + +/* Return true if c is a perl "space" character: [ \t\r\n\v\f] */ +static int re_space_char(int c){ + return c==' ' || c=='\t' || c=='\n' || c=='\r' || c=='\v' || c=='\f'; +} + +/* Run a compiled regular expression on the zero-terminated input +** string zIn[]. Return true on a match and false if there is no match. +*/ +static int re_match(ReCompiled *pRe, const unsigned char *zIn, int nIn){ + ReStateSet aStateSet[2], *pThis, *pNext; + ReStateNumber aSpace[100]; + ReStateNumber *pToFree; + unsigned int i = 0; + unsigned int iSwap = 0; + int c = RE_EOF+1; + int cPrev = 0; + int rc = 0; + ReInput in; + + in.z = zIn; + in.i = 0; + in.mx = nIn>=0 ? nIn : (int)strlen((char const*)zIn); + + /* Look for the initial prefix match, if there is one. */ + if( pRe->nInit ){ + unsigned char x = pRe->zInit[0]; + while( in.i+pRe->nInit<=in.mx + && (zIn[in.i]!=x || + strncmp((const char*)zIn+in.i, (const char*)pRe->zInit, pRe->nInit)!=0) + ){ + in.i++; + } + if( in.i+pRe->nInit>in.mx ) return 0; + } + + if( pRe->nState<=(sizeof(aSpace)/(sizeof(aSpace[0])*2)) ){ + pToFree = 0; + aStateSet[0].aState = aSpace; + }else{ + pToFree = sqlite3_malloc64( sizeof(ReStateNumber)*2*pRe->nState ); + if( pToFree==0 ) return -1; + aStateSet[0].aState = pToFree; + } + aStateSet[1].aState = &aStateSet[0].aState[pRe->nState]; + pNext = &aStateSet[1]; + pNext->nState = 0; + re_add_state(pNext, 0); + while( c!=RE_EOF && pNext->nState>0 ){ + cPrev = c; + c = pRe->xNextChar(&in); + pThis = pNext; + pNext = &aStateSet[iSwap]; + iSwap = 1 - iSwap; + pNext->nState = 0; + for(i=0; inState; i++){ + int x = pThis->aState[i]; + switch( pRe->aOp[x] ){ + case RE_OP_MATCH: { + if( pRe->aArg[x]==c ) re_add_state(pNext, x+1); + break; + } + case RE_OP_ANY: { + re_add_state(pNext, x+1); + break; + } + case RE_OP_WORD: { + if( re_word_char(c) ) re_add_state(pNext, x+1); + break; + } + case RE_OP_NOTWORD: { + if( !re_word_char(c) ) re_add_state(pNext, x+1); + break; + } + case RE_OP_DIGIT: { + if( re_digit_char(c) ) re_add_state(pNext, x+1); + break; + } + case RE_OP_NOTDIGIT: { + if( !re_digit_char(c) ) re_add_state(pNext, x+1); + break; + } + case RE_OP_SPACE: { + if( re_space_char(c) ) re_add_state(pNext, x+1); + break; + } + case RE_OP_NOTSPACE: { + if( !re_space_char(c) ) re_add_state(pNext, x+1); + break; + } + case RE_OP_BOUNDARY: { + if( re_word_char(c)!=re_word_char(cPrev) ) re_add_state(pThis, x+1); + break; + } + case RE_OP_ANYSTAR: { + re_add_state(pNext, x); + re_add_state(pThis, x+1); + break; + } + case RE_OP_FORK: { + re_add_state(pThis, x+pRe->aArg[x]); + re_add_state(pThis, x+1); + break; + } + case RE_OP_GOTO: { + re_add_state(pThis, x+pRe->aArg[x]); + break; + } + case RE_OP_ACCEPT: { + rc = 1; + goto re_match_end; + } + case RE_OP_CC_INC: + case RE_OP_CC_EXC: { + int j = 1; + int n = pRe->aArg[x]; + int hit = 0; + for(j=1; j>0 && jaOp[x+j]==RE_OP_CC_VALUE ){ + if( pRe->aArg[x+j]==c ){ + hit = 1; + j = -1; + } + }else{ + if( pRe->aArg[x+j]<=c && pRe->aArg[x+j+1]>=c ){ + hit = 1; + j = -1; + }else{ + j++; + } + } + } + if( pRe->aOp[x]==RE_OP_CC_EXC ) hit = !hit; + if( hit ) re_add_state(pNext, x+n); + break; + } + } + } + } + for(i=0; inState; i++){ + if( pRe->aOp[pNext->aState[i]]==RE_OP_ACCEPT ){ rc = 1; break; } + } +re_match_end: + sqlite3_free(pToFree); + return rc; +} + +/* Resize the opcode and argument arrays for an RE under construction. +*/ +static int re_resize(ReCompiled *p, int N){ + char *aOp; + int *aArg; + aOp = sqlite3_realloc64(p->aOp, N*sizeof(p->aOp[0])); + if( aOp==0 ) return 1; + p->aOp = aOp; + aArg = sqlite3_realloc64(p->aArg, N*sizeof(p->aArg[0])); + if( aArg==0 ) return 1; + p->aArg = aArg; + p->nAlloc = N; + return 0; +} + +/* Insert a new opcode and argument into an RE under construction. The +** insertion point is just prior to existing opcode iBefore. +*/ +static int re_insert(ReCompiled *p, int iBefore, int op, int arg){ + int i; + if( p->nAlloc<=p->nState && re_resize(p, p->nAlloc*2) ) return 0; + for(i=p->nState; i>iBefore; i--){ + p->aOp[i] = p->aOp[i-1]; + p->aArg[i] = p->aArg[i-1]; + } + p->nState++; + p->aOp[iBefore] = (char)op; + p->aArg[iBefore] = arg; + return iBefore; +} + +/* Append a new opcode and argument to the end of the RE under construction. +*/ +static int re_append(ReCompiled *p, int op, int arg){ + return re_insert(p, p->nState, op, arg); +} + +/* Make a copy of N opcodes starting at iStart onto the end of the RE +** under construction. +*/ +static void re_copy(ReCompiled *p, int iStart, int N){ + if( p->nState+N>=p->nAlloc && re_resize(p, p->nAlloc*2+N) ) return; + memcpy(&p->aOp[p->nState], &p->aOp[iStart], N*sizeof(p->aOp[0])); + memcpy(&p->aArg[p->nState], &p->aArg[iStart], N*sizeof(p->aArg[0])); + p->nState += N; +} + +/* Return true if c is a hexadecimal digit character: [0-9a-fA-F] +** If c is a hex digit, also set *pV = (*pV)*16 + valueof(c). If +** c is not a hex digit *pV is unchanged. +*/ +static int re_hex(int c, int *pV){ + if( c>='0' && c<='9' ){ + c -= '0'; + }else if( c>='a' && c<='f' ){ + c -= 'a' - 10; + }else if( c>='A' && c<='F' ){ + c -= 'A' - 10; + }else{ + return 0; + } + *pV = (*pV)*16 + (c & 0xff); + return 1; +} + +/* A backslash character has been seen, read the next character and +** return its interpretation. +*/ +static unsigned re_esc_char(ReCompiled *p){ + static const char zEsc[] = "afnrtv\\()*.+?[$^{|}]"; + static const char zTrans[] = "\a\f\n\r\t\v"; + int i, v = 0; + char c; + if( p->sIn.i>=p->sIn.mx ) return 0; + c = p->sIn.z[p->sIn.i]; + if( c=='u' && p->sIn.i+4sIn.mx ){ + const unsigned char *zIn = p->sIn.z + p->sIn.i; + if( re_hex(zIn[1],&v) + && re_hex(zIn[2],&v) + && re_hex(zIn[3],&v) + && re_hex(zIn[4],&v) + ){ + p->sIn.i += 5; + return v; + } + } + if( c=='x' && p->sIn.i+2sIn.mx ){ + const unsigned char *zIn = p->sIn.z + p->sIn.i; + if( re_hex(zIn[1],&v) + && re_hex(zIn[2],&v) + ){ + p->sIn.i += 3; + return v; + } + } + for(i=0; zEsc[i] && zEsc[i]!=c; i++){} + if( zEsc[i] ){ + if( i<6 ) c = zTrans[i]; + p->sIn.i++; + }else{ + p->zErr = "unknown \\ escape"; + } + return c; +} + +/* Forward declaration */ +static const char *re_subcompile_string(ReCompiled*); + +/* Peek at the next byte of input */ +static unsigned char rePeek(ReCompiled *p){ + return p->sIn.isIn.mx ? p->sIn.z[p->sIn.i] : 0; +} + +/* Compile RE text into a sequence of opcodes. Continue up to the +** first unmatched ")" character, then return. If an error is found, +** return a pointer to the error message string. +*/ +static const char *re_subcompile_re(ReCompiled *p){ + const char *zErr; + int iStart, iEnd, iGoto; + iStart = p->nState; + zErr = re_subcompile_string(p); + if( zErr ) return zErr; + while( rePeek(p)=='|' ){ + iEnd = p->nState; + re_insert(p, iStart, RE_OP_FORK, iEnd + 2 - iStart); + iGoto = re_append(p, RE_OP_GOTO, 0); + p->sIn.i++; + zErr = re_subcompile_string(p); + if( zErr ) return zErr; + p->aArg[iGoto] = p->nState - iGoto; + } + return 0; +} + +/* Compile an element of regular expression text (anything that can be +** an operand to the "|" operator). Return NULL on success or a pointer +** to the error message if there is a problem. +*/ +static const char *re_subcompile_string(ReCompiled *p){ + int iPrev = -1; + int iStart; + unsigned c; + const char *zErr; + while( (c = p->xNextChar(&p->sIn))!=0 ){ + iStart = p->nState; + switch( c ){ + case '|': + case '$': + case ')': { + p->sIn.i--; + return 0; + } + case '(': { + zErr = re_subcompile_re(p); + if( zErr ) return zErr; + if( rePeek(p)!=')' ) return "unmatched '('"; + p->sIn.i++; + break; + } + case '.': { + if( rePeek(p)=='*' ){ + re_append(p, RE_OP_ANYSTAR, 0); + p->sIn.i++; + }else{ + re_append(p, RE_OP_ANY, 0); + } + break; + } + case '*': { + if( iPrev<0 ) return "'*' without operand"; + re_insert(p, iPrev, RE_OP_GOTO, p->nState - iPrev + 1); + re_append(p, RE_OP_FORK, iPrev - p->nState + 1); + break; + } + case '+': { + if( iPrev<0 ) return "'+' without operand"; + re_append(p, RE_OP_FORK, iPrev - p->nState); + break; + } + case '?': { + if( iPrev<0 ) return "'?' without operand"; + re_insert(p, iPrev, RE_OP_FORK, p->nState - iPrev+1); + break; + } + case '{': { + int m = 0, n = 0; + int sz, j; + if( iPrev<0 ) return "'{m,n}' without operand"; + while( (c=rePeek(p))>='0' && c<='9' ){ m = m*10 + c - '0'; p->sIn.i++; } + n = m; + if( c==',' ){ + p->sIn.i++; + n = 0; + while( (c=rePeek(p))>='0' && c<='9' ){ n = n*10 + c-'0'; p->sIn.i++; } + } + if( c!='}' ) return "unmatched '{'"; + if( n>0 && nsIn.i++; + sz = p->nState - iPrev; + if( m==0 ){ + if( n==0 ) return "both m and n are zero in '{m,n}'"; + re_insert(p, iPrev, RE_OP_FORK, sz+1); + n--; + }else{ + for(j=1; j0 ){ + re_append(p, RE_OP_FORK, -sz); + } + break; + } + case '[': { + int iFirst = p->nState; + if( rePeek(p)=='^' ){ + re_append(p, RE_OP_CC_EXC, 0); + p->sIn.i++; + }else{ + re_append(p, RE_OP_CC_INC, 0); + } + while( (c = p->xNextChar(&p->sIn))!=0 ){ + if( c=='[' && rePeek(p)==':' ){ + return "POSIX character classes not supported"; + } + if( c=='\\' ) c = re_esc_char(p); + if( rePeek(p)=='-' ){ + re_append(p, RE_OP_CC_RANGE, c); + p->sIn.i++; + c = p->xNextChar(&p->sIn); + if( c=='\\' ) c = re_esc_char(p); + re_append(p, RE_OP_CC_RANGE, c); + }else{ + re_append(p, RE_OP_CC_VALUE, c); + } + if( rePeek(p)==']' ){ p->sIn.i++; break; } + } + if( c==0 ) return "unclosed '['"; + p->aArg[iFirst] = p->nState - iFirst; + break; + } + case '\\': { + int specialOp = 0; + switch( rePeek(p) ){ + case 'b': specialOp = RE_OP_BOUNDARY; break; + case 'd': specialOp = RE_OP_DIGIT; break; + case 'D': specialOp = RE_OP_NOTDIGIT; break; + case 's': specialOp = RE_OP_SPACE; break; + case 'S': specialOp = RE_OP_NOTSPACE; break; + case 'w': specialOp = RE_OP_WORD; break; + case 'W': specialOp = RE_OP_NOTWORD; break; + } + if( specialOp ){ + p->sIn.i++; + re_append(p, specialOp, 0); + }else{ + c = re_esc_char(p); + re_append(p, RE_OP_MATCH, c); + } + break; + } + default: { + re_append(p, RE_OP_MATCH, c); + break; + } + } + iPrev = iStart; + } + return 0; +} + +/* Free and reclaim all the memory used by a previously compiled +** regular expression. Applications should invoke this routine once +** for every call to re_compile() to avoid memory leaks. +*/ +static void re_free(ReCompiled *pRe){ + if( pRe ){ + sqlite3_free(pRe->aOp); + sqlite3_free(pRe->aArg); + sqlite3_free(pRe); + } +} + +/* +** Compile a textual regular expression in zIn[] into a compiled regular +** expression suitable for us by re_match() and return a pointer to the +** compiled regular expression in *ppRe. Return NULL on success or an +** error message if something goes wrong. +*/ +static const char *re_compile(ReCompiled **ppRe, const char *zIn, int noCase){ + ReCompiled *pRe; + const char *zErr; + int i, j; + + *ppRe = 0; + pRe = sqlite3_malloc( sizeof(*pRe) ); + if( pRe==0 ){ + return "out of memory"; + } + memset(pRe, 0, sizeof(*pRe)); + pRe->xNextChar = noCase ? re_next_char_nocase : re_next_char; + if( re_resize(pRe, 30) ){ + re_free(pRe); + return "out of memory"; + } + if( zIn[0]=='^' ){ + zIn++; + }else{ + re_append(pRe, RE_OP_ANYSTAR, 0); + } + pRe->sIn.z = (unsigned char*)zIn; + pRe->sIn.i = 0; + pRe->sIn.mx = (int)strlen(zIn); + zErr = re_subcompile_re(pRe); + if( zErr ){ + re_free(pRe); + return zErr; + } + if( rePeek(pRe)=='$' && pRe->sIn.i+1>=pRe->sIn.mx ){ + re_append(pRe, RE_OP_MATCH, RE_EOF); + re_append(pRe, RE_OP_ACCEPT, 0); + *ppRe = pRe; + }else if( pRe->sIn.i>=pRe->sIn.mx ){ + re_append(pRe, RE_OP_ACCEPT, 0); + *ppRe = pRe; + }else{ + re_free(pRe); + return "unrecognized character"; + } + + /* The following is a performance optimization. If the regex begins with + ** ".*" (if the input regex lacks an initial "^") and afterwards there are + ** one or more matching characters, enter those matching characters into + ** zInit[]. The re_match() routine can then search ahead in the input + ** string looking for the initial match without having to run the whole + ** regex engine over the string. Do not worry able trying to match + ** unicode characters beyond plane 0 - those are very rare and this is + ** just an optimization. */ + if( pRe->aOp[0]==RE_OP_ANYSTAR ){ + for(j=0, i=1; jzInit)-2 && pRe->aOp[i]==RE_OP_MATCH; i++){ + unsigned x = pRe->aArg[i]; + if( x<=127 ){ + pRe->zInit[j++] = (unsigned char)x; + }else if( x<=0xfff ){ + pRe->zInit[j++] = (unsigned char)(0xc0 | (x>>6)); + pRe->zInit[j++] = 0x80 | (x&0x3f); + }else if( x<=0xffff ){ + pRe->zInit[j++] = (unsigned char)(0xd0 | (x>>12)); + pRe->zInit[j++] = 0x80 | ((x>>6)&0x3f); + pRe->zInit[j++] = 0x80 | (x&0x3f); + }else{ + break; + } + } + if( j>0 && pRe->zInit[j-1]==0 ) j--; + pRe->nInit = j; + } + return pRe->zErr; +} + +/* +** Implementation of the regexp() SQL function. This function implements +** the build-in REGEXP operator. The first argument to the function is the +** pattern and the second argument is the string. So, the SQL statements: +** +** A REGEXP B +** +** is implemented as regexp(B,A). +*/ +static void re_sql_func( + sqlite3_context *context, + int argc, + sqlite3_value **argv +){ + ReCompiled *pRe; /* Compiled regular expression */ + const char *zPattern; /* The regular expression */ + const unsigned char *zStr;/* String being searched */ + const char *zErr; /* Compile error message */ + int setAux = 0; /* True to invoke sqlite3_set_auxdata() */ + + pRe = sqlite3_get_auxdata(context, 0); + if( pRe==0 ){ + zPattern = (const char*)sqlite3_value_text(argv[0]); + if( zPattern==0 ) return; + zErr = re_compile(&pRe, zPattern, 0); + if( zErr ){ + re_free(pRe); + sqlite3_result_error(context, zErr, -1); + return; + } + if( pRe==0 ){ + sqlite3_result_error_nomem(context); + return; + } + setAux = 1; + } + zStr = (const unsigned char*)sqlite3_value_text(argv[1]); + if( zStr!=0 ){ + sqlite3_result_int(context, re_match(pRe, zStr, -1)); + } + if( setAux ){ + sqlite3_set_auxdata(context, 0, pRe, (void(*)(void*))re_free); + } +} + +/* +** Invoke this routine to register the regexp() function with the +** SQLite database connection. +*/ +#ifdef _WIN32 +__declspec(dllexport) +#endif +int sqlite3_regexp_init( + sqlite3 *db, + char **pzErrMsg, + const sqlite3_api_routines *pApi +){ + int rc = SQLITE_OK; + SQLITE_EXTENSION_INIT2(pApi); + rc = sqlite3_create_function(db, "regexp", 2, SQLITE_UTF8|SQLITE_INNOCUOUS, + 0, re_sql_func, 0, 0); + return rc; +} diff --git a/contrib/mORMot/SQLite3/amalgamation/sqlite3mc.c b/contrib/mORMot/SQLite3/amalgamation/sqlite3mc.c index 2a797db..0b1cb46 100644 --- a/contrib/mORMot/SQLite3/amalgamation/sqlite3mc.c +++ b/contrib/mORMot/SQLite3/amalgamation/sqlite3mc.c @@ -1,53 +1,109 @@ /* -** Wrapper around SQlite3 amalgamation file with proper options and code +** Wrapper around SQlite3 amalgamation file for mORMot use ** ** Please download and put sqlite3.c in amalgamation/ sub-folder ** from https://sqlite.org/download.html -** then run ./patch.sh +** then follow the amalgamation/ReadMe.md instructions */ /* -** Define all symbols expected by SynSQLite3Static.pas +** Define conditionals / extensions specially tuned for mORMot ** ** See also https://www.sqlite.org/compile.html#recommended_compile_time_options */ #define SQLITE_DEFAULT_MEMSTATUS 0 // don't need any debug here, and don't even define sqlite3_status() -#define SQLITE_THREADSAFE 1 + // assuming multi-thread safety is made by caller - in our framework, there is // only one thread using the database connection at the same time, but there could -// be multiple database connection at the same time (previous was 0 could be unsafe) -// - this option is also needed by codecext.c +// be multiple database connection at the same time +// * 0 = single-thread = all mutexes disabled - seems unsafe +// * 1 = serialized = all calls serialized - seems overkill +// * 2 = multi-thread = thread-safe by connection - fine for our purpose +// - note that we keep 1=serialized at compile time (to allow all modes) +// but SQLITE_CONFIG_MULTITHREAD is set in TSqlite3Library.BeforeInitialization +// and rely on TSqlDataBase to do explicit Lock/LockJson/UnLock calls +#if SQLITE_NO_THREAD +# define SQLITE_THREADSAFE 0 +#else +# define SQLITE_THREADSAFE 1 +#endif + #define SQLITE_OMIT_SHARED_CACHE 1 // no need of shared cache in a threadsafe calling model + #define SQLITE_OMIT_AUTOINIT 1 -// sqlite3_initialize() is done in unit initialization -> no AUTOINIT +// sqlite3_initialize() is done in unit initialization -> no AUTOINIT + #define SQLITE_OMIT_DEPRECATED 1 -// spare some code size +// spare some code size + + #define SQLITE_LIKE_DOESNT_MATCH_BLOBS 1 // historical function, never used + #define SQLITE_ENABLE_FTS3 1 #define SQLITE_ENABLE_FTS3_PARENTHESIS 1 #define SQLITE_ENABLE_FTS4 1 #define SQLITE_ENABLE_FTS5 1 -// enable all FTS engines -#define SQLITE_ENABLE_RBU 1 -// "Resumable Bulk Update" (or OTA) is not used/published yet +// enable all FTS engines https://www.sqlite.org/fts3.html https://www.sqlite.org/fts5.html + #define SQLITE_ENABLE_JSON1 1 -// add JSON extension +// enable JSON https://www.sqlite.org/json1.html + #define SQLITE_MAX_EXPR_DEPTH 0 // no SQL depth limit, since we trust the input and expect the best performance -#define SQLITE_OMIT_LOAD_EXTENSION 1 -// we don't need/allow extension in an embedded engine -#define SQLITE_OMIT_COMPILEOPTION_DIAGS 1 -// we don't need Compilation Options Diagnostics in our embedded engine -#define SQLITE_OMIT_PROGRESS_CALLBACK 1 -// we don't need sqlite3_progress_handler() API function -#define SQLITE_ENABLE_RTREE 1 -// the RTREE extension is now (from v.1.8/3.7) compiled into the engine + #define SQLITE_ENABLE_DESERIALIZE -// enables sqlite3_serialize() and sqlite3_deserialize() +// enable sqlite3_serialize() and sqlite3_deserialize() + +#define SQLITE_ENABLE_RTREE 1 +// enable RTREE https://sqlite.org/rtree.html + +#define SQLITE_ENABLE_GEOPOLY 1 +// enable GeoJSON over RTREE https://sqlite.org/geopoly.html + +#define SQLITE_ENABLE_REGEXP 1 +// enable the compact https://www.sqlite.org/src/file?name=ext/misc/regexp.c +// - can be overloaded with any other implementation + +#define SQLITE_ENABLE_RBU 1 +// enable "Resumable Bulk Update" (or OTA) https://www.sqlite.org/rbu.html + +#define SQLITE_ENABLE_SESSION 1 +#define SQLITE_ENABLE_PREUPDATE_HOOK 1 +// enable Sessions https://sqlite.org/sessionintro.html + +#define SQLITE_ENABLE_NORMALIZE 1 +// enable all https://sqlite.org/c3ref/expanded_sql.html functions + +#define YYTRACKMAXSTACKDEPTH 1 +// enable SQLITE_STATUS_PARSER_STACK support + +#define SQLITE_ENABLE_COLUMN_METADATA 1 +//enable column_database_name, column_table_name and column_origin_name support + +#define SQLITE_ENABLE_STMT_SCANSTATUS 1 +// enable stmt_scanstatus and stmt_scanstatus_reset support + +#define SQLITE_ENABLE_SNAPSHOT 1 +// support the sqlite3_snapshot object + +#define SQLITE_ENABLE_UNLOCK_NOTIFY 1 +// enable sqlite3_unlock_notify + +/* +** Disabled conditionals / extensions +*/ + +// #define SQLITE_ENABLE_ICU +// disabled because induces a huge dependency - use WIN32NOCASE (which calls +// ICU on POSIX) or even better the UNICODENOCASE as available in mORMot 2 + +// #define SQLITE_ENABLE_STAT4 +// adds additional logic to the ANALYZE command and to the Query Planner + /* ** Define function for extra initilization @@ -229,6 +285,18 @@ static unsigned char* CodecGetPageBuffer(Codec* codec) #include "codecext.c" +/* +** REGEXP +*/ +#ifdef SQLITE_ENABLE_REGEXP +/* Prototype for initialization function of REGEXP extension */ +#ifdef _WIN32 +__declspec(dllexport) +#endif +int sqlite3_regexp_init(sqlite3 *db, char **pzErrMsg, const sqlite3_api_routines *pApi); +#include "regexp.c" +#endif + /* ** Multi cipher VFS */ @@ -259,6 +327,17 @@ sqlite3mc_initialize(const char* arg) } rc = sqlite3mc_vfs_initialize(vfsDefault, 1); } + + /* + ** Can be overloaded later with any other REGEXP engine + */ + #ifdef SQLITE_ENABLE_REGEXP + if (rc == SQLITE_OK) + { + rc = sqlite3_auto_extension((void(*)(void)) sqlite3_regexp_init); + } + #endif + return rc; } diff --git a/contrib/mORMot/SQLite3/mORMot.pas b/contrib/mORMot/SQLite3/mORMot.pas index 35d2e20..a99c962 100644 --- a/contrib/mORMot/SQLite3/mORMot.pas +++ b/contrib/mORMot/SQLite3/mORMot.pas @@ -6,7 +6,7 @@ unit mORMot; (* This file is part of Synopse mORMot framework. - Synopse mORMot framework. Copyright (C) 2020 Arnaud Bouchez + Synopse mORMot framework. Copyright (C) 2022 Arnaud Bouchez Synopse Informatique - https://synopse.info *** BEGIN LICENSE BLOCK ***** @@ -25,7 +25,7 @@ unit mORMot; The Initial Developer of the Original Code is Arnaud Bouchez. - Portions created by the Initial Developer are Copyright (C) 2020 + Portions created by the Initial Developer are Copyright (C) 2022 the Initial Developer. All Rights Reserved. Contributor(s): @@ -68,7 +68,7 @@ unit mORMot; *) -{$I Synopse.inc} // define HASINLINE CPU32 CPU64 +{$I ..\Synopse.inc} // define HASINLINE CPU32 CPU64 {.$define PUREPASCAL} // define for debugg, not on production @@ -112,7 +112,7 @@ unit mORMot; // - Setting NOGSSAPIAUTH conditional will disable this feature // Limitations: // - no NTLM support - this is a deprecated and vulnerable protocol -{$endif} +{$endif MSWINDOWS} {$ifdef KYLIX3} @@ -1929,7 +1929,7 @@ type /// =$ff for a ptField address, or =$fe for a ptVirtual method Kind: byte; end; - {$A+} // back to normal alignment + {$A+} // back to normal alignment {$endif FPC} const @@ -3441,7 +3441,7 @@ type end; var - /// acccess to Zip Deflate compression in level 6 as a TSynCompress class + /// acccess to Zip Deflate compression in level 6 as a TAlgoCompress class AlgoDeflate: TAlgoCompress; /// acccess to Zip Deflate compression in level 1 as a TAlgoCompress class AlgoDeflateFast: TAlgoCompress; @@ -4504,6 +4504,7 @@ type end; TServiceFactoryServer = class; + PSQLAccessRights = ^TSQLAccessRights; /// flags which may be set by the caller to notify low-level context @@ -4758,9 +4759,6 @@ type /// a set of potential actions to be executed from the server // - reSQL will indicate the right to execute any POST SQL statement (not only // SELECT statements) - // - reSQLSelectWithoutTable will allow executing a SELECT statement with - // arbitrary content via GET/LOCK (simple SELECT .. FROM aTable will be checked - // against TSQLAccessRights.GET[] per-table right // - reService will indicate the right to execute the interface-based JSON-RPC // service implementation // - reUrlEncodedSQL will indicate the right to execute a SQL query encoded @@ -4772,6 +4770,9 @@ type // for one user, even if connection comes from the same IP: in this case, // you may have to set the SessionTimeOut to a small value, in case the // session is not closed gracefully + // - reSQLSelectWithoutTable will allow executing a SELECT statement with + // arbitrary content via GET/LOCK (simple SELECT .. FROM aTable will be checked + // against TSQLAccessRights.GET[] per-table right // - by default, read/write access to the TSQLAuthUser table is disallowed, // for obvious security reasons: but you can define reUserCanChangeOwnPassword // so that the current logged user will be able to change its own password @@ -4970,6 +4971,9 @@ type /// the optional Blob field name as specified in URI // - e.g. retrieved from "ModelRoot/TableName/TableID/BlobFieldName" URIBlobFieldName: RawUTF8; + /// decoded URI for rsoMethodUnderscoreAsSlashURI in Server.Options + // - e.g. 'Method_Name' from 'ModelRoot/Method/Name' URI + URIUnderscoreAsSlash: RawUTF8; /// position of the &session_signature=... text in Call^.url string URISessionSignaturePos: integer; /// the Table as specified at the URI level (if any) @@ -5907,6 +5911,7 @@ type // - consider inherit from TSQLRecordNoCase and TSQLRecordNoCaseExtended if // you expect regular NOCASE collation and smaller (but not standard JSON) // variant fields persistence + // - is called TOrm in mORMot 2 TSQLRecord = class(TObject) { note that every TSQLRecord has an Instance size of 20 bytes for private and protected fields (such as fID or fProps e.g.) } @@ -6905,6 +6910,9 @@ type // EORMException: in this case, you should use a TID / T*ID kind of // published property, and not a TSQLRecord, which is limited to the // pointer size + // - on FPC, if you get an Error: Incompatible types: got "Pointer" expected + // "T...", then you are missing a {$mode Delphi} conditional in your unit: + // the easiest is to include {$I Synopse.inc} at the top of your unit property AsTSQLRecord: pointer read GetIDAsPointer; /// this property is set to true, if any published property is a BLOB (TSQLRawBlob) property HasBlob: boolean read GetHasBlob; @@ -7034,7 +7042,7 @@ type protected fRowCount: integer; fFieldCount: integer; - /// contains the data, as returned by sqlite3_get_table() + /// contains the data, e.g. as returned by sqlite3_get_table() fResults: PPUTF8CharArray; fFieldType: array of TSQLTableFieldType; fFieldTypeAllRows: boolean; @@ -7620,7 +7628,7 @@ type // of the first associated record class (from internal QueryTables[]) // - always returns an instance, even if the TSQLTable is nil or void function ToObjectList: TObjectList; overload; - {$endif} + {$endif ISDELPHI2010} /// fill an existing T*ObjArray variable with TSQLRecord instances // corresponding to this TSQLTable result set // - use the specified TSQLRecord class or create instances @@ -7836,9 +7844,10 @@ type // (no additional memory buffer is allocated) function ParseAndConvert(Buffer: PUTF8Char; BufferLen: integer): boolean; /// will check then set (if needed) internal fPrivateCopy[Hash] values - // - returns TRUE if content changed (then fPrivateCopy+fPrivateCopyHash - // will be updated using crc32c hash) - function PrivateCopyChanged(aJSON: PUTF8Char; aLen: integer): boolean; + // - returns TRUE if fPrivateCopy content changed (then fPrivateCopyHash + // will be updated using crc32c hash if aUpdateHash is set) + function PrivateCopyChanged(aJSON: PUTF8Char; aLen: integer; + aUpdateHash: boolean): boolean; public /// create the result table from a JSON-formated Data message // - the JSON data is parsed and formatted in-place @@ -9051,7 +9060,7 @@ type /// this base class will create a FTS3 table using the Unicode61 Stemming algorithm // - see http://sqlite.org/fts3.html#tokenizer - // - will generate tokenize=unicode64 by convention from the class name + // - will generate tokenize=unicode61 by convention from the class name TSQLRecordFTS3Unicode61 = class(TSQLRecordFTS3); /// a base record, corresponding to a FTS4 table, which is an enhancement to FTS3 @@ -9082,7 +9091,7 @@ type /// this base class will create a FTS4 table using the Unicode61 Stemming algorithm // - see http://sqlite.org/fts3.html#tokenizer - // - will generate tokenize=unicode64 by convention from the class name + // - will generate tokenize=unicode61 by convention from the class name TSQLRecordFTS4Unicode61 = class(TSQLRecordFTS4); /// a base record, corresponding to a FTS5 table, which is an enhancement to FTS4 @@ -9104,7 +9113,7 @@ type /// this base class will create a FTS5 table using the Unicode61 Stemming algorithm // - see https://sqlite.org/fts5.html#tokenizers - // - will generate tokenize=unicode64 by convention from the class name + // - will generate tokenize=unicode61 by convention from the class name TSQLRecordFTS5Unicode61 = class(TSQLRecordFTS5); /// class-reference type (metaclass) of a FTS3/FTS4/FTS5 virtual table @@ -9138,7 +9147,9 @@ type // pointing in opposite directions // - by default, only two TSQLRecord (i.e. INTEGER) fields must be created, // named "Source" and "Dest", the first pointing to the source record (the one - // with a TSQLRecordMany published property) and the second to the destination record + // with a TSQLRecordMany published property) and the second to the destination + // record - note that by design, those source/dest tables are stored as + // pointers, so are limited to 32-bit ID values on 32-bit systems // - you should first create a type inheriting from TSQLRecordMany, which // will define the pivot table, providing optional "through" parameters if needed // ! TSQLDest = class(TSQLRecord); @@ -10559,8 +10570,8 @@ type {$M-} /// class handling interface implementation generated from source - // - this class targets FPC, which does not generate the expected RTTI - see - // http://bugs.freepascal.org/view.php?id=26774 + // - this class targets oldest FPC, which did not generate the expected RTTI - + // see http://bugs.freepascal.org/view.php?id=26774 // - mORMotWrapper.pas will generate a new inherited class, overriding abstract // AddMethodsFromTypeInfo() to define the interface methods TInterfaceFactoryGenerated = class(TInterfaceFactory) @@ -12623,7 +12634,7 @@ type /// a dynamic array of TSQLRest instances TSQLRestDynArray = array of TSQLRest; - /// a dynamic array of TSQLRest instances, owniing the instances + /// a dynamic array of TSQLRest instances, owning the instances TSQLRestObjArray = array of TSQLRest; /// used to store the execution parameters for a TSQLRest instance @@ -12783,8 +12794,7 @@ type fServerTimestampCacheTix: cardinal; fServerTimestampCacheValue: TTimeLogBits; fServices: TServiceContainer; - fPrivateGarbageCollector: TSynObjectList; - fRoutingClass: TSQLRestServerURIContextClass; + fServicesRouting: TSQLRestServerURIContextClass; fBackgroundTimer: TSQLRestBackgroundTimer; fOnDecryptBody, fOnEncryptBody: TNotifyRestBody; fCustomEncryptAES: TAESAbstract; @@ -12792,6 +12802,7 @@ type fCustomEncryptCompress: TAlgoCompress; fCustomEncryptContentPrefix, fCustomEncryptContentPrefixUpper, fCustomEncryptUrlIgnore: RawUTF8; fAcquireExecution: array[TSQLRestServerURIContextCommand] of TSQLRestAcquireExecution; + fPrivateGarbageCollector: TSynObjectList; {$ifdef WITHLOG} fLogClass: TSynLogClass; // =SQLite3Log by default fLogFamily: TSynLogFamily; // =SQLite3Log.Family by default @@ -12870,7 +12881,7 @@ type /// used by all overloaded Add() methods function InternalAdd(Value: TSQLRecord; SendData: boolean; CustomFields: PSQLFieldBits; ForceID, DoNotAutoComputeFields: boolean): TID; virtual; - protected // these abstract methods must be overriden by real database engine + protected // these abstract methods must be overriden by real database engine /// retrieve a list of members as JSON encoded data // - implements REST GET collection // - returns '' on error, or JSON data, even with no result rows @@ -13900,7 +13911,7 @@ type function RetrieveList(const FormatSQLWhere: RawUTF8; const BoundsSQLWhere: array of const; const aCustomFieldsCSV: RawUTF8=''): TObjectList; overload; - {$endif ISDELPHIXE} + {$endif ISDELPHI2010} /// you can call this method in TThread.Execute to ensure that // the thread will be taken into account during process @@ -14160,7 +14171,8 @@ type /// access or initialize the internal IoC resolver, used for interface-based // remote services, and more generaly any Services.Resolve() call // - create and initialize the internal TServiceContainer if no service - // interface has been registered yet // - may be used to inject some dependencies, which are not interface-based + // interface has been registered yet + // - may be used to inject some dependencies, which are not interface-based // remote services, but internal IoC, without the ServiceRegister() // or ServiceDefine() methods - e.g. // ! aRest.ServiceContainer.InjectResolver([TInfraRepoUserFactory.Create(aRest)],true); @@ -14175,7 +14187,7 @@ type // server sides), if the client will rather use JSON/RPC alternative pattern // - NEVER set the abstract TSQLRestServerURIContext class on this property property ServicesRouting: TSQLRestServerURIContextClass - read fRoutingClass write SetRoutingClass; + read fServicesRouting write SetRoutingClass; /// low-level background timer thread associated with this TSQLRest // - contains nil if TimerEnable/AsynchInvoke was never executed // - you may instantiate your own TSQLRestBackgroundTimer instances, if @@ -15835,6 +15847,9 @@ type // unless rsoGetUserRetrieveNoBlobData is defined // - rsoNoInternalState could be state to avoid transmitting the // 'Server-InternalState' header, e.g. if the clients wouldn't need it + // - rsoNoTableURI will disable any /root/tablename URI for safety + // - rsoMethodUnderscoreAsSlashURI will try to decode /root/method/name + // as 'method_name' method TSQLRestServerOption = ( rsoNoAJAXJSON, rsoGetAsJsonNotAsString, @@ -15850,7 +15865,9 @@ type rsoTimestampInfoURIDisable, rsoHttpHeaderCheckDisable, rsoGetUserRetrieveNoBlobData, - rsoNoInternalState); + rsoNoInternalState, + rsoNoTableURI, + rsoMethodUnderscoreAsSlashURI); /// allow to customize the TSQLRestServer process via its Options property TSQLRestServerOptions = set of TSQLRestServerOption; @@ -16010,6 +16027,13 @@ type // - will perform any needed clean-up, and log the event // - this method is not thread-safe: caller should use Sessions.Lock/Unlock procedure SessionDelete(aSessionIndex: integer; Ctxt: TSQLRestServerURIContext); + /// SessionAccess will detect and delete outdated sessions, but you can call + // this method to force checking for deprecated session now + // - may be used e.g. from OnSessionCreate to limit the number of active sessions + // - this method is not thread-safe: caller should use Sessions.Lock/Unlock + // - you can call it often: it will seek for outdated sessions once per second + // - returns the current system Ticks number (at second resolution) + function SessionDeleteDeprecated: cardinal; /// returns TRUE if this table is worth caching (e.g. already in memory) // - this overridden implementation returns FALSE for TSQLRestStorageInMemory function CacheWorthItForTable(aTableIndex: cardinal): boolean; override; @@ -16453,7 +16477,7 @@ type // - return nil on any error, or an EModelException if the class is not in // the database model function StaticDataCreate(aClass: TSQLRecordClass; - const aFileName: TFileName = ''; aBinaryFile: boolean=false; + const aFileName: TFileName=''; aBinaryFile: boolean=false; aServerClass: TSQLRestStorageInMemoryClass=nil): TSQLRestStorage; /// register an external static storage for a given table // - will be added to StaticDataServer[] internal list @@ -16647,11 +16671,12 @@ type // can be used if you need an overridden constructor // - instance implementation pattern will be set by the appropriate parameter // - will return the first of the registered TServiceFactoryServer created - // on success (i.e. the one corresponding to the first item of the aInterfaces - // array), or nil if registration failed (e.g. if any of the supplied interfaces + // on success (i.e. corresponding to aInterfaces[0] - not to the others), + // or nil if registration failed (e.g. if any of the supplied interfaces // is not implemented by the given class) - // - you can use the returned TServiceFactoryServer instance to set the - // expected security parameters associated with this interface + // - you can use the returned TServiceFactoryServer instance to set + // the expected security parameters for aInterfaces[0] - warning: only the + // the first interface options are returned // - the same implementation class can be used to handle several interfaces // (just as Delphi allows to do natively) function ServiceRegister(aImplementationClass: TInterfacedClass; @@ -16667,8 +16692,10 @@ type // on success (i.e. the one corresponding to the first item of the aInterfaces // array), or nil if registration failed (e.g. if any of the supplied interfaces // is not implemented by the given class) - // - you can use the returned TServiceFactoryServer instance to set the - // expected security parameters associated with this interface + // - will return the first of the registered TServiceFactoryServer created + // on success (i.e. corresponding to aInterfaces[0] - not to the others), + // or nil if registration failed (e.g. if any of the supplied interfaces + // is not implemented by the given class) // - the same implementation class can be used to handle several interfaces // (just as Delphi allows to do natively) function ServiceRegister(aSharedImplementation: TInterfacedObject; @@ -16693,6 +16720,10 @@ type /// register a Service class on the server side // - this method expects the interface(s) to have been registered previously: // ! TInterfaceFactory.RegisterInterfaces([TypeInfo(IMyInterface),...]); + // - will return the first of the registered TServiceFactoryServer created + // on success (i.e. corresponding to aInterfaces[0] - not to the others), + // or nil if registration failed (e.g. if any of the supplied interfaces + // is not implemented by the given class) function ServiceDefine(aImplementationClass: TInterfacedClass; const aInterfaces: array of TGUID; aInstanceCreation: TServiceInstanceImplementation=sicSingle; @@ -16701,6 +16732,10 @@ type // - this method expects the interface(s) to have been registered previously: // ! TInterfaceFactory.RegisterInterfaces([TypeInfo(IMyInterface),...]); // - the supplied aSharedImplementation will be owned by this Server instance + // - will return the first of the registered TServiceFactoryServer created + // on success (i.e. corresponding to aInterfaces[0] - not to the others), + // or nil if registration failed (e.g. if any of the supplied interfaces + // is not implemented by the given class) function ServiceDefine(aSharedImplementation: TInterfacedObject; const aInterfaces: array of TGUID; const aContractExpected: RawUTF8=''): TServiceFactoryServer; overload; /// register a remote Service via its interface @@ -17884,7 +17919,7 @@ type // same time, you should better use BATCH process, specifying a positive // AutomaticTransactionPerRow parameter to BatchStart() function TransactionBegin(aTable: TSQLRecordClass; - SessionID: cardinal=CONST_AUTHENTICATION_NOT_USED): boolean; override; + SessionID: cardinal=CONST_AUTHENTICATION_NOT_USED): boolean; override; /// end a transaction (calls REST END Member) // - by default, Client transaction will use here a pseudo session procedure Commit(SessionID: cardinal=CONST_AUTHENTICATION_NOT_USED; @@ -18161,7 +18196,7 @@ type /// method calling the remote Server via a RESTful command // - calls the InternalURI abstract method, which should be overridden with a // local, piped or HTTP/1.1 provider - // - this method will add sign the url with the appropriate digital signature + // - this method will sign the url with the appropriate digital signature // according to the current SessionUser property // - this method will retry the connection in case of authentication failure // (i.e. if the session was closed by the remote server, for any reason - @@ -18229,7 +18264,7 @@ type /// internal method able to emulate a call to TSynLog.Add.Log() // - will compute timestamp and event text, than call the overloaded // ServerRemoteLog() method - function ServerRemoteLog(Level: TSynLogInfo; FormatMsg: PUTF8Char; + function ServerRemoteLog(Level: TSynLogInfo; const FormatMsg: RawUTF8; const Args: array of const): boolean; overload; /// start to send all logs to the server 'RemoteLog' method-based service // - will associate the EchoCustom callback of the running log class to the @@ -18269,7 +18304,8 @@ type // !end; // - you may use the dedicated TransactionBeginRetry() method in case of // potential Client concurrent access - function TransactionBegin(aTable: TSQLRecordClass; SessionID: cardinal=1): boolean; override; + function TransactionBegin(aTable: TSQLRecordClass; + SessionID: cardinal=CONST_AUTHENTICATION_NOT_USED): boolean; override; /// begin a transaction // - implements REST BEGIN collection // - in aClient-Server environment with multiple Clients connected at the @@ -18500,7 +18536,7 @@ type // - is defines as a class procedure, since the underlying TSQLRestClientURI // instance has no impact here: a single WM_* handler is enough for // several TSQLRestClientURI instances - class procedure ServiceNotificationMethodExecute(var Msg : TMessage); + class procedure ServiceNotificationMethodExecute(var Msg: TMessage); {$endif MSWINDOWS} published /// low-level error code, as returned by server @@ -19577,7 +19613,7 @@ procedure SetWeakZero(aObject: TObject; aObjectInterfaceField: PIInterface; {$ifdef ISDELPHIXE} // class helper requires Delphi 2006 or newer but are buggy before XE :( type /// TWeakZeroInterfaceHelper is a class helper that allows you to use - // SetWeakZero() in any class without specifying the Self parameter + // SetWeakZero() in any class without specifying the self parameter TWeakZeroInterfaceHelper = class helper for TObject protected /// Use SetWeak0 to assign an interface to a weak interface field @@ -19861,7 +19897,7 @@ begin {$else} if @self=TypeInfo(QWord) then result := true else - {$ifdef UINICODE}if Kind=tkInt64 then // check MinInt64Value>MaxInt64Value + {$ifdef UNICODE}if Kind=tkInt64 then // check MinInt64Value>MaxInt64Value with PHash128Rec(PAnsiChar(@Name[1])+ord(Name[0]))^ do result := Lo>Hi else {$endif} result := false; @@ -20653,7 +20689,7 @@ constructor TSQLPropInfo.Create(const aName: RawUTF8; aSQLFieldType: TSQLFieldTy aAttributes: TSQLPropInfoAttributes; aFieldWidth, aPropertyIndex: integer); begin if aName='' then - EORMException.CreateUTF8('Void name for %.Create',[self]); + raise EORMException.CreateUTF8('Void name for %.Create',[self]); if aAuxiliaryRTreeField in aAttributes then fName := copy(aName,2,MaxInt) else fName := aName; @@ -23527,7 +23563,7 @@ end; function TSQLPropInfoList.GetItem(aIndex: integer): TSQLPropInfo; begin if cardinal(aIndex)>=Cardinal(fCount) then - EORMException.Create('Invalid TSQLPropInfoList index'); + raise EORMException.Create('Invalid TSQLPropInfoList index'); result := fList[aIndex]; end; @@ -27822,15 +27858,20 @@ end; { TSQLTableJSON } -function TSQLTableJSON.PrivateCopyChanged(aJSON: PUTF8Char; aLen: integer): boolean; +function TSQLTableJSON.PrivateCopyChanged(aJSON: PUTF8Char; aLen: integer; + aUpdateHash: boolean): boolean; var Hash: cardinal; begin - Hash := crc32c(0,pointer(aJSON),aLen); - result := (fPrivateCopyHash=0) or (Hash=0) or (Hash<>fPrivateCopyHash); - if not result then - exit; - FastSetString(fPrivateCopy,aJSON,aLen+16); // +16 for SSE4.2 read-ahead - fPrivateCopyHash := Hash; + if aUpdateHash then begin + Hash := crc32c(0,pointer(aJSON),aLen); + result := (fPrivateCopyHash=0) or (Hash=0) or (Hash<>fPrivateCopyHash); + if not result then + exit; + fPrivateCopyHash := Hash; + end else + result := true; // from Create() for better performance on single use + FastSetString(fPrivateCopy,nil,aLen+16); // +16 for SSE4.2 read-ahead + MoveFast(pointer(aJSON)^,pointer(fPrivateCopy)^,aLen+1); // +1 for trailing #0 end; function TSQLTableJSON.ParseAndConvert(Buffer: PUTF8Char; BufferLen: integer): boolean; @@ -27971,7 +28012,7 @@ function TSQLTableJSON.UpdateFrom(const aJSON: RawUTF8; var Refreshed: boolean; var len: Integer; begin len := length(aJSON); - if PrivateCopyChanged(pointer(aJSON),len) then + if PrivateCopyChanged(pointer(aJSON),len,{updatehash=}true) then if ParseAndConvert(pointer(fPrivateCopy),len) then begin // parse success from new aJSON data -> need some other update? if Assigned(fIDColumn) then begin @@ -28002,7 +28043,7 @@ constructor TSQLTableJSON.Create(const aSQL, aJSON: RawUTF8); var len: integer; begin len := length(aJSON); - FastSetString(fPrivateCopy,pointer(aJSON),len+16); // +16 for SSE4.2 read-ahead + PrivateCopyChanged(pointer(aJSON),len,{updatehash=}false); Create(aSQL,pointer(fPrivateCopy),len); end; @@ -28018,7 +28059,7 @@ constructor TSQLTableJSON.CreateFromTables(const Tables: array of TSQLRecordClas var len: integer; begin len := length(aJSON); - FastSetString(fPrivateCopy,pointer(aJSON),len+16); + PrivateCopyChanged(pointer(aJSON),len,{updatehash=}false); CreateFromTables(Tables,aSQL,pointer(fPrivateCopy),len); end; @@ -28034,7 +28075,7 @@ constructor TSQLTableJSON.CreateWithColumnTypes(const ColumnTypes: array of TSQL var len: integer; begin len := length(aJSON); - FastSetString(fPrivateCopy,pointer(aJSON),len+16); + PrivateCopyChanged(pointer(aJSON),len,{updatehash=}false); CreateWithColumnTypes(ColumnTypes,aSQL,pointer(fPrivateCopy),len); end; @@ -28890,7 +28931,7 @@ begin kD := DestInfo^.PropType^.Kind; case kS of {$ifdef FPC}tkBool,{$endif} tkEnumeration, tkInteger, tkSet, tkChar, tkWChar: -int: if DestInfo=@Self then +int: if DestInfo=@self then SetOrdProp(Dest,GetOrdProp(Source)) else dst: if kD in tkOrdinalTypes then // use Int64 to handle e.g. cardinal DestInfo^.SetInt64Value(Dest,GetInt64Value(Source)); @@ -29726,7 +29767,7 @@ begin {$endif} end; -function TEnumType.GetCaptionStrings(UsedValuesBits: Pointer=nil): string; +function TEnumType.GetCaptionStrings(UsedValuesBits: Pointer): string; var List: TStringList; begin List := TStringList.Create; @@ -30236,7 +30277,7 @@ end; function SQLWhereIsEndClause(const Where: RawUTF8): boolean; begin - result := IdemPCharArray(pointer(Where),['ORDER BY ','GROUP BY ', + result := IdemPCharArray(GotoNextNotSpace(pointer(Where)),['ORDER BY ','GROUP BY ', 'LIMIT ','OFFSET ','LEFT ','RIGHT ','INNER ','OUTER ','JOIN '])>=0; end; @@ -30252,10 +30293,10 @@ end; function SQLFromSelect(const TableName, Select, Where, SimpleFields: RawUTF8): RawUTF8; begin if Select='*' then - // don't send BLOB values to query: retrieve all other fields - result := 'SELECT '+SimpleFields else - result := 'SELECT '+Select; - result := result+' FROM '+TableName+SQLFromWhere(Where); + // don't send BLOB values to query: retrieve simple = all non-blob fields + result := SimpleFields else + result := Select; + result := 'SELECT '+result+' FROM '+TableName+SQLFromWhere(Where); end; function SelectInClause(const PropName: RawUTF8; const Values: array of RawUTF8; @@ -30676,7 +30717,7 @@ begin f := D.Fields.IndexByName(SP.Name); if f>=0 then begin SP.GetValueVar(aRecord,False,tmp,@wasString); - D.Fields.List[f].SetValueVar(Self,tmp,wasString); + D.Fields.List[f].SetValueVar(self,tmp,wasString); end; end; end; @@ -31028,7 +31069,7 @@ begin W.AddString(W.ColNames[n]); // '"'+ColNames[]+'":' inc(n); end; - Props.List[W.Fields[i]].GetJSONValues(Self,W); + Props.List[W.Fields[i]].GetJSONValues(self,W); W.Add(','); end; end; @@ -31041,7 +31082,7 @@ procedure TSQLRecord.AppendAsJsonObject(W: TJSONSerializer; Fields: TSQLFieldBit var i: integer; Props: TSQLPropInfoList; begin - if Self=nil then begin + if self=nil then begin W.AddShort('null'); exit; end; @@ -31055,7 +31096,7 @@ begin W.Add(',','"'); W.AddNoJSONEscape(pointer(Props.List[i].Name),length(Props.List[i].Name)); W.Add('"',':'); - Props.List[i].GetJSONValues(Self,W); + Props.List[i].GetJSONValues(self,W); end; W.Add('}'); end; @@ -31159,9 +31200,9 @@ begin // no simple field to write -> quick return result := '' else if UsingStream<>nil then begin - UsingStream.Seek(0,soFromBeginning); + UsingStream.Seek(0,soBeginning); GetJSONValues(UsingStream,Expand,withID,Occasion,SQLRecordOptions); - FastSetString(result,UsingStream.Memory,UsingStream.Seek(0,soFromCurrent)); + FastSetString(result,UsingStream.Memory,UsingStream.Seek(0,soCurrent)); end else begin J := TRawByteStringStream.Create; try @@ -31232,15 +31273,17 @@ begin end; for i := 0 to fields.Count-1 do result := result+fields.List[i].Name+','; - tokenizer := 'simple'; + if Props.Kind=rFTS5 then // FTS5 knows ascii/porter/unicode61 + tokenizer := 'ascii' else + tokenizer := 'simple'; // FTS3-4 know simple/porter/unicode61 c := self; repeat ToText(c,cname); // TSQLFTSTest = class(TSQLRecordFTS3Porter) if IdemPChar(pointer(cname),'TSQLRECORDFTS') and (cname[14] in ['3','4','5']) then begin if length(cname)>14 then - tokenizer := copy(cname,15,100); // e.g. TSQLRecordFTS3Porter -> 'Porter' - break; + tokenizer := copy(cname,15,100); + break; // e.g. TSQLRecordFTS3Porter -> 'Porter' end; c := GetClassParent(c); until c=TSQLRecord; @@ -31344,7 +31387,7 @@ var i: integer; begin result := false; if (self=nil) or (Reference=nil) or - (PSQLRecordClass(Reference)^<>PSQLRecordClass(Self)^) or (Reference.fID<>fID) then + (PSQLRecordClass(Reference)^<>PSQLRecordClass(self)^) or (Reference.fID<>fID) then exit; with RecordProps do for i := 0 to length(SimpleFields)-1 do @@ -31402,7 +31445,7 @@ begin for i := 0 to length(CopiableFields)-1 do if CopiableFields[i].SQLFieldType<>sftID then CopiableFields[i].SetValue(self,nil,false) else - TSQLRecord(TSQLPropInfoRTTIInstance(CopiableFields[i]).GetInstance(Self)). + TSQLRecord(TSQLPropInfoRTTIInstance(CopiableFields[i]).GetInstance(self)). ClearProperties; // clear nested allocated TSQLRecord end else for i := 0 to length(CopiableFields)-1 do @@ -31431,7 +31474,7 @@ function TSQLRecord.RecordClass: TSQLRecordClass; begin if self=nil then Result := nil else - Result := PSQLRecordClass(Self)^; + Result := PSQLRecordClass(self)^; end; {$else} function TSQLRecord.RecordClass: TSQLRecordClass; @@ -31473,7 +31516,7 @@ function TSQLRecord.RecordReference(Model: TSQLModel): TRecordReference; begin if (self=nil) or (fID<=0) then result := 0 else begin - result := Model.GetTableIndexExisting(PSQLRecordClass(Self)^); + result := Model.GetTableIndexExisting(PSQLRecordClass(self)^); if result>63 then // TRecordReference handle up to 64=1 shl 6 tables result := 0 else inc(result,fID shl 6); @@ -31586,7 +31629,7 @@ var i,n: integer; SQL: RawUTF8; begin Create; - props := aClient.Model.Props[PSQLRecordClass(Self)^]; + props := aClient.Model.Props[PSQLRecordClass(self)^]; if props.props.JoinedFields=nil then raise EORMException.CreateUTF8('No nested TSQLRecord to JOIN in %',[self]); SQL := props.SQL.SelectAllJoined; @@ -31601,7 +31644,7 @@ begin fFill.fTable.OwnerMustFree := true; n := 0; with props.props do begin // follow SQL.SelectAllJoined columns - fFill.AddMapSimpleFields(Self,SimpleFields,n); + fFill.AddMapSimpleFields(self,SimpleFields,n); for i := 1 to length(JoinedFieldsTable)-1 do begin instance := JoinedFieldsTable[i].Create; JoinedFields[i-1].SetInstance(self,instance); @@ -31913,14 +31956,14 @@ end; function TSQLRecord.GetHasBlob: boolean; begin - if Self=nil then + if self=nil then result := false else result := RecordProps.BlobFields<>nil; end; function TSQLRecord.GetSimpleFieldCount: integer; begin - if Self=nil then + if self=nil then result := 0 else result := length(RecordProps.SimpleFields); end; @@ -32076,8 +32119,8 @@ end; {$ifdef FPC_OR_PUREPASCAL} class function TSQLRecord.RecordProps: TSQLRecordProperties; begin - if Self<>nil then begin - result := PPointer(PtrInt(PtrUInt(Self))+vmtAutoTable)^; + if self<>nil then begin + result := PPointer(PtrInt(PtrUInt(self))+vmtAutoTable)^; if result=nil then result := PropsCreate(self); end else @@ -32311,7 +32354,7 @@ begin end; procedure TSQLRecord.ComputeFieldsBeforeWrite(aRest: TSQLRest; aOccasion: TSQLEvent); -var F: integer; +var F: PtrInt; types: TSQLFieldTypes; i64: Int64; p: TSQLPropInfo; @@ -32326,18 +32369,18 @@ begin if integer(types)<>0 then begin i64 := aRest.ServerTimestamp; for F := 0 to Fields.Count-1 do begin - p := Fields.List[f]; + p := Fields.List[F]; if p.SQLFieldType in types then - TSQLPropInfoRTTIInt64(p).fPropInfo.SetInt64Prop(Self,i64); + TSQLPropInfoRTTIInt64(p).fPropInfo.SetInt64Prop(self,i64); end; end; if sftSessionUserID in HasTypeFields then begin i64 := aRest.GetCurrentSessionUserID; if i64<>0 then for F := 0 to Fields.Count-1 do begin - p := Fields.List[f]; + p := Fields.List[F]; if p.SQLFieldType=sftSessionUserID then - TSQLPropInfoRTTIInt64(p).fPropInfo.SetInt64Prop(Self,i64); + TSQLPropInfoRTTIInt64(p).fPropInfo.SetInt64Prop(self,i64); end; end; end; @@ -32818,7 +32861,7 @@ begin Kind := rCustomAutoID else Kind := rSQLite3; Props := TSQLModelRecordProperties.Create(self,Table,Kind); - Props.Props.InternalRegisterModel(Self,aIndex,Props); + Props.Props.InternalRegisterModel(self,aIndex,Props); for t := low(t) to high(t) do if fCustomCollationForAll[t]<>'' then Props.Props.SetCustomCollationForAll(t,fCustomCollationForAll[t]); @@ -33489,14 +33532,14 @@ end; function TSQLModel.ActionName(const Action): string; begin - if (Self=nil) or (fActions=nil) then + if (self=nil) or (fActions=nil) then result := '' else result := TSQLRecord.CaptionNameFromRTTI(fActions^.GetEnumName(byte(Action))); end; function TSQLModel.EventName(const Event): string; begin - if (Self=nil) or (fEvents=nil) then + if (self=nil) or (fEvents=nil) then result := '' else result := TSQLRecord.CaptionNameFromRTTI(fEvents^.GetEnumName(byte(Event))); end; @@ -33832,7 +33875,7 @@ begin if fBatchCount>0 then begin // if something to send for i := 0 to fDeletedCount-1 do if fDeletedRecordRef[i]<>0 then - fRest.Cache.NotifyDeletion(fDeletedRecordRef[i] and 63,fDeletedRecordRef[i] shr 6); + fRest.fCache.NotifyDeletion(fDeletedRecordRef[i] and 63,fDeletedRecordRef[i] shr 6); fBatch.CancelLastComma; fBatch.Add(']'); if fTable<>nil then @@ -33884,7 +33927,7 @@ begin if fCalledWithinRest and (FieldBits-Props.SimpleFieldsBits[soUpdate]=[]) then ForceCacheUpdate := true; // safe to update the cache with supplied values if ForceCacheUpdate then - fRest.Cache.Notify(Value,soUpdate) else + fRest.fCache.Notify(Value,soUpdate) else // may not contain all cached fields -> delete from cache AddID(fDeletedRecordRef,fDeletedCount,RecordReference(tableIndex,ID)); result := fBatchCount; @@ -33940,7 +33983,7 @@ begin fAcquireExecution[cmd] := TSQLRestAcquireExecution.Create; AcquireWriteMode := amLocked; AcquireWriteTimeOut := 5000; // default 5 seconds - fRoutingClass := TSQLRestRoutingREST; + fServicesRouting := TSQLRestRoutingREST; {$ifdef WITHLOG} SetLogClass(SQLite3Log); // by default {$endif} @@ -34411,11 +34454,11 @@ end; procedure TSQLRest.SetRoutingClass(aServicesRouting: TSQLRestServerURIContextClass); begin if self<>nil then - if aServicesRouting<>fRoutingClass then + if aServicesRouting<>fServicesRouting then if (aServicesRouting=nil) or (aServicesRouting=TSQLRestServerURIContext) then raise EServiceException.CreateUTF8('Unexpected %.SetRoutingClass(%)', [self,aServicesRouting]) else - fRoutingClass := aServicesRouting; + fServicesRouting := aServicesRouting; end; function TSQLRest.MultiFieldValue(Table: TSQLRecordClass; @@ -34506,7 +34549,7 @@ begin end; function TSQLRest.OneFieldValues(Table: TSQLRecordClass; const FieldName, - WhereClause: RawUTF8; Strings: TStrings; IDToIndex: PID=nil): Boolean; + WhereClause: RawUTF8; Strings: TStrings; IDToIndex: PID): Boolean; var Row: integer; aID: TID; T: TSQLTableJSON; @@ -34547,7 +34590,7 @@ end; function TSQLRest.OneFieldValues(Table: TSQLRecordClass; const FieldName, WhereClause, Separator: RawUTF8): RawUTF8; -var i, Len, SepLen, L: integer; +var i, Len, SepLen, L: PtrInt; Lens: TIntegerDynArray; T: TSQLTableJSON; P: PUTF8Char; @@ -34561,13 +34604,12 @@ begin // calculate row values CSV needed memory SetLength(Lens,T.fRowCount); SepLen := length(Separator); - Len := 0; + Len := SepLen*(T.fRowCount-1); for i := 1 to T.fRowCount do begin L := StrLen(T.fResults[i]); // ignore fResults[0] i.e. field name - inc(Len,L+SepLen); + inc(Len,L); Lens[i-1] := L; end; - dec(Len,SepLen); SetLength(result,Len); // add row values as CSV P := pointer(result); @@ -34730,11 +34772,15 @@ end; function TSQLRest.Retrieve(const SQLWhere: RawUTF8; Value: TSQLRecord; const aCustomFieldsCSV: RawUTF8): boolean; var T: TSQLTable; + sql: RawUTF8; begin result := false; if (self=nil) or (Value=nil) then exit; - T := MultiFieldValues(PSQLRecordClass(Value)^,aCustomFieldsCSV,SQLWhere); + sql := Trim(SQLWhere); + if not EndWith(sql,' LIMIT 1') then + sql := sql+' LIMIT 1'; // we keep a single record below + T := MultiFieldValues(PSQLRecordClass(Value)^,aCustomFieldsCSV,sql); if T<>nil then try if T.fRowCount>=1 then begin @@ -34807,7 +34853,7 @@ var Rec: TSQLRecord; begin if (self=nil) or (Table=nil) or (W=nil) then exit; - Rec := Table.CreateAndFillPrepare(Self,FormatSQLWhere,BoundsSQLWhere,CustomFieldsCSV); + Rec := Table.CreateAndFillPrepare(self,FormatSQLWhere,BoundsSQLWhere,CustomFieldsCSV); try Rec.AppendFillAsJsonArray(OutputFieldName,W,Rec.fFill.TableMapFields); finally @@ -34938,7 +34984,7 @@ begin SetVariantNull(result); if (self<>nil) and (Table<>nil) then begin with Table.RecordProps do // optimized primary key direct access - if Cache.IsCached(Table) and (length(BoundsSQLWhere)=1) and + if fCache.IsCached(Table) and (length(BoundsSQLWhere)=1) and VarRecToInt64(BoundsSQLWhere[0],Int64(ID)) and FieldBitsFromCSV(CustomFieldsCSV,bits) and (IdemPropNameU('RowID=?',FormatSQLWhere) or @@ -35540,7 +35586,7 @@ begin exit; if BlobStream.Write(pointer(BlobData)^,length(BlobData))<>length(BlobData) then result := false; - BlobStream.Seek(0,soFromBeginning); // rewind + BlobStream.Seek(0,soBeginning); // rewind end; function TSQLRest.UpdateBlob(Table: TSQLRecordClass; aID: TID; @@ -35551,9 +35597,9 @@ begin result := false; if (self=nil) or (BlobData=nil) then exit; - L := BlobData.Seek(0,soFromEnd); + L := BlobData.Seek(0,soEnd); SetLength(Blob,L); - BlobData.Seek(0,soFromBeginning); + BlobData.Seek(0,soBeginning); if BlobData.Read(pointer(Blob)^,L)<>L then exit; result := UpdateBlob(Table,aID,BlobFieldName,Blob); @@ -35622,7 +35668,7 @@ var BlobData: TSQLRawBlob; TableIndex, i: integer; begin result := false; - if (Self=nil) or (Value=nil) or (Value.fID<=0) then + if (self=nil) or (Value=nil) or (Value.fID<=0) then exit; with Value.RecordProps do if BlobFields<>nil then begin @@ -35717,7 +35763,7 @@ begin end; function TSQLRest.MainFieldValue(Table: TSQLRecordClass; ID: TID; - ReturnFirstIfNoUnique: boolean=false): RawUTF8; + ReturnFirstIfNoUnique: boolean): RawUTF8; begin if (self=nil) or (Table=nil) or (ID<=0) then result := '' else begin @@ -35836,7 +35882,7 @@ procedure TSQLRest.EndCurrentThread(Sender: TThread); begin // most will be done e.g. in TSQLRestServer.EndCurrentThread {$ifdef WITHLOG} fLogFamily.OnThreadEnded(Sender); - {$endif} + {$endif WITHLOG} end; procedure TSQLRest.WriteLock; @@ -35917,7 +35963,7 @@ begin end; end; -{$endif} +{$endif ISDELPHI2010} { TSQLRestCacheEntry } @@ -36723,7 +36769,7 @@ end; function TSQLRestClientURI.ServerInternalState: cardinal; begin - if (Self=nil) or (Model=nil) then // avoid GPF + if (self=nil) or (Model=nil) then // avoid GPF result := cardinal(-1) else result := URI(Model.Root,'STATE').Hi; end; @@ -36731,7 +36777,7 @@ end; function TSQLRestClientURI.ServerCacheFlush(aTable: TSQLRecordClass; aID: TID): boolean; var aResp: RawUTF8; begin - if (Self=nil) or (Model=nil) then // avoid GPF + if (self=nil) or (Model=nil) then // avoid GPF result := false else result := CallBackGet('CacheFlush',[],aResp,aTable,aID) in [HTTP_SUCCESS,HTTP_NOCONTENT]; end; @@ -36778,7 +36824,7 @@ begin fServiceNotificationMethodViaMessages.Msg := Msg; end; -class procedure TSQLRestClientURI.ServiceNotificationMethodExecute(var Msg : TMessage); +class procedure TSQLRestClientURI.ServiceNotificationMethodExecute(var Msg: TMessage); var exec: TSQLRestClientURIServiceNotification; begin exec := pointer(Msg.LParam); @@ -36995,7 +37041,7 @@ end; {$endif LVCL} function TSQLRestClientURI.ServerRemoteLog(Level: TSynLogInfo; - FormatMsg: PUTF8Char; const Args: array of const): boolean; + const FormatMsg: RawUTF8; const Args: array of const): boolean; begin result := ServerRemoteLog(nil,Level, FormatUTF8('%00% %',[NowToString(false),LOG_LEVEL_TEXT[Level], @@ -37035,8 +37081,8 @@ begin fRemoteLogClass := nil; end; -function TSQLRestClientURI.UpdateFromServer(const Data: array of TObject; out Refreshed: boolean; - PCurrentRow: PInteger): boolean; +function TSQLRestClientURI.UpdateFromServer(const Data: array of TObject; + out Refreshed: boolean; PCurrentRow: PInteger): boolean; // notes about refresh mechanism: // - if server doesn't implement InternalState, its value is 0 -> always refresh // - if any TSQLTableJSON or TSQLRecord belongs to a TSQLRestStorage, @@ -37216,7 +37262,7 @@ begin fSafe := TAutoLockerDebug.Create(fLogClass,aModel.Root); // more verbose {$else} fSafe := TAutoLocker.Create; - {$endif} + {$endif USELOCKERDEBUG} end; destructor TSQLRestClientURI.Destroy; @@ -37421,17 +37467,16 @@ begin exit; InternalURI(Call^); if ((Sender=nil) or OnIdleBackgroundThreadActive) and - not(isDestroying in fInternalState) then + not(isDestroying in fInternalState) then begin if (Call^.OutStatus=HTTP_NOTIMPLEMENTED) and (isOpened in fInternalState) then begin InternalClose; // force recreate connection Exclude(fInternalState,isOpened); - if ((Sender=nil) or OnIdleBackgroundThreadActive) then begin + if ((Sender=nil) or OnIdleBackgroundThreadActive) then InternalURI(Call^); // try request again - if Call^.OutStatus<>HTTP_NOTIMPLEMENTED then - Include(fInternalState,isOpened); - end; - end else + end; + if Call^.OutStatus<>HTTP_NOTIMPLEMENTED then Include(fInternalState,isOpened); + end; end; function TSQLRestClientURI.GetOnIdleBackgroundThreadActive: boolean; @@ -37472,7 +37517,7 @@ var retry: Integer; begin Call.Url := url; // reset to allow proper re-sign if fSessionAuthentication<>nil then - fSessionAuthentication.ClientSessionSign(Self,Call); + fSessionAuthentication.ClientSessionSign(self,Call); Call.Method := method; if SendData<>nil then Call.InBody := SendData^; @@ -37482,7 +37527,7 @@ var retry: Integer; if Assigned(fOnIdle) then begin if fBackgroundThread=nil then fBackgroundThread := TSynBackgroundThreadEvent.Create(OnBackgroundProcess, - OnIdle,FormatUTF8('% % background',[Self,Model.Root])); + OnIdle,FormatUTF8('% % background',[self,Model.Root])); if not fBackgroundThread.RunAndWait(@Call) then Call.OutStatus := HTTP_UNAVAILABLE; end else @@ -37500,7 +37545,7 @@ var retry: Integer; end; begin - if Self=nil then begin + if self=nil then begin Int64(result) := HTTP_UNAVAILABLE; SetLastException(nil,HTTP_UNAVAILABLE); exit; @@ -37549,7 +37594,7 @@ begin InternalLog('% % returned % (%) with message %', [method,url,Call.OutStatus,StatusMsg,fLastErrorMessage],sllError); if Assigned(fOnFailed) then - fOnFailed(Self,nil,@Call); + fOnFailed(self,nil,@Call); end; except on E: Exception do begin @@ -38186,7 +38231,7 @@ begin aMethodName := trim(aMethodName); if aMethodName='' then raise EServiceException.CreateUTF8('%.ServiceMethodRegister('''')',[self]); - if Model.GetTableIndex(aMethodName)>=0 then + if not(rsoNoTableURI in fOptions) and (Model.GetTableIndex(aMethodName)>=0) then raise EServiceException.CreateUTF8('Published method name %.% '+ 'conflicts with a Table in the Model!',[self,aMethodName]); with PSQLRestServerMethod(fPublishedMethods.AddUniqueName(aMethodName, @@ -38295,16 +38340,16 @@ begin end; end; -procedure TSQLRestServer.CreateMissingTables(user_version: cardinal=0; - Options: TSQLInitializeTableOptions=[]); +procedure TSQLRestServer.CreateMissingTables(user_version: cardinal; + Options: TSQLInitializeTableOptions); begin fCreateMissingTablesOptions := Options; end; procedure TSQLRestServer.InitializeTables(Options: TSQLInitializeTableOptions); -var t: integer; +var t: PtrInt; begin - if (Self<>nil) and (Model<>nil) then + if (self<>nil) and (Model<>nil) then for t := 0 to Model.TablesMax do if not TableHasRows(Model.Tables[t]) then Model.Tables[t].InitializeTable(self,'',Options); @@ -38317,7 +38362,7 @@ begin end; destructor TSQLRestServer.Destroy; -var i: integer; +var i: PtrInt; begin Shutdown; if GlobalURIRequestServer=self then begin @@ -38330,7 +38375,7 @@ begin CloseServerMessage; {$endif} AsynchBatchStop(nil); // may use fStaticData[] - FreeAndNil(fBackgroundTimer); + FreeAndNil(fBackgroundTimer); // do it ASAP fRecordVersionSlaveCallbacks := nil; // should be done before fServices.Free for i := 0 to high(fStaticVirtualTable) do if fStaticVirtualTable[i]<>nil then begin @@ -39176,7 +39221,7 @@ end; function TSQLRestServer.CreateSQLIndex(Table: TSQLRecordClass; const FieldNames: array of RawUTF8; Unique: boolean): boolean; -var i: integer; +var i: PtrInt; begin result := true; for i := 0 to high(FieldNames) do @@ -39199,7 +39244,7 @@ end; function TSQLRestServer.AuthenticationRegister( aMethod: TSQLRestServerAuthenticationClass): TSQLRestServerAuthentication; -var i: integer; +var i: PtrInt; begin result := nil; if self=nil then @@ -39229,14 +39274,14 @@ end; procedure TSQLRestServer.AuthenticationRegister( const aMethods: array of TSQLRestServerAuthenticationClass); -var i: integer; +var i: PtrInt; begin for i := 0 to high(aMethods) do AuthenticationRegister(aMethods[i]); end; procedure TSQLRestServer.AuthenticationUnregister(aMethod: TSQLRestServerAuthenticationClass); -var i: integer; +var i: PtrInt; begin if (self=nil) or (fSessionAuthentication=nil) then exit; @@ -39255,7 +39300,7 @@ end; procedure TSQLRestServer.AuthenticationUnregister( const aMethods: array of TSQLRestServerAuthenticationClass); -var i: integer; +var i: PtrInt; begin for i := 0 to high(aMethods) do AuthenticationUnregister(aMethods[i]); @@ -39275,7 +39320,7 @@ begin end; function TSQLRestServer.ServiceMethodByPassAuthentication(const aMethodName: RawUTF8): integer; -var i: integer; +var i: PtrInt; begin result := -1; if self=nil then @@ -39479,7 +39524,9 @@ end; procedure TSQLRestServerURIContext.InternalSetTableFromTableName(TableName: PUTF8Char); begin TableEngine := Server; - InternalSetTableFromTableIndex(Server.Model.GetTableIndexPtr(TableName)); + if rsoNoTableURI in Server.Options then + TableIndex := -1 else + InternalSetTableFromTableIndex(Server.Model.GetTableIndexPtr(TableName)); if TableIndex<0 then exit; Static := Server.GetStaticTableIndex(TableIndex,StaticKind); @@ -39544,6 +39591,13 @@ begin // expects 'ModelRoot[/TableName[/TableID][/URIBlobFieldName]][?param=...] TableID := GetCardinalDef(pointer(PtrInt(URIBlobFieldName)+j),cardinal(-1)); SetLength(URIBlobFieldName,j-1); end; + end else if rsoMethodUnderscoreAsSlashURI in Server.Options then begin + URIUnderscoreAsSlash := URI; + i := slash; // set e.g. 'Method_Name' from 'ModelRoot/Method/Name' URI + repeat + URIUnderscoreAsSlash[i] := '_'; + i := PosEx('/',URI,i+1); + until i=0; end; SetLength(URI,slash-1); end else @@ -39562,8 +39616,12 @@ end; procedure TSQLRestServerURIContext.URIDecodeSOAByMethod; begin if Table=nil then + begin // check URI as 'ModelRoot/MethodName' - MethodIndex := Server.fPublishedMethods.FindHashed(URI) else + MethodIndex := Server.fPublishedMethods.FindHashed(URI); + if (MethodIndex<0) and (URIUnderscoreAsSlash<>'') then + MethodIndex := Server.fPublishedMethods.FindHashed(URIUnderscoreAsSlash); + end else if URIBlobFieldName<>'' then // check URI as 'ModelRoot/TableName[/TableID]/MethodName' MethodIndex := Server.fPublishedMethods.FindHashed(URIBlobFieldName) else @@ -39777,6 +39835,7 @@ procedure TSQLRestServerURIContext.ExecuteSOAByMethod; var timeStart,timeEnd: Int64; sessionstat: TSynMonitorInputOutput; begin + if MethodIndex >= 0 then with Server.fPublishedMethod[MethodIndex] do begin if mlMethods in Server.StatLevels then begin {$ifdef LINUX}QueryPerformanceMicroSeconds{$else}QueryPerformanceCounter{$endif}(timeStart); @@ -40285,7 +40344,7 @@ var OK: boolean; Blob: PPropInfo; SQLSelect, SQLWhere, SQLSort, SQLDir: RawUTF8; begin - if MethodIndex=Server.fPublishedMethodBatchIndex then begin + if (MethodIndex>=0) and (MethodIndex=Server.fPublishedMethodBatchIndex) then begin ExecuteSOAByMethod; // run the BATCH process in execORMWrite context exit; end; @@ -40881,7 +40940,7 @@ begin (Length(Result)>64) then begin FindNameValue(Call.InHead,'IF-NONE-MATCH: ',clientHash); if ServerHash='' then - ServerHash := '"'+crc32cUTF8ToHex(Result)+'"'; + ServerHash := crc32cUTF8ToHex(Result); ServerHash := '"'+ServerHash+'"'; if clientHash<>ServerHash then Call.OutHead := Call.OutHead+#13#10'ETag: '+ServerHash else begin @@ -41845,10 +41904,10 @@ begin case Ctxt.Method of mGET: begin if Ctxt.Table=nil then - Cache.Flush else + fCache.Flush else if Ctxt.TableID=0 then - Cache.Flush(Ctxt.Table) else - Cache.SetCache(Ctxt.Table,Ctxt.TableID); + fCache.Flush(Ctxt.Table) else + fCache.SetCache(Ctxt.Table,Ctxt.TableID); Ctxt.Success; end; mPOST: @@ -41966,7 +42025,7 @@ begin end; procedure TSQLRestServer.Auth(Ctxt: TSQLRestServerURIContext); -var i: integer; +var i: PtrInt; begin if fSessionAuthentication=nil then exit; @@ -42016,7 +42075,7 @@ end; procedure TSQLRestServer.SessionDelete(aSessionIndex: integer; Ctxt: TSQLRestServerURIContext); var sess: TAuthSession; -begin +begin // caller made fSessions.Safe.Lock if (self<>nil) and (cardinal(aSessionIndex)nil then @@ -42033,38 +42092,48 @@ begin end; end; +function TSQLRestServer.SessionDeleteDeprecated: cardinal; +var i: PtrInt; +begin // caller made fSessions.Safe.Lock + result := GetTickCount64 shr 10; + if (self<>nil) and (fSessions<>nil) then begin + if result<>fSessionsDeprecatedTix then begin + fSessionsDeprecatedTix := result; // check sessions every second + for i := fSessions.Count-1 downto 0 do + if result>TAuthSession(fSessions.List[i]).TimeOutTix then + SessionDelete(i,nil); + end; + end; +end; + function TSQLRestServer.SessionAccess(Ctxt: TSQLRestServerURIContext): TAuthSession; var i: integer; tix, session: cardinal; sessions: ^TAuthSession; -begin // caller of RetrieveSession() made fSessions.Safe.Lock +begin // caller made fSessions.Safe.Lock if (self<>nil) and (fSessions<>nil) then begin - tix := GetTickCount64 shr 10; - if tix<>fSessionsDeprecatedTix then begin - fSessionsDeprecatedTix := tix; // check deprecated sessions every second - for i := fSessions.Count-1 downto 0 do - if tix>TAuthSession(fSessions.List[i]).TimeOutTix then - SessionDelete(i,nil); - end; + // check deprecated sessions every second + tix := SessionDeleteDeprecated; // retrieve session from its ID sessions := pointer(fSessions.List); session := Ctxt.Session; - for i := 1 to fSessions.Count do - if sessions^.IDCardinal=session then begin - result := sessions^; - result.fTimeOutTix := tix+result.TimeoutShr10; - Ctxt.fSession := result; // for TSQLRestServer internal use - // make local copy of TAuthSession information - Ctxt.SessionUser := result.User.fID; - Ctxt.SessionGroup := result.User.GroupRights.fID; - Ctxt.SessionUserName := result.User.LogonName; - if (result.RemoteIP<>'') and (Ctxt.fRemoteIP='') then - Ctxt.fRemoteIP := result.RemoteIP; - Ctxt.fSessionAccessRights := result.fAccessRights; - Ctxt.Call^.RestAccessRights := @Ctxt.fSessionAccessRights; - exit; - end else - inc(sessions); + if session>CONST_AUTHENTICATION_NOT_USED then + for i := 1 to fSessions.Count do + if sessions^.IDCardinal=session then begin + result := sessions^; + result.fTimeOutTix := tix+result.TimeoutShr10; + Ctxt.fSession := result; // for TSQLRestServer internal use + // make local copy of TAuthSession information + Ctxt.SessionUser := result.User.fID; + Ctxt.SessionGroup := result.User.GroupRights.fID; + Ctxt.SessionUserName := result.User.LogonName; + if (result.RemoteIP<>'') and (Ctxt.fRemoteIP='') then + Ctxt.fRemoteIP := result.RemoteIP; + Ctxt.fSessionAccessRights := result.fAccessRights; + Ctxt.Call^.RestAccessRights := @Ctxt.fSessionAccessRights; + exit; + end else + inc(sessions); end; result := nil; end; @@ -42305,7 +42374,7 @@ class procedure TSQLRecordHistory.InitializeTable(Server: TSQLRestServer; begin inherited InitializeTable(Server,FieldName,Options); if FieldName='' then - Server.CreateSQLMultiIndex(Self,['ModifiedRecord','Event'],false); + Server.CreateSQLMultiIndex(self,['ModifiedRecord','Event'],false); end; destructor TSQLRecordHistory.Destroy; @@ -42857,7 +42926,7 @@ begin if (Model.TableProps[TableModelIndex].Props.RecordVersionField=nil) or not result then exit; - Batch := TSQLRestBatch.Create(Self,Model.Tables[TableModelIndex],1000); + Batch := TSQLRestBatch.Create(self,fSQLRecordVersionDeleteTable,1000); try for i := 0 to high(IDs) do InternalRecordVersionDelete(TableModelIndex,IDs[i],Batch); @@ -42945,7 +43014,7 @@ var EndOfObject: AnsiChar; for i := 0 to high(RunTableTransactions) do if RunTableTransactions[i]<>nil then begin RunTableTransactions[i].Commit(CONST_AUTHENTICATION_NOT_USED,true); - if RunTableTransactions[i]=Self then + if RunTableTransactions[i]=self then RunMainTransaction := false; RunTableTransactions[i] := nil; // to acquire and begin a new transaction end; @@ -44775,7 +44844,7 @@ function TSQLRestStorageInMemory.FindWhereEqual(const WhereFieldName, WhereValue var WhereFieldIndex: integer; begin result := 0; - if (Self=nil) or not Assigned(OnFind) then + if (self=nil) or not Assigned(OnFind) then exit; if IsRowID(pointer(WhereFieldName)) then WhereFieldIndex := 0 else begin @@ -45211,7 +45280,7 @@ begin // now fValue[] contains the just loaded data cf := 'ID' else for f := 0 to high(fUnique) do if f in fIsUnique then begin - c := fUnique[f].Hasher.ReHash({forced=}true,{grow=}false); + c := fUnique[f].Hasher.ReHash({forced=}true); if c>0 then begin cf := fUnique[f].PropInfo.Name; break; @@ -45929,6 +45998,7 @@ begin n := FindWhereEqual(WhereField,FieldValue,DoAddToListEvent,match,0,0); if n=0 then exit; + result := true; SetLength(ResultID,n); for i := 0 to n-1 do ResultID[i] := TSQLRecord(match.List[i]).fID; @@ -46752,7 +46822,7 @@ begin for t := 0 to fStaticDataCount-1 do with TSQLRestStorageInMemory(fStaticData[t]) do if Count=0 then // emulates TSQLRestServerDB.CreateMissingTables - StoredClass.InitializeTable(Self,'',Options); + StoredClass.InitializeTable(self,'',Options); end; destructor TSQLRestServerFullMemory.Destroy; @@ -47303,7 +47373,7 @@ begin FreeAndNil(fLogTableStorage); exit; end; - fLogTableStorage.Seek(0,soFromBeginning); + fLogTableStorage.Seek(0,soBeginning); fLogTableStorage.WriteBuffer(Pointer(aJSON)^,L-2); end; @@ -49379,8 +49449,8 @@ begin inherited Create; with RecordProps do if (fRecordManySourceProp<>nil) and (fRecordManyDestProp<>nil) then begin - fSourceID := fRecordManySourceProp.GetFieldAddr(Self); - fDestID := fRecordManyDestProp.GetFieldAddr(Self); + fSourceID := fRecordManySourceProp.GetFieldAddr(self); + fDestID := fRecordManyDestProp.GetFieldAddr(self); end; end; @@ -49464,7 +49534,7 @@ begin end; begin result := nil; - if (Self=nil) or (fSourceID=nil) or (fDestID=nil) or (aClient=nil) then + if (self=nil) or (fSourceID=nil) or (fDestID=nil) or (aClient=nil) then exit; if aSourceID=0 then if fSourceID<>nil then @@ -49499,7 +49569,7 @@ begin SQL := 'SELECT % FROM %,% WHERE %.Source=:(%): AND %.Dest=%.RowID AND %' else // statement is not globaly inlined -> no caching of prepared statement SQL := 'SELECT % FROM %,% WHERE %.Source=% AND %.Dest=%.RowID AND %'; - result := aClient.ExecuteList([PSQLRecordClass(Self)^, + result := aClient.ExecuteList([PSQLRecordClass(self)^, TSQLRecordClass(SelfProps.Props.fRecordManyDestProp.ObjectClass)], FormatUTF8(SQL, [Select, DestProps.Props.SQLTableName,SelfProps.Props.SQLTableName, @@ -49542,7 +49612,7 @@ begin if (self=nil) or (aClient=nil) or (aSourceID=0) or (aDestID=0) then result := false else // invalid parameters result := aClient.Retrieve(FormatUTF8('Source=:(%): AND Dest=:(%):', - [aSourceID,aDestID]),Self); + [aSourceID,aDestID]),self); end; function TSQLRecordMany.ManySelect(aClient: TSQLRest; aDestID: TID): boolean; @@ -50239,7 +50309,7 @@ end; function TSQLRecordProperties.CreateJSONWriter(JSON: TStream; Expand, withID: boolean; const aFields: TSQLFieldIndexDynArray; KnownRowsCount,aBufSize: integer): TJSONSerializer; begin - if (self=nil) or ((Fields=nil) and not withID) then // no data + if (self=nil) or ((Fields.Count=0) and not withID) then // no data result := nil else begin result := TJSONSerializer.Create(JSON,Expand,withID,aFields,aBufSize); SetJSONWriterColumnNames(result,KnownRowsCount); @@ -50893,7 +50963,7 @@ var Added: boolean; AddDateTime(D64); if woDateTimeWithZSuffix in Options then if frac(D64)=0 then // FireFox can't decode short form "2017-01-01Z" - AddShort('T00:00Z') else + AddShort('T00:00:00Z') else // the same pattern for date and dateTime Add('Z'); Add('"'); end; @@ -50970,11 +51040,12 @@ var Added: boolean; {$endif NOVARIANTS} tkClass: begin Obj := P^.GetObjProp(Value); - if not(woDontStore0 in Options) or not IsObjectDefaultOrVoid(Obj) then + if Obj<>nil then if PropIsIDTypeCastedField(P,IsObj,Value) then begin HR(P); Add(PtrInt(Obj)); // not true instances, but ID - end else if Obj<>nil then begin + end else + if not(woDontStore0 in Options) or not IsObjectDefaultOrVoid(Obj) then begin HR(P); // TPersistent or any class defined with $M+ WriteObject(Obj,Options); end; @@ -51239,7 +51310,7 @@ end; function TSQLVirtualTable.Prepare(var Prepared: TSQLVirtualTablePrepared): boolean; begin - result := Self<>nil; + result := self<>nil; if result then if (vtWhereIDPrepared in fModule.Features) and Prepared.IsWhereIDEquals(true) then @@ -51309,7 +51380,7 @@ end; function TSQLVirtualTable.Structure: RawUTF8; begin result := ''; - if Self<>nil then + if self<>nil then if (Static<>nil) then // e.g. for TSQLVirtualTableJSON or TSQLVirtualTableExternal Result := StructureFromClass(StaticTable,TableName) else @@ -52677,7 +52748,7 @@ const function TSQLRestServerAuthenticationSSPI.Auth( Ctxt: TSQLRestServerURIContext): boolean; -var i: integer; +var i: PtrInt; UserName, InDataEnc: RawUTF8; ticks,ConnectionID: Int64; BrowserAuth: Boolean; @@ -52797,8 +52868,10 @@ begin // 1st call will return data, 2nd call SessionKey result := ClientGetSessionKey(Sender,User,['UserName','','data',BinToBase64(OutData)]); until Sender.fSessionData=''; - if result<>'' then - result := SecDecrypt(SecCtx,Base64ToBin(result)); + if result<>'' then begin + OutData := Base64ToBin(result); + result := SecDecrypt(SecCtx,{$ifdef MSWINDOWS}TSSPIBuffer{$endif}(OutData)); + end; finally FreeSecContext(SecCtx); end; @@ -52813,7 +52886,7 @@ begin end; destructor TSQLRestServerAuthenticationSSPI.Destroy; -var i: integer; +var i: PtrInt; begin for i := 0 to High(fSSPIAuthContexts) do FreeSecContext(fSSPIAuthContexts[i]); @@ -52886,7 +52959,7 @@ begin exit; CheckInterface(aInterfaces); for i := 0 to high(aInterfaces) do begin - F := ServicesFactoryClients.Create( + F := fServicesFactoryClients.Create( Rest,aInterfaces[i],aInstanceCreation,aContractExpected); AddServiceInternal(F); aContractExpected := ''; // supplied contract is only for the 1st interface @@ -52899,7 +52972,7 @@ function TServiceContainer.AddInterface(aInterface: PTypeInfo; const aContractExpected: RawUTF8): TServiceFactoryClient; begin CheckInterface([aInterface]); - result := ServicesFactoryClients.Create(Rest,aInterface,aInstanceCreation,aContractExpected); + result := fServicesFactoryClients.Create(Rest,aInterface,aInstanceCreation,aContractExpected); AddServiceInternal(result); end; @@ -53282,7 +53355,7 @@ const // always aligned to 8 bytes boundaries for 64-bit ARGS_IN_STACK_SIZE: array[TServiceMethodValueType] of Cardinal = ( 0, PTRSIZ,PTRSIZ, PTRSIZ,PTRSIZ,PTRSIZ, PTRSIZ, 8, 8, 8, - // None, Self, Boolean, Enum, Set, Integer, Cardinal, Int64, Double, DateTime, + // None, self, Boolean, Enum, Set, Integer, Cardinal, Int64, Double, DateTime, 8, PTRSIZ, PTRSIZ, PTRSIZ, PTRSIZ, 0, PTRSIZ, // Currency, RawUTF8, String, RawByteString, WideString, Binary, Record, {$ifndef NOVARIANTS}PTRSIZ,{$endif} // Variant @@ -54750,7 +54823,7 @@ begin {$endif KYLIX3} if fStub=MAP_FAILED then {$endif MSWINDOWS} - raise EServiceException.CreateUTF8('%.Create: OS memory allocation failed',[Self]); + raise EServiceException.CreateUTF8('%.Create: OS memory allocation failed',[self]); end; destructor TFakeStubBuffer.Destroy; @@ -55354,6 +55427,7 @@ begin batch := fBackgroundBatch[b]; if batch.Count=0 then continue; + data := ''; batch.Safe.Lock; try table := batch.Table; @@ -55643,8 +55717,7 @@ type end; TInterfacedObjectMultiDestDynArray = array of TInterfacedObjectMultiDest; TInterfacedObjectMulti = class; - TInterfacedObjectMultiList = class(TInterfacedObjectLocked, - IMultiCallbackRedirect) + TInterfacedObjectMultiList = class(TInterfacedObjectLocked, IMultiCallbackRedirect) protected fDest: TInterfacedObjectMultiDestDynArray; fDestCount: integer; @@ -57291,7 +57364,7 @@ end; function TServiceFactory.GetInterfaceTypeInfo: PTypeInfo; begin - if (Self<>nil) and (fInterface<>nil) then + if (self<>nil) and (fInterface<>nil) then result := fInterface.fInterfaceTypeInfo else result := nil; end; @@ -58067,7 +58140,7 @@ begin fImplementationClass.GetInterfaceEntry(fInterface.fInterfaceIID); if fImplementationClassInterfaceEntry=nil then raise EServiceException.CreateUTF8('%.Create: % does not implement I%', - [self,fImplementationClass,fInterfaceURI]) else + [self,fImplementationClass,fInterfaceURI]); end; if (fInterface.MethodIndexCallbackReleased>=0) and (InstanceCreation<>sicShared) then @@ -58371,17 +58444,16 @@ begin raise EInterfaceFactoryException.CreateUTF8( 'ickFromInjectedResolver: TryResolveInternal(%)=false',[fInterface.fInterfaceName]); result := TInterfacedObject(ObjectFromInterface(IInterface(dummyObj))); - if AndIncreaseRefCount then // RefCount=1 after TryResolveInternal() - AndIncreaseRefCount := false else - dec(TInterfacedObjectHooked(result).FRefCount); + dec(TInterfacedObjectHooked(result).FRefCount); // RefCount=1 after TryResolveInternal() end; else result := fImplementationClass.Create; end; + inc(TInterfacedObjectHooked(result).FRefCount); // >0 to call Support() in event if Assigned(TSQLRestServer(Rest).OnServiceCreateInstance) then TSQLRestServer(Rest).OnServiceCreateInstance(self,result); - if AndIncreaseRefCount then - IInterface(result)._AddRef; // allow passing self to sub-methods + if not AndIncreaseRefCount then + dec(TInterfacedObjectHooked(result).FRefCount); end; procedure TServiceFactoryServer.OnLogRestExecuteMethod(Sender: TServiceMethodExecute; @@ -58625,7 +58697,8 @@ begin if not execres then begin if err<>'' then Ctxt.Error('%',[err],HTTP_NOTACCEPTABLE) else - Error('execution failed (probably due to bad input parameters)',HTTP_NOTACCEPTABLE); + Error('execution failed (probably due to bad input parameters: ' + + 'e.g. did you initialize your input record(s)?)',HTTP_NOTACCEPTABLE); exit; // wrong request end; Ctxt.Call.OutHead := exec.ServiceCustomAnswerHead; @@ -60389,7 +60462,7 @@ begin end; end; // prepare the low-level call context for the asm stub - //Pass the Self (also named $this) + //Pass the self (also named $this) call.ParamRegs[PARAMREG_FIRST] := PtrInt(Instances[i]); call.method := PPtrIntArray(PPointer(Instances[i])^)^[ExecutionMethodIndex]; if ArgsResultIndex>=0 then @@ -60572,7 +60645,7 @@ begin inc(Par); '{': begin // retrieve parameters values from JSON object repeat inc(Par) until not(Par^ in [#1..' ']); - if Par<>'}' then begin + if Par^<>'}' then begin ParObjValuesUsed := true; FillCharFast(ParObjValues,(ArgsInLast+1)*SizeOf(pointer),0); // := nil a1 := ArgsInFirst; @@ -60685,7 +60758,7 @@ class procedure TSQLRecordServiceLog.InitializeTable( begin inherited; if FieldName='' then - Server.CreateSQLMultiIndex(Self,['Method','MicroSec'],false); + Server.CreateSQLMultiIndex(self,['Method','MicroSec'],false); end; @@ -60697,7 +60770,7 @@ class procedure TSQLRecordServiceNotifications.InitializeTable( begin inherited; if (FieldName='') or (FieldName='Sent') then - Server.CreateSQLMultiIndex(Self,['Sent'],false); + Server.CreateSQLMultiIndex(self,['Sent'],false); end; class function TSQLRecordServiceNotifications.LastEventsAsObjects(Rest: TSQLRest; @@ -61169,7 +61242,7 @@ var baseuri,uri,sent,resp,clientDrivenID,head,error,ct: RawUTF8; end; begin result := false; - if Self=nil then + if self=nil then exit; if fClient=nil then fClient := fRest as TSQLRestClientURI; @@ -61350,7 +61423,7 @@ function TServiceFactoryClient.Get(out Obj): Boolean; var O: TInterfacedObjectFake; begin result := false; - if Self=nil then + if self=nil then exit; case fInstanceCreation of sicShared, sicPerSession, sicPerUser, sicPerGroup, sicPerThread: diff --git a/contrib/mORMot/SQLite3/mORMotBigTable.pas b/contrib/mORMot/SQLite3/mORMotBigTable.pas index ad9b356..a60b5fb 100644 --- a/contrib/mORMot/SQLite3/mORMotBigTable.pas +++ b/contrib/mORMot/SQLite3/mORMotBigTable.pas @@ -6,7 +6,7 @@ unit mORMotBigTable; { This file is part of Synopse mORMot framework. - Synopse mORMot framework. Copyright (C) 2020 Arnaud Bouchez + Synopse mORMot framework. Copyright (C) 2022 Arnaud Bouchez Synopse Informatique - https://synopse.info *** BEGIN LICENSE BLOCK ***** @@ -25,7 +25,7 @@ unit mORMotBigTable; The Initial Developer of the Original Code is Arnaud Bouchez. - Portions created by the Initial Developer are Copyright (C) 2020 + Portions created by the Initial Developer are Copyright (C) 2022 the Initial Developer. All Rights Reserved. Contributor(s): diff --git a/contrib/mORMot/SQLite3/mORMotDB.pas b/contrib/mORMot/SQLite3/mORMotDB.pas index 55302c5..df24b20 100644 --- a/contrib/mORMot/SQLite3/mORMotDB.pas +++ b/contrib/mORMot/SQLite3/mORMotDB.pas @@ -6,7 +6,7 @@ unit mORMotDB; { This file is part of Synopse mORMot framework. - Synopse mORMot framework. Copyright (C) 2020 Arnaud Bouchez + Synopse mORMot framework. Copyright (C) 2022 Arnaud Bouchez Synopse Informatique - https://synopse.info *** BEGIN LICENSE BLOCK ***** @@ -25,7 +25,7 @@ unit mORMotDB; The Initial Developer of the Original Code is Arnaud Bouchez. - Portions created by the Initial Developer are Copyright (C) 2020 + Portions created by the Initial Developer are Copyright (C) 2022 the Initial Developer. All Rights Reserved. Contributor(s): @@ -79,7 +79,8 @@ type // - Handled=FALSE would let the default ID computation take place // - note that execution of this method would be protected by a mutex, so // it would be thread-safe - TOnEngineAddComputeID = function(Sender: TSQLRestStorageExternal; var Handled: Boolean): TID of object; + TOnEngineAddComputeID = function(Sender: TSQLRestStorageExternal; + var Handled: Boolean): TID of object; /// REST server with direct access to a SynDB-based external database // - handle all REST commands, using the external SQL database connection, @@ -119,21 +120,25 @@ type fBatchIDs: TIDDynArray; /// get fFieldsExternal[] index using fFieldsExternalToInternal[] mapping // - do handle ID/RowID fields and published methods - function InternalFieldNameToFieldExternalIndex(const InternalFieldName: RawUTF8): integer; + function InternalFieldNameToFieldExternalIndex( + const InternalFieldName: RawUTF8): integer; /// create, prepare and bound inlined parameters to a thread-safe statement // - this implementation will call the ThreadSafeConnection virtual method, // then bound inlined parameters as :(1234): and call its Execute method // - should return nil on error, and not raise an exception function PrepareInlinedForRows(const aSQL: RawUTF8): ISQLDBStatement; /// overloaded method using FormatUTF8() and binding SynDB parameters - function PrepareDirectForRows(SQLFormat: PUTF8Char; const Args, Params: array of const): ISQLDBStatement; + function PrepareDirectForRows(SQLFormat: PUTF8Char; + const Args, Params: array of const): ISQLDBStatement; /// create, prepare, bound inlined parameters and execute a thread-safe statement // - this implementation will call the ThreadSafeConnection virtual method, // then bound inlined parameters as :(1234): and call its Execute method // - should return nil on error, and not raise an exception - function ExecuteInlined(const aSQL: RawUTF8; ExpectResults: Boolean): ISQLDBRows; overload; + function ExecuteInlined(const aSQL: RawUTF8; + ExpectResults: Boolean): ISQLDBRows; overload; /// overloaded method using FormatUTF8() and inlined parameters - function ExecuteInlined(SQLFormat: PUTF8Char; const Args: array of const; ExpectResults: Boolean): ISQLDBRows; overload; + function ExecuteInlined(SQLFormat: PUTF8Char; const Args: array of const; + ExpectResults: Boolean): ISQLDBRows; overload; /// overloaded method using FormatUTF8() and binding SynDB parameters function ExecuteDirect(SQLFormat: PUTF8Char; const Args, Params: array of const; ExpectResults: Boolean): ISQLDBRows; @@ -146,10 +151,12 @@ type function EngineExecute(const aSQL: RawUTF8): boolean; override; function EngineLockedNextID: TID; virtual; function EngineAdd(TableModelIndex: integer; const SentData: RawUTF8): TID; override; - function EngineUpdate(TableModelIndex: integer; ID: TID; const SentData: RawUTF8): boolean; override; + function EngineUpdate(TableModelIndex: integer; ID: TID; const + SentData: RawUTF8): boolean; override; function EngineDeleteWhere(TableModelIndex: integer; const SQLWhere: RawUTF8; const IDs: TIDDynArray): boolean; override; - function EngineList(const SQL: RawUTF8; ForceAJAX: Boolean=false; ReturnedRowCount: PPtrInt=nil): RawUTF8; override; + function EngineList(const SQL: RawUTF8; ForceAJAX: Boolean=false; + ReturnedRowCount: PPtrInt=nil): RawUTF8; override; // BLOBs should be access directly, not through slower JSON Base64 encoding function EngineRetrieveBlob(TableModelIndex: integer; aID: TID; BlobField: PPropInfo; out BlobData: TSQLRawBlob): boolean; override; @@ -224,7 +231,8 @@ type // - must be ended with Commit on success // - must be aborted with Rollback if any SQL statement failed // - return true if no transaction is active, false otherwise - function TransactionBegin(aTable: TSQLRecordClass; SessionID: cardinal=1): boolean; override; + function TransactionBegin(aTable: TSQLRecordClass; + SessionID: cardinal=1): boolean; override; /// end a transaction (implements REST END Member) // - write all pending SQL statements to the external database procedure Commit(SessionID: cardinal=1; RaiseException: boolean=false); override; @@ -363,8 +371,8 @@ type // and TSQLRestStorageExternal as the related static class // - no particular class is supplied here, since it will depend on the // associated Static TSQLRestStorageExternal instance - class procedure GetTableModuleProperties(var aProperties: TVirtualTableModuleProperties); - override; + class procedure GetTableModuleProperties( + var aProperties: TVirtualTableModuleProperties); override; /// called to determine the best way to access the virtual table // - will prepare the request for TSQLVirtualTableCursor.Search() // - this overridden method will let the external DB engine perform the search, @@ -391,7 +399,8 @@ type /// called to update a virtual table row content // - column order follows the Structure method, i.e. StoredClassProps.Fields[] order // - returns true on success, false otherwise - function Update(oldRowID, newRowID: Int64; var Values: TSQLVarDynArray): boolean; override; + function Update(oldRowID, newRowID: Int64; + var Values: TSQLVarDynArray): boolean; override; end; @@ -440,7 +449,8 @@ function VirtualTableExternalRegister(aModel: TSQLModel; // definitions, in a fluent interface: function VirtualTableExternalMap(aModel: TSQLModel; aClass: TSQLRecordClass; aExternalDB: TSQLDBConnectionProperties; - const aExternalTableName: RawUTF8=''; aMapping: TSQLRecordPropertiesMappingOptions=[]): PSQLRecordPropertiesMapping; + const aExternalTableName: RawUTF8=''; + aMapping: TSQLRecordPropertiesMappingOptions=[]): PSQLRecordPropertiesMapping; type /// all possible options for VirtualTableExternalRegisterAll/TSQLRestExternalDBCreate @@ -505,6 +515,7 @@ function TSQLRestExternalDBCreate(aModel: TSQLModel; implementation + function VirtualTableExternalRegister(aModel: TSQLModel; aClass: TSQLRecordClass; aExternalDB: TSQLDBConnectionProperties; const aExternalTableName: RawUTF8; aMappingOptions: TSQLRecordPropertiesMappingOptions): boolean; @@ -577,7 +588,8 @@ end; function VirtualTableExternalMap(aModel: TSQLModel; aClass: TSQLRecordClass; aExternalDB: TSQLDBConnectionProperties; - const aExternalTableName: RawUTF8; aMapping: TSQLRecordPropertiesMappingOptions): PSQLRecordPropertiesMapping; + const aExternalTableName: RawUTF8; + aMapping: TSQLRecordPropertiesMappingOptions): PSQLRecordPropertiesMapping; begin if VirtualTableExternalRegister(aModel,aClass,aExternalDB,aExternalTableName,aMapping) then result := @aModel.Props[aClass].ExternalDB else @@ -657,7 +669,7 @@ constructor TSQLRestStorageExternal.Create(aClass: TSQLRecordClass; {$ifndef NOVARIANTS} ftUTF8, // sftVariant ftUTF8, // sftNullable (retrieved from Prop.SQLFieldTypeStored) - {$endif} + {$endif NOVARIANTS} ftBlob, // sftBlob ftBlob, // sftBlobDynArray ftBlob, // sftBlobCustom @@ -987,6 +999,7 @@ function TSQLRestStorageExternal.EngineLockedNextID: TID; if (rows<>nil) and rows.Step then fEngineLockedMaxID := rows.ColumnInt(0) else fEngineLockedMaxID := 0; + rows.ReleaseRows; end; var handled: boolean; @@ -1348,8 +1361,10 @@ begin result := false else begin rows := ExecuteDirect(pointer(fSelectTableHasRowsSQL),[],[],true); if rows=nil then - result := false else + result := false else begin result := rows.Step; + rows.ReleaseRows; + end; end; end; @@ -1359,9 +1374,13 @@ begin if (self=nil) or (Table<>fStoredClass) then result := 0 else begin rows := ExecuteDirect('select count(*) from %',[fTableName],[],true); - if (rows=nil) or not rows.Step then - result := 0 else - result := rows.ColumnInt(0); + if rows=nil then + result := 0 else begin + if not rows.Step then + result := 0 else + result := rows.ColumnInt(0); + rows.ReleaseRows; + end; end; end; diff --git a/contrib/mORMot/SQLite3/mORMotDDD.pas b/contrib/mORMot/SQLite3/mORMotDDD.pas index c34247e..cae0d30 100644 --- a/contrib/mORMot/SQLite3/mORMotDDD.pas +++ b/contrib/mORMot/SQLite3/mORMotDDD.pas @@ -6,7 +6,7 @@ unit mORMotDDD; { This file is part of Synopse mORMot framework. - Synopse mORMot framework. Copyright (C) 2020 Arnaud Bouchez + Synopse mORMot framework. Copyright (C) 2022 Arnaud Bouchez Synopse Informatique - https://synopse.info *** BEGIN LICENSE BLOCK ***** @@ -25,7 +25,7 @@ unit mORMotDDD; The Initial Developer of the Original Code is Arnaud Bouchez. - Portions created by the Initial Developer are Copyright (C) 2020 + Portions created by the Initial Developer are Copyright (C) 2022 the Initial Developer. All Rights Reserved. Contributor(s): @@ -2540,7 +2540,7 @@ begin end; writeln('Press [Enter] to quit'); ioresult; - readln; + ConsoleWaitForEnterKey; // for proper Synchronize() work {$ifdef LINUX} if ioresult<>0 then // e.g. when redirected from "nohup daemon &" command WaitUntilHalted; diff --git a/contrib/mORMot/SQLite3/mORMotFastCgiServer.pas b/contrib/mORMot/SQLite3/mORMotFastCgiServer.pas index dabbbab..72ab3c2 100644 --- a/contrib/mORMot/SQLite3/mORMotFastCgiServer.pas +++ b/contrib/mORMot/SQLite3/mORMotFastCgiServer.pas @@ -6,7 +6,7 @@ unit mORMotFastCgiServer; { This file is part of Synopse mORMot framework. - Synopse mORMot framework. Copyright (C) 2020 Arnaud Bouchez + Synopse mORMot framework. Copyright (C) 2022 Arnaud Bouchez Synopse Informatique - https://synopse.info *** BEGIN LICENSE BLOCK ***** @@ -25,7 +25,7 @@ unit mORMotFastCgiServer; The Initial Developer of the Original Code is Arnaud Bouchez. - Portions created by the Initial Developer are Copyright (C) 2020 + Portions created by the Initial Developer are Copyright (C) 2022 the Initial Developer. All Rights Reserved. Contributor(s): diff --git a/contrib/mORMot/SQLite3/mORMotHttpClient.pas b/contrib/mORMot/SQLite3/mORMotHttpClient.pas index 029f925..2b3938c 100644 --- a/contrib/mORMot/SQLite3/mORMotHttpClient.pas +++ b/contrib/mORMot/SQLite3/mORMotHttpClient.pas @@ -6,7 +6,7 @@ unit mORMotHttpClient; { This file is part of Synopse mORMot framework. - Synopse mORMot framework. Copyright (C) 2020 Arnaud Bouchez + Synopse mORMot framework. Copyright (C) 2022 Arnaud Bouchez Synopse Informatique - https://synopse.info *** BEGIN LICENSE BLOCK ***** @@ -25,7 +25,7 @@ unit mORMotHttpClient; The Initial Developer of the Original Code is Arnaud Bouchez. - Portions created by the Initial Developer are Copyright (C) 2020 + Portions created by the Initial Developer are Copyright (C) 2022 the Initial Developer. All Rights Reserved. Contributor(s): @@ -580,17 +580,17 @@ begin Create(URI.Server,URI.Port,aModel,URI.Https); P := Pointer(aDefinition.DataBaseName); while P<>nil do begin - if UrlDecodeCardinal(P,'CONNECTTIMEOUT',V) then + if UrlDecodeCardinal(P,'CONNECTTIMEOUT=',V) then fConnectTimeout := V else - if UrlDecodeCardinal(P,'SENDTIMEOUT',V) then + if UrlDecodeCardinal(P,'SENDTIMEOUT=',V) then fSendTimeout := V else - if UrlDecodeCardinal(P,'RECEIVETIMEOUT',V) then + if UrlDecodeCardinal(P,'RECEIVETIMEOUT=',V) then fReceiveTimeout := V else - if UrlDecodeValue(P,'PROXYNAME',tmp) then + if UrlDecodeValue(P,'PROXYNAME=',tmp) then fProxyName := CurrentAnsiConvert.UTF8ToAnsi(tmp) else - if UrlDecodeValue(P,'PROXYBYPASS',tmp) then + if UrlDecodeValue(P,'PROXYBYPASS=',tmp) then fProxyByPass := CurrentAnsiConvert.UTF8ToAnsi(tmp); - if UrlDecodeCardinal(P,'IGNORESSLCERTIFICATEERRORS',V,@P) then + if UrlDecodeCardinal(P,'IGNORESSLCERTIFICATEERRORS=',V,@P) then fExtendedOptions.IgnoreSSLCertificateErrors := Boolean(V); end; inherited RegisteredClassCreateFrom(aModel,aDefinition); // call SetUser() diff --git a/contrib/mORMot/SQLite3/mORMotHttpServer.pas b/contrib/mORMot/SQLite3/mORMotHttpServer.pas index b7084c7..66e019d 100644 --- a/contrib/mORMot/SQLite3/mORMotHttpServer.pas +++ b/contrib/mORMot/SQLite3/mORMotHttpServer.pas @@ -6,7 +6,7 @@ unit mORMotHttpServer; { This file is part of Synopse mORMot framework. - Synopse mORMot framework. Copyright (C) 2020 Arnaud Bouchez + Synopse mORMot framework. Copyright (C) 2022 Arnaud Bouchez Synopse Informatique - https://synopse.info *** BEGIN LICENSE BLOCK ***** @@ -25,7 +25,7 @@ unit mORMotHttpServer; The Initial Developer of the Original Code is Arnaud Bouchez. - Portions created by the Initial Developer are Copyright (C) 2020 + Portions created by the Initial Developer are Copyright (C) 2022 the Initial Developer. All Rights Reserved. Contributor(s): @@ -577,7 +577,6 @@ begin if aURI='' then fHosts.Delete(aDomain) else fHosts.Add(aDomain,aURI); // e.g. Add('project1.com','root1') - end; constructor TSQLHttpServer.Create(const aPort: AnsiString; @@ -925,7 +924,7 @@ begin if fRedirectServerRootUriForExactCase and (match=rmMatchWithCaseChange) then begin // force redirection to exact Server.Model.Root case sensitivity call.OutStatus := HTTP_TEMPORARYREDIRECT; - call.OutHead := 'Location: '+serv.Model.Root+ + call.OutHead := 'Location: /'+serv.Model.Root+ copy(call.Url,length(serv.Model.Root)+1,maxInt); end else begin // call matching TSQLRestServer.URI() @@ -950,6 +949,8 @@ begin if hostroot<>'' then begin if ((result=HTTP_MOVEDPERMANENTLY) or (result=HTTP_TEMPORARYREDIRECT)) then begin redirect := FindIniNameValue(P,'LOCATION: '); + if (redirect<>'') and (redirect[1]='/') then + delete(redirect,1,1); // what is needed for real URI doesn't help here hostlen := length(hostroot); if (length(redirect)>hostlen) and (redirect[hostlen+1]='/') and IdemPropNameU(hostroot,pointer(redirect),hostlen) then diff --git a/contrib/mORMot/SQLite3/mORMotMVC.pas b/contrib/mORMot/SQLite3/mORMotMVC.pas index e50a571..95c585b 100644 --- a/contrib/mORMot/SQLite3/mORMotMVC.pas +++ b/contrib/mORMot/SQLite3/mORMotMVC.pas @@ -6,7 +6,7 @@ unit mORMotMVC; { This file is part of Synopse mORMot framework. - Synopse mORMot framework. Copyright (C) 2020 Arnaud Bouchez + Synopse mORMot framework. Copyright (C) 2022 Arnaud Bouchez Synopse Informatique - https://synopse.info *** BEGIN LICENSE BLOCK ***** @@ -25,7 +25,7 @@ unit mORMotMVC; The Initial Developer of the Original Code is Arnaud Bouchez. - Portions created by the Initial Developer are Copyright (C) 2020 + Portions created by the Initial Developer are Copyright (C) 2022 the Initial Developer. All Rights Reserved. Contributor(s): @@ -84,7 +84,7 @@ type TMVCViewFlags = set of (viewHasGenerationTimeTag); /// define a particular rendered View - // - as rendered by TMVCViewsAbtract.Render() method + // - as rendered by TMVCViewsAbstract.Render() method TMVCView = record /// the low-level content of this View Content: RawByteString; @@ -95,7 +95,7 @@ type end; /// an abstract class able to implement Views - TMVCViewsAbtract = class + TMVCViewsAbstract = class protected fFactory: TInterfaceFactory; fLogClass: TSynLogClass; @@ -105,7 +105,8 @@ type fViewGenerationTimeTag: RawUTF8; procedure SetViewTemplateFolder(const aFolder: TFileName); /// overriden implementations should return the rendered content - procedure Render(methodIndex: Integer; const Context: variant; var View: TMVCView); virtual; abstract; + procedure Render(methodIndex: Integer; const Context: variant; + var View: TMVCView); virtual; abstract; /// return the static file contents - from fViewStaticFolder by default // - called if cacheStatic has been defined function GetStaticFile(const aFileName: TFileName): RawByteString; virtual; @@ -148,7 +149,7 @@ type end; /// a class able to implement Views using Mustache templates - TMVCViewsMustache = class(TMVCViewsAbtract) + TMVCViewsMustache = class(TMVCViewsAbstract) protected fViewTemplateFileTimestampMonitor: cardinal; fViewPartials: TSynMustachePartials; @@ -400,7 +401,7 @@ type TMVCApplication = class; - /// abtract MVC rendering execution context + /// abstract MVC rendering execution context // - you shoud not execute this abstract class, but any of the inherited class // - one instance inherited from this class would be allocated for each event // - may return some data (when inheriting from TMVCRendererReturningData), or @@ -511,9 +512,9 @@ type // an optional simple in-memory cache TMVCRunWithViews = class(TMVCRun) protected - fViews: TMVCViewsAbtract; + fViews: TMVCViewsAbstract; fCacheLocker: IAutoLocker; - fCache: array of record + fCache: array of record // fCache[MethodIndex] Policy: TMVCRendererCachePolicy; TimeOutSeconds: cardinal; RootValue: RawUTF8; @@ -523,7 +524,7 @@ type public /// link this runner class to a specified MVC application constructor Create(aApplication: TMVCApplication; - aViews: TMVCViewsAbtract=nil); reintroduce; + aViews: TMVCViewsAbstract=nil); reintroduce; /// method called to flush the caching mechanism for a MVC command procedure NotifyContentChangedForMethod(aMethodIndex: integer); override; /// defines the caching policy for a given MVC command @@ -537,7 +538,7 @@ type /// finalize this instance destructor Destroy; override; /// read-write access to the associated MVC Views instance - property Views: TMVCViewsAbtract read fViews; + property Views: TMVCViewsAbstract read fViews; end; /// the kinds of optional content which may be published @@ -587,7 +588,7 @@ type // - aPublishOptions could be used to specify integration with the server constructor Create(aApplication: TMVCApplication; aRestServer: TSQLRestServer=nil; const aSubURI: RawUTF8=''; - aViews: TMVCViewsAbtract=nil; aPublishOptions: TMVCPublishOptions= + aViews: TMVCViewsAbstract=nil; aPublishOptions: TMVCPublishOptions= [low(TMVCPublishOption)..high(TMVCPublishOption)]); reintroduce; /// define some content for a static file // - only used if cacheStatic has been defined @@ -645,7 +646,7 @@ type // - you should inherit from this class, then implement an interface inheriting // from IMVCApplication to define the various commands of the application // - here the Model would be a TSQLRest instance, Views will be defined by - // TMVCViewsAbtract (e.g. TMVCViewsMustache), and the ViewModel/Controller + // TMVCViewsAbstract (e.g. TMVCViewsMustache), and the ViewModel/Controller // will be implemented with IMVCApplication methods of the inherited class // - inherits from TInjectableObject, so that you could resolve dependencies // via services or stubs, following the IoC pattern @@ -726,9 +727,9 @@ const implementation -{ TMVCViewsAbtract } +{ TMVCViewsAbstract } -constructor TMVCViewsAbtract.Create(aInterface: PTypeInfo; aLogClass: TSynLogClass); +constructor TMVCViewsAbstract.Create(aInterface: PTypeInfo; aLogClass: TSynLogClass); begin inherited Create; fFactory := TInterfaceFactory.Get(aInterface); @@ -739,13 +740,13 @@ begin fViewGenerationTimeTag := '[[GENERATION_TIME_TAG]]'; end; -procedure TMVCViewsAbtract.SetViewTemplateFolder(const aFolder: TFileName); +procedure TMVCViewsAbstract.SetViewTemplateFolder(const aFolder: TFileName); begin fViewTemplateFolder := IncludeTrailingPathDelimiter(aFolder); fViewStaticFolder := IncludeTrailingPathDelimiter(fViewTemplateFolder+STATIC_URI); end; -function TMVCViewsAbtract.GetStaticFile(const aFileName: TFileName): RawByteString; +function TMVCViewsAbstract.GetStaticFile(const aFileName: TFileName): RawByteString; begin result := StringFromFile(fViewStaticFolder + aFileName); end; @@ -1490,6 +1491,7 @@ begin [self,aRestModel,fFactory.InterfaceTypeInfo^.Name,URI]) else // TServiceCustomAnswer maps TMVCAction in TMVCApplication.RunOnRestServer ArgsResultIsServiceCustomAnswer := true; + FlushAnyCache; end; destructor TMVCApplication.Destroy; @@ -1738,7 +1740,7 @@ end; { TMVCRunWithViews } constructor TMVCRunWithViews.Create(aApplication: TMVCApplication; - aViews: TMVCViewsAbtract); + aViews: TMVCViewsAbstract); begin inherited Create(aApplication); fViews := aViews; @@ -1792,7 +1794,7 @@ end; constructor TMVCRunOnRestServer.Create(aApplication: TMVCApplication; aRestServer: TSQLRestServer; const aSubURI: RawUTF8; - aViews: TMVCViewsAbtract; aPublishOptions: TMVCPublishOptions); + aViews: TMVCViewsAbstract; aPublishOptions: TMVCPublishOptions); var m: integer; bypass: boolean; method: RawUTF8; diff --git a/contrib/mORMot/SQLite3/mORMotMidasVCL.pas b/contrib/mORMot/SQLite3/mORMotMidasVCL.pas index 1e01580..a002fc0 100644 --- a/contrib/mORMot/SQLite3/mORMotMidasVCL.pas +++ b/contrib/mORMot/SQLite3/mORMotMidasVCL.pas @@ -6,7 +6,7 @@ unit mORMotMidasVCL; { This file is part of Synopse mORmot framework. - Synopse mORMot framework. Copyright (C) 2020 Arnaud Bouchez + Synopse mORMot framework. Copyright (C) 2022 Arnaud Bouchez Synopse Informatique - https://synopse.info *** BEGIN LICENSE BLOCK ***** @@ -25,7 +25,7 @@ unit mORMotMidasVCL; The Initial Developer of the Original Code is Arnaud Bouchez. - Portions created by the Initial Developer are Copyright (C) 2020 + Portions created by the Initial Developer are Copyright (C) 2022 the Initial Developer. All Rights Reserved. Contributor(s): diff --git a/contrib/mORMot/SQLite3/mORMotMongoDB.pas b/contrib/mORMot/SQLite3/mORMotMongoDB.pas index a4fa813..f2d4e9e 100644 --- a/contrib/mORMot/SQLite3/mORMotMongoDB.pas +++ b/contrib/mORMot/SQLite3/mORMotMongoDB.pas @@ -6,7 +6,7 @@ unit mORMotMongoDB; { This file is part of Synopse mORMot framework. - Synopse mORMot framework. Copyright (C) 2020 Arnaud Bouchez + Synopse mORMot framework. Copyright (C) 2022 Arnaud Bouchez Synopse Informatique - https://synopse.info *** BEGIN LICENSE BLOCK ***** @@ -25,7 +25,7 @@ unit mORMotMongoDB; The Initial Developer of the Original Code is Arnaud Bouchez. - Portions created by the Initial Developer are Copyright (C) 2020 + Portions created by the Initial Developer are Copyright (C) 2022 the Initial Developer. All Rights Reserved. Contributor(s): @@ -84,7 +84,7 @@ uses SynMongoDB; type - /// exeception class raised by this units + /// exception class raised by this units EORMMongoDBException = class(EORMException); /// how TSQLRestStorageMongoDB would compute the next ID to be inserted @@ -190,6 +190,12 @@ type /// create one index for all specific FieldNames at once function CreateSQLMultiIndex(Table: TSQLRecordClass; const FieldNames: array of RawUTF8; Unique: boolean; IndexName: RawUTF8=''): boolean; override; + /// search for a field value, according to its SQL content representation + // - return true on success (i.e. if some values have been added to ResultID) + // - store the results into the ResultID dynamic array + // - faster than OneFieldValues method, which creates a temporary JSON content + function SearchField(const FieldName, FieldValue: RawUTF8; + out ResultID: TIDDynArray): boolean; override; /// drop the whole table content // - in practice, dropping the whole MongoDB database would be faster @@ -992,6 +998,32 @@ begin result := true; // we do not have any Virtual Table yet -> always accept end; +function TSQLRestStorageMongoDB.SearchField(const FieldName, FieldValue: RawUTF8; + out ResultID: TIDDynArray): boolean; +var query: variant; + id: TBSONIterator; + n: integer; // an external count is actually faster +begin + if (fCollection = nil) or (FieldName = '') or (FieldValue = '') then + result := false else + try + // use {%:%} here since FieldValue is already JSON encoded + query := BSONVariant('{%:%}', + [fStoredClassMapping^.InternalToExternal(FieldName), FieldValue], []); + // retrieve the IDs for this query + if id.Init(fCollection.FindBSON(query, BSONVariant(['_id', 1]))) then begin + n := 0; + while id.Next do + AddInt64(TInt64DynArray(ResultID), n, id.Item.DocItemToInteger('_id')); + SetLength(ResultID, n); + result := true; + end else + result := false; + except + result := false; + end; +end; + function TSQLRestStorageMongoDB.GetJSONValues(const Res: TBSONDocument; const extFieldNames: TRawUTF8DynArray; W: TJSONSerializer): integer; function itemFind(item: PBSONElement; itemcount,o1ndx: integer; diff --git a/contrib/mORMot/SQLite3/mORMotReport.pas b/contrib/mORMot/SQLite3/mORMotReport.pas index 7b7961c..51bd969 100644 --- a/contrib/mORMot/SQLite3/mORMotReport.pas +++ b/contrib/mORMot/SQLite3/mORMotReport.pas @@ -6,7 +6,7 @@ unit mORMotReport; (* This file is part of Synopse framework. - Synopse framework. Copyright (C) 2020 Arnaud Bouchez + Synopse framework. Copyright (C) 2022 Arnaud Bouchez Synopse Informatique - https://synopse.info *** BEGIN LICENSE BLOCK ***** @@ -27,7 +27,7 @@ unit mORMotReport; Portions created by the Initial Developer are Copyright (C) 2003 the Initial Developer. All Rights Reserved. - Portions created by Arnaud Bouchez for Synopse are Copyright (C) 2020 + Portions created by Arnaud Bouchez for Synopse are Copyright (C) 2022 Arnaud Bouchez. All Rights Reserved. Contributor(s): @@ -111,7 +111,7 @@ unit mORMotReport; - full Unicode text process (even before Delphi 2009) - speed up and various bug fixes to work with Delphi 5 up to XE3 - Modifications © 2009-2020 Arnaud Bouchez + Modifications (c) 2009-2022 Arnaud Bouchez Version 1.4 - February 8, 2010 - whole Synopse SQLite3 database framework released under the GNU Lesser @@ -4932,11 +4932,9 @@ begin rc := rcPage; rc.Top := (fCurrentYPos*1440) div LogY; LastChar := 0; - with TextLenEx do begin - flags := GTL_DEFAULT; - codepage := CP_ACP; - end; - MaxLen := SendMessage(RichEditHandle, EM_GETTEXTLENGTHEX, Integer(@TextLenEx), 0); + TextLenEx.flags := GTL_DEFAULT; + TextLenEx.codepage := CP_ACP; + MaxLen := SendMessage(RichEditHandle, EM_GETTEXTLENGTHEX, PtrInt(@TextLenEx), 0); chrg.cpMax := -1; OldMap := SetMapMode(hdc, MM_TEXT); try @@ -4945,7 +4943,7 @@ begin chrg.cpMin := LastChar; hdc := fCanvas.Handle; hdcTarget := hdc; - LastChar := SendMessage(RichEditHandle, EM_FORMATRANGE, 1, Integer(@Range)); + LastChar := SendMessage(RichEditHandle, EM_FORMATRANGE, 1, PtrInt(@Range)); if EndOfPagePositions<>nil then AddInteger(EndOfPagePositions^,LastChar); if cardinal(LastChar)>=cardinal(MaxLen) then diff --git a/contrib/mORMot/SQLite3/mORMotSQLite3.pas b/contrib/mORMot/SQLite3/mORMotSQLite3.pas index 8b77e8f..ddfa3e4 100644 --- a/contrib/mORMot/SQLite3/mORMotSQLite3.pas +++ b/contrib/mORMot/SQLite3/mORMotSQLite3.pas @@ -6,7 +6,7 @@ unit mORMotSQLite3; { This file is part of Synopse mORMot framework. - Synopse mORMot framework. Copyright (C) 2020 Arnaud Bouchez + Synopse mORMot framework. Copyright (C) 2022 Arnaud Bouchez Synopse Informatique - https://synopse.info *** BEGIN LICENSE BLOCK ***** @@ -25,7 +25,7 @@ unit mORMotSQLite3; The Initial Developer of the Original Code is Arnaud Bouchez. - Portions created by the Initial Developer are Copyright (C) 2020 + Portions created by the Initial Developer are Copyright (C) 2022 the Initial Developer. All Rights Reserved. Contributor(s): @@ -118,7 +118,7 @@ type // - SQL statements for record retrieval from ID are prepared for speed TSQLRestServerDB = class(TSQLRestServer) private - /// internal copy of the SQLite3 database engine + /// access to the associated SQLite3 database engine fDB: TSQLDataBase; /// initialized by Create(aModel,aDBFileName) fOwnedDB: TSQLDataBase; @@ -511,7 +511,8 @@ type // - returns the created TSQLVirtualTableModule instance (which will be a // TSQLVirtualTableModuleSQLite3 instance in fact) // - will raise an exception of failure -function RegisterVirtualTableModule(aModule: TSQLVirtualTableClass; aDatabase: TSQLDataBase): TSQLVirtualTableModule; +function RegisterVirtualTableModule(aModule: TSQLVirtualTableClass; + aDatabase: TSQLDataBase): TSQLVirtualTableModule; implementation @@ -519,7 +520,8 @@ implementation {$ifdef SQLVIRTUALLOGS} uses mORMotDB; -{$endif} +{$endif SQLVIRTUALLOGS} + { TSQLTableDB } @@ -2186,7 +2188,7 @@ begin TSQLRestStorageExternal(Table.Static).ComputeSQL(prepared^); SQLite3Log.Add.Log(sllDebug,'vt_BestIndex(%) plan=% -> cost=% rows=%', [sqlite3.VersionNumber,ord(Prepared^.EstimatedCost),pInfo.estimatedCost,pInfo.estimatedRows]); - {$endif} + {$endif SQLVIRTUALLOGS} finally if result<>SQLITE_OK then sqlite3.free_(Prepared); // avoid memory leak on error diff --git a/contrib/mORMot/SQLite3/mORMotSelfTests.pas b/contrib/mORMot/SQLite3/mORMotSelfTests.pas index 3c7456a..0b6dbe4 100644 --- a/contrib/mORMot/SQLite3/mORMotSelfTests.pas +++ b/contrib/mORMot/SQLite3/mORMotSelfTests.pas @@ -6,7 +6,7 @@ unit mORMotSelfTests; { This file is part of Synopse mORMot framework. - Synopse mORMot framework. Copyright (C) 2020 Arnaud Bouchez + Synopse mORMot framework. Copyright (C) 2022 Arnaud Bouchez Synopse Informatique - https://synopse.info *** BEGIN LICENSE BLOCK ***** @@ -25,7 +25,7 @@ unit mORMotSelfTests; The Initial Developer of the Original Code is Arnaud Bouchez. - Portions created by the Initial Developer are Copyright (C) 2020 + Portions created by the Initial Developer are Copyright (C) 2022 the Initial Developer. All Rights Reserved. Contributor(s): @@ -110,7 +110,7 @@ end; procedure TTestSynopsemORMotFramework.SynopseLibraries; begin - //AddCase(TTestLowLevelTypes); + //AddCase(TTestProtocols); //exit; AddCase([TTestLowLevelCommon, TTestLowLevelTypes, @@ -137,7 +137,7 @@ type // mORMot.pas unit doesn't compile with Delphi 5 yet {$else} procedure TTestSynopsemORMotFramework._mORMot; begin - //AddCase(TTestExternalDatabase); + //AddCase(TTestBasicClasses); //exit; // (* AddCase([TTestFileBased,TTestFileBasedMemoryMap,TTestFileBasedWAL]); AddCase(TTestMemoryBased); diff --git a/contrib/mORMot/SQLite3/mORMotService.pas b/contrib/mORMot/SQLite3/mORMotService.pas index 84b3378..29664a6 100644 --- a/contrib/mORMot/SQLite3/mORMotService.pas +++ b/contrib/mORMot/SQLite3/mORMotService.pas @@ -6,7 +6,7 @@ unit mORMotService; { This file is part of Synopse mORMot framework. - Synopse mORMot framework. Copyright (C) 2020 Arnaud Bouchez + Synopse mORMot framework. Copyright (C) 2022 Arnaud Bouchez Synopse Informatique - https://synopse.info *** BEGIN LICENSE BLOCK ***** @@ -25,7 +25,7 @@ unit mORMotService; The Initial Developer of the Original Code is Arnaud Bouchez. - Portions created by the Initial Developer are Copyright (C) 2020 + Portions created by the Initial Developer are Copyright (C) 2022 the Initial Developer. All Rights Reserved. Contributor(s): @@ -1129,29 +1129,35 @@ begin result := FArgsList[Idx]; end; + {$ifdef CPUX86} + {.$define X86JUMPER} // this preliminary version is buggy so disabled + // a single service per excecutable is fine enough for our daemons + // also for proper Delphi 10.4 compilation with no hint +{$endif CPUX86} + +{$ifdef X86JUMPER} procedure JumpToService; asm pop eax - mov eax, [eax] // retrieve self value + mov eax, [eax] // retrieve TService self value mov edx, [esp+4] call TService.CtrlHandle ret 4 end; -{$endif CPUX86} +{$endif X86JUMPER} function TService.GetControlHandler: TServiceControlHandler; -{$ifdef CPUX86} +{$ifdef X86JUMPER} var AfterCallAddr: Pointer; Offset: Integer; -{$endif} +{$endif X86JUMPER} begin Result := fControlHandler; if not Assigned(Result) then ServiceLog.Add.Log(sllError,'%.GetControlHandler with fControlHandler=nil: '+ 'use TServiceSingle or set a custom ControlHandler',[self]); - {$ifdef CPUX86} - exit; + {$ifdef X86JUMPER} if not Assigned(Result) then begin raise EServiceException.Create('Automated jumper generation is not working: '+ @@ -1169,7 +1175,7 @@ begin end; Result := Pointer(fJumper); end; - {$endif CPUX86} + {$endif X86JUMPER} end; function TService.GetInstalled: boolean; @@ -1674,7 +1680,9 @@ begin ExeVersion.Version.DetailedOrVoid], daemon); start; while SynDaemonTerminated = 0 do - {$ifdef FPC}fpPause{$else}pause{$endif}; + if GetCurrentThreadID = MainThreadID then + CheckSynchronize(100) else + Sleep(100); finally if log <> nil then log.Log(sllNewRun, 'Stop /% from Sig=%', [TXT[dofork], SynDaemonTerminated], daemon); @@ -2000,9 +2008,6 @@ begin fLogRotateFileCount := 2; fServiceName := UTF8ToString(ExeVersion.ProgramName); fServiceDisplayName := fServiceName; - {$ifndef MSWINDOWS} - fLogPath := GetSystemPath(spLog); // /var/log or $home - {$endif MSWINDOWS} end; function TSynDaemonSettings.ServiceDescription: string; diff --git a/contrib/mORMot/SQLite3/mORMotToolBar.pas b/contrib/mORMot/SQLite3/mORMotToolBar.pas index 736ceed..de62281 100644 --- a/contrib/mORMot/SQLite3/mORMotToolBar.pas +++ b/contrib/mORMot/SQLite3/mORMotToolBar.pas @@ -9,7 +9,7 @@ interface This file is part of Synopse mORMot framework. - Synopse mORMot framework. Copyright (C) 2020 Arnaud Bouchez + Synopse mORMot framework. Copyright (C) 2022 Arnaud Bouchez Synopse Informatique - https://synopse.info *** BEGIN LICENSE BLOCK ***** @@ -28,7 +28,7 @@ interface The Initial Developer of the Original Code is Arnaud Bouchez. - Portions created by the Initial Developer are Copyright (C) 2020 + Portions created by the Initial Developer are Copyright (C) 2022 the Initial Developer. All Rights Reserved. Contributor(s): diff --git a/contrib/mORMot/SQLite3/mORMotUI.pas b/contrib/mORMot/SQLite3/mORMotUI.pas index 7534e97..d584fda 100644 --- a/contrib/mORMot/SQLite3/mORMotUI.pas +++ b/contrib/mORMot/SQLite3/mORMotUI.pas @@ -6,7 +6,7 @@ unit mORMotUI; (* This file is part of Synopse mORMot framework. - Synopse mORMot framework. Copyright (C) 2020 Arnaud Bouchez + Synopse mORMot framework. Copyright (C) 2022 Arnaud Bouchez Synopse Informatique - https://synopse.info *** BEGIN LICENSE BLOCK ***** @@ -25,7 +25,7 @@ unit mORMotUI; The Initial Developer of the Original Code is Arnaud Bouchez. - Portions created by the Initial Developer are Copyright (C) 2020 + Portions created by the Initial Developer are Copyright (C) 2022 the Initial Developer. All Rights Reserved. Contributor(s): @@ -222,7 +222,7 @@ type TSQLTableToGridAlign = (alLeft, alCenter, alRight); /// a hidden component, used for displaying a TSQLTable in a TDrawGrid - // - just call TSQLTableToGrid.Create(Grid,Table) to initiate the association + // - just call TSQLTableToGrid.Create(Grid,Table) to initiate the association // - the Table will be released when no longer necessary // - any former association by TSQLTableToGrid.Create() will be overridden // - handle unicode, column size, field sort, incremental key lookup, hide ID @@ -720,7 +720,7 @@ begin end; end; -function CreateAnIcon (const Name, Description, Path, Parameters, +function CreateAnIcon(const Name, Description, Path, Parameters, WorkingDir, IconFilename: TFileName; const IconIndex: Integer; const RunMinimized: Boolean = false): TFileName; var Dir: TFileName; @@ -2347,7 +2347,7 @@ end; procedure TUIComponentsPersist.TrackControls(const ctrls: array of TComponent); var - i: integer; + i: PtrInt; begin for i := 0 to high(ctrls) do ObjArrayAddOnce(fTracked, ctrls[i]); @@ -2359,5 +2359,12 @@ begin RegisterComponents('Synopse',[TSynLabeledEdit]); end; + +initialization + {$ifdef FPC} + // LCL/Lazarus components expect UTF-8 encoding for strings + CurrentAnsiConvert := UTF8AnsiConvert; + {$endif FPC} + end. diff --git a/contrib/mORMot/SQLite3/mORMotUIEdit.pas b/contrib/mORMot/SQLite3/mORMotUIEdit.pas index 3a1626a..598c69a 100644 --- a/contrib/mORMot/SQLite3/mORMotUIEdit.pas +++ b/contrib/mORMot/SQLite3/mORMotUIEdit.pas @@ -6,7 +6,7 @@ unit mORMotUIEdit; (* This file is part of Synopse mORMot framework. - Synopse mORMot framework. Copyright (C) 2020 Arnaud Bouchez + Synopse mORMot framework. Copyright (C) 2022 Arnaud Bouchez Synopse Informatique - https://synopse.info *** BEGIN LICENSE BLOCK ***** @@ -25,7 +25,7 @@ unit mORMotUIEdit; The Initial Developer of the Original Code is Arnaud Bouchez. - Portions created by the Initial Developer are Copyright (C) 2020 + Portions created by the Initial Developer are Copyright (C) 2022 the Initial Developer. All Rights Reserved. Contributor(s): diff --git a/contrib/mORMot/SQLite3/mORMotUILogin.pas b/contrib/mORMot/SQLite3/mORMotUILogin.pas index 251c508..143fe12 100644 --- a/contrib/mORMot/SQLite3/mORMotUILogin.pas +++ b/contrib/mORMot/SQLite3/mORMotUILogin.pas @@ -6,7 +6,7 @@ unit mORMotUILogin; (* This file is part of Synopse mORMot framework. - Synopse mORMot framework. Copyright (C) 2020 Arnaud Bouchez + Synopse mORMot framework. Copyright (C) 2022 Arnaud Bouchez Synopse Informatique - https://synopse.info *** BEGIN LICENSE BLOCK ***** @@ -25,7 +25,7 @@ unit mORMotUILogin; The Initial Developer of the Original Code is Arnaud Bouchez. - Portions created by the Initial Developer are Copyright (C) 2020 + Portions created by the Initial Developer are Copyright (C) 2022 the Initial Developer. All Rights Reserved. Contributor(s): diff --git a/contrib/mORMot/SQLite3/mORMotUIOptions.pas b/contrib/mORMot/SQLite3/mORMotUIOptions.pas index 03fb588..8347b75 100644 --- a/contrib/mORMot/SQLite3/mORMotUIOptions.pas +++ b/contrib/mORMot/SQLite3/mORMotUIOptions.pas @@ -6,7 +6,7 @@ unit mORMotUIOptions; (* This file is part of Synopse mORMot framework. - Synopse mORMot framework. Copyright (C) 2020 Arnaud Bouchez + Synopse mORMot framework. Copyright (C) 2022 Arnaud Bouchez Synopse Informatique - https://synopse.info *** BEGIN LICENSE BLOCK ***** @@ -25,7 +25,7 @@ unit mORMotUIOptions; The Initial Developer of the Original Code is Arnaud Bouchez. - Portions created by the Initial Developer are Copyright (C) 2020 + Portions created by the Initial Developer are Copyright (C) 2022 the Initial Developer. All Rights Reserved. Contributor(s): diff --git a/contrib/mORMot/SQLite3/mORMotUIQuery.pas b/contrib/mORMot/SQLite3/mORMotUIQuery.pas index 72cd061..adad045 100644 --- a/contrib/mORMot/SQLite3/mORMotUIQuery.pas +++ b/contrib/mORMot/SQLite3/mORMotUIQuery.pas @@ -6,7 +6,7 @@ unit mORMotUIQuery; (* This file is part of Synopse mORMot framework. - Synopse mORMot framework. Copyright (C) 2020 Arnaud Bouchez + Synopse mORMot framework. Copyright (C) 2022 Arnaud Bouchez Synopse Informatique - https://synopse.info *** BEGIN LICENSE BLOCK ***** @@ -25,7 +25,7 @@ unit mORMotUIQuery; The Initial Developer of the Original Code is Arnaud Bouchez. - Portions created by the Initial Developer are Copyright (C) 2020 + Portions created by the Initial Developer are Copyright (C) 2022 the Initial Developer. All Rights Reserved. Contributor(s): diff --git a/contrib/mORMot/SQLite3/mORMotVCL.pas b/contrib/mORMot/SQLite3/mORMotVCL.pas index 703dea5..e525d2a 100644 --- a/contrib/mORMot/SQLite3/mORMotVCL.pas +++ b/contrib/mORMot/SQLite3/mORMotVCL.pas @@ -6,7 +6,7 @@ unit mORMotVCL; { This file is part of Synopse mORmot framework. - Synopse mORMot framework. Copyright (C) 2020 Arnaud Bouchez + Synopse mORMot framework. Copyright (C) 2022 Arnaud Bouchez Synopse Informatique - https://synopse.info *** BEGIN LICENSE BLOCK ***** @@ -25,7 +25,7 @@ unit mORMotVCL; The Initial Developer of the Original Code is Arnaud Bouchez. - Portions created by the Initial Developer are Copyright (C) 2020 + Portions created by the Initial Developer are Copyright (C) 2022 the Initial Developer. All Rights Reserved. Contributor(s): diff --git a/contrib/mORMot/SQLite3/mORMotWrappers.pas b/contrib/mORMot/SQLite3/mORMotWrappers.pas index 3d6b4a7..0b56911 100644 --- a/contrib/mORMot/SQLite3/mORMotWrappers.pas +++ b/contrib/mORMot/SQLite3/mORMotWrappers.pas @@ -6,7 +6,7 @@ unit mORMotWrappers; { This file is part of Synopse mORMot framework. - Synopse mORMot framework. Copyright (C) 2020 Arnaud Bouchez + Synopse mORMot framework. Copyright (C) 2022 Arnaud Bouchez Synopse Informatique - https://synopse.info *** BEGIN LICENSE BLOCK ***** @@ -25,7 +25,7 @@ unit mORMotWrappers; The Initial Developer of the Original Code is Arnaud Bouchez. - Portions created by the Initial Developer are Copyright (C) 2020 + Portions created by the Initial Developer are Copyright (C) 2022 the Initial Developer. All Rights Reserved. Contributor(s) (for this unit and the .mustache templates): diff --git a/contrib/mORMot/SQLite3/mORMoti18n.pas b/contrib/mORMot/SQLite3/mORMoti18n.pas index a92e050..3d3ec60 100644 --- a/contrib/mORMot/SQLite3/mORMoti18n.pas +++ b/contrib/mORMot/SQLite3/mORMoti18n.pas @@ -6,7 +6,7 @@ unit mORMoti18n; (* This file is part of Synopse mORMot framework. - Synopse mORMot framework. Copyright (C) 2020 Arnaud Bouchez + Synopse mORMot framework. Copyright (C) 2022 Arnaud Bouchez Synopse Informatique - https://synopse.info *** BEGIN LICENSE BLOCK ***** @@ -25,7 +25,7 @@ unit mORMoti18n; The Initial Developer of the Original Code is Arnaud Bouchez. - Portions created by the Initial Developer are Copyright (C) 2020 + Portions created by the Initial Developer are Copyright (C) 2022 the Initial Developer. All Rights Reserved. Contributor(s): diff --git a/contrib/mORMot/SQLite3/sqlite3.obj b/contrib/mORMot/SQLite3/sqlite3.obj index f931be0eada9a56d200febe3518337d5864490b7..2d2b43d0e164a138680014974febed5496190b59 100644 GIT binary patch literal 923856 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%0A)FZtP2TL=4KShFLWM`67e`-2@7vYcyJENT& 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
  • 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ÐlQ^>`GQ(Pmj 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

    m+=1`}(@2PS?_Y2g#t`7HGnmw(?c+U{qG9Dz( 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 z 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 BZtDO5 delta 466204 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

    p9mbA_54`a${Ej*~&0D?~? zU?B!hBXFhjpSB&BAO-uWC9rbV-SgwE`%vy_D)-kvsN7Bg)+MLYE(5K~d((tJR2O+K zex-f6ViNP(TO~NALznykt@4Ok1*eOMj^RJRP`1^5z8cr&9J7uzJ&_Gb&1*jf z6YMd0yn>wzWZ+4KNVXPV;hSK=Mm0@!VX*+>^51S8p&sb@ZG-EU8vC{ZXtu({C%K1;R(x1{hAM@|yq*2nMk9pU4 zX?8!TZ^8Tuymk}-sd{G3NJNeI%V;6v_*?PP95V^LNcmCy4-l)gz19Mr*!r#M zJUUUzl-7L4?TONjiN}R(1llFc;1r1hU7L9;|7W6fo7CwIejrgAAbs-=zmzEXhJAa1 zW{`yOA_W2YmCbbMOb6HXEtAt^eal;WMhHuxl5 zEn{!5T?nLsK*LYe(~idx!!8^Umh8ynZ}gCc4IvkFU!4Q%?|Wt{{)K^#u-5ntp=Uwb zpRnTk7B2OaauX@$5^wl`Jw-bbx>)B{L9wC{2TK5 zQ|aHjVffeYTKAcJ(C??`PFU||v*dq<+CT#xvU{Kq4S2X#DZY}O;BcXFfw#`KRMw^rLOi*3nm=eKRn)*ht?qQ|O(43V*4$6rVvq>L+Ra z&k)2RY}HqnjMY%r$|qpNC-%!a17SCNi0OPLl`Fj^tCgTaF3zlnM%5Kp>8AA_Ux;Ik zwGJ%7HQw-v41k~wfdOC|{;DI`?nPjV@qMKD&U9$ByR`o4{Ej}-5R;!;2o{7}NaL&f zNHRIL?4;89%)PwFe>4x!egcgxRE>jU;`3s(A!E3dUL7p$-rEStL>y>owGsdqe` zmNKyK$gJ8|A(=6fs3lz{=m=d8DNCb83h#;6%`eg@?9!E zfN=mtbO0H^M_CyXQCZBGCNR*5IUL6pA% zL?h<^UHi-^cJKf5|MzLjoOAYjuf6u#>-nWR5SLgfow^V4C!TMJWdkxU18tl`g`U*{ ziKuhpR$&~9GRp-{@oZmD(BfHLJqLdR>+GRXh;@zwVwOFzd}uEgSs?JlF6jc;b1w_qqTT?~_d#(*1OfC7jpSKFQ>a3| zdkvKxekyVHWuu4A&;UsNjvn)+y;;x1e-q^XGARJL1L58VBoeNJOFZQ2kytYVe%;I0 zoTle?ld?Qwf#J`BpX<${I}rSpcZDMwkI0y%%;e#HSnn8tn69fs0*O5xmvfH*R!E$9 zdLP!ORmnh&3NP=&Iy^&Qi3O35V)!!I>c2Xgs_2P*KT_z+G^^SWVo?;3g`e zc3>!oXZ}hY8}1Z4#uIow*>!+v0`1ZVY@+#yr#T?lHVV6kPW6lI5IlNp)FpxOMx^1+8n)l#2JQ1eS9HPV+Pz z)M7(obw%6eoZ|cu_+)rZ_ypjX+Xc`TL@^r@rl>$`Jv&y1-L;V^+ z*|=YCccX*Kqr4Cp7qsS;fLc{ zuW)(`9$)nw|JniUE=@kadjRV$-ulAinE0DVRnz^W|d;|HmPksAK9e<}DH!hXyLQAr8iyLxYI@9f$xuN%TR0ggh#6Ku6WO*G!?; zf@}gVS!CSF8wSEyE{Xp>kVWB7=pYu`Wfb)@@i2fcp(%5K49KQBTIK)o^e`lPtWGk6_*Y=Lk#wT>|s2)P26Lr!O5W`nzMbxA<^d4Ibu=>Pm zY9%VTNT{vsLvYfAH?=?$SIT>nCjjYs{hVpqz3P+<==zEgR>0&YpfX+Xmj zl@Pgcr939sQ>avK0S<3n!TJ9lSgY*M-=6(y*?9=ZgZEeH{0j|_`^Cm^>NG#R2W1@B z;j46+6%09YVT9WWDG8O1fUj?>GB018AwqY+kq&Mb6suLWBt$My$_2L`%iT=;C&;Uy zK{i_TRw8(Bodv5H%uYLh-mX2X ze9XHIVWYydF~G{2aeT%QHmoCBCAmoxgmbP6+-8k7Wx;FwwIOUi?>CfxZalk*cOM3xpQ9}|4`XR;L@r-GjNQ+UP3KpJvGiV~2et8a zFo5>r(*GdDp>M$7z|kP%+Oujt^;tB@{+3dJ#Q*1Af7F1Z41xg<;}+B4`46ccMyfTeMN=XX(AEsNGAx!Biy{h`l9`6N@WU&ejP1d9^{(#%+O& z9ahzW?irVyr&T}~8CCK<9@I+63s$4W#EjKT@T(BMeaYf`lAJT&oqOB^Th^&iw`+i# zyh3PD9I^ZW-Q}}L=a7Cx`pGF}sm%+gbHo?JCIU+((6%p*JnXf~5txy7@EFlc%wVvG z>IA~(L3GS98j%6P7b=k9!6qjz(PnV;hY>o0lOaN_JM$Ay=2lE0%x#_S+@q-LyJl)p z@9l^3@?tv$9Vw5_Q%(ic-Gs`9SfLQgKw@XOBtt0is}!Q^JeaP%{zVmlX(w=nvHBz? z7aBpSU^c50)fO*gl4y0yLbrme9^&-APao2fz~9A%4g|gUDU5O_TA@Xe*EOQP&u3a< z`6!i6s3deCLmCBS>iL^jZjWU((2oLtHF+eGCC!m$D2@Gz>-U=wJv&N`J1rX%EgRr? z4r0lta89%_?wn@Ar;4wnFe#Hdz z+2D`YUWyL>m7^?a;(v`|J)H5+AOn=*3hcwxC01IVP-PE@q#+#O1|rA!BC&8*Vy|VG zZce}vFs7&K-5I0gsfcht7H}HG`rul+4&OrXi(#0B&Do6b^fj4=${*RX%uAT|2&e11 zmR#btEE9{PVm>T*lXnQ<-MAO-_;N2p4<;3oYIx@>wG(qbHHp?Mm6q<-dfC+Vnu_(h zDiCu=n=)b@p%e%$YH>@Jyk`JIBpkNSBpnm`2pkR}U<3c*;Y-J{JDqovDg@98!3}gA z(1&Ui@WG9pg1H9<2N1;27EP_ANd*+Q>h26=y$#ji{62yT1yHogM^qQhx1%GlOlh3I zG*-njPc3m$<|z2#&QgBKY}FGn8_no}CwD(k7$9G{|5&hM@-JrGnfnbsP=xx-Tz;O<9Bh(VGRU70q9OpjJyTO@t^cSW#q;drX z2S~h#a7B5tf`P^bbd>HBgNRYHoy#)rOp8v(PEQeAOdT6SePDTZVO4UJY2d_*%7vE> zGejO;g1c$AM#4-uGH6sZs1!NFo~2~Gi~>+g^>wVtm}uO4pwc|iFHd(Z%vLP)h4fbw zGcP-TGVX;tO1y{CDgg9qt0CmxOV()ifk(8=xD$~!pwiW`&aA^>1&$psF>H7W-R4x^ zop_1ak$in~lyZ9$*Iqb4 ziIA}-kZnF5tr4Vd?x1GH<+pf()dpr|9T=Ovsv7N^s*5Hm#Ba55$d|N zSFi?jivYhD^k9peucIkeQYytDAaCmfzmEVd$^lKL3VIDA028#xk*%AcLMhA@8JCbD zJF)?Mt)Lc@kpERUX1B1Yxg`H_(3Hsc!UQG~piz)`1*h;Iv`EJjXnkK1>q`h3q+it< zY9~mRs5DZ!$N=>(bvcMw5cCa^#@Wi}Yxyr0Ho9FD1b_nUnzEE7Yx&Sr)-&<}YL7Hv z5IIZ;NWWXnA4p|=+IQJRnTZr5{IB#_%b_9GyH|ynGHPADAVE#&;rc+e$aZQCKZ$BP zx3WXCT($onA@Ll=Q^`$ftbddgC_tBx`4|mmyqUM8uxS238m=Eo2kuS-wwbntze&%3 zP2@+@AaoqPg)3?7ZdUst@0HGanE+=OYlCohh=eieU~w>ROK0)z3O+==-erI0nDf4RRn+YVN0d}qN*~;4aBJ=Q1`Ad^nDSK}tH%(@}yB%q=$+Fm0jL?81jl3s)hb8IlW%H0UM@uo>7s?_)E3Yc-y_2teIr%p z?&Nd_;5`v_;)ex%U56$iUy~tU+Jt?eik2aaKVE1 zr9>o|jS5-x;JXBPkj7f!=?FBfSk(l5}1%LoESozQ$31Rt!z zoHwl2jlTOrYFFTowdgT15b{)kts5jTS8~@B_PcFf2ptLrka16l@0fcLtQfLbsV+6< z4HU&^Fa)4kUktku;iK?)w(_z^tOa?}wMXR?y^DN+xj*Tp!F!(jGg+ruV{y?T%|nsn zTahC$V!KU=(3DelD zFbne8lppxB)7a>+2gJiG{HtlKBevXyX)HZ#v3NI{kFc_gNB6ucKx!)-2vGpn^X=ek zx+79ItW`47Lr5fSdC|W`HI=#8u za4>EaD*BJ}ZB`bQz7TB!GDKk*pke76uiU-Ht8t zcz5|$*|G($tkk>9cIcgeGn8A4Yc54^bC;vr5E#?DdUxhg_1!k;02N7}y_o1(L(LS= z1z+nJ08^O^w%ie?<$^|@y&Zjo4~!5m!(~A}bU@K23BTSe=MdJ?BgSF*HW=a|rssYj z8OeK!&`&Bf!n^%W`?SG5TS4)gpFhFppsDSGIJp5q(6&F>7Cs5l_v;{@QDvRwaL8s< z;O1Qt7z8*O$7z@ z(SUFedEOEOHWgv+oj6a6!9_M=CSV3+ZW6#@cGXe0%i+1TR&s2IS8R|PxJbbHYf3C& z#hnu8drY0Y*F?8SufHE`CHWI#DifUzj!NJQIQ#-U&SeILWqlL!iQo7CpOqA~j8Qqp#rYk=zPR1ucIpK`W zCXix44xj%{AvJ|_VvVu-X{-<-ismBKQL|?a<-e&#jTB35?DwdLJu{NvDeLU&`|eSi;@2=p=!0IP`r_0HXljXWpVo z4OV3e4srtcu0{X6RoGw6KQ3Us@B0Jdl>%@kvlTD>O1$QXDmpK+0kKw7e+3TFhf5PXR8e=0{6^dqkDgyhst4keF||6 z&C&iOlW6iW(fBmjAwOv4yJoPlz5n0z-Y5eV3tbhDtX=40xJz|TBsAHAW7$U_;u4gj>>^y%8t&Nt^a!HGUtr>E3L#={AQ3xD02$*GO9f zatvbVV-1dG(rRe2SmEON6Y*RcV-pcju!zR$$A~yEqxl!8dAbzS!poQWy|dY{C@0QB zWOG25oP;0HXIxXmH_c|_6LV|C%Fqls!s^VhNJl-CK_@I>z^>O}NJ~VWO`^_kePVnY zNAn){u%3@)RETei&}4$MunZH?CK=IS^!Vq;iF0Cx{27e&eorC==agdRfzmSB*$FCd z^?=|CH99DUIw>?OcT)?rR5{8-L4{!j#h0H3@UQP-Mak@O1 zJ79Estkejr(1@F_A=k7VgX2iZ%<+!zn8SvR$ZXXF&I^Q;d1EA+@H_VV9iQZ(D@U`G z(Pn~3A^X60-k-}UzkVN!cV11u9j4FIvkM+>LIFJfNhTNEPuu(8H>8J`2p*Jv zcj1pO#C0%1J_w@Vq~kwwP6EO(5J*!eo_J17vukn8gA}z73L%SQ1WZKcS(T3E>V}4! zi;Dl~q=NR;6ddVX#PtKN4<7!Hj{k5!o6NeV^MMb*k|#c$r$4|R=>06jYiK)vDE`20 zJqnGnDP0%giAX3L`KS2e0oIwNCi9CAum_#LT@d-_5QyFMX&#U?ofEs7P8$Mrz12nx zDkWPnKPr~5(X7|mqy42i|L`IT{^==K@|1h$(>Ie3ZmsmO_WrUK01cL?JNNwAN5B|u zIujFDWY|JJUDaB^7&xNq#J`@);@O!e_@8sx7-vDYDB?Cp zxy>e+fr{`R8FY@l`7p55i-`0$2=J1RG(Sj7okbYjEG2&`l>{KTOQRsk4;L`(MK?LiY*YK6#OKk*}Cz8g>S3d}C@I_O2-Gl5nuPtO(H6QYK z=dm}~Z36YiF^^@tq z4Q*kY9DNN;Hi}bnl;4VWe)|aAs7)aQid++55K+5AKEClBW|S^Y2w4jQRlR2Ek)(+E za9tm=>ncPQ?)11I;XwZQa+E)*4LI(=l|f0I&#jNKf&Dw})XX;dQ2+O?imu7nlPR9~5TcI2=Rdg(bd_eRLbDgmYv^Xko2M^63g| z87#alEufUfKth%qH5ooIakmr(7TDBPKNIopi>CXA!58W}1cNP>_-9#IjVau!ar{evIjnDn6`dV}|!RMSn3ci$ugxcvGWMvMNr zQ%xUlNPrq$ZnPuXoeKF*RF*uIcGCL3(uJX*IHv$Q_5v~{Kr6Hi=@0;Zq^4gBt7keITSzzd(oX54M$%V)D=hCbeOb{B|RqXV@X%oCbz|X50tblEt3k}sd}1J zJAf%_+0<}IaA2aJJat(rMa#vUR&=l_D;^fBZx5_~TTDh+Ewt?=vhl|CfOZBy`vmL4 zs;l{pCs=G`PpH-?Piw&M^iA?i)qr347oSlIzTYr6Ur>q*`TKlrDI36cy7_@pmdm29 z@@PBjulboL*;%LVWw_4}4NH{poi?QxErCGXLGqCXW$7o_QIk z!c2p4$MC9_chE2UzpGk4pufLVwY-bJ4xMpFDRZ^NIUb}#1-z44=QP~mV58F`~i19Qq6HjW}L+QY_?b38ztXtvIRF8cj- zi2aV`cD0n+qpDgcgWXWo@&a$a07G{0q4+a+px|E^d}FB}B>yPth$4B)J@?SM1zEwy zC3m3Zq74b$xqx*>Af1lV#Z5qK^KibuXA8Y_6u&P8SN%7Q)FL{iN27eh0v6rrmpNhx zu%On|fL0f*e91)i(pwpbp>`dROBm2FfFzWCMFVfCs_UUs8ZyqVO=> zVC8zwU za^HaH)%r<40TY3pN!I|fqVnZsu!`!9_P@AO^F*PMk6j+1nud?3qd{P6!Csfv61b^=O~8(1^^2|b6}S} z@)Y!_&1J%`mwk-rvd5uvtjX2a?**bdx4G<=q=KxSfZx%dJC?af_<7;w>6*>Ofy?XZ zKQz*6bY)AW1LllwOf(k6VxnMRgs-F(eCt}HxH=3CkqLbtwRfl5%@VlTmid~uK z*+Wm59z?N_ul%t|{zGmO-~QO-`AB^FGw>)QC{BjnovwFdCBR9@&>@PRJ4Yw4r0Qjv z{&g=C|E>~mL`+%tD(Y9wGU{na&kgu@nM}WnW2_#T9#yOlxbVBS^T*3rm+<*;7Gpp} z;newjbs6j3xe~cRphIT~js{8*%(@&QR%egrUzD-#BZ|aGB>@9!#DQLUU?)a5${pX3j38LTIZKRG!vbjX9FOt$25Rs_YL+ZmB=T_P$}jm zZGI7dyPR#)l=7JsY@9wF)!<|)5TGs6rM5eSy-cf5{uGgynQ z{6~~w&+B=tlMU+p1vEK{IHOB>B>ak4<<5v{AHwft+2VOD@cVC#c~sa0?~+ zj%>zn!nh6RHi&e+O*uIl?OGK!XcX61GD{S#6(|tE^Xx;}a=JcCnW5+RRkG-GBA`e> zoI>nq(LVr~f4VxRV?YG*{F379kh#fX!Lw~eaJ3>?739FMV=BpsXQ=zbWBdSW(;VX` zD_M_O9%Qj8VQLnzFCuVEbb%%ZdTf?M6Z50WepS02ph$vo^2p+q<_8ptKo?PRBx2A( z(r*RvWD91tMN_0 z?S;lSu`bD!P^p1eb?coo!lBG#HWct-!8?dI?27DyJ67wh}&K z2^(noa}~%lyPTfEH9>eo;QW2co7z*9f zm;Fj$tgf1DE#aS6vmWhACMACD2=S+5oN{^zzgEq9u}6pSu1i@@!$(61M)iO!^AK)X z3Q1=7A^hQ`tdDd5UmEF8q|1T7^qW$4i$=N==|QA$q@GChUf5YHP{^SA&=NK&IPz-s zvk$py&*8+C93oPSavJV9FbcIuM6gIOj{%Ed)hZ*da?>-cQ#-XF;vnD-H}(ZS0^WF; zelV|mhIQi#tyPFz~(g1u{6&>cr%x=do@LT_cGQ!rZYH+#5_47P8DoA zi1DTMBc=06(;0V;Ya7EuP8gyh1POb>uaOQ7uf{EG7vh+Gaz+NsS9<@O75+OKy(Gb?CBu2!S4O9cqOS z&~booz8X@_YG<;exi!ZT|S3)%-*a>zC9MUr7ulPct>@hBDCO1Mmj&Ps#`%8+bcRV-MR! zG$;-jdYe_$3lGGaQ&Kbe1etZoodwL`ff9!@75p2VC0@Gf;9>&rT$!h9a3n{qS!N;^ z$q=cILg6gs`V`7&@~%`f8u#K{NsrAcN~Wr*riLl}T~r-$^TqZl{A-y-W(a-<2MTH!@F$d zn+!h1!@7=i4wl2<&eQ4$`*@lQLq$hdK zgNEKt$U-$2UPXEXX*1G0NV{=``x(gx#$6;*U!(-2M5Gj?bfgj_2(~W1fV2Y%e2j}t zAoL+?y)*=AE)rPFmtI492MO}lOVIN+s!(qp9NSVolMk;3{oW z8Qi|e-S=$a?_rQV>cks;qvc8%d2E1`Vd!9NRk5bs()lG1>m5xO3sUUrK-d96s_^*j zxi(L~%zLe1Gdc>SV~-(zki9MOgKWx}0es~O)~Vz3KhUi$T72pt=TKbQ&GCHq3fA9H zk4ixH$tL&H_59ZrFy$%XQO~l6B1#e)$|D5#zlbk;mbuxW6FhPyTNFiPfW4>3@Ddoi zxR}D?_QrU=VI><76!fcy4S5&#n5{hgIToY2m-l~;-C_Da+*C0oIpDG3Znvr*pZgq( z4Wr{xF3soibEL!qs#r+0YA45`%2c z=f6J(nw;t*XHq|E;TRgOBgxx2jz^79tB6(={Ff1k!#4%IHC$qzvHP^;SAzj?`|O29 zBSOi?3mq%S=|OUlPq6mj%Hy06k+-3m1QB$pdKkhlOwB6MC_L8uq;-;a+Zj-v1&XyP z`g2qYB3V5iJyYYel*Y4xHwN*>nx&jq-xQ6wgG5X)Z~+t8le*BV?E4XFiFGd8Cpt`A zl$Yin7bsElKh#p7`%nbD5@2%UBbXJZ+RPG^C62Y|hi599sR%Z#c4c9fQWAV4y0T&e zYR^*g)R(}5j5@gDig=r>zV$4P#>`ojw+W(dbr^SN&k6!1#3hZs)oDF-+FiO3vsJk; zi@&)FP7HFOU6BdZ|KX?VlB3>e`|msIlH>gN&z&59r0RK8!Q5F1b;+>=-AAfGQ%HVy z!H~M-B>a-#CPVN;= z?o}tbE^@RXJ;n+xSs`YW$mSxlk2kno2f|!GHfaw~Y^k&%gATyIh3j0?I=AH*H1t86 z=BRh{N~pZsLo1yf!OQ>=6e7SdP7{EEbD@<#?F&D+%ta7UxsS+fs09)2hc?5@heA2| zYB-*1o~2KfKWfo-+#lif%Va8mbrkO@&O*0hD6SpX@|A1gm1mchwhrA3ElQo`ydHU> zTkg?)^a2hDu&Wat=ga$mSkFBNi7@_tISA}(D8Y(iQHVTx4p`{C2q+REaG>VWx4|72 z!KAV?m2Xh{&2R{bKfx&S5FjCh{eg2@CwTE7lwpp9c=p&xZgW01GC)!N-0JH=vCT|w z^IR}(0&aG3kA^%)*(?gqqJoYqRPZ89inHY-qSl&VxjDix909i*FmJ@}p%C^?M}ydM zUxWSbcJ0So5df?KTg#n$489|=Lc+2Omxia%YjhdiB>{qrsNkaYfx!my(e2jX=Vpnp z%HhH9tvW7e%u);6{!f*Xxy`eH_5#C_Gv=s8j|D%@kvWldAiP&qQXikCCl&fZK!Hn( z#S;1@IWce{?aSsKxcvy3izwdL_*VRk$9%a zAF44`{-SxJi#@dEGy6!mG9;=Gw>!(H0!(HfYD@~zz|E``{AW81mS&Tw4o;1vcnE49 z2>zD!a(n+IozVh%jAlJr?log?dB0JYx%sH_~j)aAt0!Zrs3G`wg| zyeFpY2cqAI?4*Knj~t~IxeCZ)#Eh%K2zO2iC<%8)z6>>gh%8ef1DPY+65Kg4as;^= zO^%mSBB;a_krMs>``8HI_0#m~VG2wj%^#u?AR&o)uzKc6r%No5kxK4jdvjE)fFdzmI}ra}H1PJ9qYnr_urJ6!q52XW*; zH5gd^q&PEhS*v-Gbsx2I74#sm-f$oSvGbt=aEfFz$x|4N3ZS75)yKgHFk2XHMxCXJ z@U@UPPu|YYy$A-Rp21OSZ9O0!iWKc2@NdT9gh>U~ZO&81AlNyK?aa`M3Q4>ts%!<0 z@G!Z~yN3`K8AJ4U?WN5_ZY0(o!{2QGT6$I^Uxn93f^zq+L}uy|DJVR+AAPYZ7fHy8 zE`{z@b!GC=T;rjSc&F<4f>1 zH=e45$vt6L1q_j*wOgoEo?uZy9sDwRy9oO6*+NW0gn0&!FV*$u+=>CPBg!s%9VLp} zDsxp9BPtRe(IFTAwVhaL-(pZ_e`>%-qTJim+;^$D!_Ck)%PQ6>^TzR4UuH4=_u(`0 zX>*TfqfV#V4NqnRp?$a?BSyBeu3ZcN>1CFntuVIm_Ul=sbAC4gcr?XWs^O=EtFhY` z^{5bazq3~?s&UEFm~Po}6kaIk4e+h2yq=;FJmPnp15PFpy>c=Ya|7Olr5^2`$9*Y+ zT8Scc!Fu>$6kpeAET`K$bI)Oghm(bYGR%lVtvdU9TQLck6f!BXDMz{nyGl0^pqVJ+ z@*Hem(ZU)-i@))(uE3E7)dQ;SWzi&-7OOs7Wcdb@O7#aPIR*7Qi3rQR>^ewpqzZ2~ zK;FCo-LW?(Vj|O_9wQ83J4)x@#Vz4GQ2tOZxp)^+aqz?h^kJc(yOsZUu=<{i+>|G% z2N*Ai)j*SpQNcESb4iWpGWda4vXsv-8tNJ5>oOuU0?S_RA_kAbI=Rj_O4*79Jo5_c zGEOQGZP9d;omHO49NHA+SwOB_cat{`L=1^F(L_KCLxggbAmNAbp|7&|j;m%+6?B5Y zk6jaN0rHf}8T`>#Str9$%rwzur$B7+m9GMij==6BB&6!RJA}?V5Rnqo>b-$E2BMT) zknVnk=y17Uiu;x0^aJf%t?soA^xHb!z4o+tNycU*qC!6f;&OdV?MilpKN>=|A7BAm zm1`~3OWtP#iTt zE;&9D`#C^S79GWw$x=3);VU+b*sjL9XggQ=q7}|#pJ(2Q@;}HI2`E%I8jV>bC zG{z-M@BrU4=6HT(1K7Gk`#-f&rR~VTrj31+t0|0rwRM=(_eu7!eJCaQdK3|R!KU2U zL&Q6nvu!`dO#+OYRQM=2CkXY6NZg=S5!LIup>@FCqFI817y9!LF$a3rUMtN{MO)Y2 zBK&ZIB9id8vSJ#3HR4O4E5_9aTt8|OS8T^CWAz!TkCZ#Qft-{@?bo%&YGP_q3&!f# z@r$B*fa`#nCT?EW;F!V%HS49jPc6-$FC2`v*JyU&WX0JO;^-L6X{ZfBHQu$z>3a}W zbab2J$x(erDarq(Ne0vXgTS>szS&nuK6Jqf%yS$*+NtK zL}$pA;{fMt;hLeqXL3^g`#urViIY7e0spKZ2=ZBRj!Y3_ZVSPnON_Zm>j)EfINz{a z?-<_QwmS3G>THUuw)N^@|K&iPzMUWnX@)FvPP`MuR7BNJf-ig{(m^AZGs=LmHgpu5 zmikl{SoJQ!dJ}G|SDihRJrdKt<*Qr>PIPdlxfMx_O+KqM$8FR!FGR1B^M_N zBow3sL5&SZL?5DxX-}1P&rwQ={BqWaowFs@J|?c&pi%EgzB{qKP+5OO`Qw|}NN7f4GxZJ}S`FQpbh^E0Q%sLw)1ZV1@dL}&iT=_g9GYZ( z^ZRUSI2z|f4}tO87Hu30=6}oA*de}%BW5k=Y8);ZG1PYJday?j!KxpXhI`Oh;ev&-Ds%hs5nEXQ(b3pdeSrGqHu97jUG=9Gok)fNMWdWJw`mdxWjC(9 zGiM*lQJ~4_FTgL+jIJMFy@d^q`~|pOy}Xd|qrRr{mo5CeEzHdDx7%ARg+Kik`^8zS z$F!-+q|^r?3qg~(vm9_WMHru12vGOORd4LUBn9aYBtuUo4Mv)UvGQq7WF!gIHvErr;g7vd-E%kT8R{ zvYeg+CaWM{kBfr*JN8l4MZ%?pb(S00=8yB&wn8uP*GYWeRyNW$6MfMjN`v2W13L^C zCb)Txavh28(E?sU&FlRc{0LIA?zzSrq5HsB< zbX)ZS8i~(%m&Nn1-etXKJc$1f{ z{HIQos>#sd?%!ZlzW7`~yL_bv8j{GDt?d4sXZ(YWGK{3#5h=4OgAVL`_8%-o6FWDK zNjFvLo(i0`8!!cDET$77MREN1_gGXnxJEpm1?`f2#OC}061bpFQ9~cLQ@f2F)$q&j zvyvW>aKSJ*kVfHe22v5y8l?06b}smU6=_(SnVg z`L7^k^k))`Dy{<0{FTB7ZfCvq%aX9;4M%i*`gWG5>A|;dXP+V*-hz+VlYGEV1`or@ z{LURLUH>o27hJ6S{jHl%xj#ROT6xZfL~QMVuv zZ?s_$lPpke1~FYdfzPaEPY#@wz@%THbF&10S0Qah`U(kp}4sQT1 zg9q5F$rv3ri%^-Qdxu_S8pYHKP$BLTSWaLefBOml<`dSRy*`Hj`3Xy6bH?y-dsyEX zLSb-({MiEL8|KN}08#jrtU<@LxeeLML0++kbs4=Kwv-?peH}fy^@!U*t_I3VfM?4wC7~fgNfGVPK+maN%x# z$6gjcsPTQ#Tp&;>Dkj04MGu~g##%(jO+a7^F*|>MFYDTYc!wUFrt-(;%Ad61UoTs^ zmY>-R;rwc@*#|}Gx!96WuhlpbAj(oM?MCgwzjzlMR^FXr1Ce%=3Gla~2cNPJx9(%8 z5&u=rKIBRF3!F)^4tjfw%77tt4(I^`M1l|~6GP)*jox*Af@4~tY-NoHb&$&yzXjCN zzVV9xWOq1k{GX@{?1iKhBdoo9k#?%){8zYkUO|FQ!zC>eG*vIHMuHGa zg~{RMdKzXL&4ZRM!{l^ZyBz;2NZ^o?UTvc}AG(xeh-{MuD=e#+`31KNjHEhIhB9mG zmM-U4(2?E^IJA_ry9id&VF+eb?uEZv*KVp7@hc)+yO;3^hghfR?WBh-BS@A~qt1ie zU{m_PqT`PoV!hk9T={#U4Jg#B?z~v~9A(D8@B^0By#r;zq(@Wduki0tdg^MFhSiFC zFDFi}Hg&F5$^Hk*c%ojCr_L>`oA|X}Bbkl+rkY^l-r(9R;KgpLSxTTNs7vuHPqAO& zX@^+?`{o_)ILyYR|L}=`Hcnl)pWXg;(jrY)Qc|6?$*0 zt;cQeU|)$A!f$)0w%RDRA2bgFy9xfop;9~nWX#jwnE3I3!aw_euJYbTn8msDEp!J& zGU(QCKgLgJ(jK}= zTwPKWs3rAg5>TWfG;$3{3O%NbgrQpJLtrjd;R^Z2C|~_8BT2i6YXNW8``^2*vD-Sb z@jV_}&!UEeB9EZ4sTrYO0rxkSxABswC#aDG6{v&)(+ltMx%F(UrYnD~o@Hph zJoq+m|0xttYxq$5Q_QnIg+;@c@A2dUO=M3D`j>a99|2GR3P|o-ud^!fvne}X#1;igIw*!f!q?koq65vCkPQ}ffE0x$>;*MvaApOVVC6&5 zb^&fs#0epr^w=z=YZIUR8Ox07=Ef)3`!oHdoY8&{5{m~VvN14@gl+`CDl5+hY5ZZwb2=M*gG`3R-^h#+V|H^iEhcM6Z;UuFR<7y^2$dHzuE9^gy0@s z&;dderlR{&HSis{V;i8vyBn3ri2}tARe)NLxS-X6v#^}*Sh-C(%43_L-XxCX9Qhdu za||0XS~_9oR)(|g9>V8`^_5Nt*m@E3KTmO86|bP-3Os(7ItrMonB#rs;os~fXmY3I zltGa08$l$I@-od0So;NKVQTx}Kph2m)~i*bF(~FHQriZ$cpbe&$oYg39hZ!kK^>7~RC8)tUwxL52yJ4EMSQzUmmu z9=4Ju4MI!#V6ZPhJDBZI*TRKD8cf2Lm6S{5sEumRyM4{NMyEieMP`PB0hVyIwh%?S z8+Wc+$*o_rp7*R&S-#2!jG7i!pc$bi+#?7iqyGrZbv(poWaQO!9yJB>ihDmaRC5%X zH>{FXd7Sk0WiU6k`T4!yvgn90!7ppB^54H^y&~ELUzA_v-M?XdBF?GIK&$e=Rh~gF zJ_x?BT;+@C#ggEQ{#W_tZ`h!SslgYHtNhG2tg9Zri436Odh@7n+1(Mph}$H3)AuU3 zeTz2t249T4%Afw0C1}k2y>FohwdW^{4z6Vi?A>Ij*fm2ICQq`{u@4c$>OW{GO};Mj z=eXw`_z^Q{hp=Azb++=L`bl7yHa4YLPz_L)#Qd!NiFZHFoa1S0IB0RtKNmz!W?b#D zT@bAJH`FZl%IoQ%dCYIqR{4<@PhG9OKk~=_#iECe=+r{98kqTmKhhcsjp*#60YI&w z>N*J-grVqds>l15j_>{#>*`cgt{`w~3VO7F%GGuu2%0ffZaHvC>w(_M;<?p5@o zur7_x5(oj^R^XRo$S2tcyIOjdh3&%6N?(NhWq=Hp>-)&`9n_BCr`_VE2^)|gXgZ)Mm@Aj@a@J6Lv0do%gBYh6MH!0hG6`(6O!1?+lZ0Y&u z`0wAbZh^C|SX%X?BkBPW6c|O&cNO|!3J*IZSj&V}l}7mY7bEw77O0xP=lEZ_b;8>z`^ zou%JxS;n5_F%2;02vs3i%!NS?6`Q)l#?PPq0~TZN`CT_?VNd zNB9q?X;T6d6Emuw zRC-u<@cpORz`nUu8Yg!F%F;wN1bY7vy${+K@GC#DZq5Ub2$4%wdGk7CcIez;zBbw` z+J;ITyl3PiY9nYub;JUCM5O{Vm_*o@oNz(u3B88c8YI5zM+NaxA=ieB9XYMU3a2Wh zS#+0%Rw4>7UHgdE(o{_F4VQm#)`X^x4Y!9N4yog6v9>4+NLM(;cvxH>4N!La+*5um z3n9u1)Hjr~-V1go2_g$!uqZWF>+xh$7D5&bXH}wjf|}V2%L~a1lXhq+kxRzEf@m_w zCY*4DIMsaGKSbEOC7&W^<@gDXSUhSfI*>OsGOSZV)nnWjgIKYMr|bh42F?-bnxx1A z)M+BLjw>*1N+u18*%12b5B#hZb}2y)i9NblS47&eqK9$|t@>PpP&xzyB@Vi^t*n=i z!Gy`S7Uj~aKvO|k zEmqMq&A6lzEqThf{vDV-vm<(iO-e7(-W zLPGgV*6jlI5Km?va?Tt_7+zJ*329NkfEpuB3ffsYb3ldaJ|>a7I51u6R6@KmQ#Q}R z87n_wa*wDjjwAUWri{>NO=k46cBwEJT$ZW<3)@uawvED(2xUR-m#zc(?y2dObUJpU z4J7*IIygs8GieV(HOi_y)eG~jZcJ={o_q-go~zdKw=S_>>B4W+bO;SbAi=lSg#|)r zpt;}&n`tmoGSYk``25;@KNtl^$oLsH&Bv#KaKh08_)v~@XyO05#D-|A+qQ7M0=@sZ zH9Q^h9LHZ;jizw`k4w{D`2rjUL6G&l z$PpH@GYh@QPbzHqG%zA;@OMy9V-5$u&XAoWJlW0@IuoYq{mwC%!aHj+;58pMy}+ib zr`vmlkjs?rj09uM4fad3+YA$Boy4E=vRG}u_APw9m-XQX@zi+~XbVgc*+&H^51fU0 zYx#9ARH5^E&tKqix9YI&hHzv9K_clj6ahTw$7}?Y!pi104gcndlh{*@g7JoE zdGMr???wP%IKO)en#&C-7k*zyukdegXRxKjF#F ziC@593)I4LL0rI$Vm7Wz2qROY9f$oO8J|giREV$|vYE873_P7GCQW*pNy}em(no>R z{R1Zb6Dj;77(u{jJN^@}*>jonHc}fb^Akv&9%Rx7#Y}?Lymap)OzP^ykCpgge3VId z)!d$b_5iD{U{dc^DQ+biMJj!fNrPU3&@qrM;P1)}e@kzo{KIOxg5OIwLmaS`NsHcP z(%gT5?~ite{DyzNL$CfB_{&Kg`(rswI<=2UkL*>` z1&nhv#`rGMr$`Uxqe-M?1xz}CbQhcg&p}#?)Mh4=<|BQN)Mgfw#vmo%&!qp%MP2C4 zvq)VIFzHeB;71IBey2Rjq!A7#{p4Uy>Bq%Pnhgb#JxG(QnKUnuIy}Rqy@B))Dr}wZ zzL!aVPXiGJwyhhT!8#P_OPD}H;YuDGg!`yi~d9gmI=HKk9yDXpej!?@6E@9;gXrG3g+lp$al)NU& zW7Y-kM-~NMmFcj{Je|JIt#N0;xA^J8x@5HFLO6KGOWt$EqCbH;oubm9`C=~oXgA1k zu3UfJC90xlptP0rOd8c7%|NTcmGsl} z23h^?qk%lu1RP_q_%1)G9&Z*%k~)ID4Q3H)*XzhcA!SN7I3YN~HCBG$c~t2M!vNcb+*C_L#ajdP-Q=>YqAgSpc~QMnFtto5>`o~|&_`I|!L69+r&U>pM$m>BO&M;> zL|LO_n46?H{I zF2<|957Mv4@r#(wkdMN`9xzB0g_(d)g+lMDBFaDvwz z9~GIXmI=2qUvZ{DjAKKeb|ppSSrAUTxorZFKBbP)9+psje><$>3T_D zxma_Zv_6P)5AZsCT-qElYNWd-Lbf?9u8BxK%R4FbIb6nf0_+?4gCV z8V&h4(U6amBd85gU;&b+aTi-;Ae~s_iVmp7WjP%$CLw+WEi#B>V z+4X0B;`rSA7Pf_GXB#1ZGcf!As9V+65F?n5(Nd3ZtAC!T--$W!rcl2v+gz4ye!%9E z3{>xeI0HHuA@B$U?WyxH2;AH)AT2uvYT$G{I^_HK|6lWeU;@#+*F@d^TZeH*+?);n zlRE$1V(iZ^S4*6oOA;Khv#0G+=LkOJ(Bm)ZgX)Sf93PN_um{jWaxA=e0CLFhW$V?? zR6x02cc(A(q#=IL!as4JnSjWMe>(mC#uSao-c|lG00CdYD2!auiFVSc%dNhI(xj!j zLqmKoQZu>C{NQP!seUM{YI2FePM*<%`F=6pa_)e z;cBnI`|1O@3R~Xn;OJW{)nqn8Qc;8- zTIYEZD4=C|vcTf96}FkdZBCPH*;wdK*2{TdHckuXK_CX zI)W$)w;m>HZhyi#tD+4Ff?6jPai`ow%HiIh?h^nJAf(7g+XLYG7GSwH(0;!~>YPWf zvz64q$5+61Y>mhWQfQJR(g|Q*M>jVV_Q`z(Y9YoYP!fW3es|_-d72aEEH5A*j=$XH zbpGY8tcj@fK4fp-@~J%mDGlj)O>>R^t_n{$9Yp|8I3*$xyEwerH#@lXYTo0alZ5f|ZWI$~%4zZNArGj_-AG>5$knQJghIn&6_6L`H;OGX1E8^ok-(-51z3sIbBVk&xlOk1LzdJd$-lQ zcLVQ4$lO3aVoo9-6zLA$QnQM&Pjmn%Zxc`q!r|Ge&mz8Vp7OlB(YY#O6&2`rv zst~Zhu+`g$z}r$QJPS>E|EqzbR<%27Nk0VlqU8PsU3^XkM<}g2cCEqnjdbm#FRV5R z3lBD@_~sVjOoI&viYGmh@Hx7_ArUJt8)3mPZ7jz3E!%)~wER^bd5tUal@d1G0!* ztL}aALJgzqoAdvct)Jv>m9N!U)b0*Icl)9&Ph#3=C$-Qr!0?HotuWLgcwxN zK=@Re7X%iicjbRa7Sr_JxLE==TOs=2D*;+S^6NBV?elbA?cXvu8*#UH452$ku{lpk z5GMHs@vsXnZQ+_6R{2xfvNd_=K;<6#*KAyV4%HPR zpgf)mBgEhu4=}sMxKoS}Zmq}-75paWDSs#`nVXB5z`>%<1oF`+MLs728iBeb7t{4> zUiFYaSJ)?A-*^BAdklI(M(|9|)S22!%;0rU<@!z*|kVehOz~-D=1XdD2oDx)ItQ+ zO*2i`$~;lRzgv`0LB_Rb7L8vjj{@?F00ydx2G_-1osS~E!St0-auhfNk;qWfgFbr1 z#}3F0y@r~VarABty%WH$whh`cI1o^f0w(YO`;}BI7uf0q|9X2DYQuG7WWef8G-6z$ z$;Uzxr3<%q#8RLMbA5*Wj%U$HfOLRm@?9RCA=D2X9iQUC`_yGD>^^!8_Ym0C^tg>a zyn}zxsK!Cwv^>rsG+2XdZbqmCH6SdmCxPg|@XM`64>sddIt?;M?-ns55NmqR6udBc zW>XV;s20hN(CYL(d;A*F-emYI8qMq@Wzlc7K2KDo zY*88~9)sF;7g>x$J4QtFCBIVT`0|xrW7l4reZ{qhx`e5$vPlx2y^HWSEimNeMtt+m zvvH4J+trzYFVwQ5(|_@Iy@T;hknrS-Lz~&`I&fJ&;q8LA(9+G&-6ZW1=>mI%m^>l| zGCsDt6hJK$VD1)yy9Jm)v+hO$qFH(p&=04_Fv(MO55Q0s@#g`k2CN!=Lfjxxoky4C zSvik#h&qfrqL>;u`fs-j*B&YbFCG6yuBnWh`*W!Z%5xL$aMud*c!YWJPVg3vBPn1) zRv>K%G9n>O023&1T!Hi(MRf_$>@9AzRT(PwvwpZ)xQJ}U#K}@VC=<)<^-o8^R!j%y z-&im>_cY`bK2N!@P)sSsciw-kK`6+A4fAlIo?5Dh_%h$n>F0gxZ`J;-Rvh=)$`<6L z>I?oKy50sps^WSd-`%^A1%tatlpq0u291ao1r-TuUI+<+$ZkRsQBkaxrc|rKuDm7O z#G9?$TvzdBy}nl~sBL{$6x3*VX@W{CqHSr#iY@BhF0Hi17!le3^PJgDLVw>sd~)~R zc|CJx=FFLM&YbZ_SE<|(mg|s~H#jdX=Mq}I_nTec>=sFSVgXZ>`;1K_r>^jgkY{AX z1f`7-e5*^3xx%>87u(A<@5X0YAMS(rW6M7@%Vbt-b}l+>_^Dg5`F#Tmgi8E^8wusA zWIeU}f7AQC!*Nk2q_@FF$V!Xmc%i3jW!6j(q>YK!Yf&~7j1<~sWOlf{1nrPvn2%mI z#P23($4q2Sn9MLI+xS+{*_ThhTcbPj)hZ*AjmxynSf1qP6Zr=*e--l|%bzQ?RwiV4mBp4klSecgJ#Iq4%#TX8F zbZd5AQDwfr?J=&pQfA94Fvi!Z6j&$R223O>R1{2+`zE~^bD-e2qVf!;YchlLRYo%u zv{V8GCxpeNbCW-9a8Ab@S&%py0xP%>Np*rzQfNj4e2Mdkk?P@al#FI+P_@D6oR1P2 zA95PAR7=w-=BgA{B@gu!RbGF~pv3iQ%t`YBwONU)wx!8cb6YNKSvD$-fD(N!Xi~t6 z5-DcjC3kYP72|s-lVC19$$uwuZ)=y-qc#sWf=d9cde^L+tO|zQemXFy@TzcJGQY^P ztW*V2!#r1|#B1q%OOmus*R>WmG{v8JnOAy7fjp$BGI-1A#5)Lw*-8dngdm=LIjBy~#>=~gMQCAB&C z*2}E)bvPqf@RZ#H3r(q{Qcb$eF}B~>momNWmb@3qMAc8Qq>3WB2gk49dWxWlfEP(y zm!2VjrHW!~`SSDAgS$95Wp|M@AaZJv)PsdiA*7@~YOso0R;IerxDe|)*(B);snL|$ zlc%UA8v3fapm5-MU9lL)0PdB!i4(LAAz%VkK298PZPOKC3ZOGpmt}^(fpQ`vGi5x* zGLkf_kiJlL;quGrX&jV5v-EXjWTVT6@kebS?i0O*b2>ZGQ_U^khzNoB2i}t>WjceS z+?s#uWvbtMDxeX`YH$)^q|8hmm88|H5K|PI@4xMH&Xx4clIWaP)c`Fx*NnS#6l!#E zz+^AWYe@I7C2b2if5edz!gMX*uw)n*iR=#&X3U3N=DcjGtSW}mmU;cww4wU0We}yt z8EpVUgO-Y3R~RzV_!=DDlrQ2u);D{X1msmqV?3(88@p7 zj~LqyB)**k1f9_MMguIl53GPixOFh`PlQp07^Qvjz-5_RHY?81_Mt$u_hW(EiZP_+ z!t%%!6itNdT6J#WtAiO878xX1-16r%78yb;4i_vQHvmDYDC5vp zOI}*0xgxJy9h>Z5jnu~JY+(W>C@y4!ROioRHAb58`wbtr%m?7{ja>^P4foqy(o%2j8WSJD5UWe%Zj{T4}Wl zX=0HS9yz5*s*0R!bPz$_rs0;;7Mb(|k|TX4xz?XtWAc^%S8_Bm zUAnhF`88l7T!|{AZxxiNk#8t~GU*5Rrys=RqglK(*q4q$=jDedr{_VA%!6E+2RVoB z*eLA>(#^+a0gkd*7*#bmJ!Hn725K#NYVV*tkGkSc+hJ@$WG5m)){Db4t&!Spcbwlw zhaelXvSTkkUExs;PJSaO6;(RxD^aZ9E#)Qsx|M~6p7UsUoO1|=f}*U?Ev*tQFr(X% z!isIbj&;SDyg$uPRhlA6vu3a=Bps~@Tq6W7Gr9};%=kx=o9~vqYto(((jFd>+-r{= zt&O6BZm%^v$E*w_F{c0jlPh&4rES?oB5gZ9F|)$ug1~K1q6jZ4uToQf{v)(jTn`OO zOz(@s&QWOsu~+m3FrJB79h1uF5Q+tdS^^@|!TXn(9=M9}Li3R5dT9usTAYY6zvTy+={wA0Izwsr@ebaTh+x9bYUKbZn|0>Fj3ffaX-7u?f?sPtt)Jk%yvY~IYzA`)y;NbgOSrsCGCIXFLogZpNM5p4GKtzFApvXMUU|t$gvJ42 zveh>cWFTV`3rh}3MI}Jbg|DiU(zxRt6pQ~Vqy}u&C-8~|$(OXK@o)z3OCz>>oR*WP zI;24LlshuIG4LC}PE)8-T#z$PJ2}|EGmV+6L!kjf{m_8n2`sjuH#1pR!y1JKX#cqn z31Ek!%v4S*V{yRJJQZ7M+ndH|`T9JUbUAXEHy0+pC`Bq*u-sf2?FqxlgdjI@GFqrG z>`ENZci2LhMDm$}h1m)js(w%8dSvS<&UO3aboOkU z>R@T5ui}i}@;9$l8L;LL_trR<|4a1mYD7I)0!?_7Nr>`bPLU+nU&cNIB_C5mktIEW z$;2^FR~nX7z2!4pL=1uA6$PZ;LTR&g1|D=5CeI7tA&9!ix^y7tHae zXJwcmPfF$$(iXkWgpqeM{d$%)59WEjxC92l4M>3dWH?|A0tk9r_-@fZACA$4zj}05*AIrm4a&4T$7v_(()I*C6F*=*)xMtA z`%r7KzwJf*h+?pzg3(jbN-pVmi4G^tX}(oks`}j>fkO)vm0x+J$OY&Sed# zXPqM1!3@csFUHCKHf&|FT_%=`=47LEg+J1Q&v6*}3tk-BfWNEs3@tD`E?~DNV9oU4 zB4r4p0}zFyjuPB5#mV=BU(1wjbU!1w2s!o7;h3Y?{=q=`nEsjR&bE9-QaP3^SRzXS zYc5t7Fr&;m-&@$cyV`+iJ?${%RwA8~>>V6PNxX>%GD66L2AI*gc#5v8lHCdgll-7b z62nm;aKq&JmWw&qvX#|p=y%AhjFzJ;2)tTa33nyS2X(0R>LkJ?Ji8Q~$t0rb@PVAl z7oGbysv==Ky&4LOk(NpaG4Pyh6W6Tt8nbiVm;kGN9#~dqBHPQd*OtO5q}LtEP4f)* zKO^%1IBj{HjXlEF?$wLX-1qSiTrUDd)(n`u;1-37sYPWzC3qT?G z7HLZ27sxlIlcL6Azz!{*v;cDQX{sq^Y${(cOt2J0`3_uZyI>#0V0r3LJU|JBZ)#SJ zZ?B15OuKetpAYs5&@%QVgzYn-D49F`R&K7Bjk97>N=bHma4rVvTG?}Bh1zY#nF}(P z09+OdDE*T3>iuGj_}0~+OtgD*K`KCCAtn0;QohWOC+e}3RbVOG@E3+EDtL=@ zaK6GjY}Cqd)2Rn02C;clYk+UmaP9eN2tYH(SrvuoG$v>3=S@{fyiU5^T@_we^3?8% zshB}v0Q`kGld{B%Y8x<6KK;_ZW1@DNzP4cyJ8uk*!*Qr2vhgApVZg0c->6#pTWJ@B zVyMgL;1jFPJ4kB9_(8Bs&{KTJK|ma2B@^MAjR~t-=8wL40Y=C3c%(;%sK%hx&t>+Q zJWB#rD90!XxQ$Q_{S5n~El7`*&>vmS+=V+KT)F{oOEe58r4m$l|8RR6z8sWhbnprI zCKA3VKRB)+TxL~eI8TzV-n&Y@Zr$0x38Ov6IGhYj_z5A&zK|lyI>~OVgV`ikBQ9Wz zfE7&goYutO&sS+dvq~1hAKHv{%Gkn;X0x>T5l9t)Q{Yu|LEz8=v7^6@Hgv#Hh9u}P z``?!_3o+w|k;VU7j6cR_XY(cs+T8G-_8RE|fB^fOmlnuA(RW zO6w!oxacHS3~4aX&2Qkp5?|?tZv6+X!v-1e-~{uF?zOQhL@9_dc4Lxn%RW7Aa*yQ~ z(OuC?@r2dB7?naTP#AQ6_Rl`5f&EErDrQ6nA#sI|Z|RWaSR_9H$eB*O*63i@xtdN$ zzeFlj)TqeCWIY*4nug|8{TK9K?HYU3;dJBZkNnXyP)WC>V{>uS^(~2$5R|+zlRf#P zbkTZW9c8MHlOni<*p#u8?uc=R`l<+@Eyce(rN4OaKPZ1>;x&XN1JdAe|K>R-sXko` z44TWat|i)$DBXB}TQfhl5b<-~-k1?HxAvl)eNB+O`e@}6jS%irAXC4*E*!8b4> z7`kh1Xpj|W3O#W?^JiwfF=HL5&8Y}VnBAyx)WArnKFk>W+(7EbC$9PMVLF_vsu#Kd z&Al1t9W4W9I43v{!>zf{ib_;)5;z1I3wY`nJ9BWtJT?s>pdq;YJUSJx5D=mmGqwlM zSAA!eCyj{arg~M>FKeq;=ze16k%*O{Ubf@A(%x% z7fvTha}+my%A-oq(!-+`=b&Ax1syEYT$v9UGV}OF?f}&!K3;N9jPq9!ggc>IV-`KN zHsn#6yGpb)9?`;Jp5h0`Mlix9n?L$KNN^$tl^Gpu9tKJWiE=u+?FB}EJsGgT;I#js z#6I*H&V&|3u=U?pZ~Z&gLH*I@p+HV7?$h!9-wjt$4YLHk!H>R;0=H-PL<0ziN##P6 z2_+mpAT&pv&~M87cD%zUaQ`}zUPf?`A};B0mLkqX9NDry$>80E5Dc;?f309)EV@I- zn;GB9XKV+aU=dOqnK8AIV2Oz*UfZN>-@@6cp}~EUCr}&V-YO7$dt%h+4jx zB{0R1pi*E~f*?Yc2y_r%T;bBOvc-(vf)%`R_f=5*ZM;$xf5}0KA0mt^&NDy2bo>P# z>MZ6>oy3vIMA5j}i|j|IYA5M?l{Hf%l66Nnv26V7j=q_GBjtJl@9Mnam}82_to3H> z6{LXb4NpvNk3Y&?WGv2nm?9Ec0*ZXwm;DrNP~zy>91uDa!8?Ys~H$rH}30-RkpZKR!BgXL5Qe1-D1i2(@Y?(dC1HX^bBq*e$J(N1DI|$Id>Kw7| z3RjY|Qiq7+>yZ;C8!`U!Bym(;B-mI5zippmJE>wp4zd3e|E*^9X=Xd&m|+!T7}P6x z2A!l(o;W{RS9t|y&6u8o>`)hAdrAg*AhsBW<|h(okt|7WoU3pdoo?9n7fAzfrb^m# z%Xql%8v`v7tDq6EN@Pl;!cIC;g}5#0Pac7ZODC10liD6eZBujz2A@GOF-aL*;&cA| zhEND&gP^o^LmJDM7;>ZP&U-vANt(fFx|=!tCFMqD!|<2UAU*f>=V^jOr8(57h<8PX zr=qOZ(&PLw4qw-Fn=na}I4D$-G(PqtNR`%AjOic^fH<=X-lt%-FXN%VeTnCon#b@n z*5UETSWRY~lG%JeL(;THmxGrXJ>204E@^>Vi~Ycui(WsN}b#Bl7#syz>M=Rc_8pgT0XdFre*5g0wUG@-Z4~SY4sc_l6&w}Im69(8}pO5Y{3ig;|Wh!Nn2nG5kA|B zBb$z0F}T~Dk3mJC4?;q-q}UA2L^p{bgk_j<7SKM&gXNSdWb{_S`iO{N0D3fq6^@Lb z6ah@E%=bqpqai)g3fMc2W~-!+`-hCbZII4j1z<@*i$D7H8T@9Ushek}ZpL!Jy5S1a ziV~FIk|D@$wJWSa+`TS?wKtiO`>9E4vv|RSLe&ujQe8SJ@o+jmV-bndPAD&?$HOdt zz`}>*U}lK6n-Bwi6Q1ZUexT%i*dQ|_j!V)h$vbe#aX~%%Ep4(=fohd`LS$JVjh7dC=!ddG6xPr z&SxbeQ}9O@qi$I-qVokd9x=xek_vv%TDYVV3?n5l=OHAKixLxO;!VjAk8C;?T%Ftm z`QR%FO_a};6|f>5fuiWp!0k$WbvjC5hg*`UWu@i{!IAmEcE zUjwj`)e5GN(|)V)(2S@=#DJE7$Ii6mG{&fw&c>vB?oUuiqaz56f}YBGX%Eg1N^@|8 zF!Arxkn0#iT}kMpd@4Z%-#E&_9`UthI>z1`%oUTAtm_9)q0bXfGB7Poqu!ZTMDdx- zz1xcM*P0DQbTMAc=nOpDN3LeBcFjGfB|-_>U+Qd)YyWOHptv&McTJ zj0`z%{S^$J6ps58q%>f2Z7H2^NmbDvGRV66z?jAjZYV^!w>w;X>tJzc$**2VxBhDa zAjkCXpT5%&8;eiI6GBlwX$w3RFMxnQxT_ct^v$D~x~?{!*m<)sUNun}+mSBK1woE(!-3`z#oI5S{BD{V>6 zu%Zl##h44E7&c{i0r8L@XWS=>uVxx#nDUNVp!kX9iR1It0;md9?q~t|^|h5A$l&k- zWLFa67*ZU&@Z=&EQZ+mkbv?choWcxoqG>uT%9)U&w79%sZAsO__>hX1C_`i`1X&}v zG=6sJ;O;2^Mk%SbwT}TqiPa<#6dU*f2Amy8J`VK6Qp6k!R}QNia9qNy-k)YCfN)3< z%lBvYxj0;Y!pvj#xi6fGR_YL%7vq$Ev=eJIwm5h~qK>^R%-{GvaO)M_iIBw*s^f6g zF1)5$j+g*ZLYAX57?W%wX8qex$XL^n;2h2wiQ<3-riM~(djP9sFi-bKCjySd7y={P z4-Ro|`vd=P(~fOUdD$_|`THpxUHgG~=B#mUDM^nQ_IVc~6PqQ4kEaN5mVmtl*IEE7 zn$4;>l6-tYD4r#gz`moAv8WfHXf>Py#9v@QoWWhum+*w-ae%*(*fR*COc?($30YXk z$p>eV7E;521P~YOR!nhv0^P%h>nXs>Ktwe z9!4U_g%BQ$Nm=LtmaWkLm3Wo`lhvcSBff7QVY(#5w*2J)KQxXbi(fAPo$*&d#QuU1 zwZZ?*3n*FXEi+3oxn$@o*S)E`&?@r4S=+px-9u znz892Ea}f+XU28raR(a3@6@USat8wlFJoYf?`R* z^;8ec-_bq2(C8hlyqqoEfV7GGnStihJ5dM4tdTmMgC)lAJ|iHwzBZQANC&&HYjVF5 zBNC$%ntjQpx=h%CF1Xx#>wL_8RaJl{Js=h>zZ8wNT#Nt%ZWKM{837cnjV((c#vOY&N|{=rY4z6}cj5#rGT7S4ELglxf^jn5;IyH!^*V^y z7S0wC+rW(fSSw5t5Y*Mm`e=*Lc77fkslz5_@>pQcIGW{;o{6x;V4{9J-*T8FW8yG9 zOPM%`p2)-KuSYW)eJC1I6m-uxCdp3hWdyO3$$M#@irDtQ5u59`dnC)RB?cPtPBd2( zrElT|0v~=3bW{|7fguA<@S&e2$Wx3r<_4*H(|&l4HdepCkRvkJjJ|^41}9WXyt72y zx8tFm55r*un>d2n;W0$j7GrUhXSg3274kMtg`i6`rZJ|)QG~VNThIw0sB)_OtPA<+ zOI(VHLM_EWw^;+cQ0<46bRgnya1H|oF?w!igX{LEmU>g?(*2KfXgt^E?1XtTA_laN zIdRWW^l8+O3lCvAd2)fU(vVaQZ*ZDRxSYI=LxYL;@JMZZ2I*28nOYyXlMXq1k&+c` zKr#W9f^2$fIL0m1KCx3+kL($cD>ESP0c1u8qbRVb-j}-Bn>xpeGXSbYG~sW!L>`3E zj6Q}Sh)wx}#r;N}XjJ+9w?8uq&CBFvYmVltcHhGY?)2M`>sVIrUW6zXq#ky)jIP2l z?iRj)Y^J56Yw;=OfYAEyzZrjT==0rQ)Xi5{jCDQbaZzT@V^y|`I!_5v;gS+cWUHgie&|x|6g_Ch?w2C?lrzMb zZF#qGRzu=8pefK6o==El1Oc82rn6qY;~IowUjU+2en3!!Cmr{xPbr-4`y^X;Nx`H+ zppudZ^wA9HX}k%HX330nC#VE)8(3MKvBAS3y>Gr%*fdKg6LF6SW-Bk_g&+7-9RQ2T z&@}q3RCupgWR}7c9^45?;+1*kib>?+RNksao!21tXTIJHO$BP$kaYeWZnss@D`%vg zk4WDd-9fYC_P!^*O{f2 z{g~Li2gOWzM>kg#(oMF{l)F=qAC@~rkTltp%B;Tjf@gry;|iQ}l!|dFXw?jJ4iw_@ zS&i!>A*cz#nYx%fROI%nvq~4G2Pq(0xd^&L-v5R=urJXL)1#z9dId!JS<%jw8h*C) zYS?6?tE$RXj&xKyj7@r4Jwmuk8`F~jy|~hAuDBjPo(&udI|WDc6sNKLN#QiOJ4M43 z{$fq+s#sGtf8z4d(PR&;(ekIQKu{yk?Ch_Br|oiF#<@Cubg%0-9-3NW{4xzzPDsH{ zzL?W9J8{8@_Uszi#n7&B_+5MPOn6TOQKLJ`T=JlFkkd+Q#Nl_*6fyBd4hjM`7+z95 zJTx=$5ke&WCFD0YGZjc2J>wXgnGqi^G834YO{@Ulk27);>jYm}g;R5S;!Y-^ZRJeF z-^%z^#dur6U>2Ls*~pm9>72unGf~eJ{RjF>2Oukq=3;C{`Ac_eae0aHX{;2rkcwJ? z`Alk59jm+RNBGUch#OqCopx+msVA(G!9q|A&tcYh){6Bwl1Qt;r z-tKq_Z<55!BCW0PCLUu*Up-hD4cN;-C3cC+|MMvG4ffyB%rs@$51f%T3Lc6kJdM^a zQQ5c}{9jIf)DA3vz0h9n!2B>xXY1#|?C&PFyMs?TjmB`-Ef+#3b)Ka|X-|w~;b5)0 zVG6Qjpv-b5y_B@qKytwJA?NeAgvLUwb|Q8U-Pk-rL{v=WSSs=R696xrGBGJm%|uxj zoH8XgGNP}s>9!7GTiYH)l%e;xz2qtGocwoOh+pFeZsTe%%vA86So%L@@K_^V((zd1 zEytl1S?T0Nq(555a#_JaOrykk_@chVj5Y!!2%hoWvd?35&QvEj$n;gWrdN;a5D5c# zJdu4rtBvu4qHWM6RR9A(3$(Z--kN6yz-41cp^{odLe9Gw9h~q{rXULeYz}mg4B<5* zxa1&(jwCpwP>BVHp#uZe5Xp`sd+2 zzn_LhFJn|(5>&xZm!IJ)AOeMAZ)6K-yTpva;YlDq_K9t`k ztBcGE4@+^ni@91>MeB|IKHq z{ONihM0-V5GMKRco{uBPb6*c5FA!7gvCLSLJXhcui}F1fi}M3!kr{(ZY_msA;z0=6 zr-e_1Q)<{pqMYt1i{K(KMehNgqCqs`*saZ6hQuViEymPj^A!i}eVIP8dFOgCmhEkl-HO}EHWTOHrXnd-I21U*W z{`n2#^Cxi}4WxB=rrIfsGQ}NJ7i%nvu>ybFC%_++5qKXIhL=J{0l(b{MJrqALiF>& zF#SJ1ZTZ{vG92LJa2d?Ip&F-T$-QQZp{snt!{e04^Wfrfs^b;>1RGc5iAA->x&IQ5 zj_ky!VTje!b>g%MPLV)Of_g*Fvw$}}03+u@4}(C=W!3i3LWxVln1IlFBg0{Jn9ir4 zry{X?KRtt%EV$|?7@IjlFc;W2L1%_YiPR832FdU*g#?%i3X=U-rZLS>$h64I!a=tJ z$@%L)DIVbw&;a7B5*-c+v<*Druy+BDg@U!L-rH3&R2-!VzqE9s%nVNCyll(^KOR&D z!A5O>jJaRB#ianoDqEUD&TW6;hCZ!;{q#6w-i-(O;Qk{nI?uRFC8SY4gU-B-=y(x0 zw{1XbQ8=u}%aoqPoX4=Btz#glYve?5CQ2AGsYyUgL|8gl**U%vbX)svl;yO1rWG6+ zx?`=JDga=KAQOR&D_MkzU}fu2aZZ-%Yl}l_c=DT8_Nw~59 zh+dD7V`Rs%bfRBQk+xe0V^QV`I$!HDg$q!p(ZQ!HoKN<0YZFEYze6I*q}}$(36D#c z4$5SHb`t3x06LEV72<*3O5OS<5UFT1grLlMSF%rZmYrJ5Xl8OT(|?DMPEsbDkILY8 z2~XsO)v_7Q#k^!!N@HKznEkU#7y8$oKsF}mD#*kUNFbV6h{`TCnHMhcETMr<=H!G32J#^kI30gy;at#@Po=jDTX9Xs z@8x(UBy)rw{-*GxRy)QQtAkLPD`EOVcv8jQL;-7yGZ@$v=t6l55?e|^ny0}>3Iinp&BP=PEydz2-q>0QBlwic z7?X@KR@bxxra;ICGRIG>dz5{$VuTQ8V1z)XLiw@+D$rGb{bOaOzx3#VEOr<(pr2KS zLaadodz7eviV<|4-{5Yqe;732MP>ow`fYLdAof^f?`24o;bOONf)!^7X!+R~;)xrj zQpD=iPjjH{XZn~tfzN&TT9Oa9O7TgURsFuYV=M%u0D+>GVW|KJ zt~C=>H6dqw6Y5WMeWWhKL>QTEZDcwnq0C^Qt3Zm#GBL(w^NW*1*DPrk))6(khzOrDi`$5P!r%g6)a%G zm}Egvm!|Cz7c(-6|1zkrKIj--!(lUY6pIX5LsFr+X==}!U6JEKCtDZMD&sq2Q30CIbc*l~R_(;Z%p8!@QSMP} z>JtCPgUpNg``^i$aKlIy0N;e%kXN&4`m~f!IAlo^&aKQQfgH)NAQjd?IGEu}jGxBJ zsn|2zqe^cN)~}5}#5gX4SE7&g=wtjmMz;PiLSSmi@F<@}Q5M%c8K!!DVers`cQgA| zBg&UT*hn%iF6rY;-?R%%QhS+aZApDBz8(R<81th~84XIorc@x5V5T6)2w+sR5;Q@zXk_Fgd>Y%gf z7cd&B=5;jb1D&`5ih33ZdYO1&hl$$=2D*#N_lEUrA&27^#D8%6uigPad`d7lv)jEL z@34u)Gqc-etQKR473H&U6R38(12SN@`~P@?1wn%+Sk$JYT-o-rJe(NkW|pr5H?#ca zW;WcY;tXzNaZDOQx++YOfz<5$sYnV^sG5CKjx6S?QM^#0+Sj^;;!(w%d~!MU9TGdG z6GI919Hlv648TpJa0ug1@VEUKAL%V1U2eMI@axNa(uasT-o|xX<5(di&3kdBz!?CB z&S?Tbe&S*&hPQCQcv6hoFpoFKQ9B6IQ+UdSNcQWE+HpD=BQx59cGB!FiZ6^4c`h;czyArx1NaTCF>gDJY_r)ZIJqPQES1B&yNBx`BCp zI4clmsX}RVPLK5UAaD;c;ChfUZ_yxZ)Ll(i(SK_z-B!55*LFwSkQKo$+z1-uyU_hc zD4hMTX15#P4hPdMX+Wm+1wKEM6((3>*UD(V|}P zb=(8eW&sl1EvQ*5F&@lFo1nvo3L@CJ1qiNpUKi}*0w3sT7SBhQs=6Y$g9jV+7jDJ| zRCa+}EWe3%t{DN;)O}Nha?v%PxRk~dyNmJz?%TL~w7QZj8&3e{Wbs-LNd@r#7KCQ5 z1j#;duLqsWS#0BH>|r)SPp85<>q>lgR8o=HhqD9?$w6;{X{1mWjtc6Xg(HqdL5bwf zPHaU++y`rYzHl2-<6r-kn+s`G3pxAhfHm(jd;3qd@p=<1G#9+k1zdvuk28Aq@4 zNAE{gt74Hg4Zg6aFD|O}-7?hw8bn@m@4QURv>|FI%mXpJ$$tKNcrqA^I;}Z5A*V%Z3!uGH zTQbjDozvi4HM$RL4bH+ofN-0$<$B~im*@k(greYf{JjtPz(oAb!(R;EfhXhdBK*z7 zUlsnk@OL0X7Pfg@xMB_xbGeK+bLDUA_8sUBii)SaBNr;;zzQy+>*ejUkplF_V5=_Q zI4oXh>8iHpo4=ZoeB475be_8gg9Gxm{08T>pQaIaurUD4k?~})eT=8L%x=3C3y-Ob znCQ5UJj4TN^#mM@o#~2PC>ElPEGpIhAsS$0{(-RA5>gcLG9y z3amu=+FjnuVUmzlt?r@s}GV4dp%P=HcN8BNtfs7!C1~i z)}`3H-xYGk+JXScojl1QxFdSj#G*Y-AN${~pIG!4h?mL_!4KtgjgrQy%c*HJ0dIqI zyBI;>8m&Va2+jm)H=6UoB>f2hfhDRwU{&N>4RqE~%nfayvyK_Mb!hi>omILnq=N5g?^a&FObIdwjvPZTW%?GG^mnQ!1$LS^> zsA+sjn)sWvM!3I;^cJVaNXUtEpKk%L-5hfBRB+!r4;-1uDB5uQ->c#`EIt^0gGO;xMYLWZmLem7@%V8~~1jaVAGp^})sF z0ub|q=87CbfNNsTj@2EiNk#HfbR%-G#_2lDqyV`$1&RuHmfxP+jB9}O-r@c>*CS`R z*56$3-VX|~{~w?vkTQPW0V*;Ef7jvfQBV%tYxZSfhI(}4nojJ7g_Z`S%|k&S2*BXv z8R97ZB-CoG!flGf*bOBh*0bao7GP;`PF<{excbnQA*1;aR}4z{L8p6gQGG8u>*sef zMZiKM8l10xB*`=Vefda_crHS+4~5cT6vB}ZE|PYnCrgMzu99Pqsn9me!Z+k%V7L0&bJ9JGAwu6Ho7kalIZ&^Bm z)#wfEWn9UrBzI8#<=_gK8tc;RP^u2Xp@6l=_zqH5xd{5-K>!SbGb;e@AN2e3LR?{f zaGjtBZ!axTc-;eMh9M^`Fb1}%+t$^!$&K@Gu|HU*jToMVRUMKHwi*j=?Ebs` z`7&*)zKg9suK=x1-eN7wk;~&>x&np6R&7BB-kGuTBf(T802z9%GqYF5m$fJQarjKa z%9e$*HmCdb9iY{@OaR3sJrb;Uy02AG*E<{21a*&e&2m&T6W7V7@1f7!v22y)1)ZmV zNUYZu)HsjITiGN^*~J*xs?EiUUB@y5)||;9XOX=xtgW$ci)dp#%WLhYBHEHOT#t-# z?Y|U*yL-O-^CYZjATWE%gBz=7*L$D(q<*mI-8$a$xa{G#XtO-${m@=;i}ow`!?w0u zyU_iRebI8Q1V8$6?KA{Fxm-KLGo;r3V7WHI(`DJc%e7NHf330eR%q9I9$sj-uh8l} zGlKTtR%ju2t37q4R%^eoQfr^!dSsMqy{~@o`f>QX0T+F|T%WZeQ15;6x_Wi@PsjTJ zVfrfVlF@DGaI~nKw}U~K1(O1r7J@mcXt3{HrJXfu#=r#q(HpO@|FTNE$o-(5yILEI zp9@xN8$I_9xBs4ss59ck7I zPOir*{PH~4&K)B!*oLKRd-(xtm)HJXvo=jPjuRmKQF4Kg@5JMq8;*dBx)idGY^x-Q zEAIB2b!xTnak*MH77kcXUIJqY5F)HwR-NM@mrJEfj>IVAWF)RPrO1NuV~S#j*lDy@hB@A+w^6bN*D2 zA4m-`SRn$&L4^NcDlS0SAM2$xKv;dDqOxiJ-dYG{fX`}q7|6nvg*lO|qVmAfD{v5| z!8*>kP-_ZKc_#*KLG=-^HMDyPE*V_wOsQmX*)cMOgKC`ue!?493c6 ztsh)Jcz-n<=$(?5hj0Fq$Mv4%#@)I{E(aTmWfc6)9J*^{E$Ysc*@`#xpj${Xw}?2$j$ zuFw}F0Q4C%pbUIt_5)g3?ugnvb&WKvx1Bpe?6@gZv7o{D$L(a-Rjxh^Q| z#pb&=?5TG@`3U}g4+?iK?jY>RpyOkY3u~Tq#vw(-$uT&_?Sf!?`_HwoJKMhSHtjA? zebC-{n^x<-;kEp=+BUcQYkNOnwI{V}!G;MOmFBFe$pT|dA$jCkQ#s~JF_JhGdd)S3 z@F|(Ki39?^(y=>jy#22pzdxf2U0vH-8H1_g1@BFt!l8)}Y(0 zJJi~Is09Ty4Ub``0Gdz|a{k_apQV+13M{+A(n>rlXWQ+Tw$Jn7T>JXlwRxVCtL%>3 zwcVb-thMioX%E@6@6gU1GU@LSe7b^8m)(4aHqQMs`;j}ei^ouhvSC8~;0?k0!A}-| zJUoyA1j+4>@6fK#DT}C4z*&`zL00ViWR88som!o{=Cv2^)Bkc z`@8!M`|EqO74E;H{Pes@cd+^ z{p@|({NXGVS4XMEY%_FQw%gY3$-g(jDa_p-x7xDmW2E!oss%Oo<%RYuwl)>3TEf;Q zj3{hbjSW^+%jyf+rQB+7x5wYFUFJU1UUk2=`ouzXv7MgnkZ~Rf);fFtV-I~mt39#M zDxPFroevrx{Wvm~Q_TsSYcG93`+?{5bL}S{(58DPea?g0X}JMz^t}(b%MT5QO!9ne>&Ea~vs)h2M!F~4_dlrJpck68lgwq4 zg^L5dALe>;ItKbZ81NnGOS3kA<#D0WeZ+SbH{pIOH|Sh*5vS2MtbYF{i3$iGD3juu z&6Iv%nT$0*Lm7tH`&s|_?}jCnnE8~uAeytzzV;z)#Q29M%Y3B^v&b?l@+WENct2d-A%Qw2b^%*?4n|sfIcl_=L+-14LYxAVCJ8wh_!4_+P9xFci zY&H6XtuvYfz>F_4&1iBJl3AWSRIwnDi$@EXd41s5Y^Fn|ZjrILMNfW6vBNWuBz zdO5%?(g%=VY~%CK7HlSKCPsVK3^JE_lUdzm-XjoVbZd`cYiKURJ}k$aGYwUlWyR*| z8R=~WDiW0~h76Y{2X;K{mJE=a9vf~?`h_-HuUs?8D)vGQRf&cT7_1AyW`%Xg*TE^^ zjsXlgdUnd%g!$r1{semTntJDw{|ygWwS@@B(S~EwUr0W=-uVi7k11!oRaKjZ2nr02 zi5OyCQGkiuI;E3SqfS`YntJz!C&8|I>a(8Q3Bv8c*6KEF{$3K9g zzA5v%id!H{ZnsZ=44lGEO8zz2+LPRryr!pYkPlZ$L)KRBB>Nz(QY|Y_mRQ09orrcX z_+PM2?ypd3K0cePC8e*W3IRACm|eCfn&qx zTlJF>Ufv&`Zto1Meg-0j^+%|-6j&O%I}a3+tp{iRl;)jPKR&M&p4?hW>gC$w{N9_yj}Bimd^!{u1@{u=lIuv?$dPSq*Zwx%y} zVMEo`1`6?9EG?N_-#4+)L5>jrlOL)li+!fM@HKAb2$H~zR)?h1VPmR-dogG=PUG2HzI{T07!5~k#&d%O| zjoCZb+LJe6aou2tHfWPH-GqS&k=bJF(usD?liF48_4cwSwX#47cAzro;6M~Rq!RnfC$++?k07sdyg#&0 zdP@62P6Am$%jzI+`N-IU%k7uCw3@Reki&d2dsFb#(okvVU#y~P~WiOzx54Y{5mb%dHFk!>qivkt}Xv? z%E#7nZ*p<_EdTWK4Q^^U8!3h~}kQs4^@ zwaW8aD>0(5QXpMbp;b~z%_dL=KTz%iKwUhNi9RydZ}4#;tw$6A@&U7Sl=P2Z-|0G({#w;(AHyv@?* zRhrd%(KQOylLXbwAd8C}3{1tr8g(|8_<^#`Y+ z;E|LYmLWYA6FhLq{2siQGMPX2CV*f!)#Vwhay^kMEQ|hE8{`^1_-t$%1%MZum*pYe zTvgazzGpqsC)VO&RYQh|PV#A&<(il0A_}bqWl%j+>`E?*7w2JOs=5GJPf2czziqPgKYebVCU2Nh(L6UjU6$usH(>*Vcx}q8*lbh z4AT73f3pq5a`l@b=R`;~WF-5ef3`n+2IJx%XroZJF3P>OXRE~k$HiUnvekl%MhNw& zVob{F+@k7zSc}Tv3^opVj)g)wwr*gqa^wm+pMi;4P#GTGcy=byPbHC$MMF&S`7lVP zF2r9L>#2S>9y{>&&&;$;aS$c)@uWbv@i6!};1_Z=f%5Ec`ZN$6@^BiI+b)Mq(6#>C zA7InV^LV0#z+Hr1MUxV@BDl=x4it^`-%c5`^vy}^?h^a`XSHE^1Et1Pm@OtHiYx-& zdrG~<;Y%ofhVn4z{8;3E)7;G*~!Eat{E`#{&NKY}= zrkSyvSMd(TO3%!@uslu0hH9~~YTybr3WBD-MQZj$`}Z$!=3y65m%ZspVxlUyXG=a9 z#;y2Bo`g$}@%|CthxTmog$Hf=0`Gt`P}A7UR2;N@2dH)?F}C2NlH zwh+6a-tO`vF4w`gsc@%hs=6_{1e%j&zItcg;}H8S2ts}X=B--w!YAdue;(ANX|8_n zs&dQp@rc+ntOXGc%5g#N3thll3uh`U4v+!^xrQ zqTIDV8)^%zUi-hs-x1889Q>V+KS+9?e7C;g$^XO!0f;!{NGw^pF{=l!|`c(Iq zo55|oRj(Ybz6=;{hMkh(jPker5ig15QqMX5w#)IQ8=c=9&S3woLlBx+h{r|barM#u zHVe^bkyQHI&XEGNa9*MaDb9LY0^1Q-32)K(NE9HLh6={vqR0tU&K=@|N3^iPORl2z zOx?krirm8R@FlLs?B}85t9O2i*{M^sT?Wr)?E&V^lDu$yS9egBqeB?U$Y~W*&uE2w zP}^FSSf-93!+qOPGg(J)xb@Bx28SZVZwG_1IfbEm=WZ2H+rWJ&Wi-@uVwV7cA$^4r zdg!QiNPHNpMNdG4YT}?+2xEB`WdkF5f5bGe~<-0E~0eSAW3LsCpMX0D8kg z;V^N+q1o|7;J4ra6qGg2Y;GydvRq?zuI_3+t8onaquwbNMT0L@k%J3?s&`o>`7P5T zk*wEA3zCEQlV#S@hkW@it1=&V2P_#^?|k|S(N3vuVI{Vmk}cSHofP;L+KnzjfnC~i zMGr@M0c+i^p3*^u;Vi;-Dtvt_V11EM7$6hFXJCNbrUuA7s^hi??86(ibKLvwNv~?T zdb-n_k4G-*Uv9+$gi3@thjLLs&8pyd-#w zeZ1btr9U#kwf|0t{YT^Pdi?zca~@ZzeEB&3p2i>40AIp*LbN_FXKAkTk1*Qy)HLS2 zz&@_+FLDB#7#Szy800pMm=Z}gsV|w}0ou>)(4f2ywCYr7?rwe8Rg8`B?(q$|$zf6g zH0yEA&KlXZ38fUasD0}Mw$Mx(cfIC&~Lc`!*IpUX>6 zpj6wAd(72RbCN1gmoWIrB}La%m;}KjmqELdQZ0h8?S1<)7UOH3my03nN?u`o)1__b zD)Znjb8NInWAegxkIHwy%9k8`w9I4O0>2|T=yItS8`5n*=?2&BfhfuW{B1+=1|tcxt`o!R>~NsAaVZcC+IPdZGPFT~ zl;PyF>84Z!ipo3vZIl@rbG-S+yxii%Z}GwG{Dtay~yE z%m<});fsLeETgw5G8GrrfH`_klKL<^B54T#2|I}B$kxM8;VqKge%#tQo-XYf2!n63 zPHI^Nl|RlQK;ya=B0~$SWV?nNTqa)$F1|L0jDT|!A{G?&;#%~w93d-LGaxzCf|@~J|6D^4CE_Wyb=qVfG^+$igv8e@}3n2%^>6CJ30r;B6 ziJ9R(gZjcX*e@Qkh8DrdCH6RnS@|Kl;H*AU5^DTg1~M87VYccDv{n?GTnQ=sAvLpJ z7Ksi_d6b^>YD`lMrdH{TUQd(PI+_Ge0EYiy(0L8sOt^?++kKEjB$9*So1W}%_my*O z3C+Wh2F4&SpAE4phZw;2-*Fp(J*KASo3XIm&tNef8DU@9p_LE6G5jCw$;@E30$_5k z{ZfZE(Y@6Eq(jR+y$CHEfaYXjj|-w6u&bptjSibnl5=vko%fbD!JhrPcAP!?E$sxF z%LHV@(zjb_%9_%dSjWmo76rp=zzXLC8z;P&F5yX0EM*J+k~g5mQ+i@o!-;wx7@cIp zVu(o>V_a6DUJLerokcsE$Kibze0u_F%5yQpf2r0=mNCnf#3Cx&z~kVGbx!tdEPCXD zkgNa$0PN2!V_Tr;f~E~3&ZV9ZEGcBXJ;k7lL#^V%p!3b8cK+Mi8T!HFNkf%{4Jt`W z*v|P}XsnWY3oi@(Z-WTFBiUf#IujRiXw~OgLxDVM+I`%j9u%Qbz}Ny~t3!$Z5*f{h ztVYCn0@s zNQlE}lD%DXxup8U>v%I(aDJq9K|7X;eqJK{kYjpc?AsLOIe}(X#VlM~-g~J^6xFP4DWgfs67-3)tV{jObRJajQ z3{8LJzqmC8VF2@>c`K8FF5u!5tZ1Ce69s3G(Sh>qOUJ`}YThboS+yg^#c9x9B%SQ5 z;EcWs$RVlgO4}PI6yW-RZ*X>e=+?Cr6J}Xu`9+icw@G`1>yti=A1X;9q_=c2ulV0OTdyX2bC%MAokY;q_OioGChqo3)co-2YwOTU32WFAy3WtLwDNCI@S= z(NN>Izq(ThOq~*m-if(_P34z=neWj>tMT#(%{6d%`H?t{GH?)AHr@nWG}NN>8s}%3 zf-xqK?YX}I8=zJd-sI_40AiE?L_xO*UBh_RDrC`DQYMf~*DRxxWJmu4a142SMwZib zk+rN6+|~KC$&w0r+oAL)w)~g+6s`}W0M*B7b;J*?TMD4ndRN(xfG+{Tv4VL;L&Knn z9D*}kVjZd2jH%WYQzRgb&)@yUWg8t9aTE7f{o^zYH8>?y?cG<8keL8^qWiT5S9;pvGb3e5g{T9n|rbx0c zl<`tKNFw<-+GCBt%&pstt(+=hzmx!B_QFHf9BekFj_TfYt=yJE(UBcnxG4R<*!1Kq4WF|bA(4v2(-9dLuPrQ89S-Mr>3LyhMqfw`R#F31ffzCn`G(Sf*2CmbBd1xFInj5* zx4L4(7@EeXw9TR%M>LRLC5tqMw)Lp!v| z`Vh<)g*Cy0LPP6eY49-BU|J;u-dX_T0SVuK!_pk zz^phDal)c(!8+8B4I{SH3eE%uQ2U=-=KC%>mH@T9)8t@mI}Qf+s> zr=2+Z0%1pe$y_kaXR3EBi9;cg7-JuNPdjyp?7*KNt-0(GJGFD{>YduckzdLUV|n0j zyU<&^#1nYx30C^y;Rzit)X9{ErE)D@>`_43> z2aj4oVI}N!DP-gcfVa9B`OelR>jHopx{+YDI;6LXNFr8*ZjAnorN-(C>$ygUs(vCZhc5U=O6wgv|` z(~+Q<5Y>sDcxUF@nL3KX8V`xs7@(h)fd~s*((V~M94V^J7;RfkcHNzgOK&F;&;Hr~0 zkifk*IKr4qflTBC$SSbor*L9|*PZVI4~QXZU-f~Odm{ISkzC_qxX`CTBz3~g<+k;K zHd;I2|K9%92U^>4-`$NK3}@9kz0>V!yR|W%cc$45yS1Y1n`G|zF#Q*M?MHWOmD)4M ze{UytYcqy_q8M@61e{dk{9d!q{}3Ci_Z87txGiLPf=>Bw<@j30L$iY7pX}i=0g-9wmkevwd?x7AGz9^GJ6pXqmfZ(I zlI(`-ohxK$WGDT}VYR^}lWH7bo86bTzrZY!;{(cOYcQq=&o~BZ8o6PR?m1Ul0-Jt7 zI9$Z-&NjcyZ17E&fR`L=jkJz0x~mNt)|?;`lNVncNpa-YprYL0toCAjvznS_@S`x*ycu6 z!D$^2sS*v&2)t<7p<)^1S`Ray4SDrhpU;3w#fPKhNpQzY@b{sRiP{%ikMbvRIu~B9EG?H@;*V~zC;w49+w=Iv_M$&(r(FrQi)O%w zXa;<68LrrYyc3L5E5n10UjW#8r$a13vJ;yTh5>HvSzPr0v2`YZQB~K&pJW~|Kwt)l z5FkR7#Q+fzP|;uwgaibFSzrPpF16A)b!Wzngh?Et49`(4;M1xV6s!9lui^0b~EV z_tiRH(ptDg2DPATO}D?usM^j_ij~?;&i((yv*>x}umAK8pVYcRF|{dAbeBQ*T&4Uz z)u?vC{K2C;!8b^l$}U~7gMfkaeA*tRe0oOy*x{&i#%^!EbIWe8zt3j6Mm1S0r)S4s zDdtq6b8}B>b2jYume6yD54}gdo{mSI4j*}o&bkncn^gpfY-%Th?;%jEhcNN5k*%6$ z@Nqb}%URX7U^jCNpT;qK2^TuKd%SmK9+~E>*yBB| zj~s`f3)kvS%W66aE=KRsgqgg=`D~9jJ7c1AWRG{qfO`appho5-Lg8ZTb0Ey3aBFFc z@ZaHTcU=3U&cwamfma3+GXqV9w1aQ4dxd32(4OB_tx(*3GJ6?Pt39O!q8m1_ikA}A z)EwIqKvg5^Jfg;%qpvcJ1R8>l(F@>9-Nd%e9fi_UR6e2f#SZ>WkRWB4QDNZEc6 zAH(JTa`(=+oLL`x2V~u7)J5pq*Uxq;uI|_a#WwzmBA4XqIhNA8wPfhRi9@AWsjqG) zKT*iAk0i*4;{Q%T0T--J@y?`*#PJLlN>q-zFOsjx`<`ONmJc3 z9adft%vf0)?C`~l@a(Upv_sF662RH^iTA9Gvz=p~;ALFx4EWT0T6TX@>i^G#a-B;* z_4dyA#<}%V@8ALN=!|`F!lV&ItKD_!-#&?Es&=1mchuSNsdrekA^oDro;D#qWx|o- zK~`AZR_$OB%lx`hNpYhT=3+{~-hiC$Z7a$XVroyzrY*4+$Y9W?i`E%uJA!4?)e7us zBPcUmv4!!BuuI3pi^tfdW8=kR+e-T21CwQsER3}H?==UbAd1ESIEj7Ulf7Frk2=rp z^X87=qN6NWbu=j^!65j*Jk!L#w9-5AFF9gp=LD3k@kyK0^)qk2ce-`dIrlT~gy={H z$;mvm+HF(Zfb59Ysjpy-jyji7Pf+w?Cb_!m)AAVk4e6QWnQ{XucUTn*l!M^_J4*A} zAd+E5p^$GNQ$StgltsCM&ZhDRD(`bTd=6b&?hO5$V4Hy&32EyX({g^hs7 z_5}jjK~%tsJpzF!4AFIl*qq^&Bf;BLY!}*FCk@ydInUl2@;n#{RF5ysY;>Y^e}l`iWBR7T4O)`x$|FPk4jg z0;g!uedNE&I?@PtDhdToIqMomw?$^a@P)$;H0~h}$&6N{F49v94b@w!B{)Q){`?Eq z^9_c@5Eo(D!v*Sy^(|&HLe&c=TH(Gy$u3%x&v_w;5_RVd6<`FCjT9#=yyS##dX^fu zBsk~{$@V!ZhloI-CVw`ZhsM{PWP9yi5>lM?ZG5U1g{Ivrp7m{M;Vo9}OPpPzEtGKK zpA0F|AMe+`C@@4CnJ}_59-rCqa$E`GGqO!L%5n)3Kc+^1oX~~woce(R$KK@RAMl=% zaoCxBzgLQJoqNCVo{hA+YIWgN1Z+gy(gc|kt`++PHC5vwX(P@QxQ4l1$H;02U@z)UkrcETP2A7_ zrSL3wi9G4{rT3D|-Df%1f9dU$x#xAq{?dC!6nz6bB_c|0(fv(F3t^F#u-sVR;ys(j zFAmgy`E4FVJO|8M;f}Xrc+gZC?bKtv*sBf#b2RS<72=3Fy6Jvla2)i zq{o$CX1AeS8R!XrlS=uKTpZwBe6!*Ely;yzv;NC@VSoL`kDaTUyrYMuR5$gtHomkn z)-J*T9e4?~)$1I8Z>EpGb6A8wHF?jdd>SxE+L`1JMOnwY^n~}PdfZEodlrAx@Z<3w z8$QQQ;%WH7;ooT2a7m026vVz95YBCy)9~d3PQf8>&G|KoR&NeCfp8oM5{*@JgT7h) zf4sR_-`qdosPpb2@6c%Nk&Ssehk5qQY%y=>kGV&Rt&aNblGyyQ=2>NIJ}{E$PQnfl z8-x0Y?_*=Pq9l}WBkiJ`*sVnS$d5txJY4l4oFpX0IkaQL_kxJ}suPI-0);(m56W^( zS82h>I)qA#of)f6sHDn$>NS;TgP}oHZk>J?4v3e*5b=axBY5KiQ&Uf?Rc^;k@(b}z z;)ilKIp6&o|7;>wzw#DjKDEOs{mOfW&uZ!>hSv|Ay05&a_MD4MCdleu1v!*BQnAKV zI4^z$NG7Qa?_R%BdG5LKlF$9?%04$NRKNF?j>XzCBshOBYSp=-C9S(6)J?q@yXv^m zBjYy?i2l8FfM%3v2EdQ=E&ED&yD6!u8$@yM5W1GKG5U#Lfv%gVDUTS&%#%=e&Ctk7| zQK#l919@q2gsxk_zi!6o@P%?IPdbU4%sQkYf>cH=#_(TnI2t8;dI z;~hL`z2U3ulGVVTih1h{R^Cj$-!Lf>@G4&Tozv@El%1bAXMO99a}2Kg78Kp|vGeb5 zy^}I;``9_>Ki+|vZOzV{|9JC;-u7{dWbXQ>91^=V!@n0ZF=WB)ygwcs*Ga1l?WU_o(yl|9H>H5g+1mH@~ma z5IGVJ=d!{Xeb{^EB_EJ?Q~}nCud^@Q%dW-`1@DW)3OXcF1rBL-^0}_PpgcqX|0h}{ zU{@P`SLl~j?pvuxx<5m@nSIGF&W6LdN3+!*Ft3Z^6Z1pes@$^UeUc#o2}JdK&w$A6 zQw-+FC3uk^LO=hjDtC^liY`D>ZD3PIfpik`D^{r+<9J_U#tJxjmksxNhMJc-jQS~JAYtEmNSO(>fK`eE$E@Vn+VtuQ zyM4J^V_wTX-$$9mI7Ysf&76>gDX(GIEi5_YPy9-1pjZ@5qx<1UiYStiq~taA71>MI z#UIL3IM%iC%r+FJC2Or%$!h`ALhZ)nDbhsL7@`G6I7BL_n|A@~MJ0Rc>-H2y+ixk^ zGp?x<-%BDE--~ru_mI30L3i>$%mzi=9S>*zqj=oMOST2wZ_GDB7k1isj_$%^PhAjp z#Oik8D564q$qr$Mqw7VeC7ePmyPtkb&z%42hE(MifGG{Xwm8m@f`bAOIFD`Y&GqnS z_vQ&DCV+cSC<7>Lt^ff_wv~rwSGgnB$jar4%50myQr_QuzNEdNJHz}iGY)+x=1tN1R=LZ}*`y;^ZE2O;_*dO1x(UWrxljK^ zezF_du|Z~WqEzQkrec$Bm#m<>SO6qSg(xmqsV%ollPP!}ImxsC94ulFK!$JQQ_ttl z^!6==1w%_RN6tb!h;6XhMo6BYy(%wqljsIB!z1MeoSLD6#ORcsQBaV`g8hm8Jfyz5 z?lc`8KEOH2sGLUXu66Pr@R z+9mA>J;>batoh!1TJ#F_v4MjfO`zajL2Zn1hwX`DkGuBYk~)!igydSD)pSP9UXB;z z;soh&^tctG#fi=lQuABZB4_GPq3>vUziqLDDGAC2Em4(BmO;GQ+g}qco9M+ zkJvSATG7kTA|OlQruWoD0tQ-krUv?2&=nCGGPYJ!X{(WM z3ugqvrv%;Qqm{ZvSKDO}Y8F?BW$3DE_pi_DXhfoa2HKS1W|0{ylKTKrqHGN%S3r@2 z?)!a|UdbuG+5_nIvD@xzrFiJ9XcUK})fLj>uT6_~$r=bn={)|qw8+qui1pAldMie=f}bZ$K6?J=@{wfpU#6ojG9)$T+sz+t+x z=?Yk*CCs(`YJumfgk~4MXrSM@bytzEouiDLh-udpB zH!pfjTj$`z&o?_l1gZd_qv=NuBs{ESN4me$)&N6*zBX5e1`$Mn7N07?)ozoKUM+-99%oSXFvct<@`N`TdV9DE5+-Kvx=Nie}CF}t_`!x za2*IsN$v_aK_e`-g6DpqFZMWqGT015$1oBDA{x7Y>Pmwa+s5k|7ix!c@M#6EOu*Fp zD)nH-PG+1iWE&V2mNheHP&nx3_BTDMPYx99!OsnIA^{WdDvLi~ezn!Nuig#%{1mSz zqskrHH64*dT9l4$rv3dREJm}Re`~UNN~K!^jLM^g^GMAk}|&| z{z*J7N~+zyW~j+mrKuZyCgSHgZrv5!-xmAc8?mU7Llj!L2VZ72z&2nPjG!rMDe*)m zhcas_ICX;VsDRWFHNv1E1v|XbW0i1suLzS@Su>q7I?{Tk0U|bCBsw<6=zixc%&_tY zlPP9KYv|iVq$Mb5mo1Pit#(h-705lV-u)`W8rfca)$i=-49>LrW!&MEWLl?21Cv;? z^3X}u?&+%(r^zu%HZp@#2x?NLe@{aSO0w#2?J~Yr8I`Qq#xAnh>LM*ze%(`2ohy*T zofurcK~1_(Su3%vWy1!b76o(ghY+L=%_0pQLdqI$&*m&lwg8~UBejTw8W%=#h#U}` za+vy{^h0!jf#kasi_J%HC(KKL|Bs|79mHKRGck6ZcfNIGVjwRp8k2JO{3pSFd-suv z-7-ks-=VzbI^bF5{v}Icp1czar^lW*dTzr-0T29G0Ip#)L2Q-PRqi`R@fer4zYeq= zYnszE>PYdxdX$0()@<^etd$-+J1{&Ff$Ch?dfJydiIVkOO4k1_9@$j|8Up3Nn&(LghN!Ypvg^|) z%u{=JG9k}dF6=ZiW)zEm%$1g)ZV-%jR_e%_LQgiOe5{lqZ z0+VD+UZNd{oC?M?+vNzVH18G;lxif`pz(RBYF)|B6q;1+UTJ>+#KTU+ayJNnC{0uHG;EG18HU$6M&WiS%fTG17@bIkw^74d07O z)_>PCsHPUjXI6SWZ37c6mCoNgSkpW8JpMoDbf0yaGs9<{9Q|&F*YoKdujl?ty`Fzw zg3*P)8-iZXp_vHzeEvJj>p893>sd*;8>;xr?;rSkjB?jd?rxq3_`8X}4^aOT{=PxI z9n?8UyC;zdyqNEks5ge+f8(>A@+6w9N#K-;c^I;Hk^xf~W|7k^9JR8c1Z!LPT}A~i=&_*Xg-42f0lr^Y-6Qzkj5di&Qo_jI&;(N8v#p8JiR zAOkW-QwRSW(=v!iB&=%O0<_$m@80zd>ub89I1!3nCFJ@o?JL*bl>7xm1wGHmrDbvMc40ZV;8Zfy}x-2{;8m2*iDOryW92Xs>C*C;!!SI1osUu ze9R;`IpL>7`mc7k?tw&-qUt1tWxQl_=zHbX-~)su-0|)YTvQSM=0Z zl-=KnNtWg$_sU<{JBqQrjgk@i0*Nk+mIQFn)s5Ani+53~V@dMV0{;S#Tt9~@sN;V_ zR41(Ibl8b>v6kclnn&h5olwKG7z~UO@&+CZWXp5w`G$KqnGQ6!LHEBIZocngc=E+4 z^T1R@n=1E>Eqb~pd(xIBRwIK!m0JT;*nY@#2}Dy2zihba=9%&2+Zg*C)eX5A-^_zD zv`GH8{f{D<4NggB&p>-b<15jP1UZz-8oY)UC2O;EH!%5HN7xPR4;S_-x9k7W-w!f7 zwc64VIzurLRJ$u5k>$xw>a94XDTB-dOjcZ;0%($GZKvA*6y0v>h^efce}q{uQR!iBJl%T8w33DOi<1J@@&4#h!Y8U2|zA30~8>MdWoE} zSP*|QXmpz8p%4<%!)C~D`k1l^pNb99+~d{d#8xIAP*uB^>kI?QhttKi;jb8$SrJ(c zH;UTO2)c_)Gh;2M`)KdLM&I|o`$(1t*BRDJ2H0Gp-PJ(U|026s(`$%M;EGx(h_@ zjE1ILs0}<1^9J}Oui~#Al&sUMVs)#~AlqfD?2g8$zfzM~KTJ^hjC+~%O2GCo3$vus zJ!hTNh6`B{t6SkAgr{tUUi8pQO4R&8=Mgo|4atM;Wz(;~4X=oVAmXM`vO+jhIs$2y z8&a73BuX*KP`ZM25&1&uSIBm}ew*wM@}kDemf0oC3QJbjT^6fbmRE;g`!ey{EmB@!aQfc@MMtcl^SjQ!#v;iKHLST^yaq@qVeb4U%mx&=-JS{WToir$m z$w+5B08|c}BGPTRmq1VQGt1ZxL4tmhC)3cwpbIKBB;G|rqfQ0&L~YkGUKXvtFq|!l z?|O=V{JAcB@^2PmFeypD&~-|_&(EX;(?MKfw-cA&!_bab%zN>@9-k9ikRxf+p`-5Y z>x3#a)7O2|IG((TQE-iZe zcb_eNHp&lMyR$1T_`CYrJGzwk8+*&k6tDG^Bf!Z@w&R)lu~)f^TU8;G{1!di;wilr zC9afaSPaoIL~k<{;*G!J>6YSNhEq{uKUB{oNNvkvt+QxKFYb_~80S@2T+^A1hHNC{ z8QA)U9vNB5#iGz#kD|J90x!7El7AtYiLvq*A*!)lzATr2W6_P=qC8c8C-f=TT21Fy zl{*)_1`jTgS0LE(NqiypW#J0o!>TmOZ*K0CHo3X;Ek#`J;*{e0Py-eFay_G#TQ21) z-P4uL@FkOwDm`WzqxxPF^>VG{bx?=hLLJB+_{?P}bQkCu+{q0L6_9C>2 zaB3veEsgT~EYla|70)sTunPh?G5@Evr~3jBh!$2uXh1?h z$tvwzZdR^Q+9;3Wxk}S4Wo%2Py9cXHU&M44r(~ zG>&J4;Gb^KE42MU3TEjQdTXTz0SU}R^D)+%>(em)a(yW;9ru0cPOK3v1`?mI*D?!xY2VS2dbX7>R zBpK?#_9elGoKV2)dRjs0E9&5xRc>Zq`GtlWS7i6Us$@rlo6g*ikgm=p>RF`G(laO2 z6`>X!n;>VY7d?#FTNVsIGO?qNTYlSub}t6y4skxsvGOx(w>ceqTYYo(Eak!}7;YaO zZ7E`tiA$51DaQ7;`cKr9dqSq=T?U#s^>>=Q$$+fNy+&!guPG=BD%38AcPL(}2i{nF zRLaxvu=99t>!RGnFA9Kn*DrwoI9+XE%+?b=6{F*OPPaZ5KEvRdV8rgGkwN$D-ayoD zsmA~C`BaAB-Gx6!NURfxk7??LN@*-51U@tHY2u=*RA5pnZ7*6xLbt{1CWwaaTGK*d zb9j(uR_uRdV*9+X^_Q}y%)d+vmNS}-<#yjGXjnAW@_=B*ftypO@v19PA^g^PwvTo4 zsT*{g5|>mzAOAtb#Xvb4OdFJ8*bw6nH#kT8Sf_S<@ZA*mQO(08&gp%vGY4x|i)Q=> zJ&tA`Iwa;}JOF(4xA*cytoo~Rj>gto&ceRd)ab(d1oH#QCjiiqlAO$Nzo5Hor)jfC zBD+w+a2V^~TD9Y*Xu>KKJt-2=kwf|jkAOs0A$bqG>*_b5X7}By;Z)=k{FdrmWSvLs%8OU7Z%*oQ@UfnJr56 z&xt>BE3+r6lKYYQB1y(su(>2s1|neen4~l9WNUcug_;lr(_SkIz`m*_mi(9F);Y^h zw(jnIoqu5gdm!do{)MkF>EsahZ_usZ;hfjc8k9Nfx6T#)tg|xj_^tCmKWkubaY}pg zM`92UgmjuB?mnO_|ZlJX2?Jq zie@f%{*q@6$ZfTy983eMPS%3&hQ&hZis!BVSn#V+~!N5sDZ*#fl8hkQt*D~oIJo9)@$SQ2C9dtrptg)H%MCT%LZ7zdiWL_ zAa0R1F|E|>yxNHmum+DfsLmh()u{&ELBIvc&y`>`Sp~3hiz$Fb5yE~|!ufQ7)i3k0 zxYIVkI_3OK)!N8-)Kz)8pa&eRDD;{uLd57jw&*ylKuGkQN^%Y}?0J4?&Oj?~@P#Ti zq^Sb#xM*yJIYZZ~iyHv&bJjXf473U|?^@`*JJ8Chetom9RA^kaegAG|ROwdh6HO}{ z9*Xs9JM)OOcq1LKzis0?XNPW{n6ZPShg-HO_AeM@e2{NhNP(bx!$PNMkTvA&_V8Hh z@|tXwT~O!qga;wu6LTr&e55zjTF6HqL${EUM1}jV^XMS!w9Zda!_$_b-%tIL`lre*KYuh2HuXe#18{ z^Aumg9nKeYkAzjzB~&->-+H1llvLh>-Ohr+)@jkzw8cypV&D=3QZSzwSNuI>^trH& z>y3uJq%{t}e`7{jz3)HlH(_GWTyzuPv|L6?Qw+hHE zJUibS?c9@Z6?H(o^d)9m&IkF{(2UFPA7Y(z(!+m%+#t`<`F;A8V>#QII>Z_fZB`wa zxa)T}z`n)gt4O}YpkxVSKm4YyIQGNnx<0Wld-xaL#pBqQWcw4)PYy?tcgur~J7Qmw zgHNo8u`h@D7fPyw$+0in`R_>bEm#TAL<)ZixJ>Ad=zbKg+wl#C~w7vxWa z^TALnuiNFOe3Mz9dC=*jL#^HyrRx$8Rqc*UmC>7}2n7?54h-F?r+AHvfimsIr{NQN zzGJ~>zlj0rsB_IQ>+$G##@se=`Rie?r(eYDnaoH2wxmAyrhdya`850>w5+ag8fa)T z0NUl@fw3<;g-=TgDQeg#OlR!N%$xlDgTEvHv`c=(8|B+(&)Fo5(d3-MDLIjDPNINq z-XrR~T40@$*}TB{qyRd)dx3MLz#3dB%M|fz(0oUt*Fgr}wsQSoJnApACpvGtg*)9zcacAVQV zEjbOhTL2;|xeoHkbzqn0ahW%c>;xUn9AS^kiOzRmC`5?C z^w}5Z*du!otZ&&P#fHqaof}6KHZSm#3b^pRu&*e#aZ+J(^cxL)-2kCn>~-SR5BRYQx-+Rk zevLf-BxdZAVz`JIoHCQso*- z2U0HQlWBw(2KU8aF`VEzcfFy@Ew1n82Hg`j^Zj`x55=r4|G$L$#y_gXL~#B{ST`(%4pegD6Y?35>iOg^wV6iZgYzHRKG<>wg+y zDZ{5{>_3ZpW9pC+?j|x~X&Mjbdp)Ahw>wt2Esr?S(bnX%k<#Q&S~AGF58tLp7beF% zw?QrB54#U#t>i&zRnJ{qKKGLZrRdiC&$4xbyqh@9fyU-_vVk#g?zAQmtX z9wbgIXI_Vp#G@R$b4AT}lnlDR|5(c@X*v;X!#HF=A`?l#mM>UUbMiAVoHaexh*swx z7l449bFkoMn-?|D>+?4*Ko1TSQw|5(h#@^h_|R))NDjhzz?U_x~wW_KVq?E1EmSZktMA zfwn@xqPk#sd248KyK{=QT*!mx;_EdFDh-%tc3ng)-He z6$*)a5m=C`%FC;Yk(TnBc`u95)Z-UYNp?mGtTfNd7%!{cUuf}c z={%f=jEr!H;pxNxUe{7NS_-qkeoy$^wuuGyMFp6}DYZAsINeJ3a71x9bQn87cqyy2 zx4h<$LP+Fw-Ochk7Zd~=@a&RloB5{X3CXDOno0wlrDC6db&)P0NO$hD3MHJ^$)U?m z(}S07A_Ag4BP#x>h>WkU73@LBa{XATn7)-Twt1iOLOwY^>{0$k?y~mOFOZG&qG0-~ z`(%g{CvgaTG3WSBF-O3(fSuJ%(kgc#Y>E9PIr8l{aeolMhl{M(hC?hR4pn6NMdF7N zilIzA>z>ph5I%YwPpjN<=jyN&AufgT zOm>YR46WQPtsp-Oh`8r5)s-+DYp5`*slS<6QMhN#D?jV7cY0K~_F5)>I*a2Y=&pKR zz{RRWkxL0S`i(GQMb;!QdnqWyvdP*)zHEZ{Z|+_{rZD_1Or-BfgCrEmh#ZJbgZ9aI zCXTkZjjIs<>2kA=WMh255USi^=gETQiI~e>*-slBEZY~KmQ&?c$Dj{FFT5ak9}^VQ1_U+5SSY?5mHJlw^K`CP*1Dn7UHS;prT2nksf zzUa+oB%dXG$WQvk4-gZ)m=E5;M*@2Om?kVSuctB?b(d>rfuOtd77>ZpXObOp1olhw z81oopzO^EBY0!P=MjaOVzCSZ$#w0JQ+_tAiP-3|KjR~c!SzP3c zwit2UU^u4=i43?AYTEia_w)e;Aamly^8f-3DIusQHz?dolIyZ1av$ zY%)XA4U)&xDjW@jWO8toOKkb07pJx-&J7_b{IEYs>)4@K2LD1)G{s+#&Ahc#YC{WJ z=h{U>{p)s6lu z3vR3~wM5EhJRTC(a3a+6O>f$LGivAoIMgaGp2wrb=GwefQfZ}?YcBKINhr6`^0 z>ew?16fWiKQR%+;&sO4Xiv4WbDO|V^pJL%%hY@>dVaeg>W{9zHq7ws{Y_sCQNiw!^ zqq!(AJwN;Y!4wROxz*QMRs<$MLexpsH({Rw`kkX`VB z3@PBeb%8ai=;HIFwN#b?x!eIQf-K9q?Hr^~>Yg@h;t zsag6eVY~PgpCx4=ABQ;c&Ncuv9q5UtH?J>e*Z-MaPX@dv?A`f$rd4;L9;L%-_ak)&&?8f5kNb}X>D3oC=Qu!^_6J2bIhS;Seqny6 zj?temtjM9U&42=xT6iQoEFk!~{*MY@mZ5)q1k57AnB|sTVa4H?>?{^OJ}E06lP}yG zIa{Sl&R`DZ0Ef6;FE6O;FAPMixDPde<}v*S0a%Z3pUo_?5JvF3QhWku&AZs<-9mmy z5+-2HvDH1ZPn+{iu{AUb>mwt?6(bM5 z8`aDmcoJVSK9`A&fc}YtcWYv%uPIkA+3JqO>WvUc1ezGn<19)33+xL=a%H*XiO!?u zG&X?LY>`wo=Yc=U%Ijg5WmgRZ_mB`r|1qQkr+iA4Hq@@lXVv?f-ABXFgqFfHYu5oM z))(=j-uZfhm7B5I$ttmW5Bk+26{$wVy`C+1%IbOr;2hf(?y08jMZe$dOfIp`8X`rj z_09KJYp;?Nxl2=D$qh~#uOAtqb`Wz`m7u_RqE)Y(y+l%GF{u(?J6Y_elMDM$`r@p3 zj-3%7pB3-cHcm2r*~H6c*&FQumq&ffZ?!=&W_(xjn43U$4%wDu0;3FbD45_}Txy*$ zhs6^*-!zmk`J9FyRYOG5$C7yBY7(N`h&2aD|aWM_;W3vev8aV-5U$s<4D`+>CV zX?s0g$}6Gm?zl9rF%V~asWlE34r7Ny6oN^MT<+TX-Q2(;iFybn$a{_Hr9uhHt7wnw(TTk{{ldseG^HnD%7uYj;b z1>H68=uSnpBFRNM3-@ei%|vTt^qT+cT@QmU#f6tyweK4Vlx>AVU#{asjun|NR)=$k z^_Pq$E*O)jtg11J`}WR7_X{9v0)Sr5bH01F=>E@)j{@3TWA!|db&(6Y;V}u6aIYMx zxv9ZrxuX~!n!}|7MHuqn*sJ^ah8JDz)&2S6WCqM<3JXM9abD zEete1$3?prh;nYrxB*OZ8GmJYTyEdeF&0~k8Sytmm>wIoGt>!%D_IJ;`#OS*(#%&f zi42qM8T$+8`N+B}ca*u_DZXLvR*Lkw&AWvk-gmrPPjCxff_QdTFjP66CNZm)%b!h1%d5$}(Hg@O#z96B3)`By3K@E_@uQbih&P72S5-nHI zeePH08JAN8-7cm;)TFS>OBL6to`tEo1Jti`>S481KK4sfStHHgP5+S4n9ZpldZFn3 z$BCZkv3>R^g8}46y#kx$EPLrb)`aF>9;s;86zw5)cw6WbJ&xBE_t`P|4GH^~ZOdBH z$-^J0q(OK!#o8V+yKAYOZ#4Fh5mT^qV+Wh1Iy)fJuo-ulHsM)h)3p&XmZD{n|7Ofe z{n;Ytj0Q>O^MHIEC4pIziooq{-b33+s|YEK;0R0gW=NquIj6$?-uym9A}M=fZn-h0^XA z^&ZbLmxpp9H9pK2 zaco%myI{s2m+{%3CJ;-#p6Q(LTN`mtZX3b3IEMxI(CoHxj*+NC^TqoZ;5A9B}@U4$3zw@vh1l#tct|z(g*jtnnGMrw~Tz&h+?%L;R0;m7Le2O zApk2J4sq4(6U3Be`Irj0m+{{RlrEu<-re4k1$EK>}RtM8` zMR}$BPn~hVjt*p^vBF0Nwzaj9@VjImDJIboJj`i(qVTa);j)LBirG)stJjT4Wo{+d zBoe7?!Ag}e)@i@&KxS4Ldwnef#Tg<6Y$Y&0N<+3Rj!CVN#CsN|w~2dTgw{O{@!`}@ zLt-hNaW~>-Dsk}Nr;ey}jqzb`Ji1sYL7LAnerS^Vi?hI6d2yfsH^-;j4gl+Z{L4?6lzX~y3nbXoTAv_@yg4C{<&chgoedCcEe zK3irHZ&ox(XYkXpHEga08fls?aiJ-i4att+MJxQNP;hv`x%^au4+X!F@QR?kZZWU3 zK@anq3yE083!&&w;BZTM6ceI5`!fVe_Rv%OHCmBJ`NoMa3N`pzRk!*ZuMtXDnn$M|OAJ&c&xnHTLdGZjF6nE=vq}658&dnr-&iOW zc8Bt!Lg)ps|KG^uVIm>_rbu%|Vz~~ZZXa8Yjqs-@3`_TVGi5x)JS2AW1!I2lQ~I?{ zvBYwjV5K|$5?#^cW?tzI&oN5zXv02Fk@$+le(-R$ZHp&77P4c{B1dC*WHlT_c}lE9 z`GmJ0%!~mR1WX1~;r>HWCCi>oPtuO(z?I#UxFrt)szc; zli63ef0<=wzfE41t$~4Lx2}<09Cc@JNE6)Qc0$G|+04dGDjc69F;HvFhzNo(gOVW~ zFz(bk7Xm#F8>2m`?m1#)Je6*n+30COY=-1BPw0tV?*2@={iF+H0LTH5pkd~C>{f+Ic zB+tzKCFVEFHseN&S5I1>UFXK%_$j$lCh@gnZBeBtaB@O+0)&o=oD0iZrf&1dzhwDz zi{uB?W+Hlt73x4v&|fi$>{I*gu)S2N0@;^r`C{nE$$S%C_(_5}$g|>fOdeu&XQxdw z52G({+^ie(uqSd!tnM{W7;Elps-G`YomwLUO>Iz!M%p1>9wrj{Z67AScxasl;4a&(0_2Y+~z!rEBW^s&B0C$$I1PAF} z#!~5a=iK6Cil#u8K88Zxn!|+iy5;z-(cC81DYkj1q57J%RXh8ER5=j$WA!?{d5Ej237hSBGAM<7g@b`Vcf`3lee(LO=inlOve(v9-T+ zitXNo*ql>1r@8KSsem0JbxKpX=J%+YbPzqYH2MoN6PO*bT zL1MB!NZ?(I7&Bg!21K{^K|c`!Hs_F;5p9q}5XYHKwY#8Nl?2=ej{SjbVf%ftAHDv& z$FrDR%vbVX-tFBT+k`!wTHDPjd)VHj`W%v@jani-s6->t`AR4zoQv5DfWNT`7EzR2ktn(O zNN_94^nvx^+{M-B#6I_OPjjT3|K(fA7?fpu62Ue?dz*UmVDyoR=b+RJcksVlJ#uoB z*SspXOSa+Z-iba1lqKuEC2OSf3kwRjEie-(kgP}Ry=7~35~K})yj)bk6rx0LpX}LR z#=(0fn!M+OJr1nLru8XG_)NCf%jxa)1kPWf%gF^^Arq6ECYkuRm&gy+Z{N6zpyYmy zRLTR$!0vJxVWm8~BK3?TO;W;+)SnQ|*92>Ji33VYW$2PB_xGk{xv5yzXUTdx zX#iZ493~-*ymIsWK1n^Nqc}xjohwWWmM?m2)|LV|0YDjH)Yr!9){wmL7Nz+RiP2yu zk5|J}ZHVsUhF(!1v#GY*N^s&O8oKOXI1i>b)Va#NO55{QxqE}h+slaL`Cq2MQx=>caL#d3M&H895_3W1e6PD*jb$g1war zyRoL0WVFIFr-jrRRqoy83T=r`T?Njq_rA>(ff||aNTsV4a*0QcXV%nn6jZqj^(gR_ z#!4Qr=(P530SeP3MFWu#+E_rswYlL@fB3MquDy^G`Wh-ST3O7UqmHD8e1=WTbw}P# zGtCNSZ7~<<$l#hU(!m|?>|ITT=AJvvPKp|V2B7L)<^HHx!i7E$ zOZ0D*`w(lTxVe_zDAJDQ)5-u~1@GsZ_fZ>Jn>au7tBG}7YcP`X&!);F4@*D$cmdK& zHhgfL`Lugo2n@+LI8}FO0?W&Ez%0prRx7(AbSudeQjKUHv|Z-$ZB_1z)6B|7L96$a zTC#B^`NDzJk7%~&rG6BT`MLmN!ta4heO(g*r?Fw6X28NN8>d6M2a=~{SGl{jWkA$y zlN*B_F^L#mefEEi83&kWwEoWqZAQmFmgH?G2A!$RXu{zJh{O39wL9~9BTXd!NKj0O z3H47;sF?|kG(S>jD-4?MfEFAN#NyJmyO7hGdzc+p5KeO@-(a2I_gZ!T%d!{b$4c`F z5J$x?d`r+>@n7e+H&{b6ule42;|6O4o+WsOx+`!)k)>`9iZSf-t7LCwagk-LIG-Cc z@R`b~=_pI3hqHtmbyk@syQybQ7hOVeN|NXQaei^5)hA=8GwVj{w9K5doI7r`F37wx z*V%LNmhzs7PNw|6)Ag ziL$xG9A?b7Mi!FHx>KC>H(8T2U-+AoHP1Tx@@@>o?ZdS)OoY>mYeIACCv!j9#Z@)A z@~RC7MDwa3F~wWqUXQyS=YQ;2W?j2Cpi`Leg~b?6(41m@&fNI85ly*OHG`%0hI^gI z=UKnbSmSgGTLYApI>5>u7{KbC#AgH_qHql-^(PVxt=483NM~|eF<-c6*v^TB)}zo9 zGyIWkry^{PJ!5!m+Q2~5kh$Eqk(jY|E*7uEwBeOPh_Ti7hjr&_G?xPdH=Yp`bzTcw z<1+?2ZDDIzG`AafKUUR*gb8u)gpFs}K#3xOY0XH+9dsw8Yb?EAuSHx5?_cKp{y&e_@I-Qt<&d=;_!os0`bh3h_-f>gKzxSLo?Pe>V+blUSZ1u6{$^>->7n>jwD$sdV%p91kR8L15t3R3FSbbZ#EQSaN zNqi@=q5No-A4g(;V5P0_6*aq_pqD$sj34!l>Jb(`154q5>D85#*l^JD&d1u&5x=J} zbI*LvId-$v=gir_RQ9y{;?t5SC&zA1;x&mI`~7Z(!UP?aIejUEc~Wt^+CB7eaZZQ zqmS(y6WfMwlBg6`J>yKc%^Fei%N^|%+nd!wkyRpQ>m!i?TVk^8o)~wQrf?+UPvE(W zt6)KjHi~LtFJ*Y|{Pi{~zw+ci$psw}34o0g*PN*VOMmHCoFM+rhMXmrjUDU2HI~OZ zW#J!iT9tc@J%+*AW$w?{>P%}s6JLAxzK5L~ZnsYIWxgT5!Vf!Kyn1rpfjd)c>vGD@ zG!;OFF0eRktAmT2H*dG{PI+vhZUwk^Z)MFWidC3mlT5LuTCrxk;~_{KI)pZhnPQUpMVc0>Hkzts zCwl0TsydEb$@b)2d)x?T+k7i8zY6qWEpeg{m%l8MM)&~~H(#}xMhs3jG&+G=t8d4c zI4&3Z8;3aO)Dr5kGi8`)7DIF^W0&g|<$6N*rJug}T;JrGHxHk9gOmJLnQ)z&;5mQy z$JrEr=kq0-K=LR&$L9k!Lx9cjWO^gKz`5)RkHUtK4PEOL`+gqAUtB51`6Prw&Q}I4 zVa0v1l?gGG-P$fGhjNvB5C>4GOl!y@738**x`*ZCUK;l|8q!MoLCNHu%L-qddZ2kz z@+fhkc_51!9v5>C4R)<^NVVKQ_O}6CR4LrN1GW z69ObhaQiXYpTX&*iFR>O;TUYIvqy{bMYl9GQeJZgw{Xme0*$BWMVGpR$>Uxmp|Zd-o9Q8TrMl!9)7=gZ_w@!7MSPw%h_ zqV?ixGWNcvT#z$=F87&tB|WfQo=x_avG+E$TPGJC2}P{NQ(2FF3?;xs?w*Qxxxos! zrU{7NMq0mCp%Cd<|HfvV)5E%6eFFfymKA>tk7mkBq+&Vka zplV60xCX)sO~}Nl8zB(Sg0fbn9q;cOP+aa`Zd~uF6v_;`FOAjybL!WbWdrTGn$2|A zhI(&eHVKHM_lQu`(lo-hTj&=(jh)@Vx&H>Y5Ha=;SNSkZG%b(wmWy4^lcD6|`i*I% z&=%`x?cW%COunJy0L5q7#Sl$e{EtaYp{y-v=J5%%g{k#3;rdSTjr@@8WC4el(K zy%oELsM@Cm?Ax+pTS;Pd8t=z4S@n030%yL8py+8DpzXXbq6~$ncGK`$_lFmc4>V$C zxZ7D5xB5oc^fS6Anbkq+ONs}fr0I*lHA_k4)_U2iTqM1W5U5_g-j<)O}FeQmEBJZb*Skt0H6c+Es-grqUFF=raeB_>!;L| z5Kk3IuQ{ksSsJ*|R(pJQQ_Yc*tW3RcF6HHhW2CM#|ih%JDcPs}g5--Supv$sCLby03fDlrLke;xv&g?hS8Fr6# z_NiY~i^L``B{pkrZ1OZ;Y2m!=+P4v)ze1J#eufjfhd3xEj>Iri?k~6y)^8|WOwg?j z=_p~cdVBT{Lq5UMp9D`bZ8QVgiJtl4ZC|yaRt>7jrQg_!aILdt;vvY*JQ4Nwtw} zqbo&2m!rTh{j-cS3(%3{U*O7Z!>Fh>LMgn^Q$sV(HQbIm@ovA`OsrbCz}NUOTPmp+ zuXljrBS|9};`r;dIoip7Umx@B9#hvG+9ys+PDAmh>lj;W#@ViIa$ckpI*`H63-?+1 z8LOSW_gN#y+;Oe+%$?}uh`S$V2&r_By=k^NSB0^vB1o1!8)_#6fh?1r&=uwGp;1oh zZ>^%7$Em_d>8GEl;Hr%Z?(X}Y-~ZMcmw9H5^Vx5$(*lNvBDj%#5p=&@=Hx83`j4*C zUvC*!h`ioS-6Yl`8;)j)XjJTt@!rQ7TRq^pQr%g!)H*ZT(I{FnHMv}8{Rx>sopQJrE3nK}^M=IcPT($B!%7|8St*8G~OR=dwGb#7l~ zU7)v-9{3U7B+Ta_XxdypC=d=N_+VH!7`&BSHh*1a732)e3-`qzxQLs(15KTdc=Ey- z>)knb*!%XzChky`3ntCtr$WhKtw+_rVc>gq?2Q!wB_ndW z|D`u%kz!FUb?mTKP8sN)gxrIquN#C^^(y&Kn#m$34nVAk5GVq=bx#EJ6uY~ZMJtk& z4E`VxyVdgCoWn(tM7!Qcn^o?azg4g&o5g6Wxy#nct`q%b&~5z@ryz}QS-fk)`AT`e_dtqdz3u(!gZY6dAa1 zd$>D(vgb|CAmU|xCRytjZjbComXhFKX)azqDTT-+#-U+syoCS@epMVUm-3`qZVEX= zXO16vF8Pv_{T21K`|wcZN85_!rnmT%nRxQ^J!%&h#EUP%&qrQV6%hX+X4c&m)uB0J zBy`(rHH`LHeopu7|BQpmMKf>~pQre&=F`Hb4N^cD&leB#`6pzcI!y-N@_O)xe{hU; z&Xot4Vzu2XwcRYTaqf5eJV@feOLdgb4UvMPYMeH$g6oY!muhLpJT^u}CiBh+ZBMX1 z(q3nZQKc>ShWV74mg2>=AjytYFg*5Iyi-HB>JYYRi9o2W zrd;%VtQr@~wGU0!m-lMJI1Gp%0na_5+w}Q&`8w_pax9&6Gs6pobufk6pictR1d}IR zp(KIx?sy_8j_IhL)5w8Di-|7V{<9#8Xx>+_uo4+w1PT<64=?DA!mj4O;H@ekzfLHk zCU2({MgZ6{?|3Nf3D1O2eyIhFJB+ZIRFgAG2@cezYqjRQi@LYJVrb1p=m$GM=ICD8 zplWN2)dOT}j|`9h0v&)gZ07!>*KiP_9UK(@@t`Ln0#) zu&+`0Kcp@!T40HH^zmT5cz=8rWW0+e`crf{(rL@YF5|XL?3&S3Oma#5HGtJD@9tvb zbVuN_+yFq+I%x^8VcwmYcYw*v0$CR6E8n7tUFOCv3RrX7N{8|(P;kb}r>6K9WxE)! z&t9^R3UD!Hb7Qv;&8Q`seezIygGoN1YjkLFS=ORSSq+m1dVHRfJR}A2af*x=ZlsSr ziTPbT)H&r*tA}&equ8x~ywADfQ7fdU?|zhW2RMD7{0EYS5593RQpWaRhl9(~+wL7s z-?tvMM)hm0JCiXCZW60xCzA%l3i~CtlMF%2ojEJ43!+_9mt?C4im*t0~0!`Z23#G?2m4T@J0SWeU0} zua0XMDb0+iH6;Mp7kjJCUcJv_w)Jkd;*rvU7IGWzn5(6#7&M1#)vV-Sd5zJVo6gYm z5Am~}(pi_g^~d`NL!qM?;p?CZz`Mb~JF}LQ`$Tm16UhaSSGS@B2i@{SX+CGY*mPgi z1)CecG=FhPU{NU<(6Tpt?(G;f)g+*qa*M}hExKbPLy*-{sLwrBkNl?%zxl)!l zYVP2fe74ADv#g9&?@PKs#w5OdJ~7X?zfAGSkoc&07N_Jen9&#Z$(X(9g{I3^5|B(r zMBKi3k?bnr#EK9txpl$4sts!)3sq1pso%*NKHDI2DFLxlVK_q>i7At?<%l)y>N)Z* z(3B-QQP-|W_YW(KD43Q6p-RW`PfkRGR>uz=;3$N5vHFV}O-YTxv=lc5WtyDGeh3n6 zPrXmVgRr{Q&T4IwXQdWz0IqodKijU)QPd!${PhsxtjPpFJrr-pww8fsE|a zI3gMtO9NE>Dg88Jt8Yt5Tb}*$J_d@8qFqv~JaQ0W)?$ly+FENB6$O-nR|LGFprQttxTwJ*28iVQpEDbv@9%s2u4HDO`*WUi&U3C8 z1=~?v3`A#1ne>xo&SzwKtd+h2bjoy9wjrR$^mgoNcM9p))Kj z@KW!7i}q6P^u(w_^u~+3zJBZ>I?9ZL8%j~&N8yP)1%ed0&<{%Zt z&)$*<7Y{76S3YeFKJ(Oi_*N|fSBn2g)zyy)R%l82ki*(MT;P53&Gs&ePp@G#au-S& z{(-egEy+O%EdjSv@lt57Q3m3d_+Bc{+4M~^QF2AjL@iJ3rcCPt-%ABK#00W0d&ZcO zuV)@QI(E16DTEQ)xs+%>D~r0Md^UsIZ{5zwA@bW~d#aNPG$fIjk~*)u@(8hbvcG-C z$Q>MGkiHj}yxb+Jx`j(~vhStYIk`KQe8ka%lh8U957-6I8q1T(M!EG_V_v6+VMsx% zN2V_G+oRSRQzK4-qPOFwU}4l-c=$+-oQ>r0IOMY#^#(Nr5lf9E;uz?|mZ{6U$pt`> zB)`c0Y{_T*xvWSy6}-QeaKODm!c95n4-mdraNh!JiYRM*tJqGCsVZ#H%oUI?A#DZL z2nS1@#Wk{)1Vr@wX@IApGw2n5Wz9>0_L4;L@mYcPj|chHVTW^Wpi-p>PwXZRDZ3WT zRWAGLsfy)by93VFa``*qwEQ%8vQq>-d5P?N^o?wG2m>B;(rdNs$~%N!te7dMZNb(& zZfrsi68N9ENpG*5F{V_K|M5R0U44X{z~3FNStNn5rqPF+{#K*731>5b*zG7!;u3?b z#2FU1eSarB<*W9Ye>ch}xJzu*%pQ-BV%iwrOWWDj9JqTV)IFk0M|hU{f}O0{Baj`3 zFej0@AgP<4VZZ!$BWtQ-eV#k7rMho(~ zTIn3EwtxQ|NN=)wSErhCiGl7sVB60bqm#+@S^G~TbHON*ys&MRPN%PQv$v#N&d{XA z9vVzo{^}Hu;9SU*0RG0;4J~9XTTP>}f{f_m zj3g4;&df?WQkYf~nnrrmRK!PXI7R`dPoh$cN_;TFJ%w^aKGBaSZd25xS$LMs%&GxC zk=X9lT}!ouH4yrI_=JElCTRGPrE_kF$)(kxV9GhN4rv0?p&lp0d6bsPTMbE;PLK0_ zwRbR1q;jy>hvQet-LE`i_+Fa2-2c@4P-%VI2GQ2ex|^uAO>$RmD#41E(0Ajsar!%3 z!m%h{aa>O?$DjY*qGQWHAHOnf0p2Ax^==~$Y#-*5NZhaHCHJZOSof+UI@_)~+tkLI z6e6!8<>2Z*m!z8Hgp?1vPJ%#2vH13?k-E<8){G@i40mVNZqE(Pr5zBg?|1TQc7D= z#)rzS;bNNmDM|`>N%re}se*LQn=ewkl=lm)l3@#CIwu`7poCL%(Ab1JcsJprImEdvX>jd(UUt#8+6kLS2nO4Iz{3FUE2}%my zN=j_EhWKt&I!m+!Bx*ao9%q4AFp9BV1Klq5NJS6`hesDpS}jI?ytI#4?ggswPuA;2iu+mN;AZ4h3?Rk`c(UVK%=Yyb+SHee3zX z$?rXWAHoZv?l~a;B1uy+x8~av&!QQMALxX=F3=dwsI$NY+050N;)g@h$jerdHxj+I zNRo!7mR8-XgLq5_;bp*8ugRQbaeC{V;3|Pf*3yOcQ)eTz*eWj~EoM1Z@D`cQH)6~- zv*T`+(%F1WBfn4Qmcj3FJ+I@8ZIc&pWI(D_Q<<9qe zrLf<+un3MV{g?nI>e0hxRdUQr%IhV4`@cVsGN{%vO7;2Bc6sJ9vV2RDTjR+^7s7`4L{MYzdKs(F%Z{EL6#CC3Rhq%+BqH{y&R9-wR&vhHC&%l(dD&T3dIe>a%=3~Awe<00ooi&V6r}+FXQ|6GSE%BHv8Wgq z1@q+IQ0cshR$kUtZVU%q$7m=~YQ`4tW;pJEj3^^F(lI=qUN}Rn);RpE<&r&^L*QcHz%ULyF5?nEw4!B!>U9wj zG)&{5cjH&%iVXH+Nar5(1B6_d!eFSzJn~8W8vuz1MhNb}`FT%b7bW}5npXB9!sb}A z5YboVzaG^1JLByaSoikiX+i8>S(ufD@fEt5Nk@tCRHT@SVc*{^(v7;|1lVX8{m@$%g*IOFY*(@ zrlzHqRgKmrdVD0a&tM(Spnaf;am*FQPh=v2%8LmzuUt1nz!ZWo{3JtPnj*2>ri&OO z-6|QuTO;?KrR{Qms!q0ka-|mz*FhsbB>t{7y$Cd;*}pFNnQbDMxND!@!~W_uBd1Rl zqbR{m_PMKMDt;{ME6c>pWv*mU$1#}W-CmeTHp5?5wMKSDuF+rW zpva$E$Dk!C&uNA5KynMc>=u&wVGfb7?NB2VqVTPW=p9A2=%i{yBx?DYR%w>p8TT#a zOd(jWH-jCks$nV;U!x1{ zvo;#(5%Li9RB0v>Y|Xd&e>0XST2`Yygze{vaTprffyuH#n+~ZS-dES1M-^tKoS0t0 z2^A_Wi%!V$4`mOz{U$zNSiyc1X(0_p^$R9CTQ7Jb)4pMVLS^Gyyp>BqrE~dD`UZ%X zOIJut(&i|Y5r8)v|4c#f=KrXF?d(m)fXJKgOQ){(_cARd4su$S{PxWlnS0{-{LxkZ zAl3(sXYkmMN7%u>PrIO*X9%x~MICVq=tXLFmI~sw)QLYtgC_R~ZaRv0omq))BQiQQ zx`MO=U){@s9Rkj%(+2g9+mqo!E3@e-EYWU-`mt&#;&r8h;@QW|3)WvRRKIA6u89)0 zItYLCXwn&P|MD59zqgbOb&p76y&+!#=YZN4dE-Ro6#Wk+pht_9MO%&&0s*+zNUOAOmEybHB@HST~hJa45X)sZhY#Wp{ZK zDBH_WMMpR;g>^fn*CR{OAl|6%0BSaESdj>3{Q3$u`Aqnt=%l33fbx28jYW8B!z!to z$ix(2k6ojfs_|=pFA<6;K1RVT3-!LhRfpVE{2~`^<|3vw-qsqajn7ddE`m-%z@mbv zI5-2beM&jT93u(fsZ9)nj}&sBR=+3}BQhM!=D?@U%}Xjg+)8kkt$v1PbtLaeuBhcJq9mV*xJwdWn@b104)Y;)KIJk+Vj}5W(QyQC3g?B-W zgDu@2ZH6Z9 z!m{1LWTH6L_hPKA>+YPXldJvS1G?IkiFYbl)sy6GF(!JZCsjK-l3l49zFE4kbG8_x z`xHgrm{8{27ZtemVt}hQ6Nnr0TkNG01~>%3}vT_Nc8!-+mL-&&frS5(R)H zelTf?a1>YC!L3H0$ly#l@mzQIkRhpcnsC6uU&MgH>E52Q1VBLKC-k+cEOV7ts(ZSlZYa~ZGx*2fn0&(i{aZ$_c2|(*Bjto`y@f5< ze^RwZV&a3!oou%~CZv%PNQ*{R41LGiZO34(z54z+BKPF_Eux6+I+NfMoX>RC(ksQJ z{42$w;NS_|a(>ViY;T3?f)S#(WqFpGjW5g7DTG)Q_K8|+ zG56& zdRE;d-^8Wy<;1rQ`mK5|9Obu(rKQJGXZd@&$HG41#N(@dkaDs_y6oUn?cjUm%3QO< zTcp4#<^)Lc)qgH6o*MsXb^w8;7!o>{{YQ5J;OZke-Ab!oXdBsp#EgbYgQVS&V5^Br z&u>js^3~SRbhLT|md%1Lt3GL~{jJ(vc^ex=g{4t8Sct!;>8dw=#LM^v-1*dtg}zg{ z=S`{GXwTbbw4d;UwsEk;d0>w6Mv1wjlYUqmORafD2%0BEq@0!$`4DB-#L9kT8-C2L zb?M7;M=izrP1d;kCK;|vB=YUgwqd2Qd*i8B9cWb*5I6ZYOdn=#N7+aO-jqr>se`eK z^@MEWitmSlt%t_f6U)ST-!&=|B)?I$Wh~^&gQZ1D@~vCaQ$_L!N#NK;p1r0$0u*$j z*(`f{*-U4v>I1y-fr$f@&gwGM)#G(#0e(RczPjI2jW93a!yNkhg$7QI{g6iyPw{$u zqb1K-`^)WZACOJ4@iP112gby-jwmD~VkXg({DN@68DHBIw;NZUtEsh$VeQB$-Ea@T zzw_e=?Gy1yEx)%pTK)Ku1ZV$$QMbI(Mr@8_-t_QxV{poyWNdhIyD`RVs36^CYwV;Q zP?IIX=qMND4Evb#C(E!0>x^Kg8-b`tA6;;v;;`HmTs2krTn9a|&YdI2ku zyKXOklF|rkAa_CUx=i_q#s{bvU7U^52-6Z+jhWf3v1LEB!x(TzcD#SR@gfd`HA|Mm z3>r1R8UG8WO{$#zm;(K;59jExNJSQ_SZ5WlD|Oz%Kndm7Gtbok$P>U) zlxM8WhwC{!sIVzzN7=DSFU`#HZMbDv?zSao)yy29Nqk`vI84tb z+qg)kx+%U*6>(xa7UrwCqeA~a`D0F1?fqe=uXK>$7n=v`!^_C-7(hdt?2BXfkp6lSrU<ec5B(swQo1lk=o+I`mR1a*FDCo7J&K}aL?GdNslm*C&@ zHs6+V%e)qgV-e4r3-yoY1(q4&uLTesyUQ*kV`#})3yAlKk|Sg+h_*YjAbQ?_YIDKX zb_1|nNqM`p9+t6wT5^ef(Jtdm-{LU~wi-)M241%B-i0ZTBH+6K3W9Jzg z>d%cx%HrWI_Q2i7yp#=Rw%E&e8+mDKU)Of5?rc>^24_U%AXaw%Zud zziOz$8UC5Zk~m|jbOP=hs{CKNe_rV7uU2(@0v=-mrUoVVs$A zDWlE*!dRE`_Rto)4(wWa2Hzw(w}vOS!0z-~w4sg4juZBro}qc5=+xVo7LQ;Z$Gk zGqT3o+St~{I(b08gOEE*7fhI#fK#du5$q@=d(Kq%6Ok;E#l0|(S7)DmW$>yXj|N`H zqDW4<#u=!8#eJAb#%|}LFI*f@FoI$apOGjgVI*K_rH)3yV8j!Bs}?c`IVu5X!JwoT zJ9V!yI(;46gI~|9{8LhOU)x?)m)IqHjr5Fv{VHwX1BVPaIeshYFbY#Q?N5P$xofjs zyVp1;ecuu(!TmQqdMlAZP{F4oS^V~8d*@ywD`S~*LSXnTfTZZSJG#OQO7z-ecl+8X znjng;HN6Av*4#_9<<7)W{Q?xw!0)>RdHriA!!!3~tr?q^S2KHXvYOY}JH9qXCVw{M zfp3gS5!b5Ii=NlQ?W0BDy=oJ43HBsj-ruFJ>|}C$CRg-h!d*xxne&La6*>z$wSm#r z4sOVoypnRc)9-8)@`*@c7wOlN+8)$BGcdvh1iZfc-o!31 z`mMMEV%3A&>sEJlx6A4Vdj^~Vrwyhz4u9NV1xK*zq5MO(gM!gU2mebKe>;8xMKILp0dZL;2Z6_o1v@LTKMK}B=IojY7K zXNCkl%LNQIk(K>ugE6XG``J2R*hxsC5bfpmR}IE6(l1xo-hIZEDO-N=*f;Dm2Bb|z z`f)(IM_s;BBm3OKW6+Rm6~~p7Iy=vm#xOl1rF&1Sog{az8vK@cKsTM=V*j`g z$@V>14Pq-tL|m~R z;KkiQlba8AC4Tf1OscvJo7XbuokR&RkQPaHixl`?3XIA9d`X9g>#G|o$#&UdFW7Gs zT!3lg&Lu;A8{VlWj|`D}((W#^IEoS)L-yo3osj>P?$CO$7+=OR6#4Rie(}E29rA{? z;Zb|&0b^Xs-)6Shmme^)uMjX1r97^K*uNDEHN03HbqfIfE`atOE z+BQWe3NaVy;rEh=tkTK;TA+*w^^)%6HAQ{)!2`y`q)Y7~jxj0u*7xiy9b?>pj{j1~ zMXu&1hQX|Wvmiuq<;>a8!anvo$LO1P@C@Da#&-op^0D0c+y4k49u5%3|V19zNL&f3N zF#nIyq)^otGNcj=7Fdo@Rp5kax}ajzQ*#?k;MphT-F^GHqe2RxRX6Qa=mLdNYkqGX zlC>p%mgN_nKGCXX`LV9jztduoS$+)7d5B$#Kq3|WJENJCn%3Hu?gIuFS5r|gUpTOwz>iuUp};uyELx5*1au!CDM(QVs*HKE_WM?zS^u6rpE1w|N0?H+DTm`dB%154TQyP#qomwi)bw_v7^P<{2_QxaxIxOcakAKX2OOh!i{0Y~ zV|=DtalTemX=SK;z`20w&PAsGYtI(@+8>PacC#U`&)#SM=LaKufL@I;w%(7D{0R#K z>oT0hNG$5YGCs`q_dgiL{ZA=Q@uCNXWMFh5P$-w~)PffKwjYfl?Pss3XkGc>DV%V|>bAP_#YOWJG$SadU0=T|w`%O$(W3Db6c)-XUYAxUtnAGOkLQ)}zHf ze8`x5?s2Y;D^l}T`fGQFiM~E96N|OfwpzFA?b(~#6zoE#$g2va6@DF^G34?AUATq3-WaEmu5iYu*| z4}H0CPRhQ|`8CZ+*S6`w`>nh_CME_Ci$mYN9c-u}Yo< zMLAC?Jy*2lt1acVj&3P+yC@`MrMg1wuYC7QI^kI1s%Z5J#FA2qGLPmd5`snm(eOA7u;nw9y=R8Me2pxi=Ux~pc!wIG{k{tmL<_LpHjq{R)i#lBXe+ro%M?` zpxd&Cr2s<^3(+Jbix1e9zZkRS{gUR}-ezNZmzu+>Q`6BsW7=0V zBUu`vRWqZD7b5RZ__h~s7d0RYQb<&ukLBR`MT!~bGKppXtB&PxC18B862dU~YGF@Q z6(D?#v(Qaz7{koB`Yq!8044`Hj5_14VzIitGzeYo>UJ5ej;uqnDLV6(_zzg zw*sSJi1z?^%;b55ThAcV!?{U~Hujb}k%v0Vnd4}#mmD=@yAWZ|rGpWkHcrNf1>xQl zOl?quB+N02*GmJKV@wqT+3JlRap>JRyaFqW^;---uQuze&GM?4S6z8^A7c>T22$C< zX}rE$*EbH3tV6In@8ks7G~@*`L6&-*7==6W0>ve4{Ms_#hT`X055nYQ{r=*I3MGlc zFYtaXh1Qg*eweh-3L5DIwxdmQ!W6Hi#Ax}>?AU`)uBr6azegs3^!9*8d3!&Olkmd;nMm7oC7I=D@LJM9@NPDf}Sd! zIwA}zYC1@zw_J0Q4w#2sI3k~%?&9l{mo$BZdjZQR4HoOQW5fpv_S_cEK;AO z-+rLg=pK1_b!)3{19?iCSy-fQqSZ$t!IkHhA&QRBAHMT{lwMPf5dSptD`{|o zB!R*pDU*yUZw=b+P?un~;O8>udKW08 z)uTO2P=SxmElwkOa^dK<*8lpx-6_fJ*K?FxPU(mk5XqjlKi57#$xQDPB!++=$U7S= zdu447m+3?Y?65;g<`}O)2GJi7vR6K8Kc8eS&aP!vLKd7~-_;`u^m5{yUWyU*gea36 zu`80zu@NGYWuoz{BV|@gC|^@GkJsz>J?#7382&p{@#n{ia)N#_yqlf{yuuOxGDQ`0 zV|TMWD52-(#9=mLWNJtUJ4%9^P&@I4jAK_{nd5uu4bi2jIVt@0g|25CB(g4Y1QBRO zb8bZz(PNgf56NSXjNP3(?{M`jF4h_NvZJG2V5MEOAUADp*o!;kg6QPACy-4JffS{@ zDG}iTZ5{E*feLj&cSkOF7Zvx+!%JRJ1r}0CoKO^Iu%5a^A$7su-qA%OJGGk9qmgF9 zxNDz1Ss(}(xe}q7!1P<%p1-ZW_P9~K`?)kUC(iB32-UDgaQQSm~5qV70;XaKpor{IS~!`%J?eGN3nR*>j`KW~lOY;Ro6L`isZ{UqG*Kf=gxJlr>P<1f1)Gp6aiALmbY#dqD#&P{S5Cc<^2zs zX4+XHEfRH-Z)_jxQ^b;9CRTz|FczB&2XwKT_NT^61mcf&IAKpQ&57rBO}r)Z!g>7Z zUL&|gsFN86@>5;Tf8x zurOT5O<(OwmJQB}zg$(CL>6#f!LBQjIaDe+AmUSU+q@i zT&KkGNmpRS#9XS}@|YgsKEEN+uWR0elnnCA>pksGwA%NzH_x{Zwl_x&Ug?_msrBn5 zsV;h1XSDo9ctkm@t6A*4R5N3o-VRX6@B1^qgL9Ie{7WF9ZnJ+KWw0sF0!`RQ5;koK zQ9Tuo_O?4=hf>W<@3k7L1I#q1Z;QP;)$EZO>aV;Zm;9>LBha?hW4!1Ti0}jOxdw>& zIMo~&d4|CXKx4av;vVT!3Ly^V%71s_12>PiMWs)Qwx#k)WGy%aIrSzF4}mxE%whoy z@D$?u^W`lzXq0Y1NRqJTTtNc_8s%kNzv~i<&F5JA<_>02N~>|g-qgXIl-dROpKIk^ z`%+3r^M<69MrLf6#imf^B0B9pP72%ufkjUv35q%V`B}^{2coRui10lRpj{o z?CsARzuJ3bb0Hd|YD6Owv`W=!U(DN5zc8=mMZDb?W+=yA?=>^0cHs)fCmVygwKHfUF74wF0GZck2$?R;>~;G8a$G2%ipW3#SFwC|cQ$)-SyEjO5Q+ z?M^;(YNV4ZQq`6!5rEvO_MJMO#DFArDuFAX(1Y7VM8bz)^UDwp%u?f5Z=0keQEq8l zxgAoDVjZ-YCKPbvQMv5}wA^SK{CA?@*V^@|g3--;5uMcI5mkuy%*v7^=(A;?=Qn!| zDdysp8*cX1iA*8hCpX*@?osJH3@D%*3wK-x*n1im*o*z+q(9j+x|*41o`Q)TD=NNUd6#Zz#SdEw zfAqvVSX)*Ue!r#g2Ty&)_x8PA&7qxtuL=scp-g*gSF>;Dsi(aiW<**M94j6O zuXD#C4mP>rG+&+gV+1AR?oOqYqFSY3*(s67a zS4#%!8rur8Tx~we>U^|zEH&?Ed z-I%ljx$qylo5kLIEh_QI>+0;i-OV%H@I}E(s#e2Jh=@$j@Bmet`&G3Mtd>-*k*{sG z#VKuH75bx_HT)6>yqZAJg9K@U-z%LC@)tyqWB&w(K;T^>qe<=y$4)$k5e<&4bn1T< zZY{M@;-(8nWEG2zQRWI;jrIYrny)Dh`g>5kLP5zDA=s(0a`8IIz z{!KvjR@6s@{C>ml>jWS6SS~kx=Vxi}q%!Ue+7#{&Q=v7k3Zn=+nAWNZ+Qs`VG8S*+ zBeL&OCcP(X4;#W_wvz-O9mnpp$M-O&M1JG8+9E0zC!j+XP4zG#>v(Y@J_xux>x&p@ za&T}Fx=-V<m8%Pz5-dzjwH zUTCCnq-1B&HrX+eD+`C{EIOxM;@hLAf5Q>auVa4XD9e%v*h5c8o8{4&L?9Cu;F3ge zwYBOwuDE->lrM4xOGTmX{8b!b$N2Yt7OU~UvU{}i7ip)ya4lrgB}!G^r$q5T#<{Ai zG|ff2slIqESHv_jV5gy|hcpUD2*qTXglJA(+&fYAw`>Z9OQ}7?txYbU!nMIx7fd5t zfFvq4u5v*~S7N!lx{Zqx1NtN83kfpzmHY`LU~GmkO!3Otoerb-L^m4IogT?H2|l^% zi0qgu4}7)a&_k+>@?>jkZulTq*Vj`w9W|m~`&Py>Bj~zUbmWcuC{HtzrV&~+kD^OH zp_?sJ3zCN-z}YgjJjJsm<)SSGNkfCJXp^<0RCoC0&a*#2!_|99j*BTv2%?Hsi*@)v z86NicGD-qw$Un#)NFVG?k4L)8IgcJ2iopdI`q2u?9NO~Kjv*|H&n&bO7!?pqVgpV&7YE2gB*AN7Ng71 zeE#E6qAE!+fw)=6evQcDlV6F5Csk$4UUA1b&H}H6>q6*}2$Y>*JV&}4;0BrP&lOCN zV=oDp=-de40r!IME&f$p!4cVOaE3rI9#iqd?P9O%q^2$Iv36Z@2X%79W zIY|c|Z#($s8y>a#6SwQ+JT}{T?x1oxFrsootz9DaWES%T7ims-i6Mw`BDdP>tN&6~ z85VHV5xb_hxjRGB?OJaRFKiJRDx=k5zRxxxkFfi{mej*f;oA=jpzV@+?;f@v>0|aw zsuIq901`GxZ8+@C|oYtCNNT8JPn zo6qr&Q0rz5nWsyW>Ne9XtXkv0Dc&gZDMSd8MZ<{?T`3%aeM>)cc=l2Wg=?}L59wJJ zcw&glf7FASpi&HQroF8n05s12zMnbbtQEZpN|hgd!;s^+MVJZS9lNv#>TYMFn8V>C zg@=2%mIO67`R2r$!1@61w&C08AVuUUr*$ws zTDXfI_Gk~=q+UC1eywOwLLF%ugN^;5y2mJJ9r=_58^ftv7gEZ{TK<4g9%9PR9T;s&2p7_YO33&M8UM5dqO@ zgZ|GEoh{2@e4!W1g+&HC-uy*IR=JbWWdAzQ?3*5Q3yE_(ZcAMgsQJPl>@kDPtd!y- zzuG@8Zr^+K$dkizadbN zr+PxSU;_9JOiujw?lX6^Ls(V;!p+2;69fgXlu*A)9Pl!%O?G{5pP6N5Oul`!HXrXx zNvRW0!-z|9T0thou694meKA2jV64LIXTZt&*QcE$?4;qf$^98T8& zZYk0T*JnvhKRnLg@s1mA+~o17(j8;zPGXbwjYr|viXdJ1Pzt$Hke2WGv#rbqw+xeN zBd0RhYKbu6{3vxqvUmk%NJ2Z4Pa9~ll=!DKMLJp;SQk88u>Y4gDNXStGHdvkvrd-X~Lcr3IqJTv?D7ZSEE(QouDo(kyAkcCIW> ziymtqR%X^N2PEx;uL199>eo;Z*b z-Hw6nbLpdmKDvcFaw4P|Rt9}RQCjq9dwDk`@vg+#C5;Nh1(94x!?dfb*mh=;tFF~U zIr>gf@Os6f6E)GmqyzT$p=NsIDNy|ev1pHXd|j?U8#vUe76OO!zp4x_7y($>I&5R?QE!Z{Oajgx<@SWJM?;^Nbcsg44N>s#n1k!*KlnqjCh$?wRL9T3wn zs_G(Cx1?0J+cKR5Q!u9cT6=0u@(HL%$FDtl5dZ7Fk7CBgQv zPwBOyGKKb2-SdIeG}cwYUJmBGHyvL|Yb$cCWRJ6N`|;LbZq*B{tyA%?(7;r^!9+K6 zS1-4_4KoLh*Cb`X(`a=vLy6kq+whjyEY|15#Et}08SnF@&IMiMEQtnZGQ|$s*9AqQXa!CHA~Nd4bPMZ z3N8|7HqpiDfCyXPvU0}w=+`L(t0F15te{#5NQ^9k-$ZbX-wuY2Zp@;^J2cB;daRF- zay(;mk46qP?xs%ToBT(3iYA6-y%Q^L$gOVj-SHG}C034vKWbWFWt1!ctF98wzHh^u z^*J?($xnS=CT*&-8XMhW&46?^Ny0AYoPnzAm+zOOwiF|SwC5y^{K4E4h!7+Og2VRI z&&^(nNzOsNbN=XUv9}C22XoKHOQ91)E~ckAxq%>S4WIJN{kq{O<|rw!W4-u|h5)W1 zRyy7b2QX1_Px*z0M*r1Qjcz;vHoeJhOf+2BvAYkb*Ps-?5(SKLHEv5PX0@iH^#}Nx zU=&%ZE^0}_!QiS)zt2*Xl|$bl-sK>K5dvmq!TC4`{8fkUTDwwo^WW`@&N2u0*cX;1 zJ2m%WVkWGy?>WmHl04~Y`<1iI?BtrT-Efw9W^zT?_Kh;n7?MqVyNXPW*|CTDiW}PA zvS3cY`QR_wN6=3^z^Cn!QD*O6ztwKp4jFKotPX4LjIwJ+nQ2olVJY-`hL|=ud=-#k zy!`^sG2H+>VdORAJ?~6i1-Bu*mUNlVv_BbTP6YnZ>0QtU&*g^ajGzH6!O*;_JK|bh zsmZTO{q#7hCj9$RDyM^Gw3T*zL5!7E24%W1y|hZCB;<9~xhNEqe(DL`X&qZA;AHBz zlc5Lf>d|KR$ls_|>Fj#%1l_1eB6R%%{8a%C?}FTg*`Z3`hAr%iHN!aR$PE5y!Au!D zx%+QQ?1En)Fg8<6l~TGjjb~AQbB=|r!0bIabaH4dvanN0vpI#CiB@60gJ!U_2wmBwgz5U+V z=AfK=P}37Pa*AOYE7Xe`b^4-rwMPUmKcsI9Z;(b(Itv*U9$DeNWg%q@dx}_c3zA`VK-Wu#GO>?%203L=C+I#oT$+; z4UohF>lAKJ$_#smn2#M1Op{j>-H5^JZd7I&atb78WsJz5}F2@`*EPAULEBy=3p}`E(l&-gWUC!)Yeyj z=Rgx(!`xZ#l1}Hx?C%*z zQno$d9P_f-*~H1uQ*(hyr)6c(iztzNKn_^$zMR~~J@iyoC7u)Sd}KNiyAyeA2N#fp zl9>B}VfOpyn3K&F9w@xsX}o#1+Q>&Yzo1-ss@0yAfw0&zQ#25I&?Lgzvb~->)^BI@ zm?zvJDprKU;3ky=DN^+0{Ha#lQ^oLxgm4^yljgA4ISTQf3%vMhuY~_0U@%Sz4vLM6 zhM2u|Jb0oqDKUN$Kj0Q57BQS@Z<&KZfP$KMs&}Do%?2g1gtHLS4P^~EhZ(<-Qdp54 zjFaof{UFv)Iag`zgkU1n4<{c&B9Jm(xP?k5tdUf4ESv4CUCo4})lE#SP_U!x2;r-n zz=Qh1)ZNXB;ehb0LeEDMxYqfJbC6PyHa8ERq;Pw^%Q;+v!Th5W%=GhfuRTR~x2F{P zmF{Z2>;9-daZVt9`#IWU7G%IGK&Wu0WKTj|xmS+=C>z!~1OeeP{0jo&) zS`xWwLq#GS57qZyI%5N zSsJot5T|JjI`l#Io{8qrGY7e^-540iM0`8+AX%3X;n7$3D5y%*EY5^u_J~R5@TBAR zMU%{uvxhWEW8$Hg;jSSrdQg}aF<|!9e#1kf@aHa`sdVmr#{O)QIVvgB?vQI{C;iPH zlWUHvyhR1qnKg?cLfx_L?1-GFdVOjL+uw`ujRD7Ogvp$eid7d|ZaynrVhOYvXI%zb z{Xt@&nmc+qU<14E8m{GnU*WCgi2$ zZW1eWck^8>0vFv{ym?)$z;46i76_hX_|K$aSMQB)0%`9$ozKf5>wyJ*X<}EU^fa(moB& zC#BPB7WctuOq+{$Jz16I2=4@H+{=so%(>>w{zv8Law=!p8Ccl~1Is4yPMQC@U3S`e z=D6I~Favh?RPHS{pG6$Fy>TX|(CU0JgVSET+3MV+f4pUxhvg4~j+o6EcI|oQ_y}P5 zhQyFd+;LXofWEfWsXKNY5PHh$#q{foU$DxIdZWfNmgD&svm+oNp9=4;yrxe9@HmL6ISsp z?R~XxM%YpQ>?oFzX)OG|+MaV{;%JL#eb#CHF-qP{`rrQk?9Mt~K}#LNc~&psODdj9 z#DaN4a}EgvrV!d1*yw`MSh=%ctE^4L8_(2(cni|J^}1SdU)zh1+!tWl; zx|`o0`TezxJ^c;GtbO=871OQ;ImZguRK3RqGB~~5nf98Uly8oVELL|FSQ*S~MJ8q? zm@mRuE@w$MXF*U3KSIpwMh89>85mE-jiuryRqp)xT^S#{p*Lz@?H}cMam#hX{uOZ? z_QJO#bdGKu5t{;TJ*W(fB$u+c<(r)&-M7e!@nLUlieId7chTyQ#LMEnvHUcVT1YCX zWpAGOZY0HigT{VJUFwosa@(i}73}p8VWWXwZr&wAE&oXb&tfMIfDa zA;y!4>*O9c$6iuk_D-JB(Y6cBft`ilT!QLPv(wsJ3e1s{i=vxwsQN@jC!`Y*-9S4$ z>|-Se10~Y-drFt^)6e8#L*ikvBsd6cwMR`gd;NA^nkxRk!8H{*qnb5mVs)BaYme&z1(r+0y^1N?#3lJuJk>+8GWWmsKs)r- z&zp`ALRAd{k{Z1-b=lE1oa`{`wkX51q^EB~9;s|+p|*L@?kh}3%RsH6choq0XK)r5^{n z?*AL2{43fh&NVn+`mJtA4L0ksq_7aoEGtkI^T(!D#V*2~*&8cLC5J~MpGS!|I&DO< zm8T|l_V5|zg!AWHWe6{dN;zef&Ofo0LE6drw^Hj1T1&wjxZxD6l;hyFI=8?el~#HB zLe(&#!)fVvciMlMVcyimRw6|HGl1^em_2BwIrEa~J}^u<`V_f;1~_BfBnk6co?I5h z7#@E2$`y)ff>#K>&3C>3v0)Vw1CWN4Eljg|jHbJ?svPMQ|267Oxx;>Yra3a@K$lkg z=uC55(rA0sEVE$5x)@BEFuI6eXu_2gukFG%CVaxp#z%RA#@5fVADCs18}r>Fp}Is> zi(yyXW_$IT#V82jj=QvPjuGYH8sBb#ky!k#ryTe zkBC)oH8(V-u>+Ab%NZ+Nt@MdA{v-_nCH>2GH@-+a;%(##fgs8D z3{mov@G)=k+tvnI*TX7@2TDV(V+Zt12k__soy?Uwthdq2Fvsm~vn9v1Sz(`vq?JD+ zFIMoCrB>*RRi8l>{l;sTB5OS$s_F3 z=bb#BX%x=A(eSOpnFo9$enn#Am{^hvNVM~y9;{g$xw#P*2>;0Y)I;n+(&y->vRXYh z;$;==Twh;8L#0k*NOlHS8JIMT8`k3Tx-nKKLVve}>_hDSOQo!md;!hJcW9kh;a%4K zQZiPtQJhD8bz>N)Tz$*s?prrlp4yI6x;)UcFksGOe~^*D65S`~1Nie;?h8&+eukv_ z#XS@d>;d!$DADIL_Tmz=cZ5?qCA)P>wj0&3%o*JgT!FJM>rmqpB#MQR{h!}Mcy@@8w{o(TW2`?w(vu`1pYghjZ?w@T{om+xgx+Fz8&0czgH z2RZZ{i8JJY(4_Rnrc}G8%%=D&WLl`pzipX9K(u-dV9A95 zlVL|w?@>b30#~>w_s@OoQRQZzZV$hzWD%+mt$xab^exlAq#OfvA>4|LdSh!;b%zX0 z5aUn#w0PtC_VO9ajGk5N<(q8uC;Bb5aW&tN?nz)Zsd9ad?!ThLI{m~pAB^RX##tVE z5}QI$-V7W+V{mQMl1~}dRr1Y=P08e2bP_IbDlTlx@_Gg4)q0RWP%BnIbFhNu6*V$< z03C@okoaPqGMyjA^8|^6CRz8(sUUro7S5@$V{=f<;cQ6RQCby)chNf{4_%AYqp=FC zBS1v#iCz{7jDi&Z2#AB?Rr{k;bx@|hkUD6!CC{T zN<*hYLy7UaZxKJDkho&ETgc2>;v|7lKx}*6b&?cMN)cW3(PFsh1N}_{LBzRzBmn(t zcQB0t<2BVlLT^}lvUVW^3c8bey4sx-7GQ2HWrjN|IkBuI?rVnl!cL<7nx|Z|VomBcE znH2oR8vB+?#0}@VmUUdZ+4J-gYl|$v@HdL^q7^+vwN-VEr;p_NMf)^$2d-hK?sBf!ZV54zAhL?>^Y{?P{wUv7ftr~`X%n7U8j(c z9RDo^Dg(z=h`0ppM4jG3ST4HA|))BEYA?aE@*k#!Gn=jJI zy}npSZ0UD*i~g%LDiyXvcBp!p-FOk8-dh`jiKS^`tFn~`v??xuI)`?`vk;7|_&GtR zMLLIlTEZ{mU(bl2V}A~|5y_E!OU%#xA}her8muB0uWmYN>I58wu16fx-DcgO8{lD) zZ*Mt6P#IKjA(L|%$o?br6;A2;k~Uu;#K6p9(M`#cM2|}o+cZY!g6t)|Q@GuNSCT#A z?<&!og&)}z!-RFb#K{9(qJo0{7K@7D0zB;Ns89_ zAO)UEaI{eYs?EqP<<>!z14-4(G(Y-MfH*z+nl$ObzgX6KI^2Npf2YaC>#F4YV@VNA zVHaZxd%rx3!nt@G13^fvGi>YDZM$PPg>wxeprSS?oIAJqx5;h;%BwvhMcT`FB@*ab zED790Bas31%oMJsVaE9resr7}E25{1<@rWWL#m#ENmj*2Gy(X-5%MkBZQhOQ&wL(L zx&ZqIYZOwL*?6lA!0Se&AuJhEK&|QMd-$GRv`5XW+G@I&kM0%>UGz3+H(G18=i<{-godBtkybx)ZR4C@ctXyfDTj7eB+kl4OrNU>=**!1Cw0Dg??ozWT zeNHv|Mmk++YefqIZDa<7t-9I1|59^M%CgNl_V9V;g^?1ceg$-g+pKr4Z$rs2%}4J& zlFx;{i?}v5CqnecksqL4@?W3~Z8P&0YbFee(8(N`HW<0VkKicLAHvgdMXGo%R=if6 zvk&^}V|=mRr9Nqv(Rh*J>)1XmyT&Z$(aY@*h3b9k?x-jlE?65-cEktW(Iaeu{UDp6C=d zU*{-zq9@F7x;D|lDqItlbOh=0A>xU5jr-(7$Zh0f3d3$O)xQ>rl5l$p+|1_GDb}_N zViiw?DlP~WU4Y$gqoh^9`kp~PRT>;y={$C`f>^9@wcG^tfUH6J$6j(f5Z-j(IEtHGmk;jVOUa(OEWK=GF19?9KPF@ok8^B96^-27`NIhdGIlN;t1 zyZ&-y0MnHL@FLQg@2j084+62_42f&bRHig48EnjSzeI0K=c*Uf-9p%rP{gsYs*m>7 zmGDJ$(sk3Nh*-s?69q)M$J8k*F7v$@&SsxN?c|TR8PV+`zPA{jUPT#FCeL_UcR_B9k&43-XOGBCeu*$+72ZWR)Eco*itz3(w@S$xJ+9Fc1`Z#r zX{GaJNESaeo^QRY6!SqSCa!`jG|&_0nk|$VPynI}k`in~Zdh!eOjSA=Uf&v_4VY?) zZ8L^}(jZx$AI$CTTXje`230|@$0t~W`nz9g%jZNDkfkofvyDW z@-D$ngkHHx8xc1R32l^RyAcfd^vYx8fNxg7u2-6vcrrKX8k>0gQ<5hsCtW z>!yw14ScBZI>dUKrz2!#sqt@45p3l~E6jK{ORNoFEm4qQq6LQD<*$fB;T>){F2`c5 zTA1y7@y0RH<~ATY{oKn!^|J~mL>Hm??ZNUyZ%f6ZRnj?g|9rh2|HzgM&}Im5pDs9) zA|bMTS30-fsH=y(VtY;XR~~{lMv5~50J%qJFn%%Q8kyjXABh^CafG1JDz434rPDt_ zVHB%Ntu)N*DxJMINV{num1twijXhis{|iC`FYwihI$Y0yK(9!=Iqpg-yp8|h6N-1P z*E4jr{;|?Fe`^kj^#6-&$q1oIZedcbvY45r-q4ACl=t^}#brP39E5#pk4 zgKtwEUV6eSC603$L*!TB>DL(+6Pp&Ho_q*Rm_7jjc8mMvT}Lu zXVhp`#r7GDsh11Qa^r7OM1U2bYpYrNfm|`M@+oeLPF~55KP}JUz2d7p6qAcQmyHxT z?zFa6cL@(r>AZ0Z>qxRq0+I{(T+0!h>y}nDfDOf74?eAKTRkzM%wr}JjPiCI1t5)z z6pGVR@Ig`j7^7GXECMg$>*`SrcsRbL=azs#ZS(~(Gw@a6T~vKA~o-qU9l-SR&hg!{b#f!lW^Odf@|35+zT~ zd4`KD8V`V57!lc6$8fxAO51KZdJiiLW?(dbXA3V|BXRZRIxLvbjhYvW z=q*+j8{@7Mrmt>`5Q{>g&o$wIb^j89K=*Q2S|wg3q{h8YcV6oFYHcl^S(1R&M9{1t zh-$JHYeQ54mwGi`t>s(9CAkd!RRQ=GO)?i1@5Qb)&$06qkYai(0hZh$0CiTq-czhP zxoDP1jIiC;DwZRZjxvknw|m|lR0^Hozr%Wod7|&7vN0)d#PrUwweBiv=g2l)cXisiSNd2TI^I@TguiPshL>r1|H$ZAwtYliW>B%ZODWBw%cjhL z!|droYOG!^Hg2cmXl;R|(*hdU;p>>GAQ#My-TJ{3mbS}c$_RAMQlF!dnW7azFZas? zz%cW5c{9}Zy3#r3!Um^|A>@zA-Q`=2q9{Vq9_9tZEVB#{2(I>k_t z#%XX%uLwr*m!R3#!r!Wdj5`{uO-U?V`63!uhxNg{nO37SFHN#L|C02|T| z36`YS{KQGR>tPxFi2vyTd+Msy+QAgJ0~wL8c0RyxigZoaPktdG9{H2?kJX6`5cw9X z`u*=^Xzn$68GpOXf{V1%+@$3-Gs=XyZ4F9EfAqDBsNCk(ST9$0(9>-$xvI39>R-#@ z)ytqE&wn4k94??vp3jw&5}kQ^Nef1>+7m{?i4-|gN^ls~ggc^` zWGNUay2cbu?ev_-A(x>OITV)ynS<9G0HZwvm~PT51=(KBR#$7cC|bMC+!t52$i4DD z`fcctG&iruY*b=j!u{YO3H~U0Y|*p(>4z`~C%_Ugm-nZdLPe(5_L)w}>Ds$I2K3Ej zZVOk}=hWIWH7gPXxYeNIE7D_O#5RL^Fk+Ci);j5UnwCgTpMg>DO`ugc7^fwQalcfZ& z@h<RyXoCGSW-b zq46`JN-4mDpc4fa=XRXzH;Ag^Fxi~MCSjIPdyd5R*7>qy!%8BNfK&G0uF&h{R*0Bx zZtLbm)y=MvL(K+$pVLuVkXU@P>y*rq?;iyn3_itdpCs(+M-Oyrl*cr3z;yN{P;VrIN3F{WuI4?X|aFWW=(czi&QR0PLTxVwH|2ffhi)-SLxA!Ou zh|nU@Q|`$DgF1`eF!L*#782I9<`BwoMQ`9puW}bGvRc@+xMgre=8}`In%A*i(=9PZuMr5w)?Q;sz#% z9Rh2+l{7^hlXo-8I`0E^&dj;Nd>R_bZXXt_v4J29iQuCEk zm}P-)>)Cn5bMj!mL_<(iEQ+gUX_jf41;pv^D=TR6u6hj)A_K85KSf|#yiFS%Z7Mnn zw?XM=uE(UIZ#WkUqAenn)9lk07tc9OllLB*Jc;BpaSdTzirk_< z?rh206}E11W?^MlPK34{-sk7}BA;9p`ShydceL!ilwEsO!#K2<0?60rIXQ6Hkq^@t zB%S#wS=tN=Kj3r(hFusOrbRE8-e6#nk?zK7ebpL=(MT{rlQa^fAwO?C_2&G1#@LeE|Fju$D_4`qf*(X;FAZL@D@pwo~1X-_aFcknN!0l0E~&pCP)xs zc5an@y)HXZEs-)4r2d*iiMNYgqkP!ARC>h9USECZOk~{d%0Fk9@aa+*Y9d6m?_#h} zXi1OKlJLz+OLlTBHO$t-OA>+GG0}PWuPMabp?Lbd#{ufy1a*b8k!>08;doGf$0K>J`6M^1aUvFd+15Gp(AZOWDq&B zJ8?NOL2LU|Yoq#IfNODNDb(Xev&C|80~x`t;?*mSl7YlK6@6;}h~ZPdzH@7Fw*0-a z;~AEab-Z+`f{U{pY4rKjR0qX8AgyKdkpXFPhRze9C}2kVN^;#HBwCy4F)-kQis4;! zrC!1c43cBaJ1(!-C3#nn?*Be!zOO9TDYhtfRoybhN;3T83W1I1+1zC2v!@!KQ!k|u zb1}Ay^=(f5cXWcT3D>aAG`&=-e@3g%L!q#Z(~dM)qWcCSdO5Vw4YNp5$mcM}>tAVw zJ^>N{Y|uv9j;4VZay5m-YZYBmeT1u`*1u|Y0kN3{XTvPC zmBv`*`Id8|pXd~@#rBh%LYy8+t;K<@{Lq+r*If-q;E^802(JDO> zDy&}_4{zIy^Ha=-v!!wq5tDY6Z>(J73puV{E#Z;*(j3Ze@{hJm?-h=>3 zr+;^i4#aBOHxteQoOWr$L(N!@E{~^8w*Q!u1KO0#)Aw`oo=H=x^0J~OAO~U zT=mX+zkNljLB&t-{9me!i9l={h{JmS)I3L;_Ba~AYjQYtittV3s?&8OH>KJ@QAda1 zIoh}H`!Vd zmavlO`PO{@=?1$?42^ zKbn2h34f-891em0S4Xm*|Yqk$SsNJYK8Kt-}_9)5dPGwmw_T#u$FndiKsxRwk? zj$BIwTqZ;FdO%(?F>HD=V zBU$}LB5_67HEQVMGV#F3&ijY(kKb*<}Mqjia65-%pIZAmc`80v^4~JJXt$&u!ndMUYvuj1*1qYU z#82*aO*;K!t}@L5rC+s=Sbb{#&VA?Y_fp3UbHszx4V8v*ky}0)%`@9*>LoYtEiLY$hOM|_@i3v;=`xzB_#BO$@;HsbvJ71+m9xH zU+=B@X3;TkSeOp#4A|4QCa!43)9h-v`hcDNa$>v;FqNQv<&|5Bo()it?%zWCA! zU;~?j<^S2BGK=N-x-gXn?U}$4B5VvxPfF*y?j@X)p!&{qmgZmsGd?jn=9-v6c+r?E zk3XRpi~jSUC055=qtC3=16)q>0?}J4Atw!Wu18^C4O(Y zriMRcUrQb*9#62I%-aKVk9nCHK%Hha&L_W=`^1d>(%F1cKfy8RG z@yql5b)(-AK~@k%UFg_H+`D}j@~PtUQ$DZr`546Iw&=6(K-2?#gh+nP=Mc!5wP14Q zfqZ_<=F`k)BOm-w9@x%@fO!YH`5eXIC!CJ#K;rBbu9Fu5{FZ73zJU6LS`b7nn=|Ss z2kbYiIv_6qGoejTg32%X+HM=uHzFK?8^(qqQW_*j}kZ2?Jz4^+u zfK`SJt>caQeMdVlr@HPWU>3BOJ|k!>Tbf+tNaW4hbTLi_i5YR%DdC(^Qjr+4y1FJ) z3CJCl{u30N({y{I{CpSgA{OV?dmt7?<7*)H;6rdy@wntu&029*k^GwrHlNYhVx=1@ z7qE?0v4)#7MZ4scn@PsMOjC1^t|JE;z;x^Xn9~d*ZZi4pH-&n3ttpfd*`9_AA{~kZ(pe^qk+H{ z*MvC&vGTpq^W+7JcO|aO5cfTM`^sF-hHO0|Bi!9fFG~(8$jJl|_B$iro#wq~@M#Xm zKtcQ1r<{%xcQ_q?c*p5DdArl`izJ&pI8^=bVlX3XXlo>6l2R(#X|oosLhRb~;8A z$J|euzPFr?t#sfyexFbJ=RD6N-#aw2;1A^aozv0GXD*#v%ey2Ugm1&Q4Ck9f{e7+x z;RAnyvZ)TZ=!@>chKhYr{h)ffvQHO4vLOXKWpkNvxlE!p<8rt$3~bJ&B(shqCU0~P zzzEt!D`fU|@LL+W7zKl!TPrvf0X*al4Mlb1!A<2eKH8AlEJlU~lO+=}BhD#x-~U(Q zUn^Y`J&s2^9Q)XmKKo)pX5zIWL;Z=%UmNt}@P_o3!*DS$6m5o}PZb*Nh;|5tmhHSK zo{`hITco1O2iBQaTsm_=(lLJitxTz>Yl8N+8`-31u}<;;Avk85`*a-6w+(@WFB8wa zk^Y--7Z%n7Om|h?TDlytC(KhiLDWLwvWi}1{C>M}tNckb-*wMu&MPaGZluKW0QqxG z@O3?Ts<^n zinYwbI=TXkYM#}l3(?6LsMsJ{efA8dg6!)!`0SLB@6w!BhZ0dHdb>07t)Td1a5#KL z$E#BYy=purS0eU+>*VnI2r=s$=b~z8fDvhQBY7!8=d!q_y8*WX2B70b^^yJcQkf7= znj18N655XV_jr7c&eA}o*44Bjy}?eHtMlBoccLJ$0ot+?6liON{Sw5H0QrI3ekKN71UQPKnU zL~ufo^=fGzr<}7{S`R7Y$4WdM+q;2999FzOtd0ddT~j5itli#Th3=}hidC&M7Kn|A zMC7rTJ*y~@qS@Z4oBV`=$`CWRRrts1|AFB5t$rQjipLw-mHl-seAl}I_9~K5?P2v> zH7hyO#LAG(g;%nJiT*k|KWG-c=8VmnoPNCkEgzbm9jm%rj`ab1_azGbu3ylMLhOXN znnu9)ml%kFA<*DfN0@nTWe<4tc*j2YPYFQ!8QcGX7Cv_iG01dgJPgeo2qRAsi9xNs z=V}F)tTnvXR0QxDq0^^AgJ}W#a(x8Gs96cCHP<|&#F0@CdM`@l#+HMG$?5k#QBomV z9%Bb@y1tu`LVoUEm=TXPtJJSE@YvU8vOXk(qQGhJ_uzL+D&L}(7}PwOczj?igYx{k6ZIV?p*75p^h=V;IfWir@tj%dIcV5?(btWCY!76*o-Mw zFlTnG{U$O+I^^3CZoJIWyTxPI@&%S=`H$s1Vc>flZX$batzEWG=c(&FDq%O9=fAjlNYbla zLb_jTKP3vd*74FNL3f%#R5~WEp2JX?F++OcS7u0(sjECSBz-u~3~4rD6yaik zRH=KGF2db_N{H|%GH|+*&Xy>QNBze1_3+68jBc!4{>?8)hatefPgFhZ$|)BmhDe8e zqx@|2BV-Y`sd@&aK|BUj?%Ps2nDfpnGAmt=X?>tjvEf3uCDuOdniM`I)sHK9%?mCv zV;z@Mt+mt>>$r*^+PM+So|MFaS84T0NmUIsC`Bt?>zPbeeU8z+Fsc`WXXFnK79=k^ zHtm}o-@JSBjQo>>1rev3@9bZbJBBffeJR-T&D3OaOoO7&}Gmg<)2y-gIE?4e} zO%8l`A5R-Gz3<;Tvv3d{NEtcR+J*F%f|g$Dv*50pqR(>F@k&TCF;&8zFrYo$ZFvkq&tSOJX_8OmOJDI z@B8Se;W`z5s^W#;e*Y=Oebg1CMqE8w3fI%q_xO7w6c0I7|8vj_A=XRM^+FSW*jfYj z56cBhCJktrh*k+wUP4WxIB)vyz3)cYtF90N2ca~M@S}v3aKH%U@|nN~TCfjS^9Le) zI{0i((*O=p-sL}~oZ=hQENGvg*oGC&R2hGVe8Wc@`KH0g)k6hAz(){;9P;gG7-#9D zXoxk0M_Q`9Qf=QoQqk_1s=Uh&BjPc-CUTKRgB+n0aVJv|XUk*2F~;OWd1*>4I*Hfd zE*FXp>;l@QPPGPzr(Ou|Sla6_d_zooU>UPflu8y#pGAu?S}4u5%Xe|R6!pe$IHpHV z^9t27|KeC&42u^>I^>U|NyIm}j%b%BaqFN17F=?+`BTX#(NHnR_PXUvpv>RzN@f1I zfiSDX8Ci;&?|jow(F-fqo_HUVt57gQMkT6crk6Q)rL=Oai$PuKGKcG&3K*T*F^$34Y>sX)Jr6?EPeV=3Qt7hVShNxaMPgaN1A3$H8gHXm~ffSC3? zaz#4in>-67k0u!+b2BF~ovR$)GZ`*jqw)aZ$i0NVOXe`-M<|Nl9c$0$T`ZojBLb**BLh(&32z;ZpLzdic<5&i9x-�w2m09UDey=QZT|C-g;^`wr7R$mw3a1! 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�zLfl6y>D2_Kh@1j0=E{6SmVg4{)FLq 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

    mbdd^Wl};|8b+X z2WJjqUQLvXHf}$xSmmt!6ki6`o-nSW$(umUM2Kdv?b_V6*_x^_LPYr1E!alrB~HCm zN~8cWCT-Zju?}$#vayPGn#&wBsh!UtRheBb#Uw4JUoWho&LJf6RkbR7nc%5g?$|;P zH=^nj<=PSZ51zs=bKprenAtz)_Id;D2&@cC(L&W+y^`vsaRaL$? z%P!m@wlc(n{}ETTz@LdLd{8-ZM=Kv{ljaiY;x?(5>jmn;{ch@>*%tz2w~ubj7B@^l zbjY@H5I%VDS)d)J$Br6{G8YBmD;t16FGiyDLK%rtkMbYvo~|gszd~l4hQGI?5c_~# z*@(lf@c@bs<+oVus{JT<>5_s139!1uJ_R!V)+QxL2hksae5dfPQ=}oizQAv|kckau zYT799LZaR|5t0LpscecAGxS?grAdHW6z|)zKuLeI}lneB%z_oo!R3exb3E z)a+LEodfK9ete45$C!+}N(RJLe@u}qG-qQfq(MEYuAuFI7V6+AxeXt|nXs}#>eVxs z9+Cm^+u|W?FVu&x(8Jk*him`);Uc>nq+J66wXRCtMc^f?ZkLZ9$Un~KR7wL}9|Lk|chUVEyShcA18rXYTbs!@ z+k{UV&UZi_#MECd3_#vuB8c!Qc+!c!8-25|Hwckv)o);LOrk>>kY@{g)kWO{A@=>^ z!1np?jRPEqSSyZ4FfFj4_R+8cv#H{HiWY$ei~ZfG@>V)}ippz6Ws|JgalfV^0ialk zLT1_)XjpDz61uFHmF|5Mo`~9mBvStn41p>zMxmKvR5tC6}ta<8hKUIht z@2NMepoirNp|asM^=ztid?A)%gfL_PpwnSg)%9%^GrmouCbvye{sYxr6m`3Zy1>$q z^feQ9ajzs|62OL4-Z?)2Y$YT|cU%##oKpR@$u)tD0ZPQegDF6?0j+~XR5X|KqLu3j zBpHCc&}D0-;|5+O??#*QnRuPNh%{<#u%@W}xmp_w#?RdxPt?Ii+7eZlm5O;l<*c66i@3V15i~Cv zFd?9h3FAzc;v**DVWskj*msFI8DOFV9P-(8Y4CzO#iKI+mV^KUq)uS4I7!j3wee!G z_@u$v^}rC+hcukVP)@T%?oy6>>Eozfsbq_WqHazB_-eF~qqgynO~H}8dWMwclJSNz z2XZUyLidrWRxe_r+Yraa3XT%H402Q<7ze(-n2$mm=?)YktJa`AgFXNHUl}!vRTEK9K?0Vm zQ{N~Q29S-Sd)j*eI5-BO;fAHdzB}5vVWxD8-d@$t$Ig`cCho>z3G(*3ylOpG5tPGh z`b{{on<_tRzPp{fXG*D2A7Tm+;$*rLj1-*ohc>d8U$Vw!~sIzhO9kLqz_T{ z*BpMxUHchPHrTo#wRU1J3(5u>iwH8dhZzqaF-t1aKYeQ}ubCxP@P4;R9kFky0UQ7V zM9x-R_E=|#Mz737_h(C~VOO!Psh{uG!M?;4sAAELYxH*2>?WEjb|M z3)VDTpo#P!qH+H*YT|@%H8`l#-gW0=Y12dCNjn1o@Mes@f+BH}45QqbZ~ z0>Xp%!&_>#s!b}#`6sta@o}=KvHEW{rUYs{%!BTbQnb_f@H-^4zIjS3FSM9m}K?x}LQAQdx#FqWXLaeyMH6!5Kg-H>%Ov}vND z!N7%dhQ?I7k4_>a3%?nF>FjSi&+V=-bFj5`3Me5b?}Wh4DW zTu~^*gEknI&XLL^o3N5(hU{ym7Ga6`rk7ug<-g96!nJlDbf=UWdmQ~isvgmg36ygO zS+&G~+ShUGol<1nzeR^5tHgGvg`keA(v&2=`cA1|oF2QST=~xusu2ooFom$RfDomT zAG%W-;fhBM1fG=bsffu??lgMWEyEqGLSYy9m8F(b&Oh&_6x>6@6wvqR_tfDyx#CsK z--m+Ki#v<>RTPU+u6UDfc3yk=db}98BN!`W)DR7%$_0t|BERP@iJ9N<{9m=^@bz~| z*{;Y9=nU2_0e?Lu&$?H_WNR)ts8tP8KPU{d%>g{S%U!ceQw53xe6XbqOCr=lV%{RW z{7IO*;XL{0cQgWQs3(1K-pO6C@>#cFF$nIkAhSgz5@3iYG4Z_cZYf;XV&OCH1}Qe3 zI-{po=wSEq6#Pa`pZjTc69FfHBe+ZmKxsP@02H}mSGnS`3h;QcOmV4K`{5!euJYfs zD))*jOsI0Z`t;p0rBZzg`k_R9db!-7SgftQ%Uo$FA3IlCsNZUD=P%EdqO_}c{ak5! zZ#q1xg7zPb#;F15w#kzRmsuSZaX1o6AqdQNNHN+9KHVWjOJ_^bA85TwTY0TRDi|@K z8{rZ7|9Ms>0M}t|f&B^)hcO`E-g6%f65`iHA;kIMci!TV`s8O&J0dUPP;pfZ%Zo&{ z?OybUa6Q=}?fD!M>Pt8=enI&Q2yS-(!F>-dA@d$7+U2)OCDFO?>-`95nEbpPOK1Sm z>i4bQ(!{b#a%NRhgj+s%(Y7Xsim`EU47x)gYS#ZEJxXJ-ssXig$u;FB%%t5eJ|WDT7q#NqQ7fHM4TMBb%dhuCOZbo6y6`SC00*nRfJ|Lj+G5rWO%#cqw8f}_eqK^rhkP?o!)0oE236FL z-{t?fB}%YIYOB?On+YCUqVEBn z+T_4M#SD+jmgVbzvx7zI6RXlS&_PPxncqgE71nDA4TdN*GyI33T!u7?DGjW^>A_|JW`6& ziKOf=hX^j^q)3*3Q#SKY=Sy)2N3YD6hPXbvMpy#FHZ+0S)yQtr z=)0GvYpQ_jVQQ6nG`kPTPw1dNz48|}8nGOQ=t@KmE;f)|X@#j?hj%qihw(oY40P?? z1hz_3rYyJ@dk}lJX+Lg)w8CVIkwB&yv=~XP@-+=3rto*J=!PdYb^-JB7#tX0hf_$a zKQd_Ck48;8SPw+n_ipMni|<$<@cgHW!L8z56LFQ8nDX-~nnIokoV#+(b!WK$AxNQ~Vk<)GSLrx}Q7H8gJi1*ZsHk)t zqZ|9^28ji6pctSaH({2#qFYUC@t;kxf=%XQr^;Rhv;4L}m27}%6AR7Y;Q zRBdUDh}>wwQrVRg^aoQh4d<1xZV-73YnP{;t&|I@txD81aT*j=b*1>VQ+(*My}DfK zHr0RJ7?$0QqvrXh2&3lxCZpyfl;bF0p?uS1r|ZR2*7olp|5^dG@dKJXZS z{}47nQ7%Yu&>k-W!4rZ4YfMZ&EE*wVX63;8-pPq&T}2)3r~HGZ(nMX&d4$B4s=M}2D%?XFOr%NVFyLm zr%vgdP$6PD2GA1Vjc7e@-WI7@ z1o@~_Uk-rpC_!|+RO%lM!i0z>o3Rk+JrqIEtiFWCRC!6TaNRKDWvXRUCWvtuRYPB) z4p1`(@eM1aNY^2H4-r0;Kc6T4GezPNN;XL28gGD7n}GuL%B5ce;Bo~)8RgTSS!Btc z>-Yw*lav7)0)zg~Q88sW^nK)50fOKowU6>?szmtQMuCr%*Tu(RzUWdHx;xeVp+p^E z)kngAZ?71K5f=Ou^ZGcp2PQMxQ4o(W0vJ>>@b1np3V^xQv5p?&TLcy&piN$94PRE; zSsRgGGu2MPIK(KH;0(R`w838m~^KgYBi4Ta5jwyj%=1M8D{~P$2 z7|*HaROLOWoe+5Eee3sk0T2SY@XITuv3ypwG$6G13BrBbgejrXasD{}N41nZyqLJ(LrHV-WLmksDLmvK@d0P^`St%01T@*s7y9x_-NS3xTL7Di>Iqc zNX2z;$-CtaEr^>kM3#EuR=N%OD(;pkJ$e$whI7ZWBmu`vq&NRv1ZExzcY``9x$i$B zVcLO7+VVKyhuD#u8(=14emp$^)>(B@AJ^A8m`|(2gik0tr}?)ei3PBQ?8>X~LctfY z?R$`I6v@z^!vX*``bsGRJC3iCA~Zm_hUC7a=-xa@>`sz;;<0W#9+M&l<|glpSB<{# zc^!7;BRa{fJ>Y|;HYh;u!l+hWyb2h8^Ho97Ky)|-JrlU$9t4;cauD@Ak8|9CCn_RD zE*4r@%RzM)`gNBuA_e|gTIlaz(ECIpicZ}!4)+2`W(I_5Qyq^{V}KQrP^vf&t+jk0 zS_{nYE#l3Af!2_uM2vQ8v5HyUucC3!mN)4W^x0EmVb`!)D(-%Gq`H<^^^i4R@ZGBc z7Hjyi)l#90Oq&2rWJ`Xr2g+-JtmQbRR^u;Z{EbZ@b!OOm>|KEJG)&09_xsaEv>Ndl zL9Z7InLq^WRaD;yo3ii-bsNN|*$b(NP5D}Nq!)3S7|{S9`ERW33*_o)dl<)a6w=~Uok$~e-dn-TfmZr|Q3XB9{a2WoSIfDA&(Ez&`A_#73 zBxEg6qsE=74O!brv$4`ZizN0S+02SV;vY}_*DehuddH@0I>gVcf&D)DD2eV2pzib) zOi`SJeL*Nhh7&fR-f;z=w<$I1FHt#~P7?8%By_4$iTLkt48x;nLB>IV)hYry5nAo+ z+eq=!`6sV+3<4qw07oWJ%-eo**zq0t;v5*@K~3}X`C2JH_FkZ?n=VV6Z4kt_ zVF=0>BU-uipi~G!Mfro$XkAGk?tM^-(WPAH&pilz*}-Uj<3Y(QY#euK5RwCqA`R~Y z5PG8|p`@Tdc6||woQnh`=TM;BX#}wBc?4dhmk}=vh9P^AZ({EqxV|6P)wn+75B3vr z&8YEDy+D6Nf?wMCUsE6F&g3txgHy%O-}vY2r05t?k#^G~ofW+>h*2`%<=XX9oV5b; zB-U_-`W$nAm$NG{gq{vwdhye*1Jvpvx`O13!tlsja0Gx!m@phzPnM722V&0*Z{-Wn zs=1fATWp49FkRUc6Q1l?FOA`e9_a<$qx1Qf9;uvlj^dfe`Opp0pnI+eZ7Pu_066Du zSkayY0$2gHD$eF9kBb50QIde9uKNiLp#Uc^Q7fyIOG;S1cFEgoa0;pIeIr6C3RM-Jjv_NxNPwQ!$omJIcRM-bI0WLC6gw zIw1TxElYjkUha><3e5mf--5=1*nUR2at}Tf@sMQKXW?SuLsF!^HK~=adk7X&S8(yp zLsCNDC>ZsS&17Qr&vJ1xA}7&rVhfUG%V4Y0KDd?t^^i1xPxDGAbeR|TrEHXb&?btg zU$cQtJa*OBJ3=3T!cwbz6M{m=MkyKxQ>36 z;aAEQ>DGjJnYr7wNW0mtbB2R;*E;P$NLr_LKSI)Ld=vQdTcp^uAz*YuNe5RZmm`9k z)KQi}he}Hod4+}I{sF?wU;Plg1&)gvm3i#gA`MHSzBX2a2-5~;u4}Lchnk3V=Z|nh zBcGkz%JUwDdgABAR=(*`=~?YhJmViQzG)Du78ALp6ifw62lHBzjJA0YB@?!J70vUq z2_@pI8gFqnp-l)EmXI+of|nIXg{--ZCoTCA+NzLB<&$3k%@8CteJgH88bj86g)8_Z z+m$YY$1;&-CS=WfxC>*AwFg}xYxd&WodI096BnME%aj??*+*q3t$@zY(J1hluy}#+ zcCPpa*}Y%(i!7kbgu#>hpqld2)=x#X`3>%x22Is?QcjT6SJ)ik3pkV-s5(-7eI>}+$tsNn-g03OIxL+o<{K|*3;SbeG{e#X|AU_+UE17 ztx~%F;pkSLunnq+BGeK@7@)a_h;Q=GXwhIS8Z6%i{Poc`sSmI2!e-6N+tBXM!hVO4jlLt`Fpj<@x9VId>SThPG8%2OHi5k4cL|z%2ZUEF}3#ABv0AQR^fmQQTH!vtMq{$^b%fx!?CKvD- zCYGQZQow6XEK_%?h`(cE@wzvP_&F2n+v|611e_2d?{k8C`OgiY;=Kmf2D7_dS;k|# zvHm=_8#8xJ!Bd;Eg3s*6l1d>iBga2rK=e-z-vA-)*ND(3 z|J8$K=*LC1?#l>cdhNa$;VeOGO74a4gW4c(2EN|RZVCDA4?wnmB;WOJ=bxEbiasi? zonJGv;rfPHSZGGDBz;?tR$dvwHt3i44CW0HY>db$FV37NL4|(-LTVv;a3s)!SRlOK zk!+lH-`bumT`Pw8ZWLRZu`ZT|5bDDq>3Bq}#+Tv*>nP{H2#H+?290wWQbuv5rvFoJ zbZ_PJqS-qAu5PXTYBaNn0A{;DzU_`gM$g3vv~<)@#)ZXW?1eGRs?Y8n%(us|)bxx5 zU^IUawl_T$`@diC=IJ21`CU6g<-u{-zeGj^XZ$w*)R~~HgkXLnhK-X3c0+f35%31$ z1+i>Ae?6AHU}`sG4kF;xE2m+@_W+v%VaeS$UTjA!UC z|0s?nk0iY&h>y(S)$if5*O{PGRHFnm3Uxs&@7Iw6l6>7@V+z31N4N8sUaY^)x`5~Q zVo&My7=2?ec8|`L%gcJRc$NINLNnk-*^1*~w|!Q_o4Ec9IZtD(VdE!k{&5cc{2Lt*FsX!^r`f9f^F|9b4_oht#1*XV*WfGY zYA@>QC9t=mtIj*2{B^HL&{Rb`#?Y7b+v4H4lEPAEjYCBQJr`0TfS?Hhw{kSQR$z)7 z>^&M_gdt4+Kto=Hm0n{a5O@}th^h82dzQm~_C+B|>3S&_;PEsHc=k(I0!uLrOHq=- zqHn#GKDnr~GZZ1oy&{=9Q zdaw78R(=re8@JJ~k9)WB3n^?sNNxazKBZY4I$kjlSHF@G^PHiL9YSR z6bwUy>n(iC5Z2e_0xW@MNf8?fErmI(FVda%LH;|=7@MP&x*6X~+!20rLR{<16#~vI zM??~cG9m>&?0m^2SDNI>=A(ulT3;-ksE&A4QqIQxPa8aaC`(9BUWiYU8Vl0}#$S#Y zj2l3zazv`QAop+*qaa#=V;tInZQUilekkkH&qBXQF08yGG7N?KBJGZexJkJMDQt}L z3_m`Ur3N{aQ|&EWJB)?LdWmj!1Y`>YB9JYFr=6^~IRJYQN(?r^3gF8>1Aw0vtO0n$ zcOHzrWD7G|9pyCFxv{PM*f7>x+l5~m#s;t^)f?2RT>pbdr=p|Z5r>Z@PpShNNk9%D zZwV|hF+OXVvM1m=TGf@FLTxP^N9N=3jk7!MqxQFeNhkKY0+C??+i4Q?>L~~%QzneI zaS$j0XBNU8aEd{1KNUO8?71C#pcmCeE#MtIRxYYvh5Q0=P@)Q2er*$&M}c&dt`I|z zV&I70ka2rvNodGJ!mAC=D$Ypv$_$M&RMq-?z*i1uyF;Oh$tg76ltZta`2RfPyq8S=5ja2a#m)=q&xF ze9C6*J1A76AO{2FT&EK`=pwf5W*$3&CAykhX)Hu$H_{bC`gMo>i)@CK-M{Gu1coLO z7=i%(NUa(Nz!p~&p7k|xWt&~D>{Xv8mMJ^br^!}ji~2MjmVI<(Q&!WTfB+L};!6Jh z2o~(xNV|h}#$@4^LQ$_;d_zcy}VDy#BxWoX`%t-67Df^;8lot`Y z{o}7Te(Olq$5qs{8_hXB6xFY)0z>SnRQDICKfkm7^M6t9Ygnnckd)D^;`63^@usJC zYSSEQ;WN>~>zyr(7cC6xY{3y|p*qOjkzMO=hW|N|nFmd3g3o})@gLE|?VU~BBAPhU zGtlXt=yb*{toNWPO`l*292Z3W=+63iqW;63^}o2@#_Mij!(yzyDghXk%8|Z-2?0FD zpuLaLUT=W%#@_*(!f}6%xI(mRJM7c1dP-;Wpi%4=*U$KvYB&V)90{GUqbjb}TZ&GG zkVV-fNWAc8iu%VIDt=9+n02(fXj8-L0RosyLZ22YjPem2hQJv5O^V!bk6$^S4+r!w ze?w=)0xyDbqRmFLy6c#;|y6 zfI8x0GRiy@oEnXQ;znqj8ZQ8vSE48Zh|Ubwlt40`$8XDEiTakdtT?xF)Usm z)~4YIZq!@f9T)q@ur$|CzqHUcRz~+Cv-Je;~ zrhIUUI1017<~;J8A#m?;M;)59DV1N)eWP4z$}BO}V%H-1+gMR+6zuST$7{~ZhY$^? zQu$2SXOq~xm%6`*B(*}~MsbSi%jQJUSMl<)c~MH%@t<2dFfJjo_CTjnpMPG+=gF|U z8f{PWO&76oQXqy;#%~bL-jeh9SebG=Psw62uFEivOgo7g3HK~9K=$t`h8<3Wrx-J) zK-5m;bv!jhC!#VM(@oSOd^ItV`R4=d9R%nEDuPqiodrY z;p$!pAzpz^l{O&$7_S4F{Ny+`P%49sDxzNg@^cISY8!k~C;L~#0h@^BP6`kkhks0Em0pan9>95Z}M}L4uVS#~6OF3-3 zuJ}hDmdk9qE)9HcF3YP({|Vp0<_t_R2=P@PP9Y=lT{U0>|FlQ#sR5bh!v=&4l6sU_ zf+_8k9F&ExdH*pR*J&TC8ZDCV1!XVf-{-QPx}*!dEtmDtwMd~^{%bzltebP5y9?MHH8n5D z$%Z({>)nvs6Ilu}v*BxymuV21y%i8!6+&RvSimM}b+f+X=|$`x`pkwFe!Pg?tJ7}h zqbIQay25STIFbFX#&-`Z283Ok2yycGGd#JN9o4;nXw1c|KzHTQeHoM37g`=w%C3sE z61!9V2EAdE@fFi#HbEN}yhR1CW=f?1d(L|v;d8Cb7P9M+zSRuCzP{4{5`Ru(Fhx3dOpz3hLZ9GUQn})o-V{A+f9f&{wK`C302^f zby`Gby|JpyqGO473`|7l9rsG3#`%-* z)>Kg%2ZaO zZ~eN3@0!XQ^^GT6xOW;`7qJFkp&d-hGlT`WP8iI`b8lrOx<8Nehi_#z$$1hTsZ(EIJZ><5<~)1fQ7&L5u+wz3~$bpv@d zBj*7AO>-nngHlQO`5+bxyyI}7wtYkMABCtmRPB>5@lhf|WoH`*^q!>YB|FQn!AFy= zRK-=1gcfUs*fu8lP!c*`_drmQ`&e5x&arXwA!LpLr-rKeiYKq)vKkj!(9-o^i7}&E zM7_l*a5gnvEt)>)%lBA9J1hup2iaZ()-(}PGei*hl;_Q0BkciXcn-@+GQ8_@gq8Me z#br5SinxH*#wh0?7Me*mZ3d}=CZYtv{Ol(*La3Xdj3wp-50L2&l=r4j`0*JmwwL-G z9#WoMqo@MYvw3DgMOS_TZ$n6sKH>Q@nM1puzdV!OK2TWVp~1^kJ7g_(02EtBdFEAF z@-V;vC@6RqddduZ$t*TIWp-Jx1}XpAixOxQHU%#DBrG}xxCn8|D62m!w9f)Ht@ z>PGzMG*>!CoyE)$w|VO~eB$k_S9tMpz?Ya~iHseTOtih?b~Z|P;UoUW?d%{N_t>UM zU>qrn2d#3rdqt|2%;*un8N}~SqS`+5p%F0szA#!NkP0R!YvO>rA>0%&z33BE@JFxZ z_swCs*%Vd&B7|3aMnHDx@gE-D=LBnLZDt)-jf zg~#5>qWSE*Sj1@TH|Y4?H-eoE`++h7-i15(UDcf=GKA~QP31RlAr~x>WliJxnY-A` z{-+KLup|a8WX&5GoQHC}+mtFG#A#?HWbGQ<Xgxy@{80nLX1wZ``3w1Yr5DiagiynUpjgVROX}R8#(0pqwkpPEcSJl3 ziKRz807-3Aa?yW8QNvj$E-12hfSjkuf@%L1;Vle0oLCeDci%M?v8G1M_MA4I&RWDRi%GHB!ng-}73nmU@-A%}wBv#NAfV}H+4rL}yma{Qdqg;;Y6RWL` z4AF@eA%ncRq?8&L6CxHr)-g`hIVtL>iHHe6_WjS&x=JoLVU@79m&Mu=hhG&df=I*F z_kUhqNiTQ!rpon5u&O5wA2^lWM4Je)#XvjZ_<(3G58t=?7awL^;@~+A{C60`$3Dlb zIU$C0y+XgyF&GDH;Cp^Gb46(NlVWu*gYUd9Pf%BPCRP_40Fg+wAseCa5xY(*a}m&RS3o1kJH##*TjT_? zW&swpV#3b)Zj-y-#8{ZlgHAYe!J-=TsF4o=X1cyjannS=4V-f2bZ37L8o56fBayHN zL`*B>ga={km?q}3znIG?AruWQaMuIxm@*f|g*>!?mf&w072F?!>xNAxxS!gS5CYPf zMg3i+{%8|kmXMR>BSC?32hH!vcc^nn3r9AdHF=ak)`d=O!83H~Df~fdW&E#9{~>T# zL1_Wt>-NvFNuaFdUw@1@u?Fhgdb(oXkm%Pyn*H6T^X6am zR=CcSSDI>hNp>p$`7)*8BU*8>1kT>52K5l~Iw3xS8lwMvxiUi}7!s0eXBFXPtI@pz zCZQoB9-h+c0;UJ)Vs24qN3DRi$;$IQY_e6O9aM+_!lak+4EMhhOD}>YA#|AFx1#2C zkQkB{`NF3(|KK*)VD6x$t{P5!2qIbET%xZr@qs0pMYV)224Lb_v34N(LBQwJ14xOi zO27IJ7cPmZn(!sC$lrt-kk62@ZvY!VBJgdfdg7Cq^KZ|V{{5K>lmreSFA%+~!|?(D z%C3x6d*+)vFM))sodO~k(hi+3cYH@smwScSbR#5TWsVDUW4QlD&G|}41KmhaZ-_$% z)=2fVpr>t0J`hsKTX{j}De8Qp_>wU&mViSf7?o?dOM`$hL? zIQ~o)=Y@0JNiO)W}7KvPuM9>5z@^ z1lll9c;8rPr?{^fs=w--r!5Dbjp2P9tX4nd-Ije%J6M%g7xph6HIMC#Zd(MFT*Og| z1E3n&PcI61aK94;uOsN`xSa%nJ-LHTq{(dlTIeuAU&=QDOcHYanYjs~9=P%D^ zIr^XWK`=L;6-T_gPo+DdJ%y#V6RPPI`&)SV0+uU{+z%kNDMRsl=K`4a1moh!0(MG& z$6GCY`$9H@_q>nw(!YgYqwZttQ%C~08zx-4VakPE-dZ7Y7~_{41uYBKboFHmL(BSK z_{aCL!Mc&Z@Yefa+I5}}Uc?q?llb;UtWaCRe^|u&nj_x~5X7Al=bk+3ezs7z>oQ+| zKkFS6j%3=DC5mvq-u-dM-JaqYDCj`>^8@#zh1qXW3&P4JAhRCLo9}0%XWR}+P^A*> zCtxVxPH2iHAr?+c@JA2Te-7&#AFV&kX<#`Afqep*>=Y;ZgP-Cfu<>?}*H%S!cHtzt z@CmwLFgRzlEpehHhazxpb&C|0r4`X*c?IoUw%1wPpcDbW+JwD2dKY`FFb{-u*Wt-a2x zma;Xv-=F1wE@cC{V>%A{LWsyFD)6A(;x^u66&sd&Lmc7QqHv;1q&)PlBI4C6^D-;iz?{Quw^A8_bS)Sf>e!W`L<*qh zH0nD*z;;4S#lAE(nh;CIDi9X!hvg9VdR@%3jov>7`&xXW%77^EfQq)<6|FW z7Txz>^VJVBr|!h>{ACX-){Xy-7p!CV@DJCq$-0k!;}PreH~m+B_j=~kZsX_HvlF`1 zU#V_kJc;>s_r=N%#Ago&R&*1Tvmk(%%oQE6OGM)Pu-cx4J>Ef>Nvog!kHT}YSKg41a6ETOG1x8nwY>y=w$-8aI<#KmD zTq>2fPN?ItB=VULvBBCpzTqJNTE5Y}JP`uoCx}#EVg^gI9Ky^BxR4QJUz}VUB%`4& z^gGvh)G`HHA}etbnMZ5q(@m)^Yq-XFJDz9@a4&*H?pg{!=#>053w#y`Gi+90mUu>H zu&IEwf|mU2byYmu%R*f5f?1@^-VAg7x(nL%#Z#~uzHIYYtez!i>^ESg06v43(PEM_ z(ACUE#_AEO{f?hS6OMu4LO_dS?+pV>QJ<4Y$idg|eZphnO|PX0*kc?pt@=?I^tz7e zf5nGxqCyxo6Ki$zTO_0xFNNbJJ*BoJ(F}V0d1jFjmKPNWN2A%@J+PWvf;FL;wrp1y zAx1{s&W_B*VeU1I^Au-;xW4W!&O*eqe3#LEFwv?cZ5F#g@4OCQaHw}Ywm9^PCN0O1 zX-V|2X*4Ol^(zxAm2IB~Rup(eTb!)U#U+(p^`j}8HlVMTY=f>UxIR0|@lO&MlPE)S zKY)d%d#8tqo4A_2m7d<5>rnC|avI$(g9Z+>Gu^eA)Mdq5 z!vbt_K(3I!OLn1aU|(S)4FM`7eW@hrLu_O#GQPISvjB;ZmBKr{ zAUQL?tp@BP;@DosB2o}c`orTlv#ze0Z8f)mL#@k-(Kv@#GVyZ&G#hnU(ZIbHU#z#J z!JDr@{tm(FSLMns(PfkVw0@>Sbr*XD>vvUu1lb}!6SGOa9Qtoa98=M(Ehy??pM-dO znQhf0p)~S9ft`Qe&%(;3>BuMV-YyOiAUYaHeYPMde6gN_bV{0TPem;#usw#i3q7;U zO=G~qduj(GNDO!~hdO`!*C&nP;RsP}0Y;U7!oH{7VrqJb_k5W3cSQ)h6rqKyngLa3 zrX>#1N^J?5c4JK+&k~bdoB-PZUnInq%5WGeObgj}5ZJj39>EY1jp9cy8D>#M`Xy$t zM^7E^9HtgAw_-^t`Uk!-&F#YCqB(5(xR6bEkgx$SyAU$~hfFTQG!WI=30o>BNT;f` zC}iJ6gV*fwn!()~5B;FmA9f$Kx*wqC$WOAWi+j0MQ$?gOX+;{cVlzSlDQ29hawK*e0%4J=rAn{2;FM@XE-I_?)u&=SpzX1r23m@u zZIgTwUUGMK^`;14@o#EO_7bxj((=*xB%zVv7~5fSWCGR`;jRX92Ek?!!`G#y&_%nx z3+!wj3|v`C^af%Ig{;ZL6-ivjMX7jZ=$8!sF)(Yt$~{cObQWHwhtQX~L@LiI;MD8RHxpiDv$6e6Hf z8762CQ`2fRq6&YS$sx5868_B>(FRUu*ll_%E|)>(F&Ubzr*Rjjr@N+EQ*|HzU<-?O zC9A@afZHlkgk+VA^Y*@nL|ww3LSQgg{XPu{%OuQuksLSFZ&0lBnb4uR>Xagd6FDUB zpa=yRSFs7WiQEr>nqUh=q)F$x@P&`!IIx)!^i^-O;*>yEyV`q!YjF})!&*jI*x8kj zX*hUPl~0;th*0!II~9H`rbG*C(IH+D5WQJX;+!|s`$-*`7d%9`9@#cYpRa@?)TyP& zW1EEbF@~m9w5*w$5ZBsQX$H|RPksYBjE!7!j@Abmrwg%3lN)FDISBn zVlo>vh?zs71b5NGL5gKDH9^`SP9M1Jf(Se(@(rD%d_(!?=%6O)kB|W2yP9b(MD@%< zKu6F~zim)wy|@UY1Zn#d1gcndLQ;sBhqI)fyy;Xqt57*()?syzjsV1=ehOLhG9HtF zEDqfZ*>_?Z=5+1Js);yXK_=AXda32^VwyIpZ6|fO@CwZntm*KLQe$SRp{B%ZNw&(7 zdJ2>CL%j-mFxLSWg~%?VMhcyg{u&4WUPGN-d=LZmuOONR{;D6{qWd(29iW?GR`z4G0n* z3)ZZ+Aq?s1FWWuERzDom8H-@sFxL`hJwT!b8JK41_knxI$&qpl0^0n+r*bx6LY|0Q z3%&exRW@!%yp32#WMn z@Xt9*53m10j9&0CqbTLnjSIa0R`^_<{E?S#Wr_3Wz`hMO+W6`;L_qV@;*>&ALyBHa zBGE@K5KthvQ4!_d#V!4(h)8pk&<7w0sOk((Evy_p%Qj@@3c9i&8ELR-U!B0)h%A(> ziYJlk<(JJYl6T(*%i%aVl~AkA=#=ClK}PC)APwWn`~qt)>}Ii9JeG{V6J6)T1d_rg zRL(Y`32cyT15SPd?W_0?uDJOPo(jTYBuyp6`ss%P7`5=PFS$%41OPkV)m9sVGqyfg z1+T-TB`mp)bee|l5Qlfem3GRJ z%>3r12se*J+z4Da&Ia_XhBZwCZCl?06@4j$(0}BMwzJrf>c4mVl$LMb&XQf5kN5|9 zz3E#z*C_ROJk1hP!YQ}WGT;aHh1Jvqg^H%>X|Zz}a6oOv6**N1fnAW8bu50UoGW%1 zDIviY|Bi{yv>Ix@rm4j)26Y5ug}^rzx2xv5p}ISoksmP0g+?G=R}W0DDx-NKSQD6M zWa`xT9#fAl09(yvZ%wn>OL?LJ+X2do>on9)4+nnOc0MoPjMXC?FnT?O{lv*i=uS8k0jcZvugyX zsh_5!3!b}s-Riv(1**&HFlr91Si$lbK)WXUz{sAn>Ya*R>4UCn(3#sApB(xL|nB7rOyB;ZV>(4n&eR~M$St($w6Kf99+(0zTDf4LLl(zY|aV<$`QF(3E?G=a6UoMPE3Kd#~#PryNh zj;E_6JOA(lv9x}&fNsLb$d=raZ`M_Xx{D1ukY-ba|BX?QY5P^r0^HpQmKR+ioGL6~ zAV}X0;o#oL0PcMT&i<-ta)=#tzaK2)zVAnCYF_blmF<*Emg zF7PeN6D*yNev&08*If6S-;(RSW;+qAUwO0kyAOljX!B+*KXgftvqgW+U2i>_!`)A^ zz7<3RWtA!EFf>6ILF~^&jiWg_*t-lFFu`pPNL&5Nq2m{~T-Y0S(i;(tDg-fTanHSvt6SoiV4{?pL6lp>ZYWoOZQB9}r- zVc~`q3dL~nbwQ`wfL-NOj}vH_1UJZFdWLU&ip31=FAj7Sp>2V(3@-?}N00boW;F7Q z+6ZP@2(i9>ip6GSU{4G7>M9{6!Lr`rLxR(JWDJf6Se!HwR@#WG1?m;%0OI-#A5VXp zB}{Kr`8*s;N{X3UebH-=D_4%y;J01b{UAWXuT&xO6Q}m9RQ=4*FRwk2<^D?J z8-<=frE+Mn`#cD*C&LF`Lt&s@3Sti?uER<_qc^|*@*3a!3=4C;bMr(aPa0~N?B0Sz z_B9n{?u9xf>n(3qEkI4*4meYXlUbGr(84BF>y4}P#!Wr+qh5c;eFPjABJT$C{<~N>&)o&qQM6&3(#Uy zD+~hywS7DTKUoy=$U$oVp!>WwGbh~{ia3|@HBVVKsANxBo^Oy!Fn9KbQ|%`Lwc(gh z)LGXHGNL}^$U9T}KZaH$NRvj|Bub;PGI`_7R;IHoUpm zfa4JY7t>o_Q|X}#dXn)%iitz13MS)i!ao*{$Rw1!krTx7RNP*+HQNZ2o**=8E%Gdis?Q#r{Cu#6 zwiutamn{?_t1cm&>)w|Gvep-ZHMMsNwxJ?akBA$;yv5J%g)lSr0Pp=QOXQW$vJ73x zTm0c?*>K&4xA}+9GLLT4zxd?m*j)=DnJN^%qsy!~|7DH7(3~>aocU{wLH^noOv&@g zj473f4s|{4q&(S(m4@k5OCfw$5XS)c1@^Bnu!WKps%#wC9TjE}q$y5#lN~sT30m9Y zGWh2B&cYOi3_bX4&f;AhPiViB%3vs@@9#3)Gk?QZKZlgG*yuh4!XE^4TV};o z=Niv#p=oEER;Y_BT*PkVtSxth@m{iGaUmDG9v>dK;wq^c~&L8yIz?+6obT)b7Eg%9I<^u5!Am|N6qy!4*Is|58 z3G^$o;(C=S&2`Y%^JZg9yVC0u;C6I`oa}d}AW`fy&=0miuWo>ma0URW0@wEh^vtgW zYf`!fkOr}2YOhHwN7i4(q{CD4omkqm7Vm8aXhUdXV7)6ed+3}CroLDx6{;ac7QhiD zfJ3>lL`(@J@&-uckNQ7@k3b(n_#L|)#wwOco*aV=xkZ^tC`{Hlq6UwG3`KzE|HIn3$5mCO5C2?1fwL72*R!7)`Q)r5$ z7Vr02`y6BD{mtj|{_#?ed+mMJUVB}h^{i(-w`F-f$66!_c?g|LI~uR{Pvg}2WTs{y zD>2{v`c|48D8j~oH{jQa%8RJmT&Q1T1D{B!Y=2e~?>D9dioC4l&*(SUR+d=Z^r>cU zS-_;5v}9CdO~UEup+Z`j7v&pg^DOb_C-^&Sv+Wq@R2jHKm-?3D#w+*htb70L6vRRL zNmD4tom#JvIc(Sq(L?nscNz=5y?ABPc7n7N5*#5>oVY$-qVb3_`N_6DT7Cb#Yl(Lj zS}1>U3fM26BvqWokmQgkYH^O(hj#nEvscX=%}tzjBg%1&=+J*(O9@205{Q@# zS%3HiSAuuE<*@5}C!uOy3kzU+_Q_b^Yq9_#i@!W#+Ul=Y&hmf854*p2z;XuS3!QJn zK*K&y7OMXRRNq&gfIqbmR4}XgpSlAYdkSkT8yNB3W-f``yFxEF36xp>oG-gYBoZ)# zCvSbIF;8uh%8d5!og9d-46vPhCtP|VQg_HU=7@oaOy_iIid1kPq8{#)~Swkmiy8+gZ6W@UH!1 z8I|wmm$a5>2B2e!IfL)uT9Gp9Yb!9zeC-Av)_0W~*b_*XM(A)D+BhtBmGB0E$7DI) zhiFXJ0&kS9VoucxLknoT7n;L~Q$qE^8?l)Y5>Rq-?sfYzRoqLiZWE*lfBRN9L5#$t z>Kza3TP#c?_D+toTk51QztImRmcxZ`MekErO@G#^>t1pVPqXCyfmO9y(13Zg)*o3C zF<3F7j-IGLe4@-7)t;AJH}1L7o!9HH`JJL3 zpO#vn_ukiDuR6Wzig(PsUJZH`@5W=LYVxbDIgWo^r=F1KZ(XN$yh^CFP1mU}Uv(8a zZrZJezlMv<2iK{R*Ic8Azp6tF$7>g+4SZluKmxbkFR9Yy*p%UcglC;^ADYHR*QuSa zxvr$z-Cu`4TenbM{yOUQx3;R=Uw8R?-@4GM-oC)JsG)e?Ljq;h6`f|uLe+N{{r1B` zb=fXgH%H^Os$`dI5+!Zi=`GmH2v<}LesyvS(U%(N}%kA-*jDa z>5!|W+D1tLF~3A>H+xtQd=;Zb(8FFIK@SE?r5y(&=*bLNf}R2x7EY{O>Xn&H`G5V#BtHpD!RtiXYwcWb&W;;lYJNizI=&6 zZ~lxks?3C}OhGJ8dew+EJp436pQ2loNfQeRq_TT~x~az1%Uf;zt`)!Wxap$1$UaPH zQ^JJ5u7?w9i1!-$5fKr+ufNPUwCa*>iApa@smO&hNS~IxQrqnj{7izHJUtz&h}EP& z6$FTgut&TTZOeprN9j6rT1xKF-PFfdGg=UgDp8}gp|ZRCvyz>)g>?DQTC_5ucJv9g z13o=brSEq2xpw;-Qa|)2y2|*7Tb*UM9^;(9i*ZLhEClI})b#Rm6)}2IS;ZMFts)aN zpQOz5cCtd|LW->WAHMDC)A7$Wx&mVH z6T3QD{ra|RpxvvI-f^wWd0Q`w$qi+K4%rw5=LWKCwQ6;ARbccjyX%}zPZKL>lW%W+ zp}EbHmPvT_+Vs^TL-i1hq}Jw9-@oI^>YIC|9#|2@9`se@BQX)@^w`yCIBb;_v(%(L zDDXFGg$Z`}2W17(W;*kJrg`-4>ZO#3-&GH-T|q;5*zOs}i?*_xWT^{0w36Qo17d~H zJXe28zEVLPqQGkTi50cPJZ$~aI39kEU4eU+^F8PL{@sCqpZ5VSg z_J~tJgVvA5$SH6?vjZtl5+y9A#ttw zX4(~Gz-)ew_+RnC=LW~H8tRQ5kz2C%x>nf})c(B)PV;xG<9l6~MqNX=@#WKP6)Iz& zYv#b|1^MjFU!q=2yAW%7ey`#oW*PC-mglSO`>52C8uj@;SAIf+(hG_IQ2N=5Ub0)( zG2lK^>eBtLv12-byIOXpjn|=asn%{Dg^6YxE)v|vPJB-6st=ruGL|0>a7+DdzpID+ zl-jqSxHku2L#02pRww&KsT@d+BVKaD3;YxyoPwTIGHtYOoG6#oujA@I)!pUkE8JZ) zwRU`f^8{MhMf&|X%paH(7`201wmD$bEa^baIhht?b)Gx5uU1#R2jL!|9(vDp!Auiu z3}o*o?_p>09@LF^Q7|aN$=LJu__O!>ix1edYdaMmtSk@M%8IVjQR`S-qxF|rOb)hs zYW*&@(1_tTsMz<3Y;=Pf@jfw2XKYY~@4Ig8^FQD@Wi}k9a)G=W0$qxYi1odv_o(m5 zWB*DOf8ff%^7SttxQ6CAH7;pmDRf%mn!ELRdOp+$4>|5XgA%p&L)U!Aq7rrd zL)U7jb6&H$<0IE_S9e;4?*pd~>`*U!#FD@F1@+!Xu2f9_&V1zR8*f;x*0@&s0(pzf zXf^y}SFi4#SGZ$KE@HN_r7U{OXBC;-UQzS;(b44vwen+E%HS_8q6OQnm5@c|Qr^;Y zmi|AmU6*$gbDUE;7b6Hg^06z%5nG^+eT>=O#C(-j>q_Xk)CwLT@e21kYgkP}$TIsu zbxo~nc65RCuG@62ReNednrrS?CfCcWmc@nVy58Aa%&j;ySh2@t*0jSzT$JDuRRqSq zk`X^2R>`;Lq)@o6Bk_lygkFPahSt3K42#3%pSZ4bB;K!%eBv7GLIOKaG{mSQRP55I zM74Hmv@0J>U1S8x53vX(hG>c>pJiJS~3DMp55nS39$}(csN|n2cU>hQI2mc6zqiJ@*8%*FsrjnwM{u zuU^bo8)E6reU>2{jsfz@zV`AjvFUQehCm{_V{DRl1SZ1osqxR-?DXt+&fB&x`;h!f zs1vy?BH)Calq2>{1X*>|dGq;4q6dlZ+${j^D_ax6*aw6nz>+z>Kt_1Jin`_h+0vQ! zo$ueg^}@sEZA1zfSS<*UZw}8B%D88av$#4CkVn3^<27m1+Xe3jQ)Osn^^vVP2M){n zlSk;7prFMbW@SGLVDZFDs03`NhDnLFo(R)~|6)mmr(JscWurlWRExpW>zBqqBAMwa z!rM*Aq~t_lDe@zYXK{@~TD`YNw2o<;KPjC5{MVkeGV2%+$X}amv3H{>=a|>nH4+HY zC$^F?*qkqOH9X6G>>5$I{CnMIQ8$=HUESitf;NZBIJyC5)JscQY(tf;4mY)J`vUrygU-GFd3%P=3pNh0D$*v?H&&Hls;LPQ;!?%Q)f6Z9x(c#`27Tvq(pRSU!&8(&rFwveswsG30W0c?KW z9IKxlk&VhCgVVmlBuGr~V4zyXI|qS26FNaWHAU0#+voDVWU-r*BcaS2^nFxAHiZyN zE38nA_)AE=l^yiec=P>BGyVA)RmF#ypwNfBU~7R0>SX&=c0sx)K6Xtn{l0vTJfc*Q z`qD62ro_aMcYJqpY1kib`u7f|d%>e1jnVb=+J?RTx)IpoYi9S5tBmu<(F2nDi0H$R5(Q6UO&x*yK^o>7JJIQTn6Z+Ehrs6|UwV z?K8*fYVyj;5B**WH;27Pz1i2|-fp4VXPd2$Np#wA0S-AKG*fMrJF3bZA#Ez_LS}~Q z0G5n@ET7qljE*5^VsHLJUXKfS4!F$&lXcnfimz3ZDls3QX*oezd$>d`b`{ehIjXyx zj*D$zE$b>U{ST|uZ(SG1%udgWD{o(9KHH?0e(UP){YtDP!a_ii0OC~7<{Y+m0oCU6 zYB+;1jr#W->xb!I>fdkpE0<9i34ds~Z1LS4YPsxoYY^T}j>?gi`dAc;DsWefI?s z;I3xw-i0>JkEiM>k-p%mdiL9B+XdOW=TTY-=T|mMEYHejXjeE}=}gKg&22B?EPLe2 z6cCk>){$#lEKYs9RdPgQhEigl&I#vGd;aOV*ID@{;eBoWieY2Nhjb|s`R1%V#<9rE%WGD1kGM`c?t54jeCL|t$a+|9 z{EkL{>hG`~W>f3q^zkU|EDl?O$9C8#Y-nDSzr$voz%LxRP8{N^%=|*ECo^>?$o#&T zxldqmw60}hH6|LWW5{}$%0dCKaf-P9sL z|HpE#bth;m;MVRyVUhE13Jc65(3FTJe@A8+2Tzo@SlSDbjg@tiqD+I1(ZRQ{MLPVb z3`$<2e_l&fkANJ3bnk@rJaU@@>eZ$nTuJF$-j$EyK#9sy4h0JK#+zEgJ|O=NPJ->6 z3ETkOid68gq0Q*&@1t#}>eL56xO#b4S_Mc2JY&PA7Nh?X7=5eA$$mjZhHjNTVxr%j zf#4v<_iJhy``srtQVlrc8OC0Gb+u;oJ~7f3g=%=_TLI&Xbln1w!xo$D=Lj7>^vmEDKQv%EMkHOgO)x-#xd&$CSx0w zsgSdrC#g!l8MjF{;e{ZVP(NrN4nJo{}m*J(q-3YmdX_+MjlXwMd;+R}j&Bz}UR;htar1n>&*Ru}&H05}*@GMuhs z`+cGwiQV^is$QmRc*@92^q@C-wUu#4F^S%Mr!HOmUd;l3J8>5Z+^LqDuCWQvr|aC> zDi3#~S}@~qzIoNds>XEn8}e|5{*`VPBl;N2*5_MDgU*%9uP~mqh32Ih%GKbScEw{5 zOw+hd^aXK@P(Fc%-=`~~Ee!`nNM{9@T{2tirdXDFAfl*y!w-V!y2LcwJ|~pph#dCA zYS%QoMZMU7?j}O1Qw^>QI^=1}*(7+ecb&@r(bYfk#pPBjo3L+B&kL3ll>1O4uLr>x zb>EM~X3Th4ef}dVrkl2?v_@C@Wee8nx@miuL@7!=_|zzxZIGIqqqa=}jv) z`|}PKm_wgd&o;V7b~(X2!nuf1%`_=w@Pq1KjjqH+kKeBAqccAbZJd)Xda|Pnp$T}4 zYvIZ1AERx>HL`=+)dtmmpjt2x2a!Q9)hi>y+JkiER_jLilph&`D$hw>wo$FFJm%_q z(dhzkAJ5p>Rb7xN=x*DYOFv?uHOJ$`t~M+e(GGIHJv;p!cHOLAJmwlOtVZMJE`NtT zI#C0Yl?3T7F`tnMt)uWAvz29!YOeAGRfm7M`d`-LoU}S>a{8#1b*-?T*|O<9=ARl* z&wD=zpu-WG4~SeAkqtl6f-!sLt>gwn-TW_CuWo+fl&Z21R2A1+tQr2TUEWuZ|BDcO z78f9k);9Ilzg!9F8#LulOv^r*R@~Ac?5CK-?F!`WltZXIO=yR;g2uynYcZ4VEfD@zTa!WK3caFu5Z^)6_$ite2uej%IXMo3E)pib$Q-Hrn~{f>z5W-_ z;V~-2=kvo`H5C!{fp9AB()m>1`K1(FVn$I>FUb0) zgi51?mHVWY)|wl(9}~3e7YM)w-q&bm8&ozr^160`F@9 zmiB#%lhx==*gvpM4j*c=flm_MEsReri20>AogcIARzd!chJKKqce1>l-@A7x_QjHm z!J@~kq5X~7N@MV0`K1CqFw0)H{?c7m%f$mt++qtT%u8QM%zx=?!fwLyAsr8Chc#zp8H`=!M~1t)wW1@INV35ejA3|TNEmmaLnh>@0R1%w!?l=Q z&q_xVNOnDofb}dN`y~Ao)vakOyS-}OnyT`(I`&dvF)cg4O%7zQ@m8&tM+~GTl@Ek+ zq_1b1HI8ic+|%k=h2C$9O`mK$6w2{D@Cg8+I11Z-bOrDd0984<2ezFkZFS3loxocF z4A<5^=sYUeW_}XVdz?NIZPU^{Zy;}dVQD4f0`Ej%s83R@<~$^|Dl}uH8ooEmxUrS( zZ`hCYTgzU@>z!tA0_G8WwWNqbtk);&g*C3>aWe9_cs=3~)Q~?|-O+FlkHs%?vha^t z*eu-Uy*KO`d60Ef$kKikErA7maFOhjt-en$KUmRS1TDxKWX4HkU(ImVV)k3in+Fg; za6TmVzkCx9Tm99WwQ_<`n+MW82V(;@loiO{15)=XG&@=)IKA*&`>nX(RRNL528=!o z@e$MxviqC_>0=`LS4)LxWxoF*ohkcBVd=fnX>4nBmUyRUPkMQ6Mejgy4U@Q57}sZS z3fB_Rq&@pU+q!12ANrB=mjXBLrigXScddT2s#@MzG_bmwH&{hDo1_J6GNlr_Zwkyu z`RtsA@$jW$XYjK0+;%-DELCT?=X|XZ<$X#&=*W?S?azA(Mx=YARV$!O5o5$UQ3W_WMWDph8Rel1&G%ic9~Zy>&1`k6o8 zMUrXDcmCp*8LvC+ZtJy%zWhMV0@>Tr^LE9q)0Uih&p}Lg%JMVJs;nLLRNgjw-phW^ z%hI0SWDjNWn$cDsf8NW^ylqu^)r?Z!F0QCIU~F-d4K-1`8GPl>**q5pinkFpr%!?T z(T%zt)UfdCN7dGBN zk(Qp?!oO{TfA0+o;h#`MG*W_s%u~NE^*jm^*hq-Izqoqn=Y?&1=}K)<5hrbq(KaZn zK9%i{j%i>LkK z@%-5v>A&?LI&^&5M7@LMR7UZ}mTmb4Xf4f?K{k8yk+$q4npiCc($OvEMR_Ik7$M;v zVlp8#n;wMm~M@#K}WDVY`f*9FswDu_Cn0j0u`ApWr$Z&Q zmX6ms{>RF}F8{pDa>;#kVmcl}PCSOnkbjQ9p9e+eHJ_+SKfBWU>=N+^uGWR-|D+-c z!mFA%kQqA5oYBqd!Jml%_1iG@=FhHd$KK&8vWXbXN5`rmO~iakII2pTT)lID8!pI< z6p<+CD56mCvs(zJQ%LyH1!$zImM+u|a$`iiDP}HW|2a{l68tEJ4GYz-Cf6zsk~k=Q zSF8HdapZr;@NE?`B@X?0`0kQ1`!K%XBDlFD6X%}NY?^L9T!a`A2WiV&>P2Xqzl-xz z#OxyFlLKBQ9$-1J0U*lN(XIqCPvVMs_R&nPZmw5>xL<{M=o4q6Z8OrEy;9XC;m*S0 z;MPjna`0{f=Pp1(2!GlTE2JbW!N)oQtG|OVuDKDuUo*>k!gJ+E_|7qgb=jkwfnYY=1e2CRblYRkm* z;)5Qdb{?|Du6{%p*0eWb+^sqLvJXjB8ZSdZED1({=QriBnU*LlDYB0)J@AD70o}x+ zc4h<}Cd(5s7*ILzifxWvT&$1zF@%7nh;Q0EsVTzlZ5*R3Nz6*q<{27ll*7|cSDHB9 z@|{cgzF7YdUHFgJ*&)ZSX%KEy-Yl0WjeM$c>3RJ}Q3NWJ^7)l_(gz6l?wuOp|HOAD zj^pglM0t{gV0b3(_1ZFWGNss}IPnuVPC)Z?OH+JjA|z`PvAW8GHc!G{+ok`RQ#)(R zPJIpE-WxHN;2%UW&x8w=-~olWOH53En_ws9hh-GiVf7cqP~f&{tXoy$FM%f^_#gV^4PNoaULAQoW`rPzNxR&sH{i$jh91j?6Tfy z%+PIP*xP2>6UuZTl!jRe~XUNA3Xe9v>JQLb*E!jntJ(^YoarLM6>$!l;524VeyHj01&SD)55Usy%F0dS^1Rel3;FuE* zRCeO`XxpWfex%uzID8B`JSZ!{5!l{!Cc*)tp4QP2No+XeGro!hM_G*DEymZGYVc`S z@2r0em(Tx`P{rT-Cts0ozAD-M?%9Rr>-^F@IU{aP84)USVRUp(vFLq5rx=>4kcFlS zRdw1m&6^_+N7aa&7m8->$k_J#XOERyOu0mgZSwtot*4{lSClU|!`=Xv6DfaT?CSL- z2=36#d4p_K$-hCfP)WQKTcU`3xw-77N z;a06Jt{(ASimf$M4%py0KKzvbQLKjjO7w8nZL(KS)61H_gmb2f%*%%~t8dP@dW;pDrD{ym>lZ-O;Q6)K?Sv!7(xiwe z#=o2~&4s%8-P7f)3yF(VwODmM%h6D&gNe_1)|Hx>#yQP|LcW*_C;lM|BaFha@D5c! zX#Z;Jew{~zAO+fj8VORqco3B?9;&vUb&Zb6OV#L7NU2B9x-RT6ywK`Fln!-;D*2qN zWI=Q-F}{;$OIuI?czQ?$^XZu$N0?U|*xOc3$P};ET;J~w&mebE=})jp<}W4T7sOXe zL`+lTs&X?CT~y^xYD_G0mp&z{!}=-B>g#iyyEC|?S#|!+HN!QP(3B#EHec_fZv4$P za74+sx{q0x+@=~tsbYKSxgD)ezPWV_)tAtFb7_2`xp;hwdh0i&|6}^8fBxo5a8Af* zQD=T5tZAnaEvo15#EyJRzB)9wS>^uj%IKXy^Zf+F5JihN4}){TSsFZ_k=ml3_}#^! zu&L_x-|8@C=Mu=Lp3mE7jyT}f{oP2pTCg( zg+$^xRK-cv5@r35Mf;zVnce3X?G3U+T#;Za^L8&XIqwqX=>j(DTGu2~ntk*PG=S%- zW=fHAur^D=p@0!nm3IK`nx9=qEiro1^WtLz-B}}Avw3`|HTw`5?=>8i4H>bkzM`Pt z--}%Z#@Z?yQp>tmHbi19@*%hE4`tnMYS=Gvbl8V+27q~^v>$Y|!c$|wdT-~-GpQ?6 zE6EK?QZB!CaJoMyBN+*V2qMqK-puAY56`MN5{peD56JBBCP2z#*T{7{?lB$Z5OQlPp zvtkMGV)%!Ys-j_y7lz+K8fB{A)O?50-CGgPBJcWVByy_sb-p_iZ3I1=kW@uzr3uMZ zS;PG9ME}GTKH#4?OoA5mkq1*##3yR%uqr~1%}CVQlXUjveOU=~2;M_v8eKJQvvoXljJ7|8E9wj8TmQ$1^QkYoeh01uU#Ukk~4G=24 zx5PCs{M-HK%YRuvB-}pw&4DuLQ=Pswzy&yu6%M63LkyxHDAbzzmgVYPbHY4q?C zohdLa74wWAegW}f4(cBGM5I70I4An)zpbkRYLDgODG zzbv$s^_7)-8Rs#C*T>H$ScH+|houkw;?)~c0|(~G!LSDNc0#k-8)*#k_Ta=pGZ$MC zI-Yqv+~-Y#S1e8DF*eH7j_1~%L7M={K4@|hyl_JkgXAuq#WG<=|J&2)fZln6JsVb3H>P-i`U?pcZvkd!wg4Q*CT-T$d zba$-juNvAL!@UcO%oExR68oKKd6AU>@m{3oX7Taoc)fl$2`zjS)08>`7!6DVM8?SJ z4vP>N%oE z_$sv`+DPuQ)B0H?##*$n?E;j!(Z8HV&48kv1RF!+p8BJI3h0U?D^)G#f)3eO&$ss8cnO5t;h4OkqK^&4WM= z9x^+SV#%v*s+d)c4dzgih{#*T$Mei#GjR#Z%$^(mPa_g zI>NLetQ^!%Fhih}j{#eNXTu2C*q$$Eay-(8&eZ!xM+>E^JhSi)--=9kK1f?gBV;SuMkPch|Wi3d?FG|<+O_Hzd4f)qvAVELh2ijinWFWUgd0HjD`2+AZ?;VIIVs}emQY}%r#Co!z zy{El5eS-70c8!h-hyUBjMChi(l^>%lS@TyYAS!f!&f@g!pf^*jPW&Z{ou4v8!GBv- z$zp!`!*ta@v#h|J-cx9)j?Z5fSv9k)@%#ygSHgLOj4x-F`F5YgglV)?#_yff6p#20 zr87YuaEqUGBF<7tParrA=fA9sB+{G2cGL058@r=!{oW}Cni9mo9>*XwbcE7VeAUdQ zzUwni!=i;IpQRELcj>56%^*rtS%)HXAl)lVSq&Rh{wctv&PNx?jZy$LSwp>uun62MB#RICX4pbbJnL7T9Kbgg=!voYGSIak$p zHb!f;Tjv9>!v4{!5?p?8%VVPO6A_BiXSkM-7QLdaB9s#&Ac@56Tm7 z4ArNP1}_pDW{yWV(UXqd9B&ND?J_~*v~F)6tg2R>GRSY4q34Z5-14yW04ko=F9grT zxFy^N|Li?mKTqe z#$yxoMx=VGo6)cPYkJd+#Tpt!34kf9yqVTcecjFI)9WTTb}?eKh(r%%;~#HNk5uv9 zjSC#Z-73Ai(PPjnk-GaiH<_s)k$%?uZs};mx7NHrWW^V8Q~ed)jehEl?nZL|=AOF3 z4TP73yN^qsVR*V*2dK(wV17YnXM%B=ct+0a@M(;)%2dmgU;mi zX7znfW2u+8gykIXs!!H%gpZ^WRg83k;C`Y$)QMk8e-$Kob(m%nF`21yG-guy)n6Bs zIj1!epTCGhkvI-BBEQwmfn;oTeR~m-bLv}FYFRg5IeVtK(%9jY;h_wU1)_x2J-;p^ z@MCBSTL*QAj{aM#G7^me&f_tJrcX5bdk@6OOW%sDuEcyxZvNn>^;CSFQJGUZf)B{Y z^+`M1y6AzTn;lgN7Gojj9hIg_CYin;bdwBn?vSttBF=B72VYNI0tz`XaGA@=yLy~F zH-xsGE{W0O`#n;G9Kmk*sqS-k+Tz5kxF^1iSCQUC`XyWzJdFGiy@=_VTB zHdAkvvqm@;*h7%*2M=24j2V@=Z<-!I`Jsq#OTTM8C;7z~@P((6N6plj#+{|bSt+HX z#j)$cIXa@~m48?BYzt+ul|HGzKI#>9wzo0Cv37~Nw2v{?*$t829es=>=P!uQHuf?4 zI6o!vMjxZM^Xc|T`}!CY2HiYZ`jaIcZT6l5b3hcs*q9IwXW*Z{sg_XIyp`S87(a9M zn;}uxIe*92DVct8PI#j|1waz!l=atVUnTm9MY8CY4F)@#0B3j_Tc*Wb|>2AF2FFMt{eXBh~X;UJ z$R3;p%mL;@BruK&?jXIJ>la+VgJ{6@)olV*IABi8#ct*b2Uvd?_<3-c4ICK5uI*ul zfxWoL`j~IUFgz99kmAz0r)}!4envNE?~^w5NIxUB?{EZ7VQHw?`IZn#j3yAA80qJU zk*cAek<=qDmAU!xKs`dtNfuS1+;>MdtN#6sG-pLbvvT)0?#(&ww7OWRL+BW>>q=)M zkYG`2$~PzK5)8Kq9fqwxG^(}nf|5YQsL~9|M|;{d6t#8lfz4{r09FgC2vKBDb%kC@ zIr7YHX7i=_1B`A_6L~|orhI_Wv;Ea__waD_*Z`wP%;@)JF`#}0YWDzRQkS(c%zkTb zx>o*FVy+sFxI#Y@slBr6%!ut91>_F6_fS!6YrNOc;%(MwTOl{e4C;fQe} zj6z(iM`Zf=$xlU#+wCQ(nCze|GP{>U+gA8X;wxA7Y_)lA5>zkiOZXhVR?=M)GxnI) z{aj7EqKJEp8u@(}-xFnJH+HA;ZP@QTc<-jtmj}n#yaoBf64*8s*n$p_trx%^H+Ve& zf(2IrPqSL}gmx`;w>$b5(p%h}u)B437VYMgvuzPP46 zDPILboe8|$DTHg0Mdk;pLyFP8$AC6(InTe9-M&SwN&$bDf%-HTMGzu-YL-YLNqWZj zWF}kFc#D+>j@r<9WL-erg?ME1c57+qyeI;FzRVmq4(CGtaX1gpiCTZ;E|wP7z<{Tg zMNlNyku^NQIxWH2#IBpyojTkm*Cq0MhQYpM*4hsgBY1^ zxrpU;4QRL>I0T#k9V37an1b~~o%C%OHAlqQG>3{2|0{6oK6U93V|LQ79b`Pj7P|$y zBV47q_qS8Rm4G%WkuB<#Ax7$m4V&q>^ZS38s?e+*ZW((2AuFJH5A0X*LyckfU(|%5 zMwc;ZWKFzJabCDI@}l(^)`5Dp6-RbYPd1L+wTQynUz+i=~h1_IYt`&`;C208lxrYWs@)9*_033TGBclET||fv*1Xw>z+^;ONn=-w9Haqj9GO$Hf9npO=wfzarY7o-J$k}J zTE$9ySF2~IW?_S95<+EgH2Ml0Z3)3p%ds9On1KE!PPk#>pmP#Sl&U=M;f8IpC}qW0 zW*|9Q{TFVk%EekmicfX$GCn5eF6(8M#qnB(B^t;Y@JeqA&OdpZMTaM)CjBmE{`qdf zlp{8cDSGqJRacqAR}cH`>DhbABE4E_jy??Q!wq(Orve=R_$oR0Y6dX};BL()0E6+*24)0@>SEY%!~ z@CXOB{6im|<4F+wAP{CI!A6gXzld2DshesQ#Zp00N9b%Uvx9OGQ&eGXY+j8CRsW@| zpp`5#!}?c>Cu^-a}up)ove>~Hj8)fI0hHnP0EbndhoKFYYrQ8Gj=9A#YM zHQ;B!I~JMcVWvjYB|W9*cNEO97QxFRL-Tzcqaf4kBi3RWgccbU*2W*Ldm%*-n-aJi z1h;97w^h7DOeV;dbxsdySu~%F+}TI8FEm7-z7yKK$!Stzv{q!!QWHlTw-Q_D@1u>J z`Du#%0mVzy&=pa*85Wr@o&Zt60wVIs$fKlyguhTNTkR&x#M$3PPOxalt1`>?hQB1A zVyu}k`tS(s9jjH@MeI*U6J%VPEQIbV?B4V)To^sb|3W>L;}N}fatnh=C;Ur4GEjwb ztUzd$$ksCq$qBzt(6n3H?K?xeDac}7s;pVaNBUD-AB3^DPMYlY=X6CUa_civ1?w>I zNPqjPrIXZ>?2OB>gsmx_Q52$R?ghsa)Z0w(*{;Ucd+zd z>`u_aw4X0UnD*0nWNq8}sNU(uNXIq()r@pxA+H=erPibyJsmBnYFoN7%DIXM-=-Tw zUG_9uid3wBVzY|RFeVv>Jjh}QK4mpqm|YhuCr=)i#ERT*?t1dIL)t@dk z<~pBmIHiJ@8tIv~O@nPgtlg{ML~RQ9eiXBbqbLQAeg>R{2B5Xt`ntQ_*5;5i>(3Ko zZ2q-Y503lslp1oGF{$kb3VVH&pk)CtKy))$>c96M66`0Vd+a!VnK9Y!9BiIa<0lw> z2iysns?b7W7Q2>G(FmH(dKn`TJ0{D69h)W?Ple7roC!NN9w)l11-}S3`Sau$+m6gE zBiSC)Cl@Vm^6IgYQU-;t2j#707kPHk~QPwkcpcnvBKeQ1$s+?Z?c% z4{UqT?LLZ~(w5KN?z(td*@Np`t_!%LLwtHGSN2O!|HvW5DU8Z+t~YXB%Jmtp&vV7t zW$RBN7sH=urmD`NHY>NO&vK|uN2R*uavhMS=HyD%)6{0UU-yvufva=&uQoMk3gda; zV|CLMW4zeZ>rn^k?q}gBay`S9h<(~}dm`6L`hPXoeOwRF-%k2FifcO8 zOs?Cw{*5bo+o#`ix3Bw=>(AW(M!&Zwgh+T?$RD#cn+JEXl-{F8jP1}}ee`)$Ts#B3 zl>x5*HN;u|MD11DbR)$vB2CSmZVYjBQ0mXq*+E{Ere35Of^T2R^YLk_U7j(-`5IPv zqwvBpagn;v|Mx3)D?0Ctuu>_ zgf5qW>b|p)^jsu8ceb!JFHNnRNe1vGuE0o82WJ|c97SoWULL-^PQ~VfOeJaR(tN%G zj!3318Dh26w*Tj)sfY4;fojDTQp!K_DdnmS%H&~qP|n&m?Y31rp|$i_HJFU}%29nK z_g=26xZle41@56k)rYumWkfjZ^ypBosfZk!oX=O?xSqiw!Fsumt4PPMC^== zixEPy_4Ekt3%Jg8^I#>3yLeE|m4N?ku1qZlpWq zAybRq8SGe=rp6a?Z~xWmtvd^iu8tLH>VZP5kF72&=! zF%aE5HBR-NZC8=Ks2ziOrm*z>G&Oq`KQsC(XBm@XK4l(3A&(WusP|_Xy+-z%4Mi-8 zu{{pV2A*-p*scMh0N>e&*!$w({|SdWtIxT@(udPjd@&V**nv939XF?`CB;yVM?unJ zhIQbtdaj*CHn-_RiLTQ=IEaB8#=sSEoyR@>UQNFT>Gx{-JxIS-)9*q0eGC0A?pRv} zF_2Ta&f=a$cS{BL^!pb2{V4sug?>Luzdud+I__!;Oq>VAWi+Bxca$&kLyQVkB4Ss18rB|7qdfqB27iRtp@9{%X1rD zop$KswQeKRu{BL?bsHBu|CVM~N8Cog(3o^rJxj0*dMQm6moQM7kE-$#G<0K$ii8=LLd+BVIxRom!@3+RX;bOab$J(qiEP&G72yj-jIb7d9YVx!@G zxelfyZsB?x_Zzr!u)tAh(Uv{jpQGJ&LZZAFTB#c@r!kLS&T7ASPK^5Xaw9p907|bK zkZWA`W#{RM3Y6En-^^WVv;DH=N?Z%R#v80L3GSJg!W`Oh(7BuN{1_M7^WCk4tl1Nv z<;y-GoOz`0tW#wzFb2mj*DnX&Sj7hE)xMPdX4x&Oa)GfyT~Hal(Q)Dyb@(b{fL!fY zgR~VKoO@SvzFK)TqSmH)f|7x#D`V8--1q%`L5%H9pcMFMevGXg7zR9fWsEI_OkZ4W zyj@`1lx+)QyB%B)mj*H%je#qe09*$=0Gxy5I)T2x0ALU>6c`DN0>%L8At6o!CGXg9 zjZtk^71tV%rX+X>e6}OLxF^@^P?BF)mHm7A`{fq|B07E2a);AbYrD=rI(xiFO}NhJ z7Zc%=+{Qfa7hh*gbVT^n_UnwxBDMBKox9GcyFmPdBsjL9Y2CHJJ-}MvV2FG)TpeS3 z(A?j9N9*;5*Z$zD_mg+*yTPcmd&lfXro;vT&sY?dWkqJ6ciF_27XE}N&HU+o zoRD4Bu5m``pWl<`pPzpowXdBBa~emNzQKZ9WbSJF3p3l+*t4i~udKyi!Egz4Uu;x4 zumO;_ip(nSUSY$s&uRIHwKd_~Y7MnQZmU$@(gQNw2x* z;B_&!TDUq2gZ2$mxc@<8@zk%x2wQ(Dzom?ujG0C+_UG0qTAkie*WYAhsKU8XJypA#jeX8tH^ivgn^6`? zqUmNMgV@Hymmop%N|_ZwX+5?nM2!s@!cCzI5WM80phc%z`WV@?9=iQ&G9t);w3km= zFA>oavxNN^(k!t>yZa6;k`m!AI%>22ZHmSsVU7pKDIVfYA)PC7=&~{X;t?p`kABJ) zOhi^GvV}$1HtUPiG8hD2K*UZ!Y%E-KK$qGV)Nf0SUe0CjomRb;VlnxNRQ{14+Em^e zyG;{pLH04)9I_`AB+cC^IFV}(l!O^l{S47kccM+qK&zMsmAo4&`6M$Ul2O9@^}M-s za8ZmPNlb}3nVr6#KzpP&Xq!n%;))SPC(3}HtiG?k=^)> z@?qky?mnd^-eQcN_0a9s?iObSMB6Jzi!~Eg{w=sua*XKPkU%-fM5>`V-j!mWFNTbG zF_pL9mu1MHx_$4YGJvA`D#|xU?NsmIV)P%)$$Wt|#LV)qpd6X}O_;&G- zgG$nmd+X%osO4mgPG6R&hAcz2_&cgVEm*O7D4kO;q=!z1dkDj`|Eq_(=^i>+-Z9ie zqiPybs0Gr{liJo-=8pN$E4rEf-#JiBWNBGR=Q~<%CHfuR6D=JteUzv^UuMi4l;pG9 zeFYIrKMu)0f(j~RA7u8oibE~`m7n9{rc_-VJajcrPZui#iczO>E$u5F7$JmZwjV|YGV|rw6|GRpz%t-T6+}phWw*IwaFk(|iP|btAEqf7Zj^=l zIs0UK)@VZ;T-b4RzP}(<8(o^)_nlUIZU<=-{So(yxQq{DrL$t65Y{*yIGc?= za@9G>{t1cx_;+Li&3Y#S8D7I%RM$TTTl)WazLn=QJOig87M_yq?;%Ic%!#O)cdp?+ zd0|45_)WB6@JAVq71PQhrIy_GQ zgiNKD8yAfEyUrKa*n>=6d8!TcvMA=P~9D7fzua+Bqo#);? ztt#SzG1xdk2 zH5tp4V)y)~Pq`OtWo_A-@4jm58m^n&SLvX9!P{bN)cKSDIi;e?v7|0*uQJMwq7Lbm zA_t8sVg(u2UTs9s+Vd&z`A6&8X`-vo0^aktQ))tm(WiZUC!~Ts+o}62jH!t|t;~8+ zHYB(mc2t@#d3E})>Qsf1l6E49EeI$aCw3j>ENG{WfTLB}M8Ssw?V6^i0s)P+7Ov2o z8>wb^jgii~pF5>0z4Wg6;wkl{*I45CHbQl&G^UJgG(|*biRbUsOW6}YhzZgDC&VXW z!|7j{&_Sje0(n`QEY;5|XoA;OBF??x9?K3wj2yZN?J$1`$lFXg();*zP7Xgsrk9(y zy{#q4VusoN7oCT-740NUEN&lDo z>Zf0KU$eDatoxe!X(4Xx$ervE2Hk}P-px+syUQ3pdfB&D*C2h%qsyj9msMm*-w=j- zr5+aC4JDRhto88yDZi@VU2JX+{!R6}8$~zQ33nRfsJI<-+ z@5Y9w{O42Z)4M@{vwuma5?R$SCJX{4fPQ36zrl!BXX6t;fQM- zT3Vosvs2vFK4W~w%dawgxjxfzYqb2+r)*MVN$DK&l$eo!5An4LRSsSpqj4M!<1a zSLByY;>+e%Fo^jbT?6aDW{2lAI5q}Mc>zh(U%W_r-M#7Rl=ve#xVvVPU#)Gq!k zwEJbqp=XJYOVu*eFS1`}J0;tw1mH?w7w|nBlup16z#-r$@Dp$n1`&a9J$dUd0X70V z0fa7^y+2YBW6PDowf0|Kv);HN_V2fA9SJNk5uPSJpuS#jB#yr~{182a)z0;LgmF!c zZybCYOG~sV{i;?(m6-FxSWQ9N9hLRUAv>~7d7%xV@7->Y8Nm*b`_>e z_$VY2MD~Pw;8A0$XMGrsJ8^ruiqye&%;yQ=)e zLUX~-(ih#-t&ba}j?q7>!;c%2)wIn#%Oiv6Gn1}<7o3j zAGqybYRMBuZ^yI$vNEpvL1(<8Qx7Xo?5dsA*=;^@Oig>zNRme1^rSH`@xb@G(OT(U zHjFw!w*|-D=Do*uJpZJz(XOtjHWq55XF*@v6YK5vBlj|RHPyyn$J_`tV6F3ABJ&wN zW@zR{JVL1!?)|1n%5o?(QETMi1kpBcF@`zs*>FmIyv0c9dI@=i1s26btoG^l^V7L4 zM(@k7f0)l%J2yc;pZ>2*8zNsja&i8)=fCxsex7;zv%Ww_l)=aN5&ohrNE{Ck?*}W`NE%yOSajj151GgKn+j}90KI8=eY~`cc9G{0mK0b zKoXELP(88L=&v?DXA~^iw8eIGC0Ml%cn;VD90s7~b#XufA^^njbz^}{()nB$0Hvgt zbG1ZP_Bv+2wu;)x^ZlfcaBYJ6MqoYX(VOhwMoHAN)eR$MI@E3$#-;0~Yy@Bx1U?giEYj{r{q+koeRSAZJeJ>Vel1@MhxMC(Z$2buxfdMX0A zfX+Yy&<98ch5#1OfWmGxlM zTO{@a9|MPhdf*sv5;y}a0_?duZ=F0Lf*64LL0End# z;{exM;2>}a0NFqmM_mvwfnxxOBkV+73vdqPu>lbpnJg9~j&wXAh?O|Y5mBE6W;I>V zVY6Dd-5Bo$ug72ya?0)9{}GehUqWh$7kGp4IBkd0Ox>c^r4-B-oORGP+$y@1xy1< zfQ7)#z;a*}uoid(*bF=aJP*7AybXK^`~&zFs0W&W(_a2@Y-Id_9zYV10;B zGI(@8P5XzQzkvp{N$0jnyW6A}wn?vSldfu$ez;BgsW#~s+N9r-wC;bd@3r~iV4L)j zHtB{o=~Hdezqd)d9{;12-P@#-+oaRlq`m2FZYH%!&uo*P*Cu^Sn{-v1^k3SfA8(WX zpEl{2+oa#K(j9bl56aD^;|>*g#yF zV5PhFQP#AhYutVGnz-KGS2)|F)$Sy{K5lpSJGztmUGDyRt%Q{6_40^&z){H8(PQpp zSv!xmxG$)KY-z{tIQKx-QHQrK(LJaxnTILvl)6-|quqn+GPq{Cht%bAo$ekg7VdSk z-NW=EyU;znZZY>u-6Qm3yV5%Oq=W$t&nM_KgNJ-SZQ zTK7d3oppOJwur2Ij74DGV=dz99>*#TVRetUh^jkX=xKejJ3|ZoQ{9(vF1e#V!+mM} zB(Ayc%j)yFUICN>i`|*^Wn7oLC)8JRUFDuwzlQ5tcUJv+t{dHxM8#9T&Fjvt-%jFX z_vHFrTx;Ap_4~OVaOc(^_X35FdVo3zH2k!$J?YM zH-|ECiEWeab3X00*)BYP1C!7uJ*Q3jwl-;ho3v_^ezr~ewKnOG+N8g0lRhcwae(|e zpRlg-Z(^JDm^SH&ZPF!As0FVXeY|UEz&RlIE}QM1yKS~TXc(ujqL4qqWZVO%@@Kf7 zDx2+yAD#d(FtaT~y4NCG8?Mj6ImNLs!Oq+MTUk-Op8&)G|54U}|Ftl~nr##bchf;7 zy>2Y>Zhp#UtLFJ67NY$SlGM#MTd+-obY}k9S~++xaR*;!RSV;zfgq_Qm*{Xdd9leaIFQJ zfGF>?Frh#uFdHZX<9n|Anjd<@Z)bdW?F5u5rjmb=I{?jk%CfFS|hr?!f*zJHLj+;Iq#C(DZE&!5j zk@h*fmrwplQfa%x8PP6EQu1kA#tDb*xt|=i+9rps>A1s|&;9HZ4r%WHDehaqs;Jif z-!(I929QBiL`2aoilU<8E$;~`Uh;;3sd+`%Dv5xw@ornwOjA=+8#8a2shO3XtZgZ! zWbn zfJb{^_OoM#J?;q^zqhZJ2Q0e=;>)M3tH{^W=Hr$4Vm4Id8)osj=|Nbg0}Jhdt86N= zB69D&;>72eeXdV!KG9u$`GU7p4LG&a}pX=D2eBMO7)mk}`V|J!R7^JZZX}G<9B8*7G8@6&9zI^k2 z3eqvAVnI&WX^42@efZKV@om5a4VT;M);^2}TU3njOFKeQIOfB*rd370?|c&Wl@+A^p>Zry4|Sic zlu(FoLUtm8MBh^}p*lVZHSjhy_~up8hmCDM5eIYN$G+_R+!oFjcVRqPDZ;Kk3Hx?} z%a!;B+I*F3tgbk#Uw*+p+^UJ{TPl@sjO|}o$v?-gxyzOOXV?h}J?WdV;&|T#vHrYE z#uiuNd;Bg1jgB?ryIpg}T@tn#%ggHJGi#*W7ca zciRb@y}_F*=)dCAAL^|bx)XFC z=yA}~prz17eo#RU&{)vEpu<6PK<9z31KkOF5cD``31}%)P%u=H1GFP(U(j)&IiQO{ z*QL0{g9CQhoFC;>(2QBfj}_)qCv6fI~NOBYc=UyBJ3%^a9|WL z4tz5}p$pH=0~P{{;kN~J8?Y1D4eW8lvk;b}z;WOtPz1jc;Ien#R_6_$7T~DaZFi~J zTw4!{&hww}F+$5QYZ4LkmalwSer~T{nGh4dIQ&{yWj*NSvwCeDiB*rdD)YT>r*R5X zcftFmO$BdXAc_JAOE_wWbr?j`4F^6936Ft56&Q|bKmzP8KwNF%o&h=%bS9|RU)&Qb zP!(UMd=wob05l2vP*p)|0}X+uKo8t&0on%W3?u-3fx$r2qsW3_7z)P&fH)Z>++$%E zfhGczfgC_Dz>^kW8tk)yd4Nb<@IL~&3|I|_%p~r{{hdTl!|mnS0{e473TkK<=%0wo zDMCANa?YZu&ME4?nF0=TUvwrc%2 z+~Gd=Pr+TsC;SDt>)OFZ`t%1pB7*(B>67#^_6r)^PT==vD@ITS_v`yec}=(-KH(d{ zZDq)6*BH2c3)InjUlIBVS3j?Z!*tRpuiKW3X>f~f>doOJaQiONR@?k>R&d%5cVu}V z!n1F<8`^ORmLqUCGP}8zJ{^ZgRH|ROP=0N>@n_Q3bq?;v$bk3y4elmh5r?OC9Me*t z1ggR7&t^&WQY=dW?LOkD0K2u zg$@GZI(Sl{*v}MN1`Gf~zECI{nD+%!#M-=tF|jzXx4U+&P-wcS2DL-OYBg=rIGQU_ z4Xi;Z4v~oAy$7fmzT-i0%vwy4gf5WEbwOX)hXa#<8Ngy-9k3JF2NVKDKnYNa`HvqE z21ElwRbav-of!^H0%ib{Hj6aghbDb$4@5C)6_FrPV#?I%;sWP*wbly?gY zTTxi&-9C(Q6n3%A{S8nITu6~Z&65>3phFa(eBxxBPSfkV!DBvpyK0{?yWBAFG)^a= z8aj0ll_6Rc&2C>O2dZ?{#?BO#vcC7;smK>SNToy@f^X(s_ANr5}-nIAC63jY!FCfpbZIv?nS8v^&(RM;KT5yc@LVn5;cGa#3;=~MWYafkGi z;%~bbv17{ptQlG6eMQhdFgxV;6hVdA%Rl8iv=41I!TQI4CIDod3M@+J8U%ygQL z`Lt!Yx`++nKl8?T?y^HUOhM8=*a~XMf~3W;Pi36_!Gf8aT~l=FclI;eW<_|{_y+KO ztb|V^%j8o&X6r7BbU}k}X92b;;_2)w{t8QDMVJ>k&3TbAWOZN-e6yk#WJ-}XIA8-JE zCqauM@$(IE8i3Zr3(~B@ZdxGn+2Bi&&$9z?D-lQmh6AIZNYeoqun<5xXSV@+fg`|4 zpcsJi_C3vJJ|Dc?M3w@ifAR=vxFWEKt!BT{ak7MHJVmlv(kYUa@|JPiO=Go;OEKl- zuFblJ?PA`>v_dTAC)jMenWrHxtD)`K%tjY29xe&9h+Sh|ftyOT6_=8)xTh(#Z|9~$ z@K^c2&dehAChN?S=_59ToNNwrDT#N<$g8x6ZD)6k&0XFF_OO>txh$`m3Ym$Jl9fl~ zQY2zkVn_nms9=!<)h)M-E@@`LrCpx$bVPP3-?4Al*X#wFz!ssa zuVoupU->K8-k=JpIH@aH}R&dKMb|7V25_hOcn4-CHpCo%OL!mbr7uR zNkn#(*F))M$+PT4-$o4~!Pc=6axJ!uPW{{7_V@1Lo`HrD$?ZbD zS><>_=_)^gXkTQjC_<4ZqLS2>lcN-wZW$|Yrna)GsByOG?-U=Rr&lfS?`pe;7QV(ucFhiM}9 zh7kp^!U$VEdW7B2j>*U5<8p))1P#_3g^Z9cvZd?*e}{r4o!mMN;Fl>`sma374Z>I} z_CCJq)gO%Y=>R@5^RK1iaWuwcgea2#HQ&c_JZiTWGBcC1d~3~JX(DorO=p`cNa$?= zbtzU^Vwpc>va2-dF167Sb#TAkn9WUva+k(XE-LONrf`Lt0(AnV#vjb$!#WMv3Uhdx zrWDXBMO5Yqwh&df1r4!M(b!5l$JU{FA|yqu2t6?Q;b#%{7- z*-=xH9%I!cKPrOkaH!;Uav{s03mZ2?TET|0GdzZ3@V;qF zBqtK-gsJi|S@?@$IZ&*lYz6zA#wZ`K#t@r$B}8h9rmrn6V|C2LCec-zK!H+c>Wp4K z1);rZ{KPYq=UEr(;-(;Wk#?}hXe{NkeCA?hl*_tOS864%S85`eYKU@)(qCS{9)_(B zy6+dLzGxgx8VmvOXU9pB!g&)`opqybbT7@hix1+g*mSUEuX#;0Bnv!1M#e8o)9A&KI;MLRmAUGfWZ0{enOm1q`fcXJ^Nb)+V2 z0M=3W!1Q5#@!(({%y;~(DcT}zO_ZpnQbYP2bO%b=g$=@QD&njoiFz$!rM#weiTc58 zp!qPH=u`GEJxmY540p39=}B4(vx6<7MbwX}N)e^fC478z1lsWm)s!0YWJD?2aiQEF z?H|A;$bOP7`$(Q2ljq85D9sdXZ91e!=n;ApVc(?%Frr>X*y_m8LRv^eP*?BRBHf?d z{moYN%B4ekiFKCuD-X#_k)bJwY>)D?@`~~slKlX#Dm0DNlr%n)jb;P!q@*j7_J+NW z?xV5nGkTRZr{)w3(~_NH?O8AW5KCo8kf9;$Gi4cH%CR{v4ijE(n?ddjI!M{<5QR#= zBV+6Bj9sH0$X5{C&kN}dZYa0A#^Y=-x<)dd)U4ufv1jNNdLCV;6MvoUrWff8dI??Q zWoqann@w0e_pr~|chrbxvjVn=&t?B)efVqiBkG?%VKO^I8~DBKO*+YXV3{*ha^Z*;h zis(D#3ai0?r$49~-@?YQ&-onsjN>x^QU;sBoivUd>;}6@?rdeB5=p_lp>zwo6O1ip zjieWqoytV^C?j5`9HA-Z5R%2lvE95GZ^~cfPtg*p&DK+-ypugmFYsObJ!-^0q<_*N zI*G1Pl|4!!N_FKec_)2M8)+A1%k9{=6shc?&2(5^$x`SE_7Chs-0!Agw4Jul5*DSL zrthhN@&r|L#^e{SQ0Ct``GtvAB~`aat#W>mc*CrTQ-o5 zq^=hgUYrF=1*RLZHoe!uYhlWDlCxctN_Zh07bIhv8;y4@F^Au7Up@F zem5Ye-6cC1t#1T|u=8f*HETdcxoHhldKi6gLPk zKiAA&O=c(D1%V1EgjvbuL*aJ8>RL<;`y%%>Xb38_2BLfzw!SElXx&rTgtCI03Ad_Y zCs+$jI2TPRVzlD(ijQc90!VVGXb`Ic3ZQ{RWIp-G22U4ML}S(jI~LQ-tuB$1*~rH* z+#7*>%t!C3#ePI8qB85+#i*PQEA=ke4QLK2w8|3;F)pALfN1cCDb&h`S6uCU{DAuc z&DcWZWf47Umqg?&0yUxGX`?^#VM&HJ@Tp4m@fy8eF6dTX3Lxl@K+rAMV`jbzQHBrf z-r?U6i7p4Me8gGG-jsOJ$Gf1{grnDZ6MP%eehAgl1B29qXtV+dn#lNlKqw^Mr+K;* zn1a?2nYR*u60xlV_j$U2ScCvrGSLM~6%qeDie>4Do3jmT59xL6xZ+zPfT+QStP}@L zy@B$OtwL&m5#StpMIqL!R@)0jauO|g3T|PO>}W<5U_=)lLinv)!X_%B0E4=LoLxiX z4nRaTOxslWhe0EVFa=0#1jOh&2*LLlNegH&1fm8eIX_UT)tndLtcsRSvz&S0>H@Cm zrc;w4fwk-^uF=I#Zxd3#iZuT4-iR^Rjy4r`%uYaotc7?DSqp+<~*^IOgB8dTG zKirRHE@bW+`m~7lM?~AmlQXH4ux7p=MR04zSlAZfc=xh&CMAs)IzVwttG&n-X1sc~C2} z&2vybt21=B(h?Om-MlTTssO>Oqf}M#Hd82Qs2fG<4(|et>_<^7p?aQ1>uyBByfSn< zE%9(eGW|KG{axr8T8$$vRnQ(f=Hi0C8kQA(5SxU^vG%ZYC*rS%LBN4R&w*WxIS+tF zlUp*gZ6%e4;t898=&0$aMe9xun62`J!8V=hBip@U3x^Qi-Y`qYi2gtwK9Rc1@c3!hr%y>OJH0Mln75*FVAn|3klOZW}7>%BT=knrc1<~gXbl7xkSZIhZi?>W*#l|saU~U4Z#acvG=xP z0VPuk{OX}Z4s?y%&D01LD-sm2iY9FB(InomPr`p4x@UnK7EwBHNkwIeiminV^s$>` zkQvbs%m@D-snx%0fj<@8;buId8HEgp;#l(rQNw5smIM7`HKTA(2e(_~NC>r+&jk?e zi)L~{5Jg~-A*&p>Wk9&6`;@l;6)Or*3o&&8tmLfUPvH86_9H`onMQ>D^>$c@06hM} z66Tg^6RumBvce*=6@r_BsqrOVKN)!0F_URS#`OA&rLn(hdaIzo+o zn(heIXVoyhsn0^#RIFJ-=?wbJub2Rrz|rZ<+zpV7BSW)5b{+2|9G+v=d_*XSBwV6I*R znnJJ^!S)%si(n;Wtqdj=A&cNTf&*y8lHk*hJpqJ5863eb!qc>hg0V4F8{PeV)HAX7 zFp+)>hbf}j0c@omK*cPe-(jxMZ)Vr{6>%IVvE6i>KBO;UKE(P_#_w^MP}+jU5zKKY zq|G!QqnMDSC8)13jH}Ba8o?+4gKZxAUpI_gQMf0T>9DbRNH&)uA*oo#AhM~Lm-eFm z$XHX*rHErHVyFR`n~hk+A%0h5lGzjYL&+V56F9YC>1ie{CWFEyBr?pjg&`u5 zspUv$IQ;u#>MS@SVG|sYrWuW2OZfj^L{Tgks1Ne!{jCckn;QC~z%g8+#P@ zFkiE@lH2#*|ApQCe-Y{bAWnCM(*F+v{#U~7gXlrAlmj!RSI`F}U=zj>{N z&vp5_bX|JE&di+>|4;p@sg*gc*+C!M+l@3_rztILPj9BB@A@^fU% zOhsROoQj~Twrkt971)_v!A3(}55@N4MQWq8QC`C~#6hVe9=H9c#@z-_?+>v!Md*fa zrEjHlWr8B2->&^A^_Ba|?_!U&jP0kkQfKL5c_Xq@*=)LlpT)CP62{9PU}x*7oNU^j z7r*Wb(JAZ|JnZ~ZvCK~8JUh?!$uBBXrB9VS1=#-CsyhH|8`b*iT$AoHe}&I3 z^?uVVRrkRNS2D2F^lJ(Cc$i&kf;zPVuJaY@3UxiqXf;j!8nHb9n^T>HBu}e9s25?b zo5rf+?QXNGZHJG&9qTE-DIZs|<$p>=Qgc2|Zp_apqj^&{AG>Q?_$T~JewlxzMDQK_ zY2Jd+3V->B`h z&RSRPWA$_OUagkaU5nScXg#zrt&Y}9yQ22g`e^mFPFg?hJ+;3!KpUtH(gtgZT9THm z4b@WcKN7|22<-vwsyaj)=GM-rBemh$C~dShMysne*BWSJwOFl@Hcm^`(zJ9fLwitj zY7?}PT9%frP1SO>9Q^6{bZvn)Q=6sD*5+srX>M(<7N}LxbS+2=*A{9^wWqZW+D~jz zSp(@2=`m?>S~pN7zeJD|xZJP2MZ-myh5{=gnLH9kZLg#1_b3y6vZ^{cp;O(m-jUbW&23 z`<0Q(XeCXVt30eciU&JSD(jWa$}Z(4WiPf84=9I~_mq#6TMBWRbKZbA;?dYPY{6Uc zHvE1*lBe;>d1ucv7gy!DOhQw?3aEo8!jcvW3Y|wE;}hbSGI{RN=K!IaxA+? zen#FTUtzW79&C=>LLMl;B|DT$(h_-!(pYIIw=C;ZHn1$S%srxPN7+oQiE6VrylU-X zhV;HvAV(`P$|z-`lBIMlo2_h6wkR(suPSdSXYus%y5h&fcs*<}Z)XolO?V9N!4vsP zC51o0M{%e8oRZF`^Bc-+9PXIQm+>d~YQBcA<=-n3kK<$cIG)Ng!5;_LBsX8cH}C{L zg+Iu%`4H~r^Y~C4t9Xj9=bQO6d@FbH#r!dz#h3Gq{2+Jo_jn=C=QH@nd?DY)H*pVk zYxnUt_8qq@@xDO zcmK(6a7E>68CO+5)nBcm?&6yIF8`2MRln!Oyo8^@p{ZZ^O&+X9sc-TFTvvnC8fta* zEN`Wr;~{D-wYD0ewo^N*@oEpXkJ?x5r+&q2s-fzAYMk0r?XM0{2dZ7wZt5U)usTFd zL_EUG=UbJkZu|%{p9`~9X#p{MogXs&tUI@V=h?hlm6QBrx&8M2dn))njGDFXy@on^ z0+rpJ-wRE(jXQW3-i~+VbNF2Do{cJBz#j%{2ei!;KBht>wg%Sf^x!i3BR*YYRh4k@WxPjz6jyp!o?FueCCU?2id84jfAro7L4gyc;odVlokN+(WZ(RS1wlQNI zayOPAF{X%n!4kCx# zd0?_Q#_n*xfrl#lutB`vHgCdinYXb~{0_`vn1e9y!+e0vF=38kq526P?%d6Mjd}kU zFk&t%1}b4rm7A~d6zE&iyywPV!uRE-7)$Wau)IG*XJOtncLuD?6k?uGh#f&OzrKh~ z!=JGmcny0D!u*K`xWb5Sjmt1MV6NNd2$t*TVI+HAf0HaD7Wube+~PqXv8!0G{{SO4 zH$FF4>G(5BtlY1_h*i8WZ<;$0Z;EllG$LdE%rY9&@U+bjPd8u^ADBhpE+ZB%ugf7C*w&U^U<) zObGT`ESvZhkB;8NW@ngbYGa_U&D?cZ9aak_0w?0?;n}KXM3U!BV+D6GhfhRrV&C5K zZ-~vk#xPBAXqe-n+y!cCK2~eaZkrf9Nr_>xc;MECwS{R7(+;M+FmCpTd3+=Qdoit8 zOC}zGh({p8SQ{x~TjfnW<$KrO%o4))5zL1$miWC#-@%;5bH=mgp7EcM15u}p)hsv1 zC>$F_qLB4rB4HZAL^1zpCJbj}40$=QhOuWypr0`|LVw9UiA`a7Y$kgMXMq;uyv7`T*}XTP!E*)5zI;W(aKO$wFjOAV#Q zQgf+=)LQB!b(eZbeWbxK$SA7>gk%dAQZqvp%U0-i%EOY2Ccc055 zE{O)2R1psI8oEyOEW`|d33LRgc-Kh0E;J9+dkLO_S8-GxbQq}M83QU_&YK7t0J?lT z-f;vKe_X49=Gy-F(+aoA5cvl_eAEt)u0c<>*UZ<&>fWQ75w8Rl0mWa{rU4HEZoGRW zBch{KHQENm1Z%YOx=I^vsMM&sM#aFT3u zolIx;$#iDE4OE)KLhkgTRioNhfuqIOdSja&$D2V#z-a&n+s|Fa%h3pCPPi_N?2A(E z2I?+0tD~v$Vi!HoD2~*J-nU$%z{fRm01JU_fcLtv5)}#rKcUg(RTf;ZmjdHfYZM}| zMx$t8!y2oi-SL0nd#u1Kz(>GO0KZPu0O$-P0jq&yK=m6$J%E1zoo*r=@G|ffP!pp_ zS6~vb3OES-1c=We2?UqnE2+RjKt=;T2s}eLbon(by|HVy7Wf>fj^5t`*bV$H8B;TK zQzeb9e$wGbiS_)bV7bbdU_R_nudg6Ocqam{60pD*qwS`vg#En*M^k&Vs4e_J8!~CceR9t~cezY67 z0Ke0qg+SL7KT?L7Z>YEbTY$KK9iW9fU&T0XOM_p3Kbi%%_|Tc~+SbdD9`U&!onX0z zUJ{pZ8$SGnaBuT*y9W5t9v?T|=SK(N_J*I-&(BTo!^4|_aY*olPXLjjZ++Zb;QrZm zi!dUAt3KhE4Yb_e1cdvR&;84A`)gKubqD!Tufc9ViXH4n;qVlJ;u8I6CF*((5CgwS z8F=vsv=Ef}IjAS>;#GC=%DPa5>FIv747f1Uj|MpXXdG|^*fRlbGtH0Y<@r%D?Bqhb z!G7c^92VW+MxPMtBp@F zqvPqo2F8;`fzkfOIKW>5TrjQ|1=e>%zn$$1VI2VKeRXCM+?jCC0G$K67<3sZI)JIf zc7pDPdmpG!VuhebL5o0z5-S0{09p!q1DePWnkW!7474t2G-wQHyA;+A?^W9$?}JBQ z*zqpUIlRk*4@6k+T8{#SHa#~9$ONEOjX`e();1phDln$XL2w=cj;AnV+n&G%jY{qJ zB1o@}d>pCQ6rbb^ba(Xgr>p+{6dd4B+knc~5%`@3N`c@if4?urS#iy) zZQWbY&}zNXnwERH?T&NY4(s!=L1TRveP+%5&ssBstm9T>mtVqgCq}+5^`2?MC@{;P zR)a=^egfJHRF7s18#mf>1@TN39-e~q!V1OYv)~g$r()12S+)CcWn7t>kX`nf7 zlw_M(HNK40I~!Lbbw@0Tf8ie0J%A40iw23uA7bKPA=h*cpoPGJE&-H>`@spQt6umU z&XED0@4M?YB{AfT!s)b)t*IEjt2iBF+9V(oKvsQT5M;&3YB91}yck$!k7*@&p6_Gz zSmt>xL3c4vY#+pCjcg^;J>w5A=Vt}+$Tk3gvlnC*_*xMmi7_X1hOS+e!BE%;= z+5$qV7fym?0=t2|6Y$^mz`=|F+AtZCo&%}QgwRb5pfy0nJ2nIAy0Kwlsom;?A;B8@fhJGl@Ny#$iK zB%nK$!V-*sLx~3BfZ;$6up2lCoCHdNu%+m4Krdht(6(jUHZ9wFhD_2KGtx8ki@~(< z12SKDhi=B}oRU$kD6q8*ood8I>9vihLM_@z?*^IUUj}04F7`7@RvW*H*;4w0dQCU; z$7TM2c&~jg#-P5_C4v{WyfJbNLV5zH59vS#RL~V18ysY8K)# zu20rG8C^2YeQZJZ-#^t0x-%d3vO$ zeV(o}Pd}GF-=4jeZ1#LOO?NX-;tc&GW;B_lzh-WKl{{;FJxk9p?w_syWel66k74P? z>vQzH&~!(dGc(f>mEPGwgR>ktsS}-W7~SXU8^H3-T)izw@I!jBo%#;r>O*=5*28G# z);r^t)2;tzjG3o@WAvJ@Kj*3bupaJJ052FZ3-tCz?gIUYo}KSc#b zU!LHI%5zM}&q{M;WM?^|a67swWo0|^^3x`j`*${P7{6T7n<#?CSn;Ud(v0fuNA>MS z=3;$=(Qb*}8i`C?qR$F&<>otSLTYB7({p->&KdmAJ*L+;ZXDF>8ts=tgz}8OOZAp^ zCA?}(TdLnHMdcZXmg??^rrib&9WcN_$q54zdZsv9J9;M#9_Vm6r{uJ>Smye*wt0+} zkLzh7;490~u|1FLE8*~@tkCN-W7bN2xbeeUJV!%D<(8hS6|Q$rtb z^jW8O_AFkf=QE?`dcAol(o9Ru%F1>*^7EXIX{osxspB)9#=Yy&v-hso@np<&4}V&3 zTEj}^?R4i1hsb(5(lHJ`t%n)spVl`UD>k6b`VC4+=#!9S9N3^AF%E3hw~Cxj-lYE~ z=!2W}X@XwdtYbgL)U59q4Yuf6o+r2HNs_qr`&JBNpxd9-CrN_`8Tz(zL3rQjvP~ap zY}}^r^>;aExM+N4_IM-lIenLruw8Ezn307hp&ayd*92q5c74B*vO{m;nYTj^W5KEU zuIwqPu8cG**?$^w&+C!Ogxu^YM(Xo=eZ@61$7#5q*Xwz9!^u2f@6tbH#_rwv9y_BS z8>3#-^ZlJO(lYb&GNw5_$6wT|GCdWvNOVZB+<;G|v&Nsb;PXh6S#{Zfo2 zuj<$8P07!6W#nW!(UcRMXwEcep2L+5S6Y5Mx8nujyL@l6%Gva-_uf z7?5C$d>#F2&+B@-;CvbgkxJ?pKcL?O367|Is z+NUQN2lwgCgyY&i=*RhQ=t;(xZ|KmQlK2k5H$i=0PZpItS1nZ-?%2K!2dR-MvIRbZcVC+>A{~`-cPi z7fq8B;*)yzB~dbRBoFN|WN1Rt2$X`Xa+>lUpDe`j!vcM1RlGgd-4Q>imm@cOdPaIz zquyJ3M03*~mDjx~IqvV5lrl7afF0N>(|7#_^-35nbTf3s!ngFEl5y;y-qI6#NFU6M zwTE@={F#OS(fIJN-kset0^iZ+RVlsx=gsSX{z<==-Y_=315I1_j^4RJY3W}#Z~TSD z$&J5m;v4%n%WnSlXIW|4jZ%XB*3G|4jp!qK^ROHEPc-ww(}EkfZr#L#16;Stj44O- zRL^He^g!mQTgSdM9K6uHj@#5=@9CX1#{A*MjQIET+HS_`1k|Zr2Xl@8FITWP)g-IJ z&Ut=#PoKo(9)pvN)Iz;!cy9<|!l3wp3235(-U&%b3BCFyr}P`tGsW0msJE&$C}nI) zzkvzKDe(gn|tog_AVXC)FoZWEujiWcSIuN^2o45xeR^sW`oyDpnNllnRPtmroVKD>%j z1w$I9QV#}XPMs6fW;PD8SD*2C$N5W!Cr8zri~r-3Q4RO$LY~v;r(!%us4nB&# z(&pGh7V8kq=$3=%08Sjzw08JlUm)QK>0(-7wD?f3*L$UmS9Dl^+^;W~u!GX0yC5ww zv&$0}>lz}yJ2$1K){_R0&L+SceXz11SM?o`+2KK>#3YZ7|R8#rQY^r}|eHQPN? zu06yRVkx200AcOCbV#Boor?i(>aMtiKqoT{xGbq$DerKJrW zwqSU(lxO=T-@nZDMD-Q-jo#`C9MJ!Pg^v#!{7kpVxJPE>XF-=`Pt3}gg%QP-J=vMn z8ABf?5|IR@kdbALiIyrQA8Yk!Svo`f6rGoWZJffk0i}CU@J*;}u7%0bVmpKKsrclP|&3cG&Yai6P<0>|Fp}%!w>{b0lu$8qMjKpy1 zh??q&|6PAiG8sG}r5G)1S$f@JG1kKW*7ijYcep(F{i**c+vTr=44%HBhkC}})E{Nu zY+bsg-{ZNr%+9jDc6QKcGvc*pg4%d_B3RHp;RB)FTQJJ=LY4YRr@57gU3JviyTOVw=ls3Tz~Kwg&_iO2$qdLbF;A ziW?14qZXKpVFa)(v!l{!F6>!YE1ABI+2j}*HEXmGOU&R0VNi-DM{@_o;5LSpL4#8q z5lQv&Q?Lq z@i-dOGw>$30+{C_(nYLM|7_&4@o6>}&jNCkzBQQWi zWJTZ9F(n^2#ycJ5VXazEhssIEGDNh8WgnZBon?ENVU3ro25qwira3A1t!lt`!t%Yz+$v!#2>?AafH4+Dz>H087QD(*FYu7YJbh diff --git a/contrib/mORMot/SyNode/SpiderMonkey.pas b/contrib/mORMot/SyNode/SpiderMonkey.pas index 0889d93..73353ec 100644 --- a/contrib/mORMot/SyNode/SpiderMonkey.pas +++ b/contrib/mORMot/SyNode/SpiderMonkey.pas @@ -6,10 +6,10 @@ unit SpiderMonkey; { This file is part of Synopse framework. - Synopse framework. Copyright (C) 2020 Arnaud Bouchez + Synopse framework. Copyright (C) 2022 Arnaud Bouchez Synopse Informatique - http://synopse.info - SyNode for mORMot Copyright (C) 2020 Pavel Mashlyakovsky & Vadim Orel + SyNode for mORMot Copyright (C) 2022 Pavel Mashlyakovsky & Vadim Orel pavel.mash at gmail.com *** BEGIN LICENSE BLOCK ***** @@ -26,7 +26,7 @@ unit SpiderMonkey; The Initial Developer of the Original Code is Pavel Mashlyakovsky & Vadim Orel. - Portions created by the Initial Developer are Copyright (C) 2020 + Portions created by the Initial Developer are Copyright (C) 2022 the Initial Developer. All Rights Reserved. Contributor(s): diff --git a/contrib/mORMot/SyNode/SyNode.inc b/contrib/mORMot/SyNode/SyNode.inc index b9f5893..f0bca4a 100644 --- a/contrib/mORMot/SyNode/SyNode.inc +++ b/contrib/mORMot/SyNode/SyNode.inc @@ -1,10 +1,10 @@ { This file is part of Synopse framework. - Synopse framework. Copyright (C) 2020 Arnaud Bouchez + Synopse framework. Copyright (C) 2022 Arnaud Bouchez Synopse Informatique - http://synopse.info - Scripting support for mORMot Copyright (C) 2020 Pavel Mashlyakovsky + Scripting support for mORMot Copyright (C) 2022 Pavel Mashlyakovsky pavel.mash at gmail.com *** BEGIN LICENSE BLOCK ***** @@ -21,7 +21,7 @@ The Initial Developer of the Original Code is Pavel Mashlyakovsky. - Portions created by the Initial Developer are Copyright (C) 2020 + Portions created by the Initial Developer are Copyright (C) 2022 the Initial Developer. All Rights Reserved. Contributor(s): diff --git a/contrib/mORMot/SyNode/SyNode.pas b/contrib/mORMot/SyNode/SyNode.pas index 40f578a..e008709 100644 --- a/contrib/mORMot/SyNode/SyNode.pas +++ b/contrib/mORMot/SyNode/SyNode.pas @@ -6,10 +6,10 @@ unit SyNode; { This file is part of Synopse framework. - Synopse framework. Copyright (C) 2020 Arnaud Bouchez + Synopse framework. Copyright (C) 2022 Arnaud Bouchez Synopse Informatique - http://synopse.info - SyNode for mORMot Copyright (C) 2020 Pavel Mashlyakovsky & Vadim Orel + SyNode for mORMot Copyright (C) 2022 Pavel Mashlyakovsky & Vadim Orel pavel.mash at gmail.com Some ideas taken from @@ -30,7 +30,7 @@ unit SyNode; The Initial Developer of the Original Code is Pavel Mashlyakovsky. - Portions created by the Initial Developer are Copyright (C) 2020 + Portions created by the Initial Developer are Copyright (C) 2022 the Initial Developer. All Rights Reserved. Contributor(s): @@ -383,7 +383,7 @@ type FEngineClass: TSMEngineClass; /// List of loaded dll modules - FDllModules: TRawUTF8ListHashedLocked; + FDllModules: TRawUTF8List; /// Path to core modules FCoreModulesPath: RawUTF8; FEngineExpireTimeOutTicks: Int64; @@ -559,7 +559,7 @@ const ); var - GlobalSyNodeBindingHandlers: TRawUTF8ListHashedLocked; + GlobalSyNodeBindingHandlers: TRawUTF8List; /// handle errors from JavaScript. Just call DoProcessJSError of corresponding TSMEngine // to set TSMEngine error properties @@ -1116,7 +1116,7 @@ class procedure TSMEngineManager.RegisterBinding(const Name: RawUTF8; const handler: TSMProcessBindingHandler); begin if GlobalSyNodeBindingHandlers = nil then - GlobalSyNodeBindingHandlers := TRawUTF8ListHashedLocked.Create(false); + GlobalSyNodeBindingHandlers := TRawUTF8List.Create(false); GlobalSyNodeBindingHandlers.AddObject(Name, TObject(@handler)); end; @@ -1125,7 +1125,7 @@ var obj: TObject; handler: TSMProcessBindingHandler absolute obj; begin - obj := GlobalSyNodeBindingHandlers.GetObjectByName(Name); + obj := GlobalSyNodeBindingHandlers.GetObjectFrom(Name); result := handler; end; @@ -1153,7 +1153,7 @@ begin {$ifdef ISDELPHIXE2} FRttiCx := TRttiContext.Create(); {$endif} - FDllModules := TRawUTF8ListHashedLocked.Create(); + FDllModules := TRawUTF8List.Create(); FCoreModulesPath := aCoreModulesPath; FWorkersManager := TJSWorkersManager.Create; end; @@ -1261,7 +1261,7 @@ begin cx.BeginRequest; try dirname := ExtractFilePath(UTF8ToString(filename)) ; - ModuleRec := PDllModuleRec(FDllModules.GetObjectByName(filename)); + ModuleRec := PDllModuleRec(FDllModules.GetObjectFrom(filename)); if ModuleRec = nil then begin fHandle := {$IFDEF FPC}dynlibs.{$ENDIF}SafeLoadLibrary(UTF8ToString(filename)); if fHandle=0 then @@ -1422,7 +1422,7 @@ begin try i := ThreadEngineIndex(aThreadID); if i>=0 then begin - (FEnginePool[i] as TSMEngine).GarbageCollect; + (TObject(FEnginePool[i]) as TSMEngine).GarbageCollect; FEnginePool.Delete(i); end; finally diff --git a/contrib/mORMot/SyNode/SyNodeBinding_fs.pas b/contrib/mORMot/SyNode/SyNodeBinding_fs.pas index 63e3a54..e38818c 100644 --- a/contrib/mORMot/SyNode/SyNodeBinding_fs.pas +++ b/contrib/mORMot/SyNode/SyNodeBinding_fs.pas @@ -670,7 +670,7 @@ begin end; stream := TFileStream.Create(filePath, fmOpenReadWrite); - stream.Seek(0, soFromEnd); + stream.Seek(0, soEnd); writer := SynCommons.TTextWriter.Create(stream, 65536); try vp.rval := SyNodeReadWrite.SMWrite_impl(cx, argc - 1, @in_argv[1], writer); diff --git a/contrib/mORMot/SyNode/SyNodeNewProto.pas b/contrib/mORMot/SyNode/SyNodeNewProto.pas index f5c57b3..8755d27 100644 --- a/contrib/mORMot/SyNode/SyNodeNewProto.pas +++ b/contrib/mORMot/SyNode/SyNodeNewProto.pas @@ -5,10 +5,10 @@ unit SyNodeNewProto; { This file is part of Synopse framework. - Synopse framework. Copyright (C) 2020 Arnaud Bouchez + Synopse framework. Copyright (C) 2022 Arnaud Bouchez Synopse Informatique - http://synopse.info - SyNode for mORMot Copyright (C) 2020 Pavel Mashlyakovsky & Vadim Orel + SyNode for mORMot Copyright (C) 2022 Pavel Mashlyakovsky & Vadim Orel pavel.mash at gmail.com Some ideas taken from @@ -29,7 +29,7 @@ unit SyNodeNewProto; The Initial Developer of the Original Code is Pavel Mashlyakovsky. - Portions created by the Initial Developer are Copyright (C) 2020 + Portions created by the Initial Developer are Copyright (C) 2022 the Initial Developer. All Rights Reserved. Contributor(s): diff --git a/contrib/mORMot/SyNode/SyNodeProto.pas b/contrib/mORMot/SyNode/SyNodeProto.pas index 7288979..6b08103 100644 --- a/contrib/mORMot/SyNode/SyNodeProto.pas +++ b/contrib/mORMot/SyNode/SyNodeProto.pas @@ -5,10 +5,10 @@ unit SyNodeProto; { This file is part of Synopse framework. - Synopse framework. Copyright (C) 2020 Arnaud Bouchez + Synopse framework. Copyright (C) 2022 Arnaud Bouchez Synopse Informatique - http://synopse.info - SyNode for mORMot Copyright (C) 2020 Pavel Mashlyakovsky & Vadim Orel + SyNode for mORMot Copyright (C) 2022 Pavel Mashlyakovsky & Vadim Orel pavel.mash at gmail.com Some ideas taken from diff --git a/contrib/mORMot/SyNode/SyNodeRemoteDebugger.pas b/contrib/mORMot/SyNode/SyNodeRemoteDebugger.pas index 154891a..886b4d8 100644 --- a/contrib/mORMot/SyNode/SyNodeRemoteDebugger.pas +++ b/contrib/mORMot/SyNode/SyNodeRemoteDebugger.pas @@ -5,10 +5,10 @@ unit SyNodeRemoteDebugger; { This file is part of Synopse framework. - Synopse framework. Copyright (C) 2020 Arnaud Bouchez + Synopse framework. Copyright (C) 2022 Arnaud Bouchez Synopse Informatique - http://synopse.info - SyNode for mORMot Copyright (C) 2020 Pavel Mashlyakovsky & Vadim Orel + SyNode for mORMot Copyright (C) 2022 Pavel Mashlyakovsky & Vadim Orel pavel.mash at gmail.com Some ideas taken from @@ -141,8 +141,8 @@ type private fIndex: Integer; fIsPaused: boolean; - fMessagesQueue: TRawUTF8ListLocked; - fLogQueue: TRawUTF8ListLocked; + fMessagesQueue: TRawUTF8List; + fLogQueue: TRawUTF8List; {$IFNDEF SM52} fOldInterruptCallback: JSInterruptCallback; {$ENDIF} @@ -221,7 +221,7 @@ begin eng := fManager.EngineForThread(curThreadID); if eng<>nil then begin Debugger := eng.PrivateDataForDebugger; - Debugger.fLogQueue.SafePush(Text); + Debugger.fLogQueue.Add(Text); if eng.cx.IsRunning then {$IFDEF SM52} @@ -522,7 +522,7 @@ begin engine := fParent.fManager.EngineForThread(fDebugger.fSmThreadID); if (engine <> nil) then begin - fDebugger.fMessagesQueue.SafePush(VariantToUTF8(request)); + fDebugger.fMessagesQueue.Add(VariantToUTF8(request)); if not fDebugger.fIsPaused then begin if (not engine.cx.IsRunning) then begin if not Assigned(engine.doInteruptInOwnThread) then @@ -599,8 +599,8 @@ end; procedure TSMDebugger.attach(aThread: TSMRemoteDebuggerCommunicationThread); begin fCommunicationThread := aThread; - fMessagesQueue.SafeClear; - fLogQueue.SafeClear; + fMessagesQueue.Clear; + fLogQueue.Clear; end; constructor TSMDebugger.Create(aParent: TSMRemoteDebuggerThread; aEng: TSMEngine); @@ -617,8 +617,8 @@ begin fSmThreadID := GetCurrentThreadId; - fMessagesQueue := TRawUTF8ListLocked.Create(); - fLogQueue := TRawUTF8ListLocked.Create(); + fMessagesQueue := TRawUTF8List.Create(); + fLogQueue := TRawUTF8List.Create(); fNameForDebug := aEng.nameForDebug; fDebuggerName := 'synode_debPort_' + aParent.fPort; fWebAppRootPath := aEng.webAppRootDir; @@ -680,8 +680,8 @@ var dbgObject: PJSRootedObject; res: Boolean; begin - fMessagesQueue.SafeClear; - fLogQueue.SafeClear; + fMessagesQueue.Clear; + fLogQueue.Clear; cx := aEng.cx; cmpDbg := cx.EnterCompartment(aEng.GlobalObjectDbg.ptr); @@ -765,7 +765,7 @@ function debugger_read(cx: PJSContext; argc: uintN; var vp: JSArgRec): Boolean; var debugger: TSMDebugger; msg: RawUTF8; - Queue: TRawUTF8ListLocked; + Queue: TRawUTF8List; begin debugger := TSMEngine(cx.PrivateData).PrivateDataForDebugger; if (argc = 0) or vp.argv[0].asBoolean then @@ -774,7 +774,7 @@ begin Queue := debugger.fLogQueue; msg := ''; while ((Queue <> nil) and (debugger.fCommunicationThread <> nil) and - (not Queue.SafePop(msg))) and (argc = 0) do + (not Queue.PopFirst(msg))) and (argc = 0) do SleepHiRes(10); result := true; if (Queue <> nil) and (debugger.fCommunicationThread <> nil) then diff --git a/contrib/mORMot/SyNode/SyNodeSimpleProto.pas b/contrib/mORMot/SyNode/SyNodeSimpleProto.pas index e0a27e4..b9ba70f 100644 --- a/contrib/mORMot/SyNode/SyNodeSimpleProto.pas +++ b/contrib/mORMot/SyNode/SyNodeSimpleProto.pas @@ -5,10 +5,10 @@ unit SyNodeSimpleProto; { This file is part of Synopse framework. - Synopse framework. Copyright (C) 2020 Arnaud Bouchez + Synopse framework. Copyright (C) 2022 Arnaud Bouchez Synopse Informatique - http://synopse.info - SyNode for mORMot Copyright (C) 2020 Pavel Mashlyakovsky & Vadim Orel + SyNode for mORMot Copyright (C) 2022 Pavel Mashlyakovsky & Vadim Orel pavel.mash at gmail.com Some ideas taken from @@ -180,7 +180,7 @@ begin PI.SetInt64Prop(Instance^.instance, val.asInt64); tkFloat: PI.SetFloatProp(Instance^.instance, val.asDouble); - tkLString,{$IFDEF FPC}tkLStringOld{$ENDIF},tkWString{$ifdef HASVARUSTRING},tkUString{$endif}: + tkLString{$IFDEF FPC},tkLStringOld{$ENDIF},tkWString{$ifdef HASVARUSTRING},tkUString{$endif}: PI.SetLongStrValue(Instance^.instance, val.asJsString.ToUTF8(cx)); else raise ESMException.Create('NotImplemented'); diff --git a/contrib/mORMot/SynBidirSock.pas b/contrib/mORMot/SynBidirSock.pas index 38cba6c..2392200 100644 --- a/contrib/mORMot/SynBidirSock.pas +++ b/contrib/mORMot/SynBidirSock.pas @@ -6,7 +6,7 @@ unit SynBidirSock; { This file is part of the Synopse framework. - Synopse framework. Copyright (C) 2020 Arnaud Bouchez + Synopse framework. Copyright (C) 2022 Arnaud Bouchez Synopse Informatique - https://synopse.info *** BEGIN LICENSE BLOCK ***** @@ -25,7 +25,7 @@ unit SynBidirSock; The Initial Developer of the Original Code is Arnaud Bouchez. - Portions created by the Initial Developer are Copyright (C) 2020 + Portions created by the Initial Developer are Copyright (C) 2022 the Initial Developer. All Rights Reserved. Contributor(s): @@ -1291,7 +1291,6 @@ implementation { -------------- high-level SynCrtSock classes depending on SynCommons } - { THttpRequestCached } constructor THttpRequestCached.Create(const aURI: RawUTF8; @@ -1343,6 +1342,11 @@ begin headin := headin+fTokenHeader; end; if fSocket<>nil then begin + if connectionClose in fSocket.HeaderFlags then begin + // server may close after a few requests (e.g. nginx keepalive_requests) + FreeAndNil(fSocket); + fSocket := THttpClientSocket.Open(fURI.Server,fURI.Port) + end; status := fSocket.Get(aAddress,fKeepAlive,headin); result := fSocket.Content; end else diff --git a/contrib/mORMot/SynBigTable.pas b/contrib/mORMot/SynBigTable.pas index e2d6a73..a03630b 100644 --- a/contrib/mORMot/SynBigTable.pas +++ b/contrib/mORMot/SynBigTable.pas @@ -3,7 +3,7 @@ unit SynBigTable; (* - Synopse Big Table. Copyright (C) 2020 Arnaud Bouchez + Synopse Big Table. Copyright (C) 2022 Arnaud Bouchez Synopse Informatique - https://synopse.info *** BEGIN LICENSE BLOCK ***** @@ -22,7 +22,7 @@ unit SynBigTable; The Initial Developer of the Original Code is Arnaud Bouchez. - Portions created by the Initial Developer are Copyright (C) 2020 + Portions created by the Initial Developer are Copyright (C) 2022 the Initial Developer. All Rights Reserved. Contributor(s): @@ -2162,7 +2162,7 @@ begin try with TFileStream.Create(aFileName,fmOpenRead or fmShareDenyNone) do try // same exact layout as in TSynBigTable.LoadFromFile - result := (Seek(-4,soFromEnd)>0) and (Read(magic,4)=4) and + result := (Seek(-4,soEnd)>0) and (Read(magic,4)=4) and (magic=InternalMagic); finally Free; diff --git a/contrib/mORMot/SynBz.pas b/contrib/mORMot/SynBz.pas index 90707e4..f744680 100644 --- a/contrib/mORMot/SynBz.pas +++ b/contrib/mORMot/SynBz.pas @@ -5,7 +5,7 @@ unit SynBz; { This file is part of Synopse BZ2 Compression. - Synopse Synopse BZ2 Compression. Copyright (C) 2020 Arnaud Bouchez + Synopse Synopse BZ2 Compression. Copyright (C) 2022 Arnaud Bouchez Synopse Informatique - https://synopse.info *** BEGIN LICENSE BLOCK ***** @@ -24,7 +24,7 @@ unit SynBz; The Initial Developer of the Original Code is Arnaud Bouchez. - Portions created by the Initial Developer are Copyright (C) 2020 + Portions created by the Initial Developer are Copyright (C) 2022 the Initial Developer. All Rights Reserved. Contributor(s): diff --git a/contrib/mORMot/SynBzPas.pas b/contrib/mORMot/SynBzPas.pas index 5f9581e..a117752 100644 --- a/contrib/mORMot/SynBzPas.pas +++ b/contrib/mORMot/SynBzPas.pas @@ -5,7 +5,7 @@ unit SynBzPas; { This file is part of Synopse BZ2 Compression. - Synopse Synopse BZ2 Compression. Copyright (C) 2020 Arnaud Bouchez + Synopse Synopse BZ2 Compression. Copyright (C) 2022 Arnaud Bouchez Synopse Informatique - https://synopse.info *** BEGIN LICENSE BLOCK ***** @@ -24,7 +24,7 @@ unit SynBzPas; The Initial Developer of the Original Code is Arnaud Bouchez. - Portions created by the Initial Developer are Copyright (C) 2020 + Portions created by the Initial Developer are Copyright (C) 2022 the Initial Developer. All Rights Reserved. Contributor(s): diff --git a/contrib/mORMot/SynCommons.pas b/contrib/mORMot/SynCommons.pas index 84758c7..6c16dc2 100644 --- a/contrib/mORMot/SynCommons.pas +++ b/contrib/mORMot/SynCommons.pas @@ -6,7 +6,7 @@ unit SynCommons; (* This file is part of Synopse framework. - Synopse framework. Copyright (C) 2020 Arnaud Bouchez + Synopse framework. Copyright (C) 2022 Arnaud Bouchez Synopse Informatique - https://synopse.info *** BEGIN LICENSE BLOCK ***** @@ -25,7 +25,7 @@ unit SynCommons; The Initial Developer of the Original Code is Arnaud Bouchez. - Portions created by the Initial Developer are Copyright (C) 2020 + Portions created by the Initial Developer are Copyright (C) 2022 the Initial Developer. All Rights Reserved. Contributor(s): @@ -522,6 +522,30 @@ type { ************ fast UTF-8 / Unicode / Ansi types and conversion routines **** } +// some constants used for UTF-8 conversion, including surrogates +const + UTF16_HISURROGATE_MIN = $d800; + UTF16_HISURROGATE_MAX = $dbff; + UTF16_LOSURROGATE_MIN = $dc00; + UTF16_LOSURROGATE_MAX = $dfff; + UTF8_EXTRABYTES: array[$80..$ff] of byte = ( + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, + 2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2, 3,3,3,3,3,3,3,3,4,4,4,4,5,5,0,0); + UTF8_EXTRA: array[0..6] of record + offset, minimum: cardinal; + end = ( // http://floodyberry.wordpress.com/2007/04/14/utf-8-conversion-tricks + (offset: $00000000; minimum: $00010000), + (offset: $00003080; minimum: $00000080), + (offset: $000e2080; minimum: $00000800), + (offset: $03c82080; minimum: $00010000), + (offset: $fa082080; minimum: $00200000), + (offset: $82082080; minimum: $04000000), + (offset: $00000000; minimum: $04000000)); + UTF8_EXTRA_SURROGATE = 3; + UTF8_FIRSTBYTE: array[2..6] of byte = ($c0,$e0,$f0,$f8,$fc); + type /// kind of adding in a TTextWriter TTextWriterKind = (twNone, twJSONEscape, twOnSameLine); @@ -1140,18 +1164,6 @@ function UTF8ToWideChar(dest: PWideChar; source: PUTF8Char; // - faster than System.UTF8ToUnicode with dest=nil function Utf8ToUnicodeLength(source: PUTF8Char): PtrUInt; -/// returns TRUE if the supplied buffer has valid UTF-8 encoding -// - will stop when the buffer contains #0 -function IsValidUTF8(source: PUTF8Char): Boolean; overload; - -/// returns TRUE if the supplied buffer has valid UTF-8 encoding -// - will also refuse #0 characters within the buffer -function IsValidUTF8(source: PUTF8Char; sourcelen: PtrInt): Boolean; overload; - -/// returns TRUE if the supplied buffer has valid UTF-8 encoding -// - will also refuse #0 characters within the buffer -function IsValidUTF8(const source: RawUTF8): Boolean; overload; - /// returns TRUE if the supplied buffer has valid UTF-8 encoding with no #1..#31 // control characters // - supplied input is a pointer to a #0 ended text buffer @@ -1626,6 +1638,8 @@ function StringToWinAnsi(const Text: string): WinAnsiString; // according to each Args[] supplied items - so you will never get any exception // as with the SysUtils.Format() when a specifier is incorrect // - resulting string has no length limit and uses fast concatenation +// - there is no escape char, so to output a '%' character, you need to use '%' +// as place-holder, and specify '%' as value in the Args array // - note that, due to a Delphi compiler limitation, cardinal values should be // type-casted to Int64() (otherwise the integer mapped value will be converted) // - any supplied TObject instance will be written as their class name @@ -1816,8 +1830,15 @@ procedure ResourceSynLZToRawByteString(const ResName: string; // - implemented using x86 asm, if possible // - this Trim() is seldom used, but this RawUTF8 specific version is needed // e.g. by Delphi 2009+, to avoid two unnecessary conversions into UnicodeString +// - in the middle of VCL code, consider using TrimU() which won't have name +// collision ambiguity as with SysUtils' homonymous function function Trim(const S: RawUTF8): RawUTF8; +/// fast dedicated RawUTF8 version of Trim() +// - could be used if overloaded Trim() from SysUtils.pas is ambiguous +function TrimU(const S: RawUTF8): RawUTF8; + {$ifdef HASINLINE}inline;{$endif} + {$define OWNNORMTOUPPER} { NormToUpper[] exists only in our enhanced RTL } {$endif ENHANCEDRTL} @@ -3103,9 +3124,9 @@ function TrimLeft(const S: RawUTF8): RawUTF8; // newline, space, and tab characters function TrimRight(const S: RawUTF8): RawUTF8; -// single-allocation (therefore faster) alternative to Trim(copy()) +/// single-allocation (therefore faster) alternative to Trim(copy()) procedure TrimCopy(const S: RawUTF8; start,count: PtrInt; - out result: RawUTF8); + var result: RawUTF8); /// fast WinAnsi comparison using the NormToUpper[] array for all 8 bits values function AnsiIComp(Str1, Str2: pointer): PtrInt; @@ -3920,8 +3941,9 @@ procedure RaiseLastOSError; procedure VarCastError; {$endif} -/// extract file name, without its extension -// - may optionally return the associated extension, as '.ext' +/// compute the file name, including its path if supplied, but without its extension +// - e.g. GetFileNameWithoutExt('/var/toto.ext') = '/var/toto' +// - may optionally return the extracted extension, as '.ext' function GetFileNameWithoutExt(const FileName: TFileName; Extension: PFileName=nil): TFileName; @@ -5049,10 +5071,17 @@ type /// internal set to specify some standard Delphi arrays TDynArrayKinds = set of TDynArrayKind; - /// internal integer type used for string/dynarray header reference counters - TRefCnt = {$ifdef FPC}SizeInt{$else}longint{$endif}; - /// internal pointer integer type used for string/dynarray header reference counters - PRefCnt = ^TRefCnt; + /// cross-compiler type used for string reference counter + // - FPC and Delphi don't always use the same type + TStrCnt = {$ifdef STRCNT32} longint {$else} SizeInt {$endif}; + /// pointer to cross-compiler type used for string reference counter + PStrCnt = ^TStrCnt; + + /// cross-compiler type used for dynarray reference counter + // - FPC uses PtrInt/SizeInt, Delphi uses longint even on CPU64 + TDACnt = {$ifdef DACNT32} longint {$else} SizeInt {$endif}; + /// pointer to cross-compiler type used for dynarray reference counter + PDACnt = ^TDACnt; /// internal integer type used for string header length field TStrLen = {$ifdef FPC}SizeInt{$else}longint{$endif}; @@ -5068,7 +5097,7 @@ type // - match tdynarray type definition in dynarr.inc TDynArrayRec = {packed} record /// dynamic array reference count (basic memory management mechanism) - refCnt: TRefCnt; + refCnt: TDACnt; /// equals length-1 high: tdynarrayindex; function GetLength: sizeint; inline; @@ -5083,15 +5112,15 @@ const // - to be used inlined e.g. as PStrLen(p-_STRLEN)^ _STRLEN = SizeOf(TStrLen); /// cross-compiler negative offset to TStrRec.refCnt field - // - to be used inlined e.g. as PRefCnt(p-_STRREFCNT)^ - _STRREFCNT = Sizeof(TRefCnt)+_STRLEN; + // - to be used inlined e.g. as PStrCnt(p-_STRREFCNT)^ + _STRREFCNT = Sizeof(TStrCnt)+_STRLEN; /// cross-compiler negative offset to TDynArrayRec.high/length field // - to be used inlined e.g. as PDALen(PtrUInt(Values)-_DALEN)^{$ifdef FPC}+1{$endif} _DALEN = SizeOf(PtrInt); /// cross-compiler negative offset to TDynArrayRec.refCnt field - // - to be used inlined e.g. as PRefCnt(PtrUInt(Values)-_DAREFCNT)^ - _DAREFCNT = Sizeof(TRefCnt)+_DALEN; + // - to be used inlined e.g. as PDACnt(PtrUInt(Values)-_DAREFCNT)^ + _DAREFCNT = Sizeof(TDACnt)+_DALEN; function ToText(k: TDynArrayKind): PShortString; overload; @@ -5163,14 +5192,22 @@ const /// some convenient TDocVariant options, as JSON_OPTIONS[CopiedByReference] // - JSON_OPTIONS[false] is e.g. _Json() and _JsonFmt() functions default // - JSON_OPTIONS[true] are used e.g. by _JsonFast() and _JsonFastFmt() functions + // - warning: exclude dvoAllowDoubleValue so won't parse any float, just currency JSON_OPTIONS: array[Boolean] of TDocVariantOptions = ( [dvoReturnNullForUnknownProperty], [dvoReturnNullForUnknownProperty,dvoValueCopiedByReference]); /// same as JSON_OPTIONS[true], but can not be used as PDocVariantOptions + // - warning: exclude dvoAllowDoubleValue so won't parse any float, just currency + // - as used by _JsonFast() JSON_OPTIONS_FAST = [dvoReturnNullForUnknownProperty,dvoValueCopiedByReference]; + /// same as JSON_OPTIONS_FAST, but including dvoAllowDoubleValue to parse any float + // - as used by _JsonFastFloat() + JSON_OPTIONS_FAST_FLOAT = + [dvoReturnNullForUnknownProperty,dvoValueCopiedByReference,dvoAllowDoubleValue]; + /// TDocVariant options which may be used for plain JSON parsing // - this won't recognize any extended syntax JSON_OPTIONS_FAST_STRICTJSON: TDocVariantOptions = @@ -5950,7 +5987,7 @@ type procedure Clear; /// full computation of the internal hash table // - returns the number of duplicated values found - function ReHash(forced, forceGrow: boolean): integer; + function ReHash(forced: boolean): integer; /// compute the hash of a given item function HashOne(Elem: pointer): cardinal; {$ifdef FPC_OR_DELPHIXE4}inline;{$endif} { not inlined to circumvent Delphi 2007=C1632, 2010=C1872, XE3=C2130 } @@ -6051,7 +6088,7 @@ type // FindHashedForAdding / FindHashedAndUpdate / FindHashedAndDelete methods // - returns the number of duplicated items found - which won't be available // by hashed FindHashed() by definition - function ReHash(forAdd: boolean=false; forceGrow: boolean=false): integer; + function ReHash(forAdd: boolean=false): integer; /// search for an element value inside the dynamic array using hashing // - Elem should be of the type expected by both the hash function and // Equals/Compare methods: e.g. if the searched/hashed field in a record is @@ -6430,8 +6467,8 @@ type TSynLocker = object protected fSection: TRTLCriticalSection; - fSectionPadding: PtrInt; // paranoid to avoid FUTEX_WAKE_PRIVATE=EAGAIN - fLocked, fInitialized: boolean; + fLockCount: integer; + fInitialized: boolean; {$ifndef NOVARIANTS} function GetVariant(Index: integer): Variant; procedure SetVariant(Index: integer; const Value: Variant); @@ -6445,8 +6482,14 @@ type procedure SetPointer(Index: integer; const Value: Pointer); function GetUTF8(Index: integer): RawUTF8; procedure SetUTF8(Index: integer; const Value: RawUTF8); + function GetIsLocked: boolean; {$ifdef HASINLINE}inline;{$endif} {$endif NOVARIANTS} public + /// number of values stored in the internal Padding[] array + // - equals 0 if no value is actually stored, or a 1..7 number otherwise + // - you should not have to use this field, but for optimized low-level + // direct access to Padding[] values, within a Lock/UnLock safe block + PaddingUsedCount: integer; /// internal padding data, also used to store up to 7 variant values // - this memory buffer will ensure no CPU cache line mixup occurs // - you should not use this field directly, but rather the Locked[], @@ -6455,11 +6498,6 @@ type // using a Safe.Lock; try ... Padding[n] ... finally Safe.Unlock structure, // and maintain the PaddingUsedCount field accurately Padding: array[0..6] of TVarData; - /// number of values stored in the internal Padding[] array - // - equals 0 if no value is actually stored, or a 1..7 number otherwise - // - you should not have to use this field, but for optimized low-level - // direct access to Padding[] values, within a Lock/UnLock safe block - PaddingUsedCount: integer; /// initialize the mutex // - calling this method is mandatory (e.g. in the class constructor owning // the TSynLocker instance), otherwise you may encounter unexpected @@ -6531,7 +6569,7 @@ type // !end; function ProtectMethod: IUnknown; /// returns true if the mutex is currently locked by another thread - property IsLocked: boolean read fLocked; + property IsLocked: boolean read GetIsLocked; /// returns true if the Init method has been called for this mutex // - is only relevant if the whole object has been previously filled with 0, // i.e. as part of a class or as global variable, but won't be accurate @@ -8357,6 +8395,9 @@ type TSynLogInfoDynArray = array of TSynLogInfo; + /// event signature for TTextWriter.OnFlushToStream callback + TOnTextWriterFlush = procedure(Text: PUTF8Char; Len: PtrInt) of object; + /// available options for TTextWriter.WriteObject() method // - woHumanReadable will add some line feeds and indentation to the content, // to make it more friendly to the human eye @@ -8462,6 +8503,7 @@ type // - twoIgnoreDefaultInRecord will force custom record serialization to avoid // writing the fields with default values, i.e. enable soWriteIgnoreDefault // when TJSONCustomParserRTTI.WriteOneLevel is called + // - twoDateTimeWithZ appends an ending 'Z' to TDateTime/TDateTimeMS values TTextWriterOption = ( twoStreamIsOwned, twoFlushToStreamNoAutoResize, @@ -8473,7 +8515,8 @@ type twoForceJSONStandard, twoEndOfLineCRLF, twoBufferIsExternal, - twoIgnoreDefaultInRecord); + twoIgnoreDefaultInRecord, + twoDateTimeWithZ); /// options set for a TTextWriter instance // - allows to override e.g. AddRecordJSON() and AddDynArrayJSON() behavior; // or set global process customization for a TTextWriter @@ -8500,10 +8543,12 @@ type // internal temporary buffer fTempBufSize: Integer; fTempBuf: PUTF8Char; + fOnFlushToStream: TOnTextWriterFlush; fOnWriteObject: TOnTextWriterObjectProp; /// used by WriteObjectAsString/AddDynArrayJSONAsString methods fInternalJSONWriter: TTextWriter; fHumanReadableLevel: integer; + procedure WriteToStream(data: pointer; len: PtrUInt); virtual; function GetTextLength: PtrUInt; procedure SetStream(aStream: TStream); procedure SetBuffer(aBuf: pointer; aBufSize: integer); @@ -8610,17 +8655,20 @@ type procedure AddUnixMSTime(Value: PInt64; WithMS: boolean=false); /// append a TDateTime value, expanded as Iso-8601 encoded text // - use 'YYYY-MM-DDThh:mm:ss' format (with FirstChar='T') + // - if twoDateTimeWithZ CustomOption is set, will append an ending 'Z' // - if WithMS is TRUE, will append '.sss' for milliseconds resolution // - if QuoteChar is not #0, it will be written before and after the date procedure AddDateTime(Value: PDateTime; FirstChar: AnsiChar='T'; QuoteChar: AnsiChar=#0; WithMS: boolean=false); overload; /// append a TDateTime value, expanded as Iso-8601 encoded text // - use 'YYYY-MM-DDThh:mm:ss' format + // - if twoDateTimeWithZ CustomOption is set, will append an ending 'Z' // - append nothing if Value=0 // - if WithMS is TRUE, will append '.sss' for milliseconds resolution procedure AddDateTime(const Value: TDateTime; WithMS: boolean=false); overload; /// append a TDateTime value, expanded as Iso-8601 text with milliseconds // and Time Zone designator + // - twoDateTimeWithZ CustomOption is ignored in favor of the TZD parameter // - i.e. 'YYYY-MM-DDThh:mm:ss.sssZ' format // - TZD is the ending time zone designator ('', 'Z' or '+hh:mm' or '-hh:mm') procedure AddDateTimeMS(const Value: TDateTime; Expanded: boolean=true; @@ -9230,6 +9278,8 @@ type // - excluding the bytes in the internal buffer // - see TextLength for the total number of bytes, on both disk and memory property WrittenBytes: PtrUInt read fTotalFileSize; + /// low-level access to the current indentation level + property HumanReadableLevel: integer read fHumanReadableLevel write fHumanReadableLevel; /// the last char appended is canceled // - only one char cancelation is allowed at the same position: don't call // CancelLastChar/CancelLastComma more than once without appending text inbetween @@ -9251,6 +9301,8 @@ type // - see PendingBytes for the number of bytes currently in the memory buffer // or WrittenBytes for the number of bytes already written to disk property TextLength: PtrUInt read GetTextLength; + /// optional event called before FlushToStream method process + property OnFlushToStream: TOnTextWriterFlush read fOnFlushToStream write fOnFlushToStream; /// allows to override default WriteObject property JSON serialization property OnWriteObject: TOnTextWriterObjectProp read fOnWriteObject write fOnWriteObject; /// the internal TStream used for storage @@ -9805,7 +9857,7 @@ type /// abstract low-level parent class for generic compression/decompression algorithms // - will encapsulate the compression algorithm with crc32c hashing - // - all Algo* abtract methods should be overriden by inherited classes + // - all Algo* abstract methods should be overriden by inherited classes TAlgoCompress = class(TSynPersistent) public /// should return a genuine byte identifier @@ -10615,9 +10667,10 @@ function JSONRetrieveStringField(P: PUTF8Char; out Field: PUTF8Char; /// efficient JSON field in-place decoding, within a UTF-8 encoded buffer // - this function decodes in the P^ buffer memory itself (no memory allocation // or copy), for faster process - so take care that P^ is not shared -// - PDest points to the next field to be decoded, or nil when end is reached +// - PDest points to the next field to be decoded, or nil on JSON parsing error // - EndOfObject (if not nil) is set to the JSON value char (',' ':' or '}' e.g.) // - optional wasString is set to true if the JSON value was a JSON "string" +// - returns a PUTF8Char to the decoded value, with its optional length in Len^ // - '"strings"' are decoded as 'strings', with wasString=true, properly JSON // unescaped (e.g. any \u0123 pattern would be converted into UTF-8 content) // - null is decoded as nil, with wasString=false @@ -10830,9 +10883,11 @@ function JsonObjectsByPath(JsonObject,PropPath: PUTF8Char): RawUTF8; // - is the reverse of the TTextWriter.AddJSONArraysAsJSONObject() method function JSONObjectAsJSONArrays(JSON: PUTF8Char; out keys,values: RawUTF8): boolean; -/// remove comments from a text buffer before passing it to JSON parser +/// remove comments and trailing commas from a text buffer before passing it to JSON parser // - handle two types of comments: starting from // till end of line // or /* ..... */ blocks anywhere in the text content +// - trailing commas is replaced by ' ', so resulting JSON is valid for parsers +// what not allows trailing commas (browsers for example) // - may be used to prepare configuration files before loading; // for example we store server configuration in file config.json and // put some comments in this file then code for loading is: @@ -10993,7 +11048,8 @@ type // - will handle vtPointer/vtClass/vtObject/vtVariant kind of arguments, // appending class name for any class or object, the hexa value for a // pointer, or the JSON representation of any supplied TDocVariant - constructor CreateLastOSError(const Format: RawUTF8; const Args: array of const); + constructor CreateLastOSError(const Format: RawUTF8; const Args: array of const; + const Trailer: RawUtf8 = 'OSError'); {$ifndef NOEXCEPTIONINTERCEPT} /// can be used to customize how the exception is logged // - this default implementation will call the DefaultSynLogExceptionToStr() @@ -12038,12 +12094,16 @@ procedure FillZero(var secret: RawUTF8); overload; {$ifdef FPC}inline;{$endif} /// fill all bytes of a memory buffer with zero -// - is expected to be used with a constant count from SizeOf() so that -// inlining make it more efficient than FillCharFast(..,...,0): -// ! FillZero(variable,SizeOf(variable)); +// - just redirect to FillCharFast(..,...,0) procedure FillZero(var dest; count: PtrInt); overload; {$ifdef HASINLINE}inline;{$endif} +/// returns TRUE if all bytes of both buffers do match +// - this function is not sensitive to any timing attack, so is designed +// for cryptographic purposes - use CompareMem/CompareMemSmall/CompareMemFixed +// as faster alternatives for general-purpose code +function IsEqual(const A,B; count: PtrInt): boolean; overload; + /// fast computation of two 64-bit unsigned integers into a 128-bit value procedure mul64x64(const left, right: QWord; out product: THash128Rec); {$ifndef CPUINTEL}inline;{$endif} @@ -12052,27 +12112,30 @@ type /// the potential features, retrieved from an Intel CPU // - see https://en.wikipedia.org/wiki/CPUID#EAX.3D1:_Processor_Info_and_Feature_Bits // - is defined on all platforms, since an ARM desktop could browse Intel logs - TIntelCpuFeature = - ( { CPUID 1 in EDX } - cfFPU, cfVME, cfDE, cfPSE, cfTSC, cfMSR, cfPAE, cfMCE, - cfCX8, cfAPIC, cf_d10, cfSEP, cfMTRR, cfPGE, cfMCA, cfCMOV, - cfPAT, cfPSE36, cfPSN, cfCLFSH, cf_d20, cfDS, cfACPI, cfMMX, - cfFXSR, cfSSE, cfSSE2, cfSS, cfHTT, cfTM, cfIA64, cfPBE, + TIntelCpuFeature = ( + { CPUID 1 in EDX } + cfFPU, cfVME, cfDE, cfPSE, cfTSC, cfMSR, cfPAE, cfMCE, + cfCX8, cfAPIC, cf_d10, cfSEP, cfMTRR, cfPGE, cfMCA, cfCMOV, + cfPAT, cfPSE36, cfPSN, cfCLFSH, cf_d20, cfDS, cfACPI, cfMMX, + cfFXSR, cfSSE, cfSSE2, cfSS, cfHTT, cfTM, cfIA64, cfPBE, { CPUID 1 in ECX } - cfSSE3, cfCLMUL, cfDS64, cfMON, cfDSCPL, cfVMX, cfSMX, cfEST, - cfTM2, cfSSSE3, cfCID, cfSDBG, cfFMA, cfCX16, cfXTPR, cfPDCM, - cf_c16, cfPCID, cfDCA, cfSSE41, cfSSE42, cfX2A, cfMOVBE, cfPOPCNT, - cfTSC2, cfAESNI, cfXS, cfOSXS, cfAVX, cfF16C, cfRAND, cfHYP, - { extended features CPUID 7 in EBX, ECX, DL } - cfFSGS, cf_b01, cfSGX, cfBMI1, cfHLE, cfAVX2, cf_b06, cfSMEP, - cfBMI2, cfERMS, cfINVPCID, cfRTM, cfPQM, cf_b13, cfMPX, cfPQE, + cfSSE3, cfCLMUL, cfDS64, cfMON, cfDSCPL, cfVMX, cfSMX, cfEST, + cfTM2, cfSSSE3, cfCID, cfSDBG, cfFMA, cfCX16, cfXTPR, cfPDCM, + cf_c16, cfPCID, cfDCA, cfSSE41, cfSSE42, cfX2A, cfMOVBE, cfPOPCNT, + cfTSC2, cfAESNI, cfXS, cfOSXS, cfAVX, cfF16C, cfRAND, cfHYP, + { extended features CPUID 7 in EBX, ECX, EDX } + cfFSGS, cfTSCADJ, cfSGX, cfBMI1, cfHLE, cfAVX2, cfFDPEO, cfSMEP, + cfBMI2, cfERMS, cfINVPCID, cfRTM, cfPQM, cf_b13, cfMPX, cfPQE, cfAVX512F, cfAVX512DQ, cfRDSEED, cfADX, cfSMAP, cfAVX512IFMA, cfPCOMMIT, cfCLFLUSH, - cfCLWB, cfIPT, cfAVX512PF, cfAVX512ER, cfAVX512CD, cfSHA, cfAVX512BW, cfAVX512VL, - cfPREFW1, cfAVX512VBMI, cfUMIP, cfPKU, cfOSPKE, cf_c05, cfAVX512VBMI2, cf_c07, + cfCLWB, cfIPT, cfAVX512PF, cfAVX512ER, cfAVX512CD, cfSHA, cfAVX512BW, cfAVX512VL, + cfPREFW1, cfAVX512VBMI, cfUMIP, cfPKU, cfOSPKE, cf_c05, cfAVX512VBMI2, cfCETSS, cfGFNI, cfVAES, cfVCLMUL, cfAVX512NNI, cfAVX512BITALG, cf_c13, cfAVX512VPC, cf_c15, - cf_cc16, cf_c17, cf_c18, cf_c19, cf_c20, cf_c21, cfRDPID, cf_c23, - cf_c24, cf_c25, cf_c26, cf_c27, cf_c28, cf_c29, cfSGXLC, cf_c31, - cf_d0, cf_d1, cfAVX512NNIW, cfAVX512MAS, cf_d4, cf_d5, cf_d6, cf_d7); + cfFLP, cf_c17, cf_c18, cf_c19, cf_c20, cf_c21, cfRDPID, cf_c23, + cf_c24, cfCLDEMOTE, cf_c26, cfMOVDIRI, cfMOVDIR64B, cfENQCMD, cfSGXLC, cfPKS, + cf_d0, cf_d1, cfAVX512NNIW, cfAVX512MAPS, cfFSRM, cf_d5, cf_d6, cf_d7, + cfAVX512VP2I, cfSRBDS, cfMDCLR, cf_d11, cf_d12, cfTSXFA, cfSER, cfHYBRID, + cfTSXLDTRK, cf_d17, cfPCFG, cfLBR, cfIBT, cf_d21, cfAMXBF16, cf_d23, + cfAMXTILE, cfAMXINT8, cfIBRSPB, cfSTIBP, cfL1DFL, cfARCAB, cfCORCAB, cfSSBD); /// all features, as retrieved from an Intel CPU TIntelCpuFeatures = set of TIntelCpuFeature; @@ -12365,6 +12428,7 @@ type TDateTimeMSDynArray = array of TDateTimeMS; PDateTimeMSDynArray = ^TDateTimeMSDynArray; + {$A-} /// a simple way to store a date as Year/Month/Day // - with no needed computation as with TDate/TUnixTime values // - consider using TSynSystemTime if you need to handle both Date and Time @@ -12372,7 +12436,10 @@ type // is safe to be used // - DayOfWeek field is not handled by its methods by default, but could be // filled on demand via ComputeDayOfWeek - making this record 64-bit long - TSynDate = object + // - some Delphi revisions have trouble with "object" as own method parameters + // (e.g. IsEqual) so we force to use "record" type if possible + {$ifdef USERECORDWITHMETHODS}TSynDate = record{$else} + TSynDate = object{$endif} Year, Month, DayOfWeek, Day: word; /// set all fields to 0 procedure Clear; {$ifdef HASINLINE}inline;{$endif} @@ -12414,9 +12481,12 @@ type // - FPC's TSystemTime in datih.inc does NOT match Windows TSystemTime fields! // - also used to store a Date/Time in TSynTimeZone internal structures, or // for fast conversion from TDateTime to its ready-to-display members - // - DayOfWeek field is not handled by most methods by default, but could be - // filled on demand via ComputeDayOfWeek - TSynSystemTime = object + // - DayOfWeek field is not handled by most methods by default (left as 0), + // but could be filled on demand via ComputeDayOfWeek into its 1..7 value + // - some Delphi revisions have trouble with "object" as own method parameters + // (e.g. IsEqual) so we force to use "record" type if possible + {$ifdef USERECORDWITHMETHODS}TSynSystemTime = record{$else} + TSynSystemTime = object{$endif} public Year, Month, DayOfWeek, Day, Hour, Minute, Second, MilliSecond: word; @@ -12488,6 +12558,7 @@ type procedure IncrementMS(ms: integer); end; PSynSystemTime = ^TSynSystemTime; + {$A+} /// fast bit-encoded date and time value // - faster than Iso-8601 text and TDateTime, e.g. can be used as published @@ -12531,11 +12602,20 @@ type Value: Int64; /// extract the date and time content in Value into individual values procedure Expand(out Date: TSynSystemTime); - /// convert to Iso-8601 encoded text + /// convert to Iso-8601 encoded text, truncated to date/time only if needed function Text(Expanded: boolean; FirstTimeChar: AnsiChar = 'T'): RawUTF8; overload; - /// convert to Iso-8601 encoded text + /// convert to Iso-8601 encoded text, truncated to date/time only if needed function Text(Dest: PUTF8Char; Expanded: boolean; FirstTimeChar: AnsiChar = 'T'): integer; overload; + /// convert to Iso-8601 encoded text with date and time part + // - never truncate to date/time nor return '' as Text() does + function FullText(Expanded: boolean; FirstTimeChar: AnsiChar = 'T'; + QuotedChar: AnsiChar = #0): RawUTF8; overload; + {$ifdef FPC}inline;{$endif} // URW1111 on Delphi 2010 and URW1136 on XE + /// convert to Iso-8601 encoded text with date and time part + // - never truncate to date/time or return '' as Text() does + function FullText(Dest: PUTF8Char; Expanded: boolean; + FirstTimeChar: AnsiChar = 'T'; QuotedChar: AnsiChar = #0): PUTF8Char; overload; /// convert to ready-to-be displayed text // - using i18nDateText global event, if set (e.g. by mORMoti18n.pas) function i18nText: string; @@ -12725,9 +12805,20 @@ procedure IntervalTextToDateTimeVar(Text: PUTF8Char; var result: TDateTime); // - use 'YYYYMMDDThhmmss' format if not Expanded // - use 'YYYY-MM-DDThh:mm:ss' format if Expanded // - if WithMS is TRUE, will append '.sss' for milliseconds resolution +// - if QuotedChar is not default #0, will (double) quote the resulted text // - you may rather use DateTimeToIso8601Text() to handle 0 or date-only values function DateTimeToIso8601(D: TDateTime; Expanded: boolean; - FirstChar: AnsiChar='T'; WithMS: boolean=false): RawUTF8; + FirstChar: AnsiChar='T'; WithMS: boolean=false; QuotedChar: AnsiChar=#0): RawUTF8; overload; + +/// basic Date/Time conversion into ISO-8601 +// - use 'YYYYMMDDThhmmss' format if not Expanded +// - use 'YYYY-MM-DDThh:mm:ss' format if Expanded +// - if WithMS is TRUE, will append '.sss' for milliseconds resolution +// - if QuotedChar is not default #0, will (double) quote the resulted text +// - you may rather use DateTimeToIso8601Text() to handle 0 or date-only values +// - returns the number of chars written to P^ buffer +function DateTimeToIso8601(P: PUTF8Char; D: TDateTime; Expanded: boolean; + FirstChar: AnsiChar='T'; WithMS: boolean=false; QuotedChar: AnsiChar=#0): integer; overload; /// basic Date conversion into ISO-8601 // - use 'YYYYMMDD' format if not Expanded @@ -12755,14 +12846,14 @@ function TimeToIso8601(Time: TDateTime; Expanded: boolean; FirstChar: AnsiChar=' /// Write a Date to P^ Ansi buffer // - if Expanded is false, 'YYYYMMDD' date format is used // - if Expanded is true, 'YYYY-MM-DD' date format is used -procedure DateToIso8601PChar(P: PUTF8Char; Expanded: boolean; Y,M,D: PtrUInt); overload; +function DateToIso8601PChar(P: PUTF8Char; Expanded: boolean; Y,M,D: PtrUInt): PUTF8Char; overload; /// convert a date into 'YYYY-MM-DD' date format // - resulting text is compatible with all ISO-8601 functions function DateToIso8601Text(Date: TDateTime): RawUTF8; /// Write a Date/Time to P^ Ansi buffer -procedure DateToIso8601PChar(Date: TDateTime; P: PUTF8Char; Expanded: boolean); overload; +function DateToIso8601PChar(Date: TDateTime; P: PUTF8Char; Expanded: boolean): PUTF8Char; overload; /// Write a TDateTime value, expanded as Iso-8601 encoded text into P^ Ansi buffer // - if DT=0, returns '' @@ -12809,16 +12900,16 @@ procedure DateTimeToIso8601StringVar(DT: TDateTime; FirstChar: AnsiChar; var res // - if Expanded is true, 'Thh:mm:ss' time format is used // - you can custom the first char in from of the resulting text time // - if WithMS is TRUE, will append MS as '.sss' for milliseconds resolution -procedure TimeToIso8601PChar(P: PUTF8Char; Expanded: boolean; H,M,S,MS: PtrUInt; - FirstChar: AnsiChar = 'T'; WithMS: boolean=false); overload; +function TimeToIso8601PChar(P: PUTF8Char; Expanded: boolean; H,M,S,MS: PtrUInt; + FirstChar: AnsiChar = 'T'; WithMS: boolean=false): PUTF8Char; overload; /// Write a Time to P^ Ansi buffer // - if Expanded is false, 'Thhmmss' time format is used // - if Expanded is true, 'Thh:mm:ss' time format is used // - you can custom the first char in from of the resulting text time // - if WithMS is TRUE, will append '.sss' for milliseconds resolution -procedure TimeToIso8601PChar(Time: TDateTime; P: PUTF8Char; Expanded: boolean; - FirstChar: AnsiChar = 'T'; WithMS: boolean=false); overload; +function TimeToIso8601PChar(Time: TDateTime; P: PUTF8Char; Expanded: boolean; + FirstChar: AnsiChar = 'T'; WithMS: boolean=false): PUTF8Char; overload; var /// custom TTimeLog date to ready to be displayed text function @@ -13244,7 +13335,8 @@ type wSeven, wSeven_64, wServer2008_R2, wServer2008_R2_64, wEight, wEight_64, wServer2012, wServer2012_64, wEightOne, wEightOne_64, wServer2012R2, wServer2012R2_64, - wTen, wTen_64, wServer2016, wServer2016_64, wServer2019_64); + wTen, wTen_64, wServer2016, wServer2016_64, + wEleven, wEleven_64, wServer2019_64); /// the running Operating System, encoded as a 32-bit integer TOperatingSystemVersion = packed record case os: TOperatingSystem of @@ -13262,11 +13354,12 @@ const '7', '7 64bit', 'Server 2008 R2', 'Server 2008 R2 64bit', '8', '8 64bit', 'Server 2012', 'Server 2012 64bit', '8.1', '8.1 64bit', 'Server 2012 R2', 'Server 2012 R2 64bit', - '10', '10 64bit', 'Server 2016', 'Server 2016 64bit', 'Server 2019 64bit'); + '10', '10 64bit', 'Server 2016', 'Server 2016 64bit', + '11', '11 64bit', 'Server 2019 64bit'); /// the recognized Windows versions which are 32-bit WINDOWS_32 = [w2000, wXP, wServer2003, wServer2003_R2, wVista, wServer2008, wSeven, wServer2008_R2, wEight, wServer2012, wEightOne, wServer2012R2, - wTen, wServer2016]; + wTen, wServer2016, wEleven]; /// translate one operating system (and distribution) into a single character // - may be used internally e.g. for a HTTP User-Agent header, as with // TFileVersion.UserAgent @@ -13541,12 +13634,16 @@ function InterlockedDecrement(var I: Integer): Integer; {$endif FPC} -/// low-level string/dynarray reference counter unprocess +/// low-level string reference counter unprocess // - caller should have tested that refcnt>=0 // - returns true if the managed variable should be released (i.e. refcnt was 1) -// - on Delphi, RefCnt field is a 32-bit longint, whereas on FPC it is a SizeInt/PtrInt -function RefCntDecFree(var refcnt: TRefCnt): boolean; - {$ifndef CPUINTEL}inline;{$endif} +function StrCntDecFree(var refcnt: TStrCnt): boolean; + {$ifndef CPUINTEL} inline; {$endif} + +/// low-level dynarray reference counter unprocess +// - caller should have tested that refcnt>=0 +function DACntDecFree(var refcnt: TDACnt): boolean; + {$ifndef CPUINTEL} inline; {$endif} type /// stores some global information about the current executable and computer @@ -13651,7 +13748,7 @@ type {$ifdef FPC} // FPC already use heap instead of GlobalAlloc() THeapMemoryStream = TMemoryStream; {$else} - {$ifdef MSWINDOWS} + {$ifndef UNICODE} // old Delphi used GlobalAlloc() THeapMemoryStream = class(TMemoryStream) protected function Realloc(var NewCapacity: longint): Pointer; override; @@ -13746,6 +13843,7 @@ function IsRowIDShort(const FieldName: shortstring): boolean; /// retrieve the next SQL-like identifier within the UTF-8 buffer // - will also trim any space (or line feeds) and trailing ';' +// - any comment like '/*nocache*/' will be ignored // - returns true if something was set to Prop function GetNextFieldProp(var P: PUTF8Char; var Prop: RawUTF8): boolean; @@ -14370,6 +14468,7 @@ type // properties can be slow - if you expect the data to be read-only or not // propagated into another place, add dvoValueCopiedByReference in Options // will increase the process speed a lot + // - warning: exclude dvoAllowDoubleValue so won't parse any float, just currency // - in practice, you should better use the function _Json()/_JsonFast() // which are handy wrappers around this class method class function NewJSON(const JSON: RawUTF8; @@ -15438,6 +15537,7 @@ function _Arr(const Items: array of const; // from a supplied (extended) JSON content // - this global function is an alias to TDocVariant.NewJSON(), and // will return an Unassigned variant if JSON content was not correctly converted +// - warning: exclude dvoAllowDoubleValue so won't parse any float, just currency // - object or array will be initialized from the supplied JSON content, e.g. // ! aVariant := _Json('{"id":10,"doc":{"name":"John","birthyear":1972}}'); // ! // now you can access to the properties via late binding @@ -15472,6 +15572,7 @@ function _Json(const JSON: RawUTF8; // - wrapper around the _Json(FormatUTF8(...,JSONFormat=true)) function, // i.e. every Args[] will be inserted for each % and Params[] for each ?, // with proper JSON escaping of string values, and writing nested _Obj() / +// - warning: exclude dvoAllowDoubleValue so won't parse any float, just currency // _Arr() instances as expected JSON objects / arrays // - typical use (in the context of SynMongoDB unit) could be: // ! aVariant := _JSONFmt('{%:{$in:[?,?]}}',['type'],['food','snack']); @@ -15501,6 +15602,7 @@ procedure _JsonFmt(const Format: RawUTF8; const Args,Params: array of const; // from a supplied (extended) JSON content // - this global function is an alias to TDocVariant.NewJSON(), and // will return TRUE if JSON content was correctly converted into a variant +// - warning: exclude dvoAllowDoubleValue so won't parse any float, just currency // - in addition to the JSON RFC specification strict mode, this method will // handle some BSON-like extensions, e.g. unquoted field names or ObjectID() // - by default, every internal value will be copied, so access of nested @@ -15532,8 +15634,9 @@ function _ArrFast(const Items: array of const): variant; overload; /// initialize a variant instance to store some document-based content // from a supplied (extended) JSON content +// - warning: exclude dvoAllowDoubleValue so won't parse any float, just currency // - this global function is an handy alias to: -// ! _Json(JSON,JSON_OPTIONS[true]); +// ! _Json(JSON,JSON_OPTIONS[true]); or _Json(JSON,JSON_OPTIONS_FAST) // so it will return an Unassigned variant if JSON content was not correct // - so all created objects and arrays will be handled by reference, for best // speed - but you should better write on the resulting variant tree with caution @@ -15542,6 +15645,12 @@ function _ArrFast(const Items: array of const): variant; overload; function _JsonFast(const JSON: RawUTF8): variant; {$ifdef HASINLINE}inline;{$endif} +/// initialize a variant instance to store some document-based content +// from a supplied (extended) JSON content, parsing any kind of float +// - use JSON_OPTIONS_FAST_FLOAT including the dvoAllowDoubleValue option +function _JsonFastFloat(const JSON: RawUTF8): variant; + {$ifdef HASINLINE}inline;{$endif} + /// initialize a variant instance to store some extended document-based content // - this global function is an handy alias to: // ! _Json(JSON,JSON_OPTIONS_FAST_EXTENDED); @@ -15550,6 +15659,7 @@ function _JsonFastExt(const JSON: RawUTF8): variant; /// initialize a variant instance to store some document-based content // from a supplied (extended) JSON content, with parameters formating +// - warning: exclude dvoAllowDoubleValue so won't parse any float, just currency // - this global function is an handy alias e.g. to: // ! aVariant := _JSONFmt('{%:{$in:[?,?]}}',['type'],['food','snack'],JSON_OPTIONS[true]); // - so all created objects and arrays will be handled by reference, for best @@ -16279,6 +16389,9 @@ type end; /// simple reference-counted storage for local objects + // - WARNING: both FPC and Delphi 10.4+ don't keep the IAutoFree instance + // up to the end-of-method -> you should not use TAutoFree for new projects + // :( - see https://quality.embarcadero.com/browse/RSP-30050 // - be aware that it won't implement a full ARC memory model, but may be // just used to avoid writing some try ... finally blocks on local variables // - use with caution, only on well defined local scope @@ -16312,6 +16425,8 @@ type // !end; // here myVar will be released // - warning: under FPC, you should assign the result of this method to a local // IAutoFree variable - see bug http://bugs.freepascal.org/view.php?id=26602 + // - Delphi 10.4 also did change it and release the IAutoFree before the + // end of the current method, so you should better use a local variable class function One(var localVariable; obj: TObject): IAutoFree; /// protect several local TObject variable instances life time // - specified as localVariable/objectInstance pairs @@ -16325,6 +16440,8 @@ type // !end; // here var1 and var2 will be released // - warning: under FPC, you should assign the result of this method to a local // IAutoFree variable - see bug http://bugs.freepascal.org/view.php?id=26602 + // - Delphi 10.4 also did change it and release the IAutoFree before the + // end of the current method, so you should better use a local variable class function Several(const varObjPairs: array of pointer): IAutoFree; /// protect another TObject variable to an existing IAutoFree instance life time // - you may write: @@ -16549,6 +16666,8 @@ type procedure Clear; /// save the stored values as UTF-8 encoded JSON Object function ToJSON(HumanReadable: boolean=false): RawUTF8; + /// low-level access to the associated thread-safe mutex + function Lock: TAutoLocker; /// the document fields would be safely accessed via this property // - this is the main entry point of this storage // - will raise an EDocVariant exception if Name does not exist at reading @@ -16613,6 +16732,8 @@ type /// save the stored value as UTF-8 encoded JSON Object // - implemented as just a wrapper around VariantSaveJSON() function ToJSON(HumanReadable: boolean=false): RawUTF8; + /// low-level access to the associated thread-safe mutex + function Lock: TAutoLocker; /// the document fields would be safely accessed via this property // - will raise an EDocVariant exception if Name does not exist // - result variant is returned as a copy, not as varByRef, since a copy @@ -16821,20 +16942,6 @@ function SynLZDecompressBody(P,Body: PAnsiChar; PLen,BodyLen: integer; function SynLZDecompressPartial(P,Partial: PAnsiChar; PLen,PartialLen: integer): integer; -resourcestring - sInvalidIPAddress = '"%s" is an invalid IP v4 address'; - sInvalidEmailAddress = '"%s" is an invalid email address'; - sInvalidPattern = '"%s" does not match the expected pattern'; - sCharacter01n = 'character,character,characters'; - sInvalidTextLengthMin = 'Expect at least %d %s'; - sInvalidTextLengthMax = 'Expect up to %d %s'; - sInvalidTextChar = 'Expect at least %d %s %s,Expect up to %d %s %s,'+ - 'alphabetical,digital,punctuation,lowercase,uppercase,space,'+ - 'Too much spaces on the left,Too much spaces on the right'; - sValidationFailed = '"%s" rule failed'; - sValidationFieldVoid = 'An unique key field must not be void'; - sValidationFieldDuplicate = 'Value already used for this unique key field'; - implementation @@ -16878,30 +16985,6 @@ var // internal list of TSynAnsiConvert instances SynAnsiConvertList: TSynObjectList = nil; -// some constants used for UTF-8 conversion, including surrogates -const - UTF16_HISURROGATE_MIN = $d800; - UTF16_HISURROGATE_MAX = $dbff; - UTF16_LOSURROGATE_MIN = $dc00; - UTF16_LOSURROGATE_MAX = $dfff; - UTF8_EXTRABYTES: array[$80..$ff] of byte = ( - 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, - 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, - 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, - 2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2, 3,3,3,3,3,3,3,3,4,4,4,4,5,5,0,0); - UTF8_EXTRA: array[0..6] of record - offset, minimum: cardinal; - end = ( // http://floodyberry.wordpress.com/2007/04/14/utf-8-conversion-tricks - (offset: $00000000; minimum: $00010000), - (offset: $00003080; minimum: $00000080), - (offset: $000e2080; minimum: $00000800), - (offset: $03c82080; minimum: $00010000), - (offset: $fa082080; minimum: $00200000), - (offset: $82082080; minimum: $04000000), - (offset: $00000000; minimum: $04000000)); - UTF8_EXTRA_SURROGATE = 3; - UTF8_FIRSTBYTE: array[2..6] of byte = ($c0,$e0,$f0,$f8,$fc); - {$ifdef HASINLINE} {$ifdef USE_VTYPE_STATIC} // circumvent weird bug on BSD + ARM (Alfred) procedure VarClear(var v: variant); // defined here for proper inlining @@ -17038,9 +17121,6 @@ function TSynAnsiConvert.AnsiBufferToUnicode(Dest: PWideChar; Source: PAnsiChar; SourceChars: Cardinal; NoTrailingZero: boolean): PWideChar; var c: cardinal; {$ifndef MSWINDOWS} -{$ifdef FPC} - tmp: UnicodeString; -{$endif} {$ifdef KYLIX3} ic: iconv_t; DestBegin: PAnsiChar; @@ -17084,10 +17164,8 @@ begin fCodePage,MB_PRECOMPOSED,Source,SourceChars,Dest,SourceChars); {$else} {$ifdef FPC} - widestringmanager.Ansi2UnicodeMoveProc(Source, - {$ifdef ISFPC27}fCodePage,{$endif}tmp,SourceChars); - MoveFast(Pointer(tmp)^,Dest^,length(tmp)*2); - result := Dest+length(tmp); + // uses our SynFPCLinux ICU API helper + result := Dest+AnsiToWideICU(fCodePage,Source,Dest,SourceChars); {$else} {$ifdef KYLIX3} result := Dest; // makes compiler happy @@ -17139,7 +17217,8 @@ begin if SourceChars=0 then result := Dest else begin U := AnsiBufferToUnicode(tmp.Init(SourceChars*3),Source,SourceChars); - result := Dest+RawUnicodeToUtf8(Dest,SourceChars*3,tmp.buf,(PtrUInt(U)-PtrUInt(tmp.buf))shr 1,[ccfNoTrailingZero]); + result := Dest+RawUnicodeToUtf8(Dest,SourceChars*3,tmp.buf, + (PtrUInt(U)-PtrUInt(tmp.buf))shr 1,[ccfNoTrailingZero]); tmp.Done; end; if not NoTrailingZero then @@ -17209,10 +17288,13 @@ end; function TSynAnsiConvert.AnsiBufferToRawUTF8(Source: PAnsiChar; SourceChars: Cardinal): RawUTF8; var tmp: TSynTempBuffer; + endchar: pointer; // try circumvent Delphi 10.4 optimization issue begin if (Source=nil) or (SourceChars=0) then - result := '' else - tmp.Done(AnsiBufferToUTF8(tmp.Init(SourceChars*3),Source,SourceChars),result); + result := '' else begin + endchar := AnsiBufferToUTF8(tmp.Init(SourceChars*3),Source,SourceChars,true); + tmp.Done(endchar,result); + end; end; constructor TSynAnsiConvert.Create(aCodePage: cardinal); @@ -17263,9 +17345,6 @@ function TSynAnsiConvert.UnicodeBufferToAnsi(Dest: PAnsiChar; Source: PWideChar; SourceChars: Cardinal): PAnsiChar; var c: cardinal; {$ifndef MSWINDOWS} -{$ifdef FPC} - tmp: RawByteString; -{$endif} {$ifdef KYLIX3} ic: iconv_t; DestBegin: PAnsiChar; @@ -17308,10 +17387,8 @@ begin fCodePage,0,Source,SourceChars,Dest,SourceChars*3,@DefaultCharVar,nil); {$else} {$ifdef FPC} - widestringmanager.Unicode2AnsiMoveProc(Source,tmp, - {$ifdef ISFPC27}fCodePage,{$endif}SourceChars); - MoveFast(Pointer(tmp)^,Dest^,length(tmp)); - result := Dest+length(tmp); + // uses our SynFPCLinux ICU API helper + result := Dest+WideToAnsiICU(fCodePage,Source,Dest,SourceChars); {$else} {$ifdef KYLIX3} result := Dest; // makes compiler happy @@ -17520,7 +17597,11 @@ By1: c := byte(Source^); inc(Source); end; if not NoTrailingZero then Dest^ := #0; + {$ifdef ISDELPHI104} + exit(Dest); // circumvent Delphi 10.4 optimizer bug + {$else} Result := Dest; + {$endif} end; procedure TSynAnsiFixedWidth.InternalAppendUTF8(Source: PAnsiChar; SourceChars: Cardinal; @@ -18376,56 +18457,6 @@ NoSource: dest^ := #0; // always append a WideChar(0) to the end of the buffer end; -function IsValidUTF8(source: PUTF8Char): Boolean; -var extra, i: integer; - c: cardinal; -begin - result := false; - if source<>nil then - repeat - c := byte(source^); - inc(source); - if c=0 then break else - if c and $80<>0 then begin - extra := UTF8_EXTRABYTES[c]; - if extra=0 then exit else // invalid leading byte - for i := 1 to extra do - if byte(source^) and $c0<>$80 then - exit else - inc(source); // check valid UTF-8 content - end; - until false; - result := true; -end; - -function IsValidUTF8(const source: RawUTF8): Boolean; -begin - result := IsValidUTF8(pointer(Source),length(Source)); -end; - -function IsValidUTF8(source: PUTF8Char; sourcelen: PtrInt): Boolean; -var extra, i: integer; - c: cardinal; -begin - result := false; - inc(sourcelen,PtrInt(source)); - if source<>nil then - while PtrInt(PtrUInt(source))0 then begin - extra := UTF8_EXTRABYTES[c]; - if extra=0 then exit else // invalid leading byte - for i := 1 to extra do - if (PtrInt(PtrUInt(source))>=sourcelen) or (byte(source^) and $c0<>$80) then - exit else - inc(source); // check valid UTF-8 content - end; - end; - result := true; -end; - function IsValidUTF8WithoutControlChars(source: PUTF8Char): Boolean; var extra, i: integer; c: cardinal; @@ -19481,7 +19512,7 @@ smlu32: Res.Text := pointer(SmallUInt32UTF8[result]); vtPointer,vtInterface: begin Res.Text := @Res.Temp; Res.Len := SizeOf(pointer)*2; - BinToHexDisplayLower(V.VPointer,@Res.Temp,SizeOf(Pointer)); + BinToHexDisplayLower(@V.VPointer,@Res.Temp,SizeOf(Pointer)); result := SizeOf(pointer)*2; exit; end; @@ -19842,7 +19873,7 @@ begin s := pointer(Value); d := s; for i := 1 to Safe.Padding[0].VInteger do begin - if PRefCnt(PAnsiChar(s^)-_STRREFCNT)^<=aMaxRefCount then begin + if PStrCnt(PAnsiChar(s^)-_STRREFCNT)^<=aMaxRefCount then begin {$ifdef FPC} Finalize(PRawUTF8(s)^); {$else} @@ -19966,8 +19997,6 @@ begin UniqueText(aResult); end; -{$ifndef NOVARIANTS} - procedure ClearVariantForString(var Value: variant); {$ifdef HASINLINE} inline; {$endif} var v: TVarData absolute Value; begin @@ -19981,6 +20010,8 @@ begin end; end; +{$ifndef NOVARIANTS} + procedure TRawUTF8Interning.UniqueVariant(var aResult: variant; const aText: RawUTF8); begin ClearVariantForString(aResult); @@ -20440,11 +20471,11 @@ type {$ifdef ISFPC27} codePage: TSystemCodePage; // =Word elemSize: Word; + {$ifndef STRCNT32} {$ifdef CPU64} _PaddingToQWord: DWord; - {$endif} - {$endif} - refCnt: TRefCnt; // =SizeInt + {$endif} {$endif} {$endif} + refCnt: TStrCnt; // =SizeInt on older FPC, =longint since FPC 3.4 length: SizeInt; end; {$else FPC} @@ -20455,7 +20486,7 @@ type _Padding: LongInt; {$endif} /// dynamic array reference count (basic garbage memory mechanism) - refCnt: TRefCnt; + refCnt: TDACnt; /// length in element count // - size in bytes = length*ElemSize length: PtrInt; @@ -20481,7 +20512,7 @@ type elemSize: Word; {$endif UNICODE} /// COW string reference count (basic garbage memory mechanism) - refCnt: TRefCnt; + refCnt: TStrCnt; /// length in characters // - size in bytes = length*elemSize length: Longint; @@ -20702,7 +20733,8 @@ function FastNewString(len: PtrInt; cp: cardinal): PAnsiChar; inline; begin if len>0 then begin {$ifdef FPC_X64MM}result := _Getmem({$else}GetMem(result,{$endif}len+(STRRECSIZE+4)); - PCardinal(@PStrRec(result)^.codePage)^ := cp or (1 shl 16); // also set elemSize:=1 + PStrRec(result)^.codePage := cp; + PStrRec(result)^.elemSize := 1; PStrRec(result)^.refCnt := 1; PStrRec(result)^.length := len; PCardinal(result+len+STRRECSIZE)^ := 0; // ensure ends with four #0 @@ -20758,9 +20790,15 @@ asm jz @z mov qword ptr[p], rdx mov p, rax + {$ifdef STRCNT32} + cmp dword ptr[rax - _STRREFCNT], rdx + jl @z +lock dec dword ptr[rax - _STRREFCNT] + {$else} cmp qword ptr[rax - _STRREFCNT], rdx jl @z lock dec qword ptr[rax - _STRREFCNT] + {$endif STRCNT32} jbe @free @z: ret @free: sub p, STRRECSIZE @@ -20771,9 +20809,15 @@ procedure _ansistr_incr_ref(p: pointer); nostackframe; assembler; asm test p, p jz @z + {$ifdef STRCNT32} + cmp dword ptr[p - _STRREFCNT], 0 + jl @z +lock inc dword ptr[p - _STRREFCNT] + {$else} cmp qword ptr[p - _STRREFCNT], 0 jl @z lock inc qword ptr[p - _STRREFCNT] + {$endif STRCNT32} @z: end; @@ -20784,6 +20828,19 @@ asm jz @eq test s, s jz @ns + {$ifdef STRCNT32} + cmp dword ptr[s - _STRREFCNT], 0 + jl @ns +lock inc dword ptr[s - _STRREFCNT] +@ns: mov qword ptr[d], s + test rax, rax + jnz @z +@eq: ret +@z: mov d, rax + cmp dword ptr[rax - _STRREFCNT], 0 + jl @n + lock dec dword ptr[rax - _STRREFCNT] + {$else} cmp qword ptr[s - _STRREFCNT], 0 jl @ns lock inc qword ptr[s - _STRREFCNT] @@ -20795,6 +20852,7 @@ lock inc qword ptr[s - _STRREFCNT] cmp qword ptr[rax - _STRREFCNT], 0 jl @n lock dec qword ptr[rax - _STRREFCNT] + {$endif STRCNT32} ja @n @free: sub d, STRRECSIZE jmp _Freemem @@ -20919,9 +20977,15 @@ asm test rax, rax jz @z mov d, rax + {$ifdef STRCNT32} + cmp dword ptr[rax - _STRREFCNT], 0 + jl @z +lock dec dword ptr[rax - _STRREFCNT] + {$else} cmp qword ptr[rax - _STRREFCNT], 0 jl @z lock dec qword ptr[rax - _STRREFCNT] + {$endif STRCNT32} jbe @free @z: ret @free: sub d, STRRECSIZE @@ -20959,7 +21023,11 @@ asm jle _ansistr_decr_ref test rax, rax jz _ansistr_setlength_new + {$ifdef STRCNT32} + cmp dword ptr[rax - _STRREFCNT], 1 + {$else} cmp qword ptr[rax - _STRREFCNT], 1 + {$endif STRCNT32} jne _ansistr_setlength_new push len push s @@ -21193,7 +21261,7 @@ begin if sr = nil then exit; dec(sr); - if (sr^.refcnt >= 0) and RefCntDecFree(sr^.refcnt) then + if (sr^.refcnt >= 0) and StrCntDecFree(sr^.refcnt) then FreeMem(sr); end; {$endif FPC_X64} @@ -22961,6 +23029,17 @@ asm end; {$endif} +function IsEqual(const A,B; count: PtrInt): boolean; +var perbyte: boolean; // ensure no optimization takes place +begin + result := true; + while count>0 do begin + dec(count); + perbyte := PByteArray(@A)[count]=PByteArray(@B)[count]; + result := result and perbyte; + end; +end; + function PosCharAny(Str: PUTF8Char; Characters: PAnsiChar): PUTF8Char; var s: PAnsiChar; c: AnsiChar; @@ -23485,7 +23564,7 @@ var from: PUTF8Char; begin if P<>nil then begin P := SQLBegin(P); - case IdemPCharArray(P, ['SELECT','EXPLAIN ','VACUUM','PRAGMA','WITH']) of + case IdemPCharArray(P, ['SELECT','EXPLAIN ','VACUUM','PRAGMA','WITH','EXECUTE']) of 0: if P[6]<=' ' then begin if SelectClause<>nil then begin inc(P,7); @@ -23501,6 +23580,10 @@ begin 2,3: result := P[6] in [#0..' ',';']; 4: result := (P[4]<=' ') and not (StrPosI('INSERT',P+5)<>nil) or (StrPosI('UPDATE',P+5)<>nil) or (StrPosI('DELETE',P+5)<>nil); + 5: begin // FireBird specific + P := GotoNextNotSpace(P+7); + result := IdemPChar(P,'BLOCK') and IdemPChar(GotoNextNotSpace(P+5),'RETURNS'); + end else result := false; end; end else @@ -23611,11 +23694,11 @@ begin L := Length(S); I := 1; while (I<=L) and (S[I]<=' ') do inc(I); - if I>L then + if I>L then // void string result := '' else - if (I=1) and (S[L]>' ') then + if (I=1) and (S[L]>' ') then // nothing to trim result := S else begin - while S[L]<=' ' do dec(L); + while S[L]<=' ' do dec(L); // allocated trimmed result := Copy(S,I,L-I+1); end; end; @@ -23915,7 +23998,7 @@ end; {$ifdef DOUBLETOSHORT_USEGRISU} // includes Fabian Loitsch's Grisu algorithm especially compiled for double -{$I .\SynDoubleToText.inc} // implements DoubleToAscii() +{$I SynDoubleToText.inc} // implements DoubleToAscii() function DoubleToShort(var S: ShortString; const Value: double): integer; var valueabs: double; @@ -23973,6 +24056,11 @@ begin FastSetString(result,@tmp[1],DoubleToShort(tmp,Value)); end; +function TrimU(const S: RawUTF8): RawUTF8; +begin + result := Trim(s); +end; + function FormatUTF8(const Format: RawUTF8; const Args: array of const): RawUTF8; begin FormatUTF8(Format,Args,result); @@ -27318,9 +27406,10 @@ begin inc(Vers,2); // e.g. wEight -> wServer2012 if (Vers=wServer2016) and (OSVersionInfo.dwBuildNumber>=17763) then Vers := wServer2019_64; // https://stackoverflow.com/q/53393150 - end; + end else if (Vers=wTen) and (OSVersionInfo.dwBuildNumber>=22000) then + Vers := wEleven; // waiting for an official mean of Windows 11 identification if (SystemInfo.wProcessorArchitecture=PROCESSOR_ARCHITECTURE_AMD64) and - (Vers < wServer2019_64) then + (Vers wEight64 end; OSVersion := Vers; @@ -27589,23 +27678,43 @@ begin end; end; -function RefCntDecFree(var refcnt: TRefCnt): boolean; +function StrCntDecFree(var refcnt: TStrCnt): boolean; {$ifdef CPUINTEL} {$ifdef FPC}nostackframe; assembler; {$endif} asm {$ifdef CPU64DELPHI} .noframe {$endif} - {$ifdef FPC_64} - lock dec qword ptr[refcnt] // TRefCnt=SizeInt=PtrInt=Int64 for FPC_64 + {$ifdef STRCNT32} + lock dec dword ptr[refcnt] {$else} - lock dec dword ptr[refcnt] // TRefCnt=longint on Delphi and FPC_32 - {$endif} + lock dec qword ptr[refcnt] + {$endif STRCNT32} setbe al end; // we don't check for ismultithread global since lock is cheap on new CPUs {$else} begin // fallback to RTL asm e.g. for ARM - {$ifdef FPC_64} - result := InterLockedDecrement64(refcnt)<=0; - {$else} + {$ifdef STRCNT32} result := InterLockedDecrement(refcnt)<=0; - {$endif FPC_64} + {$else} + result := InterLockedDecrement64(refcnt)<=0; + {$endif STRCNT32} +end; +{$endif CPUINTEL} + +function DACntDecFree(var refcnt: TDACnt): boolean; +{$ifdef CPUINTEL} {$ifdef FPC}nostackframe; assembler; {$endif} +asm {$ifdef CPU64DELPHI} .noframe {$endif} + {$ifdef DACNT32} + lock dec dword ptr[refcnt] + {$else} + lock dec qword ptr[refcnt] + {$endif DACNT32} + setbe al +end; // we don't check for ismultithread global since lock is cheap on new CPUs +{$else} +begin // fallback to RTL asm e.g. for ARM + {$ifdef DACNT32} + result := InterLockedDecrement(refcnt)<=0; + {$else} + result := InterLockedDecrement64(refcnt)<=0; + {$endif DACNT32} end; {$endif CPUINTEL} @@ -29125,37 +29234,45 @@ begin Dest.Add(');'#13#10' %_LEN = SizeOf(%);'#13#10,[ConstName,ConstName]); end; +{$ifdef KYLIX3} function UpperCaseUnicode(const S: RawUTF8): RawUTF8; -{$ifdef MSWINDOWS} -var tmp: RawUnicode; - TmpLen: integer; -{$endif} begin -{$ifdef MSWINDOWS} // no temporary WideString involved - tmp := Utf8DecodeToRawUnicodeUI(S,@TmpLen); - TmpLen := TmpLen shr 1; - CharUpperBuffW(pointer(tmp),TmpLen); - RawUnicodeToUtf8(pointer(tmp),TmpLen,result); -{$else} result := WideStringToUTF8(WideUpperCase(UTF8ToWideString(S))); -{$endif} end; function LowerCaseUnicode(const S: RawUTF8): RawUTF8; -{$ifdef MSWINDOWS} -var tmp: RawUnicode; - TmpLen: integer; -{$endif} begin -{$ifdef MSWINDOWS} // no temporary WideString involved - tmp := Utf8DecodeToRawUnicodeUI(S,@TmpLen); - TmpLen := TmpLen shr 1; - CharLowerBuffW(pointer(tmp),TmpLen); - RawUnicodeToUtf8(pointer(tmp),TmpLen,result); -{$else} result := WideStringToUTF8(WideLowerCase(UTF8ToWideString(S))); -{$endif} end; +{$else} +function UpperCaseUnicode(const S: RawUTF8): RawUTF8; +var tmp: TSynTempBuffer; + len: integer; +begin + if S='' then begin + result := ''; + exit; + end; + tmp.Init(length(s)*2); + len := UTF8ToWideChar(tmp.buf,pointer(S),length(S)) shr 1; + RawUnicodeToUtf8(tmp.buf,CharUpperBuffW(tmp.buf,len),result); + tmp.Done; +end; + +function LowerCaseUnicode(const S: RawUTF8): RawUTF8; +var tmp: TSynTempBuffer; + len: integer; +begin + if S='' then begin + result := ''; + exit; + end; + tmp.Init(length(s)*2); + len := UTF8ToWideChar(tmp.buf,pointer(S),length(S)) shr 1; + RawUnicodeToUtf8(tmp.buf,CharLowerBuffW(tmp.buf,len),result); + tmp.Done; +end; +{$endif KYLIX3} function IsCaseSensitive(const S: RawUTF8): boolean; begin @@ -29262,27 +29379,30 @@ begin FastSetString(result,pointer(S),i); end; -procedure TrimCopy(const S: RawUTF8; start,count: PtrInt; - out result: RawUTF8); +procedure TrimCopy(const S: RawUTF8; start, count: PtrInt; + var result: RawUTF8); var L: PtrInt; begin - if count<=0 then - exit; - if start<=0 then - start := 1; - L := Length(S); - while (start<=L) and (S[start]<=' ') do begin - inc(start); dec(count); end; - dec(start); - dec(L,start); - if count0 do - if S[start+L]<=' ' then - dec(L) else - break; - if L>0 then - FastSetString(result,@PByteArray(S)[start],L); + if count>0 then begin + if start<=0 then + start := 1; + L := Length(S); + while (start<=L) and (S[start]<=' ') do begin + inc(start); dec(count); end; + dec(start); + dec(L,start); + if count0 do + if S[start+L]<=' ' then + dec(L) else + break; + if L>0 then begin + FastSetString(result,@PByteArray(S)[start],L); + exit; + end; + end; + result := ''; end; type @@ -30878,7 +30998,7 @@ end; function SearchRecValidFolder(const F: TSearchRec): boolean; begin - result := (F.Attr and (faDirectory {$ifdef MSWINDOWS}and faHidden{$endif})=faDirectory) and + result := (F.Attr and (faDirectory {$ifdef MSWINDOWS}+faHidden{$endif})=faDirectory) and (F.Name<>'') and (F.Name<>'.') and (F.Name<>'..'); end; @@ -31403,7 +31523,7 @@ begin exit; // wrong Index dec(n); if n>Index then begin - if PRefCnt(PtrUInt(Values)-_DAREFCNT)^>1 then + if PDACnt(PtrUInt(Values)-_DAREFCNT)^>1 then DynArrayMakeUnique(@Values,TypeInfo(TWordDynArray)); MoveFast(Values[Index+1],Values[Index],(n-Index)*SizeOf(Word)); end; @@ -31418,7 +31538,7 @@ begin exit; // wrong Index dec(n); if n>Index then begin - if PRefCnt(PtrUInt(Values)-_DAREFCNT)^>1 then + if PDACnt(PtrUInt(Values)-_DAREFCNT)^>1 then DynArrayMakeUnique(@Values,TypeInfo(TIntegerDynArray)); MoveFast(Values[Index+1],Values[Index],(n-Index)*SizeOf(Integer)); end; @@ -31433,7 +31553,7 @@ begin exit; // wrong Index dec(n,Index+1); if n>0 then begin - if PRefCnt(PtrUInt(Values)-_DAREFCNT)^>1 then + if PDACnt(PtrUInt(Values)-_DAREFCNT)^>1 then DynArrayMakeUnique(@Values,TypeInfo(TIntegerDynArray)); MoveFast(Values[Index+1],Values[Index],n*SizeOf(Integer)); end; @@ -31448,7 +31568,7 @@ begin exit; // wrong Index dec(n); if n>Index then begin - if PRefCnt(PtrUInt(Values)-_DAREFCNT)^>1 then + if PDACnt(PtrUInt(Values)-_DAREFCNT)^>1 then DynArrayMakeUnique(@Values,TypeInfo(TInt64DynArray)); MoveFast(Values[Index+1],Values[Index],(n-Index)*SizeOf(Int64)); end; @@ -31463,7 +31583,7 @@ begin exit; // wrong Index dec(n,Index+1); if n>0 then begin - if PRefCnt(PtrUInt(Values)-_DAREFCNT)^>1 then + if PDACnt(PtrUInt(Values)-_DAREFCNT)^>1 then DynArrayMakeUnique(@Values,TypeInfo(TInt64DynArray)); MoveFast(Values[Index+1],Values[Index],n*SizeOf(Int64)); end; @@ -31475,9 +31595,9 @@ var i,v,x,n: PtrInt; begin if (Values=nil) or (Excluded=nil) then exit; // nothing to exclude - if PRefCnt(PtrUInt(Values)-_DAREFCNT)^>1 then + if PDACnt(PtrUInt(Values)-_DAREFCNT)^>1 then DynArrayMakeUnique(@Values,TypeInfo(TIntegerDynArray)); - if PRefCnt(PtrUInt(Excluded)-_DAREFCNT)^>1 then + if PDACnt(PtrUInt(Excluded)-_DAREFCNT)^>1 then DynArrayMakeUnique(@Excluded,TypeInfo(TIntegerDynArray)); v := length(Values); n := 0; @@ -31510,9 +31630,9 @@ begin Values := nil; exit; end; - if PRefCnt(PtrUInt(Values)-_DAREFCNT)^>1 then + if PDACnt(PtrUInt(Values)-_DAREFCNT)^>1 then DynArrayMakeUnique(@Values,TypeInfo(TIntegerDynArray)); - if PRefCnt(PtrUInt(Included)-_DAREFCNT)^>1 then + if PDACnt(PtrUInt(Included)-_DAREFCNT)^>1 then DynArrayMakeUnique(@Included,TypeInfo(TIntegerDynArray)); v := length(Values); n := 0; @@ -33724,14 +33844,14 @@ begin _80 := PtrUInt($8080808080808080); // use registers for constants _61 := $6161616161616161; _7b := $7b7b7b7b7b7b7b7b; - for i := 0 to sourceLen shr 3 do begin + for i := 0 to (sourceLen-1) shr 3 do begin c := PPtrUIntArray(source)^[i]; d := c or _80; PPtrUIntArray(dest)^[i] := c-((d-PtrUInt(_61)) and not(d-_7b)) and ((not c) and _80)shr 2; end; {$else} // unbranched uppercase conversion of 4 chars blocks - for i := 0 to sourceLen shr 2 do begin + for i := 0 to (sourceLen-1) shr 2 do begin c := PPtrUIntArray(source)^[i]; d := c or PtrUInt($80808080); PPtrUIntArray(dest)^[i] := c-((d-PtrUInt($61616161)) and not(d-PtrUInt($7b7b7b7b))) and @@ -33965,7 +34085,6 @@ begin result := FindIniNameValueW(P,UpperName); end; end; - {$endif UNICODE} function IdemPCharAndGetNextItem(var source: PUTF8Char; const searchUp: RawUTF8; @@ -34680,7 +34799,7 @@ procedure CSVToRawUTF8DynArray(const CSV,Sep,SepEnd: RawUTF8; var Result: TRawUT var offs,i: integer; begin offs := 1; - while offs#0 then begin @@ -37802,18 +37928,20 @@ begin // use Thhmmss[.sss] format inc(P); end; PWord(P)^ := tab[S]; + inc(P,2); if WithMS then begin - inc(P,2); {$ifdef CPUX86NOTPIC}YearToPChar(MS{$else}YearToPChar2(tab,MS{$endif},P); - P^ := '.'; // override first digit + P^ := '.'; // override first '0' digit + inc(P,4); end; + result := P; end; -procedure DateToIso8601PChar(Date: TDateTime; P: PUTF8Char; Expanded: boolean); +function DateToIso8601PChar(Date: TDateTime; P: PUTF8Char; Expanded: boolean): PUTF8Char; var T: TSynSystemTime; begin // use YYYYMMDD / YYYY-MM-DD date format T.FromDate(Date); - DateToIso8601PChar(P,Expanded,T.Year,T.Month,T.Day); + result := DateToIso8601PChar(P,Expanded,T.Year,T.Month,T.Day); end; function DateToIso8601Text(Date: TDateTime): RawUTF8; @@ -37825,24 +37953,37 @@ begin // into 'YYYY-MM-DD' date format end; end; -procedure TimeToIso8601PChar(Time: TDateTime; P: PUTF8Char; Expanded: boolean; - FirstChar: AnsiChar; WithMS: boolean); +function TimeToIso8601PChar(Time: TDateTime; P: PUTF8Char; Expanded: boolean; + FirstChar: AnsiChar; WithMS: boolean): PUTF8Char; var T: TSynSystemTime; begin T.FromTime(Time); - TimeToIso8601PChar(P,Expanded,T.Hour,T.Minute,T.Second,T.MilliSecond,FirstChar,WithMS); + result := TimeToIso8601PChar(P,Expanded,T.Hour,T.Minute,T.Second,T.MilliSecond,FirstChar,WithMS); +end; + +function DateTimeToIso8601(P: PUTF8Char; D: TDateTime; Expanded: boolean; + FirstChar: AnsiChar; WithMS: boolean; QuotedChar: AnsiChar): integer; +var S: PUTF8Char; +begin + S := P; + if QuotedChar<>#0 then begin + P^ := QuotedChar; + inc(P); + end; + P := DateToIso8601PChar(D,P,Expanded); + P := TimeToIso8601PChar(D,P,Expanded,FirstChar,WithMS); + if QuotedChar<>#0 then begin + P^ := QuotedChar; + inc(P); + end; + result := P-S; end; function DateTimeToIso8601(D: TDateTime; Expanded: boolean; - FirstChar: AnsiChar; WithMS: boolean): RawUTF8; -const ISO8601_LEN: array[boolean,boolean] of integer = ((15,14),(19,18)); + FirstChar: AnsiChar; WithMS: boolean; QuotedChar: AnsiChar): RawUTF8; var tmp: array[0..31] of AnsiChar; begin // D=0 is handled in DateTimeToIso8601Text() - DateToIso8601PChar(D,tmp,Expanded); - if Expanded then - TimeToIso8601PChar(D,@tmp[10],true,FirstChar,WithMS) else - TimeToIso8601PChar(D,@tmp[8],false,FirstChar,WithMS); - FastSetString(result,@tmp,ISO8601_LEN[Expanded,FirstChar=#0]+4*integer(WithMS)); + FastSetString(result,@tmp,DateTimeToIso8601(@tmp,D,Expanded,FirstChar,WithMS,QuotedChar)); end; function DateToIso8601(Date: TDateTime; Expanded: boolean): RawUTF8; @@ -37897,14 +38038,10 @@ function DateTimeToIso8601ExpandedPChar(const Value: TDateTime; Dest: PUTF8Char; FirstChar: AnsiChar; WithMS: boolean): PUTF8Char; begin if Value<>0 then begin - if trunc(Value)<>0 then begin - DateToIso8601PChar(Value,Dest,true); - inc(Dest,10); - end; - if frac(Value)<>0 then begin - TimeToIso8601PChar(Value,Dest,true,FirstChar,WithMS); - inc(Dest,9+4*integer(WithMS)); - end; + if trunc(Value)<>0 then + Dest := DateToIso8601PChar(Value,Dest,true); + if frac(Value)<>0 then + Dest := TimeToIso8601PChar(Value,Dest,true,FirstChar,WithMS); end; Dest^ := #0; result := Dest; @@ -38202,7 +38339,8 @@ begin // faster version by AB else exit; // Month <= 0 Div100(Year,d100); Date := (146097*d100.D) shr 2+(1461*d100.M) shr 2+ - (153*Month+2) div 5+Day-693900; + (153*Month+2) div 5+Day; + Date := Date-693900; // should be separated to avoid sign issues result := true; end; end; @@ -38281,49 +38419,33 @@ end; function TTimeLogBits.Text(Dest: PUTF8Char; Expanded: boolean; FirstTimeChar: AnsiChar): integer; var lo: PtrUInt; + S: PUTF8Char; begin if Value=0 then begin result := 0; exit; end; + S := Dest; lo := {$ifdef CPU64}Value{$else}PCardinal(@Value)^{$endif}; - if lo and (1 shl (6+6+5)-1)=0 then begin + if lo and (1 shl (6+6+5)-1)=0 then // no Time: just convert date - DateToIso8601PChar(Dest, Expanded, + result := DateToIso8601PChar(Dest, Expanded, {$ifdef CPU64}lo{$else}Value{$endif} shr (6+6+5+5+4), - 1+(lo shr (6+6+5+5)) and 15, 1+(lo shr (6+6+5)) and 31); - if Expanded then - result := 10 else - result := 8; - end else - if {$ifdef CPU64}lo{$else}Value{$endif} shr (6+6+5)=0 then begin + 1+(lo shr (6+6+5+5)) and 15, 1+(lo shr (6+6+5)) and 31)-S else + if {$ifdef CPU64}lo{$else}Value{$endif} shr (6+6+5)=0 then // no Date: just convert time - TimeToIso8601PChar(Dest, Expanded, (lo shr (6+6)) and 31, - (lo shr 6) and 63, lo and 63, 0, FirstTimeChar); - if Expanded then - result := 9 else - result := 7; - if FirstTimeChar=#0 then - dec(result); - end else begin + result := TimeToIso8601PChar(Dest, Expanded, (lo shr (6+6)) and 31, + (lo shr 6) and 63, lo and 63, 0, FirstTimeChar)-S else begin // convert time and date - DateToIso8601PChar(Dest, Expanded, + Dest := DateToIso8601PChar(Dest, Expanded, {$ifdef CPU64}lo{$else}Value{$endif} shr (6+6+5+5+4), 1+(lo shr (6+6+5+5)) and 15, 1+(lo shr (6+6+5)) and 31); - if Expanded then - inc(Dest,10) else - inc(Dest,8); - TimeToIso8601PChar(Dest, Expanded, (lo shr (6+6)) and 31, - (lo shr 6) and 63, lo and 63, 0, FirstTimeChar); - if Expanded then - result := 15+4 else - result := 15; - if FirstTimeChar=#0 then - dec(result); + result := TimeToIso8601PChar(Dest, Expanded, (lo shr (6+6)) and 31, + (lo shr 6) and 63, lo and 63, 0, FirstTimeChar)-S; end; end; -function TTimeLogBits.Text(Expanded: boolean; FirstTimeChar: AnsiChar = 'T'): RawUTF8; +function TTimeLogBits.Text(Expanded: boolean; FirstTimeChar: AnsiChar): RawUTF8; var tmp: array[0..31] of AnsiChar; begin if Value=0 then @@ -38331,6 +38453,33 @@ begin FastSetString(result,@tmp,Text(tmp,Expanded,FirstTimeChar)); end; +function TTimeLogBits.FullText(Dest: PUTF8Char; Expanded: boolean; + FirstTimeChar,QuotedChar: AnsiChar): PUTF8Char; +var lo: PtrUInt; +begin // convert full time and date + if QuotedChar<>#0 then begin + Dest^ := QuotedChar; + inc(Dest); + end; + lo := {$ifdef CPU64}Value{$else}PCardinal(@Value)^{$endif}; + Dest := DateToIso8601PChar(Dest, Expanded, + {$ifdef CPU64}lo{$else}Value{$endif} shr (6+6+5+5+4), + 1+(lo shr (6+6+5+5)) and 15, 1+(lo shr (6+6+5)) and 31); + Dest := TimeToIso8601PChar(Dest, Expanded, (lo shr (6+6)) and 31, + (lo shr 6) and 63, lo and 63, 0, FirstTimeChar); + if QuotedChar<>#0 then begin + Dest^ := QuotedChar; + inc(Dest); + end; + result := Dest; +end; + +function TTimeLogBits.FullText(Expanded: boolean; FirstTimeChar,QuotedChar: AnsiChar): RawUTF8; +var tmp: array[0..31] of AnsiChar; +begin + FastSetString(result,@tmp,FullText(tmp,Expanded,FirstTimeChar,QuotedChar)-@tmp); +end; + function TTimeLogBits.i18nText: string; begin if Assigned(i18nDateText) then @@ -39851,6 +40000,7 @@ begin // see http://www.garykessler.net/library/file_sigs.html $46464f77, // 'application/font-woff' = wOFF in BigEndian $474e5089, // 'image/png' = 89 50 4E 47 0D 0A 1A 0A $4d5a4cff, // LZMA = FF 4C 5A 4D 41 00 + $72613c21, // .ar/.deb files = '!' (assuming compressed) $75b22630, // 'audio/x-ms-wma' = 30 26 B2 75 8E 66 $766f6f6d, // mov = 6D 6F 6F 76 [....moov] $89a8275f, // jar = 5F 27 A8 89 @@ -39862,6 +40012,7 @@ begin // see http://www.garykessler.net/library/file_sigs.html $afbc7a37, // 'application/x-7z-compressed' = 37 7A BC AF 27 1C $b7010000, $ba010000, // mpeg = 00 00 01 Bx $cececece, // jceks = CE CE CE CE + $dbeeabed, // .rpm package file $e011cfd0: // msi = D0 CF 11 E0 A1 B1 1A E1 result := true; else @@ -40257,7 +40408,7 @@ function FastFindUpperPUTF8CharSorted(P: PPUTF8CharArray; R: PtrInt; Value: PUTF8Char; ValueLen: PtrInt): PtrInt; var tmp: array[byte] of AnsiChar; begin - UpperCopy255Buf(@tmp,Value,ValueLen); + UpperCopy255Buf(@tmp,Value,ValueLen)^ := #0; result := FastFindPUTF8CharSorted(P,R,@tmp); end; @@ -40394,7 +40545,7 @@ begin if cardinal(Index)>=cardinal(n) then result := false else begin dec(n); - if PRefCnt(PtrUInt(Values)-_DAREFCNT)^>1 then + if PDACnt(PtrUInt(Values)-_DAREFCNT)^>1 then DynArrayMakeUnique(@Values,TypeInfo(TRawUTF8DynArray)); Values[Index] := ''; // avoid GPF if n>Index then begin @@ -40415,7 +40566,7 @@ begin result := false else begin dec(n); ValuesCount := n; - if PRefCnt(PtrUInt(Values)-_DAREFCNT)^>1 then + if PDACnt(PtrUInt(Values)-_DAREFCNT)^>1 then DynArrayMakeUnique(@Values,TypeInfo(TRawUTF8DynArray)); Values[Index] := ''; // avoid GPF dec(n,Index); @@ -40995,7 +41146,7 @@ end; {$ifndef LVCL} {$ifndef FPC} -{$ifdef MSWINDOWS} +{$ifndef UNICODE} const MemoryDelta = $8000; // 32 KB granularity (must be a power of 2) @@ -41032,7 +41183,7 @@ begin end; end; -{$endif MSWINDOWS} +{$endif UNICODE} {$endif FPC} {$endif LVCL} @@ -44203,7 +44354,7 @@ begin UpperCaseCopy(TypeName,TypeNameLen,ItemTypeName^); up := pointer(ItemTypeName^); end else begin - UpperCopy255Buf(@tmp,TypeName,TypeNameLen); + UpperCopy255Buf(@tmp,TypeName,TypeNameLen)^ := #0; up := @tmp; end; //for ndx := 1 to SORTEDMAX do assert(StrComp(SORTEDNAMES[ndx],SORTEDNAMES[ndx-1])>0,SORTEDNAMES[ndx]); @@ -44451,7 +44602,7 @@ begin p := pointer(Data); dec(p); Data := 0; - if (p^.refCnt>=0) and RefCntDecFree(p^.refCnt) then begin + if (p^.refCnt>=0) and DACntDecFree(p^.refCnt) then begin for i := 1 to p^.length do FinalizeNestedRecord(ItemData); FreeMem(p); @@ -47677,11 +47828,11 @@ begin result := false else begin dec(VCount); if VName<>nil then begin - if PRefCnt(PtrUInt(VName)-_DAREFCNT)^>1 then + if PDACnt(PtrUInt(VName)-_DAREFCNT)^>1 then DynArrayMakeUnique(@VName,TypeInfo(TRawUTF8DynArray)); VName[Index] := ''; end; - if PRefCnt(PtrUInt(VValue)-_DAREFCNT)^>1 then + if PDACnt(PtrUInt(VValue)-_DAREFCNT)^>1 then DynArrayMakeUnique(@VValue,TypeInfo(TVariantDynArray)); VarClear(VValue[Index]); if Index0) then + if (integer(VType)=DocVariantVType) and (VCount>0) and (aName<>nil) and(aNameLen>0) then if dvoIsArray in VOptions then begin // try index text in array document result := GetInteger(aName,err); if (err<>0) or (cardinal(result)>=cardinal(VCount)) then @@ -48989,6 +49140,11 @@ begin _Json(JSON,result,JSON_OPTIONS_FAST); end; +function _JsonFastFloat(const JSON: RawUTF8): variant; +begin + _Json(JSON,result,JSON_OPTIONS_FAST_FLOAT); +end; + function _JsonFastExt(const JSON: RawUTF8): variant; begin _Json(JSON,result,JSON_OPTIONS_FAST_EXTENDED); @@ -49217,7 +49373,7 @@ begin if p<>nil then begin v^ := nil; dec(p); - if (p^.refCnt>=0) and RefCntDecFree(p^.refCnt) then + if (p^.refCnt>=0) and StrCntDecFree(p^.refCnt) then freemem(p); end; inc(v); @@ -49282,7 +49438,7 @@ begin p := Value^; if p<>nil then begin dec(p); - if (p^.refCnt>=0) and RefCntDecFree(p^.refCnt) then begin + if (p^.refCnt>=0) and DACntDecFree(p^.refCnt) then begin if ElemTypeInfo<>nil then FastFinalizeArray(Value^,ElemTypeInfo,p^.length); Freemem(p); @@ -49640,7 +49796,7 @@ begin n := GetCount; if PtrUInt(aIndex)>=PtrUInt(n) then exit; // out of range - if PRefCnt(PtrUInt(fValue^)-_DAREFCNT)^>1 then + if PDACnt(PtrUInt(fValue^)-_DAREFCNT)^>1 then InternalSetLength(n,n); // unique dec(n); P := pointer(PtrUInt(fValue^)+PtrUInt(aIndex)*ElemSize); @@ -49822,7 +49978,7 @@ end; procedure TDynArray.LoadFromStream(Stream: TCustomMemoryStream); var P: PAnsiChar; begin - P := PAnsiChar(Stream.Memory)+Stream.Seek(0,soFromCurrent); + P := PAnsiChar(Stream.Memory)+Stream.Seek(0,soCurrent); Stream.Seek(LoadFrom(P,nil,false,PAnsiChar(Stream.Memory)+Stream.Size)-P,soCurrent); end; @@ -50168,7 +50324,7 @@ begin // code below must match TTextWriter.AddDynArrayJSON() end; exit; end; - inc(P); + repeat inc(P) until not(P^ in [#1..' ']); n := JSONArrayCount(P); if n<0 then exit; // invalid array content @@ -51189,7 +51345,7 @@ begin // this method is faster than default System.DynArraySetLength() function if NewLength=0 then begin if p<>nil then begin // FastDynArrayClear() with ObjArray support dec(p); - if (p^.refCnt>=0) and RefCntDecFree(p^.refCnt) then begin + if (p^.refCnt>=0) and DACntDecFree(p^.refCnt) then begin if OldLength<>0 then if ElemType<>nil then FastFinalizeArray(fValue^,ElemType,OldLength) else @@ -51214,7 +51370,7 @@ begin // this method is faster than default System.DynArraySetLength() function OldLength := NewLength; // no FillcharFast() below end else begin dec(PtrUInt(p),SizeOf(TDynArrayRec)); // p^ = start of heap object - if (p^.refCnt>=0) and RefCntDecFree(p^.refCnt) then begin + if (p^.refCnt>=0) and DACntDecFree(p^.refCnt) then begin if NewLengthnil then // release managed types in trailing items FastFinalizeArray(pointer(PAnsiChar(p)+NeededSize),ElemType,OldLength-NewLength) else @@ -51877,7 +52033,7 @@ begin // on input: HashTable[result] slot is already computed if HashTableSize=0 then RaiseFatalCollision('HashAdd',aHashCode); @@ -52124,7 +52280,7 @@ begin end; end; if not(canHash in State) then - ReHash({forced=}true,{grow=}false); // hash previous CountTrigger items + ReHash({forced=}true); // hash previous CountTrigger items result := FindOrNew(aHashCode,Elem,nil); if result<0 then begin // found no matching item wasAdded := true; @@ -52198,7 +52354,7 @@ begin inc(ScanCounter); if ScanCounter>=CountTrigger*2 then begin CountTrigger := 2; // rather use hashing from now on - ReHash(false,false); // set HashTable[] and canHash + ReHash(false); // set HashTable[] and canHash end; end; end; @@ -52215,34 +52371,30 @@ begin result := -1; // for coherency with most search methods end; -function TDynArrayHasher.ReHash(forced, forceGrow: boolean): integer; +function TDynArrayHasher.ReHash(forced: boolean): integer; var i, n, cap, siz, ndx: integer; P: PAnsiChar; hc: cardinal; begin result := 0; - // initialize a new void HashTable[]=0 - siz := HashTableSize; - Clear; - if not(hasHasher in State) then - exit; n := DynArray^.Count; - if not forced and ((n=0) or (n0) then // next power of two or next prime - {$ifdef CPU32DELPHI} - if siz 0; +end; + procedure TSynLocker.Lock; begin EnterCriticalSection(fSection); - fLocked := true; + inc(fLockCount); end; procedure TSynLocker.UnLock; begin - fLocked := false; + dec(fLockCount); LeaveCriticalSection(fSection); end; function TSynLocker.TryLock: boolean; begin - result := not fLocked and - (TryEnterCriticalSection(fSection){$ifdef LINUX}{$ifdef FPC}<>0{$endif}{$endif}); + result := TryEnterCriticalSection(fSection){$ifdef LINUX}{$ifdef FPC}<>0{$endif}{$endif}; + if result then + inc(fLockCount); end; function TSynLocker.TryLockMS(retryms: integer): boolean; @@ -53144,12 +53301,10 @@ function TSynLocker.GetVariant(Index: integer): Variant; begin if cardinal(Index)=PaddingUsedCount then PaddingUsedCount := Index+1; variant(Padding[Index]) := Value; finally - fLocked := false; - LeaveCriticalSection(fSection); + UnLock; end; end; @@ -53173,13 +53326,11 @@ function TSynLocker.GetInt64(Index: integer): Int64; begin if cardinal(Index)=PaddingUsedCount then PaddingUsedCount := Index+1; with Padding[index] do begin @@ -53257,8 +53403,7 @@ begin VUnknown := Value; end; finally - fLocked := false; - LeaveCriticalSection(fSection); + UnLock; end; end; @@ -53267,14 +53412,12 @@ var wasString: Boolean; begin if cardinal(Index)=PaddingUsedCount then PaddingUsedCount := Index+1; RawUTF8ToVariant(Value,Padding[Index],varString); finally - fLocked := false; - LeaveCriticalSection(fSection); + UnLock; end; end; @@ -53298,16 +53439,14 @@ function TSynLocker.LockedInt64Increment(Index: integer; const Increment: Int64) begin if cardinal(Index)<=high(Padding) then try - EnterCriticalSection(fSection); - fLocked := true; + Lock; result := 0; if Index#0 then @@ -53799,18 +53934,16 @@ begin dec(B); if Value^<>0 then begin inc(B); - if trunc(Value^)<>0 then begin - DateToIso8601PChar(Value^,B,true); - inc(B,10); - end; - if frac(Value^)<>0 then begin - TimeToIso8601PChar(Value^,B,true,FirstChar,WithMS); - if WithMS then - inc(B,13) else - inc(B,9); - end; + if trunc(Value^)<>0 then + B := DateToIso8601PChar(Value^,B,true); + if frac(Value^)<>0 then + B := TimeToIso8601PChar(Value^,B,true,FirstChar,WithMS); dec(B); end; + if twoDateTimeWithZ in fCustomOptions then begin + inc(B); + B^ := 'Z'; + end; if QuoteChar<>#0 then begin inc(B); B^ := QuoteChar; @@ -53821,20 +53954,16 @@ procedure TTextWriter.AddDateTime(const Value: TDateTime; WithMS: boolean); begin if Value=0 then exit; - if BEnd-B<=23 then + if BEnd-B<=24 then FlushToStream; inc(B); - if trunc(Value)<>0 then begin - DateToIso8601PChar(Value,B,true); - inc(B,10); - end; - if frac(Value)<>0 then begin - TimeToIso8601PChar(Value,B,true,'T',WithMS); - if WithMS then - inc(B,13) else - inc(B,9); - end; - dec(B); + if trunc(Value)<>0 then + B := DateToIso8601PChar(Value,B,true); + if frac(Value)<>0 then + B := TimeToIso8601PChar(Value,B,true,'T',WithMS); + if twoDateTimeWithZ in fCustomOptions then + B^ := 'Z' else + dec(B); end; procedure TTextWriter.AddDateTimeMS(const Value: TDateTime; Expanded: boolean; @@ -53911,8 +54040,12 @@ begin end; procedure TTextWriter.Add(Value: boolean); +var PS: PShortString; begin - AddShort(BOOL_STR[Value]); + if Value then // normalize: boolean may not be in the expected [0,1] range + PS := @BOOL_STR[true] else + PS := @BOOL_STR[false]; + AddShort(PS^); end; procedure TTextWriter.AddFloatStr(P: PUTF8Char); @@ -54701,7 +54834,7 @@ begin '{': begin repeat inc(JSON) until (JSON^=#0) or (JSON^>' '); if JSON^='}' then - repeat inc(JSON) until (JSON^=#0) or (JSON^>' '); + repeat inc(JSON) until (JSON^=#0) or (JSON^>' ') else begin repeat Name := GetJSONPropName(JSON); if Name=nil then @@ -54718,6 +54851,7 @@ begin Add('>'); end; until objEnd='}'; + end; end; else begin Value := GetJSONField(JSON,result,nil,EndOfObject); // let wasString=nil @@ -55019,9 +55153,7 @@ begin dec(BinBytes,ChunkBytes); if BinBytes=0 then break; // Flush writes B-buf+1 -> special one below: - ChunkBytes := B-fTempBuf; - fStream.WriteBuffer(fTempBuf^,ChunkBytes); - inc(fTotalFileSize,ChunkBytes); + WriteToStream(fTempBuf,B-fTempBuf); B := fTempBuf; until false; dec(B); // allow CancelLastChar @@ -55260,9 +55392,7 @@ begin inc(PByte(P),i); dec(Len,i); // FlushInc writes B-buf+1 -> special one below: - i := B-fTempBuf; - fStream.WriteBuffer(fTempBuf^,i); - inc(fTotalFileSize,i); + WriteToStream(fTempBuf,B-fTempBuf); B := fTempBuf; until false; dec(B); // allow CancelLastChar @@ -56165,7 +56295,7 @@ begin end; if aStream<>nil then begin fStream := aStream; - fInitialStreamPosition := fStream.Seek(0,soFromCurrent); + fInitialStreamPosition := fStream.Seek(0,soCurrent); fTotalFileSize := fInitialStreamPosition; end; end; @@ -56177,8 +56307,7 @@ begin i := B-fTempBuf+1; if i<=0 then exit; - fStream.WriteBuffer(fTempBuf^,i); - inc(fTotalFileSize,i); + WriteToStream(fTempBuf,i); if not (twoFlushToStreamNoAutoResize in fCustomOptions) then begin s := fTotalFileSize-fInitialStreamPosition; if (fTempBufSize<49152) and (s>PtrUInt(fTempBufSize)*4) then @@ -56192,12 +56321,20 @@ begin exclude(fCustomOptions,twoBufferIsExternal) else FreeMem(fTempBuf); // with big content comes bigger buffer GetMem(fTempBuf,fTempBufSize); - BEnd := fTempBuf+(fTempBufSize-2); + BEnd := fTempBuf+(fTempBufSize-16); end; end; B := fTempBuf-1; end; +procedure TTextWriter.WriteToStream(data: pointer; len: PtrUInt); +begin + if Assigned(fOnFlushToStream) then + fOnFlushToStream(data,len); + fStream.WriteBuffer(data^,len); + inc(fTotalFileSize,len); +end; + function TTextWriter.GetTextLength: PtrUInt; begin if self=nil then @@ -56295,10 +56432,8 @@ begin main := Base64EncodeMain(PAnsiChar(fTempBuf),P,n); n := main*4; if nnil then wasString^ := false; // not a string by default - PDest := nil; // PDest=nil indicates error or unexpected end (#0) + if Len<>nil then + Len^ := 0; // avoid buffer overflow on parsing error + PDest := nil; // PDest=nil indicates parsing error (e.g. unexpected #0 end) result := nil; if P=nil then exit; if P^<=' ' then repeat inc(P); if P^=#0 then exit; until P^>' '; @@ -57765,7 +57902,36 @@ begin end; end; +function TryRemoveComment(P: PUTF8Char): PUTF8Char; {$ifdef HASINLINE}inline;{$endif} +begin + result := P + 1; + case result^ of + '/': begin // this is // comment - replace by ' ' + dec(result); + repeat + result^ := ' '; + inc(result) + until result^ in [#0, #10, #13]; + if result^<>#0 then inc(result); + end; + '*': begin // this is /* comment - replace by ' ' but keep CRLF + result[-1] := ' '; + repeat + if not(result^ in [#10, #13]) then + result^ := ' '; // keep CRLF for correct line numbering (e.g. for error) + inc(result); + if PWord(result)^=ord('*')+ord('/')shl 8 then begin + PWord(result)^ := $2020; + inc(result,2); + break; + end; + until result^=#0; + end; + end; +end; + procedure RemoveCommentsFromJSON(P: PUTF8Char); +var PComma: PUTF8Char; begin // replace comments by ' ' characters which will be ignored by parser if P<>nil then while P^<>#0 do begin @@ -57773,35 +57939,22 @@ begin // replace comments by ' ' characters which will be ignored by parser '"': begin P := GotoEndOfJSONString(P); if P^<>'"' then - exit; + exit else + Inc(P); end; - '/': begin - inc(P); - case P^ of - '/': begin // this is // comment - replace by ' ' - dec(P); - repeat - P^ := ' '; - inc(P) - until P^ in [#0, #10, #13]; - end; - '*': begin // this is /* comment - replace by ' ' but keep CRLF - P[-1] := ' '; - repeat - if not(P^ in [#10, #13]) then - P^ := ' '; // keep CRLF for correct line numbering (e.g. for error) - inc(P); - if PWord(P)^=ord('*')+ord('/')shl 8 then begin - PWord(P)^ := $2020; - inc(P,2); - break; - end; - until P^=#0; - end; - end; + '/': P := TryRemoveComment(P); + ',': begin // replace trailing comma by space for strict JSON parsers + PComma := P; + repeat inc(P) until (P^>' ') or (P^=#0); + if P^='/' then + P := TryRemoveComment(P); + while (P^<=' ') and (P^<>#0) do inc(P); + if P^ in ['}', ']'] then + PComma^ := ' '; // see https://github.com/synopse/mORMot/pull/349 end; + else + inc(P); end; - inc(P); end; end; @@ -58905,12 +59058,12 @@ end; procedure TAutoLocker.Enter; begin - EnterCriticalSection(fSafe.fSection); + fSafe.Lock; end; procedure TAutoLocker.Leave; begin - LeaveCriticalSection(fSafe.fSection); + fSafe.UnLock; end; function TAutoLocker.Safe: PSynLocker; @@ -58945,6 +59098,11 @@ begin fLock.Free; end; +function TLockedDocVariant.Lock: TAutoLocker; +begin + result := fLock; +end; + function TLockedDocVariant.Exists(const Name: RawUTF8; out Value: Variant): boolean; var i: integer; begin @@ -59157,7 +59315,8 @@ begin {$elseif defined(VER320)}'Delphi 10.2 Tokyo' {$elseif defined(VER330)}'Delphi 10.3 Rio' {$elseif defined(VER340)}'Delphi 10.4 Sydney' - {$elseif defined(VER350)}'Delphi 10.5 Next' + {$elseif defined(VER350)}'Delphi 11 Alexandria' + {$elseif defined(VER360)}'Delphi 11.1 Next' {$ifend} {$endif CONDITIONALEXPRESSIONS} {$endif FPC} @@ -60958,16 +61117,33 @@ begin ord('I')+ord('D')shl 8)); end; +function GotoNextSqlIdentifier(P: PUtf8Char; tab: PTextCharSet): PUtf8Char; + {$ifdef HASINLINE} inline; {$endif} +begin + while tcCtrlNot0Comma in tab[P^] do inc(P); // in [#1..' ', ';'] + if PWord(P)^=ord('/')+ord('*') shl 8 then begin // ignore e.g. '/*nocache*/' + repeat + inc(P); + if PWord(P)^ = ord('*')+ord('/') shl 8 then begin + inc(P, 2); + break; + end; + until P^ = #0; + while tcCtrlNot0Comma in tab[P^] do inc(P); + end; + result := P; +end; + function GetNextFieldProp(var P: PUTF8Char; var Prop: RawUTF8): boolean; var B: PUTF8Char; tab: PTextCharSet; begin tab := @TEXT_CHARS; - while tcCtrlNot0Comma in tab[P^] do inc(P); + P := GotoNextSqlIdentifier(P, tab); B := P; while tcIdentifier in tab[P^] do inc(P); // go to end of field name FastSetString(Prop,B,P-B); - while tcCtrlNot0Comma in tab[P^] do inc(P); + P := GotoNextSqlIdentifier(P, tab); result := Prop<>''; end; @@ -61284,7 +61460,7 @@ begin exit; if Source.InheritsFrom(TCustomMemoryStream) then begin S := PAnsiChar(TCustomMemoryStream(Source).Memory)+PtrUInt(sourcePosition); - Source.Seek(Head.CompressedSize,soFromCurrent); + Source.Seek(Head.CompressedSize,soCurrent); end else begin if Head.CompressedSize>length(Buf) then SetString(Buf,nil,Head.CompressedSize); @@ -61847,13 +62023,14 @@ begin inherited Create(msg); end; -constructor ESynException.CreateLastOSError(const Format: RawUTF8; const Args: array of const); +constructor ESynException.CreateLastOSError( + const Format: RawUTF8; const Args: array of const; const Trailer: RawUtf8); var tmp: RawUTF8; error: integer; begin error := GetLastError; FormatUTF8(Format,Args,tmp); - CreateUTF8('OSError % [%] %',[error,SysErrorMessage(error),tmp]); + CreateUTF8('% % [%] %',[Trailer,error,SysErrorMessage(error),tmp]); end; {$ifndef NOEXCEPTIONINTERCEPT} @@ -62541,8 +62718,9 @@ var name: RawUTF8; begin FormatUTF8(Format,Args,name); name := StringReplaceAll(name,['TSQLRest','', 'TSQL','', 'TWebSocket','WS', - 'TSyn','', 'Thread','', 'Process','', 'Background','Bgd', 'Server','Svr', - 'Client','Clt', 'WebSocket','WS', 'Timer','Tmr', 'Thread','Thd']); + 'TServiceFactory','SF', 'TSyn','', 'Thread','', 'Process','', + 'Background','Bgd', 'Server','Svr', 'Client','Clt', 'WebSocket','WS', + 'Timer','Tmr', 'Thread','Thd']); SetThreadNameInternal(ThreadID,name); end; @@ -62728,7 +62906,7 @@ begin GetCPUID(7,regs); PIntegerArray(@CpuFeatures)^[2] := regs.ebx; PIntegerArray(@CpuFeatures)^[3] := regs.ecx; - PByte(@PIntegerArray(@CpuFeatures)^[4])^ := regs.edx; + PIntegerArray(@CpuFeatures)^[4] := regs.edx; {$ifdef DISABLE_SSE42} // paranoid execution on Darwin x64 (as reported by alf) CpuFeatures := CpuFeatures-[cfSSE42,cfAESNI]; {$endif DISABLE_SSE42} @@ -63063,7 +63241,9 @@ begin include(TEXT_CHARS[c], tcIdentifierFirstChar); if c in ['_','0'..'9','a'..'z','A'..'Z'] then include(TEXT_CHARS[c], tcIdentifier); - if c in ['_','-','.','~','0'..'9','a'..'z','A'..'Z'] then + if c in ['_','-','.','0'..'9','a'..'z','A'..'Z'] then + // '~' is part of the RFC 3986 but should be escaped in practice + // see https://blog.synopse.info/?post/2020/08/11/The-RFC%2C-The-URI%2C-and-The-Tilde include(TEXT_CHARS[c], tcURIUnreserved); if c in [#1..#9,#11,#12,#14..' '] then include(TEXT_CHARS[c], tcCtrlNotLF); diff --git a/contrib/mORMot/SynCrtSock.pas b/contrib/mORMot/SynCrtSock.pas index a0a12f2..0f3d72f 100644 --- a/contrib/mORMot/SynCrtSock.pas +++ b/contrib/mORMot/SynCrtSock.pas @@ -6,7 +6,7 @@ unit SynCrtSock; { This file is part of Synopse framework. - Synopse framework. Copyright (C) 2020 Arnaud Bouchez + Synopse framework. Copyright (C) 2022 Arnaud Bouchez Synopse Informatique - https://synopse.info *** BEGIN LICENSE BLOCK ***** @@ -25,7 +25,7 @@ unit SynCrtSock; The Initial Developer of the Original Code is Arnaud Bouchez. - Portions created by the Initial Developer are Copyright (C) 2020 + Portions created by the Initial Developer are Copyright (C) 2022 the Initial Developer. All Rights Reserved. Contributor(s): @@ -292,7 +292,8 @@ type // - aAddr='unix:/path/to/file' - bind to unix domain socket, e.g. 'unix:/run/mormot.sock' // - aAddr='' - bind to systemd descriptor on linux. See // http://0pointer.de/blog/projects/socket-activation.html - constructor Bind(const aAddr: SockString; aLayer: TCrtSocketLayer=cslTCP); + constructor Bind(const aAddr: SockString; aLayer: TCrtSocketLayer=cslTCP; + aTimeOut: integer=10000); /// low-level internal method called by Open() and Bind() constructors // - raise an ECrtSocket exception on error // - you may ask for a TLS secured client connection (only available under @@ -570,8 +571,8 @@ type /// same as HeaderGetValue('X-POWERED-BY'), but retrieved during Request XPoweredBy: SockString; /// map the presence of some HTTP headers, but retrieved during Request - HeaderFlags: set of(transferChuked, connectionClose, connectionUpgrade, - connectionKeepAlive, hasRemoteIP); + HeaderFlags: set of(transferChuked, + connectionClose, connectionUpgrade, connectionKeepAlive, hasRemoteIP); /// retrieve the HTTP headers into Headers[] and fill most properties below // - only relevant headers are retrieved, unless HeadersUnFiltered is set procedure GetHeader(HeadersUnFiltered: boolean=false); @@ -619,6 +620,7 @@ type /// a genuine identifier for a given client connection on server side // - maps http.sys ID, or is a genuine 31-bit value from increasing sequence THttpServerConnectionID = Int64; + /// a dynamic array of client connection identifiers, e.g. for broadcasting THttpServerConnectionIDDynArray = array of THttpServerConnectionID; @@ -805,7 +807,7 @@ type {$endif MSWINDOWS} /// defines the sub-threads used by TSynThreadPool - TSynThreadPoolSubThread = class(TSynThread) + TSynThreadPoolWorkThread = class(TSynThread) protected fOwner: TSynThreadPool; fNotifyThreadStartName: AnsiString; @@ -825,14 +827,16 @@ type procedure Execute; override; end; + TSynThreadPoolWorkThreads = array of TSynThreadPoolWorkThread; + {$M+} /// a simple Thread Pool, used e.g. for fast handling HTTP requests // - implemented over I/O Completion Ports under Windows, or a classical // Event-driven approach under Linux/POSIX TSynThreadPool = class protected - fSubThread: array of TSynThreadPoolSubThread; - fSubThreadCount: integer; + fWorkThread: TSynThreadPoolWorkThreads; + fWorkThreadCount: integer; fRunningThreads: integer; fExceptionsCount: integer; fOnThreadTerminate: TNotifyThreadEvent; @@ -887,7 +891,11 @@ type /// parameter as supplied to Create constructor property QueuePendingContext: boolean read fQueuePendingContext; {$endif USE_WINIOCP} + /// low-level access to the threads defined in this thread pool + property WorkThread: TSynThreadPoolWorkThreads read fWorkThread; published + /// how many threads have been defined in this thread pool + property WorkThreadCount: integer read fWorkThreadCount; /// how many threads are currently running in this thread pool property RunningThreads: integer read fRunningThreads; /// how many tasks were rejected due to thread pool contention @@ -1018,7 +1026,7 @@ type /// a 31-bit sequential number identifying this instance on the server property RequestID: integer read fRequestID; /// the ID of the connection which called this execution context - // - e.g. SynCrtSock's TWebSocketProcess.NotifyCallback method would use + // - e.g. SynBidirSock's TWebSocketProcess.NotifyCallback method would use // this property to specify the client connection to be notified // - is set as an Int64 to match http.sys ID type, but will be an // increasing 31-bit integer sequence for (web)socket-based servers @@ -1352,7 +1360,6 @@ type fRegisteredUnicodeUrl: array of SockUnicode; fServerSessionID: HTTP_SERVER_SESSION_ID; fUrlGroupID: HTTP_URL_GROUP_ID; - fExecuting: boolean; fLogData: pointer; fLogDataStorage: array of byte; fLoggingServiceName: SockString; @@ -2346,6 +2353,8 @@ type // back-end server applications that require access to an HTTP client stack TWinHTTP = class(TWinHttpAPI) protected + // you can override this method e.g. to disable/enable some protocols + function InternalGetProtocols: cardinal; virtual; // those internal methods will raise an EOSError exception on error procedure InternalConnect(ConnectionTimeOut,SendTimeout,ReceiveTimeout: DWORD); override; procedure InternalCreateRequest(const aMethod,aURL: SockString); override; @@ -2491,9 +2500,9 @@ type function Request(const uri: SockString; const method: SockString='GET'; const header: SockString = ''; const data: SockString = ''; const datatype: SockString = ''; keepalive: cardinal=10000): integer; overload; - /// returns the HTTP body as returnsd by a previous call to Request() + /// returns the HTTP body as returned by a previous call to Request() property Body: SockString read fBody; - /// returns the HTTP headers as returnsd by a previous call to Request() + /// returns the HTTP headers as returned by a previous call to Request() property Headers: SockString read fHeaders; /// allows to customize the user-agent header property UserAgent: SockString read fUserAgent write fUserAgent; @@ -2531,7 +2540,8 @@ function OpenHttp(const aURI: SockString; aAddress: PSockString=nil): THttpClien /// retrieve the content of a web page, using the HTTP/1.1 protocol and GET method // - this method will use a low-level THttpClientSock socket: if you want -// something able to use your computer proxy, take a look at TWinINet.Get() +// something able to use your computer proxy, take a look at TWinINet.Get() or +// the overloaded HttpGet() methods function HttpGet(const server, port: SockString; const url: SockString; const inHeaders: SockString; outHeaders: PSockString=nil; aLayer: TCrtSocketLayer = cslTCP): SockString; overload; @@ -2560,6 +2570,10 @@ function HttpGetAuth(const aURI, aAuthToken: SockString; function HttpPost(const server, port: SockString; const url, Data, DataType: SockString; outData: PSockString=nil; const auth: SockString=''): boolean; +/// send some data to a remote web server, using the HTTP/1.1 protocol and PUT method +function HttpPut(const server, port: SockString; const url, Data, DataType: SockString; + outData: PSockString=nil; const auth: SockString=''): boolean; + /// compute the 'Authorization: Bearer ####' HTTP header of a given token value function AuthorizationBearer(const AuthToken: SockString): SockString; @@ -2616,7 +2630,8 @@ function SendEmail(const Server, From, CSVDest, Subject, Text: SockString; // - retry true on success // - the Subject is expected to be in plain 7 bit ASCII, so you could use // SendEmailSubject() to encode it as Unicode, if needed -// - you can optionally set the encoding charset to be used for the Text body +// - you can optionally set the encoding charset to be used for the Text body, +// or even TextCharSet='JSON' to force application/json function SendEmail(const Server: TSMTPConnection; const From, CSVDest, Subject, Text: SockString; const Headers: SockString=''; const TextCharSet: SockString = 'ISO-8859-1'; aTLS: boolean=false): boolean; overload; @@ -3895,7 +3910,6 @@ const ERROR_WINHTTP_TIMEOUT = 12002; ERROR_WINHTTP_INVALID_SERVER_RESPONSE = 12152; - function SysErrorMessagePerModule(Code: DWORD; ModuleName: PChar): string; {$ifdef MSWINDOWS} var tmpLen: DWORD; @@ -3909,6 +3923,11 @@ begin tmpLen := FormatMessage( FORMAT_MESSAGE_FROM_HMODULE or FORMAT_MESSAGE_ALLOCATE_BUFFER, pointer(GetModuleHandle(ModuleName)),Code,ENGLISH_LANGID,@err,0,nil); + // if string is empty, it may be because english is not found + if (tmpLen = 0) then + tmpLen := FormatMessage( + FORMAT_MESSAGE_FROM_HMODULE or FORMAT_MESSAGE_ALLOCATE_BUFFER or FORMAT_MESSAGE_IGNORE_INSERTS, + pointer(GetModuleHandle(ModuleName)),Code,0,@err,0,nil); try while (tmpLen>0) and (ord(err[tmpLen-1]) in [0..32,ord('.')]) do dec(tmpLen); @@ -4823,7 +4842,7 @@ function WSAIsFatalError(anothernonfatal: integer=NO_ERROR): boolean; var err: integer; begin err := WSAGetLastError; - result := (err<>NO_ERROR) and (err<>WSATRY_AGAIN) and (err<>WSAEINTR) and + result := (err<>NO_ERROR) and (err<>WSATRY_AGAIN) and {$ifdef MSWINDOWS}(err<>WSAETIMEDOUT) and (err<>WSAEWOULDBLOCK) and{$endif} (err<>anothernonfatal); // allow WSAEADDRNOTAVAIL from OpenBind() end; @@ -4928,14 +4947,15 @@ begin result := false; end; -constructor TCrtSocket.Bind(const aAddr: SockString; aLayer: TCrtSocketLayer); +constructor TCrtSocket.Bind(const aAddr: SockString; aLayer: TCrtSocketLayer; + aTimeOut: integer); var s,p: SockString; aSock: integer; {$ifdef LINUXNOTBSD} n: integer; {$endif} begin - Create(10000); + Create(aTimeOut); if aAddr='' then begin {$ifdef LINUXNOTBSD} // try systemd if not SystemdIsAvailable then @@ -4980,16 +5000,15 @@ begin if self=nil then exit; fSndBufLen := 0; // always reset (e.g. in case of further Open) - if (SockIn<>nil) or (SockOut<>nil) then begin - ioresult; // reset ioresult value if SockIn/SockOut were used - if SockIn<>nil then begin - PTextRec(SockIn)^.BufPos := 0; // reset input buffer - PTextRec(SockIn)^.BufEnd := 0; - end; - if SockOut<>nil then begin - PTextRec(SockOut)^.BufPos := 0; // reset output buffer - PTextRec(SockOut)^.BufEnd := 0; - end; + fSockInEofError := 0; + ioresult; // reset ioresult value if SockIn/SockOut were used + if SockIn<>nil then begin + PTextRec(SockIn)^.BufPos := 0; // reset input buffer + PTextRec(SockIn)^.BufEnd := 0; + end; + if SockOut<>nil then begin + PTextRec(SockOut)^.BufPos := 0; // reset output buffer + PTextRec(SockOut)^.BufEnd := 0; end; if fSock<=0 then exit; // no opened connection, or Close already executed @@ -5226,7 +5245,7 @@ begin exit; if ResultClass=nil then ResultClass := TCrtSocket; - result := ResultClass.Create; + result := ResultClass.Create(Timeout); result.AcceptRequest(client,@sin); result.CreateSockIn; // use SockIn with 1KB input buffer: 2x faster end; @@ -5435,8 +5454,8 @@ begin TSynLog.Add.Log(sllCustom2, 'TrySockRecv: sock=% AsynchRecv=% %', [Sock,read,SocketErrorMessage],self); {$endif} - if WSAIsFatalError then begin - Close; // connection broken or socket closed gracefully + if (read=0) or WSAIsFatalError then begin + Close; // connection broken or socket closed gracefully (read=0) exit; end; if StopBeforeLength then @@ -5725,7 +5744,7 @@ end; function THttpClientSocket.Request(const url, method: SockString; KeepAlive: cardinal; const header, Data, DataType: SockString; retry: boolean): integer; -procedure DoRetry(Error: integer; const msg: SockString); + procedure DoRetry(Error: integer; const msg: SockString); begin {$ifdef SYNCRTDEBUGLOW} TSynLog.Add.Log(sllCustom2, 'Request: % socket=% DoRetry(%) retry=%',[msg,Sock,Error,BOOL_STR[retry]],self); @@ -5734,7 +5753,8 @@ procedure DoRetry(Error: integer; const msg: SockString); result := Error else begin Close; // close this connection try - OpenBind(Server,Port,false); // retry this request with a new socket + HeaderFlags := []; + OpenBind(Server,Port,false,-1,cslTcp,fTLS); // retry with a new socket result := Request(url,method,KeepAlive,Header,Data,DataType,true); except on Exception do @@ -5748,8 +5768,9 @@ begin if SockIn=nil then // done once CreateSockIn; // use SockIn by default if not already initialized: 2x faster Content := ''; - if SockReceivePending(0)=cspSocketError then begin - DoRetry(STATUS_NOTFOUND,'connection broken (keepalive timeout?)'); + if (connectionClose in HeaderFlags) or + (SockReceivePending(0)=cspSocketError) then begin + DoRetry(STATUS_NOTFOUND,'connection broken (kepepalive timeout or too many requests)'); exit; end; try @@ -5801,8 +5822,10 @@ begin exit; end; GetHeader(false); // read all other headers - if (result<>STATUS_NOCONTENT) and not IdemPChar(pointer(method),'HEAD') then - GetBody; // get content if necessary (not HEAD method) + if (result>=STATUS_SUCCESS) and (result<>STATUS_NOCONTENT) and + (result<>STATUS_NOTMODIFIED) and + (IdemPCharArray(pointer(method),['HEAD','OPTIONS'])<0) then + GetBody; // get content if necessary (HEAD or OPTIONS have no body) except on Exception do DoRetry(STATUS_NOTFOUND,'Exception'); @@ -5921,6 +5944,23 @@ begin end; end; +function HttpPut(const server, port: SockString; const url, Data, DataType: SockString; + outData: PSockString; const auth: SockString): boolean; +var Http: THttpClientSocket; +begin + result := false; + Http := OpenHttp(server,port); + if Http<>nil then + try + result := Http.Put(url,Data,DataType,0,AuthorizationBearer(auth)) in + [STATUS_SUCCESS,STATUS_CREATED,STATUS_NOCONTENT]; + if outdata<>nil then + outdata^ := Http.Content; + finally + Http.Free; + end; +end; + function TSMTPConnection.FromText(const aText: SockString): boolean; var u,h: SockString; begin @@ -6002,9 +6042,12 @@ begin head := trim(Headers); if head<>'' then head := head+#13#10; - writeln(TCP.SockOut^,'Subject: ',Subject,#13#10'From: ',From, - ToList,#13#10'Content-Type: text/plain; charset=',TextCharSet, - #13#10'Content-Transfer-Encoding: 8bit'#13#10,head,#13#10,Text); + writeln(TCP.SockOut^,'Subject: ',Subject,#13#10'From: ',From,ToList); + if TextCharSet='JSON' then + writeln(TCP.SockOut^,'Content-Type: application/json; charset=UTF-8') + else + writeln(TCP.SockOut^,'Content-Type: text/plain; charset=',TextCharSet); + writeln(TCP.SockOut^,'Content-Transfer-Encoding: 8bit'#13#10,head,#13#10,Text); Exec('.','25'); writeln(TCP.SockOut^,'QUIT'); result := true; @@ -6390,7 +6433,7 @@ begin exit; Sleep(1); if GetTick64 > tix then - raise ECrtSocket.CreateFmt('%s.WaitStarted failed after % seconds with %s', + raise ECrtSocket.CreateFmt('%s.WaitStarted failed after %d seconds [%s]', [ClassName,Seconds,fExecuteMessage]); until false; end; @@ -6653,17 +6696,20 @@ begin exit; until GetTick64>endtix; end; - result := false; // normal delay expiration + result := false; // abnormal delay expiration end; {$ifndef LVCL} procedure TSynThread.DoTerminate; begin - if Assigned(fStartNotified) and Assigned(fOnThreadTerminate) then begin - fOnThreadTerminate(self); - fStartNotified := nil; + try + if Assigned(fStartNotified) and Assigned(fOnThreadTerminate) then begin + fOnThreadTerminate(self); + fStartNotified := nil; + end; + inherited DoTerminate; // call OnTerminate via Synchronize() + except // hardened: a closing thread should not jeopardize the whole project! end; - inherited DoTerminate; end; {$endif} @@ -6798,7 +6844,8 @@ begin HandleRequestsProcess; end else begin // call from TSynThreadPoolTHttpServer -> handle first request - if not fServerSock.fBodyRetrieved then + if not fServerSock.fBodyRetrieved and + (IdemPCharArray(pointer(fServerSock.fMethod),['HEAD','OPTIONS'])<0) then fServerSock.GetBody; fServer.Process(fServerSock,ConnectionID,self); if (fServer<>nil) and fServerSock.KeepAliveClient then @@ -6864,9 +6911,9 @@ begin SetLength(Content,ContentLength); // not chuncked: direct read SockInRead(pointer(Content),ContentLength); // works with SockIn=nil or not end else - if ContentLength<0 then begin // ContentLength=-1 if no Content-Length - // no Content-Length nor Chunked header -> read until eof() - if SockIn<>nil then + if (ContentLength<0) and IdemPChar(pointer(Command),'HTTP/1.0 200') then begin + // body = either Content-Length or Transfer-Encoding (HTTP/1.1 RFC 4.3) + if SockIn<>nil then // client loop for compatibility with old servers while not eof(SockIn^) do begin readln(SockIn^,Line); if Content='' then @@ -6996,7 +7043,7 @@ begin PWord(@line[len])^ := 13+10 shl 8; // CR + LF SockSend(@line,len+2); end else - SockSend(s); + SockSend(s); // SockSend() internal buffer is used as temporary buffer until false; Headers := copy(fSndBuf, 1, fSndBufLen); fSndBufLen := 0; @@ -7150,7 +7197,8 @@ begin end; end; if withBody and not (connectionUpgrade in HeaderFlags) then begin - GetBody; + if IdemPCharArray(pointer(fMethod),['HEAD','OPTIONS'])<0 then + GetBody; result := grBodyReceived; end else result := grHeaderReceived; @@ -7262,8 +7310,8 @@ end; { TSynThreadPool } const - // up to 256 * 2MB = 512MB of RAM for the TSynThreadPoolSubThread stack - THREADPOOL_MAXSUBTHREADS = 256; + // up to 256 * 2MB = 512MB of RAM for the TSynThreadPoolWorkThread stack + THREADPOOL_MAXTHREADS = 256; // kept-alive or big HTTP requests will create a dedicated THttpServerResp // - each thread reserves 2 MB of memory so it may break the server @@ -7279,8 +7327,8 @@ var i: integer; begin if NumberOfThreads=0 then NumberOfThreads := 1 else - if cardinal(NumberOfThreads)>THREADPOOL_MAXSUBTHREADS then - NumberOfThreads := THREADPOOL_MAXSUBTHREADS; + if cardinal(NumberOfThreads)>THREADPOOL_MAXTHREADS then + NumberOfThreads := THREADPOOL_MAXTHREADS; // create IO completion port to queue the HTTP requests {$ifdef USE_WINIOCP} fRequestQueue := CreateIoCompletionPort(aOverlapHandle, 0, 0, NumberOfThreads); @@ -7293,24 +7341,24 @@ begin fQueuePendingContext := aQueuePendingContext; {$endif} // now create the worker threads - fSubThreadCount := NumberOfThreads; - SetLength(fSubThread,fSubThreadCount); - for i := 0 to fSubThreadCount-1 do - fSubThread[i] := TSynThreadPoolSubThread.Create(Self); + fWorkThreadCount := NumberOfThreads; + SetLength(fWorkThread,fWorkThreadCount); + for i := 0 to fWorkThreadCount-1 do + fWorkThread[i] := TSynThreadPoolWorkThread.Create(Self); end; destructor TSynThreadPool.Destroy; var i: integer; endtix: Int64; begin - fTerminated := true; // fSubThread[].Execute will check this flag + fTerminated := true; // fWorkThread[].Execute will check this flag try // notify the threads we are shutting down - for i := 0 to fSubThreadCount-1 do + for i := 0 to fWorkThreadCount-1 do {$ifdef USE_WINIOCP} PostQueuedCompletionStatus(fRequestQueue,0,0,nil); {$else} - fSubThread[i].fEvent.SetEvent; + fWorkThread[i].fEvent.SetEvent; {$endif} {$ifndef USE_WINIOCP} // cleanup now any pending task (e.g. THttpServerSocket instance) @@ -7321,8 +7369,8 @@ begin endtix := GetTick64+30000; while (fRunningThreads>0) and (GetTick64QueueLength then + if n+fWorkThreadCount>QueueLength then exit; // too many connection limit reached (see QueueIsFull) if n=length(fPendingContext) then SetLength(fPendingContext,n+n shr 3+64); @@ -7423,7 +7471,7 @@ end; function TSynThreadPool.QueueIsFull: boolean; begin result := fQueuePendingContext and - (GetPendingContextCount+fSubThreadCount>QueueLength); + (GetPendingContextCount+fWorkThreadCount>QueueLength); end; function TSynThreadPool.PopPendingContext: pointer; @@ -7461,9 +7509,9 @@ begin end; -{ TSynThreadPoolSubThread } +{ TSynThreadPoolWorkThread } -constructor TSynThreadPoolSubThread.Create(Owner: TSynThreadPool); +constructor TSynThreadPoolWorkThread.Create(Owner: TSynThreadPool); begin fOwner := Owner; // ensure it is set ASAP: on Linux, Execute raises immediately fOnThreadTerminate := Owner.fOnThreadTerminate; @@ -7473,7 +7521,7 @@ begin inherited Create(false); end; -destructor TSynThreadPoolSubThread.Destroy; +destructor TSynThreadPoolWorkThread.Destroy; begin inherited Destroy; {$ifndef USE_WINIOCP} @@ -7488,7 +7536,7 @@ function GetQueuedCompletionStatus(CompletionPort: THandle; external kernel32; // redefine with an unique signature for all Delphi/FPC {$endif} -procedure TSynThreadPoolSubThread.DoTask(Context: pointer); +procedure TSynThreadPoolWorkThread.DoTask(Context: pointer); begin try fOwner.Task(Self,Context); @@ -7498,7 +7546,7 @@ begin end; end; -procedure TSynThreadPoolSubThread.Execute; +procedure TSynThreadPoolWorkThread.Execute; var ctxt: pointer; {$ifdef USE_WINIOCP} dum1: DWORD; @@ -7539,7 +7587,7 @@ begin end; end; -procedure TSynThreadPoolSubThread.NotifyThreadStart(Sender: TSynThread); +procedure TSynThreadPoolWorkThread.NotifyThreadStart(Sender: TSynThread); begin if Sender=nil then raise ECrtSocket.Create('NotifyThreadStart(nil)'); @@ -7606,7 +7654,8 @@ begin ServerSock := nil; // THttpServerResp will own and free ServerSock end else begin // no Keep Alive = multi-connection -> process in the Thread Pool - if not (connectionUpgrade in ServerSock.HeaderFlags) then begin + if not (connectionUpgrade in ServerSock.HeaderFlags) and + (IdemPCharArray(pointer(ServerSock.Method),['HEAD','OPTIONS'])<0) then begin ServerSock.GetBody; // we need to get it now InterlockedIncrement(fServer.fStats[grBodyReceived]); end; @@ -8960,7 +9009,7 @@ begin Http.CloseUrlGroup(fUrlGroupID); fUrlGroupID := 0; end; - CloseHandle(FReqQueue); + CloseHandle(fReqQueue); if fServerSessionID<>0 then begin Http.CloseServerSession(fServerSessionID); fServerSessionID := 0; @@ -8971,6 +9020,10 @@ begin CloseHandle(fReqQueue); // will break all THttpApiServer.Execute end; fReqQueue := 0; + {$ifdef FPC} + for i := 0 to length(fClones)-1 do + WaitForSingleObject(fClones[i].Handle,30000); // sometimes needed on FPC + {$endif FPC} for i := 0 to length(fClones)-1 do fClones[i].Free; fClones := nil; @@ -8979,18 +9032,14 @@ begin end; destructor THttpApiServer.Destroy; -var endtix: Int64; begin Terminate; // for Execute to be notified about end of process try if (fOwner=nil) and (Http.Module<>0) then // fOwner<>nil for cloned threads DestroyMainThread; - if fExecuting then begin - endtix := GetTick64+5000; // never wait forever - repeat - sleep(1); - until not fExecuting or (GetTick64>endtix); // ensure Execute has ended - end; + {$ifdef FPC} + WaitForSingleObject(Handle,30000); // wait the main Execute method on FPC + {$endif FPC} finally inherited Destroy; end; @@ -9205,7 +9254,6 @@ var Req: PHTTP_REQUEST; begin if Terminated then exit; - fExecuting := true; Context := nil; try // THttpServerGeneric thread preparation: launch any OnHttpThreadStart event @@ -9391,7 +9439,6 @@ begin until Terminated; finally Context.Free; - fExecuting := false; end; end; @@ -9863,7 +9910,8 @@ type hReceive, hSend ); -const sProtocolHeader: SockString = 'SEC-WEBSOCKET-PROTOCOL'; +const + sProtocolHeader: SockString = 'SEC-WEBSOCKET-PROTOCOL'; function HttpSys2ToWebSocketHeaders(const aHttpHeaders: HTTP_REQUEST_HEADERS): WEB_SOCKET_HTTP_HEADER_ARR; var headerCnt: Integer; @@ -10164,7 +10212,8 @@ begin fFirstEmptyConnectionIndex := index; end; -function THttpApiWebSocketServerProtocol.Send(index: Integer; aBufferType: WEB_SOCKET_BUFFER_TYPE; aBuffer: Pointer; aBufferSize: ULONG): boolean; +function THttpApiWebSocketServerProtocol.Send(index: Integer; + aBufferType: WEB_SOCKET_BUFFER_TYPE; aBuffer: Pointer; aBufferSize: ULONG): boolean; var conn: PHttpApiWebSocketConnection; begin result := false; @@ -11539,11 +11588,22 @@ end; procedure WinHTTPSecurityErrorCallback(hInternet: HINTERNET; dwContext: PDWORD; dwInternetStatus: DWORD; lpvStatusInformation: pointer; dwStatusInformationLength: DWORD); stdcall; +var err: string; + code: DWORD; begin + code := PDWORD(lpvStatusInformation)^; + if code and $00000001<>0 then err := err+' WINHTTP_CALLBACK_STATUS_FLAG_CERT_REV_FAILED'; + if code and $00000002<>0 then err := err+' WINHTTP_CALLBACK_STATUS_FLAG_INVALID_CERT'; + if code and $00000004<>0 then err := err+' WINHTTP_CALLBACK_STATUS_FLAG_CERT_REVOKED'; + if code and $00000008<>0 then err := err+' WINHTTP_CALLBACK_STATUS_FLAG_INVALID_CA'; + if code and $00000010<>0 then err := err+' WINHTTP_CALLBACK_STATUS_FLAG_CERT_CN_INVALID'; + if code and $00000020<>0 then err := err+' WINHTTP_CALLBACK_STATUS_FLAG_CERT_DATE_INVALID'; + if code and $00000040<>0 then err := err+' WINHTTP_CALLBACK_STATUS_FLAG_CERT_WRONG_USAGE'; + if code and $80000000<>0 then err := err+' WINHTTP_CALLBACK_STATUS_FLAG_SECURITY_CHANNEL_ERROR'; // in case lpvStatusInformation^=-2147483648 this is attempt to connect to // non-https socket wrong port - perhaps must be 443? - raise EWinHTTP.CreateFmt('WinHTTP security error. Status %d, statusInfo: %d', - [dwInternetStatus, pdword(lpvStatusInformation)^]); + raise EWinHTTP.CreateFmt('WinHTTP security error. Status %d, StatusInfo: %d ($%x%s)', + [dwInternetStatus, code, code, err]); end; {$ifndef UNICODE} @@ -11569,12 +11629,22 @@ function GetVersionEx(var lpVersionInformation: TOSVersionInfoEx): BOOL; stdcall var // raw OS call, to avoid dependency to SynCommons.pas unit OSVersionInfo: TOSVersionInfoEx; +function TWinHTTP.InternalGetProtocols: cardinal; +begin + // WINHTTP_FLAG_SECURE_PROTOCOL_SSL2 and WINHTTP_FLAG_SECURE_PROTOCOL_SSL3 + // are unsafe, disabled at Windows level, therefore never supplied + result := WINHTTP_FLAG_SECURE_PROTOCOL_TLS1; + // Windows 7 and newer support TLS 1.1 & 1.2 + if (OSVersionInfo.dwMajorVersion>6) or + ((OSVersionInfo.dwMajorVersion=6) and (OSVersionInfo.dwMinorVersion>=1)) then + result := result or WINHTTP_FLAG_SECURE_PROTOCOL_TLS1_1 + or WINHTTP_FLAG_SECURE_PROTOCOL_TLS1_2; +end; + procedure TWinHTTP.InternalConnect(ConnectionTimeOut,SendTimeout,ReceiveTimeout: DWORD); var OpenType: integer; Callback: WINHTTP_STATUS_CALLBACK; CallbackRes: PtrInt absolute Callback; // for FPC compatibility - // MPV - don't know why, but if I pass WINHTTP_FLAG_SECURE_PROTOCOL_SSL2 - // flag also, TLS1.2 do not work protocols: DWORD; begin if OSVersionInfo.dwOSVersionInfoSize=0 then begin // API call once @@ -11596,13 +11666,7 @@ begin ConnectionTimeOut,SendTimeout,ReceiveTimeout) then RaiseLastModuleError(winhttpdll,EWinHTTP); if fHTTPS then begin - protocols := WINHTTP_FLAG_SECURE_PROTOCOL_SSL3 - or WINHTTP_FLAG_SECURE_PROTOCOL_TLS1; - // Windows 7 and newer support TLS 1.1 & 1.2 - if (OSVersionInfo.dwMajorVersion>6) or - ((OSVersionInfo.dwMajorVersion=6) and (OSVersionInfo.dwMinorVersion>=1)) then - protocols := protocols or WINHTTP_FLAG_SECURE_PROTOCOL_TLS1_1 - or WINHTTP_FLAG_SECURE_PROTOCOL_TLS1_2; + protocols := InternalGetProtocols; if not WinHttpAPI.SetOption(fSession, WINHTTP_OPTION_SECURE_PROTOCOLS, @protocols, SizeOf(protocols)) then RaiseLastModuleError(winhttpdll,EWinHTTP); @@ -11750,10 +11814,8 @@ begin RaiseLastModuleError(winhttpdll,EWinHTTP); L := length(aData); if not _SendRequest(L) or - not WinHttpAPI.ReceiveResponse(fRequest,nil) then begin - if not fHTTPS then - RaiseLastModuleError(winhttpdll,EWinHTTP); - if (GetLastError=ERROR_WINHTTP_CLIENT_AUTH_CERT_NEEDED) and + not WinHttpAPI.ReceiveResponse(fRequest,nil) then + if fHTTPS and (GetLastError=ERROR_WINHTTP_CLIENT_AUTH_CERT_NEEDED) and IgnoreSSLCertificateErrors then begin if not WinHttpAPI.SetOption(fRequest,WINHTTP_OPTION_SECURITY_FLAGS, @SECURITY_FLAT_IGNORE_CERTIFICATES,SizeOf(SECURITY_FLAT_IGNORE_CERTIFICATES)) then @@ -11764,8 +11826,8 @@ begin if not _SendRequest(L) or not WinHttpAPI.ReceiveResponse(fRequest,nil) then RaiseLastModuleError(winhttpdll,EWinHTTP); - end; - end; + end + else RaiseLastModuleError(winhttpdll,EWinHTTP); end; @@ -11879,11 +11941,13 @@ begin if not IsAvailable then raise ECrtSocket.CreateFmt('No available %s',[LIBCURL_DLL]); fHandle := curl.easy_init; - ConnectionTimeOut := ConnectionTimeOut div 1000; // curl expects seconds - if ConnectionTimeOut=0 then - ConnectionTimeOut := 1; - curl.easy_setopt(fHandle,coConnectTimeout,ConnectionTimeOut); // default=300 ! - // coTimeout=CURLOPT_TIMEOUT is global for the transfer, so shouldn't be used + if curl.globalShare <> nil then + curl.easy_setopt(fHandle,coShare,curl.globalShare); + curl.easy_setopt(fHandle,coConnectTimeoutMs,ConnectionTimeOut); // default=300 ! + if SendTimeout0 then // prevent send+receive forever + curl.easy_setopt(fHandle,coTimeoutMs,SendTimeout); if fLayer=cslUNIX then fRootURL := 'http://localhost' else // see CURLOPT_UNIX_SOCKET_PATH doc fRootURL := AnsiString(Format('http%s://%s:%d',[HTTPS[fHttps],fServer,fPort])); @@ -11906,7 +11970,6 @@ begin fSSL.CACertFile := aCertFile; end; - procedure TCurlHTTP.UseClientCertificate( const aCertFile, aCACertFile, aKeyName, aPassPhrase: SockString); begin @@ -12077,7 +12140,7 @@ begin else try if (fHttp = nil) or (fHttp.Server <> Uri.Server) or - (fHttp.Port <> Uri.Port) then begin + (fHttp.Port <> Uri.Port) or (connectionClose in fHttp.HeaderFlags) then begin FreeAndNil(fHttps); FreeAndNil(fHttp); // need a new HTTP connection fHttp := THttpClientSocket.Open(Uri.Server,Uri.Port,cslTCP,5000,Uri.Https); diff --git a/contrib/mORMot/SynCrypto.pas b/contrib/mORMot/SynCrypto.pas index 4981492..1f315a7 100644 --- a/contrib/mORMot/SynCrypto.pas +++ b/contrib/mORMot/SynCrypto.pas @@ -8,7 +8,7 @@ unit SynCrypto; (* This file is part of Synopse framework. - Synopse framework. Copyright (C) 2020 Arnaud Bouchez + Synopse framework. Copyright (C) 2022 Arnaud Bouchez Synopse Informatique - https://synopse.info *** BEGIN LICENSE BLOCK ***** @@ -27,7 +27,7 @@ unit SynCrypto; The Initial Developer of the Original Code is Arnaud Bouchez. - Portions created by the Initial Developer are Copyright (C) 2020 + Portions created by the Initial Developer are Copyright (C) 2022 the Initial Developer. All Rights Reserved. Contributor(s): @@ -328,6 +328,76 @@ type function KeyBits: integer; {$ifdef FPC}inline;{$endif} end; +type + /// low-level AES-GCM processing + // - implements standard AEAD (authenticated-encryption with associated-data) + // algorithm, as defined by NIST and + TAESGCMEngine = object + private + /// standard AES encryption context + // - will use AES-NI if available + actx: TAES; + /// ghash value of the Authentication Data + aad_ghv: TAESBlock; + /// ghash value of the Ciphertext + txt_ghv: TAESBlock; + /// ghash H current value + ghash_h: TAESBlock; + /// number of Authentication Data bytes processed + aad_cnt: TQWordRec; + /// number of bytes of the Ciphertext + atx_cnt: TQWordRec; + /// initial 32-bit ctr val - to be reused in Final() + y0_val: integer; + /// current 0..15 position in encryption block + blen: byte; + /// the state of this context + flags: set of (flagInitialized, flagFinalComputed, flagFlushed); + /// lookup table for fast Galois Finite Field multiplication + // - is defined as last field of the object for better code generation + gf_t4k: array[byte] of TAESBlock; + /// build the gf_t4k[] internal table - assuming set to zero by caller + procedure Make4K_Table; + /// compute a * ghash_h in Galois Finite Field 2^128 + procedure gf_mul_h(var a: TAESBlock); {$ifdef FPC} inline; {$endif} + /// low-level AES-CTR encryption + procedure internal_crypt(ptp, ctp: PByte; ILen: PtrUInt); + /// low-level GCM authentication + procedure internal_auth(ctp: PByte; ILen: PtrUInt; + var ghv: TAESBlock; var gcnt: TQWordRec); + public + /// initialize the AES-GCM structure for the supplied Key + function Init(const Key; KeyBits: PtrInt): boolean; + /// start AES-GCM encryption with a given Initialization Vector + // - IV_len is in bytes use 12 for exact IV setting, otherwise the + // supplied buffer will be hashed using gf_mul_h() + function Reset(pIV: pointer; IV_len: PtrInt): boolean; + /// encrypt a buffer with AES-GCM, updating the associated authentication data + function Encrypt(ptp, ctp: Pointer; ILen: PtrInt): boolean; + /// decrypt a buffer with AES-GCM, updating the associated authentication data + // - also validate the GMAC with the supplied ptag/tlen if ptag<>nil, + // and skip the AES-CTR phase if the authentication doesn't match + function Decrypt(ctp, ptp: Pointer; ILen: PtrInt; + ptag: pointer=nil; tlen: PtrInt=0): boolean; + /// append some data to be authenticated, but not encrypted + function Add_AAD(pAAD: pointer; aLen: PtrInt): boolean; + /// finalize the AES-GCM encryption, returning the authentication tag + // - will also flush the AES context to avoid forensic issues, unless + // andDone is forced to false + function Final(out tag: TAESBlock; andDone: boolean=true): boolean; + /// flush the AES context to avoid forensic issues + // - do nothing if Final() has been already called + procedure Done; + /// single call AES-GCM encryption and authentication process + function FullEncryptAndAuthenticate(const Key; KeyBits: PtrInt; + pIV: pointer; IV_len: PtrInt; pAAD: pointer; aLen: PtrInt; + ptp, ctp: Pointer; pLen: PtrInt; out tag: TAESBlock): boolean; + /// single call AES-GCM decryption and verification process + function FullDecryptAndVerify(const Key; KeyBits: PtrInt; + pIV: pointer; IV_len: PtrInt; pAAD: pointer; aLen: PtrInt; + ctp, ptp: Pointer; pLen: PtrInt; ptag: pointer; tLen: PtrInt): boolean; + end; + /// class-reference type (metaclass) of an AES cypher/uncypher TAESAbstractClass = class of TAESAbstract; @@ -639,7 +709,7 @@ type /// release the used instance memory and resources // - also fill the TAES instance with zeros, for safety destructor Destroy; override; - /// perform the AES cypher in the corresponding mode + /// perform the AES cypher in the corresponding mode, over Count bytes // - this abstract method will set CV from fIV property, and fIn/fOut // from BufIn/BufOut procedure Encrypt(BufIn, BufOut: pointer; Count: cardinal); override; @@ -818,6 +888,46 @@ type procedure Decrypt(BufIn, BufOut: pointer; Count: cardinal); override; end; + /// handle AES-GCM cypher/uncypher with built-in authentication + // - implements AEAD (authenticated-encryption with associated-data) methods + // like MACEncrypt/MACCheckError + // - this class will use AES-NI hardware instructions, if available + TAESGCM = class(TAESAbstract) + protected + fAES: TAESGCMEngine; + fContext: (ctxNone,ctxEncrypt,ctxDecrypt); // used to call AES.Reset() + public + /// Initialize the AES-GCM context for cypher + // - first method to call before using this class + // - KeySize is in bits, i.e. 128,192,256 + constructor Create(const aKey; aKeySize: cardinal); override; + /// creates a new instance with the very same values + // - by design, our classes will use TAESGCMEngine stateless context, so + // this method will just copy the current fields to a new instance, + // by-passing the key creation step + function Clone: TAESAbstract; override; + /// release the used instance memory and resources + // - also fill the internal TAES instance with zeros, for safety + destructor Destroy; override; + /// perform the AES-GCM cypher and authentication + procedure Encrypt(BufIn, BufOut: pointer; Count: cardinal); override; + /// perform the AES un-cypher and authentication + procedure Decrypt(BufIn, BufOut: pointer; Count: cardinal); override; + /// prepare the AES-GCM process before Encrypt/Decrypt is called + // - aKey is not used: AES-GCM has its own nonce setting algorithm, and + // the IV will be set from random value by EncryptPKCS7() + // - will just include any supplied associated data to the GMAC tag + function MACSetNonce(const aKey: THash256; aAssociated: pointer=nil; + aAssociatedLen: integer=0): boolean; override; + /// returns AEAD (authenticated-encryption with associated-data) MAC + /// - only the lower 128-bit (THash256.Lo) of aCRC is filled with the GMAC + function MACGetLast(out aCRC: THash256): boolean; override; + /// validate if an encrypted buffer matches the stored AEAD MAC + // - since AES-GCM is a one pass process, always assume the content is fine + // and returns true - we don't know the IV at this time + function MACCheckError(aEncrypted: pointer; Count: cardinal): boolean; override; + end; + {$ifdef USE_PROV_RSA_AES} type /// handle AES cypher/uncypher using Windows CryptoAPI and the @@ -2403,7 +2513,7 @@ type /// finalize the encryption destructor Destroy; override; /// initialize the communication by exchanging some client/server information - // - this method will return sprUnsupported + // - this method will return sprUnsupported, since no key negociation is involved function ProcessHandshake(const MsgIn: RawUTF8; out MsgOut: RawUTF8): TProtocolResult; /// encrypt a message on one side, ready to be transmitted to the other side // - this method uses AES encryption and PKCS7 padding @@ -6221,6 +6331,433 @@ end; {$endif USETHREADSFORBIGAESBLOCKS} +{ AES-GCM Support } + +const + // lookup table as used by mul_x/gf_mul/gf_mul_h + gft_le: array[byte] of word = ( + $0000, $c201, $8403, $4602, $0807, $ca06, $8c04, $4e05, + $100e, $d20f, $940d, $560c, $1809, $da08, $9c0a, $5e0b, + $201c, $e21d, $a41f, $661e, $281b, $ea1a, $ac18, $6e19, + $3012, $f213, $b411, $7610, $3815, $fa14, $bc16, $7e17, + $4038, $8239, $c43b, $063a, $483f, $8a3e, $cc3c, $0e3d, + $5036, $9237, $d435, $1634, $5831, $9a30, $dc32, $1e33, + $6024, $a225, $e427, $2626, $6823, $aa22, $ec20, $2e21, + $702a, $b22b, $f429, $3628, $782d, $ba2c, $fc2e, $3e2f, + $8070, $4271, $0473, $c672, $8877, $4a76, $0c74, $ce75, + $907e, $527f, $147d, $d67c, $9879, $5a78, $1c7a, $de7b, + $a06c, $626d, $246f, $e66e, $a86b, $6a6a, $2c68, $ee69, + $b062, $7263, $3461, $f660, $b865, $7a64, $3c66, $fe67, + $c048, $0249, $444b, $864a, $c84f, $0a4e, $4c4c, $8e4d, + $d046, $1247, $5445, $9644, $d841, $1a40, $5c42, $9e43, + $e054, $2255, $6457, $a656, $e853, $2a52, $6c50, $ae51, + $f05a, $325b, $7459, $b658, $f85d, $3a5c, $7c5e, $be5f, + $00e1, $c2e0, $84e2, $46e3, $08e6, $cae7, $8ce5, $4ee4, + $10ef, $d2ee, $94ec, $56ed, $18e8, $dae9, $9ceb, $5eea, + $20fd, $e2fc, $a4fe, $66ff, $28fa, $eafb, $acf9, $6ef8, + $30f3, $f2f2, $b4f0, $76f1, $38f4, $faf5, $bcf7, $7ef6, + $40d9, $82d8, $c4da, $06db, $48de, $8adf, $ccdd, $0edc, + $50d7, $92d6, $d4d4, $16d5, $58d0, $9ad1, $dcd3, $1ed2, + $60c5, $a2c4, $e4c6, $26c7, $68c2, $aac3, $ecc1, $2ec0, + $70cb, $b2ca, $f4c8, $36c9, $78cc, $bacd, $fccf, $3ece, + $8091, $4290, $0492, $c693, $8896, $4a97, $0c95, $ce94, + $909f, $529e, $149c, $d69d, $9898, $5a99, $1c9b, $de9a, + $a08d, $628c, $248e, $e68f, $a88a, $6a8b, $2c89, $ee88, + $b083, $7282, $3480, $f681, $b884, $7a85, $3c87, $fe86, + $c0a9, $02a8, $44aa, $86ab, $c8ae, $0aaf, $4cad, $8eac, + $d0a7, $12a6, $54a4, $96a5, $d8a0, $1aa1, $5ca3, $9ea2, + $e0b5, $22b4, $64b6, $a6b7, $e8b2, $2ab3, $6cb1, $aeb0, + $f0bb, $32ba, $74b8, $b6b9, $f8bc, $3abd, $7cbf, $bebe); + +procedure mul_x(var a: TAESBlock; const b: TAESBlock); +// {$ifdef HASINLINE}inline;{$endif} // inlining has no benefit here +var t: cardinal; + y: TWA4 absolute b; +const + MASK_80 = cardinal($80808080); + MASK_7F = cardinal($7f7f7f7f); +begin + t := gft_le[(y[3] shr 17) and MASK_80]; + TWA4(a)[3] := ((y[3] shr 1) and MASK_7F) or (((y[3] shl 15) or (y[2] shr 17)) and MASK_80); + TWA4(a)[2] := ((y[2] shr 1) and MASK_7F) or (((y[2] shl 15) or (y[1] shr 17)) and MASK_80); + TWA4(a)[1] := ((y[1] shr 1) and MASK_7F) or (((y[1] shl 15) or (y[0] shr 17)) and MASK_80); + TWA4(a)[0] := (((y[0] shr 1) and MASK_7F) or ( (y[0] shl 15) and MASK_80)) xor t; +end; + +procedure gf_mul(var a: TAESBlock; const b: TAESBlock); +var p: array[0..7] of TAESBlock; + x: TWA4; + t: cardinal; + i: PtrInt; + j: integer; + c: byte; +begin + p[0] := b; + for i := 1 to 7 do + mul_x(p[i], p[i-1]); + FillZero(TAESBlock(x)); + for i:=0 to 15 do begin + c := a[15-i]; + if i>0 then begin + // inlined mul_x8() + t := gft_le[x[3] shr 24]; + x[3] := ((x[3] shl 8) or (x[2] shr 24)); + x[2] := ((x[2] shl 8) or (x[1] shr 24)); + x[1] := ((x[1] shl 8) or (x[0] shr 24)); + x[0] := ((x[0] shl 8) xor t); + end; + for j:=0 to 7 do begin + if c and ($80 shr j) <> 0 then begin + x[3] := x[3] xor TWA4(p[j])[3]; + x[2] := x[2] xor TWA4(p[j])[2]; + x[1] := x[1] xor TWA4(p[j])[1]; + x[0] := x[0] xor TWA4(p[j])[0]; + end; + end; + end; + a := TAESBlock(x); +end; + + +{ TAESGCMEngine } + +procedure TAESGCMEngine.Make4K_Table; +var j, k: PtrInt; +begin + gf_t4k[128] := ghash_h; + j := 64; + while j>0 do begin + mul_x(gf_t4k[j],gf_t4k[j+j]); + j := j shr 1; + end; + j := 2; + while j<256 do begin + for k := 1 to j-1 do + XorBlock16(@gf_t4k[k],@gf_t4k[j+k],@gf_t4k[j]); + inc(j,j); + end; +end; + +procedure TAESGCMEngine.gf_mul_h(var a: TAESBlock); +var + x: TWA4; + i: PtrUInt; + t: cardinal; + p: PWA4; + {$ifdef CPUX86NOTPIC} + tab: TWordArray absolute gft_le; + {$else} + tab: PWordArray; + {$endif CPUX86NOTPIC} +begin + {$ifndef CPUX86NOTPIC} + tab := @gft_le; + {$endif CPUX86NOTPIC} + x := TWA4(gf_t4k[a[15]]); + for i := 14 downto 0 do begin + p := @gf_t4k[a[i]]; + t := tab[x[3] shr 24]; + // efficient mul_x8 and xor using pre-computed table entries + x[3] := ((x[3] shl 8) or (x[2] shr 24)) xor p^[3]; + x[2] := ((x[2] shl 8) or (x[1] shr 24)) xor p^[2]; + x[1] := ((x[1] shl 8) or (x[0] shr 24)) xor p^[1]; + x[0] := ((x[0] shl 8) xor t) xor p^[0]; + end; + a := TAESBlock(x); +end; + +procedure GCM_IncCtr(var x: TAESBlock); {$ifdef HASINLINE} inline; {$endif} +begin + // in AES-GCM, CTR covers only 32 LSB Big-Endian bits, i.e. x[15]..x[12] + inc(x[15]); + if x[15]<>0 then + exit; + inc(x[14]); + if x[14]<>0 then + exit; + inc(x[13]); + if x[13]=0 then + inc(x[12]); +end; + +procedure TAESGCMEngine.internal_crypt(ptp, ctp: PByte; ILen: PtrUInt); +var b_pos: PtrUInt; +begin + b_pos := blen; + inc(blen,ILen); + blen := blen and AESBlockMod; + if b_pos=0 then + b_pos := SizeOf(TAESBlock) else + while (ILen>0) and (b_pos=SizeOf(TAESBlock) do begin + GCM_IncCtr(TAESContext(actx).IV); + actx.Encrypt(TAESContext(actx).IV,TAESContext(actx).buf); // maybe AES-NI + XorBlock16(pointer(ptp),pointer(ctp),@TAESContext(actx).buf); + inc(PAESBlock(ptp)); + inc(PAESBlock(ctp)); + dec(ILen,SizeOf(TAESBlock)); + end; + while ILen>0 do begin + if b_pos=SizeOf(TAESBlock) then begin + GCM_IncCtr(TAESContext(actx).IV); + actx.Encrypt(TAESContext(actx).IV,TAESContext(actx).buf); + b_pos := 0; + end; + ctp^ := TAESContext(actx).buf[b_pos] xor ptp^; + inc(b_pos); + inc(ptp); + inc(ctp); + dec(ILen); + end; +end; + +procedure TAESGCMEngine.internal_auth(ctp: PByte; ILen: PtrUInt; + var ghv: TAESBlock; var gcnt: TQWordRec); +var b_pos: PtrUInt; +begin + b_pos := gcnt.L and AESBlockMod; + inc(gcnt.V,ILen); + if (b_pos=0) and (gcnt.V<>0) then + gf_mul_h(ghv); + while (ILen>0) and (b_pos=SizeOf(TAESBlock) do begin + gf_mul_h(ghv); + XorBlock16(@ghv,pointer(ctp)); + inc(PAESBlock(ctp)); + dec(ILen,SizeOf(TAESBlock)); + end; + while ILen>0 do begin + if b_pos=SizeOf(TAESBlock) then begin + gf_mul_h(ghv); + b_pos := 0; + end; + ghv[b_pos] := ghv[b_pos] xor ctp^; + inc(b_pos); + inc(ctp); + dec(ILen); + end; +end; + +function TAESGCMEngine.Init(const Key; KeyBits: PtrInt): boolean; +begin + FillcharFast(self,SizeOf(self),0); + result := actx.EncryptInit(Key,KeyBits); + if not result then + exit; + actx.Encrypt(ghash_h, ghash_h); + Make4K_Table; +end; + +const + CTR_POS = 12; + +function TAESGCMEngine.Reset(pIV: pointer; IV_len: PtrInt): boolean; +var i, n_pos: PtrInt; +begin + if (pIV=nil) or (IV_len=0) then begin + result := false; + exit; + end; + if IV_len=CTR_POS then begin + // Initialization Vector size matches perfect size of 12 bytes + MoveFast(pIV^,TAESContext(actx).IV,CTR_POS); + TWA4(TAESContext(actx).IV)[3] := $01000000; + end else begin + // Initialization Vector is otherwise computed from GHASH(IV,H) + n_pos := IV_len; + FillZero(TAESContext(actx).IV); + while n_pos>=SizeOf(TAESBlock) do begin + XorBlock16(@TAESContext(actx).IV,pIV); + inc(PAesBlock(pIV)); + dec(n_pos,SizeOf(TAESBlock)); + gf_mul_h(TAESContext(actx).IV); + end; + if n_pos>0 then begin + for i := 0 to n_pos-1 do + TAESContext(actx).IV[i] := TAESContext(actx).IV[i] xor PAESBlock(pIV)^[i]; + gf_mul_h(TAESContext(actx).IV); + end; + n_pos := IV_len shl 3; + i := 15; + while n_pos>0 do begin + TAESContext(actx).IV[i] := TAESContext(actx).IV[i] xor byte(n_pos); + n_pos := n_pos shr 8; + dec(i); + end; + gf_mul_h(TAESContext(actx).IV); + end; + // reset internal state and counters + y0_val := TWA4(TAESContext(actx).IV)[3]; + FillZero(aad_ghv); + FillZero(txt_ghv); + aad_cnt.V := 0; + atx_cnt.V := 0; + flags := []; + result := true; +end; + +function TAESGCMEngine.Encrypt(ptp, ctp: Pointer; ILen: PtrInt): boolean; +begin + if ILen>0 then begin + if (ptp=nil) or (ctp=nil) or (flagFinalComputed in flags) then begin + result := false; + exit; + end; + if (ILen and AESBlockMod=0) and (blen=0) then begin + inc(atx_cnt.V,ILen); + ILen := ILen shr AESBlockShift; + repeat // loop optimized e.g. for PKCS7 padding + GCM_IncCtr(TAESContext(actx).IV); + actx.Encrypt(TAESContext(actx).IV,TAESContext(actx).buf); // maybe AES-NI + XorBlock16(ptp,ctp,@TAESContext(actx).buf); + gf_mul_h(txt_ghv); + XorBlock16(@txt_ghv,ctp); + inc(PAESBlock(ptp)); + inc(PAESBlock(ctp)); + dec(ILen); + until ILen=0; + end else begin // generic process in dual steps + internal_crypt(ptp,ctp,iLen); + internal_auth(ctp,ILen,txt_ghv,atx_cnt); + end; + end; + result := true; +end; + +function TAESGCMEngine.Decrypt(ctp, ptp: Pointer; ILen: PtrInt; + ptag: pointer; tlen: PtrInt): boolean; +var tag: TAESBlock; +begin + result := false; + if ILen>0 then begin + if (ptp=nil) or (ctp=nil) or (flagFinalComputed in flags) then + exit; + if (ILen and AESBlockMod=0) and (blen=0) then begin + inc(atx_cnt.V,ILen); + ILen := ILen shr AESBlockShift; + repeat // loop optimized e.g. for PKCS7 padding + gf_mul_h(txt_ghv); + XorBlock16(@txt_ghv,ctp); + GCM_IncCtr(TAESContext(actx).IV); + actx.Encrypt(TAESContext(actx).IV,TAESContext(actx).buf); // maybe AES-NI + XorBlock16(ctp,ptp,@TAESContext(actx).buf); + inc(PAESBlock(ptp)); + inc(PAESBlock(ctp)); + dec(ILen); + until ILen=0; + if (ptag<>nil) and (tlen>0) then begin + Final(tag,{anddone=}false); + if not IsEqual(tag,ptag^,tlen) then + exit; // check authentication after single pass encryption + auth + end; + end else begin // generic process in dual steps + internal_auth(ctp,ILen,txt_ghv,atx_cnt); + if (ptag<>nil) and (tlen>0) then begin + Final(tag,{anddone=}false); + if not IsEqual(tag,ptag^,tlen) then + exit; // check authentication before encryption + end; + internal_crypt(ctp,ptp,iLen); + end; + end; + result := true; +end; + +function TAESGCMEngine.Add_AAD(pAAD: pointer; aLen: PtrInt): boolean; +begin + if aLen>0 then begin + if (pAAD=nil) or (flagFinalComputed in flags) then begin + result := false; + exit; + end; + internal_auth(pAAD,aLen,aad_ghv,aad_cnt); + end; + result := true; +end; + +function TAESGCMEngine.Final(out tag: TAESBlock; andDone: boolean): boolean; +var + tbuf: TAESBlock; + ln: cardinal; +begin + if not (flagFinalComputed in flags) then begin + include(flags,flagFinalComputed); + // compute GHASH(H, AAD, ctp) + gf_mul_h(aad_ghv); + gf_mul_h(txt_ghv); + // compute len(AAD) || len(ctp) with each len as 64-bit big-endian + ln := (atx_cnt.V+AESBlockMod) shr AESBlockShift; + if (aad_cnt.V>0) and (ln<>0) then begin + tbuf := ghash_h; + while ln<>0 do begin + if odd(ln) then + gf_mul(aad_ghv,tbuf); + ln := ln shr 1; + if ln<>0 then + gf_mul(tbuf,tbuf); + end; + end; + TWA4(tbuf)[0] := bswap32((aad_cnt.L shr 29) or (aad_cnt.H shl 3)); + TWA4(tbuf)[1] := bswap32((aad_cnt.L shl 3)); + TWA4(tbuf)[2] := bswap32((atx_cnt.L shr 29) or (atx_cnt.H shl 3)); + TWA4(tbuf)[3] := bswap32((atx_cnt.L shl 3)); + XorBlock16(@tbuf,@txt_ghv); + XorBlock16(@aad_ghv,@tbuf); + gf_mul_h(aad_ghv); + // compute E(K,Y0) + tbuf := TAESContext(actx).IV; + TWA4(tbuf)[3] := y0_val; + actx.Encrypt(tbuf); + // GMAC = GHASH(H, AAD, ctp) xor E(K,Y0) + XorBlock16(@aad_ghv,@tag,@tbuf); + if andDone then + Done; + result := true; + end else begin + Done; + result := false; + end; +end; + +procedure TAESGCMEngine.Done; +begin + if flagFlushed in flags then + exit; + actx.Done; + include(flags,flagFlushed); +end; + +function TAESGCMEngine.FullEncryptAndAuthenticate(const Key; KeyBits: PtrInt; + pIV: pointer; IV_len: PtrInt; pAAD: pointer; aLen: PtrInt; ptp, ctp: Pointer; + pLen: PtrInt; out tag: TAESBlock): boolean; +begin + result := Init(Key,KeyBits) and Reset(pIV,IV_len) and Add_AAD(pAAD,aLen) and + Encrypt(ptp,ctp,pLen) and Final(tag); + Done; +end; + +function TAESGCMEngine.FullDecryptAndVerify(const Key; KeyBits: PtrInt; + pIV: pointer; IV_len: PtrInt; pAAD: pointer; aLen: PtrInt; ctp, ptp: Pointer; + pLen: PtrInt; ptag: pointer; tLen: PtrInt): boolean; +begin + result := Init(Key,KeyBits) and Reset(pIV,IV_len) and Add_AAD(pAAD,aLen) and + Decrypt(ctp,ptp,pLen,ptag,tlen); + Done; +end; + + + { TSHA256 } // under Win32, with a Core i7 CPU: pure pascal: 152ms - x86: 112ms @@ -7598,6 +8135,15 @@ var Data: TSHAContext absolute Context; begin if Buffer=nil then exit; // avoid GPF inc(Data.MLen,QWord(cardinal(Len)) shl 3); + {$ifdef CPUX64} + if (K256AlignedStore<>'') and (Data.Index=0) and (Len>=64) then begin + // use optimized Intel's sha256_sse4.asm for whole blocks + sha256_sse4(Buffer^,Data.Hash,Len shr 6); + inc(PByte(Buffer),Len); + Len := Len and 63; + dec(PByte(Buffer),Len); + end; + {$endif CPUX64} while Len>0 do begin aLen := 64-Data.Index; if aLen<=Len then begin @@ -7608,7 +8154,7 @@ begin end else RawSha256Compress(Data.Hash,Buffer); // avoid temporary copy dec(Len,aLen); - inc(PtrInt(Buffer),aLen); + inc(PByte(Buffer),aLen); end else begin MoveFast(Buffer^,Data.Buffer[Data.Index],Len); inc(Data.Index,Len); @@ -9576,7 +10122,7 @@ begin if OutStream<>nil then begin if OutStream.InheritsFrom(TMemoryStream) then with TMemoryStream(OutStream) do begin - P := Seek(0,soFromCurrent); + P := Seek(0,soCurrent); Size := P+Len; // auto-reserve space (no Realloc:) Seek(P+Len,soBeginning); bOut := PAnsiChar(Memory)+P; @@ -11695,6 +12241,8 @@ var p: ^TMD5In; t: cardinal; i: integer; begin + if len=0 then + exit; p := @buffer; // Update byte count t := bytes[0]; @@ -12253,7 +12801,7 @@ begin TAESPRNG.Main.FillRandom(rec.nonce); if not MACSetNonce(rec.nonce) then exit; - rec.Data := EncryptPKCS7(Data,true); + rec.Data := EncryptPKCS7(Data,{IVAtBeginning=}true); if not MACGetLast(rec.mac) then exit; rec.crc := crc32c(VERSION,@rec.nonce,CRCSIZ); @@ -12376,6 +12924,7 @@ begin inherited Destroy; AES.Done; // mandatory for Padlock - also fill buffer with 0 for safety FillZero(fCV); // may contain sensitive data on some modes + FillZero(fIV); end; function TAESAbstractSyn.Clone: TAESAbstract; @@ -13139,6 +13688,86 @@ begin end; +{ TAESGCM } + +constructor TAESGCM.Create(const aKey; aKeySize: cardinal); +begin + inherited Create(aKey,aKeySize); // set fKey/fKeySize + if not fAES.Init(aKey,aKeySize) then + raise ESynCrypto.CreateUTF8('%.Create(keysize=%) failed',[self,aKeySize]); +end; + +function TAESGCM.Clone: TAESAbstract; +begin + result := NewInstance as TAESGCM; + result.fKey := fKey; + result.fKeySize := fKeySize; + result.fKeySizeBytes := fKeySizeBytes; + TAESGCM(result).fAES := fAES; // reuse the very same TAESGCMEngine memory +end; + +destructor TAESGCM.Destroy; +begin + inherited Destroy; + fAES.Done; + FillZero(fIV); +end; + +procedure TAESGCM.Encrypt(BufIn, BufOut: pointer; Count: cardinal); +begin + if fContext<>ctxEncrypt then + if fContext=ctxNone then begin + fAES.Reset(@fIV,CTR_POS); // caller should have set the IV + fContext := ctxEncrypt; + end else + raise ESynCrypto.CreateUTF8('%.Encrypt after Decrypt',[self]); + if not fAES.Encrypt(BufIn,BufOut,Count) then + raise ESynCrypto.CreateUTF8('%.Encrypt called after GCM final state',[self]); +end; + +procedure TAESGCM.Decrypt(BufIn, BufOut: pointer; Count: cardinal); +begin + if fContext<>ctxDecrypt then + if fContext=ctxNone then begin + fAES.Reset(@fIV,CTR_POS); + fContext := ctxDecrypt; + end else + raise ESynCrypto.CreateUTF8('%.Decrypt after Encrypt',[self]); + if not fAES.Decrypt(BufIn,BufOut,Count) then + raise ESynCrypto.CreateUTF8('%.Decrypt called after GCM final state',[self]); +end; + +function TAESGCM.MACSetNonce(const aKey: THash256; aAssociated: pointer; + aAssociatedLen: integer): boolean; +begin + if fContext<>ctxNone then begin + result := false; // should be called before Encrypt/Decrypt + exit; + end; + // aKey is ignored since not used during GMAC computation + if (aAssociated<>nil) and (aAssociatedLen>0) then + fAES.Add_AAD(aAssociated,aAssociatedLen); + result := true; +end; + +function TAESGCM.MACGetLast(out aCRC: THash256): boolean; +begin + if fContext=ctxNone then begin + result := false; // should be called after Encrypt/Decrypt + exit; + end; + fAES.Final(THash256Rec(aCRC).Lo,{forreuse:anddone=}false); + FillZero(THash256Rec(aCRC).Hi); // upper 128-bit are not used + fContext := ctxNone; // allow reuse of this fAES instance + result := true; +end; + +function TAESGCM.MACCheckError(aEncrypted: pointer; Count: cardinal): boolean; +begin + result := true; // AES-GCM requires the IV to be set -> will be checked later +end; + + {$ifdef MSWINDOWS} type @@ -13580,42 +14209,42 @@ function TAESPRNG.Random32: cardinal; var block: THash128Rec; begin FillRandom(block.b); - result := block.c0 xor block.c1 xor block.c2 xor block.c3; + result := block.c0; // no need to XOR with c1, c2, c3 with a permutation algo end; function TAESPRNG.Random32(max: cardinal): cardinal; var block: THash128Rec; begin FillRandom(block.b); - result := (Qword(block.c0 xor block.c1 xor block.c2 xor block.c3)*max) shr 32; + result := (Qword(block.c0)*max) shr 32; // no need to XOR with c1, c2, c3 end; function TAESPRNG.Random64: QWord; var block: THash128Rec; begin FillRandom(block.b); - result := block.L xor block.H; + result := block.L; // no need to XOR with H end; function Hash128ToExt({$ifdef FPC}constref{$else}const{$endif} r: THash128): TSynExtended; const COEFF64: TSynExtended = (1.0/$80000000)/$100000000; // 2^-63 begin - result := ((THash128Rec(r).Lo xor THash128Rec(r).Hi) and $7fffffffffffffff)*COEFF64; + result := (THash128Rec(r).Lo and $7fffffffffffffff)*COEFF64; end; function Hash128ToDouble({$ifdef FPC}constref{$else}const{$endif} r: THash128): double; const COEFF64: double = (1.0/$80000000)/$100000000; // 2^-63 begin - result := ((THash128Rec(r).Lo xor THash128Rec(r).Hi) and $7fffffffffffffff)*COEFF64; + result := (THash128Rec(r).Lo and $7fffffffffffffff)*COEFF64; end; function Hash128ToSingle({$ifdef FPC}constref{$else}const{$endif} r: THash128): double; const COEFF64: single = (1.0/$80000000)/$100000000; // 2^-63 begin - result := ((THash128Rec(r).Lo xor THash128Rec(r).Hi) and $7fffffffffffffff)*COEFF64; + result := (THash128Rec(r).Lo and $7fffffffffffffff)*COEFF64; end; function TAESPRNG.RandomExt: TSynExtended; @@ -13968,9 +14597,9 @@ begin try if Compress then begin CompressSynLZ(Data,true); - Data := EncryptPKCS7(Data,true); + Data := EncryptPKCS7(Data,{IVAtBeginning=}true); end else begin - Data := DecryptPKCS7(Data,true); + Data := DecryptPKCS7(Data,{IVAtBeginning=}true); if CompressSynLZ(Data,false)='' then begin result := ''; exit; // invalid content @@ -14240,7 +14869,7 @@ procedure TProtocolAES.Encrypt(const aPlain: RawByteString; begin fSafe.Lock; try - aEncrypted := fAES[true].EncryptPKCS7(aPlain,{iv=}true); + aEncrypted := fAES[true].EncryptPKCS7(aPlain,{IVAtBeginning=}true); finally fSafe.UnLock; end; @@ -14500,10 +15129,8 @@ begin if N=nil then exit; V := GetJSONFieldOrObjectOrArray(P,@wasstring,@EndOfObject,true); - if V=nil then - exit; len := StrLen(N); - if len=3 then begin + if (len=3) and (V<>nil) then begin c := PInteger(N)^; for claim := low(claim) to high(claim) do if PInteger(JWT_CLAIMS_TEXT[claim])^=c then begin @@ -14560,7 +15187,7 @@ begin if JWT.data.Count=0 then JWT.data.Capacity := cap; JWT.data.AddValue(N,len,value) - until EndOfObject='}'; + until (EndOfObject='}') or (P=nil); if JWT.data.Count>0 then JWT.data.Capacity := JWT.data.Count; if requiredclaims-JWT.claims<>[] then diff --git a/contrib/mORMot/SynCurl.pas b/contrib/mORMot/SynCurl.pas index f850291..1aa931d 100644 --- a/contrib/mORMot/SynCurl.pas +++ b/contrib/mORMot/SynCurl.pas @@ -6,7 +6,7 @@ unit SynCurl; { This file is part of Synopse framework. - Synopse framework. Copyright (C) 2020 Arnaud Bouchez + Synopse framework. Copyright (C) 2022 Arnaud Bouchez Synopse Informatique - https://synopse.info *** BEGIN LICENSE BLOCK ***** @@ -25,7 +25,7 @@ unit SynCurl; The Initial Developer of the Original Code is Arnaud Bouchez. - Portions created by the Initial Developer are Copyright (C) 2020 + Portions created by the Initial Developer are Copyright (C) 2022 the Initial Developer. All Rights Reserved. Contributor(s): @@ -147,6 +147,8 @@ type coFTPSSLAuth = 129, coIgnoreContentLength = 136, coFTPSkipPasvIp = 137, + coTimeoutMs = 155, // since libcurl 7.16.2 - April 11 2007 + coConnectTimeoutMs = 156, coFile = 10001, coWriteData = coFile, coURL = 10002, @@ -243,6 +245,22 @@ type crTFTPDiskFull, crTFTPIllegal, crTFTPUnknownID, crTFTPExists, crTFTPNoSuchUser ); + CURLSHcode = (CURLSHE_OK, // all is fine + CURLSHE_BAD_OPTION, // 1 + CURLSHE_IN_USE, // 2 + CURLSHE_INVALID, // 3 + CURLSHE_NOMEM, // 4 out of memory + CURLSHE_NOT_BUILT_IN, // 5 feature not present in lib + CURLSHE_LAST); // never use + + CURLSHoption = (CURLSHOPT_NONE, + CURLSHOPT_SHARE, + CURLSHOPT_UNSHARE, + CURLSHOPT_LOCKFUNC, + CURLSHOPT_UNLOCKFUNC, + CURLSHOPT_USERDATA, + CURLSHOPT_LAST); + /// low-level information enumeration for libcurl library API calls TCurlInfo = ( ciNone, @@ -357,6 +375,7 @@ type /// low-level string list type for libcurl library API TCurlSList = type pointer; PCurlSList = ^TCurlSList; + TCurlShare = type pointer; PPCurlSListArray = ^PCurlSListArray; PCurlSListArray = array[0..(MaxInt div SizeOf(PCurlSList))-1] of PCurlSList; /// low-level access to the libcurl library instance in "multi" mode @@ -400,6 +419,26 @@ type curl_read_callback = function (buffer: PAnsiChar; size,nitems: integer; instream: pointer): integer; cdecl; + curl_lock_data = (CURL_LOCK_DATA_NONE = 0, + CURL_LOCK_DATA_SHARE, + CURL_LOCK_DATA_COOKIE, + CURL_LOCK_DATA_DNS, + CURL_LOCK_DATA_SSL_SESSION, + CURL_LOCK_DATA_CONNECT, + CURL_LOCK_DATA_PSL, + CURL_LOCK_DATA_LAST); + + curl_lock_access = (CURL_LOCK_ACCESS_NONE = 0, + CURL_LOCK_ACCESS_SHARED = 1, + CURL_LOCK_ACCESS_SINGLE = 2, + CURL_LOCK_ACCESS_LAST); + + /// lock function signature for CURLSHOPT_LOCKFUNC + curl_lock_function = procedure (handle: TCurl; data: curl_lock_data; + locktype: curl_lock_access; userptr: pointer); cdecl; + /// unlock function signature for CURLSHOPT_UNLOCKFUNC + curl_unlock_function = procedure (handle: TCurl; data: curl_lock_data; + userptr: pointer); cdecl; {$Z1} @@ -417,6 +456,11 @@ var {$else} Module: THandle; {$endif FPC} + /// in case CurlEnableShare is called this array holds a + // critical section per curl_lock_data + share_cs: array[curl_lock_data] of TRTLCriticalSection; + /// global TCurlShare object, created by CurlEnableGlobalShare + globalShare: TCurlShare; /// initialize the library global_init: function(flags: TCurlGlobalInit): TCurlResult; cdecl; /// finalize the library @@ -443,7 +487,15 @@ var slist_append: function(list: TCurlSList; s: PAnsiChar): TCurlSList; cdecl; /// free an entire slist slist_free_all: procedure(list: TCurlSList); cdecl; - {$ifdef LIBCURLMULTI} + /// create a shared object + share_init: function: pointer; cdecl; + /// clean up a shared object + share_cleanup: function(share_handle: TCurlShare): CURLSHcode; cdecl; + /// set options for a shared object + share_setopt: function(share: TCurlShare; option: CURLSHoption): CURLSHcode; cdecl varargs; + /// return the text description of an error code + share_strerror: function(code: CURLSHcode): PAnsiChar; cdecl; + {$ifdef LIBCURLMULTI} 12 /// add an easy handle to a multi session multi_add_handle: function(mcurl: TCurlMulti; curl: TCurl): TCurlMultiCode; cdecl; /// set data to associate with an internal socket @@ -498,6 +550,22 @@ function CurlIsAvailable: boolean; function CurlWriteRawByteString(buffer: PAnsiChar; size,nitems: integer; opaque: pointer): integer; cdecl; +/// enable libcurl multiple easy handles to share data +// - is called automatically during libcurl initialization +// - shared objects are: DNS cache, TLS session cache and connection cache +// - this way, each single transfer can take advantage of the context of the +// other transfer(s) +// - do nothing if the global share has already been enabled +// - see https://curl.se/libcurl/c/libcurl-share.html for details +function CurlEnableGlobalShare: boolean; + +/// disable a global share for libcurl +// - is called automatically in finalization section +// - can be called on purpose, to ensure there is no active HTTP requests +// and prevent CURLSHE_IN_USE error +// - you can re-enable the libcurl global share by CurlEnableGlobalShare +function CurlDisableGlobalShare: CURLSHcode; + implementation {$ifdef LIBCURLSTATIC} @@ -538,6 +606,14 @@ implementation function curl_slist_append(list: TCurlSList; s: PAnsiChar): TCurlSList; cdecl; external; /// free an entire slist procedure curl_slist_free_all(list: TCurlSList); cdecl; external; + /// create a shared object + function curl_share_init: pointer; cdecl; external; + /// clean up a shared object + function curl_share_cleanup(share_handle: TCurlShare): CURLSHcode; cdecl; external; + /// set options for a shared object + function curl_share_setopt(share: TCurlShare; option: CURLSHoption): CURLSHcode; cdecl varargs; external; + /// return string describing error code + function curl_share_strerror(code: CURLSHcode): PAnsiChar; cdecl; external; {$ifdef LIBCURLMULTI} /// add an easy handle to a multi session function curl_multi_add_handle(mcurl: TCurlMulti; curl: TCurl): TCurlMultiCode; cdecl; external; @@ -615,10 +691,11 @@ procedure LibCurlInitialize(engines: TCurlGlobalInit; const dllname: TFileName); var P: PPointer; api: integer; h: {$ifdef FPC}TLibHandle{$else}THandle{$endif FPC}; -const NAMES: array[0..{$ifdef LIBCURLMULTI}26{$else}12{$endif}] of string = ( +const NAMES: array[0..{$ifdef LIBCURLMULTI}30{$else}16{$endif}] of string = ( 'global_init','global_cleanup','version_info', 'easy_init','easy_setopt','easy_perform','easy_cleanup','easy_getinfo', - 'easy_duphandle','easy_reset','easy_strerror','slist_append','slist_free_all' + 'easy_duphandle','easy_reset','easy_strerror','slist_append','slist_free_all', + 'share_init', 'share_cleanup','share_setopt', 'share_strerror' {$ifdef LIBCURLMULTI}, 'multi_add_handle','multi_assign','multi_cleanup','multi_fdset', 'multi_info_read','multi_init','multi_perform','multi_remove_handle', @@ -644,6 +721,10 @@ begin curl.easy_strerror := @curl_easy_strerror; curl.slist_append := @curl_slist_append; curl.slist_free_all := @curl_slist_free_all; + curl.share_init := @curl_share_init; + curl.share_cleanup := @curl_share_cleanup; + curl.share_setopt := @curl_share_setopt; + curl.share_strerror := @curl_share_strerror; {$ifdef LIBCURLMULTI} curl.multi_add_handle := @curl_multi_add_handle; curl.multi_assign := @curl_multi_assign; @@ -714,6 +795,8 @@ begin curl.infoText := format('%s version %s',[LIBCURL_DLL,curl.info.version]); if curl.info.ssl_version<>nil then curl.infoText := format('%s using %s',[curl.infoText,curl.info.ssl_version]); + curl.globalShare := nil; + CurlEnableGlobalShare; // won't hurt, and may benefit even for the OS // api := 0; with curl.info do while protocols[api]<>nil do begin // write(protocols[api], ' '); inc(api); end; writeln(#13#10,curl.infoText); finally @@ -721,6 +804,55 @@ begin end; end; +procedure curlShareLock(handle: TCurl; data: curl_lock_data; + locktype: curl_lock_access; userptr: pointer); cdecl; +begin + EnterCriticalSection(curl.share_cs[data]); +end; + +procedure curlShareUnLock(handle: TCurl; data: curl_lock_data; + userptr: pointer); cdecl; +begin + LeaveCriticalSection(curl.share_cs[data]); +end; + +function CurlEnableGlobalShare: boolean; +var + d: curl_lock_data; +begin + result := false; + if not CurlIsAvailable or (curl.globalShare<>nil) then + exit; // not available, or already shared + curl.globalShare := curl.share_init; + if curl.globalShare = nil then + exit; // something went wrong (out of memory, etc.) and therefore the share object was not created + for d := low(d) to high(d) do + InitializeCriticalSection(curl.share_cs[d]); + curl.share_setopt(curl.globalShare,CURLSHOPT_LOCKFUNC,@curlShareLock); + curl.share_setopt(curl.globalShare,CURLSHOPT_UNLOCKFUNC,@curlShareUnLock); + curl.share_setopt(curl.globalShare,CURLSHOPT_SHARE,CURL_LOCK_DATA_DNS); + curl.share_setopt(curl.globalShare,CURLSHOPT_SHARE,CURL_LOCK_DATA_SSL_SESSION); + // CURL_LOCK_DATA_CONNECT triggers GPF e.g. on Debian Burster 10 + if curl.info.version_num>=$00074400 then // seems to be fixed in 7.68 + // see https://github.com/curl/curl/issues/4544 + curl.share_setopt(curl.globalShare,CURLSHOPT_SHARE,CURL_LOCK_DATA_CONNECT); + // CURL_LOCK_DATA_CONNECT triggers GPF on Debian Burster 10 + result := true; +end; + +function CurlDisableGlobalShare: CURLSHcode; +var + d: curl_lock_data; +begin + result := CURLSHE_OK; + if curl.globalShare = nil then + exit; // already disabled + result := curl.share_cleanup(curl.globalShare); + if result = CURLSHE_OK then + curl.globalShare := nil; + for d := low(d) to high(d) do + DeleteCriticalSection(curl.share_cs[d]); +end; initialization {$ifdef LIBCURLSTATIC} @@ -729,10 +861,13 @@ initialization finalization {$ifdef LIBCURLSTATIC} - if curl_static_initialized then + if curl_static_initialized then begin + CurlDisableGlobalShare; curl.global_cleanup; + end; {$else} if PtrInt(curl.Module)>0 then begin + CurlDisableGlobalShare; curl.global_cleanup; FreeLibrary(curl.Module); end; diff --git a/contrib/mORMot/SynDB.pas b/contrib/mORMot/SynDB.pas index 5f20ad7..e5cbc41 100644 --- a/contrib/mORMot/SynDB.pas +++ b/contrib/mORMot/SynDB.pas @@ -6,7 +6,7 @@ unit SynDB; { This file is part of Synopse framework. - Synopse framework. Copyright (C) 2020 Arnaud Bouchez + Synopse framework. Copyright (C) 2022 Arnaud Bouchez Synopse Informatique - https://synopse.info *** BEGIN LICENSE BLOCK ***** @@ -25,7 +25,7 @@ unit SynDB; The Initial Developer of the Original Code is Arnaud Bouchez. - Portions created by the Initial Developer are Copyright (C) 2020 + Portions created by the Initial Developer are Copyright (C) 2022 the Initial Developer. All Rights Reserved. Contributor(s): @@ -903,6 +903,7 @@ type {$ifndef UNICODE} fVariantWideString: boolean; {$endif} + fStatementMaxMemory: Int64; fForeignKeys: TSynNameValue; fSQLCreateField: TSQLDBFieldTypeDefinition; fSQLCreateFieldMax: cardinal; @@ -1433,6 +1434,12 @@ type // - will cache only statements containing ? parameters or a SELECT with no // WHERE clause within property UseCache: boolean read fUseCache write fUseCache; + /// maximum bytes allowed for FetchAllToJSON/FetchAllToBinary methods + // - if a result set exceeds this limit, an ESQLDBException is raised + // - default is 512 shl 20, i.e. 512MB which is very high + // - avoid unexpected OutOfMemory errors when incorrect statement is run + property StatementMaxMemory: Int64 + read fStatementMaxMemory write fStatementMaxMemory; /// if UseCache is true, how many statement replicates can be generated // if the cached ISQLDBStatement is already used // - such replication is normally not needed in a per-thread connection, @@ -1632,7 +1639,7 @@ type // corresponding TSQLDBProxyConnectionCommand on the current connection procedure RemoteProcessMessage(const Input: RawByteString; out Output: RawByteString; Protocol: TSQLDBProxyConnectionProtocol); virtual; - {$endif} + {$endif WITH_PROXY} /// the current Date and Time, as retrieved from the server // - note that this value is the DB_SERVERTIME[] constant SQL value, so @@ -1708,6 +1715,7 @@ type fSQLLogTimer: TPrecisionTimer; fCacheIndex: integer; fSQLPrepared: RawUTF8; + function GetSQLCurrent: RawUTF8; function GetSQLWithInlinedParams: RawUTF8; procedure ComputeSQLWithInlinedParams; function GetForceBlobAsNull: boolean; @@ -2189,10 +2197,15 @@ type /// low-level access to the Timer used for last DB operation property SQLLogTimer: TPrecisionTimer read fSQLLogTimer; /// after a call to Prepare(), contains the query text to be passed to the DB - // - Depends on DB parameters placeholder are replaced to ?, :AA, $1 etc + // - depending on the DB, parameters placeholders are replaced by ?, :1, $1 etc // - this SQL is ready to be used in any DB tool, e.g. to check the real // execution plan/timing property SQLPrepared: RawUTF8 read fSQLPrepared; + /// the prepared SQL statement, in its current state + // - if statement is prepared, then equals SQLPrepared, otherwise, contains + // the raw SQL property content + // - used internally by the implementation units, e.g. for errors logging + property SQLCurrent: RawUTF8 read GetSQLCurrent; /// low-level access to the statement cache index, after a call to Prepare() // - contains >= 0 if the database supports prepared statement cache //(Oracle, Postgres) and query plan is cached; contains -1 in other cases @@ -2237,7 +2250,9 @@ type // when multiple connections may break stability, consume too much resources // and/or decrease performance TSQLDBConnectionPropertiesThreadSafeThreadingMode = ( - tmThreadPool, tmMainConnection, tmBackgroundThread); + tmThreadPool, + tmMainConnection, + tmBackgroundThread); /// connection properties which will implement an internal Thread-Safe // connection pool @@ -2843,7 +2858,9 @@ function TrimLeftSchema(const TableName: RawUTF8): RawUTF8; // - returns the number of ? parameters found within aSQL // - won't generate any SQL keyword parameters (e.g. :AS :OF :BY), to be // compliant with Oracle OCI expectations -function ReplaceParamsByNames(const aSQL: RawUTF8; var aNewSQL: RawUTF8): integer; +// - any ending ';' character is deleted, unless aStripSemicolon is unset +function ReplaceParamsByNames(const aSQL: RawUTF8; var aNewSQL: RawUTF8; + aStripSemicolon: boolean=true): integer; /// replace all '?' in the SQL statement with indexed parameters like $1 $2 ... // - returns the number of ? parameters found within aSQL @@ -3292,7 +3309,7 @@ const (Position: posAfter; InsertFmt:' first % ')); { dInformix } /// the known database engines handling CREATE INDEX IF NOT EXISTS statement - DB_HANDLECREATEINDEXIFNOTEXISTS = [dSQLite]; + DB_HANDLECREATEINDEXIFNOTEXISTS = [dSQLite, dPostgreSQL]; /// the known database engines handling CREATE INDEX on BLOB columns // - SQLite3 does not have any issue about indexing any column @@ -3328,7 +3345,7 @@ function TSQLDBFieldTypeToString(aType: TSQLDBFieldType): TShort16; /// retrieve the ready-to-be displayed text of proxy commands implemented by // TSQLDBProxyConnectionProperties.Process() function ToText(cmd: TSQLDBProxyConnectionCommand): PShortString; overload; -{$endif} +{$endif WITH_PROXY} implementation @@ -3357,13 +3374,6 @@ begin FormatShort16('#%',[ord(aType)],result); end; -function OracleSQLIso8601ToDate(Iso8601: RawUTF8): RawUTF8; -begin - if (length(Iso8601)>10) and (Iso8601[11]='T') then - Iso8601[11] := ' '; // 'T' -> ' ' - result := 'to_date('''+Iso8601+''',''YYYY-MM-DD HH24:MI:SS'')'; // from Iso8601 -end; - {$ifdef EMULATES_TQUERY} @@ -3425,12 +3435,12 @@ begin with TVarData(fValue) do case VType of varNull: result := ''; - varInt64: result := UTF8ToSynUnicode(Int64ToUtf8(VInt64)); - varString: result := UTF8ToSynUnicode(RawUTF8(VAny)); + varInt64: UTF8ToSynUnicode(Int64ToUtf8(VInt64),result); + varString: UTF8ToSynUnicode(RawUTF8(VAny),result); {$ifdef HASVARUSTRING} varUString: result := UnicodeString(VAny); {$endif} - else Result := SynUnicode(fValue); + else result := SynUnicode(fValue); end; end; @@ -3647,7 +3657,10 @@ end; procedure TQuery.Close; begin try - fPrepared := nil; + if Assigned(fPrepared) then begin + fPrepared.ReleaseRows; + fPrepared := nil; + end; finally //fSQL.Clear; // original TQuery expect SQL content to be preserved fParam.Clear; @@ -3878,7 +3891,7 @@ begin req := Trim(StringToUTF8(SQL.Text)); P := pointer(req); if P=nil then - ESQLQueryException.Create('No SQL statement'); + raise ESQLQueryException.Create('No SQL statement'); col := 0; repeat B := P; @@ -4520,7 +4533,7 @@ begin // follow TSQLDBRemoteConnectionPropertiesAbstract.Process binary layout if ExecuteWithResults then begin Data := TRawByteStringStream.Create(msgOutput); try - Data.Seek(0,soFromEnd); // include header + Data.Seek(0,soEnd); // include header case header.Command of cExecuteToBinary: Stmt.FetchAllToBinary(Data); @@ -4571,6 +4584,7 @@ begin fRollbackOnDisconnect := true; // enabled by default fUseCache := true; fLoggedSQLMaxSize := 2048; // log up to 2KB of inlined SQL by default + fStatementMaxMemory := 512 shl 20; // fetch to JSON/Binary up to 512MB SetInternalProperties; // virtual method used to override default parameters aDBMS := GetDBMS; if aDBMS in [dSQLite, dDB2, dPostgreSQL] then // for SQLDateToIso8601Quoted() @@ -4615,8 +4629,8 @@ end; function TSQLDBConnectionProperties.Execute(const aSQL: RawUTF8; const Params: array of const - {$ifndef LVCL}{$ifndef DELPHI5OROLDER}; RowsVariant: PVariant=nil{$endif}{$endif}; - ForceBlobAsNull: boolean=false): ISQLDBRows; + {$ifndef LVCL}{$ifndef DELPHI5OROLDER}; RowsVariant: PVariant{$endif}{$endif}; + ForceBlobAsNull: boolean): ISQLDBRows; var Stmt: ISQLDBStatement; begin Stmt := NewThreadSafeStatementPrepared(aSQL,true,true); @@ -5723,21 +5737,17 @@ function TSQLDBConnectionProperties.SQLIso8601ToDate(const Iso8601: RawUTF8): Ra begin case DBMS of dSQLite: result := TrimTInIso; - dOracle: result := OracleSQLIso8601ToDate(Iso8601); + dOracle: result := 'to_date('''+TrimTInIso+''',''YYYY-MM-DD HH24:MI:SS'')'; dNexusDB: result := 'DATE '+Iso8601; dDB2: result := 'TIMESTAMP '''+TrimTInIso+''''; + dPostgreSQL: result := ''''+TrimTInIso+''''; else result := ''''+Iso8601+''''; end; end; function TSQLDBConnectionProperties.SQLDateToIso8601Quoted(DateTime: TDateTime): RawUTF8; -var tmp: array[0..23] of AnsiChar; - P: PUTF8Char; begin - tmp[0] := ''''; - P := DateTimeToIso8601ExpandedPChar(DateTime,@tmp[1],DateTimeFirstChar); - P^ := ''''; - FastSetString(result,@tmp,PtrUInt(P)-PtrUInt(@tmp)+1); + result := DateTimeToIso8601(DateTime,true,DateTimeFirstChar,false,''''); end; function TSQLDBConnectionProperties.SQLCreate(const aTableName: RawUTF8; @@ -6974,12 +6984,14 @@ end; function TSQLDBStatement.FetchAllToJSON(JSON: TStream; Expanded: boolean): PtrInt; var W: TJSONWriter; col: integer; + maxmem: PtrUInt; tmp: TTextWriterStackBuffer; begin result := 0; W := TJSONWriter.Create(JSON,Expanded,false,nil,0,@tmp); try Connection.InternalProcess(speActive); + maxmem := Connection.Properties.StatementMaxMemory; // get col names and types SetLength(W.ColNames,ColumnCount); for col := 0 to ColumnCount-1 do @@ -6995,6 +7007,9 @@ begin ColumnsToJSON(W); W.Add(','); inc(result); + if (maxmem>0) and (W.WrittenBytes>maxmem) then // TextLength is slower + raise ESQLDBException.CreateUTF8('%.FetchAllToJSON: overflow %', + [self, KB(maxmem)]); end; {$ifdef SYNDB_SILENCE} fSQLLogTimer.Pause; @@ -7020,6 +7035,7 @@ function TSQLDBStatement.FetchAllToCSVValues(Dest: TStream; Tab: boolean; const NULL: array[boolean] of string[7] = ('"null"','null'); BLOB: array[boolean] of string[7] = ('"blob"','blob'); var F, FMax: integer; + maxmem: PtrUInt; W: TTextWriter; tmp: RawByteString; V: TSQLVar; @@ -7031,6 +7047,7 @@ begin if Tab then CommaSep := #9; FMax := ColumnCount-1; + maxmem := Connection.Properties.StatementMaxMemory; W := TTextWriter.Create(Dest,65536); try if AddBOM then @@ -7083,6 +7100,9 @@ begin W.Add(CommaSep); end; inc(result); + if (maxmem>0) and (W.WrittenBytes>maxmem) then // TextLength is slower + raise ESQLDBException.CreateUTF8('%.FetchAllToCSVValues: overflow %', + [self, KB(maxmem)]); end; {$ifdef SYNDB_SILENCE} fSQLLogTimer.Pause; @@ -7157,13 +7177,14 @@ const function TSQLDBStatement.FetchAllToBinary(Dest: TStream; MaxRowCount: cardinal; DataRowPosition: PCardinalDynArray): cardinal; var F, FMax, FieldSize, NullRowSize: integer; - StartPos: Int64; + StartPos, MaxMem: Int64; W: TFileBufferWriter; ft: TSQLDBFieldType; ColTypes: TSQLDBFieldTypeDynArray; Null: TByteDynArray; begin result := 0; + MaxMem := Connection.Properties.StatementMaxMemory; W := TFileBufferWriter.Create(Dest); try W.WriteVarUInt32(FETCHALLTOBINARY_MAGIC); @@ -7213,6 +7234,9 @@ begin // then write data values ColumnsToBinary(W,pointer(Null),ColTypes); inc(result); + if (MaxMem>0) and (W.TotalWritten>MaxMem) then // Stream.Position is slower + raise ESQLDBException.CreateUTF8('%.FetchAllToBinary: overflow %', + [self, KB(MaxMem)]); if (MaxRowCount>0) and (result>=MaxRowCount) then break; until not Step; @@ -7405,6 +7429,13 @@ begin result := SQLLogEnd(@tmp); end; +function TSQLDBStatement.GetSQLCurrent: RawUTF8; +begin + if fSQLPrepared <> '' then + Result := fSQLPrepared else + Result := fSQL; +end; + function TSQLDBStatement.GetSQLWithInlinedParams: RawUTF8; begin if fSQL='' then @@ -7829,7 +7860,7 @@ end; procedure TSQLDBStatementWithParams.BindArray(Param: Integer; const Values: array of double); -var i: integer; +var i: PtrInt; begin with CheckParam(Param,ftDouble,paramIn,length(Values))^ do for i := 0 to high(Values) do @@ -7838,7 +7869,7 @@ end; procedure TSQLDBStatementWithParams.BindArray(Param: Integer; const Values: array of Int64); -var i: integer; +var i: PtrInt; begin with CheckParam(Param,ftInt64,paramIn,length(Values))^ do for i := 0 to high(Values) do @@ -7847,27 +7878,29 @@ end; procedure TSQLDBStatementWithParams.BindArray(Param: Integer; ParamType: TSQLDBFieldType; const Values: TRawUTF8DynArray; ValuesCount: integer); -var i: integer; +var i: PtrInt; ChangeFirstChar: AnsiChar; + p: PSQLDBParam; begin inherited; // raise an exception in case of invalid parameter if fConnection=nil then ChangeFirstChar := 'T' else ChangeFirstChar := Connection.Properties.DateTimeFirstChar; - with CheckParam(Param,ParamType,paramIn)^ do begin - VArray := Values; // immediate COW reference-counted assignment - if (ParamType=ftDate) and (ChangeFirstChar<>'T') then - for i := 0 to ValuesCount-1 do // fix e.g. for PostgreSQL - if (length(Values[i])>11) and (Values[i][12]='T') then - Values[i][12] := ChangeFirstChar; // [12] since quoted 'dateTtime' - VInt64 := ValuesCount; - end; + p := CheckParam(Param,ParamType,paramIn); + p^.VInt64 := ValuesCount; + p^.VArray := Values; // immediate COW reference-counted assignment + if (ParamType=ftDate) and (ChangeFirstChar<>'T') then + for i := 0 to ValuesCount-1 do // fix e.g. for PostgreSQL + if (p^.VArray[i]<>'') and (p^.VArray[i][1]='''') then + // not only replace 'T'->ChangeFirstChar, but force expanded format + DateTimeToIso8601(Iso8601ToDateTime(p^.VArray[i]), + {expanded=}true, ChangeFirstChar, {ms=}fForceDateWithMS, ''''); fParamsArrayCount := ValuesCount; end; procedure TSQLDBStatementWithParams.BindArray(Param: Integer; const Values: array of RawUTF8); -var i: integer; +var i: PtrInt; StoreVoidStringAsNull: boolean; begin StoreVoidStringAsNull := (fConnection<>nil) and @@ -7881,7 +7914,7 @@ end; procedure TSQLDBStatementWithParams.BindArrayCurrency(Param: Integer; const Values: array of currency); -var i: integer; +var i: PtrInt; begin with CheckParam(Param,ftCurrency,paramIn,length(Values))^ do for i := 0 to high(Values) do @@ -7890,7 +7923,7 @@ end; procedure TSQLDBStatementWithParams.BindArrayDateTime(Param: Integer; const Values: array of TDateTime); -var i: integer; +var i: PtrInt; begin with CheckParam(Param,ftDate,paramIn,length(Values))^ do for i := 0 to high(Values) do @@ -7899,7 +7932,7 @@ end; procedure TSQLDBStatementWithParams.BindArrayRowPrepare( const aParamTypes: array of TSQLDBFieldType; aExpectedMinimalRowCount: integer); -var i: integer; +var i: PtrInt; begin fParam.Count := 0; for i := 0 to high(aParamTypes) do @@ -7908,7 +7941,7 @@ begin end; procedure TSQLDBStatementWithParams.BindArrayRow(const aValues: array of const); -var i: integer; +var i: PtrInt; begin if length(aValues)<>fParamCount then raise ESQLDBException.CreateFmt('Invalid %.BindArrayRow call',[self]); @@ -7936,7 +7969,7 @@ begin end; procedure TSQLDBStatementWithParams.BindFromRows(Rows: TSQLDBStatement); -var F: integer; +var F: PtrInt; U: RawUTF8; begin if Rows<>nil then @@ -8047,7 +8080,8 @@ begin result := copy(TableName,j,maxInt); end; -function ReplaceParamsByNames(const aSQL: RawUTF8; var aNewSQL: RawUTF8): integer; +function ReplaceParamsByNames(const aSQL: RawUTF8; var aNewSQL: RawUTF8; + aStripSemicolon: boolean): integer; var i,j,B,L: PtrInt; P: PAnsiChar; c: array[0..3] of AnsiChar; @@ -8056,10 +8090,11 @@ const SQL_KEYWORDS: array[0..19] of AnsiChar = 'ASATBYIFINISOFONORTO'; begin result := 0; L := Length(aSQL); - while (L>0) and (aSQL[L] in [#1..' ',';']) do - if (aSQL[L]=';') and (L>5) and IdemPChar(@aSQL[L-3],'END') then - break else // allows 'END;' at the end of a statement - dec(L); // trim ' ' or ';' right (last ';' could be found incorrect) + if aStripSemicolon then + while (L>0) and (aSQL[L] in [#1..' ',';']) do + if (aSQL[L]=';') and (L>5) and IdemPChar(@aSQL[L-3],'END') then + break else // allows 'END;' at the end of a statement + dec(L); // trim ' ' or ';' right (last ';' could be found incorrect) if PosExChar('?',aSQL)>0 then begin aNewSQL:= ''; // change ? into :AA :BA .. diff --git a/contrib/mORMot/SynDBDataset.pas b/contrib/mORMot/SynDBDataset.pas index d448a30..4ddb509 100644 --- a/contrib/mORMot/SynDBDataset.pas +++ b/contrib/mORMot/SynDBDataset.pas @@ -1,12 +1,12 @@ /// DB.pas TDataset-based direct access classes (abstract TQuery-like) // - this unit is a part of the freeware Synopse framework, // licensed under a MPL/GPL/LGPL tri-license; version 1.18 -unit SynDBDataset; +unit SynDBDataset; { This file is part of Synopse framework. - Synopse framework. Copyright (C) 2020 Arnaud Bouchez + Synopse framework. Copyright (C) 2022 Arnaud Bouchez Synopse Informatique - https://synopse.info *** BEGIN LICENSE BLOCK ***** @@ -25,7 +25,7 @@ unit SynDBDataset; The Initial Developer of the Original Code is Arnaud Bouchez. - Portions created by the Initial Developer are Copyright (C) 2020 + Portions created by the Initial Developer are Copyright (C) 2022 the Initial Developer. All Rights Reserved. Contributor(s): diff --git a/contrib/mORMot/SynDBDataset/SynDBBDE.pas b/contrib/mORMot/SynDBDataset/SynDBBDE.pas index 565377c..6162da5 100644 --- a/contrib/mORMot/SynDBDataset/SynDBBDE.pas +++ b/contrib/mORMot/SynDBDataset/SynDBBDE.pas @@ -6,7 +6,7 @@ unit SynDBBDE; { This file is part of Synopse framework. - Synopse framework. Copyright (C) 2020 Arnaud Bouchez + Synopse framework. Copyright (C) 2022 Arnaud Bouchez Synopse Informatique - https://synopse.info *** BEGIN LICENSE BLOCK ***** @@ -25,7 +25,7 @@ unit SynDBBDE; The Initial Developer of the Original Code is Arnaud Bouchez. - Portions created by the Initial Developer are Copyright (C) 2020 + Portions created by the Initial Developer are Copyright (C) 2022 the Initial Developer. All Rights Reserved. Contributor(s): diff --git a/contrib/mORMot/SynDBDataset/SynDBFireDAC.pas b/contrib/mORMot/SynDBDataset/SynDBFireDAC.pas index 83d552b..8786597 100644 --- a/contrib/mORMot/SynDBDataset/SynDBFireDAC.pas +++ b/contrib/mORMot/SynDBDataset/SynDBFireDAC.pas @@ -6,7 +6,7 @@ unit SynDBFireDAC; { This file is part of Synopse framework. - Synopse framework. Copyright (C) 2020 Arnaud Bouchez + Synopse framework. Copyright (C) 2022 Arnaud Bouchez Synopse Informatique - https://synopse.info *** BEGIN LICENSE BLOCK ***** @@ -25,7 +25,7 @@ unit SynDBFireDAC; The Initial Developer of the Original Code is Arnaud Bouchez. - Portions created by the Initial Developer are Copyright (C) 2020 + Portions created by the Initial Developer are Copyright (C) 2022 the Initial Developer. All Rights Reserved. Contributor(s): @@ -209,6 +209,8 @@ const FIREDAC_PROVIDER: array[dOracle..high(TSQLDBDefinition)] of RawUTF8 = ( 'Ora','MSSQL','MSAcc','MySQL','SQLite','IB','','PG','DB2','Infx'); {$endif} + + implementation uses @@ -425,6 +427,7 @@ begin inherited Create(aProperties); fDatabase := TADConnection.Create(nil); fDatabase.ResourceOptions.SilentMode := True; // no need for wait cursor + fDatabase.LoginPrompt := false; fDatabase.Params.Text := (fProperties as TSQLDBFireDACConnectionProperties).fFireDACOptions.Text; end; diff --git a/contrib/mORMot/SynDBDataset/SynDBNexusDB.pas b/contrib/mORMot/SynDBDataset/SynDBNexusDB.pas index 4bd6ebe..d627442 100644 --- a/contrib/mORMot/SynDBDataset/SynDBNexusDB.pas +++ b/contrib/mORMot/SynDBDataset/SynDBNexusDB.pas @@ -6,7 +6,7 @@ unit SynDBNexusDB; { This file is part of Synopse framework. - Synopse framework. Copyright (C) 2020 Arnaud Bouchez + Synopse framework. Copyright (C) 2022 Arnaud Bouchez Synopse Informatique - https://synopse.info *** BEGIN LICENSE BLOCK ***** @@ -25,7 +25,7 @@ unit SynDBNexusDB; The Initial Developer of the Original Code is Arnaud Bouchez. - Portions created by the Initial Developer are Copyright (C) 2020 + Portions created by the Initial Developer are Copyright (C) 2022 the Initial Developer. All Rights Reserved. Contributor(s): @@ -227,7 +227,6 @@ function DropNexusEmbeddedEngine: TnxServerEngine; implementation uses - {$ifdef SYNDB_FULLNEXUSDB} nxreRemoteServerEngine, uMiscellaneous, diff --git a/contrib/mORMot/SynDBDataset/SynDBUniDAC.pas b/contrib/mORMot/SynDBDataset/SynDBUniDAC.pas index e1d7c83..bbb7908 100644 --- a/contrib/mORMot/SynDBDataset/SynDBUniDAC.pas +++ b/contrib/mORMot/SynDBDataset/SynDBUniDAC.pas @@ -6,7 +6,7 @@ unit SynDBUniDAC; { This file is part of Synopse framework. - Synopse framework. Copyright (C) 2020 Arnaud Bouchez + Synopse framework. Copyright (C) 2022 Arnaud Bouchez Synopse Informatique - https://synopse.info *** BEGIN LICENSE BLOCK ***** @@ -25,7 +25,7 @@ unit SynDBUniDAC; The Initial Developer of the Original Code is Arnaud Bouchez. - Portions created by the Initial Developer are Copyright (C) 2020 + Portions created by the Initial Developer are Copyright (C) 2022 the Initial Developer. All Rights Reserved. Contributor(s): @@ -63,9 +63,11 @@ uses Classes, Contnrs, SynCommons, + SynTable, SynLog, SynDB, SynDBDataset, + SynOleDB, // for CoInit/CoUnInit Uni, UniProvider, UniScript; @@ -173,6 +175,7 @@ type /// implements a statement via a UniDAC connection TSQLDBUniDACStatement = class(TSQLDBDatasetStatement) protected + fBatchExecute: Boolean; /// initialize and set fQuery: TUniQuery internal field as expected procedure DatasetCreate; override; /// set fQueryParams internal field as expected @@ -211,6 +214,7 @@ begin break; end; inherited Create(provider,aDatabaseName,aUserID,aPassWord); + fOnBatchInsert := nil; // MultipleValuesInsert is slower fSpecificOptions := TStringList.Create; opt := pointer(options); while opt<>nil do begin @@ -220,37 +224,71 @@ begin end; case fDBMS of dSQLite: begin // UniDAC support of SQLite3 is just buggy + {$ifdef FPC} + fSpecificOptions.Values['UseUnicode'] := 'true'; // FPC strings do like UTF8 + {$else} + {$ifndef UNICODE} + fForceUseWideString := true; // for non-unicode Delphi + {$endif} + {$endif} fSpecificOptions.Values['ForceCreateDatabase'] := 'true'; fSQLCreateField[ftInt64] := ' BIGINT'; // SQLite3 INTEGER = 32bit for UniDAC end; dFirebird: begin + {$ifdef FPC} + fSpecificOptions.Values['UseUnicode'] := 'true'; // FPC strings do like UTF8 + {$else} {$ifndef UNICODE} - fForceUseWideString := true; + fForceUseWideString := true; // for non-unicode Delphi + {$endif} {$endif} fSpecificOptions.Values['CharSet'] := 'UTF8'; - fSpecificOptions.Values['UseUnicode'] := 'true'; fSpecificOptions.Values['CharLength'] := '2'; fSpecificOptions.Values['DescribeParams'] := 'true'; end; // http://www.devart.com/unidac/docs/index.html?ibprov_article.htm - dOracle: begin - fSpecificOptions.Values['UseUnicode'] := 'true'; - fSpecificOptions.Values['Direct'] := 'true'; - fSpecificOptions.Values['HOMENAME'] := ''; - end; - dMySQL: begin - // s.d. 30.11.19 Damit der Connect schneller geht ! CRVioTCP.pas WaitForConnect - fSpecificOptions.Values['MySQL.ConnectionTimeout'] := '0'; - end; + dOracle: begin + {$ifdef FPC} + fSpecificOptions.Values['UseUnicode'] := 'true'; // FPC strings do like UTF8 + {$else} + {$ifndef UNICODE} + fForceUseWideString := true; // for non-unicode Delphi + {$endif} + {$endif} + fSpecificOptions.Values['Direct'] := 'true'; + fSpecificOptions.Values['HOMENAME'] := ''; + end; + dMySQL: begin + {$ifdef FPC} + fSpecificOptions.Values['UseUnicode'] := 'true'; // FPC strings do like UTF8 + {$else} + {$ifndef UNICODE} + fForceUseWideString := true; // for non-unicode Delphi + {$endif} + {$endif} + // s.d. 30.11.19 Damit der Connect schneller geht ! CRVioTCP.pas WaitForConnect + fSpecificOptions.Values['MySQL.ConnectionTimeout'] := '0'; + end; dMSSQL: begin + {$ifndef UNICODE} + {$ifndef FPC} + fForceUseWideString := true; // for non-unicode Delphi + {$endif} + {$endif} if aUserID='' then fSpecificOptions.Values['Authentication'] := 'auWindows'; - fSpecificOptions.Values['SQL Server.Provider'] := cMSSQLProvider; - // s.d. 30.11.19 Damit der Connect im Direct Mode so Schnell ist wie mit prAuto/OleDB - fSpecificOptions.Values['SQL Server.ConnectionTimeout'] := '0'; + fSpecificOptions.Values['SQL Server.Provider'] := cMSSQLProvider; + // s.d. 30.11.19 Damit der Connect im Direct Mode so Schnell ist wie mit prAuto/OleDB + fSpecificOptions.Values['SQL Server.ConnectionTimeout'] := '0'; end; // http://www.devart.com/unidac/docs/index.html?sqlprov_article.htm dPostgreSQL: begin // thanks delphinium for the trick! + {$ifdef FPC} + fSpecificOptions.Values['UseUnicode'] := 'true'; // FPC strings do like UTF8 + {$else} + {$ifndef UNICODE} + fForceUseWideString := true; // for non-unicode Delphi + {$endif} + {$endif} fSpecificOptions.Values['CharSet'] := 'UTF8'; - fSpecificOptions.Values['UseUnicode'] := 'true'; end; end; end; @@ -281,10 +319,10 @@ begin Table := Owner; Owner := ''; end; - if Owner = '' then + if (Owner='') and (fDBMS<>dOracle) then Owner := MainConnection.Properties.DatabaseName; // itSDS if Owner<>'' then - meta.Restrictions.Values['TABLE_SCHEMA'] := UTF8ToString(UpperCase(Owner)) + meta.Restrictions.Values['TABLE_SCHEMA'] := UTF8ToString(UpperCase(Owner)) else meta.Restrictions.Values['SCOPE'] := 'LOCAL'; meta.Restrictions.Values['TABLE_NAME'] := UTF8ToString(UpperCase(Table)); meta.Open; @@ -300,6 +338,7 @@ begin F.ColumnPrecision := meta.FieldByName('DATA_PRECISION').AsInteger; F.ColumnType := ColumnTypeNativeToDB(F.ColumnTypeNative,F.ColumnScale); if F.ColumnType=ftUnknown then begin // UniDAC metadata failed -> use SQL + Fields := nil; inherited GetFields(aTableName,Fields); exit; end; @@ -321,6 +360,7 @@ var meta, indexs: TDAMetaData; n: integer; ColName: RawUTF8; ndxName: string; + Owner,Table: RawUTF8; begin SetLength(Indexes,0); FA.Init(TypeInfo(TSQLDBIndexDefineDynArray),Indexes,@n); @@ -329,14 +369,27 @@ begin indexs := (MainConnection as TSQLDBUniDACConnection).fDatabase.CreateMetaData; try meta.MetaDataKind := 'Indexes'; - meta.Restrictions.Values['TABLE_NAME'] := UTF8ToString(UpperCase(aTableName)); + Split(aTableName,'.',Owner,Table); + if Table='' then begin + Table := Owner; + Owner := ''; + end; + if (Owner='') and (fDBMS<>dOracle) then + Owner := MainConnection.Properties.DatabaseName; // itSDS + if Owner<>'' then + meta.Restrictions.Values['TABLE_SCHEMA'] := UTF8ToString(UpperCase(Owner)) else + meta.Restrictions.Values['SCOPE'] := 'LOCAL'; + meta.Restrictions.Values['TABLE_NAME'] := UTF8ToString(UpperCase(Table)); meta.Open; while not meta.Eof do begin ndxName := meta.FieldByName('INDEX_NAME').AsString; F.IndexName := StringToUTF8(ndxName); F.KeyColumns := ''; indexs.MetaDataKind := 'indexcolumns'; - indexs.Restrictions.Values['TABLE_NAME'] := UTF8ToString(UpperCase(aTableName)); + if Owner<>'' then + indexs.Restrictions.Values['TABLE_SCHEMA'] := UTF8ToString(UpperCase(Owner)) else + indexs.Restrictions.Values['SCOPE'] := 'LOCAL'; + indexs.Restrictions.Values['TABLE_NAME'] := UTF8ToString(UpperCase(Table)); indexs.Restrictions.Values['INDEX_NAME'] := ndxName; indexs.Open; while not indexs.Eof do begin @@ -398,7 +451,7 @@ begin if aLibraryLocation<>'' then begin result := result+'?ClientLibrary='; if aLibraryLocationAppendExePath then - result := result+StringToUTF8(ExtractFilePath(ParamStr(0))); + result := result+StringToUTF8(ExeVersion.ProgramFilePath); result := result+StringToUTF8(aLibraryLocation); end; if aServerName<>'' then begin @@ -552,39 +605,170 @@ end; procedure TSQLDBUniDACStatement.DataSetBindSQLParam(const aArrayIndex, aParamIndex: integer; const aParam: TSQLDBParam); var P: TDAParam; + i: Integer; + tmp: RawUTF8; + StoreVoidStringAsNull: boolean; begin P := TDAParam(fQueryParams[aParamIndex]); - if P.InheritsFrom(TDAParam) then - with aParam do - if (VinOut<>paramInOut) and (VType=SynTable.ftBlob) then begin - P.ParamType := SQLParamTypeToDBParamType(VInOut); - if aArrayIndex>=0 then -{$ifdef UNICODE} - P.SetBlobData(Pointer(VArray[aArrayIndex]),Length(VArray[aArrayIndex])) else - P.SetBlobData(Pointer(VData),Length(VData)); -{$else} P.AsString := VArray[aArrayIndex] else - P.AsString := VData; -{$endif}exit; + if not P.InheritsFrom(TDAParam) then begin + inherited DataSetBindSQLParam(aArrayIndex, aParamIndex, aParam); + Exit; + end; + if fDatasetSupportBatchBinding then + fBatchExecute := (aArrayIndex<0) and (fParamsArrayCount>0) else + fBatchExecute := false; + if fBatchExecute then + P.ValueCount := fParamsArrayCount else + P.ValueCount := 1; + with aParam do begin + P.ParamType := SQLParamTypeToDBParamType(VInOut); + if VinOut <> paramInOut then + case VType of + SynTable.ftNull: + if fBatchExecute then + for i := 0 to fParamsArrayCount-1 do + P.Values[i].Clear else + P.Clear; + SynTable.ftInt64: begin + if fBatchExecute then + for i := 0 to fParamsArrayCount-1 do + if VArray[i]='null' then + P.Values[i].Clear else + P.Values[i].AsLargeInt := GetInt64(pointer(VArray[i])) else + if aArrayIndex>=0 then + if VArray[aArrayIndex]='null' then + P.Clear else + P.AsLargeInt := GetInt64(pointer(VArray[aArrayIndex])) else + P.AsLargeInt := VInt64; + end; + SynTable.ftDouble: + if fBatchExecute then + for i := 0 to fParamsArrayCount-1 do + if VArray[i]='null' then + P.Values[i].Clear else + P.Values[i].AsFloat := GetExtended(pointer(VArray[i])) else + if aArrayIndex>=0 then + if VArray[aArrayIndex]='null' then + P.Clear else + P.AsFloat := GetExtended(pointer(VArray[aArrayIndex])) else + P.AsFloat := PDouble(@VInt64)^; + SynTable.ftCurrency: + if fBatchExecute then + for i := 0 to fParamsArrayCount-1 do + if VArray[i]='null' then + P.Values[i].Clear else + P.Values[i].AsCurrency := StrToCurrency(pointer(VArray[i])) else + if aArrayIndex>=0 then + if VArray[aArrayIndex]='null' then + P.Clear else + P.AsCurrency := StrToCurrency(pointer(VArray[aArrayIndex])) else + P.AsCurrency := PCurrency(@VInt64)^; + SynTable.ftDate: + if fBatchExecute then + for i := 0 to fParamsArrayCount-1 do + if VArray[i]='null' then + P.Values[i].Clear else begin + UnQuoteSQLStringVar(pointer(VArray[i]),tmp); + P.Values[i].AsDateTime := Iso8601ToDateTime(tmp); + end else + if aArrayIndex>=0 then + if VArray[aArrayIndex]='null' then + P.Clear else begin + UnQuoteSQLStringVar(pointer(VArray[aArrayIndex]),tmp); + P.AsDateTime := Iso8601ToDateTime(tmp); + end else + P.AsDateTime := PDateTime(@VInt64)^; + SynTable.ftUTF8: + if fBatchExecute then begin + StoreVoidStringAsNull := fConnection.Properties.StoreVoidStringAsNull; + for i := 0 to fParamsArrayCount-1 do + if (VArray[i]='null') or + (StoreVoidStringAsNull and (VArray[i]=#39#39)) then + P.Values[i].Clear else begin + UnQuoteSQLStringVar(pointer(VArray[i]),tmp); + {$ifdef UNICODE} + P.Values[i].AsWideString := UTF8ToString(tmp); + {$else} + if fForceUseWideString then + P.Values[i].AsWideString := UTF8ToWideString(tmp) else + P.Values[i].AsString := UTF8ToString(tmp); + {$endif} + end + end else + if aArrayIndex>=0 then + if (VArray[aArrayIndex]='null') or + (fConnection.Properties.StoreVoidStringAsNull and + (VArray[aArrayIndex]=#39#39)) then + P.Clear else begin + UnQuoteSQLStringVar(pointer(VArray[aArrayIndex]),tmp); + {$ifdef UNICODE} + P.AsWideString := UTF8ToString(tmp); + {$else} + if fForceUseWideString then + P.AsWideString := UTF8ToWideString(tmp) else + P.AsString := UTF8ToString(tmp); + {$endif} + end else + if (VData='') and fConnection.Properties.StoreVoidStringAsNull then + P.Clear else + {$ifdef UNICODE} + P.AsWideString := UTF8ToString(VData); + {$else} + if not fForceUseWideString then + P.AsString := UTF8ToString(VData) else + P.AsWideString := UTF8ToWideString(VData); + {$endif} + SynTable.ftBlob: + if fBatchExecute then + for i := 0 to fParamsArrayCount-1 do + if VArray[i]='null' then + P.Values[i].Clear else begin + {$ifdef UNICODE} + P.Values[i].AsBlobRef.Clear; + P.Values[i].AsBlobRef.Write(0, Length(VArray[aArrayIndex]), Pointer(VArray[aArrayIndex])); end else + {$else} + P.Values[i].AsString := VArray[aArrayIndex]; end else + {$endif} + if aArrayIndex>=0 then + if VArray[aArrayIndex]='null' then + P.Clear else begin + {$ifdef UNICODE} + P.AsBlobRef.Clear; + P.AsBlobRef.Write(0, Length(VArray[aArrayIndex]), Pointer(VArray[aArrayIndex])); end else begin + P.AsBlobRef.Clear; + P.AsBlobRef.Write(0, Length(VData), Pointer(VData)); end; + {$else} + P.AsString := VArray[aArrayIndex] end else + P.AsString := VData; + {$endif} + else + raise ESQLDBUniDAC.CreateUTF8( + '%.DataSetBindSQLParam: invalid type % on bound parameter #%', + [Self,ord(VType),aParamIndex+1]); end; - inherited DataSetBindSQLParam(aArrayIndex, aParamIndex, aParam); + end; end; procedure TSQLDBUniDACStatement.DatasetCreate; begin fQuery := TUniQuery.Create(nil); TUniQuery(fQuery).Connection := (fConnection as TSQLDBUniDACConnection).Database; + fDatasetSupportBatchBinding := true; end; function TSQLDBUniDACStatement.DatasetPrepare(const aSQL: string): boolean; begin (fQuery as TUniQuery).SQL.Text := aSQL; + TUniQuery(fQuery).Prepare; fQueryParams := TUniQuery(fQuery).Params; result := fQueryParams<>nil; end; procedure TSQLDBUniDACStatement.DatasetExecSQL; begin - (fQuery as TUniQuery).Execute; + if fBatchExecute then + (fQuery as TUniQuery).Execute(fParamsArrayCount) else + (fQuery as TUniQuery).Execute; end; diff --git a/contrib/mORMot/SynDBFirebird.pas b/contrib/mORMot/SynDBFirebird.pas index 9175924..51d11b3 100644 --- a/contrib/mORMot/SynDBFirebird.pas +++ b/contrib/mORMot/SynDBFirebird.pas @@ -7,7 +7,7 @@ unit SynDBFirebird; { This file is part of Synopse mORMot framework. - Synopse mORMot framework. Copyright (C) 2020 Arnaud Bouchez + Synopse mORMot framework. Copyright (C) 2022 Arnaud Bouchez Synopse Informatique - https://synopse.info *** BEGIN LICENSE BLOCK ***** @@ -26,7 +26,7 @@ unit SynDBFirebird; The Initial Developer of the Original Code is Arnaud Bouchez. - Portions created by the Initial Developer are Copyright (C) 2020 + Portions created by the Initial Developer are Copyright (C) 2022 the Initial Developer. All Rights Reserved. Contributor(s): diff --git a/contrib/mORMot/SynDBMidasVCL.pas b/contrib/mORMot/SynDBMidasVCL.pas index fe030a4..3c37050 100644 --- a/contrib/mORMot/SynDBMidasVCL.pas +++ b/contrib/mORMot/SynDBMidasVCL.pas @@ -6,7 +6,7 @@ unit SynDBMidasVCL; { This file is part of Synopse framework. - Synopse framework. Copyright (C) 2020 Arnaud Bouchez + Synopse framework. Copyright (C) 2022 Arnaud Bouchez Synopse Informatique - https://synopse.info *** BEGIN LICENSE BLOCK ***** @@ -25,7 +25,7 @@ unit SynDBMidasVCL; The Initial Developer of the Original Code is Arnaud Bouchez. - Portions created by the Initial Developer are Copyright (C) 2020 + Portions created by the Initial Developer are Copyright (C) 2022 the Initial Developer. All Rights Reserved. Contributor(s): diff --git a/contrib/mORMot/SynDBODBC.pas b/contrib/mORMot/SynDBODBC.pas index 1622404..0859b90 100644 --- a/contrib/mORMot/SynDBODBC.pas +++ b/contrib/mORMot/SynDBODBC.pas @@ -6,7 +6,7 @@ unit SynDBODBC; { This file is part of Synopse mORMot framework. - Synopse mORMot framework. Copyright (C) 2020 Arnaud Bouchez + Synopse mORMot framework. Copyright (C) 2022 Arnaud Bouchez Synopse Informatique - https://synopse.info *** BEGIN LICENSE BLOCK ***** @@ -25,7 +25,7 @@ unit SynDBODBC; The Initial Developer of the Original Code is Arnaud Bouchez. - Portions created by the Initial Developer are Copyright (C) 2020 + Portions created by the Initial Developer are Copyright (C) 2022 the Initial Developer. All Rights Reserved. Contributor(s): @@ -87,6 +87,7 @@ type protected fDriverDoesNotHandleUnicode: Boolean; fSQLDriverConnectPrompt: Boolean; + fSQLStatementTimeout: integer; /// this overridden method will hide de DATABASE/PWD fields in ODBC connection string function GetDatabaseNameSafe: RawUTF8; override; /// this overridden method will retrieve the kind of DBMS from the main connection @@ -159,6 +160,10 @@ type // - set to TRUE to allow UI prompt if needed property SQLDriverConnectPrompt: boolean read fSQLDriverConnectPrompt write fSQLDriverConnectPrompt; + /// The number of seconds to wait for a SQL statement to execute before canceling the query. + // When set to 0 (the default) there is no timeout. See ODBC SQL_QUERY_TIMEOUT documentation + property SQLStatementTimeoutSec: integer read fSQLStatementTimeout + write fSQLStatementTimeout; end; /// implements a direct connection to the ODBC library @@ -292,7 +297,7 @@ type end; {$ifdef MSWINDOWS} -/// List all ODBC drivers installed +/// List all ODBC drivers installed, by reading the Windows Registry // - aDrivers is the output driver list container, which should be either nil (to // create a new TStringList), or any existing TStrings instance (may be from VCL // - aIncludeVersion: include the DLL driver version as = @@ -300,6 +305,7 @@ type function ODBCInstalledDriversList(const aIncludeVersion: Boolean; var aDrivers: TStrings): boolean; {$endif MSWINDOWS} + implementation {$ifdef MSWINDOWS} @@ -384,10 +390,12 @@ const SQL_ATTR_METADATA_ID = 10014; // statement attributes + SQL_QUERY_TIMEOUT = 0; SQL_ATTR_APP_ROW_DESC = 10010; SQL_ATTR_APP_PARAM_DESC = 10011; SQL_ATTR_IMP_ROW_DESC = 10012; SQL_ATTR_IMP_PARAM_DESC = 10013; + SQL_ATTR_QUERY_TIMEOUT = SQL_QUERY_TIMEOUT; // ODBC 3.0 SQL_ATTR_CURSOR_SCROLLABLE = (-1); SQL_ATTR_CURSOR_SENSITIVITY = (-2); @@ -1704,7 +1712,15 @@ begin end; ftDouble: begin CValueType := SQL_C_DOUBLE; - // in case of "Invalid character value for cast specification" error + if (fDBMS = dMSSQL) and (VInOut=paramIn) and + (PDouble(@VInt64)^ > -1) and (PDouble(@VInt64)^ < 1) then begin + // prevent "Invalid character value for cast specification" error for numbers (-1; 1) + // for doubles outside this range SQL_C_DOUBLE must be used + ParameterType := SQL_NUMERIC; + ColumnSize := 9; + DecimalDigits := 6; + end; + // in case of "Invalid character value for cast specification" error // for small digits like 0.01, -0.0001 under Linux msodbcsql17 should // be updated to >= 17.5.2 ParameterValue := pointer(@VInt64); @@ -1728,8 +1744,14 @@ begin if ansitext then begin retry: VData := CurrentAnsiConvert.UTF8ToAnsi(VData); CValueType := SQL_C_CHAR; - end else + end else begin VData := Utf8DecodeToRawUnicode(VData); + if (fDBMS=dMSSQL) then begin // statements like CONTAINS(field, ?) do not accept NVARCHAR(max) + ColumnSize := length(VData) shr 1; // length in characters + if (ColumnSize > 4000) then // > 8000 bytes - use varchar(max) + ColumnSize := 0; + end; + end; ftBlob: StrLen_or_Ind[p] := length(VData); else @@ -1867,6 +1889,10 @@ begin try ODBC.Check(nil,self,ODBC.PrepareW(fStatement,pointer(fSQLW),length(fSQLW) shr 1), SQL_HANDLE_STMT,fStatement); + if TODBCConnectionProperties(Connection.Properties).SQLStatementTimeoutSec > 0 then + ODBC.Check(nil,self,ODBC.SetStmtAttrA(fStatement,SQL_ATTR_QUERY_TIMEOUT, + SQLPOINTER(TODBCConnectionProperties(Connection.Properties).SQLStatementTimeoutSec), SQL_IS_INTEGER), + SQL_HANDLE_STMT,fStatement); SQLLogEnd; except on E: Exception do begin @@ -2054,7 +2080,7 @@ begin end; FA.Init(TypeInfo(TSQLDBColumnDefineDynArray),Fields,@n); FA.Compare := SortDynArrayAnsiStringI; // FA.Find() case insensitive - fillchar(F,SizeOf(F),0); + FillcharFast(F,SizeOf(F),0); if fCurrentRow>0 then // Step done above repeat F.ColumnName := Trim(ColumnUTF8(3)); // Column*() should be done in order @@ -2256,7 +2282,7 @@ begin Stmt.Step; end; PA.Init(TypeInfo(TSQLDBColumnDefineDynArray),Parameters,@n); - fillchar(P,SizeOf(P),0); + FillcharFast(P,SizeOf(P),0); if Stmt.fCurrentRow>0 then // Step done above repeat P.ColumnName := Trim(Stmt.ColumnUTF8(3)); // Column*() should be in order diff --git a/contrib/mORMot/SynDBOracle.pas b/contrib/mORMot/SynDBOracle.pas index 13aed30..5fce620 100644 --- a/contrib/mORMot/SynDBOracle.pas +++ b/contrib/mORMot/SynDBOracle.pas @@ -6,7 +6,7 @@ unit SynDBOracle; { This file is part of Synopse framework. - Synopse framework. Copyright (C) 2020 Arnaud Bouchez + Synopse framework. Copyright (C) 2022 Arnaud Bouchez Synopse Informatique - https://synopse.info *** BEGIN LICENSE BLOCK ***** @@ -25,7 +25,7 @@ unit SynDBOracle; The Initial Developer of the Original Code is Arnaud Bouchez. - Portions created by the Initial Developer are Copyright (C) 2020 + Portions created by the Initial Developer are Copyright (C) 2022 the Initial Developer. All Rights Reserved. Contributor(s): @@ -73,7 +73,7 @@ uses { -------------- Oracle Client Interface native connection } type - /// execption type associated to the native Oracle Client Interface (OCI) + /// exception type associated to the native Oracle Client Interface (OCI) ESQLDBOracle = class(ESQLDBException); POracleDate = ^TOracleDate; @@ -312,6 +312,11 @@ type // - if ExpectResults is TRUE, then Step() and Column*() methods are available // to retrieve the data rows // - raise an ESQLDBOracle on any error + // - if aSQL requires a trailing ';', you should end it with ';;' e.g. for + // $ DB.ExecuteNoResult( + // $ 'CREATE OR REPLACE FUNCTION ORA_POC(MAIN_TABLE IN VARCHAR2, REC_COUNT IN NUMBER, BATCH_SIZE IN NUMBER) RETURN VARCHAR2' + + // $ ' AS LANGUAGE JAVA' + + // $ ' NAME ''OraMain.selectTable(java.lang.String, int, int) return java.lang.String'';;', []); procedure Prepare(const aSQL: RawUTF8; ExpectResults: Boolean=false); overload; override; /// Execute a prepared SQL statement // - parameters marked as ? should have been already bound with Bind*() functions @@ -439,6 +444,7 @@ var /// how many blob chunks should be handled at once SynDBOracleBlobChunksCount: integer = 250; + implementation { TOracleDate } @@ -2611,7 +2617,9 @@ begin end; type + /// Oracle VARNUM memory structure TSQLT_VNU = array[0..21] of byte; + /// points to a Oracle VARNUM memory structure PSQLT_VNU = ^TSQLT_VNU; procedure Int64ToSQLT_VNU(Value: Int64; OutData: PSQLT_VNU); @@ -2657,7 +2665,7 @@ begin end; procedure UnQuoteSQLString(S,D: PUTF8Char; SLen: integer); -begin +begin // internal method, tuned for our OCI process if S=nil then D^ := #0 else if S^<>'''' then @@ -2970,7 +2978,14 @@ begin VDBType,@oIndicator[i],nil,nil,0,nil,OCI_DEFAULT),fError); end; end; - // 2. execute prepared statement + // 2. retrieve column information (if not already done) + if fExpectResults and (fColumn.Count = 0) then + // We move this after params binding to prevent "ORA-00932: inconsistent + // datatypes" during call to StmtExecute with OCI_DESCRIBE_ONLY. + // Because if called here sometimes it breaks the Oracle shared pool and + // only `ALTER system flush shared_pool` seems to fix the DB state + SetColumnsForPreparedStatement; + // 3. execute prepared statement and dispatch data in row buffers if (fColumnCount=0) and (Connection.TransactionCount=0) then // for INSERT/UPDATE/DELETE without a transaction: AutoCommit after execution mode := OCI_COMMIT_ON_SUCCESS else @@ -2978,6 +2993,7 @@ begin mode := OCI_DEFAULT; Status := OCI.StmtExecute(TSQLDBOracleConnection(Connection).fContext, fStatement,fError,fRowCount,0,nil,nil,mode); + // 4. check execution error, and retrieve data result range FetchTest(Status); // error + set fRowCount+fCurrentRow+fRowFetchedCurrent Status := OCI_SUCCESS; // mark OK for fBoundCursor[] below finally @@ -3412,14 +3428,16 @@ begin raise ESQLDBOracle.CreateUTF8('%.Prepare should be called only once',[self]); // 1. process SQL inherited Prepare(aSQL,ExpectResults); // set fSQL + Connect if necessary - fPreparedParamsCount := ReplaceParamsByNumbers(aSQL,fSQLPrepared,':', true); + fPreparedParamsCount := ReplaceParamsByNumbers(aSQL,fSQLPrepared,':',true); L := Length(fSQLPrepared); - while (L>0) and (fSQLPrepared[L] in [#1..' ',';']) do - if (fSQLPrepared[L]=';') and (L>5) and IdemPChar(@fSQLPrepared[L-3],'END') then - break else // allows 'END;' at the end of a statement - dec(L); // trim ' ' or ';' right (last ';' could be found incorrect) - if L <> Length(fSQLPrepared) then - fSQLPrepared := copy(fSQLPrepared,1,L); // trim right ';' if any + while (L>0) and (fSQLPrepared[L]<=' ') do // trim right + dec(L); + // allow one trailing ';' by writing ';;' or allows 'END;' at the end of a statement + if (L>5) and (fSQLPrepared[L]=';') and not + (IdemPChar(@fSQLPrepared[L-3],'END') and (fSQLPrepared[L-4]<='A')) then + dec(L); + if L<>Length(fSQLPrepared) then + SetLength(fSQLPrepared,L); // trim trailing spaces or ';' if needed // 2. prepare statement env := (Connection as TSQLDBOracleConnection).fEnv; with OCI do begin @@ -3437,8 +3455,8 @@ begin OCI_NTV_SYNTAX,OCI_DEFAULT),fError); end; end; - // 3. retrieve column information and dispatch data in row buffer - SetColumnsForPreparedStatement; + // note: if SetColumnsForPreparedStatement is called here, we randomly got + // "ORA-00932 : inconsistent datatypes" error -> moved to ExecutePrepared except on E: Exception do begin FreeHandles(True); diff --git a/contrib/mORMot/SynDBPostgres.pas b/contrib/mORMot/SynDBPostgres.pas index 3cde862..9d71fe6 100644 --- a/contrib/mORMot/SynDBPostgres.pas +++ b/contrib/mORMot/SynDBPostgres.pas @@ -322,12 +322,6 @@ type /// direct access to the libpq native Postgres protocol 3 library // - only the endpoints needed by this unit are imported TSQLDBPostgresLib = class(TSQLDBLib) - protected - /// raise an exception on error and clean result - // - will set pRes to nil if passed - // - if andClear is true - will call always PQ.Clear(res) - procedure Check(conn: PPGconn; res: PPGresult; - pRes: PPPGresult = nil; andClear: boolean = true); public LibVersion: function: integer; cdecl; IsThreadSafe: function: integer; cdecl; @@ -365,6 +359,11 @@ type /// just a wrapper around FastSetString + GetValue/GetLength procedure GetRawUTF8(res: PPGresult; tup_num, field_num: integer; var result: RawUTF8); + /// raise an exception on error and clean result + // - will set pRes to nil if passed + // - if andClear is true - will call always PQ.Clear(res) + procedure Check(conn: PPGconn; res: PPGresult; + pRes: PPPGresult = nil; andClear: boolean = true); end; const @@ -519,11 +518,13 @@ end; procedure TSQLDBPostgresConnection.Connect; var log: ISynLog; + host, port: RawUtf8; begin log := SynDBLog.Enter(self, 'Connect'); Disconnect; // force fTrans=fError=fServer=fContext=nil try - fPGConn := PQ.SetDBLogin(pointer(Properties.ServerName), nil, nil, nil, + Split(Properties.ServerName, ':', host, port); + fPGConn := PQ.SetDBLogin(pointer(host), pointer(port), nil, nil, pointer(Properties.DatabaseName), pointer(Properties.UserID), pointer(Properties.PassWord)); if PQ.Status(fPGConn) = CONNECTION_BAD then @@ -537,7 +538,8 @@ begin log.Log(sllDB, 'Connected to % % using % v%', [fProperties.ServerName, fProperties.DatabaseNameSafe, PQ.fLibraryPath, PQ.LibVersion], self); end - else // to ensure no performance drop due to notice to console + else + // to ensure no performance drop due to notice to console PQ.SetNoticeProcessor(fPGConn, DummyNoticeProcessor, nil); inherited Connect; // notify any re-connection except @@ -816,7 +818,8 @@ begin DoubleToStr(PDouble(@p^.VInt64)^, RawUTF8(p^.VData)); ftDate: // Postgres expects space instead of T in ISO8601 expanded format - p^.VData := DateTimeToIso8601(PDateTime(@p^.VInt64)^, true, ' '); + p^.VData := DateTimeToIso8601( + PDateTime(@p^.VInt64)^, true, ' ', fForceDateWithMS); ftUTF8: ; // text already in p^.VData ftBlob: diff --git a/contrib/mORMot/SynDBRemote.pas b/contrib/mORMot/SynDBRemote.pas index fbcbe66..a2773f4 100644 --- a/contrib/mORMot/SynDBRemote.pas +++ b/contrib/mORMot/SynDBRemote.pas @@ -6,7 +6,7 @@ unit SynDBRemote; { This file is part of Synopse framework. - Synopse framework. Copyright (C) 2020 Arnaud Bouchez + Synopse framework. Copyright (C) 2022 Arnaud Bouchez Synopse Informatique - https://synopse.info *** BEGIN LICENSE BLOCK ***** @@ -25,7 +25,7 @@ unit SynDBRemote; The Initial Developer of the Original Code is Arnaud Bouchez. - Portions created by the Initial Developer are Copyright (C) 2020 + Portions created by the Initial Developer are Copyright (C) 2022 the Initial Developer. All Rights Reserved. Contributor(s): diff --git a/contrib/mORMot/SynDBSQLite3.pas b/contrib/mORMot/SynDBSQLite3.pas index 4aad5d7..016591e 100644 --- a/contrib/mORMot/SynDBSQLite3.pas +++ b/contrib/mORMot/SynDBSQLite3.pas @@ -6,7 +6,7 @@ unit SynDBSQLite3; { This file is part of Synopse framework. - Synopse framework. Copyright (C) 2020 Arnaud Bouchez + Synopse framework. Copyright (C) 2022 Arnaud Bouchez Synopse Informatique - https://synopse.info *** BEGIN LICENSE BLOCK ***** @@ -25,7 +25,7 @@ unit SynDBSQLite3; The Initial Developer of the Original Code is Arnaud Bouchez. - Portions created by the Initial Developer are Copyright (C) 2020 + Portions created by the Initial Developer are Copyright (C) 2022 the Initial Developer. All Rights Reserved. Contributor(s): diff --git a/contrib/mORMot/SynDBVCL.pas b/contrib/mORMot/SynDBVCL.pas index 3c19d41..09a647e 100644 --- a/contrib/mORMot/SynDBVCL.pas +++ b/contrib/mORMot/SynDBVCL.pas @@ -6,7 +6,7 @@ unit SynDBVCL; { This file is part of Synopse framework. - Synopse framework. Copyright (C) 2020 Arnaud Bouchez + Synopse framework. Copyright (C) 2022 Arnaud Bouchez Synopse Informatique - https://synopse.info *** BEGIN LICENSE BLOCK ***** @@ -25,7 +25,7 @@ unit SynDBVCL; The Initial Developer of the Original Code is Arnaud Bouchez. - Portions created by the Initial Developer are Copyright (C) 2020 + Portions created by the Initial Developer are Copyright (C) 2022 the Initial Developer. All Rights Reserved. Contributor(s): @@ -80,7 +80,8 @@ type // - by default, ColumnDataSize would be computed from the supplied data, // unless you set IgnoreColumnDataSize=true to set the value to 0 (and // force e.g. SynDBVCL TSynBinaryDataSet.InternalInitFieldDefs define the - // field as ftDefaultMemo) + // field as ftDefaultMemo) or you define some FieldDefs.Items[].Size values + // for ftUTF8 column sizes before calling this From() method procedure From(const BinaryData: RawByteString; DataRowPosition: PCardinalDynArray=nil; IgnoreColumnDataSize: boolean=false); overload; virtual; /// initialize the virtual TDataSet from a SynDB TSQLDBStatement result set @@ -89,7 +90,8 @@ type // - by default, ColumnDataSize would be computed from the supplied data, // unless you set IgnoreColumnDataSize=true to set the value to 0 (and // force e.g. SynDBVCL TSynBinaryDataSet.InternalInitFieldDefs define the - // field as ftDefaultMemo) + // field as ftDefaultMemo) or you define some FieldDefs.Items[].Size values + // for ftUTF8 column sizes before calling this From() method procedure From(Statement: TSQLDBStatement; MaxRowCount: cardinal=0; IgnoreColumnDataSize: boolean=false); overload; virtual; /// finalize the class instance @@ -139,7 +141,8 @@ type // - by default, ColumnDataSize would be computed from the supplied data, // unless you set IgnoreColumnDataSize=true to set the value to 0 (and // force e.g. SynDBVCL TSynBinaryDataSet.InternalInitFieldDefs define the - // field as ftDefaultMemo) + // field as ftDefaultMemo) or you define some FieldDefs.Items[].Size values + // for ftUTF8 column sizes before calling this From() method procedure From(Statement: TSQLDBStatement; MaxRowCount: cardinal=0; IgnoreColumnDataSize: boolean=false); override; /// the associated connection properties @@ -260,33 +263,49 @@ begin end; procedure TSynBinaryDataSet.InternalInitFieldDefs; -var F: integer; +var F,custom: integer; DBType: TFieldType; + ExistingName: TRawUTF8DynArray; // FieldDefs.Items[].Name + ExistingSize: TIntegerDynArray; // FieldDefs.Items[].Size begin + if FieldDefs.Count>0 then begin // custom column sizes + SetLength(ExistingName,FieldDefs.Count); + SetLength(ExistingSize,FieldDefs.Count); + for F := 0 to FieldDefs.Count-1 do + with FieldDefs.Items[F] do begin + ExistingName[F] := StringToUtf8(Name); + ExistingSize[F] := Size; + end; + end; FieldDefs.Clear; if fDataAccess=nil then exit; for F := 0 to fDataAccess.ColumnCount-1 do with fDataAccess.Columns[F] do begin - case ColumnType of - SynTable.ftInt64: - DBType := ftLargeint; - SynTable.ftDate: - DBType := ftDateTime; - SynTable.ftUTF8: - if ColumnDataSize=0 then - DBType := ftDefaultMemo else - DBType := ftWideString; // means UnicodeString for Delphi 2009+ - SynTable.ftBlob: - DBType := ftBlob; - SynTable.ftDouble, SynTable.ftCurrency: - DBType := ftFloat; - else - raise EDatabaseError.CreateFmt( - 'GetFieldData ColumnType=%s',[TSQLDBFieldTypeToString(ColumnType)]); + if ExistingName<>nil then begin + custom := FindRawUTF8(ExistingName,ColumnName); + if custom>=0 then // retrieve custom max column length from FieldDefs + ColumnDataSize := ExistingSize[custom]; + end; + case ColumnType of + SynTable.ftInt64: + DBType := ftLargeint; + SynTable.ftDate: + DBType := ftDateTime; + SynTable.ftUTF8: + if ColumnDataSize=0 then + DBType := ftDefaultMemo else // no size + DBType := ftWideString; // means UnicodeString for Delphi 2009+ + SynTable.ftBlob: + DBType := ftBlob; + SynTable.ftDouble, SynTable.ftCurrency: + DBType := ftFloat; + else + raise EDatabaseError.CreateFmt( + 'GetFieldData ColumnType=%s',[TSQLDBFieldTypeToString(ColumnType)]); + end; + FieldDefs.Add(UTF8ToString(ColumnName),DBType,ColumnDataSize); end; - FieldDefs.Add(UTF8ToString(ColumnName),DBType,ColumnDataSize); - end; end; function TSynBinaryDataSet.GetRowFieldData(Field: TField; diff --git a/contrib/mORMot/SynDBZeos.pas b/contrib/mORMot/SynDBZeos.pas index ed9aae9..78d875c 100644 --- a/contrib/mORMot/SynDBZeos.pas +++ b/contrib/mORMot/SynDBZeos.pas @@ -6,7 +6,7 @@ unit SynDBZeos; { This file is part of Synopse framework. - Synopse framework. Copyright (C) 2020 Arnaud Bouchez + Synopse framework. Copyright (C) 2022 Arnaud Bouchez Synopse Informatique - https://synopse.info *** BEGIN LICENSE BLOCK ***** @@ -25,7 +25,7 @@ unit SynDBZeos; The Initial Developer of the Original Code is Arnaud Bouchez. - Portions created by the Initial Developer are Copyright (C) 2020 + Portions created by the Initial Developer are Copyright (C) 2022 the Initial Developer. All Rights Reserved. Contributor(s): @@ -158,6 +158,9 @@ uses {$if defined(ENABLE_INTERBASE) and not defined(ZEOS_DISABLE_INTERBASE)} ZDbcInterbase6, {$ifend} + {$if defined(ENABLE_FIREBIRD) and not defined(ZEOS_DISABLE_FIREBIRD)} + ZDbcFirebird, + {$ifend} {$if defined(ENABLE_SQLITE) and not defined(ZEOS_DISABLE_SQLITE)} ZDbcSqLite, {$ifend} @@ -167,6 +170,9 @@ uses {$if defined(ENABLE_ASA) and not defined(ZEOS_DISABLE_ASA)} ZDbcASA, {$ifend} + {$if defined(ENABLE_SQLANY) and not defined(ZEOS_DISABLE_SQLANY)} + ZDbcSQLAnywhere, + {$ifend} {$if defined(ENABLE_POOLED) and not defined(ZEOS_DISABLE_POOLED)} ZDbcPooled, {$ifend} @@ -176,10 +182,13 @@ uses {$if defined(ENABLE_ODBC) and not defined(ZEOS_DISABLE_ODBC)} ZDbcODBCCon, {$ifend} + // main ZDBC units ZCompatibility, ZVariant, + {$ifndef ZEOS80UP} ZURL, + {$endif ZEOS80UP} ZDbcIntfs, ZDbcResultSet, // mORMot units after ZDBC due to some name conflicts (e.g. UTF8ToString) @@ -728,9 +737,10 @@ begin sSchema := UTF8ToString(Schema); { mormot does not create the Tables casesensitive but gives mixed cased strings as tablename so we normalize the identifiers to database defaults : } - sTableName := meta.GetIdentifierConvertor.ExtractQuote(UTF8ToString(TableName)); - sTableName := meta.AddEscapeCharToWildcards(sTableName); //do not use "like" search patterns ['_','%'] so they need to be escaped - res := meta.GetColumns('',sSchema,sTableName,''); + sTableName := meta.{$ifdef ZEOS80UP}GetIdentifierConverter{$else}GetIdentifierConvertor{$endif}. + ExtractQuote(Utf8ToString(TableName)); + // do not escape sTableName - https://synopse.info/forum/viewtopic.php?pid=34896#p34896 + res := meta.GetColumns('',sSchema,meta.AddEscapeCharToWildcards(sTableName),''); FA.InitSpecific(TypeInfo(TSQLDBColumnDefineDynArray),Fields,djRawUTF8,@n,true); FillChar(F,sizeof(F),0); while res.Next do begin diff --git a/contrib/mORMot/SynDprUses.inc b/contrib/mORMot/SynDprUses.inc index 39b7ed2..5654f82 100644 --- a/contrib/mORMot/SynDprUses.inc +++ b/contrib/mORMot/SynDprUses.inc @@ -37,15 +37,14 @@ {$endif FPC_X64MM_WIN} {$ifdef Unix} // we may also be on Darwin / OSX {$ifdef Darwin} - iosxwstr, // optional WideString manager for Mac, but won't hurt + // iosxwstr, // optional WideString manager for Mac {$else} - cwstring, // optional WideString manager, but won't hurt + // cwstring, // optional WideString manager {$endif Darwin} {$else} {$ifopt D-} // at the moment, debug mode with cmem causes trouble //cmem, // default FPC's heap manager is very RAM hungry (one heap per thread) {$endif} - //FastMM4, // need the latest version, e.g. from lib\kylix sub folder {$endif Unix} {$else} // enable FastMM4 on older versions of Delphi {$IFDEF CONDITIONALEXPRESSIONS} diff --git a/contrib/mORMot/SynEcc.pas b/contrib/mORMot/SynEcc.pas index d949580..4110771 100644 --- a/contrib/mORMot/SynEcc.pas +++ b/contrib/mORMot/SynEcc.pas @@ -6,7 +6,7 @@ unit SynEcc; (* This file is part of Synopse framework. - Synopse framework. Copyright (C) 2020 Arnaud Bouchez + Synopse framework. Copyright (C) 2022 Arnaud Bouchez Synopse Informatique - https://synopse.info *** BEGIN LICENSE BLOCK ***** @@ -25,11 +25,11 @@ unit SynEcc; The Initial Developer of the Original Code is Arnaud Bouchez. - Portions created by the Initial Developer are Copyright (C) 2020 + Portions created by the Initial Developer are Copyright (C) 2022 the Initial Developer. All Rights Reserved. Contributor(s): - - Kenneth MacKay (easy-ecc source code) + - Kenneth MacKay (micro-ecc source code) Alternatively, the contents of this file may be used under the terms of either the GNU General Public License Version 2 or later (the "GPL"), or @@ -48,7 +48,7 @@ unit SynEcc; Using secp256r1 curve from "simple and secure ECDH and ECDSA library" Copyright (c) 2013, Kenneth MacKay - BSD 2-clause license - https://github.com/esxgx/easy-ecc + https://github.com/kmackay/micro-ecc *** BEGIN LICENSE BLOCK ***** Copyright (c) 2013, Kenneth MacKay @@ -164,6 +164,10 @@ type // - each ECC signature consumes 64 bytes of memory TECCSignature = array[0..(ECC_BYTES*2)-1] of byte; + /// store a signature, in the DER format + // - static allocated buffer as returned by EccSignToDer() + TEccSignatureDer = array[0..(ECC_BYTES * 2) + 7] of byte; + /// store an encryption key, as generated by ECC secp256r1 cryptography // - use ecdh_shared_secret() to compute such a key from public/private keys // - 256-bit / 32 bytes derivation from secp256r1 ECDH is expected to have at @@ -212,7 +216,7 @@ var /// create a public/private key pair // - using secp256r1 curve, i.e. NIST P-256, or OpenSSL prime256v1 -// - directly low-level access to the statically linked easy-ecc library function +// - directly low-level access to the statically linked micro-ecc library function // - returns true if the key pair was generated successfully in pub/priv // - returns false if an error occurred // - this function is thread-safe and does not perform any memory allocation @@ -221,7 +225,7 @@ function ecc_make_key(out pub: TECCPublicKey; out priv: TECCPrivateKey): boolean /// compute a shared secret given your secret key and someone else's public key // - using secp256r1 curve, i.e. NIST P-256, or OpenSSL prime256v1 -// - directly low-level access to the statically linked easy-ecc library function +// - directly low-level access to the statically linked micro-ecc library function // - note: it is recommended that you hash the result of ecdh_shared_secret // before using it for symmetric encryption or HMAC (via an intermediate KDF) // - returns true if the shared secret was generated successfully in secret @@ -233,7 +237,7 @@ function ecdh_shared_secret(const pub: TECCPublicKey; const priv: TECCPrivateKey /// generate an ECDSA signature for a given hash value // - using secp256r1 curve, i.e. NIST P-256, or OpenSSL prime256v1 -// - directly low-level access to the statically linked easy-ecc library function +// - directly low-level access to the statically linked micro-ecc library function // - returns true if the signature generated successfully in sign // - returns false if an error occurred // - this function is thread-safe and does not perform any memory allocation @@ -243,7 +247,7 @@ function ecdsa_sign(const priv: TECCPrivateKey; const hash: TECCHash; /// verify an ECDSA signature // - using secp256r1 curve, i.e. NIST P-256, or OpenSSL prime256v1 -// - directly low-level access to the statically linked easy-ecc library function +// - directly low-level access to the statically linked micro-ecc library function // - returns true if the signature is valid // - returns false if an error occurred // - this function is thread-safe and does not perform any memory allocation @@ -664,6 +668,10 @@ function ECCCheck(const content: TECCSignatureCertifiedContent): boolean; overlo /// convert a supplied base-64 text into a TECCSignatureCertifiedContent binary buffer function ECCSign(const base64: RawUTF8; out content: TECCSignatureCertifiedContent): boolean; +/// convert a raw signature into a DER compatible content +// - returns the number of bytes encoded into der[] buffer +function EccSignToDer(const sign: TEccSignature; out der: TEccSignatureDer): integer; + /// convert a supplied TECCSignatureCertifiedContent binary buffer into proper text // - returns base-64 encoded text, or '' if the signature was filled with zeros function ECCText(const sign: TECCSignatureCertifiedContent): RawUTF8; overload; @@ -1958,9 +1966,7 @@ end; {$endif ECC_STATICLIB_AVAILABLE} -{$ifdef HASUINT64} - -{ Pure Pascal Version of low-level ECC process (adapted from easy-ecc.c code) +{ Pure Pascal Version of low-level ECC process (adapted from micro-ecc.c code) Some numbers (on another slower computer than the previous values above), which is quite acceptable, since it is faster than gcc -O1 mode :) @@ -2055,6 +2061,13 @@ type // we use UInt64 instead of QWord end; PEccPoint = ^TEccPoint; +function _isZero(const VLI: TVLI): boolean; {$ifdef HASINLINE}inline;{$endif} +begin + result := (VLI[0]=0) and (VLI[1]=0) and (VLI[2]=0) and (VLI[3]=0); +end; + +{$ifdef HASUINT64} + const Curve_P_32: TVLI = ( UInt64($FFFFFFFFFFFFFFFF), UInt64($00000000FFFFFFFF), @@ -2084,11 +2097,6 @@ begin VLI[3] := 0; end; -function _isZero(const VLI: TVLI): boolean; {$ifdef HASINLINE}inline;{$endif} -begin - result := (VLI[0]=0) and (VLI[1]=0) and (VLI[2]=0) and (VLI[3]=0); -end; - function _equals(const Left, Right: TVLI): boolean; {$ifdef HASINLINE}inline;{$endif} begin result := (Left[0]=Right[0]) and (Left[1]=Right[1]) and (Left[2]=Right[2]) and @@ -3242,6 +3250,35 @@ begin result := Base64ToBin(pointer(base64),@content,length(base64),sizeof(content),false); end; +const + DER_SEQUENCE = $30; + DER_INTEGER = #$02; + +function DerAppend(P: PAnsiChar; vli: PByteArray): PAnsiChar; +var pos, prefix: PtrUInt; +begin + pos := 0; + while vli[pos] = 0 do inc(pos); // ignore trailing zeros + prefix := vli[pos] shr 7; // two's complement? + P[0] := DER_INTEGER; + P[1] := AnsiChar(ECC_BYTES - pos + prefix); + P[2] := #$00; // prepend 0 for negative number (if prefix=1) + inc(P, 2 + prefix); + MoveSmall(@vli[pos], P, ECC_BYTES - pos); + result := P + ECC_BYTES - pos; +end; + +function EccSignToDer(const sign: TEccSignature; out der: TEccSignatureDer): integer; +begin + if _isZero(PVLI(@sign[0])^) or _isZero(PVLI(@sign[ECC_BYTES])^) then + result := 0 + else begin + result := DerAppend(DerAppend(@der[2],@sign[0]),@sign[ECC_BYTES])-PAnsiChar(@der); + der[0] := DER_SEQUENCE; + der[1] := result - 2; + end; +end; + function ECCText(const sign: TECCSignatureCertifiedContent): RawUTF8; overload; begin if ECCCheck(sign) then @@ -4410,31 +4447,11 @@ begin end; function TECCSignatureCertified.SaveToDERBinary: RawByteString; -const DER_SEQUENCE = $30; - DER_INTEGER = $02; -var RPrefix,SPrefix: integer; - P: PByteArray; +var der: TEccSignatureDer; begin - if not Check then begin - result := ''; - exit; - end; - RPrefix := fContent.Signature[0] shr 7; // DER_INTEGER are two's complement - SPrefix := fContent.Signature[ECC_BYTES] shr 7; - SetLength(result,RPrefix+SPrefix+(ECC_BYTES*2+6)); - P := pointer(result); - P[0] := DER_SEQUENCE; - P[1] := RPrefix+SPrefix+(ECC_BYTES*2+4); - P[2] := DER_INTEGER; - P[3] := ECC_BYTES+RPrefix; - P[4] := $00; // prepend 0 for negative number (if RPrefix=1) - inc(PByte(P),4+RPrefix); - MoveFast(fContent.Signature[0],P[0],ECC_BYTES); - inc(PByte(P),ECC_BYTES); - P[0] := DER_INTEGER; - P[1] := ECC_BYTES+SPrefix; - P[2] := $00; - MoveFast(fContent.Signature[ECC_BYTES],P[2+SPrefix],ECC_BYTES); + if not Check then + result := '' else + SetString(result,PAnsiChar(@der),EccSignToDer(fContent.Signature,der)); end; function TECCSignatureCertified.SaveToDERFile( @@ -5060,7 +5077,7 @@ begin exit; // mandatory parameter sw.ValueEnum('k',TypeInfo(TECDHEKDF),algo.kdf); sw.ValueEnum('e',TypeInfo(TECDHEEF),algo.ef); - sw.ValueEnum('m',TypeInfo(TECDHEEF),algo.mac); + sw.ValueEnum('m',TypeInfo(TECDHEMAC),algo.mac); // compute ca: TECCCertificateChain ca := nil; c := sw.Str['ca']; diff --git a/contrib/mORMot/SynEcc32asm.inc b/contrib/mORMot/SynEcc32asm.inc index a00fd27..7734710 100644 --- a/contrib/mORMot/SynEcc32asm.inc +++ b/contrib/mORMot/SynEcc32asm.inc @@ -6,7 +6,7 @@ This file is part of Synopse framework. - Synopse framework. Copyright (C) 2020 Arnaud Bouchez + Synopse framework. Copyright (C) 2022 Arnaud Bouchez Synopse Informatique - https://synopse.info Using secp256r1 curve from "simple and secure ECDH and ECDSA library" diff --git a/contrib/mORMot/SynFPCCMemAligned.pas b/contrib/mORMot/SynFPCCMemAligned.pas index 50fd88d..dd684c4 100644 --- a/contrib/mORMot/SynFPCCMemAligned.pas +++ b/contrib/mORMot/SynFPCCMemAligned.pas @@ -10,7 +10,7 @@ unit SynFPCCMemAligned; { This file is part of Synopse mORMot framework. - Synopse mORMot framework. Copyright (C) 2020 Arnaud Bouchez + Synopse mORMot framework. Copyright (C) 2022 Arnaud Bouchez Synopse Informatique - https://synopse.info *** BEGIN LICENSE BLOCK ***** @@ -29,7 +29,7 @@ unit SynFPCCMemAligned; The Initial Developer of the Original Code is Arnaud Bouchez. - Portions created by the Initial Developer are Copyright (C) 2020 + Portions created by the Initial Developer are Copyright (C) 2022 the Initial Developer. All Rights Reserved. Contributor(s): diff --git a/contrib/mORMot/SynFPCLinux.pas b/contrib/mORMot/SynFPCLinux.pas index ef1263b..b6b8671 100644 --- a/contrib/mORMot/SynFPCLinux.pas +++ b/contrib/mORMot/SynFPCLinux.pas @@ -6,7 +6,7 @@ unit SynFPCLinux; { This file is part of Synopse mORMot framework. - Synopse mORMot framework. Copyright (C) 2020 Arnaud Bouchez + Synopse mORMot framework. Copyright (C) 2022 Arnaud Bouchez Synopse Informatique - https://synopse.info *** BEGIN LICENSE BLOCK ***** @@ -25,7 +25,7 @@ unit SynFPCLinux; The Initial Developer of the Original Code is Alfred Glaenzer. - Portions created by the Initial Developer are Copyright (C) 2020 + Portions created by the Initial Developer are Copyright (C) 2022 the Initial Developer. All Rights Reserved. Contributor(s): @@ -61,6 +61,7 @@ interface uses {$ifdef LINUX} + BaseUnix, UnixType, {$endif LINUX} SysUtils; @@ -119,10 +120,26 @@ function GetLastError: longint; inline; procedure SetLastError(error: longint); inline; /// compatibility function, wrapping Win32 API text comparison -// - somewhat slow by using two temporary UnicodeString - but seldom called, -// unless our proprietary WIN32CASE collation is used in SynSQLite3 -function CompareStringW(GetThreadLocale: DWORD; dwCmpFlags: DWORD; lpString1: Pwidechar; - cchCount1: longint; lpString2: Pwidechar; cchCount2: longint): longint; +// - will use the system ICU library if available, or the widestringmanager +// - seldom called, unless our proprietary WIN32CASE collation is used in SynSQLite3 +function CompareStringW(GetThreadLocale: DWORD; dwCmpFlags: DWORD; lpString1: PWideChar; + cchCount1: integer; lpString2: PWideChar; cchCount2: integer): integer; + +/// compatibility function, wrapping Win32 API text case conversion +function CharUpperBuffW(W: PWideChar; WLen: integer): integer; + +/// compatibility function, wrapping Win32 API text case conversion +function CharLowerBuffW(W: PWideChar; WLen: integer): integer; + +/// compatibility function, wrapping Win32 MultiByteToWideChar API conversion +// - will use the system ICU library for efficient conversion +function AnsiToWideICU(codepage: cardinal; Source: PAnsiChar; Dest: PWideChar; + SourceChars: PtrInt): PtrInt; + +/// compatibility function, wrapping Win32 WideCharToMultiByte API conversion +// - will use the system ICU library for efficient conversion +function WideToAnsiICU(codepage: cardinal; Source: PWideChar; Dest: PAnsiChar; + SourceChars: PtrInt): PtrInt; /// returns the current UTC time // - will convert from clock_gettime(CLOCK_REALTIME_COARSE) if available @@ -222,7 +239,8 @@ function UnixKeyPending: boolean; type /// the libraries supported by TExternalLibrariesAPI - TExternalLibrary = (elPThread {$ifdef LINUXNOTBSD} , elSystemD {$endif}); + TExternalLibrary = ( + elPThread, elICU {$ifdef LINUXNOTBSD} , elSystemD {$endif}); /// set of libraries supported by TExternalLibrariesAPI TExternalLibraries = set of TExternalLibrary; @@ -240,6 +258,8 @@ type systemd: pointer; {$endif LINUXNOTBSD} {$endif LINUX} + icu, icudata, icui18n: pointer; + procedure LoadIcuWithVersion; procedure Done; public {$ifdef LINUXNOTBSD} @@ -254,18 +274,63 @@ type var path: TFileName; pathLength: PtrUInt): integer; cdecl; /// systemd: submit simple, plain text log entries to the system journal // - priority value can be obtained using longint(LOG_TO_SYSLOG[logLevel]) + // - WARNING: args strings processed using C printf semantic, so % is a printf + // placeholder and should be either escaped using %% or all formatting args must be passed sd_journal_print: function(priority: longint; args: array of const): longint; cdecl; + /// systemd: submit array of iov structures instead of the format string to the system journal. + // - each structure should reference one field of the entry to submit. + // - the second argument specifies the number of structures in the array. + sd_journal_sendv: function(const iov: Piovec; n: longint): longint; cdecl; /// systemd: sends notification to systemd // - see https://www.freedesktop.org/software/systemd/man/sd_notify.html // status notification sample: sd.notify(0, 'READY=1'); // watchdog notification: sd.notify(0, 'WATCHDOG=1'); - sd_notify: function(unset_environment: longint; state: PUTF8Char): longint; cdecl; + sd_notify: function(unset_environment: longint; state: PAnsiChar): longint; cdecl; /// systemd: check whether the service manager expects watchdog keep-alive // notifications from a service // - if result > 0 then usec contains the notification interval (app should // notify every usec/2) sd_watchdog_enabled: function(unset_environment: longint; usec: Puint64): longint; cdecl; {$endif LINUXNOTBSD} + /// Initialize an ICU text converter for a given encoding + ucnv_open: function (converterName: PAnsiChar; var err: SizeInt): pointer; cdecl; + /// finalize the ICU text converter for a given encoding + ucnv_close: procedure (converter: pointer); cdecl; + /// customize the ICU text converter substitute char + ucnv_setSubstChars: procedure (converter: pointer; + subChars: PAnsiChar; len: byte; var err: SizeInt); cdecl; + /// enable the ICU text converter fallback + ucnv_setFallback: procedure (cnv: pointer; usesFallback: LongBool); cdecl; + /// ICU text conversion from UTF-16 to a given encoding + ucnv_fromUChars: function (cnv: pointer; dest: PAnsiChar; destCapacity: cardinal; + src: PWideChar; srcLength: cardinal; var err: SizeInt): cardinal; cdecl; + /// ICU text conversion from a given encoding to UTF-16 + ucnv_toUChars: function (cnv: pointer; dest: PWideChar; destCapacity: cardinal; + src: PAnsiChar; srcLength: cardinal; var err: SizeInt): cardinal; cdecl; + /// ICU UTF-16 text conversion to uppercase + u_strToUpper: function (dest: PWideChar; destCapacity: cardinal; + src: PWideChar; srcLength: cardinal; locale: PAnsiChar; + var err: SizeInt): cardinal; cdecl; + /// ICU UTF-16 text conversion to lowercase + u_strToLower: function (dest: PWideChar; destCapacity: cardinal; + src: PWideChar; srcLength: cardinal; locale: PAnsiChar; + var err: SizeInt): cardinal; cdecl; + /// ICU UTF-16 text comparison + u_strCompare: function (s1: PWideChar; length1: cardinal; + s2: PWideChar; length2: cardinal; codePointOrder: LongBool): cardinal; cdecl; + /// ICU UTF-16 text comparison with options, e.g. for case-insensitive + u_strCaseCompare: function (s1: PWideChar; length1: cardinal; + s2: PWideChar; length2: cardinal; options: cardinal; + var err: SizeInt): cardinal; cdecl; + /// get the ICU data folder + u_getDataDirectory: function: PAnsiChar; cdecl; + /// set the ICU data folder + u_setDataDirectory: procedure(directory: PAnsiChar); cdecl; + /// initialize the ICU library + u_init: procedure(var status: SizeInt); cdecl; + /// Initialize an ICU text converter for a given codepage + // - returns nil if ICU is not available on this system + function ucnv(codepage: cardinal): pointer; /// thread-safe loading of a system library // - caller should then check the API function to be not nil procedure EnsureLoaded(lib: TExternalLibrary); @@ -315,7 +380,6 @@ implementation uses Classes, Unix, - BaseUnix, {$ifdef BSD} sysctl, {$else} @@ -624,18 +688,140 @@ begin fpseterrno(error); end; -function CompareStringW(GetThreadLocale: DWORD; dwCmpFlags: DWORD; lpString1: Pwidechar; - cchCount1: longint; lpString2: Pwidechar; cchCount2: longint): longint; -var U1,U2: UnicodeString; // (may be?) faster than WideString -begin // not inlined to avoid try..finally UnicodeString protection - if cchCount1<0 then +function CompareStringRTL(a, b: PWideChar; al, bl, flags: integer): integer; +var + U1, U2: UnicodeString; +begin + SetString(U1,a,al); + SetString(U2,b,bl); + result := widestringmanager.CompareUnicodeStringProc(U1,U2,TCompareOptions(flags)); +end; + +function CompareStringW(GetThreadLocale: DWORD; dwCmpFlags: DWORD; lpString1: PWideChar; + cchCount1: integer; lpString2: PWideChar; cchCount2: integer): integer; +const + U_COMPARE_CODE_POINT_ORDER = $8000; +var + err: SizeInt; +begin + if cchCount1 < 0 then cchCount1 := StrLen(lpString1); - SetString(U1,lpString1,cchCount1); - if cchCount2<0 then + if cchCount2 < 0 then cchCount2 := StrLen(lpString2); - SetString(U2,lpString2,cchCount2); - result := widestringmanager.CompareUnicodeStringProc(U1,U2,TCompareOptions(dwCmpFlags))+2; -end; // caller would make -2 to get regular -1/0/1 comparison values + with ExternalLibraries do + begin + if not (elICU in Loaded) then + EnsureLoaded(elICU); + if Assigned(ucnv_open) then + begin + err := 0; + if dwCmpFlags and NORM_IGNORECASE <> 0 then + result := u_strCaseCompare(lpString1, cchCount1, lpString2, cchCount2, + U_COMPARE_CODE_POINT_ORDER, err) + else + result := u_strCompare(lpString1, cchCount1, lpString2, cchCount2, true); + end + else + result := CompareStringRTL(lpString1, lpString2, cchCount1, cchCount2, dwCmpFlags); + end; + inc(result, 2); // caller would make -2 to get regular -1/0/1 comparison values +end; + +function CharUpperBuffW(W: PWideChar; WLen: integer): integer; +var + err: SizeInt; +begin + with ExternalLibraries do + begin + if not (elICU in Loaded) then + EnsureLoaded(elICU); + if Assigned(ucnv_open) then + begin + err := 0; + result := u_strToUpper(W, WLen, W, WLen, nil, err); + end + else + result := WLen; + end; +end; + +function CharLowerBuffW(W: PWideChar; WLen: integer): integer; +var + err: SizeInt; +begin + with ExternalLibraries do + begin + if not (elICU in Loaded) then + EnsureLoaded(elICU); + if Assigned(ucnv_open) then + begin + err := 0; + result := u_strToLower(W, WLen, W, WLen, nil, err); + end + else + result := WLen; + end; +end; + +function AnsiToWideRTL(codepage: cardinal; Source: PAnsiChar; Dest: PWideChar; + SourceChars: PtrInt): PtrInt; +var + tmp: UnicodeString; +begin + widestringmanager.Ansi2UnicodeMoveProc(Source, codepage, tmp, SourceChars); + result := length(tmp); + Move(pointer(tmp)^, Dest^, result * 2); +end; + +function AnsiToWideICU(codepage: cardinal; Source: PAnsiChar; Dest: PWideChar; + SourceChars: PtrInt): PtrInt; +var + cnv: pointer; + err: SizeInt; +begin + if codepage = CP_UTF8 then + exit(Utf8ToUnicode(Dest, Source, SourceChars)); + cnv := ExternalLibraries.ucnv(codepage); + if cnv = nil then + exit(AnsiToWideRTL(codepage, Source, Dest, SourceChars)); + err := 0; + result := ExternalLibraries.ucnv_toUChars( + cnv, Dest, SourceChars, Source, SourceChars, err); + if result < 0 then + result := 0; + ExternalLibraries.ucnv_close(cnv); +end; + +function WideToAnsiRTL(codepage: cardinal; Source: PWideChar; Dest: PAnsiChar; + SourceChars: PtrInt): PtrInt; +var + tmp: RawByteString; +begin + widestringmanager.Unicode2AnsiMoveProc(Source, tmp, codepage, SourceChars); + result := length(tmp); + Move(pointer(tmp)^, Dest^, result); +end; + +function WideToAnsiICU(codepage: cardinal; Source: PWideChar; Dest: PAnsiChar; + SourceChars: PtrInt): PtrInt; +var + cnv: pointer; + err: SizeInt; +begin + if codepage = CP_UTF8 then + // fallback to RTL + exit(UnicodeToUTF8(Dest, Source, SourceChars)); + cnv := ExternalLibraries.ucnv(codepage); + if cnv = nil then + exit(WideToAnsiRTL(codepage, Source, Dest, SourceChars)); + err := 0; + result := ExternalLibraries.ucnv_fromUChars( + cnv, Dest, SourceChars * 3, Source, SourceChars, err); + if result < 0 then + result := 0; + ExternalLibraries.ucnv_close(cnv); +end; + function GetFileSize(hFile: cInt; lpFileSizeHigh: PDWORD): DWORD; var FileInfo: TStat; @@ -732,13 +918,108 @@ end; { TExternalLibrariesAPI } +procedure TExternalLibrariesAPI.LoadIcuWithVersion; +const + NAMES: array[0..12] of string = ( + 'ucnv_open', 'ucnv_close', 'ucnv_setSubstChars', 'ucnv_setFallback', + 'ucnv_fromUChars', 'ucnv_toUChars', 'u_strToUpper', 'u_strToLower', + 'u_strCompare', 'u_strCaseCompare', 'u_getDataDirectory', + 'u_setDataDirectory', 'u_init'); +{$ifdef ANDROID} +// from https://developer.android.com/guide/topics/resources/internationalization + ICU_VER: array[1..13] of string = ( + '_3_8', '_4_2', '_44', '_46', '_48', '_50', '_51', '_53', '_55', '_56', '_58', '_60', '_63'); + SYSDATA: PAnsiChar = '/system/usr/icu'; +{$else} + SYSDATA: PAnsiChar = ''; +{$endif ANDROID} +var + i, j: integer; + err: SizeInt; + P: PPointer; + v, vers: string; + data: PAnsiChar; +begin + {$ifdef ANDROID} + for i := high(ICU_VER) downto 1 do + begin + if dlsym(icu, pointer(NAMES[0] + ICU_VER[i])) <> nil then + begin + vers := ICU_VER[i]; + break; + end; + end; + if vers <> '' then + {$endif ANDROID} + if dlsym(icu, 'ucnv_open') = nil then + for i := 80 downto 44 do + begin + str(i, v); + if dlsym(icu, pointer('ucnv_open_' + v)) <> nil then + begin + vers := '_' + v; + break; + end; + end; + P := @@ucnv_open; + for i := 0 to high(NAMES) do + begin + P[i] := dlsym(icu, pointer(NAMES[i] + vers)); + if P[i] = nil then + begin + @ucnv_open := nil; + exit; + end; + end; + data := u_getDataDirectory; + if (data = nil) or (data^ = #0) then + if SYSDATA <> '' then + u_setDataDirectory(SYSDATA); + err := 0; + u_init(err); +end; + +function TExternalLibrariesAPI.ucnv(codepage: cardinal): pointer; +var + s: shortstring; + err: SizeInt; + {$ifdef CPUINTEL} + mask: cardinal; + {$endif CPUINTEL} +begin + if not (elICU in Loaded) then + EnsureLoaded(elICU); + if not Assigned(ucnv_open) then + exit(nil); + str(codepage, s); + Move(s[1], s[3], ord(s[0])); + PWord(@s[1])^ := ord('c') + ord('p') shl 8; + inc(s[0], 3); + s[ord(s[0])] := #0; + {$ifdef CPUINTEL} + mask := GetMXCSR; + SetMXCSR(mask or $0080 {MM_MaskInvalidOp} or $1000 {MM_MaskPrecision}); + {$endif CPUINTEL} + err := 0; + result := ucnv_open(@s[1], err); + if result <> nil then + begin + err := 0; + ucnv_setSubstChars(result, '?', 1, err); + ucnv_setFallback(result, true); + end; + {$ifdef CPUINTEL} + SetMXCSR(mask); + {$endif CPUINTEL} +end; + procedure TExternalLibrariesAPI.EnsureLoaded(lib: TExternalLibrary); var p: PPointer; i, j: integer; const - NAMES: array[0..4] of PAnsiChar = ( - 'sd_listen_fds', 'sd_is_socket_unix', 'sd_journal_print', + NAMES: array[0..5] of PAnsiChar = ( + 'sd_listen_fds', 'sd_is_socket_unix', 'sd_journal_print', 'sd_journal_sendv', 'sd_notify', 'sd_watchdog_enabled'); begin if lib in Loaded then @@ -759,6 +1040,34 @@ begin {$endif LINUX} include(Loaded, elPThread); end; + elICU: + begin + {$ifdef DARWIN} + icu := dlopen('libicuuc.dylib', RTLD_LAZY); + if icu <> nil then + icui18n := dlopen('libicui18n.dylib', RTLD_LAZY); + {$else} + // libicudata should be loaded first because other two depend on it + icudata := dlopen('libicudata.so', RTLD_LAZY); + if icudata <> nil then + begin + icu := dlopen('libicuuc.so', RTLD_LAZY); + if icu <> nil then + icui18n := dlopen('libicui18n.so', RTLD_LAZY); + end; + {$endif DARWIN} + if icui18n = nil then + begin + if icu <> nil then + dlclose(icu); + if icudata <> nil then + dlclose(icudata); + end + else + // ICU append a version prefix to all its functions e.g. ucnv_open_66 + LoadIcuWithVersion; + include(Loaded, elICU); + end; {$ifdef LINUXNOTBSD} elSystemD: begin @@ -802,6 +1111,16 @@ begin dlclose(pthread); {$endif LINUX} end; + if elICU in Loaded then + begin + if icui18n <> nil then + dlclose(icui18n); + if icu <> nil then + dlclose(icu); + if icudata <> nil then + dlclose(icudata); + @ucnv_open := nil; + end; {$ifdef LINUXNOTBSD} if (elSystemD in Loaded) and (systemd <> nil) then dlclose(systemd); @@ -814,6 +1133,7 @@ procedure SetUnixThreadName(ThreadID: TThreadID; const Name: RawByteString); var trunc: array[0..15] of AnsiChar; // truncated to 16 bytes (including #0) i,L: integer; begin + {$ifdef LINUXNOTBSD} if not(elPThread in ExternalLibraries.Loaded) then ExternalLibraries.EnsureLoaded(elPThread); if not Assigned(ExternalLibraries.pthread_setname_np) then @@ -839,7 +1159,6 @@ begin if L = 0 then exit; trunc[L] := #0; - {$ifdef LINUXNOTBSD} ExternalLibraries.pthread_setname_np(pointer(ThreadID), @trunc[0]); {$endif LINUXNOTBSD} end; diff --git a/contrib/mORMot/SynFPCSock.pas b/contrib/mORMot/SynFPCSock.pas index 73a7596..6a3695f 100644 --- a/contrib/mORMot/SynFPCSock.pas +++ b/contrib/mORMot/SynFPCSock.pas @@ -6,7 +6,7 @@ unit SynFPCSock; { This file is part of Synopse framework. - Synopse framework. Copyright (C) 2020 Arnaud Bouchez + Synopse framework. Copyright (C) 2022 Arnaud Bouchez Synopse Informatique - https://synopse.info *** BEGIN LICENSE BLOCK ***** @@ -27,7 +27,7 @@ unit SynFPCSock; Portions created by Lukas Gebauer are Copyright (C) 2003. All Rights Reserved. - Portions created by Arnaud Bouchez are Copyright (C) 2020 Arnaud Bouchez. + Portions created by Arnaud Bouchez are Copyright (C) 2022 Arnaud Bouchez. All Rights Reserved. Contributor(s): diff --git a/contrib/mORMot/SynFPCTypInfo.pas b/contrib/mORMot/SynFPCTypInfo.pas index 1d3475c..42ab29a 100644 --- a/contrib/mORMot/SynFPCTypInfo.pas +++ b/contrib/mORMot/SynFPCTypInfo.pas @@ -6,7 +6,7 @@ unit SynFPCTypInfo; { This file is part of Synopse mORMot framework. - Synopse mORMot framework. Copyright (C) 2020 Arnaud Bouchez + Synopse mORMot framework. Copyright (C) 2022 Arnaud Bouchez Synopse Informatique - https://synopse.info *** BEGIN LICENSE BLOCK ***** @@ -25,7 +25,7 @@ unit SynFPCTypInfo; The Initial Developer of the Original Code is Alfred Glaenzer. - Portions created by the Initial Developer are Copyright (C) 2020 + Portions created by the Initial Developer are Copyright (C) 2022 the Initial Developer. All Rights Reserved. Contributor(s): diff --git a/contrib/mORMot/SynFPCx64MM.pas b/contrib/mORMot/SynFPCx64MM.pas index b0b1f82..c2afc55 100644 --- a/contrib/mORMot/SynFPCx64MM.pas +++ b/contrib/mORMot/SynFPCx64MM.pas @@ -9,14 +9,8 @@ unit SynFPCx64MM; A Multi-thread Friendly Memory Manager for FPC written in x86_64 assembly - targetting Linux (and Windows) multi-threaded Services - only for FPC on the x86_64 target - use the RTL MM on Delphi or ARM - - based on FastMM4 proven algorithms by Pierre le Riche - - code has been reduced to the only necessary featureset for production - - deep asm refactoring for cross-platform, compactness and efficiency + - based on proven FastMM4 by Pierre le Riche - with tuning and enhancements - can report detailed statistics (with threads contention and memory leaks) - - mremap() makes large block ReallocMem a breeze on Linux :) - - inlined SSE2 movaps loop is more efficient that subfunction(s) - - lockless round-robin of tiny blocks (<=128/256 bytes) for better scaling - - optional lockless bin list to avoid freemem() thread contention - three app modes: default mono-thread friendly, FPCMM_SERVER or FPCMM_BOOST Usage: include this unit as the very first in your FPC project uses clause @@ -28,17 +22,18 @@ unit SynFPCx64MM; - C memory managers (glibc, Intel TBB, jemalloc) have a very high RAM consumption (especially Intel TBB) and do panic/SIGKILL on any GPF - Pascal alternatives (FastMM4,ScaleMM2,BrainMM) are Windows+Delphi specific - - Our lockess round-robin of tiny blocks is a unique algorithm in MM AFAIK + - Our lockess round-robin of tiny blocks and freemem bin list are unique + algorithms among Memory Managers, and match modern CPUs and workloads - It was so fun diving into SSE2 x86_64 assembly and Pierre's insight - Resulting code is still easy to understand and maintain - IMPORTANT NOTICE: seems stable on Linux and Win64 but feedback is welcome! + DISCLAMER: seems stable on Linux and Win64 but feedback is welcome! ***************************************************************************** This file is part of Synopse framework. - Synopse framework. Copyright (C) 2020 Arnaud Bouchez + Synopse framework. Copyright (C) 2022 Arnaud Bouchez Synopse Informatique - https://synopse.info *** BEGIN LICENSE BLOCK ***** @@ -57,7 +52,7 @@ unit SynFPCx64MM; The Initial Developer of the Original Code is Arnaud Bouchez. - Portions created by the Initial Developer are Copyright (C) 2020 + Portions created by the Initial Developer are Copyright (C) 2022 the Initial Developer. All Rights Reserved. Contributor(s): @@ -80,21 +75,27 @@ unit SynFPCx64MM; { ---- Ready-To-Use Scenarios for Memory Manager Tuning } -// by default, we target LCL/console mono-threaded apps to replace the RTL MM -// - you may define FPCMM_SERVER or even FPCMM_BOOST for a service/daemon +{ + TL;DR: + 1. default settings target LCL/console mono-threaded apps; + 2. define FPCMM_SERVER for a multi-threaded service/daemon. +} -// if defined, set FPCMM_DEBUG and FPCMM_ASSUMEMULTITHREAD -// - those flags target well a multi-threaded service -// - consider FPCMM_BOOST to try more aggressive settings +// target a multi-threaded service on a modern CPU +// - define FPCMM_DEBUG, FPCMM_ASSUMEMULTITHREAD, FPCMM_ERMS, FPCMM_LOCKLESSFREE +// - currently mormot2tests run with no sleep when FPCMM_SERVER is set :) +// - you may try to define FPCMM_BOOST for even more aggressive settings. {.$define FPCMM_SERVER} -// if defined, tiny blocks <= 256 bytes will have a bigger round-robin cycle +// increase settings for very aggressive multi-threaded process // - try to enable it if unexpected SmallGetmemSleepCount/SmallFreememSleepCount -// and SleepCount/SleepCycles contentions are reported by CurrentHeapStatus -// - will also use 2x (FPCMM_BOOST) or 4x (FPCMM_BOOSTER) more tiny blocks -// arenas to share among the threads - so process will consume slightly more RAM -// - warning: depending on the workload and hardware, it may actually be slower; -// consider FPCMM_SERVER as a fair alternative +// and SleepCount/SleepCycles contentions are reported by CurrentHeapStatus; +// - tiny blocks will be <= 256 bytes (instead of 128 bytes); +// - FPCMM_BOOSTER will use 2x more tiny blocks arenas - likely to be wasteful; +// - will enable FPCMM_SMALLNOTWITHMEDIUM trying to reduce medium sleeps; +// - warning: depending on the workload and hardware, it may actually be slower, +// triggering more Meidum arena contention, and consuming more RAM: consider +// FPCMM_SERVER as a fair alternative. {.$define FPCMM_BOOST} {.$define FPCMM_BOOSTER} @@ -104,6 +105,15 @@ unit SynFPCx64MM; // includes more detailed information to WriteHeapStatus() {.$define FPCMM_DEBUG} +// on thread contention, don't spin executing "pause" but directly call Sleep() +// - may help on a single core CPU, or for very specific workloads +{.$define FPCMM_NOPAUSE} + +// let FPCMM_DEBUG include SleepCycles information from rdtsc +// and FPCMM_PAUSE call rdtsc for its spinnning loop +// - since rdtsc is emulated so unrealiable on VM, it is disabled by default +{.$define FPCMM_SLEEPTSC} + // checks leaks and write them to the console at process shutdown // - only basic information will be included: more debugging information (e.g. // call stack) may be gathered using heaptrc or valgrid @@ -117,34 +127,40 @@ unit SynFPCx64MM; // let Freemem multi-thread contention use a lockless algorithm // - on contention, Freemem won't yield the thread using an OS call, but fill // an internal Bin list which will be released when the lock becomes available -// - from our tests on high thread contention, this may be slower on Linux, but -// sometimes slightly faster on Win64 (in a VM at least) +// - beneficial from our tests on high thread contention (HTTP/REST server) {.$define FPCMM_LOCKLESSFREE} -// won't use mremap but a regular getmem/move/freemem pattern +// won't use mremap but a regular getmem/move/freemem pattern for large blocks // - depending on the actual system (e.g. on a VM), mremap may be slower +// - will disable Linux mremap() or Windows following block VirtualQuery/Alloc {.$define FPCMM_NOMREMAP} -// on contention problem, execute "pause" opcode and spin retrying the lock -// - you may try to define this if you have more than one core, to follow Intel -// recommendation from https://software.intel.com/en-us/comment/1134767 -// - on SkylakeX (Intel 7th gen), "pause" opcode went from 10-20 to 140 cycles, -// so we use rdtsc and a given number of cycles - see http://tiny.cc/toeaqz -// - from our tests on high thread contention, spinning is slower on both -// Linux and Windows, whatever Intel is advising -{.$define FPCMM_PAUSE} +// force the tiny/small blocks to be in their own arena, not with medium blocks +// - would use a little more memory, but medium pool is less likely to sleep +// - not defined for FPCMM_SERVER because no performance difference was found +{.$define FPCMM_SMALLNOTWITHMEDIUM} + +// use "rep movsb/stosd" ERMS for blocks > 256 bytes instead of SSE2 "movaps" +// - ERMS is available since Ivy Bridge, and we use "movaps" for smallest blocks +// (to not slow down older CPUs), so it is safe to enable this on FPCMM_SERVER +{.$define FPCMM_ERMS} + +// try "cmp" before "lock cmpxchg" for old processors with huge lock penalty +{.$define FPCMM_CMPBEFORELOCK} // will export libc-like functions, and not replace the FPC MM // - e.g. to use this unit as a stand-alone C memory allocator {.$define FPCMM_STANDALONE} +// this whole unit will compile as void +// - may be defined e.g. when compiled as Design-Time Lazarus package +{.$define FPCMM_DISABLE} interface {$ifdef FPC} // cut-down version of Synopse.inc to make this unit standalone {$mode Delphi} - {$asmmode Intel} {$inline on} {$R-} // disable Range checking {$S-} // disable Stack checking @@ -152,25 +168,35 @@ interface {$Q-} // disable overflow checking {$B-} // expect short circuit boolean {$ifdef CPUX64} - {$define FPC_CPUX64} // this unit is for FPC + x86_64 only + {$define FPCX64MM} // this unit is for FPC + x86_64 only + {$asmmode Intel} {$endif CPUX64} {$ifdef FPCMM_BOOSTER} {$define FPCMM_BOOST} - {$undef FPCMM_DEBUG} // when performance matters more than stats {$endif FPCMM_BOOSTER} {$ifdef FPCMM_BOOST} - {$undef FPCMM_SERVER} - {$define FPCMM_ASSUMEMULTITHREAD} + {$define FPCMM_SERVER} + {$define FPCMM_SMALLNOTWITHMEDIUM} {$endif FPCMM_BOOST} {$ifdef FPCMM_SERVER} {$define FPCMM_DEBUG} {$define FPCMM_ASSUMEMULTITHREAD} + {$define FPCMM_LOCKLESSFREE} + {$define FPCMM_ERMS} {$endif FPCMM_SERVER} + {$ifdef FPCMM_BOOSTER} + {$undef FPCMM_DEBUG} // when performance matters more than stats + {$endif FPCMM_BOOSTER} {$endif FPC} +{$ifdef FPCMM_DISABLE} + {$undef FPCX64MM} // e.g. when compiled as Design-Time Lazarus package +{$endif FPCMM_DISABLE} -{$ifdef FPC_CPUX64} + +{$ifdef FPCX64MM} // this unit is available only for FPC + X86_64 CPU +// other targets would compile as a void unit type /// Arena (middle/large) heap information as returned by CurrentHeapStatus @@ -193,32 +219,41 @@ type /// heap information as returned by CurrentHeapStatus TMMStatus = record - /// how many tiny/small memory blocks (<=2600) are currently allocated + /// how many tiny/small memory blocks (<=2600 bytes) are currently allocated SmallBlocks: PtrUInt; /// how many bytes of tiny/small memory blocks are currently allocated - // - this size is part of the Medium.CurrentBytes arena + // - this size is included in Medium.CurrentBytes value, even if + // FPCMM_SMALLNOTWITHMEDIUM has been defined SmallBlocksSize: PtrUInt; - /// contain blocks up to 256KB (small and medium blocks) + /// information about blocks up to 256KB (tiny, small and medium) + // - includes also the memory needed for tiny/small blocks Medium: TMMStatusArena; - /// large blocks > 256KB which are directly handled by the Operating System + /// information about large blocks > 256KB + // - those blocks are directly handled by the Operating System Large: TMMStatusArena; {$ifdef FPCMM_DEBUG} + {$ifdef FPCMM_SLEEPTSC} /// how much rdtsc cycles were spent within SwitchToThread/NanoSleep API - // - we rdtsc since it is an indicative but very fast way of timing + // - we rdtsc since it is an indicative but very fast way of timing on + // direct hardware + // - warning: on virtual machines, the rdtsc opcode is usually emulated so + // these SleepCycles number are non indicative anymore SleepCycles: PtrUInt; + {$endif FPCMM_SLEEPTSC} {$ifdef FPCMM_LOCKLESSFREE} - /// how many types Freemem() did spin to acquire its lock-less bin list + /// how many times Freemem() did spin to acquire its lock-less bin list SmallFreememLockLessSpin: PtrUInt; {$endif FPCMM_LOCKLESSFREE} {$endif FPCMM_DEBUG} /// how many times the Operating System Sleep/NanoSleep API was called - // - in a perfect world, should be as small as possible + // - in a perfect world, should be as small as possible: mormot2tests run as + // SleepCount=0 when FPCMM_LOCKLESSFREE is defined (FPCMM_SERVER default) SleepCount: PtrUInt; - /// how many times Getmem() did block and wait for a small block - // - see also GetSmallBlockContention() + /// how many times Getmem() did block and wait for a tiny/small block + // - see also GetSmallBlockContention() for more detailed information SmallGetmemSleepCount: PtrUInt; - /// how many times Freemem() did block and wait for a small block - // - see also GetSmallBlockContention() + /// how many times Freemem() did block and wait for a tiny/small block + // - see also GetSmallBlockContention() for more detailed information SmallFreememSleepCount: PtrUInt; end; PMMStatus = ^TMMStatus; @@ -246,6 +281,14 @@ function _MemSize(P: pointer): PtrUInt; inline; /// retrieve high-level statistics about the current memory manager state // - see also GetSmallBlockContention for detailed small blocks information +// - standard GetHeapStatus and GetFPCHeapStatus gives less accurate information +// (only CurrHeapSize and MaxHeapSize are set), since we don't track "free" heap +// bytes: I can't figure how "free" memory is relevant nowadays - on 21th century +// Operating Systems, memory is virtual, and reserved/mapped by the OS but +// physically hosted in the HW RAM chips only when written the first time - +// GetHeapStatus information made sense on MSDOS with fixed 640KB of RAM +// - note that FPC GetHeapStatus and GetFPCHeapStatus is only about the +// current thread (irrelevant for sure) whereas CurrentHeapStatus is global function CurrentHeapStatus: TMMStatus; @@ -300,12 +343,13 @@ type /// retrieve the use counts of allocated small blocks // - returns maxcount biggest results, sorted by "orderby" field occurence function GetSmallBlockStatus(maxcount: integer = 10; - orderby: TSmallBlockOrderBy = obTotal; count: PPtrUInt = nil; - bytes: PPtrUInt = nil): TSmallBlockStatusDynArray; + orderby: TSmallBlockOrderBy = obTotal; count: PPtrUInt = nil; bytes: PPtrUInt = nil; + small: PCardinal = nil; tiny: PCardinal = nil): TSmallBlockStatusDynArray; /// retrieve all small blocks which suffered from blocking during multi-thread // - returns maxcount biggest results, sorted by SleepCount occurence -function GetSmallBlockContention(maxcount: integer = 10): TSmallBlockContentionDynArray; +function GetSmallBlockContention( + maxcount: integer = 10): TSmallBlockContentionDynArray; /// convenient debugging function into the console @@ -315,68 +359,136 @@ procedure WriteHeapStatus(const context: shortstring = ''; smallblockstatuscount: integer = 8; smallblockcontentioncount: integer = 8; compilationflags: boolean = false); +/// convenient debugging function into a string +// - if smallblockcontentioncount > 0, includes GetSmallBlockContention() info +// up to the smallblockcontentioncount biggest occurences +// - warning: this function is not thread-safe +function GetHeapStatus(const context: shortstring; smallblockstatuscount, + smallblockcontentioncount: integer; compilationflags, onsameline: boolean): string; + + +const + /// human readable information about how our MM was built + // - similar to WriteHeapStatus(compilationflags=true) output + FPCMM_FLAGS = ' ' + {$ifdef FPCMM_BOOSTER} + 'BOOSTER ' {$else} + {$ifdef FPCMM_BOOST} + 'BOOST ' {$else} + {$ifdef FPCMM_SERVER} + 'SERVER ' {$endif} + {$endif} + {$endif} + {$ifdef FPCMM_ASSUMEMULTITHREAD} + ' assumulthrd' {$endif} + {$ifdef FPCMM_LOCKLESSFREE} + ' lockless' {$endif} + {$ifdef FPCMM_PAUSE} + ' pause' {$endif} + {$ifdef FPCMM_SLEEPTSC} + ' rdtsc' {$endif} + {$ifndef BSD} + {$ifdef FPCMM_NOMREMAP} + ' nomremap' {$endif} + {$endif BSD} + {$ifdef FPCMM_SMALLNOTWITHMEDIUM}+ ' smallpool' {$endif} + {$ifdef FPCMM_ERMS} + ' erms' {$endif} + {$ifdef FPCMM_DEBUG} + ' debug' {$endif} + {$ifdef FPCMM_REPORTMEMORYLEAKS} + ' repmemleak' {$endif}; + {$endif FPCMM_STANDALONE} -{$endif FPC_CPUX64} +{$endif FPCX64MM} implementation { - High-level Algorithms Description - ----------------------------------- + High-level Allocation Strategy Description + -------------------------------------------- The allocator handles the following families of memory blocks: - - TINY <= 128 B (or <= 256 B for FPCMM_BOOST) - not existing in FastMM4 - Round-robin distribution into several arenas, fed from medium blocks + - TINY <= 128 B (<= 256 B for FPCMM_BOOST) + Round-robin distribution into several arenas, fed from shared tiny/small pool (fair scaling from multi-threaded calls, with no threadvar nor GC involved) - - SMALL <= 2600 B - Single arena per block size, fed from medium blocks + - SMALL <= 2600 B + One arena per block size, fed from shared tiny/small pool - MEDIUM <= 256 KB - Pool of bitmap-marked chunks, fed from 1MB of OS mmap/virtualalloc + Separated pool of bitmap-marked chunks, fed from 1MB of OS mmap/virtualalloc - LARGE > 256 KB Directly fed from OS mmap/virtualalloc with mremap when growing + The original FastMM4 was enhanced as such, especially in FPCMM_SERVER mode: + - FPC compatibility, even on POSIX/Linux, also for FPC specific API behavior; + - x86_64 code was refactored and tuned in regard to 2020's hardware; + - Inlined SSE2 movaps loop or ERMS are more efficient that subfunction(s); + - New round-robin thread-friendly arenas of tiny blocks; + - Tiny and small blocks can fed from their own pool, not the medium pool; + - Additional bin list to reduce small/tiny Freemem() thread contention; + - Memory leaks and thread sleep tracked with almost no performance loss; + - Large blocks logic has been rewritten, especially realloc; + - On Linux, mremap is used for efficient realloc of large blocks. + About locking: - - Tiny and Small blocks have their own per-size lock, in every arena - - Medium and Large blocks have one giant lock each (seldom used) - - SwitchToThread/FpNanoSleep OS call is done after initial spinning - - FPCMM_LOCKLESSFREE reduces OS calls on Freemem() thread contention - - FPCMM_DEBUG / WriteHeapStatus allows to identify the lock contention + - Tiny and Small blocks have their own per-size lock; + - Tiny and Small blocks have one giant lock when fedding from their pool; + - Medium blocks have one giant lock over their own pool; + - Large blocks have one giant lock over mmap/virtualalloc system calls; + - SwitchToThread/FpNanoSleep OS call is done after initial spinning; + - FPCMM_LOCKLESSFREE reduces Freemem() thread contention; + - FPCMM_DEBUG / WriteHeapStatus helps identifying the lock contention(s). } -{$ifdef FPC_CPUX64} +{$ifdef FPCX64MM} // this unit is available only for FPC + X86_64 CPU +{$ifndef FPCMM_NOPAUSE} + // on contention problem, execute "pause" opcode and spin retrying the lock + // - defined by default to follow Intel recommendatations from + // https://software.intel.com/content/www/us/en/develop/articles/benefitting-power-and-performance-sleep-loops.html + // - spinning loop is either using constants or rdtsc (if FPCMM_SLEEPTSC is set) + // - on SkylakeX (Intel 7th gen), "pause" opcode went from 10-20 to 140 cycles + // so our constants below will favor those latest CPUs with a longer pause + {$define FPCMM_PAUSE} +{$endif FPCMM_NOPAUSE} + { ********* Operating System Specific API Calls } {$ifdef MSWINDOWS} -var - HeapStatus: TMMStatus; +// Win64: any assembler function with sub-calls should have a stack frame +// -> nostackframe is defined only on Linux or for functions with no nested call +{$undef NOSFRAME} const kernel32 = 'kernel32.dll'; - MEM_COMMIT = $1000; + MEM_COMMIT = $1000; MEM_RESERVE = $2000; - MEM_RELEASE = $8000; - MEM_FREE = $10000; + MEM_RELEASE = $8000; + MEM_FREE = $10000; MEM_TOP_DOWN = $100000; PAGE_READWRITE = 4; + PAGE_GUARD = $0100; + PAGE_VALID = $00e6; // PAGE_READONLY or PAGE_READWRITE or PAGE_EXECUTE or + // PAGE_EXECUTE_READ or PAGE_EXECUTE_READWRITE or PAGE_EXECUTE_WRITECOPY + +type + // VirtualQuery() API result structure + TMemInfo = record + BaseAddress, AllocationBase: pointer; + AllocationProtect: cardinal; + PartitionId: word; + RegionSize: PtrUInt; + State, Protect, MemType: cardinal; + end; function VirtualAlloc(lpAddress: pointer; - dwSize: PtrUInt; flAllocationType, flProtect: Cardinal): pointer; stdcall; - external kernel32 name 'VirtualAlloc'; + dwSize: PtrUInt; flAllocationType, flProtect: cardinal): pointer; + stdcall; external kernel32 name 'VirtualAlloc'; function VirtualFree(lpAddress: pointer; dwSize: PtrUInt; - dwFreeType: Cardinal): LongBool; stdcall; - external kernel32 name 'VirtualFree'; -procedure SwitchToThread; stdcall; - external kernel32 name 'SwitchToThread'; + dwFreeType: cardinal): LongBool; + stdcall; external kernel32 name 'VirtualFree'; +procedure SwitchToThread; + stdcall; external kernel32 name 'SwitchToThread'; +function VirtualQuery(lpAddress, lpMemInfo: pointer; dwLength: PtrUInt): PtrUInt; + stdcall; external kernel32 name 'VirtualQuery'; function AllocMedium(Size: PtrInt): pointer; inline; begin @@ -386,16 +498,54 @@ end; function AllocLarge(Size: PtrInt): pointer; inline; begin - // top-down allocation to reduce fragmentation + // top-down allocation of large blocks to reduce fragmentation + // (MEM_TOP_DOWN is not available on POSIX, but seems less needed) result := VirtualAlloc(nil, Size, MEM_COMMIT or MEM_TOP_DOWN, PAGE_READWRITE); end; -procedure Free(ptr: pointer; Size: PtrInt); inline; +procedure FreeMediumLarge(ptr: pointer; Size: PtrInt); inline; begin VirtualFree(ptr, 0, MEM_RELEASE); end; -{$define FPCMM_NOMREMAP} +{$ifndef FPCMM_NOMREMAP} + +function RemapLarge(addr: pointer; old_len, new_len: size_t): pointer; +var + meminfo: TMemInfo; + next: pointer; + nextsize: PtrUInt; +begin + // old_len and new_len have 64KB granularity, so match Windows page size + nextsize := new_len - old_len; + if PtrInt(nextsize) > 0 then + begin + // try to allocate the memory just after the existing one + FillChar(meminfo, SizeOf(meminfo), 0); + next := addr + old_len; + if (VirtualQuery(next, @meminfo, SizeOf(meminfo)) = SizeOf(meminfo)) and + (meminfo.State = MEM_FREE) and + (meminfo.RegionSize >= nextsize) and // enough space? + // reserve the address space in two steps for thread safety + (VirtualAlloc(next, nextsize, MEM_RESERVE, PAGE_READWRITE) <> nil) and + (VirtualAlloc(next, nextsize, MEM_COMMIT, PAGE_READWRITE) <> nil) then + begin + result := addr; // in-place realloc: no need to move memory :) + exit; + end; + end; + // we need to use the slower but safe Alloc/Move/Free pattern + result := AllocLarge(new_len); + if new_len > old_len then + new_len := old_len; // handle size up or down + Move(addr^, result^, new_len); // RTL non-volatile asm or our AVX MoveFast() + FreeMediumLarge(addr, old_len); +end; + +{$endif FPCMM_NOMREMAP} + +// experimental VirtualQuery detection of object class - use at your own risk +{$define FPCMM_REPORTMEMORYLEAKS_EXPERIMENTAL} {$else} @@ -405,10 +555,17 @@ uses {$endif DARWIN} BaseUnix; -var - HeapStatus: TMMStatus; +// in practice, SYSV ABI seems to not require a stack frame, as Win64 does, for +// our use case of nested calls with no local stack storage and direct kernel +// syscalls - but since it is clearly undocumented, we set it on LINUX only +// -> appears to work with no problem from our tests: feedback is welcome! +// -> see FPCMM_NOSFRAME conditional to disable it on LINUX +{$ifdef LINUX} + {$define NOSFRAME} +{$endif LINUX} -// we directly call the Kernel, so this unit doesn't require any libc + +// we directly call the OS Kernel, so this unit doesn't require any libc function AllocMedium(Size: PtrInt): pointer; inline; begin @@ -418,14 +575,12 @@ end; function AllocLarge(Size: PtrInt): pointer; inline; begin - result := fpmmap(nil, Size, PROT_READ or PROT_WRITE, - MAP_PRIVATE or MAP_ANONYMOUS, -1, 0); + result := AllocMedium(size); // same API (no MEM_TOP_DOWN option) end; -procedure Free(ptr: pointer; Size: PtrInt); inline; +procedure FreeMediumLarge(ptr: pointer; Size: PtrInt); inline; begin - Size := fpmunmap(ptr, Size); - // assert(Size = 0); + fpmunmap(ptr, Size); end; {$ifdef LINUX} @@ -436,21 +591,26 @@ const syscall_nr_mremap = 25; // valid on x86_64 Linux and Android MREMAP_MAYMOVE = 1; -function fpmremap(addr: pointer; old_len, new_len: size_t; may_move: longint): pointer; inline; +function RemapLarge(addr: pointer; old_len, new_len: size_t): pointer; inline; begin + // let the Linux Kernel mremap() the memory using its TLB magic result := pointer(do_syscall(syscall_nr_mremap, TSysParam(addr), - TSysParam(old_len), TSysParam(new_len), TSysParam(may_move))); + TSysParam(old_len), TSysParam(new_len), TSysParam(MREMAP_MAYMOVE))); end; {$endif FPCMM_NOMREMAP} -{$else BSD} +// experimental detection of object class - use at your own risk +{$define FPCMM_REPORTMEMORYLEAKS_EXPERIMENTAL} +// (untested on BSD/DARWIN) + +{$else} {$define FPCMM_NOMREMAP} // mremap is a Linux-specific syscall {$endif LINUX} -procedure SwitchToThread; inline; +procedure SwitchToThread; var t: Ttimespec; begin @@ -462,11 +622,38 @@ end; {$endif MSWINDOWS} +// fallback to safe and simple Alloc/Move/Free pattern +{$ifdef FPCMM_NOMREMAP} + +function RemapLarge(addr: pointer; old_len, new_len: size_t): pointer; +begin + result := AllocLarge(new_len); + if new_len > old_len then + new_len := old_len; // resize down + Move(addr^, result^, new_len); // RTL non-volatile asm or our AVX MoveFast() + FreeMediumLarge(addr, old_len); +end; + +{$endif FPCMM_NOMREMAP} + + +{ ********* Some Assembly Helpers } + +// low-level conditional to disable nostackframe code on Linux +{$ifdef FPCMM_NOSFRAME} + {$undef NOSFRAME} +{$endif FPCMM_NOSFRAME} + +var + HeapStatus: TMMStatus; + {$ifdef FPCMM_DEBUG} -procedure ReleaseCore; nostackframe; assembler; +procedure ReleaseCore; + {$ifdef NOSFRAME} nostackframe; {$endif} assembler; asm - rdtsc + {$ifdef FPCMM_SLEEPTSC} + rdtsc // returns the TSC in EDX:EAX shl rdx, 32 or rax, rdx push rax @@ -477,8 +664,12 @@ asm or rax, rdx lea rdx, [rip + HeapStatus] sub rax, rcx - lock xadd qword ptr [rdx + TMMStatus.SleepCycles], rax - lock inc qword ptr [rdx + TMMStatus.SleepCount] + lock add qword ptr [rdx + TMMStatus.SleepCycles], rax + {$else} + call SwitchToThread + lea rdx, [rip + HeapStatus] + {$endif FPCMM_SLEEPTSC} + lock inc qword ptr [rdx + TMMStatus.SleepCount] end; {$else} @@ -491,79 +682,63 @@ end; {$endif FPCMM_DEBUG} - -{ ********* Some Assembly Helpers } - -procedure NotifyAlloc(var Arena: TMMStatusArena; Size: PtrUInt); +procedure NotifyArenaAlloc(var Arena: TMMStatusArena; Size: PtrUInt); nostackframe; assembler; asm - mov rax, Size - lock xadd qword ptr [Arena].TMMStatusArena.CurrentBytes, rax - lock xadd qword ptr [Arena].TMMStatusArena.CumulativeBytes, Size {$ifdef FPCMM_DEBUG} - lock inc qword ptr [Arena].TMMStatusArena.CumulativeAlloc + lock add qword ptr [Arena].TMMStatusArena.CurrentBytes, Size + lock add qword ptr [Arena].TMMStatusArena.CumulativeBytes, Size + lock inc qword ptr [Arena].TMMStatusArena.CumulativeAlloc mov rax, qword ptr [Arena].TMMStatusArena.CurrentBytes cmp rax, qword ptr [Arena].TMMStatusArena.PeakBytes jbe @s mov qword ptr [Arena].TMMStatusArena.PeakBytes, rax -@s: {$endif FPCMM_DEBUG} +@s: {$else} + add qword ptr [Arena].TMMStatusArena.CurrentBytes, Size + add qword ptr [Arena].TMMStatusArena.CumulativeBytes, Size + {$endif FPCMM_DEBUG} end; -procedure NotifyFree(var Arena: TMMStatusArena; Size: PtrUInt); +procedure NotifyMediumLargeFree(var Arena: TMMStatusArena; Size: PtrUInt); nostackframe; assembler; asm neg Size - lock xadd qword ptr [Arena].TMMStatusArena.CurrentBytes, Size {$ifdef FPCMM_DEBUG} - lock inc qword ptr [Arena].TMMStatusArena.CumulativeFree + lock add qword ptr [Arena].TMMStatusArena.CurrentBytes, Size + lock inc qword ptr [Arena].TMMStatusArena.CumulativeFree + {$else} + add qword ptr [Arena].TMMStatusArena.CurrentBytes, Size {$endif FPCMM_DEBUG} end; -// faster than Move() as called from ReallocateLargeBlock -procedure MoveLarge(src, dst: pointer; cnt: PtrInt); nostackframe; assembler; -asm - sub cnt, 8 - add src, cnt - add dst, cnt - neg cnt - jns @z - align 16 -@s: movaps xmm0, oword ptr [src + cnt] // AVX move is not really faster - movntdq oword ptr [dst + cnt], xmm0 // non-temporal loop - add cnt, 16 - js @s - sfence -@z: mov rax, qword ptr [src + cnt] - mov qword ptr [dst + cnt], rax -end; - - { ********* Constants and Data Structures Definitions } const - {$ifdef FPCMM_BOOST} // someimtes the more arenas, the better multi-threadable + // (sometimes) the more arenas, the better multi-threadable {$ifdef FPCMM_BOOSTER} - NumTinyBlockTypesPO2 = 4; - NumTinyBlockArenasPO2 = 5; // will probably end up with Medium lock contention + NumTinyBlockTypesPO2 = 4; + NumTinyBlockArenasPO2 = 4; // will probably end up with Medium lock contention {$else} - NumTinyBlockTypesPO2 = 4; // tiny are <= 256 bytes - NumTinyBlockArenasPO2 = 4; // 16 + 1 arenas + {$ifdef FPCMM_BOOST} + NumTinyBlockTypesPO2 = 4; // tiny are <= 256 bytes + NumTinyBlockArenasPO2 = 3; // 8 arenas + {$else} + // default (or FPCMM_SERVER) settings + NumTinyBlockTypesPO2 = 3; // multiple arenas for tiny blocks <= 128 bytes + NumTinyBlockArenasPO2 = 3; // 8 round-robin arenas (including main) by default + {$endif FPCMM_BOOST} {$endif FPCMM_BOOSTER} - {$else} - NumTinyBlockTypesPO2 = 3; // multiple arenas for tiny blocks <= 128 bytes - NumTinyBlockArenasPO2 = 3; // 8 round-robin arenas + 1 main by default - {$endif FPCMM_BOOST} NumSmallBlockTypes = 46; MaximumSmallBlockSize = 2608; SmallBlockSizes: array[0..NumSmallBlockTypes - 1] of word = ( - 16, 32, 48, 64, 80, 96, 112, 128, 144, 160, 176, 192, 208, 224, 240, 256, - 272, 288, 304, 320, 352, 384, 416, 448, 480, 528, 576, 624, 672, 736, 800, - 880, 960, 1056, 1152, 1264, 1376, 1504, 1648, 1808, 1984, 2176, 2384, - MaximumSmallBlockSize, MaximumSmallBlockSize, MaximumSmallBlockSize); + 16, 32, 48, 64, 80, 96, 112, 128, 144, 160, 176, 192, 208, 224, 240, 256, + 272, 288, 304, 320, 352, 384, 416, 448, 480, 528, 576, 624, 672, 736, 800, + 880, 960, 1056, 1152, 1264, 1376, 1504, 1648, 1808, 1984, 2176, 2384, + MaximumSmallBlockSize, MaximumSmallBlockSize, MaximumSmallBlockSize); NumTinyBlockTypes = 1 shl NumTinyBlockTypesPO2; - NumTinyBlockArenas = 1 shl NumTinyBlockArenasPO2; + NumTinyBlockArenas = (1 shl NumTinyBlockArenasPO2) - 1; // -1 = main Small[] NumSmallInfoBlock = NumSmallBlockTypes + NumTinyBlockArenas * NumTinyBlockTypes; SmallBlockGranularity = 16; TargetSmallBlocksPerPool = 48; @@ -571,10 +746,10 @@ const SmallBlockDownsizeCheckAdder = 64; SmallBlockUpsizeAdder = 32; {$ifdef FPCMM_LOCKLESSFREE} - SmallBlockTypePO2 = 8; // SizeOf(TSmallBlockType)=256 - SmallBlockBinCount = (((1 shl SmallBlockTypePO2) - 64) div 8) - 1; + SmallBlockTypePO2 = 8; // SizeOf(TSmallBlockType)=256 with Bin list + SmallBlockBinCount = (((1 shl SmallBlockTypePO2) - 64) div 8) - 1; // =23 {$else} - SmallBlockTypePO2 = 6; + SmallBlockTypePO2 = 6; // SizeOf(TSmallBlockType)=64 {$endif FPCMM_LOCKLESSFREE} MediumBlockPoolSizeMem = 20 * 64 * 1024; @@ -593,7 +768,6 @@ const 64 * 1024 - MediumBlockGranularity + MediumBlockSizeOffset; MaximumSmallBlockPoolSize = OptimalSmallBlockPoolSizeUpperLimit + MinimumMediumBlockSize; - LargeBlockGranularity = 65536; MediumInPlaceDownsizeLimit = MinimumMediumBlockSize div 4; IsFreeBlockFlag = 1; @@ -607,29 +781,45 @@ const DropMediumAndLargeFlagsMask = -16; ExtractMediumAndLargeFlagsMask = 15; - // use pause before ReleaseCore API call when spinning locks - // pause is 140 cycles since SkylakeX - see http://tiny.cc/010ioz -> use rdtsc - // which has 30 cycles latency; ring3 to ring 0 transition is 1000 cycles + {$ifdef FPCMM_SLEEPTSC} + // pause using rdtsc (30 cycles latency on hardware but emulated on VM) + SpinMediumLockTSC = 10000; + SpinLargeLockTSC = 10000; {$ifdef FPCMM_PAUSE} SpinSmallGetmemLockTSC = 1000; SpinSmallFreememLockTSC = 1000; // _freemem has more collisions {$ifdef FPCMM_LOCKLESSFREE} SpinSmallFreememBinTSC = 2000; {$endif FPCMM_LOCKLESSFREE} - SpinMediumLockTSC = 2000; - SpinLargeLockTSC = 2000; - {$else} - SpinMediumLockTSC = 1000; // minimum spinning - SpinLargeLockTSC = 1000; {$endif FPCMM_PAUSE} + {$else} + // pause with constant spinning counts (empirical values from fastmm4-avx) + SpinMediumLockCount = 2500; + SpinLargeLockCount = 5000; + {$ifdef FPCMM_PAUSE} + SpinSmallGetmemLockCount = 500; + SpinSmallFreememLockCount = 500; + {$ifdef FPCMM_LOCKLESSFREE} + SpinFreememBinCount = 500; + {$endif FPCMM_LOCKLESSFREE} + {$endif FPCMM_PAUSE} + {$endif FPCMM_SLEEPTSC} + + {$ifdef FPCMM_ERMS} + // pre-ERMS expects at least 256 bytes, IvyBridge+ with ERMS is good from 64 + // see https://stackoverflow.com/a/43837564/458259 for explanations and timing + // -> "movaps" loop is used up to 256 bytes of data: good on all CPUs + // -> "movnt" Move/MoveFast is used for large blocks: always faster than ERMS + ErmsMinSize = 256; + {$endif FPCMM_ERMS} type PSmallBlockPoolHeader = ^TSmallBlockPoolHeader; // information for each small block size - 64/256 bytes long >= CPU cache line TSmallBlockType = record - BlockTypeLocked: boolean; - AllowedGroupsForBlockPoolBitmap: Byte; + Locked: boolean; + AllowedGroupsForBlockPoolBitmap: byte; BlockSize: Word; MinimumBlockPoolSize: Word; OptimalBlockPoolSize: Word; @@ -642,8 +832,8 @@ type FreememCount: cardinal; GetmemSleepCount: cardinal; FreememSleepCount: cardinal; - {$ifdef FPCMM_LOCKLESSFREE} // 192 optional bytes for FreeMem Bin - BinLocked: boolean; + {$ifdef FPCMM_LOCKLESSFREE} // 192 optional bytes for FreeMem Bin (= 13KB) + BinLocked: boolean; // dedicated lock for less contention BinCount: byte; BinSpinCount: cardinal; BinInstance: array[0.. SmallBlockBinCount - 1] of pointer; @@ -669,12 +859,12 @@ type BlockType: PSmallBlockType; {$ifdef CPU32} Padding32Bits: cardinal; - {$endif} + {$endif CPU32} NextPartiallyFreePool: PSmallBlockPoolHeader; PreviousPartiallyFreePool: PSmallBlockPoolHeader; FirstFreeBlock: pointer; - BlocksInUse: Cardinal; - SmallBlockPoolSignature: Cardinal; + BlocksInUse: cardinal; + SmallBlockPoolSignature: cardinal; FirstBlockPoolPointerAndFlags: PtrUInt; end; @@ -692,24 +882,44 @@ type NextFreeBlock: PMediumFreeBlock; end; + // medium locks occurs at getmem: freemem bin list got MaxCount=0 :( + {.$define FPCMM_LOCKLESSFREEMEDIUM} + +{$ifdef FPCMM_LOCKLESSFREEMEDIUM} +const + MediumBlockLocklessBinCount = 255; + +type + // used by TMediumBlockInfo to reduce thread pressure + TMediumLocklessBin = record + Locked: boolean; // dedicated lock for less contention + Count: byte; + MaxCount: byte; + Instance: array[0 .. MediumBlockLocklessBinCount - 1] of pointer; + end; +{$endif FPCMM_LOCKLESSFREEMEDIUM} + TMediumBlockInfo = record Locked: boolean; PoolsCircularList: TMediumBlockPoolHeader; LastSequentiallyFed: pointer; - SequentialFeedBytesLeft: Cardinal; - BinGroupBitmap: Cardinal; + SequentialFeedBytesLeft: cardinal; + BinGroupBitmap: cardinal; {$ifndef FPCMM_ASSUMEMULTITHREAD} IsMultiThreadPtr: PBoolean; // safe access to IsMultiThread global variable {$endif FPCMM_ASSUMEMULTITHREAD} - BinBitmaps: array[0..MediumBlockBinGroupCount - 1] of Cardinal; + BinBitmaps: array[0..MediumBlockBinGroupCount - 1] of cardinal; Bins: array[0..MediumBlockBinCount - 1] of TMediumFreeBlock; + {$ifdef FPCMM_LOCKLESSFREEMEDIUM} + LocklessBin: TMediumLocklessBin; + {$endif FPCMM_LOCKLESSFREEMEDIUM} end; PLargeBlockHeader = ^TLargeBlockHeader; TLargeBlockHeader = record PreviousLargeBlockHeader: PLargeBlockHeader; NextLargeBlockHeader: PLargeBlockHeader; - Reserved1: PtrUInt; + Reserved: PtrUInt; BlockSizeAndFlags: PtrUInt; end; @@ -718,10 +928,16 @@ const SmallBlockPoolHeaderSize = SizeOf(TSmallBlockPoolHeader); MediumBlockPoolHeaderSize = SizeOf(TMediumBlockPoolHeader); LargeBlockHeaderSize = SizeOf(TLargeBlockHeader); + LargeBlockGranularity = 65536; var SmallBlockInfo: TSmallBlockInfo; MediumBlockInfo: TMediumBlockInfo; + SmallMediumBlockInfo: TMediumBlockInfo + {$ifdef FPCMM_SMALLNOTWITHMEDIUM} ; + {$else} absolute MediumBlockInfo; + {$endif FPCMM_SMALLNOTWITHMEDIUM} + LargeBlocksLocked: boolean; LargeBlocksCircularList: TLargeBlockHeader; @@ -729,9 +945,16 @@ var { ********* Shared Routines } -procedure LockMediumBlocks; nostackframe; assembler; +{$define FPCMM_CMPBEFORELOCK_SPIN} +// during spinning, there is clearly thread contention: in this case, plain +// "cmp" before "lock cmpxchg" is mandatory to leverage the CPU cores + + +procedure LockMediumBlocks; + {$ifdef NOSFRAME} nostackframe; {$endif} assembler; +// on input/output: r10=TMediumBlockInfo asm - // on input/output: r10=MediumBlockInfo + {$ifdef FPCMM_SLEEPTSC} @s: rdtsc // tsc in edx:eax shl rdx, 32 lea r9, [rax + rdx + SpinMediumLockTSC] // r9 = endtsc @@ -741,14 +964,22 @@ asm or rax, rdx cmp rax, r9 ja @rc // timeout + {$else} +@s: mov edx, SpinMediumLockCount +@sp: pause + dec edx + jz @rc //timeout + {$endif FPCMM_SLEEPTSC} mov rcx, r10 mov eax, $100 + {$ifdef FPCMM_CMPBEFORELOCK_SPIN} cmp byte ptr [r10].TMediumBlockInfo.Locked, true je @sp + {$endif FPCMM_CMPBEFORELOCK_SPIN} lock cmpxchg byte ptr [rcx].TMediumBlockInfo.Locked, ah je @ok jmp @sp -@rc: push rsi // preserve POSIX ABI registers +@rc: push rsi // preserve POSIX and Win64 ABI registers push rdi push r10 push r11 @@ -758,14 +989,15 @@ asm pop rdi pop rsi lea rax, [rip + HeapStatus] - lock inc qword ptr [rax].TMMStatus.Medium.SleepCount + {$ifdef FPCMM_DEBUG} lock {$endif} + inc qword ptr [rax].TMMStatus.Medium.SleepCount jmp @s @ok: end; procedure InsertMediumBlockIntoBin; nostackframe; assembler; +// rcx=P edx=blocksize r10=TMediumBlockInfo - even on POSIX asm - // rcx=MediumFreeBlock edx=MediumBlockSize r10=MediumBlockInfo - even on POSIX mov rax, rcx // Get the bin number for this block size sub edx, MinimumMediumBlockSize @@ -806,7 +1038,7 @@ end; procedure RemoveMediumFreeBlock; nostackframe; assembler; asm - // rcx=MediumFreeBlock r10=MediumBlockInfo - even on POSIX + // rcx=MediumFreeBlock r10=TMediumBlockInfo - even on POSIX // Get the current previous and next blocks mov rdx, TMediumFreeBlock[rcx].PreviousFreeBlock mov rcx, TMediumFreeBlock[rcx].NextFreeBlock @@ -836,14 +1068,15 @@ asm @Done: end; -procedure BinMediumSequentialFeedRemainder; nostackframe; assembler; +procedure BinMediumSequentialFeedRemainder( + var Info: TMediumBlockInfo); nostackframe; assembler; asm - // r10=MediumBlockInfo - even on POSIX - mov eax, [r10 + TMediumBlockInfo.SequentialFeedBytesLeft] + mov r10, Info + mov eax, [Info + TMediumBlockInfo.SequentialFeedBytesLeft] test eax, eax jz @Done // Is the last fed sequentially block free? - mov rax, [r10 + TMediumBlockInfo.LastSequentiallyFed] + mov rax, [Info + TMediumBlockInfo.LastSequentiallyFed] test byte ptr [rax - BlockHeaderSize], IsFreeBlockFlag jnz @LastBlockFedIsFree // Set the "previous block is free" flag in the last block fed @@ -861,7 +1094,7 @@ asm cmp edx, MinimumMediumBlockSize jb @Done mov rcx, rax - call InsertMediumBlockIntoBin // rcx=APMediumFreeBlock, edx=AMediumBlockSize + jmp InsertMediumBlockIntoBin // rcx=APMediumFreeBlock, edx=AMediumBlockSize @Done: ret @LastBlockFedIsFree: // Drop the flags @@ -888,46 +1121,48 @@ end; procedure FreeMedium(ptr: PMediumBlockPoolHeader); begin - Free(ptr, MediumBlockPoolSizeMem); - NotifyFree(HeapStatus.Medium, MediumBlockPoolSizeMem); + FreeMediumLarge(ptr, MediumBlockPoolSizeMem); + NotifyMediumLargeFree(HeapStatus.Medium, MediumBlockPoolSizeMem); end; -function AllocNewSequentialFeedMediumPool(blocksize: Cardinal): pointer; +function AllocNewSequentialFeedMediumPool(BlockSize: cardinal; + var Info: TMediumBlockInfo): pointer; var old: PMediumBlockPoolHeader; new: pointer; begin - BinMediumSequentialFeedRemainder; + BinMediumSequentialFeedRemainder(Info); new := AllocMedium(MediumBlockPoolSizeMem); - with MediumblockInfo do if new <> nil then begin - old := PoolsCircularList.NextMediumBlockPoolHeader; - PMediumBlockPoolHeader(new).PreviousMediumBlockPoolHeader := @PoolsCircularList; - PoolsCircularList.NextMediumBlockPoolHeader := new; + old := Info.PoolsCircularList.NextMediumBlockPoolHeader; + PMediumBlockPoolHeader(new).PreviousMediumBlockPoolHeader := @Info.PoolsCircularList; + Info.PoolsCircularList.NextMediumBlockPoolHeader := new; PMediumBlockPoolHeader(new).NextMediumBlockPoolHeader := old; old.PreviousMediumBlockPoolHeader := new; PPtrUInt(PByte(new) + MediumBlockPoolSize - BlockHeaderSize)^ := IsMediumBlockFlag; - SequentialFeedBytesLeft := - (MediumBlockPoolSize - MediumBlockPoolHeaderSize) - blocksize; - result := pointer(PByte(new) + MediumBlockPoolSize - blocksize); - LastSequentiallyFed := result; - PPtrUInt(PByte(result) - BlockHeaderSize)^ := blocksize or IsMediumBlockFlag; - NotifyAlloc(HeapStatus.Medium, MediumBlockPoolSizeMem); + Info.SequentialFeedBytesLeft := + (MediumBlockPoolSize - MediumBlockPoolHeaderSize) - BlockSize; + result := pointer(PByte(new) + MediumBlockPoolSize - BlockSize); + Info.LastSequentiallyFed := result; + PPtrUInt(PByte(result) - BlockHeaderSize)^ := BlockSize or IsMediumBlockFlag; + NotifyArenaAlloc(HeapStatus.Medium, MediumBlockPoolSizeMem); end else begin - SequentialFeedBytesLeft := 0; + Info.SequentialFeedBytesLeft := 0; result := nil; end; end; -procedure LockLargeBlocks; nostackframe; assembler; +procedure LockLargeBlocks; + {$ifdef NOSFRAME} nostackframe; {$endif} assembler; asm @s: mov eax, $100 lea rcx, [rip + LargeBlocksLocked] lock cmpxchg byte ptr [rcx], ah je @ok + {$ifdef FPCMM_SLEEPTSC} rdtsc shl rdx, 32 lea r9, [rax + rdx + SpinLargeLockTSC] // r9 = endtsc @@ -937,21 +1172,30 @@ asm or rax, rdx cmp rax, r9 ja @rc // timeout + {$else} + mov edx, SpinLargeLockCount +@sp: pause + dec edx + jz @rc // timeout + {$endif FPCMM_SLEEPTSC} mov eax, $100 - cmp byte ptr [rcx], ah // don't flush the CPU cache if Locked still true + {$ifdef FPCMM_CMPBEFORELOCK_SPIN} + cmp byte ptr [rcx], true je @sp + {$endif FPCMM_CMPBEFORELOCK_SPIN} lock cmpxchg byte ptr [rcx], ah je @ok jmp @sp @rc: call ReleaseCore lea rax, [rip + HeapStatus] - lock inc qword ptr [rax].TMMStatus.Large.SleepCount + {$ifdef FPCMM_DEBUG} lock {$endif} + inc qword ptr [rax].TMMStatus.Large.SleepCount jmp @s -@ok: +@ok: // reset the stack frame before ret end; function AllocateLargeBlockFrom(size: PtrUInt; - existing: pointer; oldsize: PtrUInt): pointer; + existing: pointer; oldsize: PtrUInt): pointer; var blocksize: PtrUInt; header, old: PLargeBlockHeader; @@ -961,16 +1205,12 @@ begin if existing = nil then header := AllocLarge(blocksize) else - {$ifdef FPCMM_NOMREMAP} - header := nil; // paranoid - {$else} - header := fpmremap(existing, oldsize, blocksize, MREMAP_MAYMOVE); - {$endif FPCMM_NOMREMAP} + header := RemapLarge(existing, oldsize, blocksize); if header <> nil then begin - NotifyAlloc(HeapStatus.Large, blocksize); + NotifyArenaAlloc(HeapStatus.Large, blocksize); if existing <> nil then - NotifyFree(HeapStatus.Large, oldsize); + NotifyMediumLargeFree(HeapStatus.Large, oldsize); header.BlockSizeAndFlags := blocksize or IsLargeBlockFlag; LockLargeBlocks; old := LargeBlocksCircularList.NextLargeBlockHeader; @@ -991,8 +1231,8 @@ end; procedure FreeLarge(ptr: PLargeBlockHeader; size: PtrUInt); begin - NotifyFree(HeapStatus.Large, size); - Free(ptr, size); + NotifyMediumLargeFree(HeapStatus.Large, size); + FreeMediumLarge(ptr, size); end; function FreeLargeBlock(p: pointer): PtrInt; @@ -1001,7 +1241,8 @@ var begin header := pointer(PByte(p) - LargeBlockHeaderSize); if header.BlockSizeAndFlags and IsFreeBlockFlag <> 0 then - begin // try to duplicate the same pointer twice + begin + // try to release the same pointer twice result := 0; exit; end; @@ -1018,11 +1259,11 @@ end; function ReallocateLargeBlock(p: pointer; size: PtrUInt): pointer; var oldavail, minup, new: PtrUInt; - {$ifndef FPCMM_NOMREMAP} prev, next, {$endif} header: PLargeBlockHeader; + prev, next, header: PLargeBlockHeader; begin header := pointer(PByte(p) - LargeBlockHeaderSize); oldavail := (DropMediumAndLargeFlagsMask and header^.BlockSizeAndFlags) - - (LargeBlockHeaderSize + BlockHeaderSize); + (LargeBlockHeaderSize + BlockHeaderSize); new := size; if size > oldavail then begin @@ -1045,111 +1286,36 @@ begin // size-down and move just the trailing data oldavail := size; end; - {$ifdef FPCMM_NOMREMAP} - // no mremap(): reallocate a new block, copy the existing data, free old - result := _GetMem(new); - if result <> nil then - MoveLarge(p, result, oldavail); - _FreeMem(p); - {$else} - // remove from current chain list - LockLargeBlocks; - prev := header^.PreviousLargeBlockHeader; - next := header^.NextLargeBlockHeader; - next.PreviousLargeBlockHeader := prev; - prev.NextLargeBlockHeader := next; - LargeBlocksLocked := False; - // let the Linux Kernel mremap() the memory using its TLB magic - size := DropMediumAndLargeFlagsMask and header^.BlockSizeAndFlags; - result := AllocateLargeBlockFrom(new, header, size); - {$endif FPCMM_NOMREMAP} + if new < MaximumMediumBlockSize then + begin + // size was reduced to a small/medium block: use GetMem/Move/FreeMem + result := _GetMem(new); + if result <> nil then + Move(p^, result^, oldavail); // RTL non-volatile asm or our AVX MoveFast() + _FreeMem(p); + end + else + begin + // remove large block from current chain list + LockLargeBlocks; + prev := header^.PreviousLargeBlockHeader; + next := header^.NextLargeBlockHeader; + next.PreviousLargeBlockHeader := prev; + prev.NextLargeBlockHeader := next; + LargeBlocksLocked := False; + size := DropMediumAndLargeFlagsMask and header^.BlockSizeAndFlags; + // on Linux, call Kernel mremap() and its TLB magic + // on Windows, try to reserve the memory block just after the existing + // otherwise, use Alloc/Move/Free pattern, with asm/AVX move + result := AllocateLargeBlockFrom(new, header, size); + end; end; { ********* Main Memory Manager Functions } -procedure LockGetMem; nostackframe; assembler; -asm - // Can use one of the several arenas reserved for tiny blocks? - cmp ecx, SizeOf(TTinyBlockTypes) - jae @NotTinyBlockType - { ---------- TINY (size<=128B) block lock ---------- } -@LockTinyBlockTypeLoop: - // Round-Robin attempt to lock of SmallBlockInfo.Tiny[] - // -> fair distribution among calls to reduce thread contention - mov edx, NumTinyBlockArenas -@TinyBlockArenaLoop: - mov eax, SizeOf(TTinyBlockTypes) - lock xadd dword ptr [r8 + TSmallBlockInfo.TinyCurrentArena], eax - and eax, (NumTinyBlockArenas * Sizeof(TTinyBlockTypes)) - 1 - add rax, rcx - lea rbx, [r8 + rax].TSmallBlockInfo.Tiny - mov eax, $100 - cmp [rbx].TSmallBlockType.BlockTypeLocked, ah - je @NextTinyBlockArena - lock cmpxchg byte ptr [rbx].TSmallBlockType.BlockTypeLocked, ah - jne @NextTinyBlockArena -@GotLockOnTinyBlockType: - ret -@NextTinyBlockArena: - dec edx - jnz @TinyBlockArenaLoop - // Also try the default SmallBlockInfo.Small[] - lea rbx, [r8 + rcx] - mov eax, $100 - lock cmpxchg byte ptr [rbx].TSmallBlockType.BlockTypeLocked, ah - je @GotLockOnTinyBlockType - // Thread Contention (occurs much less than during _Freemem) - lock inc dword ptr [rbx].TSmallBlockType.GetmemSleepCount - push r8 - push rcx - call ReleaseCore - pop rcx - pop r8 - jmp @LockTinyBlockTypeLoop - { ---------- SMALL (size<2600) block lock ---------- } -@NotTinyBlockType: - lea rbx, [r8 + rcx].TSmallBlockInfo.Small -@LockBlockTypeLoopRetry: - {$ifdef FPCMM_PAUSE} - rdtsc - shl rdx, 32 - lea r9, [rax + rdx + SpinSmallGetmemLockTSC] // r9 = endtsc - {$endif FPCMM_PAUSE} -@LockBlockTypeLoop: - // Grab the default block type - mov eax, $100 - lock cmpxchg byte ptr [rbx].TSmallBlockType.BlockTypeLocked, ah - jne @LockNextSmallBlockType -@GotLockOnSmallBlockType: - ret -@LockNextSmallBlockType: - // Try up to two next sizes - add rbx, SizeOf(TSmallBlockType) - mov eax, $100 - lock cmpxchg byte ptr [rbx].TSmallBlockType.BlockTypeLocked, ah - je @GotLockOnSmallBlockType - pause - add rbx, SizeOf(TSmallBlockType) - mov eax, $100 - lock cmpxchg byte ptr [rbx].TSmallBlockType.BlockTypeLocked, ah - je @GotLockOnSmallBlockType - sub rbx, 2 * SizeOf(TSmallBlockType) - {$ifdef FPCMM_PAUSE} - pause - rdtsc - shl rdx, 32 - or rax, rdx - cmp rax, r9 - jb @LockBlockTypeLoop // no timeout yet - {$endif FPCMM_PAUSE} - // Block type and two sizes larger are all locked - give up and sleep - lock inc dword ptr [rbx].TSmallBlockType.GetmemSleepCount - call ReleaseCore - jmp @LockBlockTypeLoopRetry -end; - -function _GetMem(size: PtrUInt): pointer; nostackframe; assembler; +function _GetMem(size: PtrUInt): pointer; + {$ifdef NOSFRAME} nostackframe; {$endif} assembler; asm {$ifndef MSWINDOWS} mov rcx, size @@ -1161,12 +1327,12 @@ asm // Since most allocations are for small blocks, determine small block type lea rbx, [rip + SmallBlockInfo] @VoidSizeToSomething: - lea rdx, [rcx + BlockHeaderSize - 1] + lea rdx, [size + BlockHeaderSize - 1] shr rdx, 4 // div SmallBlockGranularity // Is it a tiny/small block? - cmp rcx, (MaximumSmallBlockSize - BlockHeaderSize) + cmp size, (MaximumSmallBlockSize - BlockHeaderSize) ja @NotTinySmallBlock - test rcx, rcx + test size, size jz @VoidSize {$ifndef FPCMM_ASSUMEMULTITHREAD} mov rax, qword ptr [rbx].TSmallBlockInfo.IsMultiThreadPtr @@ -1175,25 +1341,133 @@ asm movzx ecx, byte ptr [rbx + rdx].TSmallBlockInfo.GetmemLookup mov r8, rbx shl ecx, SmallBlockTypePO2 - // Acquire block type lock - {$ifdef FPCMM_ASSUMEMULTITHREAD} - call LockGetMem + // ---------- Acquire block type lock ---------- + {$ifndef FPCMM_ASSUMEMULTITHREAD} + cmp byte ptr [rax], false + je @GotLockOnSmallBlock // no lock if IsMultiThread=false + {$endif FPCMM_ASSUMEMULTITHREAD} + // Can use one of the several arenas reserved for tiny blocks? + cmp ecx, SizeOf(TTinyBlockTypes) + jae @NotTinyBlockType + // ---------- TINY (size<=128B) block lock ---------- +@LockTinyBlockTypeLoop: + // Round-Robin attempt to lock next SmallBlockInfo.Tiny[] + // -> fair distribution among calls to reduce thread contention + mov dl, NumTinyBlockArenas + 1 // 8/16 arenas (including Small[]) +@TinyBlockArenaLoop: + mov eax, SizeOf(TTinyBlockTypes) + // note: "lock xadd" decreases the loop iterations but is slower + xadd dword ptr [r8 + TSmallBlockInfo.TinyCurrentArena], eax + lea rbx, [r8 + rcx] + and eax, ((NumTinyBlockArenas + 1) * SizeOf(TTinyBlockTypes)) - 1 + jz @TinySmall // Arena 0 = TSmallBlockInfo.Small[] + lea rbx, [rax + rbx + TSmallBlockInfo.Tiny - SizeOf(TTinyBlockTypes)] +@TinySmall: + mov eax, $100 + {$ifdef FPCMM_CMPBEFORELOCK} + cmp byte ptr [rbx].TSmallBlockType.Locked, false + jnz @NextTinyBlockArena1 + {$endif FPCMM_CMPBEFORELOCK} + lock cmpxchg byte ptr [rbx].TSmallBlockType.Locked, ah + je @GotLockOnSmallBlockType +@NextTinyBlockArena1: + dec dl + jnz @TinyBlockArenaLoop + // Fallback to SmallBlockInfo.Small[] next 2 small sizes - never occurs + lea rbx, [r8 + rcx + TSmallBlockInfo.Small + SizeOf(TSmallBlockType)] + mov eax, $100 + lock cmpxchg byte ptr [rbx].TSmallBlockType.Locked, ah + je @GotLockOnSmallBlockType + add rbx, SizeOf(TSmallBlockType) // next two small sizes + mov eax, $100 + lock cmpxchg byte ptr [rbx].TSmallBlockType.Locked, ah + je @GotLockOnSmallBlockType + // Thread Contention (_Freemem is more likely) + {$ifdef FPCMM_DEBUG} lock {$endif} + inc dword ptr [rbx].TSmallBlockType.GetmemSleepCount + push r8 + push rcx + call ReleaseCore + pop rcx + pop r8 + jmp @LockTinyBlockTypeLoop + // ---------- SMALL (size<2600) block lock ---------- +@NotTinyBlockType: + lea rbx, [r8 + rcx].TSmallBlockInfo.Small +@LockBlockTypeLoopRetry: + {$ifdef FPCMM_PAUSE} + {$ifdef FPCMM_SLEEPTSC} + rdtsc + shl rdx, 32 + lea r9, [rax + rdx + SpinSmallGetmemLockTSC] // r9 = endtsc {$else} - cmp byte ptr [rax], 0 - jne @CheckTinySmallLock + mov edx, SpinSmallGetmemLockCount + {$endif FPCMM_SLEEPTSC} + {$endif FPCMM_PAUSE} +@LockBlockTypeLoop: + // Grab the default block type + mov eax, $100 + {$ifdef FPCMM_CMPBEFORELOCK} + cmp byte ptr [rbx].TSmallBlockType.Locked, false + jnz @NextLockBlockType1 + {$endif FPCMM_CMPBEFORELOCK} + lock cmpxchg byte ptr [rbx].TSmallBlockType.Locked, ah + je @GotLockOnSmallBlockType + // Try up to two next sizes + mov eax, $100 +@NextLockBlockType1: + add rbx, SizeOf(TSmallBlockType) + {$ifdef FPCMM_CMPBEFORELOCK} + cmp byte ptr [rbx].TSmallBlockType.Locked, al + jnz @NextLockBlockType2 + {$endif FPCMM_CMPBEFORELOCK} + lock cmpxchg byte ptr [rbx].TSmallBlockType.Locked, ah + je @GotLockOnSmallBlockType + mov eax, $100 +@NextLockBlockType2: + add rbx, SizeOf(TSmallBlockType) + pause + {$ifdef FPCMM_CMPBEFORELOCK} + cmp byte ptr [rbx].TSmallBlockType.Locked, al + jnz @NextLockBlockType3 + {$endif FPCMM_CMPBEFORELOCK} + lock cmpxchg byte ptr [rbx].TSmallBlockType.Locked, ah + je @GotLockOnSmallBlockType +@NextLockBlockType3: + sub rbx, 2 * SizeOf(TSmallBlockType) + {$ifdef FPCMM_PAUSE} + pause + {$ifdef FPCMM_SLEEPTSC} + rdtsc + shl rdx, 32 + or rax, rdx + cmp rax, r9 + jb @LockBlockTypeLoop // continue spinning until timeout + {$else} + dec edx + jnz @LockBlockTypeLoop // continue until spin count reached + {$endif FPCMM_SLEEPTSC} + {$endif FPCMM_PAUSE} + // Block type and two sizes larger are all locked - give up and sleep + {$ifdef FPCMM_DEBUG} lock {$endif} + inc dword ptr [rbx].TSmallBlockType.GetmemSleepCount + call ReleaseCore + jmp @LockBlockTypeLoopRetry + // ---------- TINY/SMALL block registration ---------- + {$ifndef FPCMM_ASSUMEMULTITHREAD} +@GotLockOnSmallBlock: add rbx, rcx {$endif FPCMM_ASSUMEMULTITHREAD} - { ---------- TINY/SMALL block registration ---------- } @GotLockOnSmallBlockType: // set rdx=NextPartiallyFreePool rax=FirstFreeBlock rcx=DropSmallFlagsMask mov rdx, [rbx].TSmallBlockType.NextPartiallyFreePool - inc [rbx].TSmallBlockType.GetmemCount + add [rbx].TSmallBlockType.GetmemCount, 1 mov rax, [rdx].TSmallBlockPoolHeader.FirstFreeBlock mov rcx, DropSmallFlagsMask // Is there a pool with free blocks? cmp rdx, rbx je @TrySmallSequentialFeed - inc [rdx].TSmallBlockPoolHeader.BlocksInUse + add [rdx].TSmallBlockPoolHeader.BlocksInUse, 1 // Set the new first free block and the block header and rcx, [rax - BlockHeaderSize] mov [rdx].TSmallBlockPoolHeader.FirstFreeBlock, rcx @@ -1201,21 +1475,16 @@ asm // Is the chunk now full? jz @RemoveSmallPool // Unlock the block type and leave - mov byte ptr [rbx].TSmallBlockType.BlockTypeLocked, false -@Done: pop rbx - {$ifdef MSWINDOWS} - pop rdi - pop rsi - {$endif MSWINDOWS} + mov byte ptr [rbx].TSmallBlockType.Locked, false + {$ifdef NOSFRAME} + pop rbx ret + {$else} + jmp @Done // on Win64, a stack frame is required + {$endif NOSFRAME} @VoidSize: - inc ecx // "we always need to allocate something" (see RTL heap.inc) + inc size // "we always need to allocate something" (see RTL heap.inc) jmp @VoidSizeToSomething - {$ifndef FPCMM_ASSUMEMULTITHREAD} -@CheckTinySmallLock: - call LockGetMem - jmp @GotLockOnSmallBlockType - {$endif FPCMM_ASSUMEMULTITHREAD} @TrySmallSequentialFeed: // Feed a small block sequentially movzx ecx, [rbx].TSmallBlockType.BlockSize @@ -1226,37 +1495,37 @@ asm ja @AllocateSmallBlockPool // Adjust number of used blocks and sequential feed pool mov [rbx].TSmallBlockType.NextSequentialFeedBlockAddress, rcx - inc [rdx].TSmallBlockPoolHeader.BlocksInUse + add [rdx].TSmallBlockPoolHeader.BlocksInUse, 1 // Unlock the block type, set the block header and leave - mov byte ptr [rbx].TSmallBlockType.BlockTypeLocked, false + mov byte ptr [rbx].TSmallBlockType.Locked, false mov [rax - BlockHeaderSize], rdx + {$ifdef NOSFRAME} pop rbx - {$ifdef MSWINDOWS} - pop rdi - pop rsi - {$endif MSWINDOWS} ret + {$else} + jmp @Done // on Win64, a stack frame is required + {$endif NOSFRAME} @RemoveSmallPool: // Pool is full - remove it from the partially free list mov rcx, [rdx].TSmallBlockPoolHeader.NextPartiallyFreePool mov [rcx].TSmallBlockPoolHeader.PreviousPartiallyFreePool, rbx mov [rbx].TSmallBlockType.NextPartiallyFreePool, rcx // Unlock the block type and leave - mov byte ptr [rbx].TSmallBlockType.BlockTypeLocked, false + mov byte ptr [rbx].TSmallBlockType.Locked, false + {$ifdef NOSFRAME} pop rbx - {$ifdef MSWINDOWS} - pop rdi - pop rsi - {$endif MSWINDOWS} ret + {$else} + jmp @Done // on Win64, a stack frame is required + {$endif NOSFRAME} @AllocateSmallBlockPool: // Access shared information about Medium blocks storage - lea rcx, [rip + MediumBlockInfo] + lea rcx, [rip + SmallMediumBlockInfo] mov r10, rcx {$ifndef FPCMM_ASSUMEMULTITHREAD} mov rax, [rcx + TMediumBlockinfo.IsMultiThreadPtr] cmp byte ptr [rax], false - je @MediumLocked1 + je @MediumLocked1 // no lock if IsMultiThread=false {$endif FPCMM_ASSUMEMULTITHREAD} mov eax, $100 lock cmpxchg byte ptr [rcx].TMediumBlockInfo.Locked, ah @@ -1333,22 +1602,31 @@ asm jmp @GotMediumBlock @AllocateNewSequentialFeed: // Use the optimal size for allocating this small block pool - movzx size, word ptr [rbx].TSmallBlockType.OptimalBlockPoolSize - push size // use "size" variable = first argument in current ABI call + {$ifdef MSWINDOWS} + movzx ecx, word ptr [rbx].TSmallBlockType.OptimalBlockPoolSize + lea rdx, [rip + SmallMediumBlockInfo] + push rcx + push rdx + {$else} + movzx edi, word ptr [rbx].TSmallBlockType.OptimalBlockPoolSize + lea rsi, [rip + SmallMediumBlockInfo] + push rdi + push rsi + {$endif MSWINDOWS} call AllocNewSequentialFeedMediumPool - pop rdi // restore edi=blocksize and r10=MediumBlockInfo - lea r10, [rip + MediumBlockInfo] + pop r10 + pop rdi // restore edi=blocksize and r10=TMediumBlockInfo mov rsi, rax test rax, rax jnz @GotMediumBlock // rsi=freeblock rbx=blocktype edi=blocksize mov [r10 + TMediumBlockInfo.Locked], al - mov [rbx].TSmallBlockType.BlockTypeLocked, al - {$ifdef MSWINDOWS} - jmp @Done - {$else} + mov [rbx].TSmallBlockType.Locked, al + {$ifdef NOSFRAME} pop rbx ret - {$endif MSWINDOWS} + {$else} + jmp @Done // on Win64, a stack frame is required + {$endif NOSFRAME} @UseWholeBlock: // rsi = free block, rbx = block type, edi = block size // Mark this block as used in the block following it @@ -1374,15 +1652,15 @@ asm sub rdi, rcx mov [rbx].TSmallBlockType.MaxSequentialFeedBlockAddress, rdi // Unlock the small block type, set header and leave - mov byte ptr [rbx].TSmallBlockType.BlockTypeLocked, false + mov byte ptr [rbx].TSmallBlockType.Locked, false mov [rax - BlockHeaderSize], rsi + {$ifdef NOSFRAME} pop rbx - {$ifdef MSWINDOWS} - pop rdi - pop rsi - {$endif MSWINDOWS} ret - { ---------- MEDIUM block allocation ---------- } + {$else} + jmp @Done // on Win64, a stack frame is required + {$endif NOSFRAME} + // ---------- MEDIUM block allocation ---------- @NotTinySmallBlock: // Do we need a Large block? lea r10, [rip + MediumBlockInfo] @@ -1396,7 +1674,7 @@ asm {$ifndef FPCMM_ASSUMEMULTITHREAD} mov rax, [r10 + TMediumBlockinfo.IsMultiThreadPtr] cmp byte ptr [rax], false - je @MediumLocked2 + je @MediumLocked2 // no lock if IsMultiThread=false {$endif FPCMM_ASSUMEMULTITHREAD} mov eax, $100 lock cmpxchg byte ptr [rcx].TMediumBlockInfo.Locked, ah @@ -1408,7 +1686,7 @@ asm mov ecx, edx shr edx, 8 + 5 shr ecx, 8 - mov eax, - 1 + mov eax, -1 shl eax, cl and eax, [r10 + TMediumBlockInfo.BinBitmaps + rdx * 4] jz @GroupIsEmpty @@ -1444,22 +1722,28 @@ asm or rbx, IsMediumBlockFlag mov [rax - BlockHeaderSize], rbx mov byte ptr [r10 + TMediumBlockInfo.Locked], false - {$ifdef MSWINDOWS} - jmp @Done - {$else} + {$ifdef NOSFRAME} pop rbx ret - {$endif MSWINDOWS} + {$else} + jmp @Done // on Win64, a stack frame is required + {$endif NOSFRAME} @AllocateNewSequentialFeedForMedium: - mov size, rbx // 'size' variable is the first argument register in ABI call - call AllocNewSequentialFeedMediumPool - mov byte ptr [rip + MediumBlockInfo.Locked], false // r10 has been overwritten {$ifdef MSWINDOWS} - jmp @Done + mov ecx, ebx + lea rdx, [rip + MediumBlockInfo] {$else} + mov edi, ebx + lea rsi, [rip + MediumBlockInfo] + {$endif MSWINDOWS} + call AllocNewSequentialFeedMediumPool + mov byte ptr [rip + MediumBlockInfo.Locked], false + {$ifdef NOSFRAME} pop rbx ret - {$endif MSWINDOWS} + {$else} + jmp @Done // on Win64, a stack frame is required + {$endif NOSFRAME} @GotBinAndGroup: // ebx = block size, ecx = bin number, edx = group number // Compute rdi = @bin, rsi = free block @@ -1473,7 +1757,7 @@ asm // Is this bin now empty? cmp rdi, rax jne @MediumBinNotEmptyForMedium - // edx = bin group number, ecx = bin number, rdi = @bin, rsi = free block, ebx = block size + // edx=bingroupnumber, ecx=binnumber, rdi=@bin, rsi=freeblock, ebx=blocksize // Flag this bin and group as empty mov eax, - 2 mov r11d, [r10 + TMediumBlockInfo.BinGroupBitmap] @@ -1504,39 +1788,40 @@ asm @UseWholeBlockForMedium: // Mark this block as used in the block following it and byte ptr [rsi + rdi - BlockHeaderSize], NOT PreviousMediumBlockIsFreeFlag - @GotMediumBlockForMedium: +@GotMediumBlockForMedium: // Set the size and flags for this block lea rcx, [rbx + IsMediumBlockFlag] mov [rsi - BlockHeaderSize], rcx // Unlock medium blocks and leave mov byte ptr [r10 + TMediumBlockInfo.Locked], false mov rax, rsi - {$ifdef MSWINDOWS} - jmp @Done - {$else} + {$ifdef NOSFRAME} pop rbx ret - {$endif MSWINDOWS} - { ---------- LARGE block allocation ---------- } + {$else} + jmp @Done // on Win64, a stack frame is required + {$endif NOSFRAME} + // ---------- LARGE block allocation ---------- @IsALargeBlockRequest: xor rax, rax test rcx, rcx - js @DoneLarge + js @Done // Note: size is still in the rcx/rdi first param register call AllocateLargeBlock -@DoneLarge: - {$ifdef MSWINDOWS} - jmp @Done - {$else} +@Done: // restore registers and the stack frame before ret pop rbx + {$ifdef MSWINDOWS} + pop rdi + pop rsi {$endif MSWINDOWS} end; -function FreeMediumBlock(arg1: pointer): PtrUInt; nostackframe; assembler; -// rcx=P rdx=[P-BlockHeaderSize] +function FreeMediumBlock(arg1: pointer): PtrUInt; + {$ifdef NOSFRAME} nostackframe; {$endif} assembler; +// rcx=P rdx=[P-BlockHeaderSize] r10=TMediumBlockInfo +// (arg1 is used only for proper call of pascal functions below on all ABI) asm - // Drop the flags, set r10=MediumBlockInfo r11=P rbx=blocksize - lea r10, [rip + MediumBlockInfo] + // Drop the flags, and set r11=P rbx=blocksize and rdx, DropMediumAndLargeFlagsMask push rbx push rdx // save blocksize @@ -1547,11 +1832,39 @@ asm {$ifndef FPCMM_ASSUMEMULTITHREAD} mov rax, [r10 + TMediumBlockinfo.IsMultiThreadPtr] cmp byte ptr [rax], false - je @MediumBlocksLocked + je @MediumBlocksLocked // no lock if IsMultiThread=false {$endif FPCMM_ASSUMEMULTITHREAD} mov eax, $100 lock cmpxchg byte ptr [rcx].TMediumBlockInfo.Locked, ah je @MediumBlocksLocked + {$ifdef FPCMM_LOCKLESSFREEMEDIUM} + // locked: try to put r11=P in TMediumBlockInfo.LocklessBin.Instance[] + lea rcx, [rcx].TMediumBlockInfo.LocklessBin + cmp byte ptr [rcx].TMediumLocklessBin.Count, MediumBlockLocklessBinCount + je @DoLock // all slots are filled + mov r9d, SpinFreememBinCount +@BinSp: mov eax, $100 + lock cmpxchg byte ptr [rcx].TMediumLocklessBin.Locked, ah + je @BinOk +@BinNo: pause + dec r9d + jnz @BinSp + jmp @DoLock +@BinOk: // we acquired TMediumLocklessBin.Locked + movzx eax, byte ptr [rcx].TMediumLocklessBin.Count + cmp al, MediumBlockLocklessBinCount + je @DoLoc2 + add byte ptr [rcx].TMediumLocklessBin.Count, 1 + + cmp al, byte ptr [rcx].TMediumLocklessBin.MaxCount + jb @max + mov byte ptr [rcx].TMediumLocklessBin.MaxCount, al +@max: + mov [rcx + TMediumLocklessBin.Instance + rax * 8], r11 + mov byte ptr [rcx].TMediumLocklessBin.Locked, false + jmp @Quit +@DoLoc2:mov byte ptr [rcx].TMediumLocklessBin.Locked, false +@DoLock:{$endif FPCMM_LOCKLESSFREEMEDIUM} call LockMediumBlocks @MediumBlocksLocked: // Get rcx = next block size and flags @@ -1570,19 +1883,33 @@ asm // Check if entire medium block pool is free cmp ebx, (MediumBlockPoolSize - MediumBlockPoolHeaderSize) je @EntireMediumPoolFree -@BinFreeMediumBlock: - // Store size of the block, flags and trailing size marker and insert into bin +@Bin: // Store size of the block, flags and trailing size marker and insert into bin lea rax, [rbx + IsMediumBlockFlag + IsFreeBlockFlag] mov [r11 - BlockHeaderSize], rax mov [r11 + rbx - 16], rbx mov rcx, r11 mov rdx, rbx - call InsertMediumBlockIntoBin // rcx=APMediumFreeBlock, edx=AMediumBlockSize + call InsertMediumBlockIntoBin // rcx=P, edx=blocksize + {$ifdef FPCMM_LOCKLESSFREEMEDIUM} + // recycle any pending TMediumLocklessBin.Instance[] pointer + lea rcx, [r10].TMediumBlockInfo.LocklessBin + cmp byte ptr [rcx].TMediumLocklessBin.Count, 0 + je @Done + mov eax, $100 + lock cmpxchg byte ptr [rcx].TMediumLocklessBin.Locked, ah // just try once + jne @Done + // compute r11=P and rbx=blocksize from pending pointer + movzx eax, byte ptr [rcx].TMediumLocklessBin.Count + dec byte ptr [rcx].TMediumLocklessBin.Count + mov r11, [rcx + TMediumLocklessBin.Instance - 8 + rax * 8] + mov byte ptr [rcx].TMediumLocklessBin.Locked, false + mov rbx, qword ptr [r11 - BlockHeaderSize] + and rbx, DropMediumAndLargeFlagsMask + jmp @MediumBlocksLocked +@Done: {$endif FPCMM_LOCKLESSFREEMEDIUM} // Unlock medium blocks and leave mov byte ptr [r10 + TMediumBlockInfo.Locked], false - pop rax // medium block size - pop rbx - ret + jmp @Quit @NextBlockIsFree: // Get rax = next block address, rbx = end of the block lea rax, [r11 + rbx] @@ -1619,27 +1946,65 @@ asm mov byte ptr [r10 + TMediumBlockInfo.Locked], false mov arg1, r11 call FreeMedium - pop rax // medium block size - pop rbx - ret + jmp @Quit @MakeEmptyMediumPoolSequentialFeed: // Get rbx = end-marker block, and recycle the current sequential feed pool lea rbx, [r11 + MediumBlockPoolSize - MediumBlockPoolHeaderSize] + mov arg1, r10 call BinMediumSequentialFeedRemainder // Set this medium pool up as the new sequential feed pool, unlock and leave mov qword ptr [rbx - BlockHeaderSize], IsMediumBlockFlag mov dword ptr [r10 + TMediumBlockInfo.SequentialFeedBytesLeft], MediumBlockPoolSize - MediumBlockPoolHeaderSize mov [r10 + TMediumBlockInfo.LastSequentiallyFed], rbx mov byte ptr [r10 + TMediumBlockInfo.Locked], false - pop rax +@Quit: // restore registers and the stack frame + pop rax // medium block size pop rbx end; -procedure FreeSmallLocked; nostackframe; assembler; -// rbx=TSmallBlockType rcx=P rdx=TSmallBlockPoolHeader +{$ifdef FPCMM_REPORTMEMORYLEAKS} +const + /// mark freed blocks with 00000000 BLODLESS marker to track incorrect usage + REPORTMEMORYLEAK_FREEDHEXSPEAK = $B10D1E55; +{$endif FPCMM_REPORTMEMORYLEAKS} + +function _FreeMem(P: pointer): PtrUInt; + {$ifdef NOSFRAME} nostackframe; {$endif} assembler; asm + {$ifdef FPCMM_REPORTMEMORYLEAKS} + mov eax, REPORTMEMORYLEAK_FREEDHEXSPEAK // 00000000 BLODLESS marker + {$endif FPCMM_REPORTMEMORYLEAKS} + {$ifndef MSWINDOWS} + mov rcx, P + {$endif MSWINDOWS} + test P, P + jz @Quit // void pointer + {$ifdef FPCMM_REPORTMEMORYLEAKS} + mov qword ptr [P], rax // over TObject VMT or string/dynarray header + {$endif FPCMM_REPORTMEMORYLEAKS} + mov rdx, qword ptr [P - BlockHeaderSize] + {$ifdef FPCMM_ASSUMEMULTITHREAD} + mov eax, $100 + {$else} + mov rax, qword ptr [rip + SmallBlockInfo].TSmallBlockInfo.IsMultiThreadPtr + {$endif FPCMM_ASSUMEMULTITHREAD} + // Is it a small block in use? + test dl, IsFreeBlockFlag + IsMediumBlockFlag + IsLargeBlockFlag + jnz @NotSmallBlockInUse + // Get the small block type in rbx and try to grab it + push rbx + mov rbx, [rdx].TSmallBlockPoolHeader.BlockType + {$ifdef FPCMM_ASSUMEMULTITHREAD} + lock cmpxchg byte ptr [rbx].TSmallBlockType.Locked, ah + jne @CheckTinySmallLock + {$else} + cmp byte ptr [rax], false + jne @TinySmallLockLoop // lock if IsMultiThread=true + {$endif FPCMM_ASSUMEMULTITHREAD} +@FreeAndUnlock: + // rbx=TSmallBlockType rcx=P rdx=TSmallBlockPoolHeader // Adjust number of blocks in use, set rax = old first free block - inc [rbx].TSmallBlockType.FreememCount + add [rbx].TSmallBlockType.FreememCount, 1 mov rax, [rdx].TSmallBlockPoolHeader.FirstFreeBlock sub [rdx].TSmallBlockPoolHeader.BlocksInUse, 1 jz @PoolIsNowEmpty @@ -1663,8 +2028,14 @@ asm cmp byte ptr [rbx].TSmallBlockType.BinCount, 0 jne @ProcessPendingBin {$endif FPCMM_LOCKLESSFREE} - mov byte ptr [rbx].TSmallBlockType.BlockTypeLocked, false + mov byte ptr [rbx].TSmallBlockType.Locked, false + movzx eax, word ptr [rbx].TSmallBlockType.BlockSize + {$ifdef NOSFRAME} + pop rbx ret + {$else} + jmp @Done // on Win64, a stack frame is required + {$endif NOSFRAME} @PoolIsNowEmpty: // FirstFreeBlock=nil means it is the sequential feed pool with a single block test rax, rax @@ -1682,21 +2053,28 @@ asm mov [rbx].TSmallBlockType.MaxSequentialFeedBlockAddress, rax @NotSequentialFeedPool: // Unlock blocktype and release this pool - mov byte ptr [rbx].TSmallBlockType.BlockTypeLocked, false + mov byte ptr [rbx].TSmallBlockType.Locked, false mov rcx, rdx mov rdx, qword ptr [rdx - BlockHeaderSize] - jmp FreeMediumBlock // no call nor BinLocked to avoid race condition + lea r10, [rip + SmallMediumBlockInfo] + call FreeMediumBlock // no call nor BinLocked to avoid race condition + movzx eax, word ptr [rbx].TSmallBlockType.BlockSize + {$ifdef NOSFRAME} + pop rbx + ret + {$else} + jmp @Done // on Win64, a stack frame is required + {$endif NOSFRAME} {$ifdef FPCMM_LOCKLESSFREE} @ProcessPendingBin: - // Try twice to acquire BinLocked (spinning now may induce race condition) - mov eax, $100 - lock cmpxchg byte ptr [rbx].TSmallBlockType.BinLocked, ah - je @BinLocked - pause + // Try once to acquire BinLocked (spinning may induce race condition) + {$ifdef FPCMM_CMPBEFORELOCK} + cmp byte ptr [rbx].TSmallBlockType.BinLocked, true + je @BinAlreadyLocked + {$endif FPCMM_CMPBEFORELOCK} mov eax, $100 lock cmpxchg byte ptr [rbx].TSmallBlockType.BinLocked, ah jne @BinAlreadyLocked -@BinLocked: movzx eax, byte ptr [rbx].TSmallBlockType.BinCount test al, al jz @NoBin @@ -1705,107 +2083,102 @@ asm dec byte ptr [rbx].TSmallBlockType.BinCount mov byte ptr [rbx].TSmallBlockType.BinLocked, false mov rdx, [rcx - BlockHeaderSize] - jmp FreeSmallLocked + jmp @FreeAndUnlock // loop until BinCount=0 or BinLocked @NoBin: mov byte ptr [rbx].TSmallBlockType.BinLocked, false @BinAlreadyLocked: - mov byte ptr [rbx].TSmallBlockType.BlockTypeLocked, false + mov byte ptr [rbx].TSmallBlockType.Locked, false {$endif FPCMM_LOCKLESSFREE} -end; - -function _FreeMem(P: pointer): PtrUInt; nostackframe; assembler; -asm - xor eax, eax - {$ifndef MSWINDOWS} - mov rcx, P - {$endif MSWINDOWS} - test P, P - jz @VoidPointer - {$ifdef FPCMM_REPORTMEMORYLEAKS} - mov qword ptr [P], rax // reset TObject VMT or string/dynarray header - {$endif FPCMM_REPORTMEMORYLEAKS} - mov rdx, qword ptr [P - BlockHeaderSize] - {$ifndef FPCMM_ASSUMEMULTITHREAD} - mov rax, qword ptr [rip + SmallBlockInfo].TSmallBlockInfo.IsMultiThreadPtr - {$endif FPCMM_ASSUMEMULTITHREAD} - // Is it a small block in use? - test dl, IsFreeBlockFlag + IsMediumBlockFlag + IsLargeBlockFlag - jnz @NotSmallBlockInUse - // Get the small block type in rbx and try to grab it - push rbx - mov rbx, [rdx].TSmallBlockPoolHeader.BlockType - {$ifdef FPCMM_ASSUMEMULTITHREAD} - mov eax, $100 - lock cmpxchg byte ptr [rbx].TSmallBlockType.BlockTypeLocked, ah - jne @CheckTinySmallLock - {$else} - cmp byte ptr [rax], 0 - jne @TinySmallLockLoop - {$endif FPCMM_ASSUMEMULTITHREAD} -@FreeAndUnlock: - call FreeSmallLocked movzx eax, word ptr [rbx].TSmallBlockType.BlockSize + {$ifdef NOSFRAME} pop rbx -@VoidPointer: ret + {$else} + jmp @Done // on Win64, a stack frame is required + {$endif NOSFRAME} @NotSmallBlockInUse: + lea r10, [rip + MediumBlockInfo] test dl, IsFreeBlockFlag + IsLargeBlockFlag - jz FreeMediumBlock - jmp FreeLargeBlock // P is still in rcx/rdi first param register + jz @DoFreeMedium + call FreeLargeBlock // P is still in rcx/rdi first param register + jmp @Quit +@DoFreeMedium: + call FreeMediumBlock + jmp @Quit @TinySmallLockLoop: mov eax, $100 - lock cmpxchg byte ptr [rbx].TSmallBlockType.BlockTypeLocked, ah + lock cmpxchg byte ptr [rbx].TSmallBlockType.Locked, ah je @FreeAndUnlock - @CheckTinySmallLock: +@CheckTinySmallLock: {$ifdef FPCMM_LOCKLESSFREE} // Try to put rcx=P in TSmallBlockType.BinInstance[] cmp byte ptr [rbx].TSmallBlockType.BinCount, SmallBlockBinCount - je @LockBlockTypeSleep + je @LockBlockTypeSleep // wait if all slots are filled mov eax, $100 lock cmpxchg byte ptr [rbx].TSmallBlockType.BinLocked, ah je @BinLocked {$ifdef FPCMM_PAUSE} + {$ifdef FPCMM_SLEEPTSC} push rdx rdtsc shl rdx, 32 lea r9, [rax + rdx + SpinSmallFreememBinTSC] // r9 = endtsc + {$else} + mov r9d, SpinFreememBinCount + {$endif FPCMM_SLEEPTSC} @SpinBinLock: pause + {$ifdef FPCMM_SLEEPTSC} rdtsc shl rdx, 32 or rax, rdx cmp rax, r9 ja @SpinTimeout + {$else} + dec r9 + jz @SpinTimeout + {$endif FPCMM_SLEEPTSC} + {$ifdef FPCMM_CMPBEFORELOCK_SPIN} cmp byte ptr [rbx].TSmallBlockType.BinLocked, true je @SpinBinLock + {$endif FPCMM_CMPBEFORELOCK_SPIN} mov eax, $100 lock cmpxchg byte ptr [rbx].TSmallBlockType.BinLocked, ah jne @SpinBinLock + {$ifdef FPCMM_SLEEPTSC} pop rdx + {$endif FPCMM_SLEEPTSC} jmp @BinLocked @SpinTimeout: + {$ifdef FPCMM_SLEEPTSC} pop rdx + {$endif FPCMM_SLEEPTSC} {$endif FPCMM_PAUSE} - {$ifdef FPCMM_DEBUG} - inc dword ptr [rbx].TSmallBlockType.BinSpinCount // no lock (informative only) + {$ifdef FPCMM_DEBUG} // no lock (informative only) + inc dword ptr [rbx].TSmallBlockType.BinSpinCount {$endif FPCMM_DEBUG} jmp @LockBlockTypeSleep @BinLocked: movzx eax, byte ptr [rbx].TSmallBlockType.BinCount cmp al, SmallBlockBinCount je @LockBlockType - inc byte ptr [rbx].TSmallBlockType.BinCount + add byte ptr [rbx].TSmallBlockType.BinCount, 1 mov [rbx + TSmallBlockType.BinInstance + rax * 8], rcx mov byte ptr [rbx].TSmallBlockType.BinLocked, false movzx eax, word ptr [rbx].TSmallBlockType.BlockSize + {$ifdef NOSFRAME} pop rbx ret + {$else} + jmp @Done // on Win64, a stack frame is required + {$endif NOSFRAME} @LockBlockType: // Fallback to main block lock if TSmallBlockType.BinInstance[] is full mov byte ptr [rbx].TSmallBlockType.BinLocked, false - {$endif FPCMM_LOCKLESSFREE} @LockBlockTypeSleep: + {$endif FPCMM_LOCKLESSFREE} {$ifdef FPCMM_PAUSE} // Spin to grab the block type (don't try too long due to contention) + {$ifdef FPCMM_SLEEPTSC} push rdx rdtsc shl rdx, 32 @@ -1817,28 +2190,46 @@ asm or rax, rdx cmp rax, r9 ja @LockBlockTypeReleaseCore - cmp byte ptr [rbx].TSmallBlockType.BlockTypeLocked, 1 - je @SpinLockBlockType + {$else} + mov r8d, SpinSmallFreememLockCount +@SpinLockBlockType: + pause + dec r8d + jz @LockBlockTypeReleaseCore + {$endif FPCMM_SLEEPTSC} mov eax, $100 - lock cmpxchg byte ptr [rbx].TSmallBlockType.BlockTypeLocked, ah + {$ifdef FPCMM_CMPBEFORELOCK_SPIN} + cmp byte ptr [rbx].TSmallBlockType.Locked, true + je @SpinLockBlockType + {$endif FPCMM_CMPBEFORELOCK_SPIN} + lock cmpxchg byte ptr [rbx].TSmallBlockType.Locked, ah jne @SpinLockBlockType + {$ifdef FPCMM_SLEEPTSC} pop rdx + {$endif FPCMM_SLEEPTSC} jmp @FreeAndUnlock @LockBlockTypeReleaseCore: + {$ifdef FPCMM_SLEEPTSC} pop rdx + {$endif FPCMM_SLEEPTSC} {$endif FPCMM_PAUSE} // Couldn't grab the block type - sleep and try again - lock inc dword ptr [rbx].TSmallBlockType.FreeMemSleepCount + {$ifdef FPCMM_DEBUG} lock {$endif} + inc dword ptr [rbx].TSmallBlockType.FreeMemSleepCount push rdx push rcx call ReleaseCore pop rcx pop rdx jmp @TinySmallLockLoop +@Done: // restore rbx and the stack frame before ret + pop rbx +@Quit: end; // warning: FPC signature is not the same than Delphi: requires "var P" -function _ReallocMem(var P: pointer; Size: PtrUInt): pointer; nostackframe; assembler; +function _ReallocMem(var P: pointer; Size: PtrUInt): pointer; + {$ifdef NOSFRAME} nostackframe; {$endif} assembler; asm {$ifdef MSWINDOWS} push rdi @@ -1857,7 +2248,7 @@ asm mov rcx, [r14 - BlockHeaderSize] test cl, IsFreeBlockFlag + IsMediumBlockFlag + IsLargeBlockFlag jnz @NotASmallBlock - { -------------- TINY/SMALL block ------------- } + // -------------- TINY/SMALL block ------------- // Get rbx=blocktype, rcx=available size, rax=inplaceresize mov rbx, [rcx].TSmallBlockPoolHeader.BlockType lea rax, [rdx * 4 + SmallBlockDownsizeCheckAdder] @@ -1869,15 +2260,16 @@ asm cmp eax, ecx jb @GetMemMoveFreeMem // r14=P rdx=size @NoResize: + // branchless execution if current block is good enough for this size mov rax, r14 // keep original pointer pop rcx + {$ifdef NOSFRAME} pop r14 pop rbx - {$ifdef MSWINDOWS} - pop rsi - pop rdi - {$endif MSWINDOWS} ret + {$else} + jmp @Quit // on Win64, a stack frame is required + {$endif NOSFRAME} @VoidSize: push rdx // to set P=nil jmp @DoFree // ReallocMem(P,0)=FreeMem(P) @@ -1911,37 +2303,39 @@ asm @MoveFreeMem: // copy and free: rax=New r14=P rbx=size-8 push rax + {$ifdef FPCMM_ERMS} + cmp rbx, ErmsMinSize // startup cost of 0..255 bytes + jae @erms + {$endif FPCMM_ERMS} lea rcx, [r14 + rbx] lea rdx, [rax + rbx] neg rbx jns @Last8 align 16 -@MoveBy16: - movaps xmm0, oword ptr [rcx + rbx] +@By16: movaps xmm0, oword ptr [rcx + rbx] movaps oword ptr [rdx + rbx], xmm0 add rbx, 16 - js @MoveBy16 + js @By16 @Last8: mov rax, qword ptr [rcx + rbx] mov qword ptr [rdx + rbx], rax -@DoFree: - mov P, r14 +@DoFree:mov P, r14 call _FreeMem pop rax -@Done: pop rcx - pop r14 - pop rbx - {$ifdef MSWINDOWS} - pop rsi - pop rdi - {$endif MSWINDOWS} - mov qword ptr [rcx], rax // store new pointer in var P - ret + jmp @Done + {$ifdef FPCMM_ERMS} +@erms: cld + mov rsi, r14 + mov rdi, rax + lea rcx, [rbx + 8] + rep movsb + jmp @DoFree + {$endif FPCMM_ERMS} @NotASmallBlock: // Is this a medium block or a large block? test cl, IsFreeBlockFlag + IsLargeBlockFlag jnz @PossibleLargeBlock - { -------------- MEDIUM block ------------- } - // rcx = Current Size + Flags, r14 = P, rdx = Requested Size, r10 = MediumBlockInfo + // -------------- MEDIUM block ------------- + // rcx=CurrentSize+Flags, r14=P, rdx=RequestedSize, r10=TMediumBlockInfo lea rsi, [rdx + rdx] lea r10, [rip + MediumBlockInfo] mov rbx, rcx @@ -1952,8 +2346,8 @@ asm // Is it an upsize or a downsize? cmp rdx, rcx ja @MediumBlockUpsize - // rcx = Current Block Size - BlockHeaderSize, rbx = Current Block Flags, - // rdi = @Next Block, r14 = P, rdx = Requested Size + // rcx=CurrentBlockSize-BlockHeaderSize, rbx=CurrentBlockFlags, + // rdi=@NextBlock, r14=P, rdx=RequestedSize // Downsize reallocate and move data only if less than half the current size cmp rsi, rcx jae @NoResize @@ -1981,7 +2375,7 @@ asm {$ifndef FPCMM_ASSUMEMULTITHREAD} mov rax, [r10 + TMediumBlockinfo.IsMultiThreadPtr] cmp byte ptr [rax], false - je @MediumBlocksLocked1 + je @MediumBlocksLocked1 // no lock if IsMultiThread=false {$endif FPCMM_ASSUMEMULTITHREAD} mov eax, $100 lock cmpxchg byte ptr [rcx].TMediumBlockInfo.Locked, ah @@ -2047,7 +2441,7 @@ asm {$ifndef FPCMM_ASSUMEMULTITHREAD} mov rax, [r10 + TMediumBlockinfo.IsMultiThreadPtr] cmp byte ptr [rax], false - je @MediumBlocksLocked2 + je @MediumBlocksLocked2 // no lock if IsMultiThread=false {$endif FPCMM_ASSUMEMULTITHREAD} mov eax, $100 lock cmpxchg byte ptr [rcx].TMediumBlockInfo.Locked, ah @@ -2085,11 +2479,11 @@ asm // Get the maximum of the requested size and the minimum growth size xor edi, edi sub eax, edx - adc edi, - 1 + adc edi, -1 and eax, edi // Round up to the nearest block size granularity lea rax, [rax + rdx + BlockHeaderSize + MediumBlockGranularity - 1 - MediumBlockSizeOffset] - and eax, - MediumBlockGranularity + and eax, -MediumBlockGranularity add eax, MediumBlockSizeOffset // Calculate the size of the second split and check if it fits lea rdx, [rsi + BlockHeaderSize] @@ -2128,7 +2522,7 @@ asm lea P, qword ptr [rcx + rax] // NextUpBlockSize = OldSize+25% jmp @AdjustGetMemMoveFreeMem // P=BlockSize, rdx=NewSize, rbx=OldSize-8 @PossibleLargeBlock: - { -------------- LARGE block ------------- } + // -------------- LARGE block ------------- test cl, IsFreeBlockFlag + IsMediumBlockFlag jnz @Error {$ifdef MSWINDOWS} @@ -2140,15 +2534,25 @@ asm call ReallocateLargeBlock // with restored proper registers jmp @Done @Error: xor eax, eax - jmp @Done +@Done: // restore registers and the stack frame before ret + pop rcx + mov qword ptr [rcx], rax // store new pointer in var P +@Quit: pop r14 + pop rbx + {$ifdef MSWINDOWS} + pop rsi + pop rdi + {$endif MSWINDOWS} end; -function _AllocMem(Size: PtrUInt): pointer; nostackframe; assembler; +function _AllocMem(Size: PtrUInt): pointer; + {$ifdef NOSFRAME} nostackframe; {$endif} assembler; asm push rbx - // Get rbx = size rounded down to the previous multiple of SizeOf(pointer) + // Compute rbx = size rounded down to the last pointer lea rbx, [Size - 1] and rbx, - 8 + // Perform the memory allocation call _GetMem // Could a block be allocated? rcx = 0 if yes, -1 if no cmp rax, 1 @@ -2161,21 +2565,53 @@ asm // Large blocks from mmap/VirtualAlloc are already zero filled cmp rbx, MaximumMediumBlockSize - BlockHeaderSize jae @Done + {$ifdef FPCMM_ERMS} + cmp rbx, ErmsMinSize // startup cost of 0..255 bytes + jae @erms + {$endif FPCMM_ERMS} neg rbx pxor xmm0, xmm0 align 16 -@FillLoop: // non-temporal movntdq not needed when size <256KB (small/medium) +@FillLoop: // non-temporal movntdq not needed with small/medium size movaps oword ptr [rdx + rbx], xmm0 add rbx, 16 js @FillLoop + // fill the last pointer @LastQ: xor rcx, rcx mov qword ptr [rdx], rcx -@Done: pop rbx + {$ifdef FPCMM_ERMS} + {$ifdef NOSFRAME} + pop rbx + ret + {$else} + jmp @Done // on Win64, a stack frame is required + {$endif NOSFRAME} + // ERMS has a startup cost, but "rep stosd" is fast enough on all CPUs +@erms: mov rcx, rbx + push rax + {$ifdef MSWINDOWS} + push rdi + {$endif MSWINDOWS} + cld + mov rdi, rdx + xor eax, eax + sub rdi, rbx + shr ecx, 2 + mov qword ptr [rdx], rax + rep stosd + {$ifdef MSWINDOWS} + pop rdi + {$endif MSWINDOWS} + pop rax + {$endif FPCMM_ERMS} +@Done: // restore rbx register and the stack frame before ret + pop rbx end; function _MemSize(P: pointer): PtrUInt; begin - // AFAIK used only by fpc_AnsiStr_SetLength() in RTL + // AFAIK used only by fpc_AnsiStr_SetLength() in FPC RTL + // also used by our static SQLite3 for its xSize() callback P := PPointer(PByte(P) - BlockHeaderSize)^; if (PtrUInt(P) and (IsMediumBlockFlag or IsLargeBlockFlag)) = 0 then result := PSmallBlockPoolHeader(PtrUInt(P) and DropSmallFlagsMask). @@ -2191,7 +2627,8 @@ end; function _FreeMemSize(P: pointer; size: PtrUInt): PtrInt; begin // should return the chunk size - only used by heaptrc AFAIK - if (P <> nil) and (size <> 0) then + if (P <> nil) and + (size <> 0) then result := _FreeMem(P) else result := 0; @@ -2208,14 +2645,28 @@ end; {$else} +function _GetFPCHeapStatus: TFPCHeapStatus; +var + mm: PMMStatus; +begin + mm := @HeapStatus; + {$ifdef FPCMM_DEBUG} + result.MaxHeapSize := mm^.Medium.PeakBytes + mm^.Large.PeakBytes; + {$else} + result.MaxHeapSize := 0; + {$endif FPCMM_DEBUG} + result.MaxHeapUsed := result.MaxHeapSize; + result.CurrHeapSize := mm^.Medium.CurrentBytes + mm^.Large.CurrentBytes; + result.CurrHeapUsed := result.CurrHeapSize; + result.CurrHeapFree := 0; +end; + function _GetHeapStatus: THeapStatus; begin FillChar(result, sizeof(result), 0); -end; - -function _GetFPCHeapStatus: TFPCHeapStatus; -begin - FillChar(result, sizeof(result), 0); + with HeapStatus do + result.TotalAllocated := Medium.CurrentBytes + Large.CurrentBytes; + result.TotalAddrSpace := result.TotalAllocated; end; type @@ -2255,7 +2706,8 @@ begin end; until I > J; if J - L < R - I then - begin // use recursion only for smaller range + begin + // use recursion only for smaller range if L < J then QuickSortRes(Res, L, J, Level); L := I; @@ -2269,16 +2721,19 @@ begin until L >= R; end; -procedure SetSmallBlockStatus(var res: TResArray); +procedure SetSmallBlockStatus(var res: TResArray; out small, tiny: cardinal); var i, a: integer; p: PSmallBlockType; d: ^TSmallBlockStatus; begin + small := 0; + tiny := 0; d := @res; p := @SmallBlockInfo; for i := 1 to NumSmallBlockTypes do begin + inc(small, ord(p^.GetmemCount <> 0)); d^.Total := p^.GetmemCount; d^.Current := p^.GetmemCount - p^.FreememCount; d^.BlockSize := p^.BlockSize; @@ -2290,6 +2745,7 @@ begin d := @res; // aggregate counters for i := 1 to NumTinyBlockTypes do begin + inc(tiny, ord(p^.GetmemCount <> 0)); inc(d^.Total, p^.GetmemCount); inc(d^.Current, p^.GetmemCount - p^.FreememCount); inc(d); @@ -2320,7 +2776,8 @@ begin result := maxcount; if result > NumSmallBlockTypes then result := NumSmallBlockTypes; - while (result > 0) and (res[result - 1, orderby] = 0) do + while (result > 0) and + (res[result - 1, orderby] = 0) do dec(result); end; @@ -2360,146 +2817,191 @@ begin result := maxcount; end; -function K(i: PtrUInt): shortstring; +const + K_: array[0..4] of string[1] = ( + 'P', 'T', 'G', 'M', 'K'); + +function K(i: PtrUInt): ShortString; var - tmp: string[1]; + j, n: PtrUInt; + tmp: PShortString; begin - if i >= 1 shl 50 then - begin - i := i shr 50; - tmp := 'Z'; - end - else - if i >= 1 shl 40 then - begin - i := i shr 40; - tmp := 'T'; - end - else - if i >= 1 shl 30 then - begin - i := i shr 30; - tmp := 'G'; - end - else - if i >= 1 shl 20 then - begin - i := i shr 20; - tmp := 'M'; - end - else - if i >= 1 shl 10 then - begin - i := i shr 10; - tmp := 'K'; - end - else - tmp := ''; + tmp := nil; + n := 1 shl 50; + for j := 0 to high(K_) do + if i >= n then + begin + i := i div n; + tmp := @K_[j]; + break; + end + else + n := n shr 10; str(i, result); - result := result + tmp; + if tmp <> nil then + result := result + tmp^; end; -{$I-} +function S(i: PtrUInt): ShortString; +begin + str(i, result); +end; + +type + // allow to write into a temp string or the console + TGetHeapStatusWrite = + procedure(const V: array of ShortString; CRLF: boolean = true); procedure WriteHeapStatusDetail(const arena: TMMStatusArena; - const name: shortstring); + const name: ShortString; Wr: TGetHeapStatusWrite); begin - write(name, K(arena.CurrentBytes):4, - 'B/', K(arena.CumulativeBytes), 'B '); + Wr([name, K(arena.CurrentBytes), + 'B/', K(arena.CumulativeBytes), 'B '], {crlf=}false); {$ifdef FPCMM_DEBUG} - write(' peak=', K(arena.PeakBytes), + Wr([' peak=', K(arena.PeakBytes), 'B current=', K(arena.CumulativeAlloc - arena.CumulativeFree), - ' alloc=', K(arena.CumulativeAlloc), ' free=', K(arena.CumulativeFree)); + ' alloc=', K(arena.CumulativeAlloc), + ' free=', K(arena.CumulativeFree)], false); {$endif FPCMM_DEBUG} - writeln(' sleep=', K(arena.SleepCount)); + Wr([' sleep=', K(arena.SleepCount)]); end; -procedure WriteHeapStatus(const context: shortstring; smallblockstatuscount, - smallblockcontentioncount: integer; compilationflags: boolean); +procedure ComputeHeapStatus(const context: ShortString; smallblockstatuscount, + smallblockcontentioncount: integer; compilationflags: boolean; + Wr: TGetHeapStatusWrite); var res: TResArray; // no heap allocation involved i, n, smallcount: PtrInt; t, b: PtrUInt; + small, tiny: cardinal; begin if context[0] <> #0 then - writeln(context); + Wr([context]); if compilationflags then - writeln(' Flags: ' {$ifdef FPCMM_BOOSTER} + 'BOOSTER ' {$else} - {$ifdef FPCMM_BOOST} + 'BOOST ' {$endif}{$endif} - {$ifdef FPCMM_SERVER} + 'SERVER ' {$endif} - {$ifdef FPCMM_ASSUMEMULTITHREAD} + ' assumulthrd' {$endif} - {$ifdef FPCMM_LOCKLESSFREE} + ' lockless' {$endif} - {$ifdef FPCMM_PAUSE} + ' pause' {$endif} - {$ifdef FPCMM_NOMREMAP} + ' nomremap' {$endif} - {$ifdef FPCMM_DEBUG} + ' debug' {$endif} - {$ifdef FPCMM_REPORTMEMORYLEAKS} + ' repmemleak' {$endif}); + Wr([' Flags:' + FPCMM_FLAGS]); with CurrentHeapStatus do begin - writeln(' Small: blocks=', K(SmallBlocks), ' size=', K(SmallBlocksSize), - 'B (part of Medium arena)'); - WriteHeapStatusDetail(Medium, ' Medium: '); - WriteHeapStatusDetail(Large, ' Large: '); + Wr([' Small: blocks=', K(SmallBlocks), + ' size=', K(SmallBlocksSize), 'B (part of Medium arena)']); + WriteHeapStatusDetail(Medium, ' Medium: ', Wr); + WriteHeapStatusDetail(Large, ' Large: ', Wr); if SleepCount <> 0 then - writeln(' Total Sleep: count=', K(SleepCount) - {$ifdef FPCMM_DEBUG} , ' rdtsc=', K(SleepCycles) {$endif}); - smallcount := SmallGetmemSleepCount + SmallFreememSleepCount; + Wr([' Total Sleep: count=', K(SleepCount) + {$ifdef FPCMM_SLEEPTSC} , ' rdtsc=', K(SleepCycles) {$endif}]); + smallcount := SmallGetmemSleepCount + SmallFreememSleepCount + {$ifdef FPCMM_LOCKLESSFREE} {$ifdef FPCMM_DEBUG} + + SmallFreememLockLessSpin {$endif} {$endif}; if smallcount <> 0 then - writeln(' Small Sleep: getmem=', K(SmallGetmemSleepCount), - ' freemem=', K(SmallFreememSleepCount) + Wr([' Small Sleep: getmem=', K(SmallGetmemSleepCount), + ' freemem=', K(SmallFreememSleepCount) {$ifdef FPCMM_LOCKLESSFREE} {$ifdef FPCMM_DEBUG} , - ' locklessspin=', K(SmallFreememLockLessSpin) {$endif} {$endif} ); + ' locklessspin=', K(SmallFreememLockLessSpin) {$endif} {$endif}]); end; - if (smallblockcontentioncount > 0) and (smallcount <> 0) then + if (smallblockcontentioncount > 0) and + (smallcount <> 0) then begin n := SetSmallBlockContention(res, smallblockcontentioncount); for i := 0 to n - 1 do with TSmallBlockContention(res[i]) do begin if GetmemBlockSize <> 0 then - write(' getmem(', GetmemBlockSize) + Wr([' getmem(', S(GetmemBlockSize)], {crlf=}false) else - write(' freemem(', FreememBlockSize); - write(')=' , K(SleepCount)); - if (i and 3 = 3) or (i = n - 1) then - writeln; + Wr([' freemem(', S(FreememBlockSize)], false); + Wr([')=' , K(SleepCount)], false); + if (i and 3 = 3) or + (i = n - 1) then + Wr([]); end; end; if smallblockstatuscount > 0 then begin - SetSmallBlockStatus(res); + SetSmallBlockStatus(res, small, tiny); n := SortSmallBlockStatus(res, smallblockstatuscount, ord(obTotal), @t, @b) - 1; - writeln(' Small Blocks since beginning: ', K(t), '/', K(b), 'B'); + Wr([' Small Blocks since beginning: ', K(t), '/', K(b), + 'B (as small=', K(small), '/', S(NumSmallBlockTypes), + ' tiny=', K(tiny), '/', S(NumTinyBlockArenas * NumTinyBlockTypes), ')']); for i := 0 to n do with TSmallBlockStatus(res[i]) do begin - write(' ', BlockSize, '=', K(Total)); - if (i and 7 = 7) or (i = n) then - writeln; + Wr([' ', S(BlockSize), '=', K(Total)], false); + if (i and 7 = 7) or + (i = n) then + Wr([]); end; n := SortSmallBlockStatus(res, smallblockstatuscount, ord(obCurrent), @t, @b) - 1; - writeln(' Small Blocks current: ', K(t), '/', K(b), 'B'); + Wr([' Small Blocks current: ', K(t), '/', K(b), 'B']); for i := 0 to n do with TSmallBlockStatus(res[i]) do begin - write(' ', BlockSize, '=', K(Current)); - if (i and 7 = 7) or (i = n) then - writeln; + Wr([' ', S(BlockSize), '=', K(Current)], false); + if (i and 7 = 7) or + (i = n) then + Wr([]); end; end; end; -{$I+} +var + WrStrTemp: string; // we don't require thread safety here + WrStrOnSameLine: boolean; -function GetSmallBlockStatus(maxcount: integer; - orderby: TSmallBlockOrderBy; count, bytes: PPtrUInt): TSmallBlockStatusDynArray; +procedure WrStr(const V: array of ShortString; CRLF: boolean); +var + i: PtrInt; +begin // we don't have format() nor formatutf8() -> this is good enough + for i := 0 to high(V) do + WrStrTemp := WrStrTemp + string(V[i]); // fast enough + if CRLF and + not WrStrOnSameLine then + WrStrTemp := WrStrTemp + #13#10; +end; + +function GetHeapStatus(const context: ShortString; smallblockstatuscount, + smallblockcontentioncount: integer; compilationflags, onsameline: boolean): string; +begin + WrStrOnSameLine := onsameline; + ComputeHeapStatus(context, smallblockstatuscount, smallblockcontentioncount, + compilationflags, WrStr); + result := WrStrTemp; + WrStrTemp := ''; +end; + +procedure WrConsole(const V: array of ShortString; CRLF: boolean); +var + i: PtrInt; +begin // direct write to the console with no memory heap allocation + {$I-} + for i := 0 to high(V) do + write(V[i]); + if CRLF then + writeln; + ioresult; + {$I+} +end; + +procedure WriteHeapStatus(const context: ShortString; smallblockstatuscount, + smallblockcontentioncount: integer; compilationflags: boolean); +begin + ComputeHeapStatus(context, smallblockstatuscount, smallblockcontentioncount, + compilationflags, WrConsole); +end; + +function GetSmallBlockStatus(maxcount: integer; orderby: TSmallBlockOrderBy; + count, bytes: PPtrUInt; small, tiny: PCardinal): TSmallBlockStatusDynArray; var res: TResArray; + sm, ti: cardinal; begin assert(SizeOf(TRes) = SizeOf(TSmallBlockStatus)); result := nil; if maxcount <= 0 then exit; - SetSmallBlockStatus(res); + SetSmallBlockStatus(res, sm, ti); + if small <> nil then + small^ := sm; + if tiny <> nil then + tiny^ := ti; maxcount := SortSmallBlockStatus(res, maxcount, ord(orderby), count, bytes); if maxcount = 0 then exit; @@ -2554,18 +3056,36 @@ end; { ********* Initialization and Finalization } +procedure InitializeMediumPool(var Info: TMediumBlockInfo); +var + i: PtrInt; + medium: PMediumFreeBlock; +begin + {$ifndef FPCMM_ASSUMEMULTITHREAD} + Info.IsMultiThreadPtr := @IsMultiThread; + {$endif FPCMM_ASSUMEMULTITHREAD} + Info.PoolsCircularList.PreviousMediumBlockPoolHeader := @Info.PoolsCircularList; + Info.PoolsCircularList.NextMediumBlockPoolHeader := @Info.PoolsCircularList; + for i := 0 to MediumBlockBinCount -1 do + begin + medium := @Info.Bins[i]; + medium.PreviousFreeBlock := medium; + medium.NextFreeBlock := medium; + end; +end; + procedure InitializeMemoryManager; var small: PSmallBlockType; a, i, min, poolsize, num, perpool, size, start, next: PtrInt; - medium: PMediumFreeBlock; begin small := @SmallBlockInfo; assert(SizeOf(small^) = 1 shl SmallBlockTypePO2); for a := 0 to NumTinyBlockArenas do for i := 0 to NumSmallBlockTypes - 1 do begin - if (i = NumTinyBlockTypes) and (a > 0) then + if (i = NumTinyBlockTypes) and + (a > 0) then break; size := SmallBlockSizes[i]; assert(size and 15 = 0); @@ -2584,7 +3104,7 @@ begin (MediumBlockBinsPerGroup * MediumBlockGranularity); if num > 7 then num := 7; - small^.AllowedGroupsForBlockPoolBitmap := Byte(Byte(-1) shl num); + small^.AllowedGroupsForBlockPoolBitmap := byte(byte(-1) shl num); small^.MinimumBlockPoolSize := MinimumMediumBlockSize + num * (MediumBlockBinsPerGroup * MediumBlockGranularity); poolsize := ((size * TargetSmallBlocksPerPool + @@ -2602,8 +3122,7 @@ begin end; assert(small = @SmallBlockInfo.GetmemLookup); {$ifndef FPCMM_ASSUMEMULTITHREAD} - SmallBlockInfo.IsMultiThreadPtr := @IsMultiThread; - MediumBlockInfo.IsMultiThreadPtr := @IsMultiThread; + SmallBlockInfo.IsMultiThreadPtr := @IsMultiThread; {$endif FPCMM_ASSUMEMULTITHREAD} start := 0; with SmallBlockInfo do @@ -2616,17 +3135,10 @@ begin inc(start); end; end; - with MediumBlockInfo do - begin - PoolsCircularList.PreviousMediumBlockPoolHeader := @PoolsCircularList; - PoolsCircularList.NextMediumBlockPoolHeader := @PoolsCircularList; - for i := 0 to MediumBlockBinCount -1 do - begin - medium := @Bins[i]; - medium.PreviousFreeBlock := medium; - medium.NextFreeBlock := medium; - end; - end; + InitializeMediumPool(MediumBlockInfo); + {$ifdef FPCMM_SMALLNOTWITHMEDIUM} + InitializeMediumPool(SmallMediumBlockInfo); + {$endif FPCMM_SMALLNOTWITHMEDIUM} LargeBlocksCircularList.PreviousLargeBlockHeader := @LargeBlocksCircularList; LargeBlocksCircularList.NextLargeBlockHeader := @LargeBlocksCircularList; end; @@ -2634,6 +3146,7 @@ end; {$I-} {$ifdef FPCMM_REPORTMEMORYLEAKS} + var MemoryLeakReported: boolean; @@ -2641,46 +3154,62 @@ procedure StartReport; begin if MemoryLeakReported then exit; - writeln; + writeln {$ifndef MSWINDOWS} (#27'[1;31m') {$endif}; // lightred posix console WriteHeapStatus('WARNING! THIS PROGRAM LEAKS MEMORY!'#13#10'Memory Status:'); - writeln('Leaks Identified:'); + writeln('Leaks Identified:' {$ifndef MSWINDOWS} + #27'[1;37m' {$endif}); MemoryLeakReported := true; end; -{$ifdef LINUX} - // experimental detection of object class - use at your own risk - {$define FPCMM_REPORTMEMORYLEAKS_EXPERIMENTAL} -{$endif LINUX} +{$ifdef FPCMM_REPORTMEMORYLEAKS_EXPERIMENTAL} +function SeemsRealPointer(p: pointer): boolean; +{$ifdef MSWINDOWS} +var + meminfo: TMemInfo; +{$endif MSWINDOWS} +begin + result := false; + if PtrUInt(p) <= 65535 then + exit; + {$ifdef MSWINDOWS} + // VirtualQuery API is slow but better than raising an exception + // see https://stackoverflow.com/a/37547837/458259 + FillChar(meminfo, SizeOf(meminfo), 0); + result := (VirtualQuery(p, @meminfo, SizeOf(meminfo)) = SizeOf(meminfo)) and + (meminfo.RegionSize >= SizeOf(pointer)) and + (meminfo.State = MEM_COMMIT) and + (meminfo.Protect and PAGE_VALID <> 0) and + (meminfo.Protect and PAGE_GUARD = 0); + {$else} + // let the GPF happen silently in the kernel + result := (fpaccess(p, F_OK) <> 0) and + (fpgeterrno <> ESysEFAULT); + {$endif MSWINDOWS} +end; +{$endif FPCMM_REPORTMEMORYLEAKS_EXPERIMENTAL} -procedure MediumMemoryLeakReport(p: PMediumBlockPoolHeader); +procedure MediumMemoryLeakReport( + var Info: TMediumBlockInfo; p: PMediumBlockPoolHeader); var block: PByte; header, size: PtrUInt; - {$ifdef FPCMM_REPORTMEMORYLEAKS_EXPERIMENTAL} first, last: PByte; vmt: PAnsiChar; - small: PSmallBlockPoolHeader; - - function SeemsRealPointer(p: pointer): boolean; - begin - result := (PtrUInt(p) > 65535) - {$ifndef MSWINDOWS} - // let the GPF happen silently in the kernel - and (fpaccess(p, F_OK) <> 0) and (fpgeterrno <> ESysEFAULT) - {$endif MSWINDOWS} - end; + exceptcount: integer; {$endif FPCMM_REPORTMEMORYLEAKS_EXPERIMENTAL} - begin - with MediumBlockInfo do - if (SequentialFeedBytesLeft = 0) or (PtrUInt(LastSequentiallyFed) < PtrUInt(p)) or - (PtrUInt(LastSequentiallyFed) > PtrUInt(p) + MediumBlockPoolSize) then + if (Info.SequentialFeedBytesLeft = 0) or + (PtrUInt(Info.LastSequentiallyFed) < PtrUInt(p)) or + (PtrUInt(Info.LastSequentiallyFed) > PtrUInt(p) + MediumBlockPoolSize) then block := Pointer(PByte(p) + MediumBlockPoolHeaderSize) - else if SequentialFeedBytesLeft <> MediumBlockPoolSize - MediumBlockPoolHeaderSize then - block := LastSequentiallyFed + else if Info.SequentialFeedBytesLeft <> + MediumBlockPoolSize - MediumBlockPoolHeaderSize then + block := Info.LastSequentiallyFed else exit; + {$ifdef FPCMM_REPORTMEMORYLEAKS_EXPERIMENTAL} + exceptcount := 0; + {$endif FPCMM_REPORTMEMORYLEAKS_EXPERIMENTAL} repeat header := PPtrUInt(block - BlockHeaderSize)^; size := header and DropMediumAndLargeFlagsMask; @@ -2701,14 +3230,20 @@ begin and DropMediumAndLargeFlagsMask) - BlockSize else last := Pointer(PByte(NextSequentialFeedBlockAddress) - 1); - while first <= last do + while (first <= last) and + (exceptcount < 64) do begin if ((PPtrUInt(first - BlockHeaderSize)^ and IsFreeBlockFlag) = 0) then begin - vmt := PPointer(first)^; // _FreeMem() ensured vmt=nil - if (vmt <> nil) and SeemsRealPointer(vmt) then + vmt := PPointer(first)^; // _FreeMem() ensured vmt=nil/$b10dle55 + if (vmt <> nil) and + {$ifdef FPCMM_REPORTMEMORYLEAKS} + (PtrUInt(vmt) <> REPORTMEMORYLEAK_FREEDHEXSPEAK) and + // FreeMem marked freed blocks with 00000000 BLOODLESS marker + {$endif FPCMM_REPORTMEMORYLEAKS} + SeemsRealPointer(vmt) then try - // try to access the TObject VMT (seems to work on Linux) + // try to access the TObject VMT if (PPtrInt(vmt + vmtInstanceSize)^ >= sizeof(vmt)) and (PPtrInt(vmt + vmtInstanceSize)^ <= PSmallBlockPoolHeader(block).BlockType.BlockSize) and @@ -2716,10 +3251,13 @@ begin begin StartReport; writeln(' probable ', PShortString(PPointer(vmt + vmtClassName)^)^, - ' leak (', PSmallBlockPoolHeader(block).BlockType.BlockSize, ' bytes)'); + ' leak (', PPtrInt(vmt + vmtInstanceSize)^, '/', + PSmallBlockPoolHeader(block).BlockType.BlockSize, + ' bytes) at $', HexStr(first)); end; except - // intercept any GPF + // intercept and ignore any GPF - SeemsRealPointer() + inc(exceptcount); end; end; inc(first, PSmallBlockPoolHeader(block).BlockType.BlockSize); @@ -2730,7 +3268,7 @@ begin else begin StartReport; - writeln(' medium block leak of ', K(size), 'B'); + writeln(' medium block leak of ', size, ' bytes (', K(size), 'B)'); end; inc(block, size); until false; @@ -2738,10 +3276,44 @@ end; {$endif FPCMM_REPORTMEMORYLEAKS} -procedure FreeAllMemory; +procedure FreeMediumPool(var Info: TMediumBlockInfo); var medium, nextmedium: PMediumBlockPoolHeader; bin: PMediumFreeBlock; + i: PtrInt; +begin + medium := Info.PoolsCircularList.NextMediumBlockPoolHeader; + while medium <> @Info.PoolsCircularList do + begin + {$ifdef FPCMM_REPORTMEMORYLEAKS} + MediumMemoryLeakReport(Info, medium); + {$endif FPCMM_REPORTMEMORYLEAKS} + nextmedium := medium.NextMediumBlockPoolHeader; + FreeMedium(medium); + medium := nextmedium; + end; + Info.PoolsCircularList.PreviousMediumBlockPoolHeader := @Info.PoolsCircularList; + Info.PoolsCircularList.NextMediumBlockPoolHeader := @Info.PoolsCircularList; + for i := 0 to MediumBlockBinCount - 1 do + begin + bin := @Info.Bins[i]; + bin.PreviousFreeBlock := bin; + bin.NextFreeBlock := bin; + end; + Info.BinGroupBitmap := 0; + Info.SequentialFeedBytesLeft := 0; + for i := 0 to MediumBlockBinGroupCount - 1 do + Info.BinBitmaps[i] := 0; + {$ifdef FPCMM_LOCKLESSFREEMEDIUM} + with Info.LocklessBin do + for i := 0 to Count - 1 do + if Instance[i] <> nil then + _FreeMem(Instance[i]); // release (unlikely) pending instances + {$endif FPCMM_LOCKLESSFREEMEDIUM} +end; + +procedure FreeAllMemory; +var large, nextlarge: PLargeBlockHeader; p: PSmallBlockType; i, size: PtrUInt; @@ -2759,9 +3331,13 @@ begin for i := 1 to NumSmallInfoBlock do begin {$ifdef FPCMM_LOCKLESSFREE} + {$ifdef FPCMM_REPORTMEMORYLEAKS} + if p^.BinCount <> 0 then + writeln('BinCount=', p^.BinCount, ' for small=', p^.BlockSize); + {$endif FPCMM_REPORTMEMORYLEAKS} for j := 0 to p^.BinCount - 1 do - if p^.BinInstance[i] <> nil then - _FreeMem(p^.BinInstance[i]); // release (unlikely) pending instances + if p^.BinInstance[j] <> nil then + _FreeMem(p^.BinInstance[j]); // release (unlikely) pending instances {$endif FPCMM_LOCKLESSFREE} p^.PreviousPartiallyFreePool := pointer(p); p^.NextPartiallyFreePool := pointer(p); @@ -2783,38 +3359,17 @@ begin if leaks <> 0 then writeln(' Total small block leaks = ', leaks); {$endif FPCMM_REPORTMEMORYLEAKS} - with MediumBlockInfo do - begin - medium := PoolsCircularList.NextMediumBlockPoolHeader; - while medium <> @PoolsCircularList do - begin - {$ifdef FPCMM_REPORTMEMORYLEAKS} - MediumMemoryLeakReport(medium); - {$endif FPCMM_REPORTMEMORYLEAKS} - nextmedium := medium.NextMediumBlockPoolHeader; - FreeMedium(medium); - medium := nextmedium; - end; - PoolsCircularList.PreviousMediumBlockPoolHeader := @PoolsCircularList; - PoolsCircularList.NextMediumBlockPoolHeader := @PoolsCircularList; - for i := 0 to MediumBlockBinCount - 1 do - begin - bin := @Bins[i]; - bin.PreviousFreeBlock := bin; - bin.NextFreeBlock := bin; - end; - BinGroupBitmap := 0; - SequentialFeedBytesLeft := 0; - for i := 0 to MediumBlockBinGroupCount - 1 do - BinBitmaps[i] := 0; - end; + {$ifdef FPCMM_SMALLNOTWITHMEDIUM} + FreeMediumPool(SmallMediumBlockInfo); + {$endif FPCMM_SMALLNOTWITHMEDIUM} + FreeMediumPool(MediumBlockInfo); large := LargeBlocksCircularList.NextLargeBlockHeader; while large <> @LargeBlocksCircularList do begin size := large.BlockSizeAndFlags and DropMediumAndLargeFlagsMask; {$ifdef FPCMM_REPORTMEMORYLEAKS} StartReport; - writeln(' large block leak of ', K(size), 'B'); + writeln(' large block leak of ', size, ' bytes (', K(size), 'B)'); {$endif FPCMM_REPORTMEMORYLEAKS} nextlarge := large.NextLargeBlockHeader; FreeLarge(large, size); @@ -2830,17 +3385,17 @@ end; const NewMM: TMemoryManager = ( - NeedLock: false; - GetMem: @_Getmem; - FreeMem: @_FreeMem; - FreememSize: @_FreememSize; - AllocMem: @_AllocMem; - ReallocMem: @_ReAllocMem; - MemSize: @_MemSize; - InitThread: nil; - DoneThread: nil; - RelocateHeap: nil; - GetHeapStatus: @_GetHeapStatus; + NeedLock: false; + GetMem: @_Getmem; + FreeMem: @_FreeMem; + FreememSize: @_FreememSize; + AllocMem: @_AllocMem; + ReallocMem: @_ReAllocMem; + MemSize: @_MemSize; + InitThread: nil; + DoneThread: nil; + RelocateHeap: nil; + GetHeapStatus: @_GetHeapStatus; GetFPCHeapStatus: @_GetFPCHeapStatus); var @@ -2857,7 +3412,7 @@ finalization {$endif FPCMM_STANDALONE} -{$endif FPC_CPUX64} +{$endif FPCX64MM} end. diff --git a/contrib/mORMot/SynFastWideString.pas b/contrib/mORMot/SynFastWideString.pas index cf4d793..fb806ff 100644 --- a/contrib/mORMot/SynFastWideString.pas +++ b/contrib/mORMot/SynFastWideString.pas @@ -9,7 +9,7 @@ interface (* This file is part of Synopse Framework. - Synopse Framework. Copyright (C) 2020 Arnaud Bouchez + Synopse Framework. Copyright (C) 2022 Arnaud Bouchez Synopse Informatique - https://synopse.info *** BEGIN LICENSE BLOCK ***** @@ -28,7 +28,7 @@ interface The Initial Developer of the Original Code is Arnaud Bouchez. - Portions created by the Initial Developer are Copyright (C) 2020 + Portions created by the Initial Developer are Copyright (C) 2022 the Initial Developer. All Rights Reserved. Contributor(s): diff --git a/contrib/mORMot/SynGSSAPI.pas b/contrib/mORMot/SynGSSAPI.pas index b39e8a0..7922ed4 100644 --- a/contrib/mORMot/SynGSSAPI.pas +++ b/contrib/mORMot/SynGSSAPI.pas @@ -6,7 +6,7 @@ unit SynGSSAPI; { This file is part of Synopse mORMot framework. - Synopse mORMot framework. Copyright (C) 2020 Arnaud Bouchez + Synopse mORMot framework. Copyright (C) 2022 Arnaud Bouchez Synopse Informatique - https://synopse.info *** BEGIN LICENSE BLOCK ***** @@ -25,7 +25,7 @@ unit SynGSSAPI; The Initial Developer of the Original Code is pavelmash/ssoftpro. - Portions created by the Initial Developer are Copyright (C) 2020 + Portions created by the Initial Developer are Copyright (C) 2022 the Initial Developer. All Rights Reserved. Contributor(s): @@ -303,46 +303,46 @@ type // - used to hold information between calls to ServerSSPIAuth TSecContextDynArray = array of TSecContext; - /// Sets aSecHandle fields to empty state for a given connection ID - procedure InvalidateSecContext(var aSecContext: TSecContext; aConnectionID: Int64); +/// Sets aSecHandle fields to empty state for a given connection ID +procedure InvalidateSecContext(var aSecContext: TSecContext; aConnectionID: Int64); - /// Free aSecContext on client or server side - procedure FreeSecContext(var aSecContext: TSecContext); +/// Free aSecContext on client or server side +procedure FreeSecContext(var aSecContext: TSecContext); - /// Encrypts a message - // - aSecContext must be set e.g. from previous success call to ServerSSPIAuth - // or ClientSSPIAuth - // - aPlain contains data that must be encrypted - // - returns encrypted message - function SecEncrypt(var aSecContext: TSecContext; const aPlain: TGSSAPIBuffer): TGSSAPIBuffer; +/// Encrypts a message +// - aSecContext must be set e.g. from previous success call to ServerSSPIAuth +// or ClientSSPIAuth +// - aPlain contains data that must be encrypted +// - returns encrypted message +function SecEncrypt(var aSecContext: TSecContext; const aPlain: TGSSAPIBuffer): TGSSAPIBuffer; - /// Decrypts a message - // - aSecContext must be set e.g. from previous success call to ServerSSPIAuth - // or ClientSSPIAuth - // - aEncrypted contains data that must be decrypted - // - returns decrypted message - function SecDecrypt(var aSecContext: TSecContext; const aEncrypted: TGSSAPIBuffer): TGSSAPIBuffer; +/// Decrypts a message +// - aSecContext must be set e.g. from previous success call to ServerSSPIAuth +// or ClientSSPIAuth +// - aEncrypted contains data that must be decrypted +// - returns decrypted message +function SecDecrypt(var aSecContext: TSecContext; const aEncrypted: TGSSAPIBuffer): TGSSAPIBuffer; - /// Checks the return value of GSSAPI call and raises ESynGSSAPI exception - // when it indicates failure - procedure GSSCheck(AMajorStatus, AMinorStatus: Cardinal; const APrefix: String = ''); +/// Checks the return value of GSSAPI call and raises ESynGSSAPI exception +// when it indicates failure +procedure GSSCheck(AMajorStatus, AMinorStatus: Cardinal; const APrefix: String = ''); - /// Lists supported security mechanisms in form - // sasl:name:description - // - not all mechanisms provide human readable name and description - procedure GSSEnlistMechsSupported(MechList: TStringList); +/// Lists supported security mechanisms in form +// sasl:name:description +// - not all mechanisms provide human readable name and description +procedure GSSEnlistMechsSupported(MechList: TStringList); - /// Dynamically load GSSAPI library - // - in multithreaded server application you must call LoadGSSAPI - // at startup to avoid race condition (if you do not use mORMot.pas) - procedure LoadGSSAPI; +/// Dynamically load GSSAPI library +// - in multithreaded server application you must call LoadGSSAPI +// at startup to avoid race condition (if you do not use mORMot.pas) +procedure LoadGSSAPI; - /// Call this function to check whether GSSAPI library loaded or not - function GSSAPILoaded: Boolean; +/// Call this function to check whether GSSAPI library loaded or not +function GSSAPILoaded: Boolean; - /// Call this function to check whether GSSAPI library loaded - // and raise exception if not. - procedure RequireGSSAPI; +/// Call this function to check whether GSSAPI library loaded +// and raise exception if not. +procedure RequireGSSAPI; implementation @@ -432,14 +432,16 @@ end; procedure RequireGSSAPI; begin if GSSAPILibrary=0 then - raise ENotSupportedException.Create('No GSSAPI library found - please install either MIT or Heimdal GSSAPI implementation'); + raise ENotSupportedException.Create( + 'No GSSAPI library found - please install ' + + 'either MIT or Heimdal GSSAPI implementation'); end; function gss_compare_oid(oid1, oid2: gss_OID): Boolean; begin if (oid1<>nil) and (oid2<>nil) then begin - Result := (oid1^.length = oid2^.length) - and CompareMem(oid1^.elements, oid2^.elements, oid1^.length); + Result := (oid1^.length = oid2^.length) and + CompareMem(oid1^.elements, oid2^.elements, oid1^.length); end else Result := False; diff --git a/contrib/mORMot/SynGSSAPIAuth.pas b/contrib/mORMot/SynGSSAPIAuth.pas index 1cae409..026f892 100644 --- a/contrib/mORMot/SynGSSAPIAuth.pas +++ b/contrib/mORMot/SynGSSAPIAuth.pas @@ -5,7 +5,7 @@ unit SynGSSAPIAuth; { This file is part of Synopse mORMot framework. - Synopse mORMot framework. Copyright (C) 2020 Arnaud Bouchez + Synopse mORMot framework. Copyright (C) 2022 Arnaud Bouchez Synopse Informatique - https://synopse.info *** BEGIN LICENSE BLOCK ***** @@ -24,7 +24,7 @@ unit SynGSSAPIAuth; The Initial Developer of the Original Code is Chaa. - Portions created by the Initial Developer are Copyright (C) 2020 + Portions created by the Initial Developer are Copyright (C) 2022 the Initial Developer. All Rights Reserved. Contributor(s): @@ -133,6 +133,8 @@ var // - use only if automatic conversion (truncate on first dot) do it wrong ServerDomainMap: TSynNameValue; + + implementation var @@ -338,5 +340,5 @@ begin end; initialization - ServerDomainMap.Init(False); + ServerDomainMap.Init({casesensitive=}false); end. diff --git a/contrib/mORMot/SynGdiPlus.pas b/contrib/mORMot/SynGdiPlus.pas index e4bfe32..87e6d15 100644 --- a/contrib/mORMot/SynGdiPlus.pas +++ b/contrib/mORMot/SynGdiPlus.pas @@ -9,7 +9,7 @@ unit SynGdiPlus; { This file is part of Synopse framework. - Synopse framework. Copyright (C) 2020 Arnaud Bouchez + Synopse framework. Copyright (C) 2022 Arnaud Bouchez Synopse Informatique - https://synopse.info *** BEGIN LICENSE BLOCK ***** @@ -28,7 +28,7 @@ unit SynGdiPlus; The Initial Developer of the Original Code is Arnaud Bouchez. - Portions created by the Initial Developer are Copyright (C) 2020 + Portions created by the Initial Developer are Copyright (C) 2022 the Initial Developer. All Rights Reserved. Contributor(s): @@ -2285,7 +2285,7 @@ begin end; - { TGDIPlusEnum } +{ TGDIPlusEnum } type {$ifdef FPC} @@ -2365,7 +2365,7 @@ type end; const - GdipRectFNull: TGdipRectF = (X:0;Y:0;Width:0;Height:0); + GdipRectFNull: TGdipRectF = (X:0;Y:0;Width:0;Height:0); function DXTextWidth(DX: PIntegerArray; n: Integer): integer; var i: integer; diff --git a/contrib/mORMot/SynKylix.pas b/contrib/mORMot/SynKylix.pas index af7f2f6..d62cca4 100644 --- a/contrib/mORMot/SynKylix.pas +++ b/contrib/mORMot/SynKylix.pas @@ -4,7 +4,7 @@ unit SynKylix; { This file is part of Synopse mORMot framework. - Synopse mORMot framework. Copyright (C) 2020 Arnaud Bouchez + Synopse mORMot framework. Copyright (C) 2022 Arnaud Bouchez Synopse Informatique - https://synopse.info *** BEGIN LICENSE BLOCK ***** @@ -23,7 +23,7 @@ unit SynKylix; The Initial Developer of the Original Code is Arnaud Bouchez - Portions created by the Initial Developer are Copyright (C) 2020 + Portions created by the Initial Developer are Copyright (C) 2022 the Initial Developer. All Rights Reserved. Contributor(s): diff --git a/contrib/mORMot/SynLZ.pas b/contrib/mORMot/SynLZ.pas index 960065a..1c66a66 100644 --- a/contrib/mORMot/SynLZ.pas +++ b/contrib/mORMot/SynLZ.pas @@ -5,7 +5,7 @@ unit SynLZ; { This file is part of Synopse SynLZ Compression. - Synopse SynLZ Compression. Copyright (C) 2020 Arnaud Bouchez + Synopse SynLZ Compression. Copyright (C) 2022 Arnaud Bouchez Synopse Informatique - https://synopse.info *** BEGIN LICENSE BLOCK ***** @@ -24,7 +24,7 @@ unit SynLZ; The Initial Developer of the Original Code is Arnaud Bouchez. - Portions created by the Initial Developer are Copyright (C) 2020 + Portions created by the Initial Developer are Copyright (C) 2022 the Initial Developer. All Rights Reserved. Contributor(s): diff --git a/contrib/mORMot/SynLZO.pas b/contrib/mORMot/SynLZO.pas index 5afd4f7..bde3a5d 100644 --- a/contrib/mORMot/SynLZO.pas +++ b/contrib/mORMot/SynLZO.pas @@ -5,7 +5,7 @@ unit SynLZO; { This file is part of Synopse LZO Compression. - Synopse LZO Compression. Copyright (C) 2020 Arnaud Bouchez + Synopse LZO Compression. Copyright (C) 2022 Arnaud Bouchez Synopse Informatique - https://synopse.info *** BEGIN LICENSE BLOCK ***** @@ -24,7 +24,7 @@ unit SynLZO; The Initial Developer of the Original Code is Arnaud Bouchez. - Portions created by the Initial Developer are Copyright (C) 2020 + Portions created by the Initial Developer are Copyright (C) 2022 the Initial Developer. All Rights Reserved. Contributor(s): diff --git a/contrib/mORMot/SynLizard.pas b/contrib/mORMot/SynLizard.pas index 0a943a4..37fad96 100644 --- a/contrib/mORMot/SynLizard.pas +++ b/contrib/mORMot/SynLizard.pas @@ -5,7 +5,7 @@ unit SynLizard; { This file is part of Synopse Lizard Compression. - Synopse Lizard Compression. Copyright (C) 2020 Arnaud Bouchez + Synopse Lizard Compression. Copyright (C) 2022 Arnaud Bouchez Synopse Informatique - https://synopse.info *** BEGIN LICENSE BLOCK ***** @@ -24,7 +24,7 @@ unit SynLizard; The Initial Developer of the Original Code is Arnaud Bouchez. - Portions created by the Initial Developer are Copyright (C) 2020 + Portions created by the Initial Developer are Copyright (C) 2022 the Initial Developer. All Rights Reserved. Contributor(s): diff --git a/contrib/mORMot/SynLog.pas b/contrib/mORMot/SynLog.pas index fcddac5..ed6be48 100644 --- a/contrib/mORMot/SynLog.pas +++ b/contrib/mORMot/SynLog.pas @@ -6,7 +6,7 @@ unit SynLog; (* This file is part of Synopse framework. - Synopse framework. Copyright (C) 2020 Arnaud Bouchez + Synopse framework. Copyright (C) 2022 Arnaud Bouchez Synopse Informatique - https://synopse.info *** BEGIN LICENSE BLOCK ***** @@ -25,7 +25,7 @@ unit SynLog; The Initial Developer of the Original Code is Arnaud Bouchez. - Portions created by the Initial Developer are Copyright (C) 2020 + Portions created by the Initial Developer are Copyright (C) 2022 the Initial Developer. All Rights Reserved. Contributor(s): @@ -121,7 +121,7 @@ type fUnit: TSynMapUnitDynArray; fSymbols: TDynArray; fUnits: TDynArrayHashed; - fUnitSynLogIndex,fUnitSystemIndex: integer; + fSymCount, fUnitCount, fUnitSynLogIndex, fUnitSystemIndex: integer; fCodeOffset: PtrUInt; fHasDebugInfo: boolean; public @@ -425,12 +425,12 @@ type // ! end; //- then use the logging system inside a method: // ! procedure TMyDB.MyMethod; - // ! var ILog: ISynLog; + // ! var log: ISynLog; // ! begin - // ! ILog := TSynLogDB.Enter(self,'MyMethod'); + // ! log := TSynLogDB.Enter(self,'MyMethod'); // ! // do some stuff - // ! ILog.Log(sllInfo,'method called'); - // ! end; + // ! log.Log(sllInfo,'method run with no problem and value=%',[value]); + // ! end; // here log will be released and method leaving will be logged TSynLogFamily = class protected fLevel, fLevelStackTrace: TSynLogInfos; @@ -476,6 +476,7 @@ type fRotateFileCount: cardinal; fRotateFileSize: cardinal; fRotateFileAtHour: integer; + fRotateFileNoCompression: boolean; function CreateSynLog: TSynLog; procedure StartAutoFlush; procedure SetDestinationPath(const value: TFileName); @@ -700,6 +701,8 @@ type // specified hour // - is not used if RotateFileCount is left to its default 0 property RotateFileDailyAtHour: integer read fRotateFileAtHour write fRotateFileAtHour; + /// if set to TRUE, no #.synlz will be created at rotation but plain #.log file + property RotateFileNoCompression: boolean read fRotateFileNoCompression write fRotateFileNoCompression; /// the recursive depth of stack trace symbol to write // - used only if exceptions are handled, or by sllStackTrace level // - default value is 30, maximum is 255 @@ -744,6 +747,7 @@ type /// if the method name is local, i.e. shall not be displayed at Leave() MethodNameLocal: (mnAlways, mnEnter, mnLeave, mnEnterOwnMethodName); end; + PSynLogThreadRecursion = ^TSynLogThreadRecursion; /// thread-specific internal context used during logging // - this structure is a hashed-per-thread variable @@ -850,6 +854,7 @@ type procedure AddRecursion(aIndex: integer; aLevel: TSynLogInfo); procedure LockAndGetThreadContext; {$ifdef HASINLINENOTX86}inline;{$endif} procedure GetThreadContextInternal; + function NewRecursion: PSynLogThreadRecursion; procedure ThreadContextRehash; function Instance: TSynLog; function ConsoleEcho(Sender: TTextWriter; Level: TSynLogInfo; @@ -895,19 +900,23 @@ type /// handle generic method enter / auto-leave tracing // - this is the main method to be called within a procedure/function to trace: // ! procedure TMyDB.SQLExecute(const SQL: RawUTF8); - // ! var ILog: ISynLog; + // ! var log: ISynLog; // ! begin - // ! ILog := TSynLogDB.Enter(self,'SQLExecute'); + // ! log := TSynLogDB.Enter(self,'SQLExecute'); // ! // do some stuff - // ! ILog.Log(sllInfo,'SQL=%',[SQL]); - // ! end; + // ! log.Log(sllInfo,'SQL=%',[SQL]); + // ! end; // here log will be released, and method leaving will be logged // - returning a ISynLog interface will allow you to have an automated // sllLeave log created when the method is left (thanks to the hidden // try..finally block generated by the compiler to protect the ISynLog var) - // - it is convenient to define a local variable to store the returned ISynLog - // and use it for any specific logging within the method execution - // - if you just need to access the log inside the method block, you may - // not need any ISynLog interface variable: + // - WARNING: due to a limitation (feature?) of the FPC compiler and + // Delphi 10.4 and later, you NEED to hold the returned value into a + // local ISynLog variable; as a benefit, it is always convenient to define + // a local variable to store the returned ISynLog and use it for any + // specific logging within the method execution + // - on Delphi earlier to 10.4 (and not FPC), you could just call Enter() + // inside the method block, without any ISynLog interface variable - but + // it is not very future-proof to write the following code: // ! procedure TMyDB.SQLFlush; // ! begin // ! TSynLogDB.Enter(self,'SQLFlush'); @@ -915,10 +924,12 @@ type // ! end; // - if no Method name is supplied, it will use the caller address, and // will write it as hexa and with full unit and symbol name, if the debugging - // information is available (i.e. if TSynMapFile retrieved the .map content): + // information is available (i.e. if TSynMapFile retrieved the .map content; + // note that this is not available yet on FPC): // ! procedure TMyDB.SQLFlush; + // ! var log: ISynLog; // ! begin - // ! TSynLogDB.Enter(self); + // ! log := TSynLogDB.Enter(self); // ! // do some stuff // ! end; // - note that supplying a method name is faster than using the .map content: @@ -935,16 +946,6 @@ type // $ 20110325 19325801 + MyDBUnit.TMyDB(004E11F4).SQLExecute // $ 20110325 19325801 info SQL=SELECT * FROM Table; // $ 20110325 19325801 - 01.512.320 - // - note that due to a limitation (feature?) of the FPC compiler, you need - // to hold the returned value into a local ISynLog variable, as such: - // ! procedure TMyDB.SQLFlush; - // ! var Log: ISynLog; - // ! begin - // ! Log := TSynLogDB.Enter(self); - // ! // do some stuff - // ! end; // here Log will be released - // otherwise, the ISynLog instance would be released just after the Enter() - // call, so the timing won't match the method execution class function Enter(aInstance: TObject=nil; aMethodName: PUTF8Char=nil; aMethodNameLocal: boolean=false): ISynLog; overload; /// handle method enter / auto-leave tracing, with some custom text @@ -1038,6 +1039,14 @@ type // be added to the log content (to be used e.g. with '--' for SQL statements) procedure LogLines(Level: TSynLogInfo; LinesToLog: PUTF8Char; aInstance: TObject=nil; const IgnoreWhenStartWith: PAnsiChar=nil); + /// manual low-level TSynLog.Enter execution without the ISynLog + // - may be used to log Enter/Leave stack from non-pascal code + // - each call to ManualEnter should be followed by a matching ManualLeave + // - aMethodName should be a not nil constant text + procedure ManualEnter(aMethodName: PUtf8Char; aInstance: TObject = nil); + /// manual low-level ISynLog release after TSynLog.Enter execution + // - each call to ManualEnter should be followed by a matching ManualLeave + procedure ManualLeave; /// allow to temporary disable remote logging // - to be used within a try ... finally section: // ! log.DisableRemoteLog(true); @@ -1764,8 +1773,12 @@ constructor TSynMapFile.Create(const aExeName: TFileName=''; MabCreate: boolean= end; end; procedure ReadSymbols; - var Beg: PAnsiChar; + var Beg: PUtf8Char; Sym: TSynMapSymbol; + {$ifdef ISDELPHI2005ANDUP} + u, l: PtrInt; + LastUnitUp: RawUTF8; // e.g. 'MORMOT.CORE.DATA.' + {$endif ISDELPHI2005ANDUP} begin NextLine; NextLine; @@ -1773,36 +1786,29 @@ constructor TSynMapFile.Create(const aExeName: TFileName=''; MabCreate: boolean= if GetCode(Sym.Start) then begin while (P' ') do inc(P); {$ifdef ISDELPHI2005ANDUP} // trim left 'UnitName.' for each symbol (since Delphi 2005) - case PWord(P)^ of // ignore RTL namespaces - ord('S')+ord('y') shl 8: - if IdemPChar(P+2,'STEM.') then - if IdemPChar(P+7,'WIN.') then inc(P,9) else - if IdemPChar(P+7,'RTTI.') then inc(P,10) else - if IdemPChar(P+7,'TYPES.') then inc(P,10) else - if IdemPChar(P+7,'ZLIB.') then inc(P,10) else - if IdemPChar(P+7,'CLASSES.') then inc(P,10) else - if IdemPChar(P+7,'SYSUTILS.') then inc(P,10) else - if IdemPChar(P+7,'VARUTILS.') then inc(P,10) else - if IdemPChar(P+7,'STRUTILS.') then inc(P,10) else - if IdemPChar(P+7,'SYNCOBJS.') then inc(P,10) else - if IdemPChar(P+7,'GENERICS.') then inc(P,16) else - if IdemPChar(P+7,'CHARACTER.') then inc(P,10) else - if IdemPChar(P+7,'TYPINFO.') then inc(P,10) else - if IdemPChar(P+7,'VARIANTS.') then inc(P,10); - ord('W')+ord('i') shl 8: if IdemPChar(P+2,'NAPI.') then inc(P,7); - ord('F')+ord('m') shl 8: if IdemPChar(P+2,'X.') then inc(P,7); - ord('V')+ord('c') shl 8: if IdemPChar(P+2,'L.') then inc(P,7); + if (LastUnitUp <> '') and IdemPChar(Beg, pointer(LastUnitUp)) then + // common case since symbols are grouped by address, i.e. by unit + inc(Beg, length(LastUnitUp)) + else begin // manual unit name search + LastUnitUp := ''; + for u := 0 to fUnits.Count - 1 do + with fUnit[u].Symbol do begin + l := length(Name); + if (Beg[l] = '.') and (l > length(LastUnitUp)) and + IdemPropNameU(Name, Beg, l) then + LastUnitUp := UpperCase(Name); // find longest match + end; + if LastUnitUp <> '' then begin + l := length(LastUnitUp); + SetLength(LastUnitUp, l + 1); + LastUnitUp[l] := '.'; + inc(Beg, l + 1); + end; end; - while (P'.') do if P^<=' ' then break else inc(P); - if P^='.' then begin - while (P' ') do inc(P); FastSetString(Sym.Name,Beg,P-Beg); if (Sym.Name<>'') and not (Sym.Name[1] in ['$','?']) then fSymbols.Add(Sym); @@ -1932,13 +1938,13 @@ constructor TSynMapFile.Create(const aExeName: TFileName=''; MabCreate: boolean= end; end; -var SymCount, UnitCount, i: integer; +var i: integer; MabFile: TFileName; MapAge, MabAge: TDateTime; U: RawUTF8; begin - fSymbols.Init(TypeInfo(TSynMapSymbolDynArray),fSymbol,@SymCount); - fUnits.Init(TypeInfo(TSynMapUnitDynArray),fUnit,nil,nil,nil,@UnitCount); + fSymbols.Init(TypeInfo(TSynMapSymbolDynArray),fSymbol,@fSymCount); + fUnits.Init(TypeInfo(TSynMapUnitDynArray),fUnit,nil,nil,nil,@fUnitCount); fUnitSynLogIndex := -1; fUnitSystemIndex := -1; // 1. search for an external .map file matching the running .exe/.dll name @@ -1962,19 +1968,19 @@ begin if (MapAge>0) and (MabAge0) then + if (fSymCount=0) and (MabAge<>0) then LoadMab(MabFile); // 3. search for an embedded compressed .mab file appended to the .exe/.dll - if SymCount=0 then + if fSymCount=0 then if aExeName='' then LoadMab(GetModuleName(hInstance)) else LoadMab(aExeName); // finalize symbols - if SymCount>0 then begin - for i := 1 to SymCount-1 do + if fSymCount>0 then begin + for i := 1 to fSymCount-1 do assert(fSymbol[i].Start>fSymbol[i-1].Stop); - SetLength(fSymbol,SymCount); - SetLength(fUnit,UnitCount); + SetLength(fSymbol,fSymCount); + SetLength(fUnit,fUnitCount); fSymbols.Init(TypeInfo(TSynMapSymbolDynArray),fSymbol); fUnits.Init(TypeInfo(TSynMapUnitDynArray),fUnit); if MabCreate then @@ -3736,6 +3742,20 @@ begin // should match TSynLog.ThreadContextRehash fThreadContext^.ID := fThreadID; end; +function TSynLog.NewRecursion: PSynLogThreadRecursion; +begin + with fThreadContext^ do begin + if RecursionCount = RecursionCapacity then begin + RecursionCapacity := NextGrow(RecursionCapacity); + SetLength(Recursion, RecursionCapacity); + end; + result := @Recursion[RecursionCount]; + result^.Caller := 0; // no stack trace by default + result^.RefCount := 0; + inc(RecursionCount); + end; +end; + procedure TSynLog.ThreadContextRehash; var i: integer; id, hash: PtrUInt; @@ -3809,7 +3829,7 @@ begin inc(RefCount); result := RefCount; end else - result := 1; // should never be 0 (mark release of TSynLog instance) + result := 1; // should never be 0 (would release of TSynLog instance) finally {$ifndef NOEXCEPTIONINTERCEPT} GlobalCurrentHandleExceptionSynLog := fThreadHandleExceptionBackup; @@ -3872,7 +3892,7 @@ begin result := RefCount; end; end else - result := 1; // should never be 0 (mark release of TSynLog) + result := 1; // should never be 0 (would release TSynLog instance) finally {$ifndef NOEXCEPTIONINTERCEPT} GlobalCurrentHandleExceptionSynLog := fThreadHandleExceptionBackup; @@ -3991,12 +4011,8 @@ begin // recursively store parameters if sllEnter in aSynLog.fFamily.fLevel then begin aSynLog.LockAndGetThreadContext; - with aSynLog.fThreadContext^ do + with aSynLog.NewRecursion^ do try - if RecursionCount=RecursionCapacity then begin - RecursionCapacity := NextGrow(RecursionCapacity); - SetLength(Recursion,RecursionCapacity); - end; {$ifdef CPU64} {$ifdef MSWINDOWS} if RtlCaptureStackBackTrace(1,1,@aStackFrame,nil)=0 then @@ -4017,16 +4033,12 @@ begin end; {$endif} {$endif} - with Recursion[RecursionCount] do begin - Instance := aInstance; - MethodName := aMethodName; - if aMethodNameLocal then - MethodNameLocal := mnEnter else - MethodNameLocal := mnAlways; - Caller := aStackFrame; - RefCount := 0; - end; - inc(RecursionCount); + Instance := aInstance; + MethodName := aMethodName; + if aMethodNameLocal then + MethodNameLocal := mnEnter else + MethodNameLocal := mnAlways; + Caller := aStackFrame; finally {$ifndef NOEXCEPTIONINTERCEPT} GlobalCurrentHandleExceptionSynLog := aSynLog.fThreadHandleExceptionBackup; @@ -4046,21 +4058,12 @@ begin aSynLog := Family.SynLog; if (aSynLog<>nil) and (sllEnter in aSynLog.fFamily.fLevel) then begin aSynLog.LockAndGetThreadContext; - with aSynLog.fThreadContext^ do + with aSynLog.NewRecursion^ do try - if RecursionCount=RecursionCapacity then begin - RecursionCapacity := NextGrow(RecursionCapacity); - SetLength(Recursion,RecursionCapacity); - end; - with Recursion[RecursionCount] do begin - Instance := aInstance; - MethodName := nil; // avoid GPF in RawUTF8(pointer(MethodName)) below - FormatUTF8(TextFmt,TextArgs,RawUTF8(pointer(MethodName))); - MethodNameLocal := mnEnterOwnMethodName; - Caller := 0; // No stack trace needed here - RefCount := 0; - end; - inc(RecursionCount); + Instance := aInstance; + MethodName := nil; // avoid GPF in RawUTF8(pointer(MethodName)) below + FormatUTF8(TextFmt,TextArgs,RawUTF8(pointer(MethodName))); + MethodNameLocal := mnEnterOwnMethodName; finally {$ifndef NOEXCEPTIONINTERCEPT} GlobalCurrentHandleExceptionSynLog := aSynLog.fThreadHandleExceptionBackup; @@ -4072,6 +4075,38 @@ begin result := aSynLog; end; +procedure TSynLog.ManualEnter(aMethodName: PUtf8Char; aInstance: TObject); +begin + if (self = nil) or + (fFamily.fLevel * [sllEnter, sllLeave] = []) then + exit; + if aMethodName = nil then + aMethodName := ' '; // something non void (call stack is irrelevant) + LockAndGetThreadContext; + try + with NewRecursion^ do begin + // inlined TSynLog.Enter + Instance := aInstance; + MethodName := aMethodName; + MethodNameLocal := mnEnter; + // inlined TSynLog._AddRef + if sllEnter in fFamily.Level then begin + LogHeaderLock(sllEnter, true); + AddRecursion(fThreadContext^.RecursionCount - 1, sllEnter); + end; + inc(RefCount); + end; + finally + LeaveCriticalSection(GlobalThreadLock); + end; +end; + +procedure TSynLog.ManualLeave; +begin + if self <> nil then + _Release; +end; + class function TSynLog.FamilyCreate: TSynLogFamily; var PVMT: pointer; begin // private sub function makes the code faster in most case @@ -4114,16 +4149,27 @@ function TSynLog.ConsoleEcho(Sender: TTextWriter; Level: TSynLogInfo; {$ifdef MSWINDOWS} var tmp: AnsiString; {$endif} +{$ifdef LINUXNOTBSD} +var + tmp, mtmp: RawUTF8; + jvec: Array[0..1] of TioVec; +{$endif} begin result := true; if not (Level in fFamily.fEchoToConsole) then exit; {$ifdef LINUXNOTBSD} if Family.EchoToConsoleUseJournal then begin + if length(Text)<18 then // should be at last "20200615 08003008 " + exit; + FormatUTF8('PRIORITY=%', [LOG_TO_SYSLOG[Level]],tmp); + jvec[0].iov_base := pointer(tmp); + jvec[0].iov_len := length(tmp); // skip time "20200615 08003008 ." - journal do it for us; and first space after it - if length(Text)>18 then - ExternalLibraries.sd_journal_print(longint(LOG_TO_SYSLOG[Level]), - [PUTF8Char(pointer(Text))+18]); + FormatUTF8('MESSAGE=%', [PUTF8Char(pointer(Text))+18],mtmp); + jvec[1].iov_base := pointer(mtmp); + jvec[1].iov_len := length(mtmp); + ExternalLibraries.sd_journal_sendv(@jvec[0],2); exit; end; {$endif} @@ -4546,6 +4592,7 @@ begin end; procedure TSynLog.PerformRotation; +const _LOG_SYNLZ: array[boolean] of TFileName = ('.synlz','.log'); var currentMaxSynLZ: cardinal; i: integer; FN: array of TFileName; @@ -4559,7 +4606,8 @@ begin if fFamily.fRotateFileCount>1 then begin SetLength(FN,fFamily.fRotateFileCount-1); for i := fFamily.fRotateFileCount-1 downto 1 do begin - FN[i-1] := ChangeFileExt(fFileName,'.'+IntToStr(i)+'.synlz'); + FN[i-1] := ChangeFileExt(fFileName, + '.'+IntToStr(i)+_LOG_SYNLZ[fFamily.fRotateFileNoCompression]); if (currentMaxSynLZ=0) and FileExists(FN[i-1]) then currentMaxSynLZ := i; end; @@ -4567,7 +4615,9 @@ begin DeleteFile(FN[currentMaxSynLZ-1]); // delete e.g. '9.synlz' for i := fFamily.fRotateFileCount-2 downto 1 do RenameFile(FN[i-1],FN[i]); // e.g. '8.synlz' -> '9.synlz' - FileSynLZ(fFileName,FN[0],LOG_MAGIC); // main -> '1.synlz' + if fFamily.fRotateFileNoCompression then + RenameFile(fFileName,FN[0]) else // main -> '1.log' + FileSynLZ(fFileName,FN[0],LOG_MAGIC); // main -> '1.synlz' end; DeleteFile(fFileName); end; @@ -4735,7 +4785,7 @@ begin if fWriterStream=nil then // go on if file creation fails (e.g. RO folder) fWriterStream := TFakeWriterStream.Create; if (fFileRotationSize>0) or (fFamily.FileExistsAction<>acOverwrite) then - fWriterStream.Seek(0,soFromEnd); // in rotation mode, append at the end + fWriterStream.Seek(0,soEnd); // in rotation mode, append at the end end; if fWriterClass=nil then // set to TTextWriterWithEcho or TJSONSerializer if mORMot.pas is linked @@ -4769,7 +4819,8 @@ begin end; procedure TSynLog.AddRecursion(aIndex: integer; aLevel: TSynLogInfo); -begin // aLevel = sllEnter,sllLeave or sllNone +begin + // at entry, aLevel is sllEnter, sllLeave or sllNone (from LogHeaderBegin) with fThreadContext^ do if cardinal(aIndex)=min_stack then - try - while (PtrUInt(stack)max_stack) or (st=min_stack then + try + while (PtrUInt(stack)max_stack) or (st 0 then begin - fIsJournald := true; + if unaligned(fStartDateTime) <> 0 then begin + if (fMap.Buffer+8)^ <> ' ' then //20200821 14450738 ... - syn log without header + fIsJournald := true; fHeaderLinesCount := 0; fLineHeaderCountToIgnore := 0; end; @@ -5128,8 +5180,8 @@ begin // 2. fast retrieval of header OK := false; try - // journald export - if fIsJournald then begin + // journald export or TSynLog WITHOUT regular header + if fIsJournald or (fLineHeaderCountToIgnore=0) then begin if LineSizeSmallerThan(1,34) then exit; Iso8601ToDateTimePUTF8CharVar(fLines[1],26,fStartDateTime); if fStartDateTime=0 then diff --git a/contrib/mORMot/SynMemoEx.pas b/contrib/mORMot/SynMemoEx.pas index 08c1731..802e441 100644 --- a/contrib/mORMot/SynMemoEx.pas +++ b/contrib/mORMot/SynMemoEx.pas @@ -5,7 +5,7 @@ unit SynMemoEx; { This file is part of Synopse extended TMemo - Synopse SynMemoEx. Copyright (C) 2020 Arnaud Bouchez + Synopse SynMemoEx. Copyright (C) 2022 Arnaud Bouchez Synopse Informatique - https://synopse.info *** BEGIN LICENSE BLOCK ***** @@ -24,7 +24,7 @@ unit SynMemoEx; The Initial Developer of the Original Code is Arnaud Bouchez. - Portions created by the Initial Developer are Copyright (C) 2020 + Portions created by the Initial Developer are Copyright (C) 2022 the Initial Developer. All Rights Reserved. Contributor(s): @@ -350,6 +350,7 @@ type {$ENDIF MEMOEX_DEFLAYOUT} end; + EComplete = class(EAbort); EMemoExError = class(Exception); {$IFDEF MEMOEX_UNDO} @@ -2071,7 +2072,6 @@ begin fillchar(FList[Index], sizeof(TParagraph), 0); // avoid memory bug Inc(FCount); AddParaStr(Index, S); - Changed; end; {$WARNINGS OFF} @@ -2275,7 +2275,9 @@ begin dec(FMemoEx.FUpdateLock); if FMemoEx.FUpdateLock = 0 then begin - {$IFDEF MEMOEX_EDITOR} FMemoEx.CantUndo; {$ENDIF MEMOEX_EDITOR} + {$IFDEF MEMOEX_EDITOR} + FMemoEx.CantUndo; + {$ENDIF MEMOEX_EDITOR} FMemoEx.TextAllChanged; FMemoEx.SetCaretInternal(0, 0); end; @@ -2777,8 +2779,10 @@ begin FCellRect.Height := Max(1, Size.cy); EditorClient.Canvas.Font := Font; Size := EditorClient.Canvas.TextExtent(BiggestSymbol); + { if FCellRect.Width <> Max(1, Size.cx) then raise EMemoExError.CreateFmt('Font %s has inconsistent width vs style', [Font.Name]); + } FDrawBitmap.Canvas.Font.Assign(Font); FDrawBitmap.Canvas.Brush.Assign(EditorClient.Canvas.Brush); FDrawBitmap.Width := Width; @@ -3477,8 +3481,6 @@ begin end; {$IFDEF MEMOEX_EDITOR} -type - EComplete = class(EAbort); procedure TCustomMemoEx.Command(ACommand: TEditCommand); var diff --git a/contrib/mORMot/SynMongoDB.pas b/contrib/mORMot/SynMongoDB.pas index d54f58b..8e10b80 100644 --- a/contrib/mORMot/SynMongoDB.pas +++ b/contrib/mORMot/SynMongoDB.pas @@ -6,7 +6,7 @@ unit SynMongoDB; { This file is part of Synopse framework. - Synopse framework. Copyright (C) 2020 Arnaud Bouchez + Synopse framework. Copyright (C) 2022 Arnaud Bouchez Synopse Informatique - https://synopse.info *** BEGIN LICENSE BLOCK ***** @@ -25,7 +25,7 @@ unit SynMongoDB; The Initial Developer of the Original Code is Arnaud Bouchez. - Portions created by the Initial Developer are Copyright (C) 2020 + Portions created by the Initial Developer are Copyright (C) 2022 the Initial Developer. All Rights Reserved. Contributor(s): @@ -1263,7 +1263,7 @@ type // be sent in the responseTo field from the database // - responseTo is the requestID taken from previous opQuery or opGetMore constructor Create(const FullCollectionName: RawUTF8; - opCode: TMongoOperation; requestID, responseTo: Integer); reintroduce; + opCode: TMongoOperation; requestID, responseTo: integer); reintroduce; /// append a query parameter as a BSON document // - param can be a TDocVariant, e.g. created with: // ! _JsonFast('{name:"John",age:{$gt:21}}'); @@ -1415,7 +1415,7 @@ type // $ { ReturnFieldsSelector: 1 } constructor Create(const FullCollectionName: RawUTF8; const Query, ReturnFieldsSelector: variant; NumberToReturn: integer; - NumberToSkip: Integer=0; Flags: TMongoQueryFlags=[]); reintroduce; + NumberToSkip: integer=0; Flags: TMongoQueryFlags=[]); reintroduce; /// write the main parameters of the request as JSON procedure ToJSON(W: TTextWriter; Mode: TMongoJSONMode); override; /// retrieve the NumberToReturn parameter as set to the constructor @@ -1653,7 +1653,7 @@ type /// where in the cursor this reply is starting property StartingFrom: integer read fStartingFrom; /// number of documents in the reply - property DocumentCount: Integer read fNumberReturned; + property DocumentCount: integer read fNumberReturned; /// points to the first document binary // - i.e. just after the Reply header property FirstDocument: PAnsiChar read fFirstDocument; @@ -1814,14 +1814,16 @@ type // - in case of any error, the error message is returned as text // - in case of success, this method will return '' function RunCommand(const aDatabaseName: RawUTF8; - const command: variant; var returnedValue: variant): RawUTF8; overload; + const command: variant; var returnedValue: variant; + flags: TMongoQueryFlags=[]): RawUTF8; overload; /// run a database command, supplied as a TDocVariant, TBSONVariant or a // string, and return the raw BSON document array of received items // - this overloaded method can be used on huge content to avoid the slower // conversion to an array of TDocVariant instances // - in case of success, this method will return TRUE, or FALSE on error function RunCommand(const aDatabaseName: RawUTF8; - const command: variant; var returnedValue: TBSONDocument): boolean; overload; + const command: variant; var returnedValue: TBSONDocument; + flags: TMongoQueryFlags=[]): boolean; overload; /// return TRUE if the Open method has successfully been called property Opened: boolean read GetOpened; @@ -1850,17 +1852,20 @@ type // data because secondaries replicate operations from the primary with some // delay - ensure that your application can tolerate stale data if you choose // to use a non-primary mode - // - rpPrimary: Default mode - all operations read from the current replica + // - rpPrimary: Default mode - all operations read from the current replica // set primary // - rpPrimaryPreferred: in most situations, operations read from the primary // but if it is unavailable, operations read from secondary members. // - rpPsecondary: all operations read from the secondary members // of the replica set - // - rpPsecondaryPreferred: in most situations, operations read from + // - rpPsecondaryPreferred: in most situations, operations read from // secondary members but if no secondary members are available, operations // read from the primary + // rpNearest: read from the member of the replica set with the least network + // latency, irrespective of whether that member is a primary or secondary + // (in practice, we won't use latency, just a random distribution) TMongoClientReplicaSetReadPreference = ( - rpPrimary, rpPrimaryPreferred, rpSecondary, rpSecondaryPreferred); + rpPrimary, rpPrimaryPreferred, rpSecondary, rpSecondaryPreferred, rpNearest); /// define Write Concern property of a MongoDB connection // - Write concern describes the guarantee that MongoDB provides when @@ -1923,13 +1928,13 @@ type fLogReplyEventMaxSize: cardinal; fServerBuildInfo: variant; fServerBuildInfoNumber: cardinal; - fLatestReadConnectionIndex: integer; - procedure AfterOpen; virtual; - function GetOneReadConnection: TMongoConnection; + fLatestReadConnectionIndex: PtrInt; + procedure AfterOpen(ConnectionIndex: PtrInt); virtual; function GetBytesReceived: Int64; function GetBytesSent: Int64; function GetBytesTransmitted: Int64; - procedure Auth(const DatabaseName,UserName,Digest: RawUTF8; ForceMongoDBCR: boolean); + procedure Auth(const DatabaseName,UserName,Digest: RawUTF8; + ForceMongoDBCR: boolean; ConnectionIndex: PtrInt); function ReOpen: boolean; public /// prepare a connection to a MongoDB server or Replica Set @@ -1937,7 +1942,7 @@ type // is called // - you can specify multiple hosts, as CSV values, if necessary // - depending on the platform, you may request for a TLS secured connection - constructor Create(const Host: RawUTF8; Port: Integer=MONGODB_DEFAULTPORT; + constructor Create(const Host: RawUTF8; Port: integer=MONGODB_DEFAULTPORT; aTLS: boolean=false; const SecondaryHostCSV: RawUTF8=''; const SecondaryPortCSV: RawUTF8=''); overload; /// connect to a database on a remote MongoDB primary server // - this method won't use authentication, and will return the corresponding @@ -1967,6 +1972,8 @@ type // - will create a string from ServerBuildInfo object, e.g. as // $ 'MongoDB 3.2.0 mozjs mmapv1,wiredTiger' function ServerBuildInfoText: RawUTF8; + // select Connection in dependence of ReadPreference + function GetOneReadConnection: TMongoConnection; /// retrieve the server version and build information // - return the content as a TDocVariant document, e.g. // ! ServerBuildInfo.version = '2.4.9' @@ -2156,7 +2163,7 @@ type // either null or the single returned document) // - if the query does not have any matching record, it will return null function FindDoc(const Criteria, Projection: Variant; - NumberToReturn: integer=1; NumberToSkip: Integer=0; + NumberToReturn: integer=1; NumberToSkip: integer=0; Flags: TMongoQueryFlags=[]): variant; overload; /// select documents in a collection and returns a dvArray TDocVariant // instance containing the selected documents @@ -2173,7 +2180,7 @@ type // either null or the single returned document) // - if the query does not have any matching record, it will return null function FindDoc(Criteria: PUTF8Char; const Params: array of const; - NumberToReturn: integer=maxInt; NumberToSkip: Integer=0; + NumberToReturn: integer=maxInt; NumberToSkip: integer=0; Flags: TMongoQueryFlags=[]): variant; overload; /// find an existing document in a collection, by its _id field // - _id will identify the unique document to be retrieved @@ -2197,7 +2204,7 @@ type // field names to retrieve, or a TDocVariant or TBSONVariant with // projection operators procedure FindDocs(var result: TVariantDynArray; const Projection: variant; - NumberToReturn: integer=maxInt; NumberToSkip: Integer=0; + NumberToReturn: integer=maxInt; NumberToSkip: integer=0; Flags: TMongoQueryFlags=[]); overload; /// select documents in a collection and returns a dynamic array of // TDocVariant instance containing the selected documents @@ -2208,7 +2215,7 @@ type // projection operators procedure FindDocs(Criteria: PUTF8Char; const Params: array of const; var result: TVariantDynArray; const Projection: variant; - NumberToReturn: integer=maxInt; NumberToSkip: Integer=0; + NumberToReturn: integer=maxInt; NumberToSkip: integer=0; Flags: TMongoQueryFlags=[]); overload; /// select documents in a collection and returns a dynamic array of // TDocVariant instance containing the selected documents @@ -2219,7 +2226,7 @@ type // field names to retrieve, or a TDocVariant or TBSONVariant with // projection operators function FindDocs(Criteria: PUTF8Char; const Params: array of const; - const Projection: variant; NumberToReturn: integer=maxInt; NumberToSkip: Integer=0; + const Projection: variant; NumberToReturn: integer=maxInt; NumberToSkip: integer=0; Flags: TMongoQueryFlags=[]): TVariantDynArray; overload; /// select documents in a collection and returns a JSON array of documents @@ -2242,7 +2249,7 @@ type // directly into JSON, in either modMongoStrict or modMongoShell layout // (modNoMongo will do the same as modMongoStrict) function FindJSON(const Criteria, Projection: Variant; - NumberToReturn: integer=maxInt; NumberToSkip: Integer=0; + NumberToReturn: integer=maxInt; NumberToSkip: integer=0; Flags: TMongoQueryFlags=[]; Mode: TMongoJSONMode=modMongoStrict): RawUTF8; overload; /// select documents in a collection and returns a JSON array of documents // containing the selected documents @@ -2258,14 +2265,14 @@ type // for one document - in this case, the returned instance won't be a '[..]' // JSON array, but either 'null' or a single '{..}' JSON object) function FindJSON(Criteria: PUTF8Char; const Params: array of const; - NumberToReturn: integer=maxInt; NumberToSkip: Integer=0; + NumberToReturn: integer=maxInt; NumberToSkip: integer=0; Flags: TMongoQueryFlags=[]; Mode: TMongoJSONMode=modMongoStrict): RawUTF8; overload; /// select documents in a collection and returns a JSON array of documents // containing the selected documents // - Criteria and Projection can specify the query selector as (extended) // JSON and parameters function FindJSON(Criteria: PUTF8Char; const CriteriaParams: array of const; - const Projection: variant; NumberToReturn: integer=maxInt; NumberToSkip: Integer=0; + const Projection: variant; NumberToReturn: integer=maxInt; NumberToSkip: integer=0; Flags: TMongoQueryFlags=[]; Mode: TMongoJSONMode=modMongoStrict): RawUTF8; overload; /// select documents in a collection and returns a TBSONDocument instance @@ -2282,7 +2289,7 @@ type // - NumberToReturn can be left to its default maxInt value to return all // matching documents, or specify a limit (e.g. 1 for one document) function FindBSON(const Criteria, Projection: Variant; - NumberToReturn: integer=maxInt; NumberToSkip: Integer=0; + NumberToReturn: integer=maxInt; NumberToSkip: integer=0; Flags: TMongoQueryFlags=[]): TBSONDocument; /// insert one document, supplied as (extended) JSON and parameters, @@ -2465,7 +2472,7 @@ type // - optional NumberToSkip can specify the number of matching documents // to skip before counting function FindCount(Criteria: PUTF8Char; const Args,Params: array of const; - MaxNumberToReturn: integer=0; NumberToSkip: Integer=0): Int64; overload; + MaxNumberToReturn: integer=0; NumberToSkip: integer=0): Int64; overload; /// returns TRUE if the collection has no document, FALSE otherwise // - is much faster than Count, especially for huge collections function IsEmpty: boolean; @@ -2694,7 +2701,7 @@ var //betNull, betRegEx, betDeprecatedDbptr, betJS, betDeprecatedSymbol, 0, -1, -1, -1, -1, //betJSScope, betInt32, betTimestamp, betInt64, betDecimal128 - -1, sizeof(Integer), sizeof(Int64), SizeOf(Int64), Sizeof(TDecimal128)); + -1, sizeof(integer), sizeof(Int64), SizeOf(Int64), Sizeof(TDecimal128)); /// types which do not have an exact equivalency to a standard variant // type will be mapped as varUnknown - and will be changed into @@ -3018,7 +3025,7 @@ str:Kind := betString; end; function TBSONElement.FromDocument(const doc: TBSONDocument): boolean; -var n: Integer; +var n: integer; begin FillCharFast(self,sizeof(self),0); n := length(doc); @@ -3170,7 +3177,7 @@ begin result := false; end; -procedure BSONToDoc(BSON: PByte; var Result: Variant; ExpectedBSONLen: Integer; +procedure BSONToDoc(BSON: PByte; var Result: Variant; ExpectedBSONLen: integer; Option: TBSONDocArrayConversion); begin if Option=asBSONVariant then @@ -3483,7 +3490,7 @@ begin end; procedure TBSONWriter.BSONAdjustDocumentsSize(BSON: PByteArray); -var i: Integer; +var i: integer; begin for i := 0 to fDocumentCount-1 do with fDocument[i] do @@ -4180,7 +4187,7 @@ var bsonvalue: TBSONVariantData absolute Value; Return(betDecimal128,P+L+1,GotoEndOfObject); end; var Reg,Opt: PUTF8Char; - RegLen,OptLen: Integer; + RegLen,OptLen: integer; procedure ReturnRegEx(P: PUTF8Char; GotoEndOfObject: AnsiChar); var buf: PAnsiChar; begin @@ -4486,7 +4493,7 @@ end; function BSON(const NameValuePairs: array of const): TBSONDocument; var W: TBSONWriter; name: RawUTF8; - a: Integer; + a: integer; procedure WriteValue; var ndx: cardinal; begin @@ -4715,10 +4722,10 @@ const CLIENT_OPCODES = [opUpdate,opInsert,opQuery,opGetMore,opDelete,opKillCursors]; var - GlobalRequestID: Integer; + GlobalRequestID: integer; constructor TMongoRequest.Create(const FullCollectionName: RawUTF8; - opCode: TMongoOperation; requestID, responseTo: Integer); + opCode: TMongoOperation; requestID, responseTo: integer); begin if not (opCode in CLIENT_OPCODES) then raise EMongoException.CreateUTF8('Unexpected %.Create(opCode=%)',[self,ToText(opCode)^]); @@ -4875,7 +4882,7 @@ end; constructor TMongoRequestQuery.Create(const FullCollectionName: RawUTF8; const Query, ReturnFieldsSelector: variant; NumberToReturn: integer; - NumberToSkip: Integer=0; Flags: TMongoQueryFlags=[]); + NumberToSkip: integer; Flags: TMongoQueryFlags); begin inherited Create(FullCollectionName,opQuery,0,0); fNumberToReturn := NumberToReturn; @@ -5490,10 +5497,10 @@ begin end; function TMongoConnection.RunCommand(const aDatabaseName: RawUTF8; - const command: variant; var returnedValue: variant): RawUTF8; + const command: variant; var returnedValue: variant; flags: TMongoQueryFlags): RawUTF8; begin GetDocumentsAndFree( - TMongoRequestQuery.Create(aDatabaseName+'.$cmd',command,null,1), + TMongoRequestQuery.Create(aDatabaseName+'.$cmd',command,null,1,0,flags), returnedValue); with _Safe(returnedValue)^ do if GetValueOrDefault('ok',1)<>0 then @@ -5503,11 +5510,12 @@ begin end; function TMongoConnection.RunCommand(const aDatabaseName: RawUTF8; - const command: variant; var returnedValue: TBSONDocument): boolean; + const command: variant; var returnedValue: TBSONDocument; + flags: TMongoQueryFlags): boolean; var item: TBSONElement; begin returnedValue := GetBSONAndFree( - TMongoRequestQuery.Create(aDatabaseName+'.$cmd',command,null,1)); + TMongoRequestQuery.Create(aDatabaseName+'.$cmd',command,null,1,0,flags)); result := true; item.FromDocument(returnedValue); if item.DocItemToInteger('ok',1)=0 then @@ -5647,7 +5655,7 @@ end; { TMongoClient } -constructor TMongoClient.Create(const Host: RawUTF8; Port: Integer; +constructor TMongoClient.Create(const Host: RawUTF8; Port: integer; aTLS: boolean; const SecondaryHostCSV, SecondaryPortCSV: RawUTF8); const PROT: array[boolean] of string[1] = ('', 's'); var secHost: TRawUTF8DynArray; @@ -5711,40 +5719,41 @@ begin end; function TMongoClient.GetOneReadConnection: TMongoConnection; -function GetUnlockedSecondaryIndex: integer; -var retry: integer; -begin - if Length(fConnections)=1 then // no secondary? use primary - result := 0 else begin - for retry := 1 to 100 do begin // search for an inactive connection - result := fLatestReadConnectionIndex; // simple round-robin pattern - if result=high(fConnections) then - if ReadPreference=rpSecondary then - result := 1 else - result := 0 else - inc(result); // thread-safety is not an issue here - if (retry<=length(fConnections)) and not fConnections[result].Opened then - try - fConnections[result].Open; - except - on E: Exception do - begin - SleepHiRes(2); - continue; + function GetUnlockedSecondaryIndex: PtrInt; + var retry: integer; + begin + if Length(fConnections)=1 then // no secondary? use primary + result := 0 else begin + for retry := 1 to 100 do begin // search for an inactive connection + result := fLatestReadConnectionIndex; // simple round-robin pattern + if result=high(fConnections) then + if ReadPreference=rpSecondary then + result := 1 else + result := 0 else + inc(result); // thread-safety is not an issue here + if (retry<=length(fConnections)) and not fConnections[result].Opened then + try + fConnections[result].Open; + except + on E: Exception do + begin + SleepHiRes(2); + continue; + end; end; + if fConnections[result].Opened then + if fConnections[result].Locked then + if retry mod length(fConnections)=0 then + SleepHiRes(2) else + continue else + break; end; - if fConnections[result].Opened then - if fConnections[result].Locked then - if retry mod length(fConnections)=0 then - SleepHiRes(2) else - continue else - break; + if not fConnections[result].Opened then + result := 0; // safe fallback to primary member in worst case + fLatestReadConnectionIndex := result; end; - if not fConnections[result].Opened then - result := 0; // safe fallback to primary member in worst case - fLatestReadConnectionIndex := result; end; -end; +var n, retry: integer; begin case ReadPreference of rpPrimaryPreferred: @@ -5753,7 +5762,15 @@ begin result := fConnections[0]; rpSecondary, rpSecondaryPreferred: result := fConnections[GetUnlockedSecondaryIndex]; - else // rpPrimary: + rpNearest: begin + n := Length(fConnections); + for retry := 1 to n*2 do begin + result := fConnections[Random32(n)]; + if not result.Locked then + exit; + end; + result := fConnections[0]; // falback to the main instance + end else // rpPrimary or not handled yet result := fConnections[0]; end; end; @@ -5766,7 +5783,7 @@ begin if result=nil then begin // not already opened -> try now from primary host if not fConnections[0].Opened then begin fConnections[0].Open; - AfterOpen; + AfterOpen(0); end; result := TMongoDatabase.Create(Self,DatabaseName); fDatabases.AddObjectUnique(DatabaseName,@result); @@ -5782,29 +5799,32 @@ end; function TMongoClient.OpenAuth(const DatabaseName,UserName,PassWord: RawUTF8; ForceMongoDBCR: boolean): TMongoDatabase; var digest: RawByteString; + i: PtrInt; begin if (self=nil) or (DatabaseName='') or (UserName='') or (PassWord='') then raise EMongoException.CreateUTF8('Invalid %.OpenAuth("%") call',[self,DatabaseName]); result := fDatabases.GetObjectFrom(DatabaseName); - if result=nil then // not already opened -> try now from primary host - try // note: authentication works on a single database per socket connection - if not fConnections[0].Opened then - try - fConnections[0].Open; // socket connection - AfterOpen; // need ServerBuildInfoNumber just below - digest := PasswordDigest(UserName,Password); - Auth(DatabaseName,UserName,digest,ForceMongoDBCR); - with fGracefulReconnect do - if Enabled and (EncryptedDigest='') then begin - ForcedDBCR := ForceMongoDBCR; - User := UserName; - Database := DatabaseName; - EncryptedDigest := CryptDataForCurrentUser(digest,Database,true); + if result=nil then // not already opened -> try now + try + // ensure we are opened and authenticated on all connections + for i := 0 to High(fConnections) do + if not fConnections[i].Opened then + try + fConnections[i].Open; // socket connection + AfterOpen(i); // need ServerBuildInfoNumber just below + digest := PasswordDigest(UserName,Password); + Auth(DatabaseName,UserName,digest,ForceMongoDBCR, i); + with fGracefulReconnect do + if Enabled and (EncryptedDigest='') then begin + ForcedDBCR := ForceMongoDBCR; + User := UserName; + Database := DatabaseName; + EncryptedDigest := CryptDataForCurrentUser(digest,Database,true); + end; + except + fConnections[i].Close; + raise; end; - except - fConnections[0].Close; - raise; - end; result := TMongoDatabase.Create(Self,DatabaseName); fDatabases.AddObjectUnique(DatabaseName,@result); finally @@ -5813,7 +5833,7 @@ begin end; procedure TMongoClient.Auth(const DatabaseName,UserName,Digest: RawUTF8; - ForceMongoDBCR: boolean); + ForceMongoDBCR: boolean; ConnectionIndex: PtrInt); var res,bson: variant; err,nonce,first,key,user,msg,rnonce: RawUTF8; payload: RawByteString; @@ -5840,7 +5860,7 @@ begin // caller should have made fConnections[0].Open // MONGODB-CR // http://docs.mongodb.org/meta-driver/latest/legacy/implement-authentication-in-driver bson := BSONVariant(['getnonce',1]); - err := fConnections[0].RunCommand(DatabaseName,bson,res); + err := fConnections[ConnectionIndex].RunCommand(DatabaseName,bson,res); if (err='') and not _Safe(res)^.GetAsRawUTF8('nonce',nonce) then err := 'missing returned nonce'; if err<>'' then @@ -5848,7 +5868,7 @@ begin // caller should have made fConnections[0].Open [self,DatabaseName,err,res]); key := MD5(nonce+UserName+Digest); bson := BSONVariant(['authenticate',1,'user',UserName,'nonce',nonce,'key',key]); - err := fConnections[0].RunCommand(DatabaseName,bson,res); + err := fConnections[ConnectionIndex].RunCommand(DatabaseName,bson,res); if err<>'' then raise EMongoException.CreateUTF8('%.OpenAuthCR("%") step2: % - res=%', [self,DatabaseName,err,res]); @@ -5860,7 +5880,7 @@ begin // caller should have made fConnections[0].Open nonce := BinToBase64(@rnd,sizeof(rnd)); FormatUTF8('n=%,r=%',[user,nonce],first); BSONVariantType.FromBinary('n,,'+first,bbtGeneric,bson); - err := fConnections[0].RunCommand(DatabaseName,BSONVariant([ + err := fConnections[ConnectionIndex].RunCommand(DatabaseName,BSONVariant([ 'saslStart',1,'mechanism','SCRAM-SHA-1','payload',bson,'autoAuthorize',1]),res); CheckPayload; if err='' then begin @@ -5882,7 +5902,7 @@ begin // caller should have made fConnections[0].Open HMAC_SHA1(server,msg,server); msg := key+',p='+BinToBase64(@client,SizeOf(client)); BSONVariantType.FromBinary(msg,bbtGeneric,bson); - err := fConnections[0].RunCommand(DatabaseName,BSONVariant([ + err := fConnections[ConnectionIndex].RunCommand(DatabaseName,BSONVariant([ 'saslContinue',1,'conversationId',res.conversationId,'payload',bson]),res); resp.Clear; CheckPayload; @@ -5893,7 +5913,7 @@ begin // caller should have made fConnections[0].Open [self,DatabaseName,err,res]); if not res.done then begin // third empty challenge may be required - err := fConnections[0].RunCommand(DatabaseName,BSONVariant([ + err := fConnections[ConnectionIndex].RunCommand(DatabaseName,BSONVariant([ 'saslContinue',1,'conversationId',res.conversationId,'payload','']),res); if (err='') and not res.done then err := 'SASL conversation failed to complete'; @@ -5904,10 +5924,10 @@ begin // caller should have made fConnections[0].Open end; end; -procedure TMongoClient.AfterOpen; +procedure TMongoClient.AfterOpen(ConnectionIndex: PtrInt); begin if VarIsEmptyOrNull(fServerBuildInfo) then begin - fConnections[0].RunCommand('admin','buildinfo',fServerBuildInfo); + fConnections[ConnectionIndex].RunCommand('admin','buildinfo',fServerBuildInfo); with _Safe(fServerBuildInfo)^.A['versionArray']^ do if Count=4 then fServerBuildInfoNumber := // e.g. 2040900 for MongoDB 2.4.9 @@ -5929,7 +5949,7 @@ begin if EncryptedDigest<>'' then try digest := CryptDataForCurrentUser(EncryptedDigest,Database,false); - Auth(Database,User,digest,ForcedDBCR); + Auth(Database,User,digest,ForcedDBCR,0); finally FillZero(digest); end; @@ -5982,7 +6002,7 @@ var colls: TBSONIterator; full,db,coll: RawUTF8; resp,batch: variant; mc: TMongoCollection; - ndx: Integer; + ndx: integer; begin fClient := aClient; fName := aDatabaseName; @@ -6245,7 +6265,7 @@ end; procedure TMongoCollection.EnsureIndex(const Keys: array of RawUTF8; Ascending, Unique: boolean); -const Order: array[boolean] of Integer = (-1,1); +const Order: array[boolean] of integer = (-1,1); var k,opt: variant; A: integer; begin @@ -6275,7 +6295,7 @@ end; function TMongoCollection.FindCount(Criteria: PUTF8Char; const Args,Params: array of const; - MaxNumberToReturn: integer=0; NumberToSkip: Integer=0): Int64; + MaxNumberToReturn, NumberToSkip: integer): Int64; var cmd: RawUTF8; res: variant; begin @@ -6296,7 +6316,7 @@ begin // much faster than Count>0 for huge collections end; function TMongoCollection.FindBSON(const Criteria, Projection: Variant; - NumberToReturn, NumberToSkip: Integer; Flags: TMongoQueryFlags): TBSONDocument; + NumberToReturn, NumberToSkip: integer; Flags: TMongoQueryFlags): TBSONDocument; begin result := Database.Client.GetOneReadConnection.GetBSONAndFree( TMongoRequestQuery.Create(fFullCollectionName, @@ -6304,7 +6324,7 @@ begin end; function TMongoCollection.FindDoc(const Criteria, Projection: Variant; - NumberToReturn, NumberToSkip: Integer; Flags: TMongoQueryFlags): variant; + NumberToReturn, NumberToSkip: integer; Flags: TMongoQueryFlags): variant; begin Database.Client.GetOneReadConnection.GetDocumentsAndFree( TMongoRequestQuery.Create(fFullCollectionName, @@ -6312,7 +6332,7 @@ begin end; function TMongoCollection.FindDoc(Criteria: PUTF8Char; - const Params: array of const; NumberToReturn, NumberToSkip: Integer; + const Params: array of const; NumberToReturn, NumberToSkip: integer; Flags: TMongoQueryFlags): variant; begin result := FindDoc(BSONVariant(Criteria,[],Params),null, @@ -6321,7 +6341,7 @@ end; procedure TMongoCollection.FindDocs(Criteria: PUTF8Char; const Params: array of const; var result: TVariantDynArray; - const Projection: variant; NumberToReturn, NumberToSkip: Integer; + const Projection: variant; NumberToReturn, NumberToSkip: integer; Flags: TMongoQueryFlags); begin Database.Client.GetOneReadConnection.GetDocumentsAndFree( @@ -6331,7 +6351,7 @@ begin end; function TMongoCollection.FindDocs(Criteria: PUTF8Char; const Params: array of const; - const Projection: variant; NumberToReturn,NumberToSkip: Integer; + const Projection: variant; NumberToReturn,NumberToSkip: integer; Flags: TMongoQueryFlags): TVariantDynArray; begin FindDocs(Criteria,Params,result,Projection,NumberToReturn,NumberToSkip,Flags); @@ -6356,7 +6376,7 @@ begin end; procedure TMongoCollection.FindDocs(var result: TVariantDynArray; - const Projection: variant; NumberToReturn, NumberToSkip: Integer; + const Projection: variant; NumberToReturn, NumberToSkip: integer; Flags: TMongoQueryFlags); begin Database.Client.GetOneReadConnection.GetDocumentsAndFree( @@ -6365,7 +6385,7 @@ begin end; function TMongoCollection.FindJSON(const Criteria, Projection: Variant; - NumberToReturn, NumberToSkip: Integer; Flags: TMongoQueryFlags; + NumberToReturn, NumberToSkip: integer; Flags: TMongoQueryFlags; Mode: TMongoJSONMode): RawUTF8; begin result := Database.Client.GetOneReadConnection.GetJSONAndFree( @@ -6374,7 +6394,7 @@ begin end; function TMongoCollection.FindJSON(Criteria: PUTF8Char; - const Params: array of const; NumberToReturn, NumberToSkip: Integer; + const Params: array of const; NumberToReturn, NumberToSkip: integer; Flags: TMongoQueryFlags; Mode: TMongoJSONMode): RawUTF8; begin result := FindJSON(BSONVariant(Criteria,[],Params),null, @@ -6383,7 +6403,7 @@ end; function TMongoCollection.FindJSON( Criteria: PUTF8Char; const CriteriaParams: array of const; - const Projection: variant; NumberToReturn, NumberToSkip: Integer; + const Projection: variant; NumberToReturn, NumberToSkip: integer; Flags: TMongoQueryFlags; Mode: TMongoJSONMode): RawUTF8; begin result := FindJSON(BSONVariant(Criteria,[],CriteriaParams), @@ -6398,7 +6418,7 @@ begin end; procedure TMongoCollection.Insert(const Documents: TBSONDocument; - Flags: TMongoInsertFlags=[]; NoAcknowledge: boolean=false); + Flags: TMongoInsertFlags; NoAcknowledge: boolean); begin Database.Client.Connections[0].SendAndFree(TMongoRequestInsert.Create( fFullCollectionName,Documents,Flags),NoAcknowledge); @@ -6612,19 +6632,24 @@ begin end; function div128bits9digits(var value: THash128Rec): PtrUInt; -var r64: QWord; +var r: QWord; i: PtrInt; begin - r64 := 0; + r := 0; for i := 0 to high(value.c) do begin - r64 := r64 shl 32; // adjust remainder to match value of next dividend - inc(r64,value.c[i]); // add the divided to _rem - if r64=0 then + {$ifdef FPC_32} // circumvent bug at least with FPC 3.2 + Int64Rec(r).Hi := Int64Rec(r).Lo; + Int64Rec(r).Lo := 0; + {$else} + r := r shl 32; // adjust remainder to match value of next dividend + {$endif FPC_32} + inc(r,value.c[i]); // add the divided to _rem + if r=0 then continue; - value.c[i] := r64 div 1000000000; - dec(r64,QWord(value.c[i])*1000000000); + value.c[i] := r div 1000000000; + dec(r,QWord(value.c[i])*1000000000); end; - result := r64; + result := r; end; procedure append(var dest: PUTF8Char; var dig: PByte; digits: PtrInt); diff --git a/contrib/mORMot/SynMustache.pas b/contrib/mORMot/SynMustache.pas index 6c18a67..7e86dcb 100644 --- a/contrib/mORMot/SynMustache.pas +++ b/contrib/mORMot/SynMustache.pas @@ -6,7 +6,7 @@ unit SynMustache; { This file is part of Synopse mORMot framework. - Synopse mORMot framework. Copyright (C) 2020 Arnaud Bouchez + Synopse mORMot framework. Copyright (C) 2022 Arnaud Bouchez Synopse Informatique - https://synopse.info *** BEGIN LICENSE BLOCK ***** @@ -25,7 +25,7 @@ unit SynMustache; The Initial Developer of the Original Code is Arnaud Bouchez. - Portions created by the Initial Developer are Copyright (C) 2020 + Portions created by the Initial Developer are Copyright (C) 2022 the Initial Developer. All Rights Reserved. Contributor(s): diff --git a/contrib/mORMot/SynOleDB.pas b/contrib/mORMot/SynOleDB.pas index 3a16cb4..77428e1 100644 --- a/contrib/mORMot/SynOleDB.pas +++ b/contrib/mORMot/SynOleDB.pas @@ -6,7 +6,7 @@ unit SynOleDB; { This file is part of Synopse framework. - Synopse framework. Copyright (C) 2020 Arnaud Bouchez + Synopse framework. Copyright (C) 2022 Arnaud Bouchez Synopse Informatique - https://synopse.info *** BEGIN LICENSE BLOCK ***** @@ -25,7 +25,7 @@ unit SynOleDB; The Initial Developer of the Original Code is Arnaud Bouchez. - Portions created by the Initial Developer are Copyright (C) 2020 + Portions created by the Initial Developer are Copyright (C) 2022 the Initial Developer. All Rights Reserved. Contributor(s): @@ -603,6 +603,7 @@ type { -------------- TOleDB* OleDB classes and types } + type /// generic Exception type, generated for OleDB connection EOleDBException = class(ESQLDBException); @@ -2792,6 +2793,7 @@ type bClass: byte; wLineNumber: word; end; + /// to retrieve enhanced Microsoft SQL Server error information ISQLServerErrorInfo = interface(IUnknown) ['{5CF4CA12-EF21-11d0-97E7-00C04FC2AD98}'] function GetErrorInfo(out ppErrorInfo: PSSERRORINFO; @@ -3188,6 +3190,7 @@ initialization {$ifndef CPU64} // Jet is not available on Win64 TOleDBJetConnectionProperties.RegisterClassNameForDefinition; {$endif} + TOleDBACEConnectionProperties.RegisterClassNameForDefinition; TOleDBAS400ConnectionProperties.RegisterClassNameForDefinition; TOleDBODBCSQLConnectionProperties.RegisterClassNameForDefinition; diff --git a/contrib/mORMot/SynOpenSSL.pas b/contrib/mORMot/SynOpenSSL.pas index 52e2483..a310f33 100644 --- a/contrib/mORMot/SynOpenSSL.pas +++ b/contrib/mORMot/SynOpenSSL.pas @@ -6,7 +6,7 @@ unit SynOpenSSL; { This file is part of Synopse framework. - Synopse framework. Copyright (C) 2020 Arnaud Bouchez + Synopse framework. Copyright (C) 2022 Arnaud Bouchez Synopse Informatique - https://synopse.info *** BEGIN LICENSE BLOCK ***** @@ -25,7 +25,7 @@ unit SynOpenSSL; The Initial Developer of the Original Code is Arnaud Bouchez. - Portions created by the Initial Developer are Copyright (C) 2020 + Portions created by the Initial Developer are Copyright (C) 2022 the Initial Developer. All Rights Reserved. Contributor(s): diff --git a/contrib/mORMot/SynPdf.pas b/contrib/mORMot/SynPdf.pas index ab8257d..8156fab 100644 --- a/contrib/mORMot/SynPdf.pas +++ b/contrib/mORMot/SynPdf.pas @@ -6,7 +6,7 @@ unit SynPdf; { This file is part of Synopse framework. - Synopse framework. Copyright (C) 2020 Arnaud Bouchez + Synopse framework. Copyright (C) 2022 Arnaud Bouchez Synopse Informatique - https://synopse.info *** BEGIN LICENSE BLOCK ***** @@ -25,7 +25,7 @@ unit SynPdf; The Initial Developer of the Original Code is Arnaud Bouchez. - Portions created by the Initial Developer are Copyright (C) 2020 + Portions created by the Initial Developer are Copyright (C) 2022 the Initial Developer. All Rights Reserved. Contributor(s): @@ -41,6 +41,7 @@ unit SynPdf; Harald Simon Josh Kelley (joshkel) Karel (vandrovnik) + Kukhtin Igor LoukaO Marsh MChaos @@ -69,7 +70,7 @@ unit SynPdf; Sponsors: https://synopse.info/fossil/wiki?name=HelpDonate Ongoing development and maintenance of the SynPDF library was sponsored in part by: - http://www.helpndoc.com + https://www.helpndoc.com Easy to use yet powerful help authoring environment which can generate various documentation formats from a single source. Thanks for your contribution! @@ -88,6 +89,14 @@ unit SynPdf; {$define NO_USE_BITMAP} {$endif} +{.$define USE_PDFALEVEL} +{ - if defined, the TPdfDocument*.Create() constructor will have an PDF/A level + value instead of the boolean value PDFA1} +{$ifdef NO_USE_PDFALEVEL} + { this special conditional can be set globaly for an application which doesn't + need the PDF/A level features } + {$undef USE_PDFALEVEL} +{$endif} {$define USE_PDFSECURITY} { - if defined, the TPdfDocument*.Create() constructor will have an additional @@ -103,8 +112,8 @@ unit SynPdf; { - if defined, the PDF engine will use the Windows Uniscribe API to render Ordering and Shaping of the text (useful for Hebrew, Arabic and some Asiatic languages) - - this feature need the TPdfDocument.UseUniscribe property to be forced to true - according to the language of the text you want to render + - this feature need the TPdfDocument.UseUniscribe property to be forced to + true according to the language of the text you want to render - can be undefined to safe some KB if you're sure you won't need it } {$ifdef NO_USE_UNISCRIBE} { this special conditional can be set globaly for an application which doesn't @@ -287,6 +296,7 @@ type indexToLocFormat: SmallInt; glyphDataFormat: SmallInt end; + PCmapHEAD = ^TCmapHEAD; /// header for the 'cmap' Format 4 table // - this is a two-byte encoding format TCmapFmt4 = packed record @@ -311,6 +321,9 @@ type /// the internal pdf file format TPdfFileFormat = (pdf13, pdf14, pdf15, pdf16); + /// the PDF/A level + TPdfALevel = (pdfaNone, pdfa1A, pdfa1B, pdfa2A, pdfa2B, pdfa3A, pdfa3B); + /// PDF exception, raised when an invalid value is given to a constructor EPdfInvalidValue = class(Exception); @@ -833,6 +846,10 @@ type TPdfName = class(TPdfText) protected procedure InternalWriteTo(W: TPdfWrite); override; + public + /// append the 'SUBSET+' prefix to the Value + // - used e.g. to notify that a font is included as a subset + procedure AppendPrefix; end; /// used to store an array of PDF objects @@ -1176,7 +1193,7 @@ type /// internal temporary variable - used by CreateOutline fLastOutline: TPdfOutlineEntry; fFileFormat: TPdfFileFormat; - fPDFA1: boolean; + fPDFA: TPdfALevel; fSaveToStreamWriter: TPdfWrite; {$ifdef USE_PDFSECURITY} fEncryption: TPdfEncryption; @@ -1195,7 +1212,11 @@ type procedure SetDefaultPageHeight(const Value: cardinal); procedure SetDefaultPageWidth(const Value: cardinal); procedure SetUseOptionalContent(const Value: boolean); + procedure SetPDFA(const Value: TPdfALevel); + {$ifndef USE_PDFALEVEL} + function GetPDFA1: boolean; procedure SetPDFA1(const Value: boolean); + {$endif} function GetDefaultPageLandscape: boolean; procedure SetDefaultPageLandscape(const Value: boolean); procedure SetFontFallBackName(const Value: string); @@ -1240,10 +1261,11 @@ type // - note that only Win-Ansi encoding allows use of embedded standard fonts // - you can specify a Code Page to be used for the PDFString encoding; // by default (ACodePage left to 0), the current system code page is used - // - you can create a PDF/A-1 compliant document by setting APDFA1 to true + // - you can create a PDF/A compliant document by setting APDFA to PDF/A Level + // or APDFA1 to true // - you can set an encryption instance, by using TPdfEncryption.New() constructor Create(AUseOutlines: Boolean=false; ACodePage: integer=0; - APDFA1: boolean=false + {$ifdef USE_PDFALEVEL]}APDFA: TPdfALevel=pdfaNone{$else}APDFA1: boolean=false{$endif} {$ifdef USE_PDFSECURITY}; AEncryption: TPdfEncryption=nil{$endif}); reintroduce; /// release the PDF document instance destructor Destroy; override; @@ -1253,6 +1275,11 @@ type procedure NewDoc; /// add a Page to the current PDF document function AddPage: TPdfPage; virtual; + /// register a font to the internal TTF font list + // - some fonts may not be enumerated in the system, e.g. after calling + // AddFontMemResourceEx, so could be registered by this method + // - to be called just after Create(), before anything is written + function AddTrueTypeFont(const TTFName: RawUtf8): boolean; /// create a Pages object // - Pages objects can be nested, to save memory used by the Viewer // - only necessary if you have more than 8000 pages (this method is called @@ -1464,12 +1491,20 @@ type // the native resolution of the PDF, i.e. more than 7200 DPI (since we // write coordinates with 2 decimals per point - which is 1/72 inch) property ScreenLogPixels: Integer read FScreenLogPixels write FScreenLogPixels; + /// is pdfaXXX if the file was created in order to be PDF/A compliant + // - set APDFA parameter to a level for Create constructor in order to use it + // - warning: setting a value to this propery after creation will call the + // NewDoc method, therefore will erase all previous content and pages + // (including Info properties) + property PDFA: TPdfALevel read fPDFA write SetPDFA; + {$ifndef USE_PDFALEVEL} /// is TRUE if the file was created in order to be PDF/A-1 compliant // - set APDFA1 parameter to true for Create constructor in order to use it // - warning: setting a value to this propery after creation will call the // NewDoc method, therefore will erase all previous content and pages // (including Info properties) - property PDFA1: boolean read fPDFA1 write SetPDFA1; + property PDFA1: boolean read GetPDFA1 write SetPDFA1; + {$endif} /// set to TRUE to force PDF 1.5 format, which may produce smaller files property GeneratePDF15File: boolean read GetGeneratePDF15File write SetGeneratePDF15File; end; @@ -1510,24 +1545,24 @@ type // the index in Text, not the glyphs index function MeasureText(const Text: PDFString; Width: Single): integer; public - /// retrieve or set the word Space attribute + /// retrieve or set the word Space attribute, in PDF coordinates of 1/72 inch property WordSpace: Single read FWordSpace write SetWordSpace; - /// retrieve or set the Char Space attribute + /// retrieve or set the Char Space attribute, in PDF coordinates of 1/72 inch property CharSpace: Single read FCharSpace write SetCharSpace; - /// retrieve or set the Horizontal Scaling attribute + /// retrieve or set the Horizontal Scaling attribute, in PDF coordinates of 1/72 inch property HorizontalScaling: Single read FHorizontalScaling write SetHorizontalScaling; - /// retrieve or set the text Leading attribute + /// retrieve or set the text Leading attribute, in PDF coordinates of 1/72 inch property Leading: Single read FLeading write SetLeading; - /// retrieve or set the font Size attribute + /// retrieve or set the font Size attribute, in system TFont.Size units property FontSize: Single read FFontSize write SetFontSize; /// retrieve the current used font // - for TPdfFontTrueType, this points not always to the WinAnsi version of // the Font, but can also point to the Unicode Version, if the last // drawn character by ShowText() was unicode - see TPdfWrite.AddUnicodeHexText property Font: TPdfFont read FFont write FFont; - /// retrieve or set the current page width + /// retrieve or set the current page width, in PDF coordinates of 1/72 inch property PageWidth: integer read GetPageWidth write SetPageWidth; - /// retrieve or set the current page height + /// retrieve or set the current page height, in PDF coordinates of 1/72 inch property PageHeight: integer read GetPageHeight write SetPageHeight; /// retrieve or set the paper orientation property PageLandscape: Boolean read GetPageLandscape write SetPageLandscape; @@ -1580,7 +1615,8 @@ type // = XOff,YOff parameters specified in RenderMetaFile() FOffsetXDef, FOffsetYDef: Single; // WorldTransform factor and offs - FWorldFactorX, FWorldFactorY, FWorldOffsetX, FWorldOffsetY: single; + FWorldFactorX, FWorldFactorY, FWorldOffsetX, FWorldOffsetY, FAngle, + FWorldCos, FWorldSin: single; FDevScaleX, FDevScaleY: single; FWinSize, FViewSize: TSize; FWinOrg, FViewOrg: TPoint; @@ -2019,7 +2055,7 @@ type // - use the GDIComment*() functions to append the corresponding // EMR_GDICOMMENT message to a metafile content TPdfGDIComment = - (pgcOutline, pgcBookmark, pgcLink, pgcLinkNoBorder); + (pgcOutline, pgcBookmark, pgcLink, pgcLinkNoBorder, pgcJpegDirect); /// a dictionary wrapper class for the PDF document information fields // - all values use the generic VCL string type, and will be encoded @@ -2444,7 +2480,7 @@ type /// create the PDF document instance, with a VCL Canvas property // - see TPdfDocument.Create connstructor for the arguments expectations constructor Create(AUseOutlines: Boolean=false; ACodePage: integer=0; - APDFA1: boolean=false + {$ifdef USE_PDFALEVEL]}APDFA: TPdfALevel=pdfaNone{$else}APDFA1: boolean=false{$endif} {$ifdef USE_PDFSECURITY}; AEncryption: TPdfEncryption=nil{$endif}); /// add a Page to the current PDF document function AddPage: TPdfPage; override; @@ -2659,6 +2695,8 @@ procedure GDICommentOutline(MetaHandle: HDC; const aTitle: RawUTF8; aLevel: Inte procedure GDICommentLink(MetaHandle: HDC; const aBookmarkName: RawUTF8; const aRect: TRect; NoBorder: boolean); +/// append a EMR_GDICOMMENT message for adding jpeg direct +procedure GDICommentJpegDirect(MetaHandle: HDC; const aFileName: RawUTF8; const aRect: TRect); {$ifdef USE_PDFSECURITY} const @@ -2942,6 +2980,13 @@ function ScriptApplyDigitSubstitution( implementation +const + // those constants are not defined in earlier Delphi revisions + cPI: single = 3.141592654; + cPIdiv180: single = 0.017453292; + c180divPI: single = 57.29577951; + c2PI: double = 6.283185307; + cPIdiv2: double = 1.570796326; function RGBA(r, g, b, a: cardinal): COLORREF; {$ifdef HASINLINE}inline;{$endif} begin @@ -3118,6 +3163,20 @@ begin // high(TPdfGDIComment)<$47 so it will never begin with GDICOMMENT_IDENTIF Windows.GdiComment(MetaHandle,L+(1+sizeof(TRect)),D); end; +procedure GDICommentJpegDirect(MetaHandle: HDC; const aFileName: RawUTF8; const aRect: TRect); +var Data: RawByteString; + D: PAnsiChar; + L: integer; +begin // high(TPdfGDIComment)<$47 so it will never begin with GDICOMMENT_IDENTIFIER + L := length(aFileName); + SetLength(Data,L+(1+sizeof(TRect))); + D := pointer(Data); + D^ := AnsiChar(pgcJpegDirect); + PRect(D+1)^ := aRect; + MoveFast(pointer(aFileName)^,D[1+sizeof(TRect)],L); + Windows.GdiComment(MetaHandle,L+(1+sizeof(TRect)),D); +end; + {$ifndef DELPHI5OROLDER} // used by TPdfFontTrueType.PrepareForSaving() function GetTTCIndex(const FontName: RawUTF8; var ttcIndex: Word; @@ -3234,7 +3293,6 @@ type TCoeff = array[0..3] of double; TCoeffArray = array[0..1, 0..3] of TCoeff; const - twoPi = 2 * PI; // coefficients for error estimation // while using cubic Bezier curves for approximation // 0 < b/a < 1/4 @@ -3292,11 +3350,11 @@ begin feta1 := ArcTan2(sin(lambda1) / fbRad, cos(lambda1) / faRad); feta2 := ArcTan2(sin(lambda2) / fbRad, cos(lambda2) / faRad); // make sure we have eta1 <= eta2 <= eta1 + 2 PI - feta2 := feta2 - (twoPi * floor((feta2 - feta1) / twoPi)); + feta2 := feta2 - (c2PI * floor((feta2 - feta1) / c2PI)); // the preceding correction fails if we have exactly et2 - eta1 = 2 PI // it reduces the interval to zero length if SameValue(feta1, feta2) then - feta2 := feta2 + twoPi; + feta2 := feta2 + c2PI; // start point fx1 := fcx + (faRad * cos(feta1)); fy1 := fcy + (fbRad * sin(feta1)); @@ -3360,7 +3418,7 @@ begin n := 1; while (not found) and (n < 1024) do begin dEta := (feta2 - feta1) / n; - if dEta <= 0.5 * PI then begin + if dEta <= cPIdiv2 then begin etaB := feta1; found := true; for i := 0 to n - 1 do begin @@ -3713,6 +3771,24 @@ begin W.Add('/').AddEscapeName(pointer(FValue)); end; +procedure TPdfName.AppendPrefix; +var prefix: RawUtf8; + c: cardinal; + i: PtrInt; +begin + if self=nil then + exit; + SetLength(prefix, 7); + c := Random32; // we will consume only 24-bit of randomness + for i := 1 to 6 do + begin + prefix[i] := AnsiChar((c and 15) + 65); + c := c shr 4; + end; + prefix[7] := '+'; + FValue := prefix+FValue; // we ensured a single subset per font +end; + { TPdfArray } @@ -4206,7 +4282,7 @@ function _PdfDateToDateTime(const AText: TPdfDate): TDateTime; var Y,M,D, H,MI,SS: cardinal; begin if Length(AText)<16 then - EConvertError.CreateRes(@SDateEncodeError); + raise EConvertError.CreateRes(@SDateEncodeError); Y := ord(AText[3])*1000+ord(AText[4])*100+ord(AText[5])*10+ord(AText[6]) -(48+480+4800+48000); M := ord(AText[7])*10+ord(AText[8])-(48+480); @@ -4218,7 +4294,7 @@ begin if (H<24) and (MI<60) and (SS<60) then // inlined EncodeTime() result := result + (H * (MinsPerHour * SecsPerMin * MSecsPerSec) + MI * (SecsPerMin * MSecsPerSec) + SS * MSecsPerSec) / MSecsPerDay else - EConvertError.CreateRes(@SDateEncodeError); + raise EConvertError.CreateRes(@SDateEncodeError); end; function _HasMultiByteString(Value: PAnsiChar): boolean; @@ -4876,8 +4952,8 @@ begin aChanged := fAddGlyphFont=fNone; Glyph := TTF.fUsedWide[TTF.FindOrAddUsedWideChar(Char)].Glyph; with Canvas.fDoc do - if fPDFA1 and (Glyph=0) and (fFontFallBackIndex<0) then - raise Exception.Create('PDFA/1 expects font fallback to be enabled, '+ + if (fPDFA <> pdfaNone) and (Glyph=0) and (fFontFallBackIndex<0) then + raise Exception.Create('PDFA expects font fallback to be enabled, '+ 'and the required font is not available on this system') else if (Glyph=0) and fUseFontFallBack and (fFontFallBackIndex>=0) then begin if fAddGlyphFont=fMain then @@ -5073,7 +5149,7 @@ constructor TPdfWrite.Create(Destination: TPdfDocument; DestStream: TStream); begin fDoc := Destination; fDestStream := DestStream; - fDestStreamPosition := fDestStream.Seek(0,soFromCurrent); + fDestStreamPosition := fDestStream.Seek(0,soCurrent); fCodePage := fDoc.CodePage; B := @Tmp; Bend := B+high(Tmp); @@ -5103,7 +5179,7 @@ begin Save; result := ''; SetLength(result,fDestStreamPosition); - fDestStream.Seek(0,soFromBeginning); + fDestStream.Seek(0,soBeginning); fDestStream.Read(pointer(result)^,fDestStreamPosition); end; end; @@ -5341,11 +5417,18 @@ begin end; constructor TPdfDocument.Create(AUseOutlines: Boolean; ACodePage: integer; - APDFA1: boolean{$ifdef USE_PDFSECURITY}; AEncryption: TPdfEncryption{$endif}); + {$ifdef USE_PDFALEVEL]}APDFA: TPdfALevel{$else}APDFA1: boolean{$endif} + {$ifdef USE_PDFSECURITY}; AEncryption: TPdfEncryption{$endif}); var LFont: TLogFontW; // TLogFontW to add to FTrueTypeFonts array as UTF-8 i: integer; begin - fPDFA1 := APDFA1; + {$ifdef USE_PDFALEVEL]} + fPDFA:=APDFA; + {$else} + if APDFA1 then + fPDFA := pdfa1B else + fPDFA := pdfaNone; + {$endif USE_PDFALEVEL} {$ifdef USE_PDFSECURITY} fEncryption := AEncryption; {$endif USE_PDFSECURITY} @@ -5671,18 +5754,18 @@ begin if fEncryption<>nil then NeedFileID := true; {$endif USE_PDFSECURITY} - if PDFA1 then begin + if PDFA<>pdfaNone then begin if fFileFormatnil then - raise EPdfInvalidOperation.Create('PDF/A-1 not allowed when encryption is enabled'); + raise EPdfInvalidOperation.Create('PDF/A not allowed when encryption is enabled'); {$endif USE_PDFSECURITY} fUseFontFallBack := true; FOutputIntents := TPdfArray.Create(FXref); Dico := TPdfDictionary.Create(FXRef); Dico.AddItem('Type','OutputIntent'); - Dico.AddItem('S','GTS_PDFA1'); + Dico.AddItem('S','GTS_PDFA1'); // there is no definition GTS_PDFA2 or GTS_PDFA3 Dico.AddItemText('OutputConditionIdentifier','sRGB'); Dico.AddItemText('RegistryName','http://www.color.org'); RGB := TPdfStream.Create(self); @@ -5764,6 +5847,15 @@ begin FCanvas.SetPage(result); end; +function TPdfDocument.AddTrueTypeFont(const TTFName: RawUtf8): boolean; +begin + result := GetTrueTypeFontIndex(TTFName) < 0; + if not result then + exit; + AddRawUTF8(FTrueTypeFonts,TTFName); + QuickSortRawUTF8(FTrueTypeFonts,length(FTrueTypeFonts),nil,@StrIComp); +end; + procedure TPdfDocument.FreeDoc; var i: integer; begin @@ -5815,6 +5907,8 @@ procedure TPdfDocument.SaveToStreamDirectBegin(AStream: TStream; const PDFHEADER: array[TPdfFileFormat] of PDFString = ( '%PDF-1.3'#10, '%PDF-1.4'#10'%'#228#229#230#240#10, '%PDF-1.5'#10'%'#241#242#243#244#10, '%PDF-1.6'#10'%'#245#246#247#248#10); +const PDFAPART: array[TPdfALevel] of PDFString = ('', '1', '1', '2', '2', '3', '3'); +const PDFACONFORMANCE: array[TPdfALevel] of PDFString = ('', 'A', 'B', 'A', 'B', 'A', 'B'); begin if fSaveToStreamWriter<>nil then raise EPdfInvalidOperation.Create('SaveToStreamDirectBegin called twice'); @@ -5824,7 +5918,7 @@ begin FInfo.ModDate := ForceModDate; FRoot.SaveOpenAction; // some PDF/A-1 requirements - if PDFA1 then begin + if PDFA<>pdfaNone then begin FMetaData.Writer.Add(RawByteString( ''+ ''+ @@ -5846,7 +5940,7 @@ begin '').Add(StringToUTF8(Info.Keywords)).Add(''+ ''+PDF_PRODUCER+''+ ''+ - '1A'+ + ''+PDFAPART[PDFA]+''+PDFACONFORMANCE[PDFA]+''+ ''); end; // write beginning of the content @@ -6053,11 +6147,13 @@ const TTFCFP_MS_PLATFORMID = 3; TTFCFP_SYMBOL_CHAR_SET = 0; TTFCFP_UNICODE_CHAR_SET = 1; + TTFCFP_DONT_CARE = 65535; TTFCFP_FLAGS_SUBSET = 1; TTFMFP_SUBSET = 0; TTFCFP_FLAGS_TTC = 4; - TTCF_TABLE = $66637474; + HEAD_TABLE = $64616568; // 'head' + TTCF_TABLE = $66637474; // 'ttcf' type /// a TTF name record used for the 'name' Format 4 table @@ -6322,12 +6418,26 @@ begin NewDoc; end; -procedure TPdfDocument.SetPDFA1(const Value: boolean); +procedure TPdfDocument.SetPDFA(const Value: TPdfALevel); begin - fPDFA1 := Value; + fPDFA := Value; NewDoc; end; +{$ifndef USE_PDFALEVEL} +function TPdfDocument.GetPDFA1: boolean; +begin + result := (fPDFA = pdfa1B); +end; + +procedure TPdfDocument.SetPDFA1(const Value: boolean); +begin + if Value then + SetPDFA(pdfa1B) else + SetPDFA(pdfaNone); +end; +{$endif USE_PDFALEVEL} + procedure TPdfDocument.SetFontFallBackName(const Value: string); begin fFontFallBackIndex := GetTrueTypeFontIndex(StringToUTF8(Value)); @@ -6664,7 +6774,7 @@ begin if ALogFont.lfWeight>=FW_SEMIBOLD then include(AStyle,pfsBold); result := SetFont(AName,ASize,AStyle,ALogFont.lfCharSet,-1, - (ALogFont.lfPitchAndFamily and 3) = FIXED_PITCH); + ALogFont.lfPitchAndFamily and TMPF_FIXED_PITCH=0); end; procedure TPdfCanvas.TextOut(X, Y: Single; const Text: PDFString); @@ -7199,7 +7309,7 @@ begin if FPage.FFont.FTrueTypeFontsIndex=0 then begin Ansi := CurrentAnsiConvert.UnicodeBufferToAnsi(PW,StrLenW(PW)); i := 1; - while i self then // WinAnsiFont.fUsedWide[] = glyphs for ShowText + begin + result := WinAnsiFont.FindOrAddUsedWideChar(aWideChar); + exit; + end; result := fUsedWideChar.Add(ord(aWideChar)); if result<0 then begin result := -(result+1); // this WideChar was already existing -> return index @@ -8004,14 +8118,15 @@ begin end; function TPdfFontTrueType.GetWideCharWidth(aWideChar: WideChar): Integer; +var ref: TPdfFontTrueType; begin - self := self.WinAnsiFont; // we need fUsedWide[] to be used glyphs + ref := WinAnsiFont; // WinAnsiFont.fUsedWide[] = glyphs used by ShowText result := WideCharToWinAnsi(ord(aWideChar)); if result>=0 then - if (fWinAnsiWidth<>nil) and (result>=32) then - result := fWinAnsiWidth[AnsiChar(result)] else - result := fDefaultWidth else - result := fUsedWide[FindOrAddUsedWideChar(aWideChar)].Width; + if (ref.fWinAnsiWidth<>nil) and (result>=32) then + result := ref.fWinAnsiWidth[AnsiChar(result)] else + result := ref.fDefaultWidth else + result := ref.fUsedWide[ref.FindOrAddUsedWideChar(aWideChar)].Width; end; { font subset embedding using Windows XP CreateFontPackage() FontSub.dll @@ -8033,6 +8148,95 @@ begin FreeMem(Buffer); end; +type + TTtfTableDirectory = packed record + sfntVersion: cardinal; // 0x00010000 for version 1.0 + numTables: word; // number of tables + searchRange: word; // HighBit(NumTables) x 16 + entrySelector: word; // Log2(HighBit(NumTables)) + rangeShift: word; // NumTables x 16 - SearchRange + end; + PTtfTableDirectory = ^TTtfTableDirectory; + + TTtfTableEntry = packed record + tag: cardinal; // table identifier + checksum: cardinal; // checksum for this table + offset: cardinal; // offset from start of font file + length: cardinal; // length of this table + end; + PTtfTableEntry = ^TTtfTableEntry; + +const + // see http://www.4real.gr/technical-documents-ttf-subset.html + // https://developer.apple.com/fonts/TrueType-Reference-Manual/RM06/Chap6.html + TTF_SUBSET: array[0..9] of array[0..3] of AnsiChar = ( + 'head', 'cvt ', 'fpgm', 'prep', 'hhea', 'maxp', 'hmtx', 'cmap', 'loca', 'glyf'); + +procedure ReduceTTF(out ttf: PDFString; SubSetData: pointer; SubSetSize: integer); +var dir: PTtfTableDirectory; + d, e: PTtfTableEntry; + head: PCmapHEAD; + n, i, len: PtrInt; + checksum: cardinal; +begin + SetLength(ttf, SubSetSize); // maximum size + d := pointer(ttf); + inc(PTtfTableDirectory(d)); + // identify the tables to be included + e := SubSetData; + inc(PTtfTableDirectory(e)); + n := 0; + if SubSetSize > SizeOf(dir^) then + for i := 1 to swap(PTtfTableDirectory(SubSetData)^.numTables) do begin + if IntegerScan(@TTF_SUBSET, length(TTF_SUBSET), e^.tag) <> nil then begin + d^ := e^; + inc(d); + inc(n); + end; + inc(e); + end; + // update the main directory + if n < 8 then begin // pdf expects 10, and our fixed dir^ below in 8..15 + MoveFast(SubSetData^, pointer(ttf)^, SubSetSize); // paranoid + exit; + end; + dir := pointer(ttf); + dir^.sfntVersion := PTtfTableDirectory(SubSetData)^.sfntVersion; + dir^.numTables := swap(word(n)); + //len := HighBit(n); // always 8 when n in 8..15 + //dir^.searchRange := swap(len * 16); + //dir^.entrySelector := swap(Floor(log2(len))); // requires the Math unit + //dir^.rangeShift := swap((integer(n) - len) * 16); + dir^.searchRange := 32768; // pre-computed values for n in 8..15 + dir^.entrySelector := 768; + dir^.rangeShift := 8192; + // include the associated data + checksum := 0; + head := nil; + e := pointer(ttf); + inc(PTtfTableDirectory(e)); + for i := 1 to n do begin + len := bswap32(e^.length); + MoveFast(PByteArray(SubSetData)[bswap32(e^.offset)], d^, len); + e^.offset := bswap32(PtrUInt(d) - PtrUInt(ttf)); + if e^.tag = HEAD_TABLE then // 'head' table + head := pointer(d); + while len and 3 <> 0 do begin // 32-bit padding + PByteArray(d)[len] := 0; + inc(len); + end; + inc(checksum, bswap32(e^.checksum)); // we didn't change the table itself + inc(PByte(d), len); + inc(e); + end; + // finalize the generated content + for i := 0 to ((SizeOf(dir^) + (integer(n) * SizeOf(e^))) shr 2) - 1 do + inc(checksum, PCardinalArray(ttf)[i]); + if head <> nil then + head^.checkSumAdjustment := bswap32($B1B0AFBA - checksum); + PStrLen(PtrUInt(ttf) - _STRLEN)^ := PtrUInt(d) - PtrUInt(ttf); // no realloc +end; + var FontSub: THandle = INVALID_HANDLE_VALUE; CreateFontPackage: function(puchSrcBuffer: pointer; ulSrcBufferSize: cardinal; @@ -8061,7 +8265,6 @@ var c: AnsiChar; tableTag: Longword; {$ifndef DELPHI5OROLDER} ttcNumFonts: Longword; - ttcBytes: array of byte; {$endif} begin DS := THeapMemoryStream.Create; @@ -8074,7 +8277,7 @@ begin Descendant.AddItem('Type','Font'); Descendant.AddItem('Subtype','CIDFontType2'); Descendant.AddItem('BaseFont',FName); - if fDoc.PDFA1 then + if fDoc.PDFA<>pdfaNone then Descendant.AddItem('CIDToGIDMap','Identity'); CIDSystemInfo := TPdfDictionary.Create(FDoc.FXref); CIDSystemInfo.AddItem('Supplement',0); @@ -8087,7 +8290,7 @@ begin fLastChar := WinAnsiFont.fUsedWide[n-1].Glyph; end; Descendant.AddItem('DW',WinAnsiFont.fDefaultWidth); - if fDoc.PDFA1 or not WinAnsiFont.fFixedWidth then begin + if (fDoc.PDFA<>pdfaNone) or not WinAnsiFont.fFixedWidth then begin WR.Add('['); // fixed width will use /DW value // WinAnsiFont.fUsedWide[] contains glyphs used by ShowText for i := 0 to n-1 do @@ -8155,7 +8358,7 @@ begin FData.AddItem('Widths',TPdfRawText.Create(WR.Add(']').ToPDFString)); end; // embedd True Type font into the PDF file (allow subset of used glyph) - if fDoc.PDFA1 or (fDoc.EmbeddedTTF and + if (fDoc.PDFA<>pdfaNone) or (fDoc.EmbeddedTTF and ((fDoc.fEmbeddedTTFIgnore=nil) or (fDoc.fEmbeddedTTFIgnore. IndexOf(fDoc.FTrueTypeFonts[fTrueTypeFontsIndex-1])<0))) then begin fDoc.GetDCWithFont(self); @@ -8165,9 +8368,8 @@ begin if ttfSize<>GDI_ERROR then begin // Yes, the font is in a .ttc collection // find out how many fonts are included in the collection - SetLength(ttcBytes,4); - if GetFontData(fDoc.FDC,TTCF_TABLE,8,pointer(ttcBytes),4) <> GDI_ERROR then - ttcNumFonts := ttcBytes[3] else // Higher bytes will be zero + if GetFontData(fDoc.FDC,TTCF_TABLE,8,@ttcNumFonts,4) <> GDI_ERROR then + ttcNumFonts := bswap32(ttcNumFonts) else ttcNumFonts := 1; // we need to find out the index of the font within the ttc collection // (this is not easy, so GetTTCIndex uses lookup on known ttc fonts) @@ -8206,12 +8408,15 @@ begin if CreateFontPackage(pointer(ttf),ttfSize, SubSetData,SubSetMem,SubSetSize, usFlags,ttcIndex,TTFMFP_SUBSET,0, - TTFCFP_MS_PLATFORMID,TTFCFP_UNICODE_CHAR_SET, + TTFCFP_MS_PLATFORMID,TTFCFP_DONT_CARE, pointer(Used.Values),Used.Count, @lpfnAllocate,@lpfnReAllocate,@lpfnFree,nil)=0 then begin // subset was created successfully -> save to PDF file - SetString(ttf,SubSetData,SubSetSize); + ReduceTTF(ttf,SubSetData,SubSetSize); FreeMem(SubSetData); + // see 5.5.3 Font Subsets: begins with a tag followed by a + + TPdfName(fFontDescriptor.ValueByName('FontName')).AppendPrefix; + TPdfName(fFontDescriptor.ValueByName('BaseFont')).AppendPrefix; end; end; end; @@ -8724,7 +8929,8 @@ begin end; constructor TPdfDocumentGDI.Create(AUseOutlines: Boolean; ACodePage: integer; - APDFA1: boolean{$ifdef USE_PDFSECURITY}; AEncryption: TPdfEncryption{$endif}); + {$ifdef USE_PDFALEVEL]}APDFA: TPdfALevel{$else}APDFA1: boolean{$endif} + {$ifdef USE_PDFSECURITY}; AEncryption: TPdfEncryption{$endif}); begin inherited; fTPdfPageClass := TPdfPageGdi; @@ -9924,6 +10130,9 @@ end; procedure TPdfEnum.HandleComment(Kind: TPdfGDIComment; P: PAnsiChar; Len: integer); var Text: RawUTF8; W: integer; + Img: TPdfImage; + ImgName: PDFString; + ImgRect: TPdfRect; begin try case Kind of @@ -9945,6 +10154,18 @@ begin W := 0; Canvas.Doc.CreateLink(Canvas.RectI(PRect(P)^,true),Text,abSolid,W); end; + pgcJpegDirect: + if Len>Sizeof(TRect) then begin + SetString(Text,P+SizeOf(TRect),Len-SizeOf(TRect)); + ImgName := 'SynImgJpg'+PDFString(crc32cUTF8ToHex(Text)); + if Canvas.Doc.GetXObject(ImgName) = nil then + begin + Img := TPdfImage.CreateJpegDirect(Canvas.Doc,UTF8ToString(Text)); + Canvas.Doc.RegisterXObject(Img,ImgName); + end; + ImgRect := Canvas.RectI(PRect(P)^,true); + Canvas.DrawXObject(ImgRect.Left,ImgRect.Top,ImgRect.Right-ImgRect.Left,ImgRect.Bottom-ImgRect.Top,ImgName); + end; end; except on E: Exception do ; // ignore any error (continue EMF enumeration) @@ -10109,10 +10330,40 @@ begin end; // use transformation ScaleXForm := WorldTransform; - FWorldFactorX := WorldTransform.eM11; - FWorldFactorY := WorldTransform.eM22; - FWorldOffsetX := WorldTransform.eDx; - FWorldOffsetY := WorldTransform.eDy; + if (ScaleXForm.eM11 > 0) and + (ScaleXForm.eM22 > 0) and + (ScaleXForm.eM12 = 0) and + (ScaleXForm.eM21 = 0) then + begin // Scale + FWorldFactorX := ScaleXForm.eM11; + FWorldFactorY := ScaleXForm.eM22; + FWorldOffsetX := WorldTransform.eDx; + FWorldOffsetY := WorldTransform.eDy; + end + else + if (ScaleXForm.eM22 = ScaleXForm.eM11) and + (ScaleXForm.eM21 = -ScaleXForm.eM12) then + begin // Rotate + FAngle := ArcSin(ScaleXForm.eM12) * c180divPI; + FWorldCos := ScaleXForm.eM11; + FWorldSin := ScaleXForm.eM12; + end + else + if (ScaleXForm.eM11 = 0) and + (ScaleXForm.eM22 = 0) and + ((ScaleXForm.eM12 <> 0) or + (ScaleXForm.eM21 <> 0)) then + begin //Shear + + end + else + if ((ScaleXForm.eM11 < 0) or + (ScaleXForm.eM22 < 0)) and + (ScaleXForm.eM12 = 0) and + (ScaleXForm.eM21 = 0) then + begin //Reflection + + end; end; end; @@ -10425,7 +10676,7 @@ begin {$endif} Canvas.BeginText; if font.spec.angle<>0 then begin - a := font.spec.angle*(PI/180); + a := font.spec.angle*cPIdiv180; acos := cos(a); asin := sin(a); PosX := 0; @@ -10433,7 +10684,25 @@ begin Canvas.SetTextMatrix(acos, asin, -asin, acos, Canvas.I2X(Posi.X-Round(W*acos+H*asin)), Canvas.I2Y(Posi.Y-Round(H*acos-W*asin))); - end else begin + end else + if (WorldTransform.eM11 = WorldTransform.eM22) and + (WorldTransform.eM12 = -WorldTransform.eM21) and + not SameValue(ArcCos(WorldTransform.eM11), 0, 0.0001) then + begin + PosX := 0; + PosY := 0; + if SameValue(ArcCos(WorldTransform.eM11), 0, 0.0001) or //0deg + SameValue(ArcCos(WorldTransform.eM11), cPI, 0.0001) then //180deg + Canvas.SetTextMatrix(WorldTransform.eM11, WorldTransform.eM12, WorldTransform.eM21, WorldTransform.eM22, + Canvas.I2X(posi.X * WorldTransform.eM11 + posi.Y * WorldTransform.eM21 + WorldTransform.eDx), + Canvas.I2y(posi.X * WorldTransform.eM12 + posi.Y * WorldTransform.eM22 + WorldTransform.eDy)) + else + Canvas.SetTextMatrix(-WorldTransform.eM11, -WorldTransform.eM12, -WorldTransform.eM21, -WorldTransform.eM22, + Canvas.I2X(posi.X * WorldTransform.eM11 + posi.Y * WorldTransform.eM21 + WorldTransform.eDx), + Canvas.I2y(posi.X * WorldTransform.eM12 + posi.Y * WorldTransform.eM22 + WorldTransform.eDy)); + end + else + begin acos := 0; asin := 0; if Canvas.fViewSize.cx>0 then @@ -10600,7 +10869,7 @@ begin {$endif} SaveToStream(FWriter.fDestStream); // with CompressionQuality recompress end; - FWriter.fDestStreamPosition := FWriter.fDestStream.Seek(0,soFromCurrent); + FWriter.fDestStreamPosition := FWriter.fDestStream.Seek(0,soCurrent); end else begin if aImage.InheritsFrom(TBitmap) then B := TBitmap(aImage) else @@ -10684,7 +10953,7 @@ begin jpeg.Read(b,1); case b of $C0..$C3: begin - jpeg.Seek(3,soFromCurrent); + jpeg.Seek(3,soCurrent); jpeg.Read(w,2); height := swap(w); jpeg.Read(w,2); @@ -10697,12 +10966,12 @@ begin $FF: jpeg.Read(b,1); $D0..$D9, $01: begin - jpeg.Seek(1,soFromCurrent); + jpeg.Seek(1,soCurrent); jpeg.Read(b,1); end; else begin jpeg.Read(w,2); - jpeg.Seek(swap(w)-2, soFromCurrent); + jpeg.Seek(swap(w)-2, soCurrent); jpeg.Read(b,1); end; end; @@ -10722,7 +10991,7 @@ begin FFilter := 'DCTDecode'; FWriter.Save; // flush to allow direct access to fDestStream FWriter.Add(aJpegFile.Memory,Len); - FWriter.fDestStreamPosition := FWriter.fDestStream.Seek(0,soFromCurrent); + FWriter.fDestStreamPosition := FWriter.fDestStream.Seek(0,soCurrent); FAttributes.AddItem('Width',fPixelWidth); FAttributes.AddItem('Height',fPixelHeight); case BitDepth of @@ -10747,7 +11016,7 @@ begin FResources.AddItem('ProcSet',TPdfArray.CreateNames(nil,['PDF','Text','ImageC'])); FPage := TPdfPage.Create(nil); FCanvas := TPdfCanvas.Create(aDoc); - FCanvas.FPage:=FPage; + FCanvas.FPage := FPage; FCanvas.FPageFontList := FFontList; FCanvas.FContents := self; FCanvas.FFactor := 1; @@ -10927,12 +11196,14 @@ procedure TPdfEncryptionRC4MD5.EncodeBuffer(const BufIn; var BufOut; Count: card fLastObjectNumber := fDoc.fCurrentObjectNumber; fLastGenerationNumber := fDoc.fCurrentGenerationNumber; end; +var work: TRC4; // Encrypt() changes the RC4 state -> local copy for reuse begin if (fDoc.fCurrentObjectNumber<>fLastObjectNumber) or (fDoc.fCurrentGenerationNumber<>fLastGenerationNumber) then // a lot of string encodings have the same context ComputeNewRC4Key; - fLastRC4Key.Encrypt(BufIn,BufOut,Count); // RC4 allows in-place encryption :) + work := fLastRC4Key; + work.Encrypt(BufIn,BufOut,Count); // RC4 allows in-place encryption :) end; {$endif USE_PDFSECURITY} diff --git a/contrib/mORMot/SynProtoRTSPHTTP.pas b/contrib/mORMot/SynProtoRTSPHTTP.pas index d5513fe..42b40c6 100644 --- a/contrib/mORMot/SynProtoRTSPHTTP.pas +++ b/contrib/mORMot/SynProtoRTSPHTTP.pas @@ -6,7 +6,7 @@ unit SynProtoRTSPHTTP; { This file is part of Synopse mORMot framework. - Synopse mORMot framework. Copyright (C) 2020 Arnaud Bouchez + Synopse mORMot framework. Copyright (C) 2022 Arnaud Bouchez Synopse Informatique - https://synopse.info *** BEGIN LICENSE BLOCK ***** @@ -25,7 +25,7 @@ unit SynProtoRTSPHTTP; The Initial Developer of the Original Code is Arnaud Bouchez. - Portions created by the Initial Developer are Copyright (C) 2020 + Portions created by the Initial Developer are Copyright (C) 2022 the Initial Developer. All Rights Reserved. Contributor(s): diff --git a/contrib/mORMot/SynProtoRelay.pas b/contrib/mORMot/SynProtoRelay.pas index fce9f6b..db25af6 100644 --- a/contrib/mORMot/SynProtoRelay.pas +++ b/contrib/mORMot/SynProtoRelay.pas @@ -6,7 +6,7 @@ unit SynProtoRelay; { This file is part of Synopse mORMot framework. - Synopse mORMot framework. Copyright (C) 2020 Arnaud Bouchez + Synopse mORMot framework. Copyright (C) 2022 Arnaud Bouchez Synopse Informatique - https://synopse.info *** BEGIN LICENSE BLOCK ***** @@ -25,7 +25,7 @@ unit SynProtoRelay; The Initial Developer of the Original Code is Arnaud Bouchez. - Portions created by the Initial Developer are Copyright (C) 2020 + Portions created by the Initial Developer are Copyright (C) 2022 the Initial Developer. All Rights Reserved. Contributor(s): diff --git a/contrib/mORMot/SynSM.inc b/contrib/mORMot/SynSM.inc index ce1fd4e..1d01f28 100644 --- a/contrib/mORMot/SynSM.inc +++ b/contrib/mORMot/SynSM.inc @@ -1,10 +1,10 @@ { This file is part of Synopse framework. - Synopse framework. Copyright (C) 2020 Arnaud Bouchez + Synopse framework. Copyright (C) 2022 Arnaud Bouchez Synopse Informatique - https://synopse.info - Scripting support for mORMot Copyright (C) 2020 Pavel Mashlyakovsky + Scripting support for mORMot Copyright (C) 2022 Pavel Mashlyakovsky pavel.mash at gmail.com *** BEGIN LICENSE BLOCK ***** diff --git a/contrib/mORMot/SynSM.pas b/contrib/mORMot/SynSM.pas index b8743a1..1b973e4 100644 --- a/contrib/mORMot/SynSM.pas +++ b/contrib/mORMot/SynSM.pas @@ -5,10 +5,10 @@ unit SynSM; { This file is part of Synopse framework. - Synopse framework. Copyright (C) 2020 Arnaud Bouchez + Synopse framework. Copyright (C) 2022 Arnaud Bouchez Synopse Informatique - https://synopse.info - Scripting support for mORMot Copyright (C) 2020 Pavel Mashlyakovsky + Scripting support for mORMot Copyright (C) 2022 Pavel Mashlyakovsky pavel.mash at gmail.com Some ideas taken from @@ -29,7 +29,7 @@ unit SynSM; The Initial Developer of the Original Code is Pavel Mashlyakovsky. - Portions created by the Initial Developer are Copyright (C) 2020 + Portions created by the Initial Developer are Copyright (C) 2022 the Initial Developer. All Rights Reserved. Contributor(s): diff --git a/contrib/mORMot/SynSMAPI.pas b/contrib/mORMot/SynSMAPI.pas index a215a2b..0a6d83b 100644 --- a/contrib/mORMot/SynSMAPI.pas +++ b/contrib/mORMot/SynSMAPI.pas @@ -5,10 +5,10 @@ unit SynSMAPI; { This file is part of Synopse framework. - Synopse framework. Copyright (C) 2020 Arnaud Bouchez + Synopse framework. Copyright (C) 2022 Arnaud Bouchez Synopse Informatique - https://synopse.info - Scripting support for mORMot Copyright (C) 2020 Pavel Mashlyakovsky + Scripting support for mORMot Copyright (C) 2022 Pavel Mashlyakovsky pavel.mash at gmail.com Some ideas taken from @@ -29,7 +29,7 @@ unit SynSMAPI; The Initial Developer of the Original Code is Pavel Mashlyakovsky. - Portions created by the Initial Developer are Copyright (C) 2020 + Portions created by the Initial Developer are Copyright (C) 2022 the Initial Developer. All Rights Reserved. Contributor(s): diff --git a/contrib/mORMot/SynSQLite3.pas b/contrib/mORMot/SynSQLite3.pas index 5dff52e..57b7c39 100644 --- a/contrib/mORMot/SynSQLite3.pas +++ b/contrib/mORMot/SynSQLite3.pas @@ -6,7 +6,7 @@ unit SynSQLite3; { This file is part of Synopse mORMot framework. - Synopse mORMot framework. Copyright (C) 2020 Arnaud Bouchez + Synopse mORMot framework. Copyright (C) 2022 Arnaud Bouchez Synopse Informatique - https://synopse.info *** BEGIN LICENSE BLOCK ***** @@ -25,7 +25,7 @@ unit SynSQLite3; The Initial Developer of the Original Code is Arnaud Bouchez. - Portions created by the Initial Developer are Copyright (C) 2020 + Portions created by the Initial Developer are Copyright (C) 2022 the Initial Developer. All Rights Reserved. Contributor(s): @@ -48,7 +48,7 @@ unit SynSQLite3; ***** END LICENSE BLOCK ***** - SQLite3 3.32.3 database engine + SQLite3 3.38.2 database engine ******************************** Brand new SQLite3 library to be used with Delphi/FPC @@ -1187,7 +1187,7 @@ type close: function(DB: TSQLite3DB): integer; cdecl; /// Return the version of the SQLite database engine, in ascii format - // - currently returns '3.32.3', when used with our SynSQLite3Static unit + // - currently returns '3.38.2', when used with our SynSQLite3Static unit // - if an external SQLite3 library is used, version may vary // - you may use the VersionText property (or Version for full details) instead libversion: function: PUTF8Char; cdecl; @@ -2682,6 +2682,7 @@ type fBackupBackgroundInProcess: TSQLDatabaseBackupThread; fBackupBackgroundLastTime: RawUTF8; fBackupBackgroundLastFileName: TFileName; + fUseCacheSize: integer; {$ifdef WITHLOG} fLogResultMaximumSize: integer; fLog: TSynLogClass; @@ -2786,7 +2787,8 @@ type // - initialize a internal mutex to ensure that all access to the database is atomic // - raise an ESQLite3Exception on any error constructor Create(const aFileName: TFileName; const aPassword: RawUTF8=''; - aOpenV2Flags: integer=0; aDefaultCacheSize: integer=10000; aDefaultPageSize: integer=4096); reintroduce; + aOpenV2Flags: integer=0; aDefaultCacheSize: integer=10000; + aDefaultPageSize: integer=4096); reintroduce; /// close a database and free its memory and context //- if TransactionBegin was called but not commited, a RollBack is performed destructor Destroy; override; @@ -3035,6 +3037,9 @@ type // - cache is consistent only if ExecuteJSON() Expand parameter is constant // - cache is used by TSQLDataBase.ExecuteJSON() and TSQLTableDB.Create() property UseCache: boolean read GetUseCache write SetUseCache; + /// cache size in JSON bytes, to be set before UseCache is set to true + // - default is 16MB + property UseCacheSize: integer read fUseCacheSize write fUseCacheSize; /// return TRUE if a Transaction begun property TransactionActive: boolean read fTransactionActive; /// sets a busy handler that sleeps for a specified amount of time @@ -3905,6 +3910,7 @@ begin fIsMemory := true else fFileNameWithoutPath := ExtractFileName(fFileName); fPassword := aPassword; + fUseCacheSize := 16384*1024; fSQLFunctions := TSynObjectList.Create; result := DBOpen; if result<>SQLITE_OK then @@ -4228,7 +4234,7 @@ begin if self<>nil then if Value<>UseCache then if Value then - fCache := TSynCache.Create(16384*1024,true) else + fCache := TSynCache.Create(fUseCacheSize,true) else FreeAndNil(fCache); end; @@ -5258,8 +5264,11 @@ begin begin result := sqlite3.prepare_v2(RequestDB, pointer(SQL), length(SQL)+1, fRequest, fNextSQL); - while (result=SQLITE_OK) and (Request=0) do // comment or white-space + while (result=SQLITE_OK) and (Request=0) do begin // comment or white-space + if fNextSQL^ = #0 then // statement contains only comment + raise ESQLite3Exception.Create(DB,SQLITE_EMPTY,SQL); result := sqlite3.prepare_v2(RequestDB, fNextSQL, -1, fRequest, fNextSQL); + end; fFieldCount := sqlite3.column_count(fRequest); if not NoExcept then sqlite3_check(RequestDB,result,SQL); diff --git a/contrib/mORMot/SynSQLite3RegEx.pas b/contrib/mORMot/SynSQLite3RegEx.pas index ca1e86a..c4e832c 100644 --- a/contrib/mORMot/SynSQLite3RegEx.pas +++ b/contrib/mORMot/SynSQLite3RegEx.pas @@ -6,7 +6,7 @@ unit SynSQLite3RegEx; { This file is part of Synopse mORMot framework. - Synopse mORMot framework. Copyright (C) 2020 Arnaud Bouchez + Synopse mORMot framework. Copyright (C) 2022 Arnaud Bouchez Synopse Informatique - https://synopse.info *** BEGIN LICENSE BLOCK ***** @@ -25,7 +25,7 @@ unit SynSQLite3RegEx; The Initial Developer of the Original Code is Arnaud Bouchez. - Portions created by the Initial Developer are Copyright (C) 2020 + Portions created by the Initial Developer are Copyright (C) 2022 the Initial Developer. All Rights Reserved. Contributor(s): diff --git a/contrib/mORMot/SynSQLite3Static.pas b/contrib/mORMot/SynSQLite3Static.pas index 776d10e..0d86c8a 100644 --- a/contrib/mORMot/SynSQLite3Static.pas +++ b/contrib/mORMot/SynSQLite3Static.pas @@ -1,4 +1,4 @@ -/// SQLite3 3.32.3 Database engine - statically linked for Windows/Linux +/// SQLite3 3.38.2 Database engine - statically linked for Windows/Linux // - this unit is a part of the freeware Synopse mORMot framework, // licensed under a MPL/GPL/LGPL tri-license; version 1.18 unit SynSQLite3Static; @@ -6,7 +6,7 @@ unit SynSQLite3Static; { This file is part of Synopse mORMot framework. - Synopse mORMot framework. Copyright (C) 2020 Arnaud Bouchez + Synopse mORMot framework. Copyright (C) 2022 Arnaud Bouchez Synopse Informatique - https://synopse.info *** BEGIN LICENSE BLOCK ***** @@ -25,7 +25,7 @@ unit SynSQLite3Static; The Initial Developer of the Original Code is Arnaud Bouchez. - Portions created by the Initial Developer are Copyright (C) 2020 + Portions created by the Initial Developer are Copyright (C) 2022 the Initial Developer. All Rights Reserved. Contributor(s): @@ -47,7 +47,7 @@ unit SynSQLite3Static; ***** END LICENSE BLOCK ***** - Statically linked SQLite3 3.32.3 engine with optional AES encryption + Statically linked SQLite3 3.38.2 engine with optional AES encryption ********************************************************************** To be declared in your project uses clause: will fill SynSQlite3.sqlite3 @@ -210,13 +210,21 @@ implementation {$ifdef ANDROID} const _PREFIX = ''; {$ifdef CPUAARCH64} - {$L .\static\aarch64-android\libsqlite3.a} - {$L .\static\aarch64-android\libgcc.a} + {$L .\static\aarch64-android\sqlite3.o} + {$linklib .\static\aarch64-android\libgcc.a} {$endif CPUAARCH64} {$ifdef CPUARM} - {$L .\static\arm-android\libsqlite3.a} - {$L .\static\arm-android\libgcc.a} + {$L .\static\arm-android\sqlite3.o} + {$linklib .\static\arm-android\libgcc.a} {$endif CPUARM} + {$ifdef CPUX86} + {$L .\static\i386-android\sqlite3.o} + {$endif CPUX86} + {$ifdef CPUX64} + {$L .\static\x86_64-android\sqlite3.o} + // x86_64-linux-android-ld.bfd: final link failed + // (Nonrepresentable section on output) + {$endif CPUX64} {$endif ANDROID} {$ifdef FREEBSD} @@ -1142,7 +1150,8 @@ function sqlite3_trace_v2(DB: TSQLite3DB; Mask: integer; Callback: TSQLTraceCall const // error message if statically linked sqlite3.o(bj) does not match this - EXPECTED_SQLITE3_VERSION = {$ifdef ANDROID}''{$else}'3.32.3'{$endif}; + // - Android may be a little behind, so we don't check exact version + EXPECTED_SQLITE3_VERSION = {$ifdef ANDROID}''{$else}'3.38.2'{$endif}; constructor TSQLite3LibraryStatic.Create; var error: RawUTF8; @@ -1256,16 +1265,20 @@ begin if (EXPECTED_SQLITE3_VERSION='') or (fVersionText=EXPECTED_SQLITE3_VERSION) then exit; // you should never see it if you cloned https://github.com/synopse/mORMot - FormatUTF8('Static SQLite3 library as included within % is outdated!'#13+ - 'Linked version is % whereas the current/expected is '+EXPECTED_SQLITE3_VERSION+'.'#13#13+ - 'Please download supported latest SQLite3 '+EXPECTED_SQLITE3_VERSION+' revision'#13+ + FormatUTF8('Static SQLite3 library as included within % is outdated!'#13#10+ + 'Linked version is % whereas the current/expected is '+EXPECTED_SQLITE3_VERSION+'.'#13#10#13#10+ + 'Please download supported latest SQLite3 '+EXPECTED_SQLITE3_VERSION+' revision'#13#10+ 'from https://synopse.info/files/sqlite3'+{$ifdef FPC}'fpc'{$else}'obj'{$endif}+'.7z', [ExeVersion.ProgramName,fVersionText],error); LogToTextFile(error); // annoyning enough on all platforms // SynSQLite3Log.Add.Log() would do nothing: we are in .exe initialization - {$ifdef MSWINDOWS} // PITA popup - MessageBoxA(0,pointer(error),' WARNING: deprecated SQLite3 engine',MB_OK or MB_ICONWARNING); - {$endif} + {$ifdef MSWINDOWS} + AllocConsole; // PITA popup - better than a MessageBox() especially for services + {$endif MSWINDOWS} + {$I-} + writeln(error); + ioresult; + {$I+} end; destructor TSQLite3LibraryStatic.Destroy; diff --git a/contrib/mORMot/SynSSPI.pas b/contrib/mORMot/SynSSPI.pas index ba5c47f..da370ac 100644 --- a/contrib/mORMot/SynSSPI.pas +++ b/contrib/mORMot/SynSSPI.pas @@ -5,7 +5,7 @@ unit SynSSPI; { This file is part of Synopse mORMot framework. - Synopse mORMot framework. Copyright (C) 2020 Arnaud Bouchez + Synopse mORMot framework. Copyright (C) 2022 Arnaud Bouchez Synopse Informatique - https://synopse.info *** BEGIN LICENSE BLOCK ***** @@ -24,7 +24,7 @@ unit SynSSPI; The Initial Developer of the Original Code is Chaa. - Portions created by the Initial Developer are Copyright (C) 2020 + Portions created by the Initial Developer are Copyright (C) 2022 the Initial Developer. All Rights Reserved. Contributor(s): @@ -318,8 +318,9 @@ function SecEncrypt(var aSecContext: TSecContext; const aPlain: TSSPIBuffer): TS // - aSecContext must be set e.g. from previous success call to ServerSSPIAuth // or ClientSSPIAuth // - aEncrypted contains data that must be decrypted +// - warning: aEncrypted is modified in-place during the process // - returns decrypted message -function SecDecrypt(var aSecContext: TSecContext; const aEncrypted: TSSPIBuffer): TSSPIBuffer; +function SecDecrypt(var aSecContext: TSecContext; var aEncrypted: TSSPIBuffer): TSSPIBuffer; type @@ -393,8 +394,6 @@ function CertCloseStore; external crypt32; function CertFindCertificateInStore; external crypt32; -(* ========================= High-Level SSPI / SChannel API wrappers === *) - { TSecBuffer } procedure TSecBuffer.Init(aType: cardinal; aData: pointer; @@ -405,6 +404,7 @@ begin cbBuffer := aSize; end; + { TSecBufferDesc } procedure TSecBufferDesc.Init(aVersion: cardinal; aBuffers: PSecBuffer; @@ -416,6 +416,9 @@ begin end; + +(* ========================= High-Level SSPI / SChannel API wrappers === *) + procedure InvalidateSecContext(var aSecContext: TSecContext; aConnectionID: Int64); begin aSecContext.ID := aConnectionID; @@ -506,7 +509,7 @@ begin Move(PByte(InBuf[2].pvBuffer)^, BufPtr^, InBuf[2].cbBuffer); end; -function SecDecrypt(var aSecContext: TSecContext; const aEncrypted: TSSPIBuffer): TSSPIBuffer; +function SecDecrypt(var aSecContext: TSecContext; var aEncrypted: TSSPIBuffer): TSSPIBuffer; var EncLen, SigLen: Cardinal; BufPtr: PByte; InBuf: array [0..1] of TSecBuffer; @@ -541,7 +544,6 @@ begin raise ESynSSPI.CreateLastOSError(aSecContext); SetString(Result, PAnsiChar(InBuf[1].pvBuffer), InBuf[1].cbBuffer); - FreeContextBuffer(InBuf[1].pvBuffer); end; diff --git a/contrib/mORMot/SynSSPIAuth.pas b/contrib/mORMot/SynSSPIAuth.pas index b26e542..c4c5245 100644 --- a/contrib/mORMot/SynSSPIAuth.pas +++ b/contrib/mORMot/SynSSPIAuth.pas @@ -5,7 +5,7 @@ unit SynSSPIAuth; { This file is part of Synopse mORMot framework. - Synopse mORMot framework. Copyright (C) 2020 Arnaud Bouchez + Synopse mORMot framework. Copyright (C) 2022 Arnaud Bouchez Synopse Informatique - https://synopse.info *** BEGIN LICENSE BLOCK ***** @@ -24,7 +24,7 @@ unit SynSSPIAuth; The Initial Developer of the Original Code is Chaa. - Portions created by the Initial Developer are Copyright (C) 2020 + Portions created by the Initial Developer are Copyright (C) 2022 the Initial Developer. All Rights Reserved. Contributor(s): diff --git a/contrib/mORMot/SynScaleMM.pas b/contrib/mORMot/SynScaleMM.pas index 12d4da1..75fd1e6 100644 --- a/contrib/mORMot/SynScaleMM.pas +++ b/contrib/mORMot/SynScaleMM.pas @@ -49,12 +49,12 @@ unit SynScaleMM; The Initial Developer of the Original Code is André Mussche. - Portions created by the Initial Developer are Copyright (C) 2020 + Portions created by the Initial Developer are Copyright (C) 2022 the Initial Developer. All Rights Reserved. Contributor(s): - Arnaud Bouchez https://synopse.info - Portions created by each contributor are Copyright (C) 2020 + Portions created by each contributor are Copyright (C) 2022 each contributor. All Rights Reserved. Alternatively, the contents of this file may be used under the terms of diff --git a/contrib/mORMot/SynSelfTests.pas b/contrib/mORMot/SynSelfTests.pas index c1dbab1..9af8064 100644 --- a/contrib/mORMot/SynSelfTests.pas +++ b/contrib/mORMot/SynSelfTests.pas @@ -6,7 +6,7 @@ unit SynSelfTests; { This file is part of Synopse mORMot framework. - Synopse framework. Copyright (C) 2020 Arnaud Bouchez + Synopse framework. Copyright (C) 2022 Arnaud Bouchez Synopse Informatique - https://synopse.info *** BEGIN LICENSE BLOCK ***** @@ -25,7 +25,7 @@ unit SynSelfTests; The Initial Developer of the Original Code is Arnaud Bouchez. - Portions created by the Initial Developer are Copyright (C) 2020 + Portions created by the Initial Developer are Copyright (C) 2022 the Initial Developer. All Rights Reserved. Contributor(s): @@ -371,6 +371,8 @@ type procedure UrlEncoding; /// some low-level JSON encoding/decoding procedure EncodeDecodeJSON; + /// some performance numbers about JSON parsing and generating + procedure JSONBenchmark; /// HTML generation from Wiki Or Markdown syntax procedure WikiMarkdownToHtml; {$ifndef NOVARIANTS} @@ -397,7 +399,7 @@ type {$ifndef DELPHI5OROLDER} -/// this test case will test some generic classes + /// this test case will test some generic classes // defined and implemented in the mORMot.pas unit TTestBasicClasses = class(TSynTestCase) published @@ -461,6 +463,8 @@ type procedure _SHA3; /// AES encryption/decryption functions procedure _AES256; + /// AES-GCM encryption/decryption with authentication + procedure _AES_GCM; /// RC4 encryption function procedure _RC4; /// Base-64 encoding/decoding functions @@ -824,15 +828,15 @@ type {$ifdef MSWINDOWS} /// test external DB using the JET engine procedure JETDatabase; - {$endif} - {$endif} - {$endif} + {$endif MSWINDOWS} + {$endif LVCL} + {$endif CPU64} {$ifdef MSWINDOWS} {$ifdef USEZEOS} /// test external Firebird embedded engine via Zeos/ZDBC (if available) procedure FirebirdEmbeddedViaZDBCOverHTTP; - {$endif} - {$endif} + {$endif USEZEOS} + {$endif MSWINDOWS} end; /// a test case for multi-threading abilities of the framework @@ -1357,8 +1361,8 @@ uses {$else} Graphics, {$endif} -{$endif} -{$endif} +{$endif FPC} +{$endif MSWINDOWS} SynCrypto, SynZip, SynLZO, @@ -3160,7 +3164,7 @@ begin str := UTF8ToString(UrlEncode(StringToUTF8('https://test3.diavgeia.gov.gr/doc/'))); check(str='https%3A%2F%2Ftest3.diavgeia.gov.gr%2Fdoc%2F'); Test('abcdef','abcdef'); - Test('abcdefyzABCDYZ01239_-.~ ','abcdefyzABCDYZ01239_-.~+'); + Test('abcdefyzABCDYZ01239_-.~ ','abcdefyzABCDYZ01239_-.%7E+'); Test('"Aardvarks lurk, OK?"','%22Aardvarks+lurk%2C+OK%3F%22'); Test('"Aardvarks lurk, OK%"','%22Aardvarks+lurk%2C+OK%25%22'); Test('where=name like :(''Arnaud%'')','where%3Dname+like+%3A%28%27Arnaud%25%27%29'); @@ -3909,6 +3913,7 @@ var crc: array[0..10000] of record crc: cardinal; end; totallen: Cardinal; + s2: RawByteString; procedure Test(hash: THasher; const name: string); var i: Integer; Timer: TPrecisionTimer; @@ -4034,6 +4039,11 @@ begin hmac32.Init(@c1,4); hmac32.Update(pointer(s),length(s)); check(hmac32.Done=c2); + s2 := s; + SymmetricEncrypt(i, s2); + check(s2 <> s); + SymmetricEncrypt(i, s2); + check(s2 = s); end; Test(crc32creference,'pas'); Test(crc32cfast,'fast'); @@ -5098,6 +5108,20 @@ begin Check(arr[0]='-1'); Check(arr[1]='25'); Check(arr[2]='0'); + Finalize(arr); + CSVToRawUTF8DynArray('AA,BB,CC,DD', ',', ',', arr); + check(Length(arr)=4); + Check(arr[0]='AA'); + Check(arr[1]='BB'); + Check(arr[2]='CC'); + Check(arr[3]='DD'); + Finalize(arr); + CSVToRawUTF8DynArray('A,B,C,D', ',', ',', arr); + check(Length(arr)=4); + Check(arr[0]='A'); + Check(arr[1]='B'); + Check(arr[2]='C'); + Check(arr[3]='D'); Check(AddPrefixToCSV('One,Two,Three','Pre')='PreOne,PreTwo,PreThree'); Check(CSVOfValue('?',3)='?,?,?'); {$ifndef DELPHI5OROLDER} @@ -5813,7 +5837,7 @@ var W: TFileBufferWriter; i: integer; V: double; u: SynUnicode; - a: WinAnsiString; + a: RawUTF8; {$endif NOVARIANTS} begin T := TSynTable.Create('Test'); @@ -5863,10 +5887,10 @@ begin for i := 1 to 100 do begin u := RandomUnicode(i*2); data.Field['text'] := u; - check(data.Field['text']=u); + check(SynUnicode(data.Field['text'])=u); a := RandomAnsi7(i*2); data.Field['ansi'] := a; - check(data.Field['ansi']=a); + check(SynUnicode(data.Field['ansi'])=SynUnicode(a)); // here, ansi is more efficent than text for storage size end; check(data.Field['bool']=true); @@ -5881,8 +5905,8 @@ begin CheckSame(data.Field['double'],V); end; check(data.Field['bool']=true); - check(data.Field['text']=u); - check(data.Field['ansi']=a); + check(SynUnicode(data.Field['text'])=u); + check(SynUnicode(data.Field['ansi'])=SynUnicode(a)); check(data.Field['ID']=1); // test TSynTableVariantType rec := T.Data; @@ -5904,10 +5928,11 @@ begin CheckSame(rec.double,3.141592654); for i := 1 to 100 do begin a := RandomAnsi7(i*2); + u := SynUnicode(a); rec.text := a; - check(rec.text=a,'rec.text'); + check(SynUnicode(rec.text)=u,'rec.text'); rec.ansi := a; - check(rec.ansi=a,'rec.ansi'); + check(SynUnicode(rec.ansi)=u,'rec.ansi'); end; check(rec.bool=true,'rec.bool'); check(rec.varint=100); @@ -5921,8 +5946,8 @@ begin CheckSame(rec.double,V); end; check(rec.bool=true); - check(rec.text=a); - check(rec.ansi=a); + check(SynUnicode(rec.text)=u); + check(SynUnicode(rec.ansi)=u); check(rec.ID=1); except on E: Exception do // variant error could raise exceptions @@ -7450,6 +7475,8 @@ begin Check(length(mus.tests)>5); for i := 0 to high(mus.tests) do with mus.Tests[i] do begin + if desc='Dotted names should be resolved against former resolutions.' then + continue; // we don't handle a":{"b":{}} context (yet) if PosEx(' {{>partial}}',template)>0 then continue; // we don't indent each line of the expanded partials (yet) mustache := TSynMustache.Parse(template); @@ -8797,6 +8824,14 @@ check(IsValidJSON(J)); check(IsValidJSON('['+J)); J := GetJSONObjectAsSQL(J,false,true); CheckEqual(J,U); + J := '{'#10'"httpServer": {'#10'"host": "*",'#10'"port": "8881",'#10 + + '"serverType": "Socket",'#10'/*"reverseProxy": {'#10'"kind": "nginx",'#10 + + '"sendFileLocationRoot": "snake-ukrpatent-local"'#10'}*/'#10'} //eol'#10'}'; + check(not IsValidJSON(J)); + RemoveCommentsFromJSON(UniqueRawUTF8(J)); + CheckUTF8(IsValidJSON(J),J); + J := JSONReformat(J,jsonCompact); + CheckEqual(J,'{"httpServer":{"host":"*","port":"8881","serverType":"Socket"}}'); J := '{"RowID": 210 ,"Name":"Alice","Role":"User","Last Login":null, // comment'#13#10+ '"First Login" : /* to be ignored */ null , "Department" : "{\"relPath\":\"317\\\\\",\"revision\":1}" } ]'; check(not IsValidJSON(J)); @@ -9402,6 +9437,105 @@ check(IsValidJSON(J)); {$endif} end; +procedure TTestLowLevelTypes.JSONBenchmark; +const + ITER = 20; + ONLYLOG = false; +var + people, notexpanded: RawUtf8; + P: PUtf8Char; + count, len, lennexp, i: integer; + dv: TDocVariantData; + table: TSQLTableJson; + timer: TPrecisionTimer; +begin + people := StringFromFile('People.json'); + if people = '' then + exit; // need to run at least once the ORM tests + len := length(people); + check(len > 800000, 'unexpected people.json'); + len := len * ITER; + timer.Start; + for i := 1 to ITER do + Check(IsValidUtf8(people)); + NotifyTestSpeed('IsValidUtf8()', 0, len, @timer, ONLYLOG); + timer.Start; + for i := 1 to ITER do + Check(IsValidJson(people)); + NotifyTestSpeed('IsValidJson(RawUtf8)', 0, len, @timer, ONLYLOG); + timer.Start; + for i := 1 to ITER do + Check(IsValidJson(PUTF8Char(pointer(people)))); + NotifyTestSpeed('IsValidJson(PUtf8Char)', 0, len, @timer, ONLYLOG); + P := @people[2]; // point just after initial '[' for JsonArrayCount + count := JsonArrayCount(P); + check(count > 8200); // = 8227 in current People.json ORM tests file + timer.Start; + for i := 1 to ITER do + Check(JsonArrayCount(P) = count); + NotifyTestSpeed('JsonArrayCount(P)', 0, len, @timer, ONLYLOG); + timer.Start; + for i := 1 to ITER do + Check(JsonArrayCount(P, P + length(people) - 1) = count); + NotifyTestSpeed('JsonArrayCount(P,PMax)', 0, len, @timer, ONLYLOG); + timer.Start; + for i := 1 to ITER * 5000 do + Check(JsonObjectPropCount(P + 3) = 6, 'first TOrmPeople object'); + NotifyTestSpeed('JsonObjectPropCount()', 0, ITER * 5000 * 119, @timer, ONLYLOG); + timer.Start; + for i := 1 to ITER do + begin + dv.InitJson(people, JSON_OPTIONS_FAST); + Check(dv.count = count); + dv.Clear; // to reuse dv + end; + NotifyTestSpeed('TDocVariant', 0, len, @timer, ONLYLOG); + timer.Start; + for i := 1 to ITER do + begin + dv.InitJson(people, JSON_OPTIONS_FAST + [dvoInternNames]); + Check(dv.count = count); + dv.Clear; // to reuse dv + end; + NotifyTestSpeed('TDocVariant dvoInternNames', 0, len, @timer, ONLYLOG); + table := TSQLTableJson.Create('', people); + try + Check(table.RowCount = count); + timer.Start; + for i := 1 to ITER do + begin + notexpanded := table.GetJsonValues({expand=}false, 0, 65536); + lennexp := length(notexpanded); + Check(lennexp < length(people), 'notexpanded'); + end; + NotifyTestSpeed('TSQLTableJson GetJsonValues', 0, lennexp * ITER, @timer, ONLYLOG); + finally + table.Free; + end; + timer.Start; + for i := 1 to ITER do + begin + table := TSQLTableJson.Create('', people); + try + Check(table.RowCount = count); + finally + table.Free; + end; + end; + NotifyTestSpeed('TSQLTableJson expanded', 0, len, @timer, ONLYLOG); + timer.Start; + for i := 1 to ITER do + begin + table := TSQLTableJson.Create('', notexpanded); + try + Check(table.RowCount = count); + finally + table.Free; + end; + end; + NotifyTestSpeed('TSQLTableJson not expanded', 0, lennexp * ITER, @timer, ONLYLOG); +end; + procedure TTestLowLevelTypes.WikiMarkdownToHtml; begin // wiki @@ -9740,6 +9874,11 @@ begin check(json='{"double_params":[-12.12345678,-9.9E-15,-9.88E-15,-9E-15]}'); {$endif} CheckSame(double(TDocVariantData(o).A['double_params'].Value[1]),-9.9E-15); + // floats are stored as varCurrency by default in _Json() + o := _Json('{"value":99.99}'); + d := _Safe(o)^.D['value']; + CheckSame(d,99.99,DOUBLE_SAME,'99.99'); + CheckEqual(DoubleToStr(d),'99.99'); // see http://bsonspec.org/#/specification o := _JSON('{"hello": "world"}'); bsonDat := BSON(TDocVariantData(o)); @@ -10168,7 +10307,7 @@ var Doc,Doc2: TDocVariantData; s,j: RawUTF8; vd: double; vs: single; - lTable: TSQLTableJSON; + lTable: TSQLTableJson; lRefreshed: Boolean; begin Doc.Init; @@ -10436,7 +10575,7 @@ begin s := VariantSaveJSON(V); check(s='{"ID":1,"Notation":"ABC","Price":10.1,"CustomNotation":"XYZ"}'); // some tests to avoid regression about bugs reported by users on forum - lTable := TSQLTableJSON.Create(''); + lTable := TSQLTableJson.Create(''); try lTable.UpdateFrom(TEST_DATA_1,lRefreshed,nil); ndx := lTable.FieldIndex('RELATION_ID'); @@ -10911,7 +11050,7 @@ var Model: TSQLModel; Batch: TSQLRestBatch; IDs: TIDDynArray; i,j,n: integer; - dummy: RawUTF8; + dummy, s: RawUTF8; {$ifndef NOVARIANTS} procedure CheckVariantWith(const V: Variant; const i: Integer; const offset: integer=0); begin @@ -11051,7 +11190,7 @@ begin Client.RetrieveDocVariantArray(TSQLRecordTest,'items','Int,Test')); check(IdemPChar(pointer(dummy),'1=1'#$D#$A'2=2'#$D#$A'3=3'#$D#$A'4=4')); check(Hash32(dummy)=$BC89CA72); - {$endif} + {$endif NOVARIANTS} Check(Client.UpdateField(TSQLRecordTest,100,'ValWord',[100+10]), 'update one field of a given record'); R := TSQLRecordTest.Create(Client,100); @@ -11060,6 +11199,8 @@ begin finally R.Free; end; + s := Client.OneFieldValues(TSQLRecordTest,'Test','ValWord=:(110):'); + Check(s='100,110'); Check(Client.UpdateField(TSQLRecordTest,100,'ValWord',[100])); R := TSQLRecordTest.Create(Client,100); try @@ -11067,6 +11208,8 @@ begin finally R.Free; end; + s := Client.OneFieldValues(TSQLRecordTest,'Test',FormatUTF8('ValWord=?',[],[110])); + Check(s='110'); Check(Client.UpdateField(TSQLRecordTest,'Unicode',['110'],'ValWord',[120]), 'update one field of a given record'); R := TSQLRecordTest.Create(Client,110); @@ -11246,7 +11389,7 @@ begin {$ifndef LVCL} s := ObjectToJSON(T); Check(s='{"ID":0,"Int":0,"Test":"","Unicode":"","Ansi":"","ValFloat":0,'+ - '"ValWord":0,"ValDate":"","Next":0,"Data":"","ValVariant":null}'); + '"ValWord":0,"ValDate":"","Data":"","ValVariant":null}'); {$endif} T.ValDate := 39882.888612; // a fixed date and time T.Ansi := 'abcde6ef90'; @@ -11304,7 +11447,7 @@ begin s := ObjectToJSON(T); Check(s='{"ID":10,"Int":0,"Test":"'+T.Test+'","Unicode":"'+T.Test+ '","Ansi":"'+T.Test+'","ValFloat":3.141592653,"ValWord":1203,'+ - '"ValDate":"2009-03-10T21:19:36","Next":0,"Data":""'{$ifndef NOVARIANTS} + '"ValDate":"2009-03-10T21:19:36","Data":""'{$ifndef NOVARIANTS} +',"ValVariant":3.1416'{$endif}+'}'); T2.ClearProperties; Check(not T.SameValues(T2)); @@ -11357,7 +11500,8 @@ begin Check(s=StringReplaceAll(s2,', ',',')+',ValVariant=''{"name":"John","int":1234}'''); s := ObjectToJSON(T); delete(s1,3,3); // "RowID":10 -> "ID":10 - Check(s=s1+',"Data":"","ValVariant":{"name":"John","int":1234}}'); + s1 := StringReplaceAll(s1,',"Next":0',''); + CheckEqual(s,s1+',"Data":"","ValVariant":{"name":"John","int":1234}}'); bin := T.GetBinary; T2.ClearProperties; Check(T2.SetBinary(pointer(bin),PAnsiChar(pointer(bin))+length(bin))); @@ -11653,7 +11797,7 @@ begin Free; end; end; -{$ifdef MSWINDOWS} +{$ifdef MSWINDOWS} // PasZip.TZipRead uses memory mapped API procedure TestPasZipRead(const FN: TFileName; Count: integer); var pasZR: PasZip.TZipRead; begin @@ -11667,7 +11811,7 @@ begin end; end; var pasZW: PasZip.TZipWrite; -{$endif} +{$endif MSWINDOWS} var i: integer; begin ExeName := ExtractFileName(ExeVersion.ProgramFileName); @@ -11764,9 +11908,9 @@ var s,t,rle: RawByteString; i,j, complen2: integer; comp2,dec1: array of byte; {$ifdef CPUINTEL} - comp1,dec2: array of byte; + comp1, dec2: array of byte; complen1: integer; - {$endif} + {$endif CPUINTEL} begin for i := 1 to 200 do begin s := SynLZCompress(StringOfChar(AnsiChar(i),i)); @@ -12118,6 +12262,143 @@ begin {$endif CPUINTEL} end; +procedure TTestCryptographicRoutines._AES_GCM; +const + hex32: THash256 = ($00,$01,$02,$03,$04,$05,$06,$07,$08,$09,$0a,$0b,$0c,$0d,$0e,$0f, + $10,$11,$12,$13,$14,$15,$16,$17,$18,$19,$1a,$1b,$1c,$1d,$1e,$1f); + buf32: THash256 = ($92,$4e,$17,$8a,$17,$fa,$1c,$a0,$e7,$48,$6f,$04,$04,$12,$3b,$91, + $db,$f7,$97,$bb,$9d,$bd,$e9,$b1,$d4,$8d,$5c,$7f,$53,$16,$59,$12); + tag32: array[0..15] of byte = ($10,$f9,$72,$b6,$f9,$e0,$a3,$c1,$cf,$9c,$cf,$56,$54,$3d,$ca,$79); + K01: THash256 = (0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0); + I01: array[0..11] of byte = (0,0,0,0,0,0,0,0,0,0,0,0); + P01: array[0..15] of byte = (0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0); + C01: array[0..15] of byte = ($ce,$a7,$40,$3d,$4d,$60,$6b,$6e,$07,$4e,$c5,$d3,$ba,$f3,$9d,$18); + T01: array[0..15] of byte = ($d0,$d1,$c8,$a7,$99,$99,$6b,$f0,$26,$5b,$98,$b5,$d4,$8a,$b9,$19); + K02: THash256 = (0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0); + I02: array[0..11] of byte = (0,0,0,0,0,0,0,0,0,0,0,0); + H02: array[0..15] of byte = (0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0); + T02: array[0..15] of byte = ($2d,$45,$55,$2d,$85,$75,$92,$2b,$3c,$a3,$cc,$53,$84,$42,$fa,$26); + K03: THash256 = (0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0); + I03: array[0..11] of byte = (0,0,0,0,0,0,0,0,0,0,0,0); + H03: array[0..15] of byte = (0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0); + P03: array[0..15] of byte = (0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0); + C03: array[0..15] of byte = ($ce,$a7,$40,$3d,$4d,$60,$6b,$6e,$07,$4e,$c5,$d3,$ba,$f3,$9d,$18); + T03: array[0..15] of byte = ($ae,$9b,$17,$71,$db,$a9,$cf,$62,$b3,$9b,$e0,$17,$94,$03,$30,$b4); + K04: THash256 = ($fb,$76,$15,$b2,$3d,$80,$89,$1d,$d4,$70,$98,$0b,$c7,$95,$84,$c8, + $b2,$fb,$64,$ce,$60,$97,$8f,$4d,$17,$fc,$e4,$5a,$49,$e8,$30,$b7); + I04: array[0..11] of byte = ($db,$d1,$a3,$63,$60,$24,$b7,$b4,$02,$da,$7d,$6f); + P04: array[0..15] of byte = ($a8,$45,$34,$8e,$c8,$c5,$b5,$f1,$26,$f5,$0e,$76,$fe,$fd,$1b,$1e); + C04: array[0..15] of byte = ($5d,$f5,$d1,$fa,$bc,$bb,$dd,$05,$15,$38,$25,$24,$44,$17,$87,$04); + T04: array[0..15] of byte = ($4c,$43,$cc,$e5,$a5,$74,$d8,$a8,$8b,$43,$d4,$35,$3b,$d6,$0f,$9f); + K05: THash256 = ($40,$41,$42,$43,$44,$45,$46,$47,$48,$49,$4a,$4b,$4c,$4d,$4e,$4f, + $50,$51,$52,$53,$54,$55,$56,$57,$58,$59,$5a,$5b,$5c,$5d,$5e,$5f); + I05: array[0..11] of byte = ($10,$11,$12,$13,$14,$15,$16,$17,$18,$19,$1a,$1b); + H05: array[0..19] of byte = (0,$01,$02,$03,$04,$05,$06,$07,$08,$09,$0a,$0b,$0c,$0d,$0e,$0f,$10,$11,$12,$13); + P05: array[0..23] of byte = ($20,$21,$22,$23,$24,$25,$26,$27,$28,$29,$2a,$2b,$2c,$2d,$2e,$2f,$30,$31,$32,$33,$34,$35,$36,$37); + C05: array[0..23] of byte = ($59,$1b,$1f,$f2,$72,$b4,$32,$04,$86,$8f,$fc,$7b,$c7,$d5,$21,$99,$35,$26,$b6,$fa,$32,$24,$7c,$3c); + T05: array[0..15] of byte = ($7d,$e1,$2a,$56,$70,$e5,$70,$d8,$ca,$e6,$24,$a1,$6d,$f0,$9c,$08); + K07: THash256 = ($40,$41,$42,$43,$44,$45,$46,$47,$48,$49,$4a,$4b,$4c,$4d,$4e,$4f, + $50,$51,$52,$53,$54,$55,$56,$57,$58,$59,$5a,$5b,$5c,$5d,$5e,$5f); + I07: array[0..11] of byte = ($10,$11,$12,$13,$14,$15,$16,$17,$18,$19,$1a,$1b); + H07: THash256 = ($20,$21,$22,$23,$24,$25,$26,$27,$28,$29,$2a,$2b,$2c,$2d,$2e,$2f, + $30,$31,$32,$33,$34,$35,$36,$37,$38,$39,$3a,$3b,$3c,$3d,$3e,$3f); + P07: array[0..255] of byte =(0,$01,$02,$03,$04,$05,$06,$07,$08,$09,$0a,$0b,$0c,$0d,$0e,$0f,$10,$11,$12,$13,$14,$15,$16,$17, + $18,$19,$1a,$1b,$1c,$1d,$1e,$1f,$20,$21,$22,$23,$24,$25,$26,$27,$28,$29,$2a,$2b,$2c,$2d,$2e,$2f, + $30,$31,$32,$33,$34,$35,$36,$37,$38,$39,$3a,$3b,$3c,$3d,$3e,$3f,$40,$41,$42,$43,$44,$45,$46,$47, + $48,$49,$4a,$4b,$4c,$4d,$4e,$4f,$50,$51,$52,$53,$54,$55,$56,$57,$58,$59,$5a,$5b,$5c,$5d,$5e,$5f, + $60,$61,$62,$63,$64,$65,$66,$67,$68,$69,$6a,$6b,$6c,$6d,$6e,$6f,$70,$71,$72,$73,$74,$75,$76,$77, + $78,$79,$7a,$7b,$7c,$7d,$7e,$7f,$80,$81,$82,$83,$84,$85,$86,$87,$88,$89,$8a,$8b,$8c,$8d,$8e,$8f, + $90,$91,$92,$93,$94,$95,$96,$97,$98,$99,$9a,$9b,$9c,$9d,$9e,$9f,$a0,$a1,$a2,$a3,$a4,$a5,$a6,$a7, + $a8,$a9,$aa,$ab,$ac,$ad,$ae,$af,$b0,$b1,$b2,$b3,$b4,$b5,$b6,$b7,$b8,$b9,$ba,$bb,$bc,$bd,$be,$bf, + $c0,$c1,$c2,$c3,$c4,$c5,$c6,$c7,$c8,$c9,$ca,$cb,$cc,$cd,$ce,$cf,$d0,$d1,$d2,$d3,$d4,$d5,$d6,$d7, + $d8,$d9,$da,$db,$dc,$dd,$de,$df,$e0,$e1,$e2,$e3,$e4,$e5,$e6,$e7,$e8,$e9,$ea,$eb,$ec,$ed,$ee,$ef, + $f0,$f1,$f2,$f3,$f4,$f5,$f6,$f7,$f8,$f9,$fa,$fb,$fc,$fd,$fe,$ff); + C07: array[0..255] of byte =($79,$3b,$3f,$d2,$52,$94,$12,$24,$a6,$af,$dc,$5b,$e7,$f5,$01,$b9, + $15,$06,$96,$da,$12,$04,$5c,$1c,$60,$77,$d3,$ca,$c7,$74,$ac,$cf,$c3,$d5,$30,$d8,$48,$d6,$65,$d8, + $1a,$49,$cb,$b5,0,$b8,$8b,$bb,$62,$4a,$e6,$1d,$16,$67,$22,$9c,$30,$2d,$c6,$ff,$0b,$b4,$d7,$0b, + $db,$bc,$85,$66,$d6,$f5,$b1,$58,$da,$99,$a2,$ff,$2e,$01,$dd,$a6,$29,$b8,$9c,$34,$ad,$1e,$5f,$eb, + $a7,$0e,$7a,$ae,$43,$28,$28,$9c,$36,$29,$b0,$58,$83,$50,$58,$1c,$a8,$b9,$7c,$cf,$12,$58,$fa,$3b, + $be,$2c,$50,$26,$04,$7b,$a7,$26,$48,$96,$9c,$ff,$8b,$a1,$0a,$e3,$0e,$05,$93,$5d,$f0,$c6,$93,$74, + $18,$92,$b7,$6f,$af,$67,$13,$3a,$bd,$2c,$f2,$03,$11,$21,$bd,$8b,$b3,$81,$27,$a4,$d2,$ee,$de,$ea, + $13,$27,$64,$94,$f4,$02,$cd,$7c,$10,$7f,$b3,$ec,$3b,$24,$78,$48,$34,$33,$8e,$55,$43,$62,$87,$09, + $2a,$c4,$a2,$6f,$5e,$a7,$ea,$4a,$d6,$8d,$73,$15,$16,$39,$b0,$5b,$24,$e6,$8b,$98,$16,$d1,$39,$83, + $76,$d8,$e4,$13,$85,$94,$75,$8d,$b9,$ad,$3b,$40,$92,$59,$b2,$6d,$cf,$c0,$6e,$72,$2b,$e9,$87,$b3, + $76,$7f,$70,$a7,$b8,$56,$b7,$74,$b1,$ba,$26,$85,$b3,$68,$09,$14,$29,$fc,$cb,$8d,$cd,$de,$09,$e4); + T07: array[0..15] of byte = ($87,$ec,$83,$7a,$bf,$53,$28,$55,$b2,$ce,$a1,$69,$d6,$94,$3f,$cd); + K08: THash256 = ($fb,$76,$15,$b2,$3d,$80,$89,$1d,$d4,$70,$98,$0b,$c7,$95,$84,$c8, + $b2,$fb,$64,$ce,$60,$97,$87,$8d,$17,$fc,$e4,$5a,$49,$e8,$30,$b7); + I08: array[0..11] of byte = ($db,$d1,$a3,$63,$60,$24,$b7,$b4,$02,$da,$7d,$6f); + H08: array[0.. 0] of byte = ($36); + P08: array[0.. 0] of byte = ($a9); + C08: array[0.. 0] of byte = ($0a); + T08: array[0..15] of byte = ($be,$98,$7d,0,$9a,$4b,$34,$9a,$a8,$0c,$b9,$c4,$eb,$c1,$e9,$f4); + K09: THash256 = ($f8,$d4,$76,$cf,$d6,$46,$ea,$6c,$23,$84,$cb,$1c,$27,$d6,$19,$5d, + $fe,$f1,$a9,$f3,$7b,$9c,$8d,$21,$a7,$9c,$21,$f8,$cb,$90,$d2,$89); + I09: array[0..11] of byte = ($db,$d1,$a3,$63,$60,$24,$b7,$b4,$02,$da,$7d,$6f); + H09: array[0..19] of byte = ($7b,$d8,$59,$a2,$47,$96,$1a,$21,$82,$3b,$38,$0e,$9f,$e8,$b6,$50,$82,$ba,$61,$d3); + P09: array[0..19] of byte = ($90,$ae,$61,$cf,$7b,$ae,$bd,$4c,$ad,$e4,$94,$c5,$4a,$29,$ae,$70,$26,$9a,$ec,$71); + C09: array[0..19] of byte = ($ce,$20,$27,$b4,$7a,$84,$32,$52,$01,$34,$65,$83,$4d,$75,$fd,$0f,$07,$29,$75,$2e); + T09: array[0..15] of byte = ($ac,$d8,$83,$38,$37,$ab,$0e,$de,$84,$f4,$74,$8d,$a8,$89,$9c,$15); + K10: THash256 = ($db,$bc,$85,$66,$d6,$f5,$b1,$58,$da,$99,$a2,$ff,$2e,$01,$dd,$a6, + $29,$b8,$9c,$34,$ad,$1e,$5f,$eb,$a7,$0e,$7a,$ae,$43,$28,$28,$9c); + I10: array[0..15] of byte = ($cf,$c0,$6e,$72,$2b,$e9,$87,$b3,$76,$7f,$70,$a7,$b8,$56,$b7,$74); + P10: array[0..15] of byte = ($ce,$20,$27,$b4,$7a,$84,$32,$52,$01,$34,$65,$83,$4d,$75,$fd,$0f); + C10: array[0..15] of byte = ($dc,$03,$e5,$24,$83,$0d,$30,$f8,$8e,$19,$7f,$3a,$ca,$ce,$66,$ef); + T10: array[0..15] of byte = ($99,$84,$ef,$f6,$90,$57,$55,$d1,$83,$6f,$2d,$b0,$40,$89,$63,$4c); + K11: THash256 = ($0e,$05,$93,$5d,$f0,$c6,$93,$74,$18,$92,$b7,$6f,$af,$67,$13,$3a, + $bd,$2c,$f2,$03,$11,$21,$bd,$8b,$b3,$81,$27,$a4,$d2,$ee,$de,$ea); + I11: array[0..16] of byte = ($74,$b1,$ba,$26,$85,$b3,$68,$09,$14,$29,$fc,$cb,$8d,$cd,$de,$09,$e4); + H11: array[0..19] of byte = ($7b,$d8,$59,$a2,$47,$96,$1a,$21,$82,$3b,$38,$0e,$9f,$e8,$b6,$50,$82,$ba,$61,$d3); + P11: array[0..19] of byte = ($90,$ae,$61,$cf,$7b,$ae,$bd,$4c,$ad,$e4,$94,$c5,$4a,$29,$ae,$70,$26,$9a,$ec,$71); + C11: array[0..19] of byte = ($6b,$e6,$5e,$56,$06,$6c,$40,$56,$73,$8c,$03,$fe,$23,$20,$97,$4b,$a3,$f6,$5e,$09); + T11: array[0..15] of byte = ($61,$08,$dc,$41,$7b,$f3,$2f,$7f,$b7,$55,$4a,$e5,$2f,$08,$8f,$87); + procedure test(ptag: pointer; tlen: PtrInt; const key; kbits: PtrInt; + pIV: pointer; IV_Len: PtrInt; pAAD: pointer; aLen: PtrInt; + ctp: pointer; cLen: PtrInt; ptp: pointer; tn: integer); + var tag: TAESBLock; + ctxt: TAESGCMEngine; + pt, ct: array[0..511] of byte; + begin + FillCharFast(pt,SizeOf(pt),0); + CheckUTF8(ctxt.FullDecryptAndVerify( + key,kbits,pIV,IV_Len,pAAD,aLen,ctp,@pt,cLen,ptag,tlen), + 'FullDecryptAndVerify #%',[tn]); + CheckUTF8(CompareMem(@pt,ptp,cLen),'Plain #%',[tn]); + FillCharFast(ct,SizeOf(ct),0); + CheckUTF8(ctxt.FullEncryptAndAuthenticate( + key,kbits,pIV,IV_len,pAAD,aLen,ptp,@ct,clen,tag), + 'FullEncryptAndAuthenticate #%',[tn]); + CheckUTF8(CompareMem(@tag,ptag,tLen),'Tag #%',[tn]); + CheckUTF8(CompareMem(@ct,ctp,cLen),'Encoded #%',[tn]); + end; +var ctxt: TAESGCMEngine; + key,tag: TAESBlock; + buf: THash512; + n: integer; +begin + key := PAESBlock(@hex32)^; + for n := 1 to 32 do begin + Check(ctxt.Init(key,128)); + Check(ctxt.Reset(@hex32,n)); + Check(ctxt.Add_AAD(@hex32,n)); + Check(ctxt.Encrypt(@hex32,@buf,n)); + Check(ctxt.Final(tag)); + key := tag; + end; + Check(CompareMem(@buf32,@buf,SizeOf(buf32))); + Check(CompareMem(@tag32,@tag,SizeOf(tag32))); + test(@T01,16,K01,8*sizeof(K01),@I01,sizeof(I01),nil,0,@C01,sizeof(C01),@P01,01); + test(@T02,16,K02,8*sizeof(K02),@I02,sizeof(I02),@H02,sizeof(H02),nil,0,nil,02); + test(@T03,16,K03,8*sizeof(K03),@I03,sizeof(I03),@H03,sizeof(H03),@C03,sizeof(C03),@P03,03); + test(@T04,16,K04,8*sizeof(K04),@I04,sizeof(I04),nil,0,@C04,sizeof(C04),@P04,04); + test(@T05,16,K05,8*sizeof(K05),@I05,sizeof(I05),@H05,sizeof(H05),@C05,sizeof(C05),@P05,05); + test(@T07,16,K07,8*sizeof(K07),@I07,sizeof(I07),@H07,sizeof(H07),@C07,sizeof(C07),@P07,07); + test(@T08,16,K08,8*sizeof(K08),@I08,sizeof(I08),@H08,sizeof(H08),@C08,sizeof(C08),@P08,08); + test(@T09,16,K09,8*sizeof(K09),@I09,sizeof(I09),@H09,sizeof(H09),@C09,sizeof(C09),@P09,09); + test(@T10,16,K10,8*sizeof(K10),@I10,sizeof(I10),nil,0,@C10,sizeof(C10),@P10,10); + test(@T11,16,K11,8*sizeof(K11),@I11,sizeof(I11),@H11,sizeof(H11),@C11,sizeof(C11),@P11,11); +end; + procedure TTestCryptographicRoutines._CompressShaAes; var s1,s2: RawByteString; keysize,i: integer; @@ -12796,19 +13077,19 @@ type bSHA3_256, bSHA3_512, // encryption bRC4, - bAES128CFB, bAES128OFB, bAES128CFBCRC, bAES128OFBCRC, - bAES256CFB, bAES256OFB, bAES256CFBCRC, bAES256OFBCRC, + bAES128CFB, bAES128OFB, bAES128CFBCRC, bAES128OFBCRC, bAES128GCM, + bAES256CFB, bAES256OFB, bAES256CFBCRC, bAES256OFBCRC, bAES256GCM, bSHAKE128, bSHAKE256 ); procedure TTestCryptographicRoutines.Benchmark; const SIZ: array[0..4] of integer = (8, 50, 100, 1000, 10000); COUNT = 500; - AESCLASS: array[bAES128CFB .. bAES256OFBCRC] of TAESAbstractClass = ( - TAESCFB, TAESOFB, TAESCFBCRC, TAESOFBCRC, - TAESCFB, TAESOFB, TAESCFBCRC, TAESOFBCRC); - AESBITS: array[bAES128CFB .. bAES256OFBCRC] of integer = ( - 128, 128, 128, 128, 256, 256, 256, 256); + AESCLASS: array[bAES128CFB .. bAES256GCM] of TAESAbstractClass = ( + TAESCFB, TAESOFB, TAESCFBCRC, TAESOFBCRC, TAESGCM, + TAESCFB, TAESOFB, TAESCFBCRC, TAESOFBCRC, TAESGCM); + AESBITS: array[bAES128CFB .. bAES256GCM] of integer = ( + 128, 128, 128, 128, 128, 256, 256, 256, 256, 256); var b: TBenchmark; s, i, size, n: integer; data, encrypted: RawByteString; @@ -12822,7 +13103,7 @@ var b: TBenchmark; RC4: TRC4; timer: TPrecisionTimer; time: array[TBenchmark] of Int64; - AES: array[bAES128CFB .. bAES256OFBCRC] of TAESAbstract; + AES: array[bAES128CFB .. bAES256GCM] of TAESAbstract; TXT: array[TBenchmark] of RawUTF8; begin GetEnumTrimmedNames(TypeInfo(TBenchmark),@TXT); @@ -12861,9 +13142,10 @@ begin bSHA3_512: SHA3.Full(pointer(data),SIZ[s],dig.b); bRC4: RC4.EncryptBuffer(pointer(data), pointer(Encrypted), SIZ[s]); bAES128CFB, bAES128OFB, bAES256CFB, bAES256OFB: - AES[b].EncryptPKCS7(Data, true); - bAES128CFBCRC, bAES128OFBCRC, bAES256CFBCRC, bAES256OFBCRC: - AES[b].MACAndCrypt(Data,true); + AES[b].EncryptPKCS7(Data,{encrypt=}true); + bAES128CFBCRC, bAES128OFBCRC, bAES256CFBCRC, bAES256OFBCRC, + bAES128GCM, bAES256GCM: + AES[b].MACAndCrypt(Data,{encrypt=}true); bSHAKE128: SHAKE128.Cypher(pointer(Data), pointer(Encrypted), SIZ[s]); bSHAKE256: SHAKE256.Cypher(pointer(Data), pointer(Encrypted), SIZ[s]); end; @@ -14418,12 +14700,12 @@ end; procedure TTestMemoryBased._TSQLTableWritable; procedure Test(intern: TRawUTF8Interning); - var s1,s2: TSQLTableJSON; + var s1,s2: TSQLTableJson; w: TSQLTableWritable; f,r: integer; begin - s1 := TSQLTableJSON.CreateFromTables([TSQLRecordPeople],'',JS); - s2 := TSQLTableJSON.CreateFromTables([TSQLRecordPeople],'',JS); + s1 := TSQLTableJson.CreateFromTables([TSQLRecordPeople],'',JS); + s2 := TSQLTableJson.CreateFromTables([TSQLRecordPeople],'',JS); w := TSQLTableWritable.CreateFromTables([TSQLRecordPeople],'',JS); try // merge the same data twice, and validate duplicated columns w.NewValuesInterning := intern; @@ -14824,10 +15106,10 @@ begin if CheckFailed(Resp<>nil) then exit; try - Check(Resp.InheritsFrom(TSQLTableJSON)); - len := Length(TSQLTableJSON(Resp).PrivateInternalCopy)-16; + Check(Resp.InheritsFrom(TSQLTableJson)); + len := Length(TSQLTableJson(Resp).PrivateInternalCopy)-16; if not CheckFailed(len>0) then - Check(Hash32(pointer(TSQLTableJSON(Resp).PrivateInternalCopy),len)=$F11CEAC0); + Check(Hash32(pointer(TSQLTableJson(Resp).PrivateInternalCopy),len)=$F11CEAC0); //FileFromString(Resp.GetODSDocument,'people.ods'); finally Resp.Free; @@ -14915,9 +15197,9 @@ begin Resp := Client.List([TSQLRecordPeople],'*',CLIENTTEST_WHERECLAUSE); if CheckFailed(Resp<>nil) then exit; - siz := length(TSQLTableJSON(Resp).PrivateInternalCopy)-16; + siz := length(TSQLTableJson(Resp).PrivateInternalCopy)-16; if not CheckFailed(siz=4818) then - Check(Hash32(pointer(TSQLTableJSON(Resp).PrivateInternalCopy),siz)=$8D727024); + Check(Hash32(pointer(TSQLTableJson(Resp).PrivateInternalCopy),siz)=$8D727024); Resp.Free; {$ifdef WTIME} fRunConsole := format('%s%s, first %s, ',[fRunConsole,KB(siz),Timer.Stop]); @@ -14992,11 +15274,11 @@ begin if CheckFailed(Resp<>nil) then exit; try - Check(Resp.InheritsFrom(TSQLTableJSON)); + Check(Resp.InheritsFrom(TSQLTableJson)); // every answer contains 113 rows, for a total JSON size of 4803 bytes - siz := length(TSQLTableJSON(Resp).PrivateInternalCopy)-16; + siz := length(TSQLTableJson(Resp).PrivateInternalCopy)-16; if not CheckFailed(siz>0) then - Check(Hash32(pointer(TSQLTableJSON(Resp).PrivateInternalCopy),siz)=$8D727024); + Check(Hash32(pointer(TSQLTableJson(Resp).PrivateInternalCopy),siz)=$8D727024); finally Resp.Free; end; @@ -15345,6 +15627,8 @@ type {$endif} end; +{ TSQLRecordCustomProps } + class procedure TSQLRecordCustomProps.InternalRegisterCustomProperties(Props: TSQLRecordProperties); begin Props.RegisterCustomPropertyFromTypeName(self,'TGUID','GUID', @@ -15600,6 +15884,10 @@ begin 'select count(*) from SampleRecord'); Test2('select count(*) from PeopleExt where rowid=2', 'select count(*) from SampleRecord where id=2'); + Test2('select count(*) from PeopleExt where rowid=2 /*tobeignored*/', + 'select count(*) from SampleRecord where id=2'); + Test2('select count(*) from PeopleExt where /*tobeignored*/ rowid=2', + 'select count(*) from SampleRecord where id=2'); Test2('select Distinct(firstname) , max(lastchange)+100 from PeopleExt where rowid >= :(2):', 'select Distinct(FirstName),max(Changed)+100 as LastChange from SampleRecord where ID>=:(2):'); Test2('select Distinct(lastchange) , max(rowid)-100 as newid from PeopleExt where rowid >= :(2):', @@ -15644,6 +15932,9 @@ begin Test(dJet,true,'select top 2 id,firstname from SampleRecord order by firstname'); Test(dMySQL,true,'select id,firstname from SampleRecord order by firstname limit 2'); Test(dSQLite,true,'select id,firstname from SampleRecord order by firstname limit 2'); + SqlOrigin := 'SELECT RowID,firstname FROM PeopleExt WHERE :(3001): '+ + 'BETWEEN firstname AND RowID LIMIT 1'; + Test(dSQLite,false); finally Ext.Free; end; @@ -16597,7 +16888,7 @@ var V,V2: TSQLRecordPeople; Curr: Currency; DaVinci, s: RawUTF8; Refreshed: boolean; - J: TSQLTableJSON; + J: TSQLTableJson; i, n, nupd, ndx: integer; IntArray: TInt64DynArray; Results: TIDDynArray; @@ -17041,7 +17332,7 @@ begin J := Client.List([TSQLRecordPeople],'*',s); Check(Client.UpdateFromServer([J],Refreshed)); Check(not Refreshed); - Check(TestTable(J),'incorrect TSQLTableJSON'); + Check(TestTable(J),'incorrect TSQLTableJson'); Check(Client.OneFieldValues(TSQLRecordPeople,'ID','LastName=:("Dali"):',IntArray)); Check(length(IntArray)=1001); for i := 0 to high(IntArray) do @@ -17400,8 +17691,8 @@ begin {$endif} end; -procedure TTestSQLite3Engine._TSQLTableJSON; -var J: TSQLTableJSON; +procedure TTestSQLite3Engine._TSQLTableJson; +var J: TSQLTableJson; i1, i2, aR, aF, F1,F2, n: integer; Comp, Comp1,Comp2: TUTF8Compare; {$ifdef UNICODE} @@ -17441,7 +17732,7 @@ const '"V03_TM":"sjentonpg@senate.gov"}]'; {$endif} begin - J := TSQLTableJSON.Create('',JS); + J := TSQLTableJson.Create('',JS); try J.SetFieldType('YearOfBirth',sftModTime); if JS<>'' then // avoid memory leak @@ -17591,7 +17882,7 @@ begin end; // some tests to avoid regression about bugs reported by users on forum {$ifndef NOVARIANTS} - J := TSQLTableJSON.Create('',TEST_DATA); + J := TSQLTableJson.Create('',TEST_DATA); try check(J.fieldCount=24); check(J.rowCount=3); @@ -17616,7 +17907,7 @@ begin i1 := PosEx(',"CHANNEL":132',s); i2 := PosEx('}',s,i1); delete(s,i1,i2-i1); // truncate the 2nd object - J := TSQLTableJSON.Create('',s); + J := TSQLTableJson.Create('',s); try check(J.fieldCount=24); if not checkfailed(J.rowCount=3) then diff --git a/contrib/mORMot/SynTable.pas b/contrib/mORMot/SynTable.pas index e479e1b..7779730 100644 --- a/contrib/mORMot/SynTable.pas +++ b/contrib/mORMot/SynTable.pas @@ -6,7 +6,7 @@ unit SynTable; (* This file is part of Synopse framework. - Synopse framework. Copyright (C) 2020 Arnaud Bouchez + Synopse framework. Copyright (C) 2022 Arnaud Bouchez Synopse Informatique - https://synopse.info *** BEGIN LICENSE BLOCK ***** @@ -25,7 +25,7 @@ unit SynTable; The Initial Developer of the Original Code is Arnaud Bouchez. - Portions created by the Initial Developer are Copyright (C) 2020 + Portions created by the Initial Developer are Copyright (C) 2022 the Initial Developer. All Rights Reserved. Contributor(s): @@ -308,6 +308,26 @@ const // compute up to 7 soundex chars in a cardinal (that's our choice) SOUNDEX_BITS = 4; +var + DoIsValidUTF8: function(source: PUTF8Char): Boolean; + DoIsValidUTF8Len: function(source: PUTF8Char; sourcelen: PtrInt): Boolean; + +/// returns TRUE if the supplied buffer has valid UTF-8 encoding +// - will stop when the buffer contains #0 +// - on Haswell AVX2 Intel/AMD CPUs, will use very efficient ASM +function IsValidUTF8(source: PUTF8Char): Boolean; overload; {$ifdef HASINLINE}inline;{$endif} + +/// returns TRUE if the supplied buffer has valid UTF-8 encoding +// - will also refuse #0 characters within the buffer +// - on Haswell AVX2 Intel/AMD CPUs, will use very efficient ASM +function IsValidUTF8(source: PUTF8Char; sourcelen: PtrInt): Boolean; overload; {$ifdef HASINLINE}inline;{$endif} + +/// returns TRUE if the supplied buffer has valid UTF-8 encoding +// - will also refuse #0 characters within the buffer +// - on Haswell AVX2 Intel/AMD CPUs, will use very efficient ASM +function IsValidUTF8(const source: RawUTF8): Boolean; overload; + + { ************ filtering and validation classes and functions ************** } @@ -698,6 +718,20 @@ type property UTF8Length: boolean read fUTF8Length write fUTF8Length; end; +resourcestring + sInvalidIPAddress = '"%s" is an invalid IP v4 address'; + sInvalidEmailAddress = '"%s" is an invalid email address'; + sInvalidPattern = '"%s" does not match the expected pattern'; + sCharacter01n = 'character,character,characters'; + sInvalidTextLengthMin = 'Expect at least %d %s'; + sInvalidTextLengthMax = 'Expect up to %d %s'; + sInvalidTextChar = 'Expect at least %d %s %s,Expect up to %d %s %s,'+ + 'alphabetical,digital,punctuation,lowercase,uppercase,space,'+ + 'Too much spaces on the left,Too much spaces on the right'; + sValidationFailed = '"%s" rule failed'; + sValidationFieldVoid = 'An unique key field must not be void'; + sValidationFieldDuplicate = 'Value already used for this unique key field'; + { ************ Database types and classes ************************** } @@ -3870,7 +3904,7 @@ type function SaveToBuffer: RawByteString; /// retrieve the time bias (in minutes) for a given date/time on a TzId function GetBiasForDateTime(const Value: TDateTime; const TzId: TTimeZoneID; - out Bias: integer; out HaveDaylight: boolean): boolean; + out Bias: integer; out HaveDaylight: boolean; DateIsUTC: boolean=false): boolean; /// retrieve the display text corresponding to a TzId // - returns '' if the supplied TzId is not recognized function GetDisplay(const TzId: TTimeZoneID): RawUTF8; @@ -7694,7 +7728,7 @@ lim2: if IdemPropNameU(Prop,'LIMIT') then end else exit; // incorrect SQL statement end else - if Prop<>'' then + if (Prop<>'') or not(GotoNextNotSpace(P)^ in [#0, ';']) then exit else // incorrect SQL statement break; // reached the end of the statement end; @@ -9101,6 +9135,312 @@ begin next^ := FindNextUTF8WordBegin(U); end; +{$ifdef ASMX64AVX} // AVX2 ASM not available on Delphi yet +// adapted from https://github.com/simdjson/simdjson - Apache License 2.0 +function IsValidUtf8LenAvx2(source: PUtf8Char; sourcelen: PtrInt): boolean; + {$ifdef FPC}nostackframe; assembler; asm {$else} asm .noframe {$endif FPC} + test source, source + jz @ok + test sourcelen, sourcelen + jle @ok + {$ifdef win64} // this ABI doesn't consider rsi/rdi as volatile + push rsi + push rdi + {$endif} + push rbp + mov r8, source + mov rdx, sourcelen + mov rsi, r8 + mov ecx, 64 + mov rax, rsi + mov rdi, rdx + mov rbp, rsp + and rsp, 0FFFFFFFFFFFFFFE0H // align stack at 32 bytes + sub rsp, 160 + cmp rdx, 64 + cmovnc rcx, rdx + sub rcx, 64 + je @small + vpxor xmm3, xmm3, xmm3 + vmovdqa ymm7, ymmword ptr [rip + @0f] + vmovdqa ymm15, ymmword ptr [rip + @_6] + xor esi, esi + vmovdqa ymm14, ymmword ptr [rip + @_7] + vmovdqa ymm13, ymmword ptr [rip + @_8] + vmovdqa ymm5, ymm3 + vmovdqa ymm2, ymm3 + // main processing loop, 64 bytes per iteration + align 16 +@loop: vmovdqu xmm6, xmmword ptr [rax + rsi] + vinserti128 ymm0, ymm6, xmmword ptr [rax + rsi + 10H], 01H + vmovdqu xmm6, xmmword ptr [rax + rsi + 20H] + vinserti128 ymm1, ymm6, xmmword ptr [rax + rsi + 30H], 01H + add rsi, 64 + vpor ymm4, ymm1, ymm0 + vpmovmskb rdx, ymm4 // check set MSB of each 64 bytes + test edx, edx + jne @check + vpor ymm2, ymm5, ymm2 + vmovdqa ymm4, ymm2 + cmp rcx, rsi + ja @loop + // process trailing 0..63 bytes +@trail: sub rdi, rsi + jz @ended + add rsi, rax + vmovdqa xmm0, xmmword ptr [rip + @20] + lea rdx, qword ptr [rsp + 60H] // copy on stack with space padding + sub rsi, rdx + vmovdqa xmmword ptr [rdx], xmm0 + vmovdqa xmmword ptr [rdx + 10H], xmm0 + vmovdqa xmmword ptr [rdx + 20H], xmm0 + vmovdqa xmmword ptr [rdx + 30H], xmm0 +@by8: sub rdi, 8 + jb @by1 + mov rax, qword ptr [rsi + rdx] + mov qword ptr [rdx], rax + add rdx, 8 // in-order copy to preserve UTF-8 encoding + jmp @by8 +@by1: add rdi, 8 + jz @0 +@sml: mov al, byte ptr [rsi + rdx] + mov byte ptr [rdx], al + add rdx, 1 + sub rdi, 1 + jnz @sml +@0: vmovdqa ymm1, ymmword ptr [rsp + 60H] + vmovdqa ymm2, ymmword ptr [rsp + 80H] + vpor ymm0, ymm1, ymm2 + vpmovmskb rax, ymm0 // check any set MSB + test eax, eax + jne @last +@ended: vpor ymm5, ymm5, ymm4 +@final: vptest ymm5, ymm5 + sete al + vzeroupper + leave // mov rsp,rbp + pop rbp + {$ifdef win64} + pop rdi + pop rsi + {$endif} + ret +@ok: mov al, 1 + ret +@small: vpxor xmm4, xmm4, xmm4 + xor esi, esi + vmovdqa ymm3, ymm4 + vmovdqa ymm5, ymm4 + jmp @trail + // validate UTF-8 extra bytes from main loop + align 8 +@check: vpsrlw ymm9, ymm0, 4 + vpsrlw ymm12, ymm1, 4 + vperm2i128 ymm3, ymm3, ymm0, 21H + vpalignr ymm5, ymm0, ymm3, 0FH + vpalignr ymm11, ymm0, ymm3, 0EH + vpsubusb ymm11, ymm11, ymmword ptr [rip + @_9] + vpalignr ymm3, ymm0, ymm3, 0DH + vperm2i128 ymm0, ymm0, ymm1, 21H + vpsubusb ymm3, ymm3, ymmword ptr [rip + @_10] + vpalignr ymm8, ymm1, ymm0, 0FH + vpsrlw ymm10, ymm5, 4 + vpand ymm5, ymm7, ymm5 + vpsrlw ymm6, ymm8, 4 + vpalignr ymm4, ymm1, ymm0, 0EH + vpsubusb ymm4, ymm4, ymmword ptr [rip + @_9] + vpalignr ymm0, ymm1, ymm0, 0DH + vpsubusb ymm0, ymm0, ymmword ptr [rip + @_10] + vpand ymm10, ymm10, ymm7 + vpand ymm6, ymm6, ymm7 + vpand ymm8, ymm7, ymm8 + vpor ymm3, ymm3, ymm11 + vpor ymm0, ymm4, ymm0 + vpxor xmm11, xmm11, xmm11 + vpshufb ymm10, ymm15, ymm10 + vpshufb ymm5, ymm14, ymm5 + vpand ymm9, ymm9, ymm7 + vpshufb ymm6, ymm15, ymm6 + vpshufb ymm8, ymm14, ymm8 + vpand ymm12, ymm12, ymm7 + vpand ymm5, ymm5, ymm10 + vpcmpgtb ymm3, ymm3, ymm11 + vpcmpgtb ymm0, ymm0, ymm11 + vpshufb ymm9, ymm13, ymm9 + vpand ymm3, ymm3, ymmword ptr [rip + @_11] + vpand ymm0, ymm0, ymmword ptr [rip + @_11] + vpshufb ymm12, ymm13, ymm12 + vpand ymm6, ymm6, ymm8 + vpand ymm9, ymm5, ymm9 + vpsubusb ymm5, ymm1, ymmword ptr [rip + @_12] + vpand ymm12, ymm6, ymm12 + vpxor ymm9, ymm3, ymm9 + vmovdqa ymm3, ymm1 + vpxor ymm12, ymm0, ymm12 + vpor ymm9, ymm9, ymm12 + vpor ymm2, ymm9, ymm2 + vmovdqa ymm4, ymm2 + cmp rcx, rsi + ja @loop + jmp @trail + // validate UTF-8 extra bytes from input ending + align 8 +@last: vmovdqa ymm5, ymmword ptr [rip + @0f] + vperm2i128 ymm3, ymm3, ymm1, 21H + vmovdqa ymm9, ymmword ptr [rip + @_7] + vpsrlw ymm11, ymm1, 4 + vpalignr ymm0, ymm1, ymm3, 0FH + vmovdqa ymm13, ymmword ptr [rip + @_10] + vmovdqa ymm14, ymmword ptr [rip + @_9] + vpsrlw ymm6, ymm0, 4 + vpand ymm0, ymm5, ymm0 + vpand ymm11, ymm11, ymm5 + vmovdqa ymm7, ymmword ptr [rip + @_6] + vpshufb ymm10, ymm9, ymm0 + vpalignr ymm0, ymm1, ymm3, 0EH + vpand ymm6, ymm6, ymm5 + vmovdqa ymm8, ymmword ptr [rip + @_8] + vpalignr ymm3, ymm1, ymm3, 0DH + vperm2i128 ymm1, ymm1, ymm2, 21H + vpsubusb ymm0, ymm0, ymm14 + vpsubusb ymm12, ymm3, ymm13 + vpalignr ymm3, ymm2, ymm1, 0FH + vpshufb ymm6, ymm7, ymm6 + vpsrlw ymm15, ymm3, 4 + vpand ymm3, ymm5, ymm3 + vpor ymm0, ymm0, ymm12 + vpshufb ymm9, ymm9, ymm3 + vpsrlw ymm3, ymm2, 4 + vpand ymm15, ymm15, ymm5 + vpand ymm5, ymm3, ymm5 + vpalignr ymm3, ymm2, ymm1, 0EH + vpxor xmm12, xmm12, xmm12 + vpalignr ymm1, ymm2, ymm1, 0DH + vpsubusb ymm3, ymm3, ymm14 + vpshufb ymm11, ymm8, ymm11 + vpsubusb ymm1, ymm1, ymm13 + vpcmpgtb ymm0, ymm0, ymm12 + vpshufb ymm7, ymm7, ymm15 + vpor ymm1, ymm3, ymm1 + vpshufb ymm8, ymm8, ymm5 + vpsubusb ymm5, ymm2, ymmword ptr [rip + @_12] + vmovdqa ymm2, ymmword ptr [rip + @_11] + vpcmpgtb ymm1, ymm1, ymm12 + vpand ymm6, ymm6, ymm10 + vpand ymm7, ymm7, ymm9 + vpand ymm0, ymm0, ymm2 + vpand ymm11, ymm6, ymm11 + vpand ymm8, ymm7, ymm8 + vpxor ymm0, ymm0, ymm11 + vpor ymm5, ymm4, ymm5 + vpand ymm1, ymm1, ymm2 + vpxor ymm1, ymm1, ymm8 + vpor ymm0, ymm0, ymm1 + vpor ymm5, ymm0, ymm5 + jmp @final + align 16 +@20: dq 2020202020202020H + dq 2020202020202020H + align 32 +@0f: dq 0F0F0F0F0F0F0F0FH + dq 0F0F0F0F0F0F0F0FH + dq 0F0F0F0F0F0F0F0FH + dq 0F0F0F0F0F0F0F0FH +@_6: dq 0202020202020202H + dq 4915012180808080H + dq 0202020202020202H + dq 4915012180808080H +@_7: dq 0CBCBCB8B8383A3E7H + dq 0CBCBDBCBCBCBCBCBH + dq 0CBCBCB8B8383A3E7H + dq 0CBCBDBCBCBCBCBCBH +@_8: dq 0101010101010101H + dq 01010101BABAAEE6H + dq 0101010101010101H + dq 01010101BABAAEE6H +@_9: dq 0DFDFDFDFDFDFDFDFH + dq 0DFDFDFDFDFDFDFDFH + dq 0DFDFDFDFDFDFDFDFH + dq 0DFDFDFDFDFDFDFDFH +@_10: dq 0EFEFEFEFEFEFEFEFH + dq 0EFEFEFEFEFEFEFEFH + dq 0EFEFEFEFEFEFEFEFH + dq 0EFEFEFEFEFEFEFEFH +@_11: dq 8080808080808080H + dq 8080808080808080H + dq 8080808080808080H + dq 8080808080808080H +@_12: db 0FFH, 0FFH, 0FFH, 0FFH, 0FFH, 0FFH, 0FFH, 0FFH + db 0FFH, 0FFH, 0FFH, 0FFH, 0FFH, 0FFH, 0FFH, 0FFH + db 0FFH, 0FFH, 0FFH, 0FFH, 0FFH, 0FFH, 0FFH, 0FFH + db 0FFH, 0FFH, 0FFH, 0FFH, 0FFH, 0EFH, 0DFH, 0BFH +end; + +function IsValidUTF8Avx2(source: PUTF8Char): Boolean; +begin + result := IsValidUTF8LenAvx2(source,StrLen(source)); +end; +{$endif ASMX64AVX} + +function IsValidUTF8Pas(source: PUTF8Char): Boolean; +var extra, i: integer; + c: cardinal; +begin + result := false; + if source<>nil then + repeat + c := byte(source^); + inc(source); + if c=0 then break else + if c and $80<>0 then begin + extra := UTF8_EXTRABYTES[c]; + if extra=0 then exit else // invalid leading byte + for i := 1 to extra do + if byte(source^) and $c0<>$80 then + exit else + inc(source); // check valid UTF-8 content + end; + until false; + result := true; +end; + +function IsValidUTF8LenPas(source: PUTF8Char; sourcelen: PtrInt): Boolean; +var extra, i: integer; + c: cardinal; +begin + result := false; + inc(sourcelen,PtrInt(source)); + if source<>nil then + while PtrInt(PtrUInt(source))0 then begin + extra := UTF8_EXTRABYTES[c]; + if extra=0 then exit else // invalid leading byte + for i := 1 to extra do + if (PtrInt(PtrUInt(source))>=sourcelen) or (byte(source^) and $c0<>$80) then + exit else + inc(source); // check valid UTF-8 content + end; + end; + result := true; +end; + +function IsValidUTF8(source: PUTF8Char): Boolean; +begin + result := DoIsValidUTF8(source); +end; + +function IsValidUTF8(source: PUTF8Char; sourcelen: PtrInt): Boolean; +begin + result := DoIsValidUTF8Len(source,sourcelen); +end; + +function IsValidUTF8(const source: RawUTF8): Boolean; +begin + result := DoIsValidUTF8Len(pointer(Source),length(Source)); +end; + { ************ filtering and validation classes and functions *************** } @@ -11173,7 +11513,7 @@ var s: TStream; begin if Append and FileExists(aFileName) then begin s := TFileStream.Create(aFileName,fmOpenWrite); - s.Seek(0,soFromEnd); + s.Seek(0,soEnd); end else s := TFileStream.Create(aFileName,fmCreate); Create(s,BufLen); @@ -11930,7 +12270,7 @@ begin end else // file bigger than 2 GB: slower but accurate reading from file if Data=nil then begin - FileSeek(fMap.FileHandle,soFromCurrent,DataLen); + FileSeek64(fMap.FileHandle,DataLen,soFromCurrent); result := DataLen; end else result := FileRead(fMap.FileHandle,Data^,DataLen) else @@ -12346,7 +12686,7 @@ begin if result=0 then exit; count := result; - if count>length(Values) then // only set length is not big enough + if count>length(Values) then // change Values[] length only if not big enough SetLength(Values,count); PI := pointer(Values); fixedsize := ReadVarUInt32; @@ -16626,7 +16966,7 @@ var i: integer; if not withfreespace or not GetDiskInfo(p.mounted,av,fr,tot) then {$ifdef MSWINDOWS} FormatShort('%: % (%)',[p.mounted[1],p.name,KB(p.size,nospace)],result) else - FormatShort(F[nospace],[p.mounted[1],p.name,KB(p.size,nospace)],result); + FormatShort(F[nospace],[p.mounted[1],p.name,KB(fr,nospace),KB(tot,nospace)],result); {$else} FormatShort('% % (%)',[p.mounted,p.name,KB(p.size,nospace)],result) else FormatShort(F[nospace],[p.mounted,p.name,KB(fr,nospace),KB(tot,nospace)],result); @@ -17182,7 +17522,8 @@ begin end; function TSynTimeZone.GetBiasForDateTime(const Value: TDateTime; - const TzId: TTimeZoneID; out Bias: integer; out HaveDaylight: boolean): boolean; + const TzId: TTimeZoneID; out Bias: integer; out HaveDaylight: boolean; + DateIsUTC: boolean): boolean; var ndx: integer; d: TSynSystemTime; tzi: PTimeZoneInfo; @@ -17211,6 +17552,10 @@ begin HaveDaylight := true; std := tzi.change_time_std.EncodeForTimeChange(d.Year); dlt := tzi.change_time_dlt.EncodeForTimeChange(d.Year); + if DateIsUTC then begin // std shifts by the DST bias, dst by STD + std := ((std*MinsPerDay)+tzi.Bias+tzi.bias_dlt)/MinsPerDay; + dlt := ((dlt*MinsPerDay)+tzi.Bias+tzi.bias_std)/MinsPerDay; + end; if std TLSRECMAXSIZE then raise ESChannel.CreateFmt('InputSize=%d>%d', [InputSize, TLSRECMAXSIZE]); @@ -1720,36 +1723,42 @@ end; procedure TSChannelClient.HandshakeLoop(aSocket: THandle); var buf: THandshakeBuf; - len: integer; - tmp: AnsiString; res, f: cardinal; begin - len := 0; - SetLength(tmp, 65536); res := SEC_I_CONTINUE_NEEDED; while (res = SEC_I_CONTINUE_NEEDED) or (res = SEC_E_INCOMPLETE_MESSAGE) do begin - if res <> SEC_E_INCOMPLETE_MESSAGE then - len := 0; - inc(len, CheckSocket(Recv(aSocket, @PByteArray(tmp)[len], length(tmp) - len, 0))); + inc(DataCount, CheckSocket(Recv(aSocket, + @PByteArray(Data)[DataCount], length(Data) - DataCount, 0))); buf.Init; - buf.buf[0].cbBuffer := len; + buf.buf[0].cbBuffer := DataCount; buf.buf[0].BufferType := SECBUFFER_TOKEN; - buf.buf[0].pvBuffer := pointer(tmp); + buf.buf[0].pvBuffer := pointer(Data); res := InitializeSecurityContext(@Cred, @Ctxt, nil, ISC_REQ_FLAGS, 0, SECURITY_NATIVE_DREP, @buf.input, 0, @Ctxt, @buf.output, @f, nil); + if res = SEC_I_INCOMPLETE_CREDENTIALS then + // check https://stackoverflow.com/a/47479968/458259 + res := InitializeSecurityContext(@Cred, @Ctxt, nil, ISC_REQ_FLAGS, 0, + SECURITY_NATIVE_DREP, @buf.input, 0, @Ctxt, @buf.output, @f, nil); if (res = SEC_E_OK) or (res = SEC_I_CONTINUE_NEEDED) or ((f and ISC_REQ_EXTENDED_ERROR) <> 0) then begin if (buf.buf[2].cbBuffer <> 0) and (buf.buf[2].pvBuffer <> nil) then begin - CheckSocket(SynWinSock.Send(aSocket, buf.buf[2].pvBuffer, buf.buf[2].cbBuffer, 0)); + CheckSocket( + SynWinSock.Send(aSocket, buf.buf[2].pvBuffer, buf.buf[2].cbBuffer, 0)); CheckSEC_E_OK(FreeContextBuffer(buf.buf[2].pvBuffer)); end; end; + if buf.buf[1].BufferType = SECBUFFER_EXTRA then begin + // reuse pending Data bytes to avoid SEC_E_INVALID_TOKEN + Move(PByteArray(Data)[cardinal(DataCount) - buf.buf[1].cbBuffer], + PByteArray(Data)[0], buf.buf[1].cbBuffer); + DataCount := buf.buf[1].cbBuffer; + end else + if res <> SEC_E_INCOMPLETE_MESSAGE then + DataCount := 0; end; // TODO: handle SEC_I_INCOMPLETE_CREDENTIALS ? // see https://github.com/curl/curl/blob/master/lib/vtls/schannel.c CheckSEC_E_OK(res); - if buf.buf[1].BufferType = SECBUFFER_EXTRA then - AppendData(buf.buf[1]); end; procedure TSChannelClient.BeforeDisconnection(aSocket: THandle); @@ -1788,7 +1797,8 @@ begin end; end; -function TSChannelClient.Receive(aSocket: THandle; aBuffer: pointer; aLength: integer): integer; +function TSChannelClient.Receive(aSocket: THandle; + aBuffer: pointer; aLength: integer): integer; var desc: TSecBufferDesc; buf: array[0..3] of TSecBuffer; @@ -1817,47 +1827,49 @@ begin exit; end; result := 0; - while DataCount = 0 do - try - DataPos := 0; - desc.ulVersion := SECBUFFER_VERSION; - desc.cBuffers := 4; - desc.pBuffers := @buf[0]; - repeat - read := Recv(aSocket, @PByteArray(Input)[InputCount], InputSize - InputCount, MSG_NOSIGNAL); - if read <= 0 then begin - result := read; // return socket error (may be WSATRY_AGAIN) - exit; - end; - inc(InputCount, read); - res := DecryptInput; - until res <> SEC_E_INCOMPLETE_MESSAGE; - needsRenegotiate := false; - repeat - case res of - SEC_I_RENEGOTIATE: needsRenegotiate := true; - SEC_I_CONTEXT_EXPIRED: exit; - SEC_E_INCOMPLETE_MESSAGE: break; - else CheckSEC_E_OK(res); - end; - InputCount := 0; - for i := 1 to 3 do - case buf[i].BufferType of - SECBUFFER_DATA: AppendData(buf[i]); - SECBUFFER_EXTRA: begin - Move(buf[i].pvBuffer^, pointer(Input)^, buf[i].cbBuffer); - InputCount := buf[i].cbBuffer; - end; + if not SessionClosed then + while DataCount = 0 do + try + DataPos := 0; + desc.ulVersion := SECBUFFER_VERSION; + desc.cBuffers := 4; + desc.pBuffers := @buf[0]; + repeat + read := Recv(aSocket, @PByteArray(Input)[InputCount], + InputSize - InputCount, MSG_NOSIGNAL); + if read <= 0 then begin + result := read; // return socket error (may be WSATRY_AGAIN) + exit; end; - if InputCount = 0 then - break; - res := DecryptInput; - until false; - if needsRenegotiate then - HandshakeLoop(aSocket); - except - exit; // shutdown the connection on ESChannel fatal error - end; + inc(InputCount, read); + res := DecryptInput; + until res <> SEC_E_INCOMPLETE_MESSAGE; + needsRenegotiate := false; + repeat + case res of + SEC_I_RENEGOTIATE: needsRenegotiate := true; + SEC_I_CONTEXT_EXPIRED: SessionClosed := true; + SEC_E_INCOMPLETE_MESSAGE: break; + else CheckSEC_E_OK(res); + end; + InputCount := 0; + for i := 1 to 3 do + case buf[i].BufferType of + SECBUFFER_DATA: AppendData(buf[i]); + SECBUFFER_EXTRA: begin + Move(buf[i].pvBuffer^, pointer(Input)^, buf[i].cbBuffer); + InputCount := buf[i].cbBuffer; + end; + end; + if InputCount = 0 then + break; + res := DecryptInput; + until false; + if needsRenegotiate then + HandshakeLoop(aSocket); + except + exit; // shutdown the connection on ESChannel fatal error + end; result := DataCount; if aLength < result then result := aLength; @@ -1914,7 +1926,7 @@ begin exit; // report connection closed if integer(s) < 0 then begin res := WSAGetLastError; - if (res <> WSATRY_AGAIN) and (res <> WSAEINTR) then begin + if res <> WSATRY_AGAIN then begin result := s; exit; // report socket fatal error end; diff --git a/contrib/mORMot/SynZLibSSE.pas b/contrib/mORMot/SynZLibSSE.pas index 5386e05..350159c 100644 --- a/contrib/mORMot/SynZLibSSE.pas +++ b/contrib/mORMot/SynZLibSSE.pas @@ -21,7 +21,7 @@ end. // put as reference all deprecated code below This file is part of Synopse framework. - Synopse framework. Copyright (C) 2020 Arnaud Bouchez + Synopse framework. Copyright (C) 2022 Arnaud Bouchez Synopse Informatique - https://synopse.info *** BEGIN LICENSE BLOCK ***** @@ -40,7 +40,7 @@ end. // put as reference all deprecated code below The Initial Developer of the Original Code is Arnaud Bouchez. - Portions created by the Initial Developer are Copyright (C) 2020 + Portions created by the Initial Developer are Copyright (C) 2022 the Initial Developer. All Rights Reserved. Contributor(s): diff --git a/contrib/mORMot/SynZip.pas b/contrib/mORMot/SynZip.pas index be68d37..a2396dc 100644 --- a/contrib/mORMot/SynZip.pas +++ b/contrib/mORMot/SynZip.pas @@ -6,7 +6,7 @@ unit SynZip; { This file is part of Synopse framework. - Synopse framework. Copyright (C) 2020 Arnaud Bouchez + Synopse framework. Copyright (C) 2022 Arnaud Bouchez Synopse Informatique - https://synopse.info *** BEGIN LICENSE BLOCK ***** @@ -25,11 +25,12 @@ unit SynZip; The Initial Developer of the Original Code is Arnaud Bouchez. - Portions created by the Initial Developer are Copyright (C) 2020 + Portions created by the Initial Developer are Copyright (C) 2022 the Initial Developer. All Rights Reserved. Contributor(s): - Alf + - Andre Heider (dhewg) - ehansen - jpdk - Gigo @@ -389,7 +390,7 @@ type TFileHeader = {$ifdef USERECORDWITHMETHODS}record {$else}object{$endif} signature : dword; // $02014b50 PK#1#2 - madeBy : word; // $14 + madeBy : word; // $0314 = OS + version fileInfo : TFileInfo; commentLen : word; // 0 firstDiskNo : word; // 0 @@ -692,6 +693,8 @@ type end; /// abstract write-only access for creating a .zip archive + // - update can be done manualy by using a TZipRead instance and the + // AddFromZip() method TZipWriteAbstract = class protected fAppendOffset: cardinal; @@ -723,6 +726,8 @@ type // - by default, the 1st of January, 2010 is used if not date is supplied procedure AddStored(const aZipName: TFileName; Buf: pointer; Size: integer; FileAge: integer=1+1 shl 5+30 shl 9); + /// add a file from an already compressed zip entry + procedure AddFromZip(Source: TZipRead; EntryIndex: integer); /// append a file content into the destination file // - useful to add the initial Setup.exe file, e.g. procedure Append(const Content: ZipString); @@ -732,8 +737,6 @@ type /// write-only access for creating a .zip archive file // - not to be used to update a .zip file, but to create a new one - // - update can be done manualy by using a TZipRead instance and the - // AddFromZip() method TZipWrite = class(TZipWriteAbstract) protected fFileName: TFileName; @@ -759,8 +762,6 @@ type // - if Recursive is TRUE, would include files from nested sub-folders procedure AddFolder(const FolderName: TFileName; const Mask: TFileName=ZIP_FILES_ALL; Recursive: boolean=true; CompressLevel: integer=6); - /// add a file from an already compressed zip entry - procedure AddFromZip(const ZipEntry: TZipEntry); /// release associated memory, and close destination file destructor Destroy; override; end; @@ -864,7 +865,7 @@ begin with Entry[Count] do begin fHr.signature := ENTRY_SIGNATURE_INC; // +1 to avoid finding it in the exe dec(fHr.signature); - fHr.madeBy := $14; + fHr.madeBy := $0314; // where $03=Unix (for proper UTF8 filenames) and $14=version fHr.fileInfo.neededVersion := $14; result := InternalWritePosition; fHr.localHeadOff := result-fAppendOffset; @@ -885,6 +886,7 @@ begin end; {$endif} fHr.fileInfo.nameLen := length(intName); + fhr.fileInfo.extraLen := 0; // source may have something here InternalWrite(fMagic,sizeof(fMagic)); InternalWrite(fhr.fileInfo,sizeof(fhr.fileInfo)); InternalWrite(pointer(intName)^,fhr.fileInfo.nameLen); @@ -913,7 +915,7 @@ begin tmpsize := (Int64(Size)*11) div 10+12; Getmem(tmp,tmpSize); zzipSize := CompressMem(Buf,tmp,Size,tmpSize,CompressLevel); - InternalAdd(aZipName,tmp,zzipSize); // write stored data + InternalAdd(aZipName,tmp,zzipSize); // write deflated data and inc(Count) Freemem(tmp); end; end; @@ -931,10 +933,29 @@ begin zfullSize := Size; zzipSize := Size; zlastMod := FileAge; - InternalAdd(aZipName,Buf,Size); + InternalAdd(aZipName,Buf,Size); // write stored data and inc(Count) end; end; +procedure TZipWriteAbstract.AddFromZip(Source: TZipRead; EntryIndex: integer); +var s: ^TZipEntry; + origZipName: TFileName; +begin + if (self=nil) or (Source=nil) then + exit; + if Count>=length(Entry) then + SetLength(Entry,length(Entry)+20); + with Entry[Count] do + if Source.RetrieveFileInfo(EntryIndex, fhr.FileInfo) then begin + s := @Source.Entry[EntryIndex]; + // backslash in s^.storedName are replaced to '\' by TZipRead.Create, + // accoding to ZIP file format "All slashes MUST be forward slashes '/' as opposed to + // backwards slashes '\'" + SetString(origZipName,s^.storedName,s^.infoLocal.nameLen); + InternalAdd(origZipName,s^.data,fhr.fileInfo.zzipSize); + end; +end; + procedure TZipWriteAbstract.Append(const Content: ZipString); begin if (self=nil) or (fAppendOffset<>0) then @@ -1040,7 +1061,7 @@ begin raise ESynZipException.CreateFmt('%s file too big for .zip',[aFileName]); if Count>=length(Entry) then SetLength(Entry,length(Entry)+20); - OffsHead := InternalAdd(ZipName,nil,0); + OffsHead := InternalAdd(ZipName,nil,0); // write data and inc(Count) D := THandleStream.Create(Handle); Z := TSynZipCompressor.Create(D,CompressLevel); try @@ -1074,18 +1095,6 @@ begin end; end; -procedure TZipWrite.AddFromZip(const ZipEntry: TZipEntry); -begin - if (self=nil) or (Handle<=0) then - exit; - if Count>=length(Entry) then - SetLength(Entry,length(Entry)+20); - with Entry[Count] do begin - fhr.fileInfo := ZipEntry.infoLocal^; - InternalAdd(ZipEntry.zipName,ZipEntry.data,fhr.fileInfo.zzipSize); - end; -end; - constructor TZipWrite.Create(const aFileName: TFileName); begin Create; @@ -1223,7 +1232,7 @@ begin // UnZip() will call RetrieveFileInfo() end else if (zzipSize=cardinal(-1)) or (zfullSize=cardinal(-1)) then - raise ESynZipException.Create('ZIP64 format not supported'); + raise ESynZipException.Create('ZIP64 format not supported - use mORMot 2'); with Entry[Count] do begin infoLocal := @lfhr^.fileInfo; infoDirectory := H; @@ -5028,6 +5037,26 @@ begin Z_DEFAULT_STRATEGY, ZLIB_VERSION, sizeof(Stream))>=0 end; +function ZLibError(Code: integer): shortstring; +begin + case Code of + Z_ERRNO: + result := 'Z_ERRNO'; + Z_STREAM_ERROR: + result := 'Z_STREAM_ERROR'; + Z_DATA_ERROR: + result := 'Z_DATA_ERROR'; + Z_MEM_ERROR: + result := 'Z_MEM_ERROR'; + Z_BUF_ERROR: + result := 'Z_BUF_ERROR'; + Z_VERSION_ERROR: + result := 'Z_VERSION_ERROR'; + else + str(Code,result); + end; +end; + function Check(const Code: Integer; const ValidCodes: array of Integer; const Context: string): integer; var i: Integer; @@ -5038,7 +5067,7 @@ begin for i := Low(ValidCodes) to High(ValidCodes) do if ValidCodes[i]=Code then Exit; - raise ESynZipException.CreateFmt('Error %d during %s process',[Code,Context]); + raise ESynZipException.CreateFmt('Error %s during %s process',[ZLibError(Code),Context]); end; function CompressString(const data: ZipString; failIfGrow: boolean = false; @@ -5195,6 +5224,8 @@ begin try repeat code := Check(inflate(strm, Z_FINISH),[Z_OK,Z_STREAM_END,Z_BUF_ERROR],'UnCompressStream'); + if (code=Z_BUF_ERROR) and (TempBufSize=integer(strm.avail_out)) then + Check(code,[],'UnCompressStream'); // occur on invalid input FlushBuf; until code=Z_STREAM_END; FlushBuf; diff --git a/contrib/mORMot/SynZipFiles.pas b/contrib/mORMot/SynZipFiles.pas index 607887f..6a0bdc0 100644 --- a/contrib/mORMot/SynZipFiles.pas +++ b/contrib/mORMot/SynZipFiles.pas @@ -6,7 +6,7 @@ unit SynZipFiles; (* This file is part of Synopse framework. - Synopse framework. Copyright (C) 2020 Arnaud Bouchez + Synopse framework. Copyright (C) 2022 Arnaud Bouchez Synopse Informatique - https://synopse.info *** BEGIN LICENSE BLOCK ***** @@ -25,7 +25,7 @@ unit SynZipFiles; The Initial Developer of the Original Code is Arnaud Bouchez. - Portions created by the Initial Developer are Copyright (C) 2020 + Portions created by the Initial Developer are Copyright (C) 2022 the Initial Developer. All Rights Reserved. Contributor(s): @@ -168,7 +168,7 @@ type fAlgorithmStream: THeapMemoryStream; fAlgorithmID: integer; // =0 if not Assigned(fAlgorithm) fCRC: Cardinal; - fBlobDataHeaderPosition: integer; + fBlobDataHeaderPosition: Int64; fBufferIn, fBufferOut: array[word] of byte; // two 64kb buffers procedure Finish; function FlushBufferOut: integer; @@ -910,12 +910,12 @@ constructor TZipCompressor.CreateAsBlobData(outStream: TStream; CompressionLevel, Algorithm: Integer); begin Create(outStream,CompressionLevel,Algorithm); - fBlobDataHeaderPosition := outStream.Seek(0,soFromCurrent); + fBlobDataHeaderPosition := outStream.Seek(0,soCurrent); outStream.WriteBuffer(FBufferOut,BLOBDATA_HEADSIZE); // save Bulk header end; destructor TZipCompressor.Destroy; -var p: integer; +var p: Int64; blob: TBlobData; begin if FInitialized then begin @@ -927,7 +927,7 @@ begin FreeAndNil(fAlgorithm); end; if fBlobDataHeaderPosition>=0 then begin // CreateAsBlobData() -> update header - p := fDestStream.Seek(0,soFromCurrent); + p := fDestStream.Seek(0,soCurrent); with blob do begin dataFullSize := SizeIn; dataSize := p-fBlobDataHeaderPosition-BLOBDATA_HEADSIZE; @@ -938,9 +938,9 @@ begin dataMethod := 8 else dataMethod := fAlgorithmID shl 4; // stored + AlgoID end; - fDestStream.Seek(fBlobDataHeaderPosition,soFromBeginning); + fDestStream.Seek(fBlobDataHeaderPosition,soBeginning); fDestStream.WriteBuffer(blob,BLOBDATA_HEADSIZE); - fDestStream.Seek(p,soFromBeginning); + fDestStream.Seek(p,soBeginning); end; inherited; end; @@ -952,7 +952,7 @@ begin if assigned(fAlgorithmStream) then begin fAlgorithmStream.WriteBuffer(fBufferIn,fStrm.avail_in); // write pending data fStrm.total_in := fAlgorithm.Compress( // compress whole data at once - fAlgorithmStream.Memory,fAlgorithmStream.Seek(0,soFromCurrent),@fCRC); + fAlgorithmStream.Memory,fAlgorithmStream.Seek(0,soCurrent),@fCRC); end else if FStrm.avail_in>0 then inc(fStrm.total_in,fAlgorithm.Compress(@fBufferIn,FStrm.avail_in,@fCRC)); @@ -1291,7 +1291,7 @@ begin outFile := TFileStream.Create(fFileName,fmCreate); if fromStream.InheritsFrom(TMemoryStream) then fromMemory := PAnsiChar(TMemoryStream(fromStream).Memory)+ - fromStream.Seek(0,soFromCurrent) else + fromStream.Seek(0,soCurrent) else fromMemory := nil; srcLen := 0; src := nil; @@ -1363,7 +1363,7 @@ begin thisFiles := Count; totalFiles := Count; headerSize := 0; - headerOffset := outFile.seek(0,soFromCurrent); // position of file entries + headerOffset := outFile.seek(0,soCurrent); // position of file entries commentLen := 0; end; // 2. write file entries from Entry[] @@ -1497,7 +1497,7 @@ begin if Zip.FInitialized then Header.fileInfo.zzipSize := Zip.SizeOut else Header.fileInfo.zzipSize := Header.fileInfo.zfullSize; - p := outFile.Seek(0,soFromCurrent); + p := outFile.Seek(0,soCurrent); outFile.Seek(Header.localHeadOff+sizeof(dword),soBeginning); outFile.WriteBuffer(Header.fileInfo,sizeof(Header.fileInfo)); // save updated fileInfo outFile.Seek(p,soBeginning); diff --git a/contrib/mORMot/Synopse.inc b/contrib/mORMot/Synopse.inc index e63db4c..626b732 100644 --- a/contrib/mORMot/Synopse.inc +++ b/contrib/mORMot/Synopse.inc @@ -1,7 +1,7 @@ { This file is part of Synopse framework. - Synopse framework. Copyright (C) 2020 Arnaud Bouchez + Synopse framework. Copyright (C) 2022 Arnaud Bouchez Synopse Informatique - https://synopse.info *** BEGIN LICENSE BLOCK ***** @@ -20,7 +20,7 @@ The Initial Developer of the Original Code is Arnaud Bouchez. - Portions created by the Initial Developer are Copyright (C) 2020 + Portions created by the Initial Developer are Copyright (C) 2022 the Initial Developer. All Rights Reserved. Contributor(s): @@ -76,6 +76,8 @@ // - it will avoid error like "[DCC Error] E2201 Need imported data reference ($G) // to access 'VarCopyProc' from unit 'SynCommons'" // - shall be set at the package options level, and left untouched by default +// - note: you should probably also set "Generate DCUs only" in Project Options +// -> Delphi Compiler -> Output C/C++ -> C/C++ output file generation {.$define DOPATCHTRTL} // if defined, some low-level patches are applied to Delphi or FPC RTL @@ -162,6 +164,7 @@ {$INLINE ON} {$MINENUMSIZE 1} + {$PACKRECORDS DEFAULT} // force normal alignment {$PACKSET 1} {$PACKENUM 1} {$CODEPAGE UTF8} // otherwise unexpected behavior occurs in most cases @@ -210,11 +213,15 @@ {$define ISFPC27} {$define ISFPC30} {$define ISFPC32} + {$ifdef VER3_2_2} + {$define HASTTHREADTERMINATESET} // introduced TThread.TerminateSet + {$endif VER3_2_2} {$endif} {$ifdef VER3_3} // trunk before 3.4 {$define ISFPC27} {$define ISFPC30} {$define ISFPC32} + {$define HASTTHREADTERMINATESET} // introduced TThread.TerminateSet {$endif} {$ifdef VER3_4} {$define ISFPC27} @@ -222,11 +229,15 @@ {$define ISFPC32} {$define ISFPC34} {$define FPC_PROVIDE_ATTR_TABLE} // introducing TTypeData.AttributeTable + {$define STRCNT32} // 32-bit TAnsiRec.RefCnt even on 64-bit CPU + {$define HASTTHREADTERMINATESET} // introduced TThread.TerminateSet {$endif} {$if not defined(VER3_0) and not defined(VER3_2) and not defined(VER2)} {.$define FPC_PROVIDE_ATTR_TABLE} // to be defined since SVN 42356-42411 // on compilation error in SynFPCTypInfo, undefine the above conditional // see https://lists.freepascal.org/pipermail/fpc-announce/2019-July/000612.html + {$define STRCNT32} // 32-bit TAnsiRec.RefCnt even on 64-bit CPU + // see https://gitlab.com/freepascal.org/fpc/source/-/issues/38018 {$ifend} {$ifdef ANDROID} @@ -321,6 +332,7 @@ {$define FPC_CPUINTEL} {$ifndef BSD} {$define CPUX64ASM} // Delphi XE4 or Darwin asm are buggy :( + {$define ASMX64AVX} // only FPC supports AVX/AVX2/AVX512 {$define HASAESNI} // SynCrypto rejected by Darwin asm {$endif BSD} {$define FPC_X64} // supports AVX/AVX2/AVX512 - which Delphi doesn't @@ -331,6 +343,8 @@ {$endif CPUAARCH64} {$else} {$define FPC_32} + {$define STRCNT32} // 32-bit TAnsiRec.RefCnt on 32-bit CPU + {$define DACNT32} // 32-bit dynarray refcnt on 32-bit CPU {$ifdef CPUARM} {$define PUREPASCAL} // ARM32 {$define CPUARM3264} @@ -414,13 +428,17 @@ {$define DELPHI_OR_FPC_OLDRTTI} {$define USE_VTYPE_STATIC} // "and VTYPE_STATIC" test before VarClear() + {$define STRCNT32} // always 32-bit TAnsiRec.RefCnt on Delphi + {$define DACNT32} // always 32-bit dynarray refcnt on Delphi {$undef FPC_X64MM} // this is a FPC-specific memory manager + {$A+} // force normal alignment + {$ifdef LVCL} {$define OWNNORMTOUPPER} // NormToUpper[] exists only in our enhanced RTL {$define NOVARIANTS} // LVCL does not support variants {$define EXTENDEDTOSHORT_USESTR} // no FloatToText implemented in LVCL - {$endif} + {$endif LVCL} {$ifdef UNICODE} {$undef ENHANCEDRTL} // Delphi 2009 and up don't have our Enhanced Runtime library @@ -432,7 +450,7 @@ since TDynArrayHashed = object(TDynArray) fails to initialize http://blog.synopse.info/post/2011/01/29/record-and-object-issue-in-Delphi-2010 } {$define UNDIRECTDYNARRAY} - {$endif} + {$endif UNICODE} {$ifndef PUREPASCAL} {$define CPUINTEL} // Delphi only for Intel by now @@ -585,6 +603,12 @@ {$if CompilerVersion >= 33.0} {$define ISDELPHI103} {$ifend} + {$if CompilerVersion >= 34.0} + {$define ISDELPHI104} + {$ifend} + {$if CompilerVersion >= 35.0} + {$define ISDELPHI11} + {$ifend} {$ifend CompilerVersion >= 17} {$ifopt O-} // if we don't expect fast code, don't optimize the framework {$undef ENHANCEDRTL} diff --git a/contrib/mORMot/SynopseCommit.inc b/contrib/mORMot/SynopseCommit.inc index 387b377..1ee26f4 100644 --- a/contrib/mORMot/SynopseCommit.inc +++ b/contrib/mORMot/SynopseCommit.inc @@ -1 +1 @@ -'1.18.6082' +'1.18.6385'