From 6613c5b972bda6d028eb5780c5416aadc745f8cb Mon Sep 17 00:00:00 2001 From: Olaf Monien Date: Thu, 23 Jul 2026 13:18:04 +0200 Subject: [PATCH 1/3] Fix GCM multi-call Encode/Decode GHASH and CTR streaming (Option A). Accumulate GHASH and partial CTR keystream across Encode/Decode calls; finalize the authentication tag in TGCM.Done (wired from TDECCipherModes). Add multi-chunk stream tests (2x16 and uneven 7+25). Correct the large CAVS fixture tag to match AES-GCM (verified with PyCryptodome). Document Option A vs later AEAD package in Cleanup-Roadmap. --- Source/DECCipherModes.pas | 3 + Source/DECCipherModesGCM.pas | 262 +++++++++++++++---- Unit Tests/Data/gcmEncryptExtIV256_large.rsp | 2 +- Unit Tests/Tests/TestDECCipherModesGCM.pas | 102 +++++++- 4 files changed, 308 insertions(+), 61 deletions(-) diff --git a/Source/DECCipherModes.pas b/Source/DECCipherModes.pas index e4673bda..532cae85 100644 --- a/Source/DECCipherModes.pas +++ b/Source/DECCipherModes.pas @@ -1149,6 +1149,9 @@ procedure TDECCipherModes.Done; case FMode of cmGCM : begin + // Finalize multi-call GHASH + tag before optional ExpectedTag check + if Assigned(FGCM) then + FGCM.Done; if (length(FGCM.ExpectedAuthenticationTag) > 0) and (not IsEqual(FGCM.ExpectedAuthenticationTag, FGCM.CalculatedAuthenticationTag)) then raise EDECCipherAuthenticationException.CreateRes(@sInvalidAuthenticationValue); diff --git a/Source/DECCipherModesGCM.pas b/Source/DECCipherModesGCM.pas index ed2cde55..c5594720 100644 --- a/Source/DECCipherModesGCM.pas +++ b/Source/DECCipherModesGCM.pas @@ -75,6 +75,36 @@ TGCM = class(TAuthenticatedCipherModesBase) /// FE_K_Y0 : T128; + /// + /// Running GHASH state (NIST "X"). Allows multi-call Encode/Decode. + /// Tag is finalized only in Done — see Cleanup-Roadmap §3.1 (Option A). + /// + FX : T128; + /// + /// Incomplete 16-byte GHASH block carried across Encode/Decode calls + /// + FGHASHPartial : array[0..15] of Byte; + /// + /// Number of valid bytes in FGHASHPartial (0..15) + /// + FGHASHPartialLen : Integer; + /// + /// Total ciphertext bytes processed since Init (for length block) + /// + FTotalCiphertextBytes : UInt64; + /// + /// True after AAD has been absorbed into FX (and padded to 16 bytes) + /// + FAuthDataHashed : Boolean; + /// + /// Leftover keystream from an incomplete CTR block (multi-call Encode/Decode) + /// + FKeystream : T128; + /// + /// Number of unused bytes remaining in FKeystream (0..15) + /// + FKeystreamRemain : Integer; + /// /// XOR implementation for unsigned 128 bit numbers /// @@ -198,6 +228,27 @@ TGCM = class(TAuthenticatedCipherModesBase) Ciphertext : PUInt8Array; CiphertextSize : Integer): T128; + /// + /// Feeds data into the running GHASH state FX (supports partial blocks). + /// + procedure GHASHUpdate(Data: PUInt8Array; DataSize: Integer); + /// + /// Pads any incomplete GHASH block with zeros and multiplies into FX. + /// + procedure GHASHPadPartial; + /// + /// Ensures AAD has been GHASH'd and padded once before ciphertext bytes. + /// + procedure EnsureAuthDataHashed; + /// + /// Completes GHASH (length block) and writes CalculatedAuthenticationTag. + /// + procedure FinalizeAuthenticationTag; + /// + /// GCM-CTR keystream XOR with multi-call partial-block carry. + /// + procedure ApplyCTR(Source, Dest: PUInt8Array; Size: Integer); + /// /// Encrypts a T128 value using the encryption method specified on init /// @@ -263,6 +314,12 @@ TGCM = class(TAuthenticatedCipherModesBase) Dest : PUInt8Array; Size : Integer); override; + /// + /// Finishes GHASH and materializes CalculatedAuthenticationTag. + /// Must be called after the last Encode/Decode (cipher Done does this). + /// + procedure Done; + /// /// Returns a list of authentication tag lengths explicitely specified by /// the official specification of the standard. @@ -439,6 +496,17 @@ procedure TGCM.Init(EncryptionMethod : TEncodeDecodeMethod; Nullbytes[0] := 0; Nullbytes[1] := 0; + // Streaming GHASH + CTR state for multi-call Encode/Decode (Option A) + FX[0] := 0; + FX[1] := 0; + FGHASHPartialLen := 0; + FillChar(FGHASHPartial[0], SizeOf(FGHASHPartial), 0); + FTotalCiphertextBytes := 0; + FAuthDataHashed := False; + FKeystreamRemain := 0; + FKeystream[0] := 0; + FKeystream[1] := 0; + OldH := FH; EncryptionMethod(@Nullbytes[0], @FH[0], 16); @@ -455,11 +523,100 @@ procedure TGCM.Init(EncryptionMethod : TEncodeDecodeMethod; b^ := 1; end else + // One-shot GHASH over IV only (does not use streaming FX) FY := CalcGaloisHash(nil, 0, @InitVector[0], length(InitVector)); FEncryptionMethod(@FY[0], @FE_K_Y0[0], 16); end; +procedure TGCM.GHASHUpdate(Data: PUInt8Array; DataSize: Integer); +var + Offset, Take : Integer; +begin + if (DataSize <= 0) or (Data = nil) then + Exit; + + Offset := 0; + + if FGHASHPartialLen > 0 then + begin + Take := 16 - FGHASHPartialLen; + if Take > DataSize then + Take := DataSize; + Move(Data^[Offset], FGHASHPartial[FGHASHPartialLen], Take); + Inc(FGHASHPartialLen, Take); + Inc(Offset, Take); + if FGHASHPartialLen = 16 then + begin + FX := poly_mult_H(XOR_PointerWithT128(@FGHASHPartial[0], FX)); + FGHASHPartialLen := 0; + end; + end; + + while Offset + 16 <= DataSize do + begin + FX := poly_mult_H(XOR_PointerWithT128(@Data^[Offset], FX)); + Inc(Offset, 16); + end; + + if Offset < DataSize then + begin + FGHASHPartialLen := DataSize - Offset; + Move(Data^[Offset], FGHASHPartial[0], FGHASHPartialLen); + end; +end; + +procedure TGCM.GHASHPadPartial; +var + Block : T128; +begin + if FGHASHPartialLen > 0 then + begin + Block := nullbytes; + Move(FGHASHPartial[0], Block[0], FGHASHPartialLen); + FX := poly_mult_H(XOR_T128(Block, FX)); + FGHASHPartialLen := 0; + end; +end; + +procedure TGCM.EnsureAuthDataHashed; +begin + if FAuthDataHashed then + Exit; + + if Length(DataToAuthenticate) > 0 then + GHASHUpdate(@DataToAuthenticate[0], Length(DataToAuthenticate)); + // Pad AAD to 16-byte boundary before ciphertext (NIST GHASH layout) + GHASHPadPartial; + FAuthDataHashed := True; +end; + +procedure TGCM.FinalizeAuthenticationTag; +var + AuthTag : T128; + AuthCipherLength : T128; + AuthLen : Integer; +begin + EnsureAuthDataHashed; + // Pad incomplete ciphertext block + GHASHPadPartial; + + AuthLen := Length(DataToAuthenticate); + SetAuthenticationCipherLength(AuthCipherLength, UInt64(AuthLen) shl 3, + FTotalCiphertextBytes shl 3); + FX := poly_mult_H(XOR_T128(AuthCipherLength, FX)); + AuthTag := XOR_T128(FX, FE_K_Y0); + + SetLength(FCalcAuthenticationTag, FCalcAuthenticationTagLength); + if (FCalcAuthenticationTagLength > 0) then + Move(AuthTag[0], FCalcAuthenticationTag[0], FCalcAuthenticationTagLength); +end; + +procedure TGCM.Done; +begin + FinalizeAuthenticationTag; +end; + function TGCM.CalcGaloisHash(AuthenticatedData : PUInt8Array; AuthLen : integer; Ciphertext : PUInt8Array; CiphertextSize: Integer): T128; var @@ -507,85 +664,84 @@ function TGCM.CalcGaloisHash(AuthenticatedData : PUInt8Array; AuthLen : integer; Result := poly_mult_H(XOR_T128(AuthCipherLength, x)); end; -procedure TGCM.Decode(Source, Dest: PUInt8Array; Size: Integer); +procedure TGCM.ApplyCTR(Source, Dest: PUInt8Array; Size: Integer); var - i, j, BlockCount : UInt64; - a_tag : T128; - pDataToAuth : PUInt8Array; - pSrc : PUInt8Array; + i, Take : Integer; + KSBytes : P16ByteArray; begin + if Size <= 0 then + Exit; + i := 0; - BlockCount := Size div 16; + // Drain leftover keystream from a previous partial block + if FKeystreamRemain > 0 then + begin + KSBytes := @FKeystream[0]; + Take := FKeystreamRemain; + if Take > Size then + Take := Size; + XOR_ArrayWithT128(Source, i, Take, FKeystream, Dest); + // Shift remaining keystream left so index 0 is next unused byte + if Take < FKeystreamRemain then + Move(KSBytes^[Take], KSBytes^[0], FKeystreamRemain - Take); + Dec(FKeystreamRemain, Take); + Inc(i, Take); + end; - for j := 1 to BlockCount do + while i + 16 <= Size do begin INCR(FY); P128(@Dest^[i])^ := XOR_PointerWithT128(@Source^[i], EncodeT128(FY)); - inc(i, 16); + Inc(i, 16); end; if i < Size then begin INCR(FY); - XOR_ArrayWithT128(@Source^[0], i, UInt64(Size)-i, EncodeT128(FY), @Dest^[0]); + FKeystream := EncodeT128(FY); + Take := Size - i; + XOR_ArrayWithT128(Source, i, Take, FKeystream, Dest); + // Keep unused tail of this keystream block for the next call + Move(P16ByteArray(@FKeystream[0])^[Take], P16ByteArray(@FKeystream[0])^[0], 16 - Take); + // Clear used prefix is unnecessary; only FKeystreamRemain matters + FKeystreamRemain := 16 - Take; end; +end; - pDataToAuth := nil; - if Length(DataToAuthenticate) > 0 then - pDataToAuth := @DataToAuthenticate[0]; - pSrc := nil; - if Size > 0 then - pSrc := @source[0]; - - a_tag := XOR_T128(CalcGaloisHash(pDataToAuth, Length(DataToAuthenticate), - pSrc, Size), FE_K_Y0); +procedure TGCM.Decode(Source, Dest: PUInt8Array; Size: Integer); +begin + // AAD into GHASH once; tag finalized in Done (supports multi-call streams) + EnsureAuthDataHashed; - Setlength(FCalcAuthenticationTag, FCalcAuthenticationTagLength); - if (FCalcAuthenticationTagLength > 0) then - Move(a_tag[0], FCalcAuthenticationTag[0], FCalcAuthenticationTagLength); + if Size < 0 then + Size := 0; - // Check for correct authentication result is in Done of DECCipherModes - // if not IsEqual(FExpectedAuthenticationTag, FCalcAuthenticationTag) then - // raise EDECCipherAuthenticationException.CreateRes(@sInvalidAuthenticationValue); + // GHASH over ciphertext before CTR (Source is ciphertext) + if Size > 0 then + begin + GHASHUpdate(Source, Size); + Inc(FTotalCiphertextBytes, UInt64(Size)); + end; - // In difference to the NIST recommendation we do not discard plaintext if - // authentication failed to make data recovery possible. But since we throw - // an exception the user will get notified that there's something wrong - // if not IsEqual(authenticaton_tag, ba_tag) then - // SetLength(plaintext, 0); // NIST FAIL => pt='' + ApplyCTR(Source, Dest, Size); end; procedure TGCM.Encode(Source, Dest: PUInt8Array; Size: Integer); -var - i, j, div_len_plain : UInt64; - AuthTag : T128; - pDataToAuth : PUInt8Array; begin - i := 0; - div_len_plain := Size div 16; + // AAD into GHASH once; tag finalized in Done (supports multi-call streams) + EnsureAuthDataHashed; - for j := 1 to div_len_plain do - begin - INCR(FY); - - P128(@Dest^[i])^ := XOR_PointerWithT128(@Source^[i], EncodeT128(FY)); + if Size < 0 then + Size := 0; - inc(i,16); - end; + ApplyCTR(Source, Dest, Size); - if i < Size then + // GHASH over ciphertext produced in Dest + if Size > 0 then begin - INCR(FY); - XOR_ArrayWithT128(Source, i, UInt64(Size)-i, EncodeT128(FY), Dest); + GHASHUpdate(Dest, Size); + Inc(FTotalCiphertextBytes, UInt64(Size)); end; - - pDataToAuth := nil; - if Length(DataToAuthenticate) > 0 then - pDataToAuth := @DataToAuthenticate[0]; - AuthTag := XOR_T128(CalcGaloisHash(pDataToAuth, Length(DataToAuthenticate), @Dest[0], Size), FE_K_Y0); - Setlength(FCalcAuthenticationTag, FCalcAuthenticationTagLength); - if (FCalcAuthenticationTagLength > 0) then - Move(AuthTag[0], FCalcAuthenticationTag[0], FCalcAuthenticationTagLength); end; function TGCM.EncodeT128(Value: T128): T128; diff --git a/Unit Tests/Data/gcmEncryptExtIV256_large.rsp b/Unit Tests/Data/gcmEncryptExtIV256_large.rsp index 3d348062..4353bcdf 100644 --- a/Unit Tests/Data/gcmEncryptExtIV256_large.rsp +++ b/Unit Tests/Data/gcmEncryptExtIV256_large.rsp @@ -13,5 +13,5 @@ IV = 516c33929df5a3284ff463d7 PT = FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AAD = CT = 1962739460744fc0940336d9f22e3bef68003be42c3f92b24e3171cb1a21ba82c0df5dbbc9425b5e1a2f0f64a45a96667b72c2a3eaabbba0f521981981967fc79485a3b481582ddccefe29d7571b8f8836bec442eba11d31021ab410d269b6c2733cc635ae2e78ea1af6a16dbb2afa0f8ac27f77fd2361245f0920a339a96eacddf21650c5eecd095ca471d139aab4ac87d62aa92e5496a40bf318d47cf3acb26ec12512cd926a60b4fd7a006e9d292ff733cbd613f6a47ab94b3463b02d0174591ffd383199dd083a1102ea984763aab8cc173e7859d99bfd3a14aa6d78ac8896ff5ff864a7b7980511b794fdfa03a294bbdb5db009409d4e6bc9f01d2af0bf8e5a3028a6cd121b0eda67615dba5e455d7f3a22e0b553739cb0c4b5f6dae16c3d457de7079ee33a45475a5543f90dc1fb0b46ee8fd90abbfaa4e9e8bd8c27c7a5978b9004134eb400d373ae2c2c77710ab89946337fa22d5a9519d96fa292824c73cbc3d0653815ffa853ed11aa70a29b6a4b9773e5e8e21e13f60bdd4cb5fb6e9e9887c327f10545a57e04f1d7eed7c3befe3f6558008958fb153aaed1428cfea47185a39922027d6d989bb44cb0d96f60177aad57b679900a7e012dfdb0b215b6c44e7193fe06ad1cb6b6099da17866eecc85e6b081e9becd825b8c7e9e776b86ab3b098879a23639cc982dc10d0f4d79baa78082aef7f89e6735646afcf5d299920c42cf4965da583a7252d8f87bb5783994a7c90df6b072e1686ff99594a7c969f8b254eda7408200b75a1114216d9704ba9e23ab191e64608b5d456fff0cfe987e66b5c9be087b261c3f290c317ad6968bca6ae6476438eb3373d94c0143efc42bc74d065b6e8fd1a26028a24d9b2c193775b6346a0c561dbeda87642604a4395436a7f6333e74e97ce1b769b1b2f32e6952714ce0781da9667f50f491326fee90ff13989d157229436f1f7195449acd68e1f96244729d72a498c2a5a6cac22c979c3784b73a0e13fe5e97fb99915d02a6221417d0182553a4ed2013371402cd6ff46d2d3d5b6f42ae8a0132a7272322314ef83f7f322a18fbde5edfd9b58d9900010090e580660f623abfc5573194553185e0504d52ce86ace475cba7cec3f3fafb3feb69d57462d6dcaf1fb15c7870425832c6bbc1258a41acc1d4b70a4a1377092a5891629bf164175b4805ce73ebd4bab9edd5733e7f921137ea799f39cef1ed4ca884380f4101dbe2924b322c6ab4d4fec87bddf96ca7cdc1e6cd0d0b4ddb5ae1baca304f8347264999da1e8aac724d69634a547a51855b603b47ebff350bec81ef0bf69979e9be55222c7c8c1f3c56a7db5fc2e9c8504c49f60a44f6d7d94f7d43bb76c2d0691b28a840544664d95cc00527299f768fafc51f80d34e0df0cf075d97dc1d662703484e48eb792fde650c263aadd57e2a677cb7bfee08eee0e86ad53f733ab24900353b70232c6c7cf357869e257d780462fa8fc4e51cc1c9cf421e8ecd5ec55db601ebae9bd4db553206ac61e091db35e4e48bd525c9e279a432cc8bd40378f54be7e3ae5b494a26ef99a94879cdb57e556a7b3f194cee8273c9bb40544ce09293941bcd3f66453b347b6e0e7202a664ea11c8c47a5d8faa9b3e18aa963c5edad7c5856b804bbec815f7e8783bdb3f54009b776ac005f1fd04f7f8be3e84a45049f364453117e9dfb4356c28e958a60802589dcec3fc8453ab3215fd22c646a02801bf34a647122441ce0343c53a7e8fbd5842d5e9deb02aa90ed6792cccd05048316c008403f3ac8b2a94e5d5f03ad2499a8d9225dc09e880332965fefd203348cdd14a0d227ca7dbf706151f512ec3445c15c18acaebb72f9b2d5c062e28676b3a0e7610fafc79584f6e51a59f8f47919243bbf554e28bafbcc7bf98269bef90a62ed42d6ff2efd3562ded66333cbd5b4ae571c3282bc3805e770f56b2c777e8443820f60eb7d3b8f636677fcad5c88a104d7cb85716d52145d6d464f90654bfea7aec649cd374fa372170bc6d1c70f63d91b8a5a19aa7ef9a382c173a485160254fb8ce3f6b3fd62a85f79dfadd8beeec8d3747b946fda7d961e03a035f4187e1cf5b3e4674951c1e73e50e1b1cecb468f5b713364e541d23dcc9241e60968365153a5fefe9ffbe2912d032ef1fa05c2c939cec8491d145392127a3a60c4d876079a6894011ea024830a94b19a9fa64f2a6f1ceb1515d083aa2538c5b62624cc136c1b4a1f689231aface60eb39d6d9e499a431f6f29a036028db9c45f8af06856282c1763650ef36420a1e62a3ae4388be2c4282b35587325a247861700d226403916d38fbc322b9f96eb2ddb087392b0c4d040fadb4cb230c47dc743ddc7a915482c9a7c2d9e7e081147f45a0a451dcc87680368e0d437ccf6d74b437097b8d62d3326fdd305f086691f8cb0da5779a7278a7c4658f61fc46cb5f41a5367d669e7f017c36154e57c1cca23103293956acb224a86e1dd2b8a117d585a352b7e3eda6ddc14f54a78afbe6fdc77c6a434b01626a6a099355dc7da790de63d3d8b06094cca5f019d9d891e85961376ae414b416da25393fb4078290e92bd6d219176ea4f9f0bc55c09cf05ed0bdd91e3beacbcfb0ef7b0ca6995c8d7fc3eedfa9f2dcf940006e148c96177c7b8f88272149772e91216a9c989be6ea35cfc327c7ef3a62452e9cc2b3d7da37bed0e831ffc4128a54b10413916c436a52335ac04632581d505304ade624c8c5b3a2a7d31ca2195cc249eedc500e206494c8ef4bd07ab853ee847358199a457d72d853d00608a26971b9656c2a0e6a0fd7469c918fdab7d5160d05a46c38b1df1317bb76f8bef540508113b062a3e143b6fea35ae52e88eeb6a8bc5725732e44235d9990c3783bf9c0c9be7171eeb28476fd2a2d4bdf884a8a95c9e6163c0c97c5ba9f97c553e8594afc8afb2f2620b733d5887c21b2718a24a0be7c2f7923a418ac627ced96a81fecb01d68940e6cce8c28115d33117581d456179757e16de455baffd6feb443543723383584a4bb4bfa084fca4e20e573f67e5f09878974b641968dbc15e3fb20b380e14e8fe5a403e2caf71be464d93bfbe51f0ef31929973433478c42b12887d4eab7165f1ca1801f81e6269b11b4037ef74bab90e4da20ba106d4a616ecc3b8cb1287e70ae847fa03729d6d3b7e87ebf68b1b8c2b64eca1c5cee9ffa566ad5419087301c6aa21025b4de393b3896e6c9be4ea79ea913bf952e03b2d73617c4b8c9640eca26e32df31cfaedc96bd113cd9a2418122f3887b9ef3ae8b8c12f9c43bef17e27691fbeaa2f2f80641183c97d008a1f066c976166a9993f8f0cf02ab9d403c215b3f24d0278cc46606cc2f7b70e76140c5c4dd5bce2a2150bb58f8fd326d2221209c44440a8cb966c451a39c5af2265d66c343fba036a44663e0cc62e76e6271559c8137f08e2ad72bd42cd4e68f9f7b74a195f556f6d7aa243d38cf2f75b6428e737def85e3d95afbaecc1959f41a7f5b270f4fb5d37f6d20da559878c8b341b619c9240cb01cd2a02986c9431ce1dbb785d40ad9b701bdcaa796fe350a2700ebfe4369147e3b3612692e7fc290a98e04cf3ba36ce72092b762d2abd463031f7aa8c0e499d2408a627e854ddf905f3e15b822acf5b1b81a3b2637018e1ac5c3e86c9f1b0141e9be43ceaed4d2617881fac59943c89928bfb16c93f5956738959791e422dc17f4fadb111e34b36ff17ea2c5988c5a21bf6584f5a52fefc3754c425ef6d73c9b32618ad5a4e13de31101dd46b12a2b68da3958e1b419a1881ce8b694594c9f1f636311765ec8671ad447200f1bbdd77c267f7c85fa7b386d00c7721d80b1dfa6581ee75e3fd83161910122a0d3ae09e7a90795ce17504deadef82a9baf3010b6d27d9f98c6bdaff044140c7c8a81b5fc3706613e6c15c294e5861da668fb64f9c97184191f74c93b237f9e4905b2dd16c9d0c6c61a802fae9ef29323d6099cdd0e6137a098e93b4e969af13c93d2ddae2d853e95c634a1bd77f1c81516ce02b8a0a7e8f0de92f4832cd29ab4e6d2c2f7103f76c9da7c6a5e75d9106d25f3eb58b95b4bf7e8d425b1ee1570d5940a40f6161093e272d3fc4b6e1a7b5cd499a0c12166d8a62be072591496b61d437ae3c049e58526f03a6856bfe8c63224986b1009b2f1a4053ed8f47c1a09d0466ef0cafa0c1de966499f92c40206290c658581507a47dad25e3a25ea730b14197993c8e571aef8ea935e86497e758a3419e62778360f7a7f86ba94b9d8b13cd740106c2c418dd87a26388d096eb08cacb6eb2c743991185543bdb6628d2bb620ee45b84ed85eeac8112e5112a1247d76b08c7b4bfb44f25980ed4d54dc42ba5995b14cacbb979aeb9a65cca3adcbd397656c3e6162d610ae214acb39be5648ae16ada3dc5d6548e15cca75ad7ad9f098ef0612f77b583f0b4111d24cdca27cddff68ec9fbb75929d51d175490c6c8a674eb9261da87b51e5d05c8d4de2c2ac2cf434a0620b8cb5e0044e3e3006001894d4edbd645d99dc2299af1eba50d1e2e8ee88a6f665f5e58755a941266d9906a356e47c75269c1d3761ace61b83c3c9f27560b39de16c42d13aa2d46a95f6913f2b7e974b635295d7529493053ba31641ac88ac4b364272052608015c790c56d4d010acbbaca42165782cb8f938f77592661656e19fe30a4f077e2e1cc1356f5ee00e3dbaf433c5b60924c64daf2c2f6b7d5fc68514ba04c15af8aaa6cc9554cd3a339a1342b9ade7e512c350baf68c2a7a168277f525f1301bc4ce10d05ef8835a11dc93bc04b9315899e2c66ea3878f0be955bbd6616efb2b92420d0ddefce419d0f2179dc7f313adaf03898bcf81aa803511db73d8910737ecc61727b06a3ca51aa8fe2a6f201500d1b8cbc1ccf0794cb1a8d6508a10dca44532a16435186ba6c5906a61d71b83fba1ea8e35a9f69630cd854fc883123398616244f8d6615254d2c04072a7c1a702bff82dac4fc2b3a11ef923c062043a90906a12d23791fb9a275bd2cfe4cdd9ac1a78794e5fbbe834ed1af1c69de004450bfdedfbf7d9b255b2c03654b29becb57a4f5860e83d29577bc6edb1e51853d496dbe3ec60b7341f4edc32555cb9739071171b9a72268b07dd8bef66eaad217b00fb80bce4b7943fa497f0f31ccc9c840540efbe87921f6d3ff62afca8ae44a84e7d8f9995cfea72fed40d40bf27d192a5d383e730a4ffce342d6b4d1b58e1718dc8bd82293a8f7d3a1379ba8c2f1efe1f818afaeb56782831b83cd358655b99b7160d354e7c7bde14672e391c4ffe5293e5968440f201e1e9214a620e9fa6514228fa249536e4f06cc4f572211b8c50130aa16fd8fb476eeed9885c444c6b79cd3f408e02344459989138048b12d78124a6761b537f4bb9d26041adb35ebaa91bbf2e5ee51de34512556b150d05c6b86f36a27480903171fe7880cd5e7b2dc67c7a4045b62028f28b0366ae93e755ea75acf572c976c5d428db08228f6d35476d87006962798386eaa017426a18a2aa7335cc67154a867ecb93adca6e9cfe835c9492ee4c59cab815765fcd57dff2d985893a24f559748ea71a1dadb5ddb316c614db768c90f58cca0f9940326fa0fb57c6e7bb3d0b3e8f8d14a440992072bca0565c32d292cd31ea2267d65e89abf51c5dfca96192a05c155d87f18806257012beab17c768629c580bb0f591b3a53b1f34d464d28a4cab1b524507eb606f4203136f350490911a3543257d2e5073b55b13b055f6fcd293d477bf969df479cbb7d47f977233d53b5dc4052e9fab6c559e85505b279a3e8970d460903d9e93d64536c898dbf05b182ffe4ac8f9cc6a6ceb8768bd00c03ab26c83b816aad21a7eeaac5a34ef7bd0e07a6f97f0c084b9427426341dcc449b7a2fc82e7ed9bad72a5f36e8cd70c9c7a4a3e1798ebe5efc6ac1ea80f8bd9902eb7bff63211c0e5dc766e47c214b1824c951a4a81f34e65131b9dc0e7e6ffab53c5361ca938c372a78be114b6d9efc1fc7103aa1aca82d3c0b1e4eb5ec1382cffed520bf746d37cadb0fee7fce4599237c2bc07d2171fad1b0e5571cacf9c4ca6dacb6728b11139d6de0bf3d42f2a0c04cce637da1b0ccbb8d7f5578c1c2cf91865e9764bd916855b5f0b459466507c561ecc1f324343ada7876ba72f0679a2238c64caaba488438d16444a7eaecd3cdd2ff26bca474d66921500c5bafae41c352dfdeaa1b69f675ad5e536509a19c1d5068df9e068e286e6834db74500d38e9cd0852e22d6757fed155f5116b1dd9de55c1fc9e6ca105a9909b0ff67a2f0652283441ad9976a79e001fab7a5927fa6d1014426102d14170e50027073f44c9863085bfd2db896806e59c136dc6ad33c64f93c7290596349b29a612c275bf17796811637f38b27d04f188b4afe8880ce5992b54e7d38ba6bb56a62e5249ed612e30fbc4905c52981086bd9f97d81776d89deaf979327246565b9ec58b7162e140165b0e0cc240e68e6afbde8aa1aadec1beb641bd04853f6bbc903094e0195a23faa9960217506654433e0427655127d0d68c708a663de14364bd4147f03d3aa2ed0a9d47e34b22b613763c1fef4ba26801d27990fbab8992bcb951125a656393c7a45b191a1ba33d2f95ee2aa4e26da7e5f40c5b781417f3bada1869175c08951fc9ef2bdc7be922d6b9f946b108868050b0c616573f887f0e2fcc69d24e06675de6e75a30360c12b31fbef86a6deeed5b9b950ccbee02b86d683c0cfad5087054e5d1b9d6189ebbbf8bc46c80b467441eb87a72abe07f86bf0705adb9ff74981e01f06ffcc3c4d549fa0c72b9669f95b9eb31c1ea5b307ad70e5289a282e38fc1ae85418a3a396a756f63417027d54203c71f66de683ddb5f2e6eb8650f1eb17b12fa7594e4994ff9543d68d3e779889c15c4e66da0844cdd2e59c0e763ec067d44f96e57ab68ef4310f6e1eea00550976a18e21f14bda593fd816eb149ddfd48838518f3e8acc32b43ff7d9fc358ab80e4249d9761c0fcc8f8ee69f7760b8bae25c78e2875c025b1effc273d47a26745ae22be5d5d7a24c17c5b46b47fe07f596a99ec0eabda65f977034082807b7245f4b533b74758f009c7c734b1b700251c74f987c0c955a4ac8195dacf489b874ab936b78f7c0469e6465e46d091b6eb1fb2e84b6b85b2cdc3c519562b9ed2d081e58e21b5c28425a21205ce9823f07d9b4d4fd1563c07f7b56560b53b2124f8537274069bb41d87d9a307a2a6760b8c5964cfa644918a3e21fe29111acaba95791e0b09254902009d974c1e1ef9cd2c2973a60307c3739948f3a35f826544f94bdd454695e21a06c738e0ed9bfdda908d9dca474e835e1375635d4a2169d34c68704588fdbc0dd947eb0ef97aba3dc0314811b03b65b232ca656353b8c295aaefcf5173d85a4adfab963752e971ddbc5ba162d28ebf396e23c596e96717a31bef2ad7b34cd6ea6b08b5e6b623cb38bddcb8d2deba1cfd5657adfd9cd5ee42687d28ec3ccf014f1a410f82791c465e2cff9898ef916e74d295d4811e30edfba03e036e668ed66be635b3099b5972fce9d83c4270d244b48bffb7fd9d27e7820aa1b1bcfcc5bed5c478022a7ff8607a59a68ef1c759a3c51fc12a8f014308479137416281d065aa3b465cd20817cb8aee3006b1a5296421d2607513b7f2a22b99fb2795b27eb12ce76063672a426a52390efac80f64426c6f526f74bbac72f28710c952bc6b33d3ebaf58faab3a4858153bd0083d8c4e028ead2d4dc9a4660983f3f05bf6087153fba6088386c074840649591d0ffbb462b2d22fbee8d39a96ce86b0f8e4c44c057345e5a9763b66cf145a322c384196a46acc49d40cdad44d164293eeb2c432f9007942fddfe79acce1b96de01e41c4a6fa45993d6806248818d44594975e6b08c812fef3563c7a700a114103f77e6a65a27b25d235a01861aa9a2d3d79d5f24a61cdb9472899684d0677086b280fd6037418e126c527849654ce9b037a5e7ac384e4cce9357c9c1946e1329bab80593570f2bb2dd34a1f8e91752a77e6294ecc9cbcb1bfe672ff129d266e02d85e4d9624ff0bb47ab973dd4dec320e7c7abaeb839de47b97f964a5d8eb885adf2fb7a0333168e06a3ae27145f734df128a8660cd50799fd53038a9135aedbb71e710989da2ee4ba3ddedde03b260735d62ef47e6ff12f7ca6eb154eaf912fe8434f39755683eb985c062436a36765c5014741e9f89399b0d3935c745e16c52736cab827e0f88f5f0529e773e1bf01712563aaaef2020905fde6b405ae0556f126b2dd592a10f263a7766ad32168c189db902ce80ae321ced61fcf37ee1d9ff92c11ce2ca86e795eaee9f64338a7fcb98c51c89b9fa5b940470c4c7dc7c39e39b8790faf5f579607fc1e20a1175e6cc91e5fc644feb495f9a82b339921d68b76ed5a8f3d66d5604b9b3bd0fad13c8c0b667209e629b02ebbfb31311572c194f680e76b53f7f6ba1a0dca5a220e8bcc8f83e0e10ceee87e470d0a1b24134d0dc181a9987d6376991f3a5668a974aa08e815eaadb2a363c04830b623878cf7d471fbb172a1ffc15b3873b17a73aa9dc1e8a40dbe73459df0dcf40836b76ce7d34b6ba1f6f87489561b65d6c0f933aafe80b35821bc9dcb51cccc8a0f157af947eb17ca76a0a48b640c02cd305343a21a8622f1e44d49d1cc333700a2642aa714fcfaa00b048f1b11c3069dcfd9d82001b4d02c2cae54e069a3756f3271b139d06b118d2a26da503f56e0f8ec85bb540d337d398bbdb35827186a311af491d5a3c4340ca8cb73846c288ddd7636421562de17780de817f7826d82d6fac0447a62ab20d8d0ec9c2991145094e74d43c693065a37d6d392f8691411e5bab60bcac8f9dc45f618be9f844622d5fdde4a8eb75c428030a531b8770e26447578b3ff6454866642135b3253a89868d12e3c0f0936fdac4086dbce2a1791b6cb981fb0bbaae5cb7731b4c429d50e5826881047e53d739b39f7f9eee92c9d8937b39786ede0ca273f9bdb73082940d2eeac29875b12cb3ecb2a8ff1cf068de5db4ce0c19e52fdace96d1ef94d4093f87b74c3438480455ea8f4a1e73db6d66c39b83467cd923fe924a142a511afe39f2bf52db804b045c707995f1eb0490cddd2f31a1fc379e0e0f73a723f23933b85c16b21a0e85f0b7dcc4c3241d84c2cffd6e14ddd15181deefc4981452e6fdc1bdccaeb2d498ce657eb35df01c6f4987a2bfe69ce876a5fc269abc44d17275b0b5cf2cefd0215d3a4bfcd0767e794dd454c86ae68c009aba6c7799c4c259c5384b7fa8a1fb224fc88a6026d35760f379df149d352f197dd24ab7574ea61ff5a58b6ca03478ee783925a82f26363e0f9d91d633799f4586f3df2968e7e979ddb9a46163d4c782bf3abceb4bd1b0ef80740c322227d0a9cdc742c27e503236425e4427658cfca597592cb6ccae15d812dd5498eb19c96e55e52fc7bb1efe5389df12f5f13ef6565bc89b2339ed88d39c7b3dbd4d8600c360df3d811c46e0d298d4af6c54b8b2fa5c4d677419ffb3a8ae60ecf8f40314ddb38637834676b16bab358ee636cb5bbd24b0d5cab8ad4e5ecc4ed0cc9e603de66693163b362d2c17c3765c4414fdc91d4d55cade75873e594fe4788580fe86b555898f7fedd2d5d4abe54a0ea973b4c35359086ac588c2dbe0124c20843204d68e3ec9eab206ddd9a01b518ec38c1538086717459a776c2bd9b1c33326b9c810b3a533acfbfcd04e29f6e35196dd67597f53948b4304f59af91889112eaeab5a00e6847f00c0b29e6d33cc95fcc1ce5dd9fbe69da2bd6f0f9c703cb3bf611f748b1373d4f58e79e594cc126f23f388ebde22765c13f85edea0d5c433743397820246ec687cb03f461791fccf87907519146ac4803f881fdcb89111db1a01214ad9adf1638a8778886a5d25732632f86c84ed8ea8c89e08dcebbf0364f484ab4fe77a6a083d955a4fc044b50717d16d7f830977cd4a099b6e96bbf16b924472a75367291e8b18125f7160f340fb1104abe4decf1b15fa44e7b2a81f1c1f1e43e1795fd1bea5be903f347748f32ed0103499ebd52b5b111e02d17604a7531d5f77e0dc11b31f17284adb19f96c8244755051b67f3787538441d4f0124a477fc24dccc2876383e8ffab429cc7cbb392cd4529b8b1d2bda3a1034b6c076317ee03be06c1fa583f29c4910c0a6b1d928561641525ca68c487c006f8f1a05f00453341bb8c90e9a7cfee2852d5031276db9278746099a7d1914af7c6e0e1337c5f578c637ab8be002e1e89265e887b803d52a6123398a292d44daa37a6b7d1b55b4fb29b67efb63bf71cbe4ea5562ceb330fab4f871f19cf387e8a1ced194f1223596d05b13a57658b96262be77bdc62c96b6077514fecc345055b463c4ee358376cc68f2982f4d0b0c894ba0edb1c48055b35a82d00a3085401875d5d7ced71e1292179219558e1f6952b78a7fbed4148044e095bb1100dce58640352330f6b59dd7c8bd94aea2270257089ae1ca9b5fc904d705170c5480bd215586283c8bb3144886ce99e001f4cb66a643a8a962d830a940e29fccd126844421c4b343b40a12426352058f8e2dfb69e9d1d342cfe1d6f78ba114538b64c8198228a80a9f9e2998347dab5fdb9b04cd4025ea75b9ca70f83cef28cb2af74d5dfb887869acc8dd748e2f62dd3eb7fd60758ff46adb4c60bc2d5b023bdeb9dc1cdd20076d7a1a53d60ed345a29692746decac6975628d9a7840476df754e448ecffcd1544f0d37ef0932819c1cb00ae384144230b386675df5e82b4ea6dda50109543a1bd0fc1158f4221304700d390fc2ae525e45ab6e839e2c51511a60b9810e13870d89ec11428d3b9e67f94e12e85af79d16cea0ca12181b3a9db8e2aceacef9fb445f56ff1b751645a5f9f66205fc3f83bd52f6378ec4ca6cf1e37bbdcfc68f5281fadd051a15e911cc92d8c137748cd4ed8f7fd9cfd5e81339a9d16b8eeb3d286af478400a54c0900b6e9c1ccd2f579da1b0afe3f18047f851c5c2e6839d9daf93451bcb1a3552d7ecd7a3cbd6db11aff73e99ff4faab639a202530224ee220196d0c18249d7d02ca09dd8235fa0329ae029b7b89588210a442a6f6d4b9565541651a66f252a79857df062893a39d9bfe95b1292f2e318b86f861479051fbd1b93cf32007b361cda84960eae23175f67aa4176be10fe6db20a3f93d388e4fef5352de9f75a436bf4c865ff9b59dc1b10f01d0b79eab0e7bb2d735a088b77350a0c9820ce9f755f5fb0172baa9ef2e77811e70783f938734284cdd9479997e1f816c7833c398440e676e8498a684b9aeb1f3c4b98c7863e2faff4f9d90ac75303fd3a5132c328031dd4d896792affc92cba00e9bce1cb4c219d89bcac35bf8c28d9781661130cf084ae7a450ea19cdafaf7dbc4223a4cc6b36d7f59d2724a903792e330214b60a0ce92cee736ee5f42f19eabc6575922169e934cbec296e1434ca835773eb2059db075c2ba0fc36a989e3c3724da9c02e6d35bd285bd10830916e12211f9425488cf5d3db3ff8175beffe48502db96037439f6632a24b2b6bc392f2608a7691872df141dde41bbb9e54a8c28f11cb5869e911d1444fa9970f32b402e4dff925530b084f6a48f5da80cd0c68c3c40839bf6e6f45054ff20f2a18bf53f79266ad71bac06e20220a8b2ae55ebc104b5e96de200bc4e953e1b527dab7608f41a3890c51f9ab8a2908629119c7bb4a3703e6a2c515f6ff32e8d7b47e543746465d4a66bfc7d33bc5bbf237400bf89905d75eda073ae398b615cffdb29dd909f60bbb17a0ee2dde1b401228f9350f866bbeff302ef4159c9cf0c0c1234d849e47ab2066bd7d3f334a8ecd963d4e00538ddf3f0205f164c37af43b30993a7659a143d6ee01ce2d8f9fd0ca41b93e0e9e590ac02017b60486d5a72300be36d3145c69f8da26a18fb7a4cd3cbe03ae0c60b36847c49a9ea4a647620796de292638e7340c3c85dd216c5d785a274763a4cd7fb06a76a794b044cf4329ed3e6cdce0aaebdb5637d2637f3894d6a03063011ed46d64895451e08f820e4a06a0eaee1464100d20a4e788a98bd7dc81b65a9dabed3a7e9995201f78055a11f92a36486f25bb68e0e41a31575f319597265ea12cab364f75c85ddc1837aa6dc5a3730e066da68e5d2ad1882c6aa05b2aa18bdcec637b620a72390169aed98cbf27806ff17106cf86ce7f07300cad2bc5f5d49aad86dd843952b3e92ef2c6518172311a8ae05cd2423cc822d38dbcdb3a6f8d10fa308a151628e64928a129db5e0cb4df00f124b09fb0d8b0867191104e02b78306711f347e155f1c864c7a41168acb7234bb137d0731d3a02a05865dff1e1b8ffbd4e6d1b6fbc99d4190020dbbc68c8743c29bc831728f1e496a17113adb1fd3b046f66f2a50a5346105c913a475ab953d0d33828c85cb96ba398f670f80c1bfc57c7a043400cabc2f8b2e954bd3385f1d84a0acb32fa5b28155e3559c923b1f143700aebe06b8471e8d29e52c2226d991c349a50bc2d1dc6091288d19c95eafe1720f4edf9b4479019618f2f9753971791dbea20ec25d6cb731f46b0e98d783ef110a84a35d576c87c1280956757d81b5f6003147b7393e3ea05aaae6b4c1888417084af3f8c5c57ec70ed098b83864368210173bd9190420e8837856e065976e56838e63b526f2579e198a3dde90685f12d357e2b839a30c13d922a27453c67a870547f75654a667202d163217b58a600fd653aebe87c813f50693f4237d1c356c92fe3ce9f208e9f56c900ee0c35f9dd5140a42b78cd1b4d2df4ab3723e4a504dd3317938c6ea2c77cb0c3eb23a1d41319131235bb1128db8882e01fe896500efb09690481e03bcbe9ddeb2d6470015f43d462b7d7c520f54e83c6e3eeec37e505b0aa527f979f49b551e5b72db59b783ba77d59494b92c0d7c05cac66ca0b8558a98ab684826ed38f68ca41b223acbefbbd40d39de41b31a6ef206d8bd47f2c1c5bfba0fc0dc8fb3edc2934867b503e8d730b3d7d37511bb77ba458f465378d8b94e7e9045d7fc0b9d444cac58ca403dfd74ba9d471fbc9a2f5dd36b396b6dffab5a0d534d94a0cd6f60e5056f798e6a67a360727da5554fd3c74783d28131f1059363348d4c0c1888454e1b74c46c96e68ccddd34f9abd856fbc051d565b92372d4f8044910a8db2dab4f64f21388c1ce3c79fee9d4706024de1bbc157a5f382a4e6e105e1682a55e5edf2dbe43e5a60db0369ad6ec3553079e8f0f501eeca7a5eb108440bae20e75c7fb96432e611544c348282b9abe62be41b48e2fb68f06d22749f6e61a0fd05a7736a2864dbd35e37157e9345b2b304ff1f5b5cc9a69b7f64b62b7d2c4942b65dd7725782be92b2884ba7e1c4929042f19c5995a4e031cb07501686554e6a07670688d729ba51dd64719b526b4726c95101a159459c8d9e87c8546bf77cfc6f94b680c014e140fe412e9a5d3c53fa42cb7dea39da034b3869839fd70d783172eaaeb2bc4848000b7e36782e8bbbf7d16bb1a617fcbdc57a4b6f1f8579f31088302fe0bc3214e50de4a627b9c3f648bfe8db0c52b98631cb642a5b413e9f181546bd7c8fdd6edc4edfd15886cdbe846729caef143961bc5329d09b6413db4d4144bd19a6ed21016817724c602e909a2e714dda52495b22342d6108bc2b9353a9d1d3fb6248448e3aab8693101bb83bfc75cefeab2999f8429a442a4a85afb036af080be53eda8d7ad0549610d25f1be1db3a08063d2f33cd9a22d22606b40aea4e590b2ea13189323cc6ba26e2cadbf324f039621cec39b28318ec4b0eb69624a185895bf493c561967a036ddee3e7e4477419863df6e7b80d254bc21dec545fc6a1cf81360851a0bd729d883bc33bba6c863e77eaba4d2fdd4ebc63f467fa59dc1d7542f0b082d7769cce5fa37a46e7d17de28afed2fa48a5d524ca8d5ad9cab11bcc47f13c09e1af78f21ef41df23e6a3f82692990de26ba9d46c4d941224fe808bf3d64ef76ef57a0f44b60619ba96fa2d00ae6c5904c6a29e1ec9fe22ce369b42713c7b2ae798835f15b3b7e019b43025af9582a2e340bcd965a875923d82090174b1e56dd0ca3fc1bc9e47026d4ccf4cdd94ac0893ce8705109154f9495b488b3c58b818b7f18dd270e3145c8fd1897e59d5c1f5f66c6e5e22c3e9f22fa520c93001e24471b589bfe50e566f5c13c111af3a9960ae2a841a8f670db52ea4dedf8184e79e71012c5141910547da6ac43467442582e69ed3ab8e4aa24a546d3821b97a7f66f25e72e55b59628762a9fac88aad9973e8c697bde115455107e6efb093149cc394fdec50171391b1f92e8a358b9f2bb17a59c5cd473f7941ee0f2146cad85a8cb93698962b2a9221a6d2e9808de522e6dff064dad004f278c4ed679651b87bf246073fb41088750af7ac3253c5c6fe21c962800b329c659bbd2ee1628b08ab6e4cb4240d14867a2a4bfe23b55edf4ad34174ea118bdf35d3033428cf3bad8fa254151cd0f499e7ed807ffc5054021d2bfb9abfb256fb3bc77571e5d4469f3f5ab84414a1ca74ec329c489366f23961b2b3a53cff73afdfd4b8c891dd62ec0214536f6a760011bd7b5426e315a35006e82e0e4db9a5b432adfcb285462b83e453b6571c371a21be7fd7266809ba0527d366fc680b150c495785c9aad590c329b98c3f3597994c1cced78b7c00d79348b26eb52c4e952558236b4c6b3b7446d9cd5c7e7719063643d10977b3a78368f17bf5d0e0a7fb82ed3e88aff6c4ff1985521df45fa58afc41572c9f942c733cffe2ad111943eb6a483afb34d4faba4af57be2048019e7c8dfbf43963ec3379c484c86ff7ac67b5db931dd80d28c39733eb95ccca1b0c472d5ad042097c3db699976baf7b5c9b895bc91c102b16aeaa89de1c9a168413f8c2df93682cd201d559259e16d539cca0d92bb1ce66b60d5e40dddd14c12d37b1a61810c23c3a655ebdff89a45acdf3ca99cbb6f23a0d9a2bc31adc9f798381cd2ffd75bc7c22398418b21f6e38133963595d2ef88fb2e54911c15776d9ae336ca7760a969504b2d700095943c76aea975afda0b7a00e7e647264818980e995e0796c772ac0ab166dd937fc5a601f09dc5e408b35e1b16fd3421ae4b3dd349c8252b1ee4cd59794e0b63dba4238498aeb7d3baf5122563aa71b56919ad05301d44f68ad9ed3ae569e733d99f01ea8d91cfd5854ef03b3ba86b225c343ee0785f2521161344428ee7bd90a0f21567e59d0e559c9d046dbf75987b66915fd5c29827dd7a7447f738f06e215b0e1a465e7a3723428c6e0ff27710211f0b36cfef21f7946e8b98ad3c221abcb19f7b8589bc0ea38e3b845590ae3c08396d6597385ca93e9b9962352d0846f248d68360c201a6dc9a1208b1ce5cc6fced87383ebd94c902e99532761c95dd9f9f3953245a12d43a04e0e7ba7ee8199922abdc7a411c45712ae0839b2a1a70854f01ac68b57cefc478c3e9a197d1af9e51836bd01d70bb633c20ade6d77a0fc39b7f914d4d80380b2e062253fcf3091acdaa199e7317f73623bb8316cfcc18f61b397993c7d6b1f07b2fce6603a249b5858f0f071a874ecc5bb5f525ea6aa333fb4d26a810cc0c5576a4d5772dc77c05786cfecca8d9bd862fc1e952d461b8067e0a00cd757795f77340923dc140635870d26f0f34b4468875d026d111f56a464393ce744f709d4d4813bdc426e6fb3259d9f76146efec0f4999045d6ce12709342259ca13d4ca6430eaad18adfd632c25d771dbda3dec07389af255d7db81fe6fdb6da59b4f8adc876843e319c3f9fe6716e4e4819e529f126b1433f379322cb24d1c44d5c86b2c9f1998ff6dc7451ad11cd1c4a09232f1068298ac7346f6ffc2012c959927477ea2c92a23c6ccab449aed9fc7b7e6adbfdfa7c141797dc0f5ea18ea719ad3632dfcf83dc1019bc676c01dd54bff868a5c664bc94b8b6bb7f6cb26679a7710bbd609640d0983bb438806fbbbafffabc36be4437ede0a6e5f1d0058604dca937d09c6aba45c63438e352bedf93df57f46887664a252d5297a4f88aafd247907f194d115f75f02d0642f28018c49d829e6abe1f827515b9530de240698041b4e265f1387368081dcac217be5c66625ec8c6db96585c9f872942b0078d44881457f1032647972ea053fc79a9ab60abfae6392009981e7d0865ba87e1175e40a4ab47f00f45304dd49e69698a6ba078b36bf822ffb69ab6359ec3acdfa9e80d3a85faaf6480968af359cb1d70636fa3b6c488656e01784c0c01c152cf4f61aa3b69e99df84de84c92493465f1d0ecb7c2b3b7eb0179ea2d8260c107e1a612af1648b7b82727c599a6fe52dd59be808863c021bb13c7d888fce865519614696689a0adb7de154fd80993853407578253047219ca1b3cf2426676f42e45b7f7ee64ca2c300d3585bbed4639956ff909e22be9b3de02ea9dbcfdf8dcc4225471a7ad6603244bbd54daa44dbf361b2ba87ed77c607e476eefd1c7ae9944b122107e7e16e828aa9b5fe96ac792736900333d039b91d2cd762e4cc8718bd34c56538f85eda4b34666defd47e2dc96c9fe77e53c027d2302f7129db1474c9354cd07f4c0f59b88fcc38921a23e40a0fe4d78f88fca4121ed81454e9cd64bdce282ef1097e42ee72ca2cdf8491592702453b838a26bad7773de66c8b40f437fd7adcd2e72762e506735c5a3a7c48d91b89ae45006032023d7f7b4d8cc8bb6ffd353108287c5cbb2b376f0fd7b66240e82f40b184fa1eeea02c097ca72a9dd6e5a7653c7e0ce6117ba2eff78344d981ac4572035f311f99a9c6cd9baaed11a6153918ce330e603153b8f95baae1dd131ff5892b5e66d72890e8dd172b4de78c7b361ccb09b649cefe444c358cb5868325a15a73375fdf129f06bb90738b8aa70700bdeb4c7f0462c1fc0963d4c9a0722031da110265cecac78db92b450d2d3f31a5187c8bbe8f534a06385d951f87df053c780376cbba7aebec2118bd5003ab3ca03e6d633c6063aaf2c27054eb575d0be452eea09077b96a8bf8d3f136b37df569c6d70acc92069f2314cd5f80904a9990d8ba72b5c23770246a22040253d7fdd668f4f62d953005c7e0967adfcdd0ad7c375900ce8e136d11498342a68ff2e1d8a33c387720a3a4a70f5120abbca55c663777a42c623a0b6c6263acf0ab7874d0d8b8a5cc8799f2054328589a8954d04e8b1326e3538da504c7571b598e666d18055073de5e4978e54d023c96c53f0536e67d71d0810b -Tag = 1355862d917943aaa7ea7d18a8ed5b2f +Tag = 1b668695f48ea9fb1b9c8a5e178605c3 diff --git a/Unit Tests/Tests/TestDECCipherModesGCM.pas b/Unit Tests/Tests/TestDECCipherModesGCM.pas index b7f5f6cc..e4425ea9 100644 --- a/Unit Tests/Tests/TestDECCipherModesGCM.pas +++ b/Unit Tests/Tests/TestDECCipherModesGCM.pas @@ -142,6 +142,9 @@ TestTDECGCM = class(TTestCase) procedure DoTestEncodeStream_LoadAndTestCAVSData(const aMaxChunkSize: Int64); procedure DoTestEncodeStream_TestSingleSet(const aSetIndex, aDataIndex: Integer; const aMaxChunkSize: Int64 = -1); + procedure DoEncodeStreamChunkList(const AKey, AIV, APT, AAD, ACT, + ATag: RawByteString; ATagBits: Integer; + const AChunkSizes: array of Integer); public procedure SetUp; override; procedure TearDown; override; @@ -153,6 +156,14 @@ TestTDECGCM = class(TTestCase) procedure TestEncodeStream; procedure TestEncodeLargeStream; procedure TestEncodeStreamChunked; + /// + /// CAVS set 105 (first 2-block PT) with two equal 16-byte EncodeStream calls. + /// + procedure TestEncodeStreamMultiChunkTwoBlocks; + /// + /// Same vector with uneven chunks (7 + 25) to exercise GHASH partial blocks. + /// + procedure TestEncodeStreamMultiChunkUneven; procedure TestSetGetDataToAuthenticate; procedure TestSetGetAuthenticationBitLength; procedure TestGetStandardAuthenticationTagBitLengths; @@ -398,7 +409,7 @@ procedure TestTDECGCM.TestDecode; string(TestDataSet.TestData[i].CT) + ' Act.: ' + StringOf(TFormat_HexL.Encode(DecryptData))); - // Additional Authentication Data prüfen + // Additional Authentication Data pr�fen CheckEquals(string(TestDataSet.TestData[i].TagResult), StringOf(TFormat_HexL.Encode(FCipherAES.CalculatedAuthenticationResult)), 'Authentication tag wrong for key ' + @@ -497,7 +508,7 @@ procedure TestTDECGCM.TestEncode; string(TestDataSet.TestData[i].CT) + ' Act.: ' + EncrDataStr); - // Additional Authentication Data prüfen + // Additional Authentication Data pr�fen CheckEquals(string(TestDataSet.TestData[i].TagResult), StringOf(TFormat_HexL.Encode(FCipherAES.CalculatedAuthenticationResult)), 'Authentication tag wrong for Key ' + @@ -584,6 +595,8 @@ procedure TestTDECGCM.TestEncodeConstData_86; cipher.DataToAuthenticate := hea; cipher.Encode(refPlainText, ciphText, sizeof(refPlainText)); + // Tag is finalized in Done (multi-call GHASH); required before reading the tag + cipher.Done; tag := cipher.CalculatedAuthenticationResult; finally cipher.Free; @@ -691,7 +704,7 @@ procedure TestTDECGCM.TestDecodeStream; string(TestDataSet.TestData[i].CT) + ' Act.: ' + StringOf(TFormat_HexL.Encode(DecryptData))); - // Additional Authentication Data prüfen + // Additional Authentication Data pr�fen CheckEquals(string(TestDataSet.TestData[i].TagResult), StringOf(TFormat_HexL.Encode(FCipherAES.CalculatedAuthenticationResult)), 'Authentication tag wrong for key ' + @@ -719,6 +732,81 @@ procedure TestTDECGCM.TestEncodeStreamChunked; Max(FCipherAES.Context.BlockSize, FCipherAES.Context.BufferSize)); end; +procedure TestTDECGCM.DoEncodeStreamChunkList(const AKey, AIV, APT, AAD, ACT, + ATag: RawByteString; ATagBits: Integer; const AChunkSizes: array of Integer); +var + ptBytes, EncryptData, EmptyAAD: TBytes; + ptbStream, ctbStream: TBytesStream; + i, offset, n: Integer; +begin + ptBytes := TFormat_HexL.Decode(BytesOf(APT)); + FCipherAES.Init(BytesOf(TFormat_HexL.Decode(AKey)), + BytesOf(TFormat_HexL.Decode(AIV)), $FF); + FCipherAES.AuthenticationResultBitLength := ATagBits; + if AAD <> '' then + FCipherAES.DataToAuthenticate := TFormat_HexL.Decode(BytesOf(AAD)) + else + begin + SetLength(EmptyAAD, 0); + FCipherAES.DataToAuthenticate := EmptyAAD; + end; + + ptbStream := TBytesStream.Create(ptBytes); + ctbStream := TBytesStream.Create; + try + offset := 0; + for i := Low(AChunkSizes) to High(AChunkSizes) do + begin + n := AChunkSizes[i]; + CheckTrue(offset + n <= Length(ptBytes), + 'Chunk list exceeds plaintext length'); + FCipherAES.EncodeStream(ptbStream, ctbStream, n); + Inc(offset, n); + end; + CheckEquals(Length(ptBytes), offset, 'Chunk sizes must cover full PT'); + FCipherAES.Done; + EncryptData := ctbStream.Bytes; + SetLength(EncryptData, ctbStream.Size); + finally + ptbStream.Free; + ctbStream.Free; + end; + + CheckEquals(string(ACT), StringOf(TFormat_HexL.Encode(EncryptData)), + 'Ciphertext mismatch for multi-chunk encode'); + CheckEquals(string(ATag), + StringOf(TFormat_HexL.Encode(FCipherAES.CalculatedAuthenticationResult)), + 'Authentication tag mismatch for multi-chunk encode'); +end; + +procedure TestTDECGCM.TestEncodeStreamMultiChunkTwoBlocks; +begin + // NIST gcmEncryptExtIV128: first PTlen=256 set (set 105), Count=0 — 32-byte PT + DoEncodeStreamChunkList( + '9971071059abc009e4f2bd69869db338', + '07a9a95ea3821e9c13c63251', + 'f54bc3501fed4f6f6dfb5ea80106df0bd836e6826225b75c0222f6e859b35983', + '', + '0556c159f84ef36cb1602b4526b12009c775611bffb64dc0d9ca9297cd2c6a01', + '7870d9117f54811a346970f1de090c41', + 128, + [16, 16]); +end; + +procedure TestTDECGCM.TestEncodeStreamMultiChunkUneven; +begin + // Same CAVS vector; GHASH must carry a partial block between calls + DoEncodeStreamChunkList( + '9971071059abc009e4f2bd69869db338', + '07a9a95ea3821e9c13c63251', + 'f54bc3501fed4f6f6dfb5ea80106df0bd836e6826225b75c0222f6e859b35983', + '', + '0556c159f84ef36cb1602b4526b12009c775611bffb64dc0d9ca9297cd2c6a01', + '7870d9117f54811a346970f1de090c41', + 128, + [7, 25]); +end; + procedure TestTDECGCM.DoTestEncodeStream_LoadAndTestCAVSData(const aMaxChunkSize: Int64); var @@ -789,9 +877,9 @@ procedure TestTDECGCM.DoTestEncodeStream_TestSingleSet(const aSetIndex, if aMaxChunkSize > 0 then curChunkSize := Min(dataLeftToEncode, aMaxChunkSize); // Darf vermutlich so nicht sein, es darf vermutlich nur einen EncodeStream Aufruf -// geben. Möglicherwiese ist das Padding wie es jetzt umgesetzt ist nicht ganz richtig, -// da man sonst keinen dynamischen Stream haben kann. Gehört vermutlich ins Done, -// aber das hat noch keinen Stream, braucht also eine überladene Variante mit +// geben. M�glicherwiese ist das Padding wie es jetzt umgesetzt ist nicht ganz richtig, +// da man sonst keinen dynamischen Stream haben kann. Geh�rt vermutlich ins Done, +// aber das hat noch keinen Stream, braucht also eine �berladene Variante mit // Outputstream als Parameter... // Zuerst test mal ohne Schleife testen. EncodeStream darf nicht anhand der Size // das "globale" Ende des Streams ermitteln, sonst nichts nachschiebbar. @@ -821,7 +909,7 @@ procedure TestTDECGCM.DoTestEncodeStream_TestSingleSet(const aSetIndex, string(TestDataSet.TestData[aDataIndex].AAD) + ' Act.: ' + StringOf(TFormat_HexL.Encode(FCipherAES.DataToAuthenticate))); - // Additional Authentication Data prüfen + // Additional Authentication Data pr�fen CheckEquals(string(TestDataSet.TestData[aDataIndex].TagResult), StringOf(TFormat_HexL.Encode(FCipherAES.CalculatedAuthenticationResult)), 'Authentication tag wrong for Set ' + aSetIndex.ToString + ' and Data ' + aDataIndex.ToString + From 8bff62bc3b470e47e5823c7459d36ba996e78897 Mon Sep 17 00:00:00 2001 From: Olaf Monien Date: Thu, 23 Jul 2026 13:18:06 +0200 Subject: [PATCH 2/3] Make GCM Done idempotent and reject post-finalization updates. Add FFinalized so repeated Done leaves the tag unchanged and Encode/Decode after finalization raise until Init. Document the contract in Option A and add lifecycle regression tests (double Done, reject after Done, uneven multi-chunk decrypt). --- Source/DECCipherModesGCM.pas | 21 +++ Unit Tests/Tests/TestDECCipherModesGCM.pas | 153 ++++++++++++++++++++- 2 files changed, 173 insertions(+), 1 deletion(-) diff --git a/Source/DECCipherModesGCM.pas b/Source/DECCipherModesGCM.pas index c5594720..1313b71c 100644 --- a/Source/DECCipherModesGCM.pas +++ b/Source/DECCipherModesGCM.pas @@ -104,6 +104,11 @@ TGCM = class(TAuthenticatedCipherModesBase) /// Number of unused bytes remaining in FKeystream (0..15) /// FKeystreamRemain : Integer; + /// + /// True after Done has materialized the authentication tag. + /// Prevents double-finalization and post-Done GHASH/CTR updates. + /// + FFinalized : Boolean; /// /// XOR implementation for unsigned 128 bit numbers @@ -317,6 +322,8 @@ TGCM = class(TAuthenticatedCipherModesBase) /// /// Finishes GHASH and materializes CalculatedAuthenticationTag. /// Must be called after the last Encode/Decode (cipher Done does this). + /// Idempotent: a second call leaves the tag unchanged. + /// After finalization, Encode/Decode raise until Init is called again. /// procedure Done; @@ -332,6 +339,10 @@ TGCM = class(TAuthenticatedCipherModesBase) implementation +resourcestring + sGCMAlreadyFinalized = + 'GCM authentication already finalized; call Init before further Encode/Decode'; + function TGCM.XOR_T128(const x, y : T128): T128; begin Result[0] := x[0] xor y[0]; @@ -506,6 +517,7 @@ procedure TGCM.Init(EncryptionMethod : TEncodeDecodeMethod; FKeystreamRemain := 0; FKeystream[0] := 0; FKeystream[1] := 0; + FFinalized := False; OldH := FH; EncryptionMethod(@Nullbytes[0], @FH[0], 16); @@ -614,7 +626,10 @@ procedure TGCM.FinalizeAuthenticationTag; procedure TGCM.Done; begin + if FFinalized then + Exit; FinalizeAuthenticationTag; + FFinalized := True; end; function TGCM.CalcGaloisHash(AuthenticatedData : PUInt8Array; AuthLen : integer; Ciphertext : PUInt8Array; @@ -710,6 +725,9 @@ procedure TGCM.ApplyCTR(Source, Dest: PUInt8Array; Size: Integer); procedure TGCM.Decode(Source, Dest: PUInt8Array; Size: Integer); begin + if FFinalized then + raise EDECCipherException.CreateRes(@sGCMAlreadyFinalized); + // AAD into GHASH once; tag finalized in Done (supports multi-call streams) EnsureAuthDataHashed; @@ -728,6 +746,9 @@ procedure TGCM.Decode(Source, Dest: PUInt8Array; Size: Integer); procedure TGCM.Encode(Source, Dest: PUInt8Array; Size: Integer); begin + if FFinalized then + raise EDECCipherException.CreateRes(@sGCMAlreadyFinalized); + // AAD into GHASH once; tag finalized in Done (supports multi-call streams) EnsureAuthDataHashed; diff --git a/Unit Tests/Tests/TestDECCipherModesGCM.pas b/Unit Tests/Tests/TestDECCipherModesGCM.pas index e4425ea9..3e7c03f4 100644 --- a/Unit Tests/Tests/TestDECCipherModesGCM.pas +++ b/Unit Tests/Tests/TestDECCipherModesGCM.pas @@ -33,6 +33,7 @@ interface Generics.Collections, System.Math, DECBaseClass, + DECTypes, DECCipherBase, DECCipherModes, DECCipherFormats, @@ -136,6 +137,10 @@ TestTDECGCM = class(TTestCase) // Needed for passing data to and from DoTestDecodeFailure FDecryptedData : TBytes; FCipherText : TBytes; + /// + /// Scratch PT for post-Done Encode/Decode exception helpers + /// + FLifecycleScratch : TBytes; private function IsEqual(const a, b: TBytes): Boolean; procedure DoTestDecodeFailure; @@ -145,6 +150,11 @@ TestTDECGCM = class(TTestCase) procedure DoEncodeStreamChunkList(const AKey, AIV, APT, AAD, ACT, ATag: RawByteString; ATagBits: Integer; const AChunkSizes: array of Integer); + procedure DoDecodeStreamChunkList(const AKey, AIV, APT, AAD, ACT, + ATag: RawByteString; ATagBits: Integer; + const AChunkSizes: array of Integer); + procedure DoEncodeAfterDone; + procedure DoDecodeAfterDone; public procedure SetUp; override; procedure TearDown; override; @@ -164,6 +174,22 @@ TestTDECGCM = class(TTestCase) /// Same vector with uneven chunks (7 + 25) to exercise GHASH partial blocks. /// procedure TestEncodeStreamMultiChunkUneven; + /// + /// Same vector; multi-chunk decrypt with expected-tag verification. + /// + procedure TestDecodeStreamMultiChunkUneven; + /// + /// Done twice must leave CalculatedAuthenticationResult unchanged. + /// + procedure TestDoneIdempotent; + /// + /// Encode after Done must raise until Init is called again. + /// + procedure TestEncodeAfterDoneRejected; + /// + /// Decode after Done must raise until Init is called again. + /// + procedure TestDecodeAfterDoneRejected; procedure TestSetGetDataToAuthenticate; procedure TestSetGetAuthenticationBitLength; procedure TestGetStandardAuthenticationTagBitLengths; @@ -181,7 +207,6 @@ implementation uses System.Classes, - DECTypes, DECFormat; { TGCMTestDataLoader } @@ -807,6 +832,132 @@ procedure TestTDECGCM.TestEncodeStreamMultiChunkUneven; [7, 25]); end; +procedure TestTDECGCM.DoDecodeStreamChunkList(const AKey, AIV, APT, AAD, ACT, + ATag: RawByteString; ATagBits: Integer; const AChunkSizes: array of Integer); +var + ctBytes, DecryptData, EmptyAAD: TBytes; + ctbStream, ptbStream: TBytesStream; + i, offset, n: Integer; +begin + ctBytes := TFormat_HexL.Decode(BytesOf(ACT)); + FCipherAES.Init(BytesOf(TFormat_HexL.Decode(AKey)), + BytesOf(TFormat_HexL.Decode(AIV)), $FF); + FCipherAES.AuthenticationResultBitLength := ATagBits; + if AAD <> '' then + FCipherAES.DataToAuthenticate := TFormat_HexL.Decode(BytesOf(AAD)) + else + begin + SetLength(EmptyAAD, 0); + FCipherAES.DataToAuthenticate := EmptyAAD; + end; + FCipherAES.ExpectedAuthenticationResult := + TFormat_HexL.Decode(BytesOf(ATag)); + + ctbStream := TBytesStream.Create(ctBytes); + ptbStream := TBytesStream.Create; + try + offset := 0; + for i := Low(AChunkSizes) to High(AChunkSizes) do + begin + n := AChunkSizes[i]; + CheckTrue(offset + n <= Length(ctBytes), + 'Chunk list exceeds ciphertext length'); + FCipherAES.DecodeStream(ctbStream, ptbStream, n); + Inc(offset, n); + end; + CheckEquals(Length(ctBytes), offset, 'Chunk sizes must cover full CT'); + FCipherAES.Done; + DecryptData := ptbStream.Bytes; + SetLength(DecryptData, ptbStream.Size); + finally + ctbStream.Free; + ptbStream.Free; + end; + + CheckEquals(string(APT), StringOf(TFormat_HexL.Encode(DecryptData)), + 'Plaintext mismatch for multi-chunk decode'); + CheckEquals(string(ATag), + StringOf(TFormat_HexL.Encode(FCipherAES.CalculatedAuthenticationResult)), + 'Authentication tag mismatch for multi-chunk decode'); +end; + +procedure TestTDECGCM.TestDecodeStreamMultiChunkUneven; +begin + DoDecodeStreamChunkList( + '9971071059abc009e4f2bd69869db338', + '07a9a95ea3821e9c13c63251', + 'f54bc3501fed4f6f6dfb5ea80106df0bd836e6826225b75c0222f6e859b35983', + '', + '0556c159f84ef36cb1602b4526b12009c775611bffb64dc0d9ca9297cd2c6a01', + '7870d9117f54811a346970f1de090c41', + 128, + [7, 25]); +end; + +procedure TestTDECGCM.TestDoneIdempotent; +var + Tag1, Tag2: TBytes; +begin + DoEncodeStreamChunkList( + '9971071059abc009e4f2bd69869db338', + '07a9a95ea3821e9c13c63251', + 'f54bc3501fed4f6f6dfb5ea80106df0bd836e6826225b75c0222f6e859b35983', + '', + '0556c159f84ef36cb1602b4526b12009c775611bffb64dc0d9ca9297cd2c6a01', + '7870d9117f54811a346970f1de090c41', + 128, + [7, 25]); + Tag1 := Copy(FCipherAES.CalculatedAuthenticationResult); + FCipherAES.Done; + Tag2 := FCipherAES.CalculatedAuthenticationResult; + CheckTrue(IsEqual(Tag1, Tag2), + 'Second Done must not change CalculatedAuthenticationResult'); +end; + +procedure TestTDECGCM.DoEncodeAfterDone; +begin + FCipherAES.EncodeBytes(FLifecycleScratch); +end; + +procedure TestTDECGCM.DoDecodeAfterDone; +begin + FCipherAES.DecodeBytes(FLifecycleScratch); +end; + +procedure TestTDECGCM.TestEncodeAfterDoneRejected; +begin + DoEncodeStreamChunkList( + '9971071059abc009e4f2bd69869db338', + '07a9a95ea3821e9c13c63251', + 'f54bc3501fed4f6f6dfb5ea80106df0bd836e6826225b75c0222f6e859b35983', + '', + '0556c159f84ef36cb1602b4526b12009c775611bffb64dc0d9ca9297cd2c6a01', + '7870d9117f54811a346970f1de090c41', + 128, + [16, 16]); + SetLength(FLifecycleScratch, 16); + FillChar(FLifecycleScratch[0], Length(FLifecycleScratch), $A5); + CheckException(DoEncodeAfterDone, EDECCipherException, + 'Encode after Done must raise EDECCipherException'); +end; + +procedure TestTDECGCM.TestDecodeAfterDoneRejected; +begin + DoEncodeStreamChunkList( + '9971071059abc009e4f2bd69869db338', + '07a9a95ea3821e9c13c63251', + 'f54bc3501fed4f6f6dfb5ea80106df0bd836e6826225b75c0222f6e859b35983', + '', + '0556c159f84ef36cb1602b4526b12009c775611bffb64dc0d9ca9297cd2c6a01', + '7870d9117f54811a346970f1de090c41', + 128, + [16, 16]); + SetLength(FLifecycleScratch, 16); + FillChar(FLifecycleScratch[0], Length(FLifecycleScratch), $5A); + CheckException(DoDecodeAfterDone, EDECCipherException, + 'Decode after Done must raise EDECCipherException'); +end; + procedure TestTDECGCM.DoTestEncodeStream_LoadAndTestCAVSData(const aMaxChunkSize: Int64); var From da75fc33d803ee5ceaef3f0fb529f03f81aa2683 Mon Sep 17 00:00:00 2001 From: Olaf Monien Date: Thu, 23 Jul 2026 13:18:17 +0200 Subject: [PATCH 3/3] Extract shared CAVS multi-chunk vector constants in GCM tests. Deduplicate the NIST set-105 key/IV/PT/CT/tag literals used by the six multi-chunk and Done-lifecycle tests (CodeRabbit DRY nitpick). --- Unit Tests/Tests/TestDECCipherModesGCM.pas | 76 +++++++++------------- 1 file changed, 32 insertions(+), 44 deletions(-) diff --git a/Unit Tests/Tests/TestDECCipherModesGCM.pas b/Unit Tests/Tests/TestDECCipherModesGCM.pas index 3e7c03f4..fc53cf44 100644 --- a/Unit Tests/Tests/TestDECCipherModesGCM.pas +++ b/Unit Tests/Tests/TestDECCipherModesGCM.pas @@ -804,31 +804,35 @@ procedure TestTDECGCM.DoEncodeStreamChunkList(const AKey, AIV, APT, AAD, ACT, 'Authentication tag mismatch for multi-chunk encode'); end; +// NIST gcmEncryptExtIV128: first PTlen=256 set (set 105), Count=0 — 32-byte PT. +// Shared by multi-chunk encode/decode and Done-lifecycle regression tests. +const + cCAVS_MultiChunkKey : RawByteString = '9971071059abc009e4f2bd69869db338'; + cCAVS_MultiChunkIV : RawByteString = '07a9a95ea3821e9c13c63251'; + cCAVS_MultiChunkPT : RawByteString = + 'f54bc3501fed4f6f6dfb5ea80106df0bd836e6826225b75c0222f6e859b35983'; + cCAVS_MultiChunkAAD : RawByteString = ''; + cCAVS_MultiChunkCT : RawByteString = + '0556c159f84ef36cb1602b4526b12009c775611bffb64dc0d9ca9297cd2c6a01'; + cCAVS_MultiChunkTag : RawByteString = '7870d9117f54811a346970f1de090c41'; + cCAVS_MultiChunkTagBits = 128; + procedure TestTDECGCM.TestEncodeStreamMultiChunkTwoBlocks; begin - // NIST gcmEncryptExtIV128: first PTlen=256 set (set 105), Count=0 — 32-byte PT DoEncodeStreamChunkList( - '9971071059abc009e4f2bd69869db338', - '07a9a95ea3821e9c13c63251', - 'f54bc3501fed4f6f6dfb5ea80106df0bd836e6826225b75c0222f6e859b35983', - '', - '0556c159f84ef36cb1602b4526b12009c775611bffb64dc0d9ca9297cd2c6a01', - '7870d9117f54811a346970f1de090c41', - 128, + cCAVS_MultiChunkKey, cCAVS_MultiChunkIV, cCAVS_MultiChunkPT, + cCAVS_MultiChunkAAD, cCAVS_MultiChunkCT, cCAVS_MultiChunkTag, + cCAVS_MultiChunkTagBits, [16, 16]); end; procedure TestTDECGCM.TestEncodeStreamMultiChunkUneven; begin - // Same CAVS vector; GHASH must carry a partial block between calls + // GHASH must carry a partial block between calls DoEncodeStreamChunkList( - '9971071059abc009e4f2bd69869db338', - '07a9a95ea3821e9c13c63251', - 'f54bc3501fed4f6f6dfb5ea80106df0bd836e6826225b75c0222f6e859b35983', - '', - '0556c159f84ef36cb1602b4526b12009c775611bffb64dc0d9ca9297cd2c6a01', - '7870d9117f54811a346970f1de090c41', - 128, + cCAVS_MultiChunkKey, cCAVS_MultiChunkIV, cCAVS_MultiChunkPT, + cCAVS_MultiChunkAAD, cCAVS_MultiChunkCT, cCAVS_MultiChunkTag, + cCAVS_MultiChunkTagBits, [7, 25]); end; @@ -884,13 +888,9 @@ procedure TestTDECGCM.DoDecodeStreamChunkList(const AKey, AIV, APT, AAD, ACT, procedure TestTDECGCM.TestDecodeStreamMultiChunkUneven; begin DoDecodeStreamChunkList( - '9971071059abc009e4f2bd69869db338', - '07a9a95ea3821e9c13c63251', - 'f54bc3501fed4f6f6dfb5ea80106df0bd836e6826225b75c0222f6e859b35983', - '', - '0556c159f84ef36cb1602b4526b12009c775611bffb64dc0d9ca9297cd2c6a01', - '7870d9117f54811a346970f1de090c41', - 128, + cCAVS_MultiChunkKey, cCAVS_MultiChunkIV, cCAVS_MultiChunkPT, + cCAVS_MultiChunkAAD, cCAVS_MultiChunkCT, cCAVS_MultiChunkTag, + cCAVS_MultiChunkTagBits, [7, 25]); end; @@ -899,13 +899,9 @@ procedure TestTDECGCM.TestDoneIdempotent; Tag1, Tag2: TBytes; begin DoEncodeStreamChunkList( - '9971071059abc009e4f2bd69869db338', - '07a9a95ea3821e9c13c63251', - 'f54bc3501fed4f6f6dfb5ea80106df0bd836e6826225b75c0222f6e859b35983', - '', - '0556c159f84ef36cb1602b4526b12009c775611bffb64dc0d9ca9297cd2c6a01', - '7870d9117f54811a346970f1de090c41', - 128, + cCAVS_MultiChunkKey, cCAVS_MultiChunkIV, cCAVS_MultiChunkPT, + cCAVS_MultiChunkAAD, cCAVS_MultiChunkCT, cCAVS_MultiChunkTag, + cCAVS_MultiChunkTagBits, [7, 25]); Tag1 := Copy(FCipherAES.CalculatedAuthenticationResult); FCipherAES.Done; @@ -927,13 +923,9 @@ procedure TestTDECGCM.DoDecodeAfterDone; procedure TestTDECGCM.TestEncodeAfterDoneRejected; begin DoEncodeStreamChunkList( - '9971071059abc009e4f2bd69869db338', - '07a9a95ea3821e9c13c63251', - 'f54bc3501fed4f6f6dfb5ea80106df0bd836e6826225b75c0222f6e859b35983', - '', - '0556c159f84ef36cb1602b4526b12009c775611bffb64dc0d9ca9297cd2c6a01', - '7870d9117f54811a346970f1de090c41', - 128, + cCAVS_MultiChunkKey, cCAVS_MultiChunkIV, cCAVS_MultiChunkPT, + cCAVS_MultiChunkAAD, cCAVS_MultiChunkCT, cCAVS_MultiChunkTag, + cCAVS_MultiChunkTagBits, [16, 16]); SetLength(FLifecycleScratch, 16); FillChar(FLifecycleScratch[0], Length(FLifecycleScratch), $A5); @@ -944,13 +936,9 @@ procedure TestTDECGCM.TestEncodeAfterDoneRejected; procedure TestTDECGCM.TestDecodeAfterDoneRejected; begin DoEncodeStreamChunkList( - '9971071059abc009e4f2bd69869db338', - '07a9a95ea3821e9c13c63251', - 'f54bc3501fed4f6f6dfb5ea80106df0bd836e6826225b75c0222f6e859b35983', - '', - '0556c159f84ef36cb1602b4526b12009c775611bffb64dc0d9ca9297cd2c6a01', - '7870d9117f54811a346970f1de090c41', - 128, + cCAVS_MultiChunkKey, cCAVS_MultiChunkIV, cCAVS_MultiChunkPT, + cCAVS_MultiChunkAAD, cCAVS_MultiChunkCT, cCAVS_MultiChunkTag, + cCAVS_MultiChunkTagBits, [16, 16]); SetLength(FLifecycleScratch, 16); FillChar(FLifecycleScratch[0], Length(FLifecycleScratch), $5A);