2021-09-07 18:07:00 -04:00
// Copyright Epic Games, Inc. All Rights Reserved.
# include "ZenSerialization.h"
2022-05-31 12:42:17 -04:00
# include "Compression/OodleDataCompression.h"
2022-11-07 08:32:37 -05:00
# include "HAL/FileManager.h"
2023-03-23 05:15:22 -04:00
# include "Misc/ScopeExit.h"
2021-09-07 18:07:00 -04:00
# include "Serialization/CompactBinary.h"
# include "Serialization/CompactBinaryPackage.h"
# include "Serialization/CompactBinarySerialization.h"
# include "Serialization/CompactBinaryValidation.h"
# include "Serialization/CompactBinaryWriter.h"
2023-03-23 05:15:22 -04:00
# if PLATFORM_WINDOWS
# include "Windows / WindowsHWrapper.h"
# endif // PLATFORM_WINDOWS
2021-09-07 18:07:00 -04:00
# if UE_WITH_ZEN
2022-11-29 06:18:18 -05:00
DEFINE_LOG_CATEGORY_STATIC ( LogZenSerialization , Log , All ) ;
2022-05-31 12:42:17 -04:00
namespace UE : : Zen
{
2021-09-07 18:07:00 -04:00
2022-05-31 12:42:17 -04:00
namespace OpLog
{
2021-09-07 18:07:00 -04:00
void SaveCbAttachment ( const FCbAttachment & Attachment , FCbWriter & Writer )
{
2021-10-27 15:14:40 -04:00
if ( Attachment . IsCompressedBinary ( ) )
2021-09-07 18:07:00 -04:00
{
2021-10-27 15:14:40 -04:00
Writer . AddBinary ( Attachment . AsCompressedBinary ( ) . GetCompressed ( ) ) ;
2021-09-07 18:07:00 -04:00
Writer . AddBinaryAttachment ( Attachment . GetHash ( ) ) ;
}
else if ( Attachment . IsNull ( ) )
{
Writer . AddBinary ( FMemoryView ( ) ) ;
}
else
{
2021-10-27 15:14:40 -04:00
// NOTE: All attachments needs to be compressed
2021-09-07 18:07:00 -04:00
checkNoEntry ( ) ;
}
}
void SaveCbPackage ( const FCbPackage & Package , FCbWriter & Writer )
{
if ( const FCbObject & RootObject = Package . GetObject ( ) )
{
Writer . AddObject ( RootObject ) ;
Writer . AddObjectAttachment ( Package . GetObjectHash ( ) ) ;
}
for ( const FCbAttachment & Attachment : Package . GetAttachments ( ) )
{
SaveCbAttachment ( Attachment , Writer ) ;
}
Writer . AddNull ( ) ;
}
void SaveCbPackage ( const FCbPackage & Package , FArchive & Ar )
{
FCbWriter Writer ;
SaveCbPackage ( Package , Writer ) ;
Writer . Save ( Ar ) ;
}
bool TryLoadCbPackage ( FCbPackage & Package , FArchive & Ar , FCbBufferAllocator Allocator )
{
uint8 StackBuffer [ 64 ] ;
const auto StackAllocator = [ & Allocator , & StackBuffer ] ( uint64 Size ) - > FUniqueBuffer
{
return Size < = sizeof ( StackBuffer ) ? FUniqueBuffer : : MakeView ( StackBuffer , Size ) : Allocator ( Size ) ;
} ;
Package = FCbPackage ( ) ;
for ( ; ; )
{
FCbField ValueField = LoadCompactBinary ( Ar , StackAllocator ) ;
if ( ! ValueField )
{
2022-11-30 04:12:54 -05:00
UE_LOG ( LogZenSerialization , Warning , TEXT ( " Package is malformed, can't read compact binary data " ) ) ;
2021-09-07 18:07:00 -04:00
Ar . SetError ( ) ;
return false ;
}
if ( ValueField . IsNull ( ) )
{
return true ;
}
else if ( ValueField . IsBinary ( ) )
{
const FMemoryView View = ValueField . AsBinaryView ( ) ;
if ( View . GetSize ( ) > 0 )
{
FSharedBuffer Buffer = FSharedBuffer : : MakeView ( View , ValueField . GetOuterBuffer ( ) ) . MakeOwned ( ) ;
FCbField HashField = LoadCompactBinary ( Ar , StackAllocator ) ;
const FIoHash & Hash = HashField . AsAttachment ( ) ;
if ( HashField . HasError ( ) | | FIoHash : : HashBuffer ( Buffer ) ! = Hash )
{
2022-11-30 04:12:54 -05:00
UE_LOG ( LogZenSerialization , Warning , TEXT ( " Package attachment has malformed/invalid attachment hash field " ) ) ;
2021-09-07 18:07:00 -04:00
Ar . SetError ( ) ;
return false ;
}
if ( HashField . IsObjectAttachment ( ) )
{
Package . AddAttachment ( FCbAttachment ( FCbObject ( MoveTemp ( Buffer ) ) , Hash ) ) ;
}
else
{
Package . AddAttachment ( FCbAttachment ( FCompositeBuffer ( MoveTemp ( Buffer ) ) , Hash ) ) ;
}
}
}
else
{
FCbObject Object = ValueField . AsObject ( ) ;
if ( ValueField . HasError ( ) )
{
2022-11-30 04:12:54 -05:00
UE_LOG ( LogZenSerialization , Warning , TEXT ( " Package attachment has malformed/invalid object field " ) ) ;
2021-09-07 18:07:00 -04:00
Ar . SetError ( ) ;
return false ;
}
if ( Object )
{
FCbField HashField = LoadCompactBinary ( Ar , StackAllocator ) ;
FIoHash ObjectHash = HashField . AsObjectAttachment ( ) ;
if ( HashField . HasError ( ) | | Object . GetHash ( ) ! = ObjectHash )
{
2022-11-30 04:12:54 -05:00
UE_LOG ( LogZenSerialization , Warning , TEXT ( " Package attachment has malformed/invalid object hash field " ) ) ;
2021-09-07 18:07:00 -04:00
Ar . SetError ( ) ;
return false ;
}
Package . SetObject ( Object , ObjectHash ) ;
}
}
}
}
2022-05-31 12:42:17 -04:00
}
namespace Http
{
struct CbPackageHeader
{
uint32 HeaderMagic ;
uint32 AttachmentCount ;
uint32 Reserved1 ;
uint32 Reserved2 ;
} ;
struct CbAttachmentEntry
{
uint64 AttachmentSize ;
uint32 Flags ;
FIoHash AttachmentHash ;
enum
{
IsCompressed = ( 1u < < 0 ) , // Is marshaled using compressed buffer storage format
IsObject = ( 1u < < 1 ) , // Is compact binary object
2022-11-07 05:33:21 -05:00
kIsError = ( 1u < < 2 ) , // Is error (compact binary formatted) object
kIsLocalRef = ( 1u < < 3 ) , // Is "local reference"
2022-05-31 12:42:17 -04:00
} ;
} ;
2022-11-07 05:33:21 -05:00
struct CbAttachmentReferenceHeader
{
2022-11-29 06:18:18 -05:00
uint64 PayloadByteOffset = 0 ;
uint64 PayloadByteSize = ~ 0u ;
uint16 AbsolutePathLength = 0 ;
2022-11-07 05:33:21 -05:00
// This header will be followed by UTF8 encoded absolute path to backing file
} ;
2022-05-31 12:42:17 -04:00
void SaveCbPackage ( const FCbPackage & Package , FArchive & Ar )
{
TConstArrayView < FCbAttachment > Attachments = Package . GetAttachments ( ) ;
const FCbObject & Object = Package . GetObject ( ) ;
FCompressedBuffer ObjectBuffer = FCompressedBuffer : : Compress ( Object . GetBuffer ( ) , FOodleDataCompression : : ECompressor : : NotSet , FOodleDataCompression : : ECompressionLevel : : None ) ;
CbPackageHeader Hdr ;
2022-10-13 11:17:53 -04:00
Hdr . HeaderMagic = kCbPkgMagic ;
2022-05-31 12:42:17 -04:00
Hdr . AttachmentCount = Attachments . Num ( ) ;
Hdr . Reserved1 = 0 ;
Hdr . Reserved2 = 0 ;
Ar . Serialize ( & Hdr , sizeof Hdr ) ;
// Root object metadata
{
CbAttachmentEntry Entry ;
Entry . AttachmentHash = ObjectBuffer . GetRawHash ( ) ;
Entry . AttachmentSize = ObjectBuffer . GetCompressedSize ( ) ;
Entry . Flags = CbAttachmentEntry : : IsObject | CbAttachmentEntry : : IsCompressed ;
Ar . Serialize ( & Entry , sizeof Entry ) ;
}
// Attachment metadata
for ( const FCbAttachment & Attachment : Attachments )
{
CbAttachmentEntry Entry ;
Entry . AttachmentHash = Attachment . GetHash ( ) ;
Entry . Flags = 0 ;
if ( Attachment . IsCompressedBinary ( ) )
{
Entry . AttachmentSize = Attachment . AsCompressedBinary ( ) . GetCompressedSize ( ) ;
Entry . Flags | = CbAttachmentEntry : : IsCompressed ;
}
else if ( Attachment . IsBinary ( ) )
{
Entry . AttachmentSize = Attachment . AsCompositeBinary ( ) . GetSize ( ) ;
}
else if ( Attachment . IsNull ( ) )
{
checkNoEntry ( ) ;
}
else if ( Attachment . IsObject ( ) )
{
Entry . AttachmentSize = Attachment . AsObject ( ) . GetSize ( ) ;
Entry . Flags | = CbAttachmentEntry : : IsObject ;
}
else
{
checkNoEntry ( ) ;
}
Ar . Serialize ( & Entry , sizeof Entry ) ;
}
// Root object
Ar < < ObjectBuffer ;
// Payloads back-to-back
for ( const FCbAttachment & Attachment : Attachments )
{
if ( Attachment . IsCompressedBinary ( ) )
{
FCompressedBuffer Payload = Attachment . AsCompressedBinary ( ) ;
Ar < < Payload ;
}
else if ( Attachment . IsBinary ( ) )
{
const FCompositeBuffer & Buffer = Attachment . AsCompositeBinary ( ) ;
FSharedBuffer SharedBuffer = Buffer . ToShared ( ) ;
Ar . Serialize ( ( void * ) SharedBuffer . GetData ( ) , SharedBuffer . GetSize ( ) ) ;
}
else if ( Attachment . IsNull ( ) )
{
checkNoEntry ( ) ;
}
else if ( Attachment . IsObject ( ) )
{
checkNoEntry ( ) ;
}
else
{
checkNoEntry ( ) ;
}
}
}
bool TryLoadCbPackage ( FCbPackage & Package , FArchive & Ar , FCbBufferAllocator Allocator )
{
CbPackageHeader Hdr ;
Ar . Serialize ( & Hdr , sizeof Hdr ) ;
2022-10-13 11:17:53 -04:00
if ( Hdr . HeaderMagic ! = kCbPkgMagic )
2022-05-31 12:42:17 -04:00
{
return false ;
}
TArray < CbAttachmentEntry > AttachmentEntries ;
AttachmentEntries . SetNum ( Hdr . AttachmentCount + 1 ) ;
Ar . Serialize ( AttachmentEntries . GetData ( ) , ( Hdr . AttachmentCount + 1 ) * sizeof ( CbAttachmentEntry ) ) ;
int Index = 0 ;
for ( const CbAttachmentEntry & Entry : AttachmentEntries )
{
FUniqueBuffer AttachmentData = FUniqueBuffer : : Alloc ( Entry . AttachmentSize ) ;
Ar . Serialize ( AttachmentData . GetData ( ) , AttachmentData . GetSize ( ) ) ;
2022-11-07 05:33:21 -05:00
if ( Entry . Flags & CbAttachmentEntry : : kIsLocalRef )
{
// Marshal local reference - a "pointer" to the chunk backing file
2022-11-29 06:18:18 -05:00
if ( AttachmentData . GetSize ( ) < sizeof ( CbAttachmentReferenceHeader ) )
{
// We did not get the full package data - treat it as a malformed package
2022-11-30 04:12:54 -05:00
UE_LOG ( LogZenSerialization , Warning , TEXT ( " Package payload is not large enough while trying to read local ref header, need % " UINT64_FMT " bytes, but only % " UINT64_FMT " available. " ) , sizeof ( CbAttachmentReferenceHeader ) , AttachmentData . GetSize ( ) ) ;
2022-11-29 06:18:18 -05:00
Ar . SetError ( ) ;
return false ;
}
2022-11-07 05:33:21 -05:00
if ( ! ( Entry . Flags & CbAttachmentEntry : : IsCompressed ) )
{
2022-11-29 06:18:18 -05:00
// We only support compressed attachments - treat it as an error
2022-11-30 04:12:54 -05:00
UE_LOG ( LogZenSerialization , Error , TEXT ( " Attachment data is not compressed " ) ) ;
2022-11-07 05:33:21 -05:00
Ar . SetError ( ) ;
return false ;
}
const CbAttachmentReferenceHeader * AttachRefHdr = reinterpret_cast < CbAttachmentReferenceHeader * > ( AttachmentData . GetData ( ) ) ;
const UTF8CHAR * PathPointer = reinterpret_cast < const UTF8CHAR * > ( AttachRefHdr + 1 ) ;
2022-11-29 06:18:18 -05:00
const uint64 ExpectedHeaderSize = sizeof ( CbAttachmentReferenceHeader ) + AttachRefHdr - > AbsolutePathLength ;
if ( AttachmentData . GetSize ( ) < ExpectedHeaderSize )
{
// We did not get the full package data - treat it as a malformed package
2022-11-30 04:12:54 -05:00
UE_LOG ( LogZenSerialization , Warning , TEXT ( " Package payload is not large enough while trying to read local ref path, need % " UINT64_FMT " bytes, but only % " UINT64_FMT " available. " ) , ExpectedHeaderSize , AttachmentData . GetSize ( ) ) ;
2022-11-29 06:18:18 -05:00
Ar . SetError ( ) ;
return false ;
}
2022-11-07 05:33:21 -05:00
2023-03-23 05:15:22 -04:00
FCompressedBuffer CompBuf ;
2022-11-07 05:33:21 -05:00
FString Path ( FUtf8StringView ( PathPointer , static_cast < uint32_t > ( AttachRefHdr - > AbsolutePathLength ) ) ) ;
2023-03-23 05:15:22 -04:00
const FStringView HandlePrefix ( TEXT ( " :?#: " ) ) ;
if ( Path . StartsWith ( HandlePrefix ) )
2022-11-07 05:33:21 -05:00
{
2023-03-23 05:15:22 -04:00
# if PLATFORM_WINDOWS
FString HandleString ( Path . RightChop ( HandlePrefix . Len ( ) ) ) ;
uint64 HandleNum = 0 ;
LexFromString ( HandleNum , * HandleString ) ;
if ( HandleNum ! = 0 )
{
HANDLE Handle = reinterpret_cast < HANDLE > ( HandleNum ) ;
ON_SCOPE_EXIT { CloseHandle ( Handle ) ; } ;
LARGE_INTEGER FileSize ;
BOOL OK = GetFileSizeEx ( Handle , & FileSize ) ;
if ( ! OK )
{
UE_LOG ( LogZenSerialization , Warning , TEXT ( " Unable to read local file via handle '%u', treating it as a missing attachment " ) , HandleNum ) ;
+ + Index ;
continue ;
}
FUniqueBuffer MemBuffer = Allocator ( FileSize . QuadPart ) ;
LONGLONG Offset = 0 ;
while ( Offset < FileSize . QuadPart )
{
DWORD Length = static_cast < DWORD > ( FMath : : Min < LONGLONG > ( 0xffff'ffffu , FileSize . QuadPart - Offset ) ) ;
DWORD dwNumberOfBytesRead = 0 ;
OVERLAPPED Ovl { } ;
Ovl . Offset = DWORD ( Offset & 0xffff'ffffu ) ;
Ovl . OffsetHigh = DWORD ( Offset > > 32 ) ;
BOOL Success = : : ReadFile ( Handle , ( void * ) MemBuffer . GetView ( ) . Mid ( Offset , Length ) . GetData ( ) , Length , & dwNumberOfBytesRead , & Ovl ) ;
if ( ! Success )
{
DWORD LastError = GetLastError ( ) ;
UE_LOG ( LogZenSerialization , Warning , TEXT ( " Unable to read local file via handle '%u', error: %u, treating it as a missing attachment " ) , HandleNum , LastError ) ;
break ;
}
Offset + = Length ;
}
if ( Offset ! = FileSize . QuadPart )
{
+ + Index ;
continue ;
}
CompBuf = FCompressedBuffer : : FromCompressed ( MemBuffer . MoveToShared ( ) ) ;
}
# else // PLATFORM_WINDOWS
// Only suported on Windows. For Linux we could potentially use pidfd_getfd() but that requires a fairly new Linux kernel/includes and
// to deal with acceess rights etc.
checkf ( false , TEXT ( " Passing file handles is not supported on this platform " ) ) ;
# endif // PLATFORM_WINDOWS
2022-11-07 05:33:21 -05:00
}
2023-03-23 05:15:22 -04:00
else
{
TUniquePtr < FArchive > ChunkReader ( IFileManager : : Get ( ) . CreateFileReader ( * Path , FILEREAD_Silent ) ) ;
if ( ! ChunkReader )
{
// File does not exist on disk - treat it as missing
UE_LOG ( LogZenSerialization , Warning , TEXT ( " Unable to read local file '%s', treating it as a missing attachment " ) , * Path ) ;
+ + Index ;
continue ;
}
CompBuf = FCompressedBuffer : : Load ( * ChunkReader ) ;
}
2022-11-07 05:33:21 -05:00
if ( ! CompBuf )
{
// File has wrong format - treat it as an error
2022-11-30 04:12:54 -05:00
UE_LOG ( LogZenSerialization , Error , TEXT ( " Local file '%s' format is not compressed " ) , * Path ) ;
2022-11-07 05:33:21 -05:00
Ar . SetError ( ) ;
return false ;
}
2024-03-26 12:34:33 -04:00
FCbAttachment Attachment ( MoveTemp ( CompBuf ) , Entry . AttachmentHash ) ;
2022-11-07 05:33:21 -05:00
Package . AddAttachment ( Attachment ) ;
}
else if ( Entry . Flags & CbAttachmentEntry : : IsCompressed )
2022-05-31 12:42:17 -04:00
{
FCompressedBuffer CompBuf ( FCompressedBuffer : : FromCompressed ( AttachmentData . MoveToShared ( ) ) ) ;
if ( Entry . Flags & CbAttachmentEntry : : IsObject )
{
checkf ( Index = = 0 , TEXT ( " Object attachments are not currently supported " ) ) ;
Package . SetObject ( FCbObject ( CompBuf . Decompress ( ) ) ) ;
}
else
{
2024-03-26 12:34:33 -04:00
FCbAttachment Attachment ( MoveTemp ( CompBuf ) , Entry . AttachmentHash ) ;
2022-05-31 12:42:17 -04:00
Package . AddAttachment ( Attachment ) ;
}
}
else /* not compressed */
{
if ( Entry . Flags & CbAttachmentEntry : : IsObject )
{
checkf ( Index = = 0 , TEXT ( " Object attachments are not currently supported " ) ) ;
Package . SetObject ( FCbObject ( AttachmentData . MoveToShared ( ) ) ) ;
}
else
{
2024-03-26 12:34:33 -04:00
FCbAttachment Attachment ( AttachmentData . MoveToShared ( ) , Entry . AttachmentHash ) ;
2022-05-31 12:42:17 -04:00
Package . AddAttachment ( Attachment ) ;
}
}
+ + Index ;
}
return true ;
}
}
2021-09-07 18:07:00 -04:00
}
# endif // UE_WITH_ZEN