// Copyright Epic Games, Inc. All Rights Reserved. #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace zen::detail { /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// static constexpr uint64_t DefaultBlockSize = 256 * 1024; static constexpr uint64_t DefaultHeaderSize = 4 * 1024; /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// /** Method used to compress the data in a compressed buffer. */ enum class CompressionMethod : uint8_t { /** Header is followed by one uncompressed block. */ None = 0, /** Header is followed by an array of compressed block sizes then the compressed blocks. */ Oodle = 3, /** Header is followed by an array of compressed block sizes then the compressed blocks. */ LZ4 = 4, }; /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// /** Header used on every compressed buffer. Always stored in big-endian format. */ struct BufferHeader { static constexpr uint32_t ExpectedMagic = 0xb7756362; // ucb uint32_t Magic = ExpectedMagic; // A magic number to identify a compressed buffer. Always 0xb7756362. uint32_t Crc32 = 0; // A CRC-32 used to check integrity of the buffer. Uses the polynomial 0x04c11db7 CompressionMethod Method = CompressionMethod::None; // The method used to compress the buffer. Affects layout of data following the header uint8_t Compressor = 0; // The method-specific compressor used to compress the buffer. uint8_t CompressionLevel = 0; // The method-specific compression level used to compress the buffer. uint8_t BlockSizeExponent = 0; // The power of two size of every uncompressed block except the last. Size is 1 << BlockSizeExponent uint32_t BlockCount = 0; // The number of blocks that follow the header uint64_t TotalRawSize = 0; // The total size of the uncompressed data uint64_t TotalCompressedSize = 0; // The total size of the compressed data including the header BLAKE3 RawHash; // The hash of the uncompressed data /** Checks validity of the buffer based on the magic number, method, and CRC-32. */ static bool IsValid(const CompositeBuffer& CompressedData, IoHash& OutRawHash, uint64_t& OutRawSize, uint64_t* OutOptionalTotalCompressedSize); static bool IsValid(const SharedBuffer& CompressedData, IoHash& OutRawHash, uint64_t& OutRawSize, uint64_t* OutOptionalTotalCompressedSize) { return IsValid(CompositeBuffer(CompressedData), OutRawHash, OutRawSize, OutOptionalTotalCompressedSize); } /** Read a header from a buffer that is at least sizeof(BufferHeader) without any validation. */ static BufferHeader Read(const CompositeBuffer& CompressedData) { BufferHeader Header; if (sizeof(BufferHeader) <= CompressedData.GetSize()) { // if (CompressedData.GetSegments()[0].AsIoBuffer().IsWholeFile()) // { // ZEN_ASSERT(true); // } CompositeBuffer::Iterator It; CompressedData.CopyTo(MakeMutableMemoryView(&Header, &Header + 1), It); Header.ByteSwap(); } return Header; } /** * Write a header to a memory view that is at least sizeof(BufferHeader). * * @param HeaderView View of the header to write, including any method-specific header data. */ void Write(MutableMemoryView HeaderView) const { BufferHeader Header = *this; Header.ByteSwap(); HeaderView.CopyFrom(MakeMemoryView(&Header, &Header + 1)); Header.ByteSwap(); Header.Crc32 = CalculateCrc32(HeaderView); Header.ByteSwap(); HeaderView.CopyFrom(MakeMemoryView(&Header, &Header + 1)); } void ByteSwap() { Magic = zen::ByteSwap(Magic); Crc32 = zen::ByteSwap(Crc32); BlockCount = zen::ByteSwap(BlockCount); TotalRawSize = zen::ByteSwap(TotalRawSize); TotalCompressedSize = zen::ByteSwap(TotalCompressedSize); } /** Calculate the CRC-32 from a view of a header including any method-specific header data. */ static uint32_t CalculateCrc32(MemoryView HeaderView) { uint32_t Crc32 = 0; constexpr uint64_t MethodOffset = offsetof(BufferHeader, Method); for (MemoryView View = HeaderView + MethodOffset; const uint64_t ViewSize = View.GetSize();) { const int32_t Size = static_cast(zen::Min(ViewSize, /* INT_MAX */ 2147483647u)); Crc32 = zen::MemCrc32(View.GetData(), Size, Crc32); View += Size; } return Crc32; } bool TryGetCompressParameters(OodleCompressor& OutCompressor, OodleCompressionLevel& OutCompressionLevel, uint64_t& OutBlockSize) const { switch (Method) { case CompressionMethod::None: OutCompressor = OodleCompressor::NotSet; OutCompressionLevel = OodleCompressionLevel::None; OutBlockSize = 0; return true; case CompressionMethod::Oodle: OutCompressor = OodleCompressor(Compressor); OutCompressionLevel = OodleCompressionLevel(CompressionLevel); OutBlockSize = uint64_t(1) << BlockSizeExponent; return true; default: return false; } } }; using FHeader = BufferHeader; static_assert(sizeof(BufferHeader) == 64, "BufferHeader is the wrong size."); /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// class DecoderSource { public: virtual bool Read(uint64_t Offset, MutableMemoryView Data) const = 0; virtual MemoryView ReadOrView(uint64_t Offset, uint64_t Size, DecoderContext& Context) const = 0; virtual CompositeBuffer ReadToComposite(uint64_t Offset, uint64_t Size) const = 0; }; class BaseEncoder { public: [[nodiscard]] virtual CompositeBuffer Compress(const CompositeBuffer& RawData, uint64_t BlockSize = DefaultBlockSize) const = 0; [[nodiscard]] virtual bool CompressToStream( const CompositeBuffer& RawData, std::function&& Callback, uint64_t BlockSize = DefaultBlockSize) const = 0; }; class BaseDecoder { public: [[nodiscard]] virtual uint64_t GetHeaderSize(const BufferHeader& Header) const = 0; [[nodiscard]] virtual bool TryDecompressTo(const BufferHeader& Header, const CompositeBuffer& CompressedData, MutableMemoryView RawView, uint64_t RawOffset) const = 0; [[nodiscard]] virtual bool TryDecompressTo(DecoderContext& Context, const DecoderSource& Source, const BufferHeader& Header, MemoryView HeaderView, uint64_t RawOffset, MutableMemoryView RawView) const = 0; [[nodiscard]] virtual CompositeBuffer DecompressToComposite(const BufferHeader& Header, const CompositeBuffer& CompressedData) const = 0; virtual CompositeBuffer DecompressToComposite(DecoderContext& Context, const DecoderSource& Source, const FHeader& Header, const MemoryView HeaderView, uint64_t RawOffset, uint64_t RawSize) const = 0; virtual bool DecompressToStream( const BufferHeader& Header, const CompositeBuffer& CompressedData, uint64_t RawOffset, uint64_t RawSize, std::function&& Callback) const = 0; }; /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// class NoneEncoder final : public BaseEncoder { public: [[nodiscard]] virtual CompositeBuffer Compress(const CompositeBuffer& RawData, uint64_t /* BlockSize */) const final { UniqueBuffer HeaderData = CompressedBuffer::CreateHeaderForNoneEncoder(RawData.GetSize(), BLAKE3::HashBuffer(RawData)); return CompositeBuffer(HeaderData.MoveToShared(), RawData.MakeOwned()); } [[nodiscard]] virtual bool CompressToStream( const CompositeBuffer& RawData, std::function&& Callback, uint64_t /* BlockSize */) const final { const uint64_t HeaderSize = CompressedBuffer::GetHeaderSizeForNoneEncoder(); uint64_t RawOffset = 0; BLAKE3Stream HashStream; for (const SharedBuffer& Segment : RawData.GetSegments()) { IoBufferFileReference FileRef = {nullptr, 0, 0}; IoBuffer SegmentBuffer = Segment.AsIoBuffer(); if (SegmentBuffer.GetFileReference(FileRef)) { ZEN_ASSERT(FileRef.FileHandle != nullptr); ScanFile(FileRef.FileHandle, FileRef.FileChunkOffset, FileRef.FileChunkSize, 512u * 1024u, [&](const void* Data, size_t Size) { HashStream.Append(Data, Size); CompositeBuffer Tmp(SharedBuffer::MakeView(Data, Size)); Callback(RawOffset, Size, HeaderSize + RawOffset, Tmp); RawOffset += Size; }); } else { const uint64_t Size = SegmentBuffer.GetSize(); HashStream.Append(SegmentBuffer); Callback(RawOffset, Size, HeaderSize + RawOffset, CompositeBuffer(Segment)); RawOffset += Size; } } ZEN_ASSERT(RawOffset == RawData.GetSize()); UniqueBuffer HeaderData = CompressedBuffer::CreateHeaderForNoneEncoder(RawData.GetSize(), HashStream.GetHash()); ZEN_ASSERT(HeaderData.GetSize() == HeaderSize); Callback(0, 0, 0, CompositeBuffer(HeaderData.MoveToShared())); return true; } }; class NoneDecoder final : public BaseDecoder { public: [[nodiscard]] CompositeBuffer DecompressToComposite(const BufferHeader& Header, const CompositeBuffer& CompressedData) const final { if (Header.Method == CompressionMethod::None && Header.TotalCompressedSize == CompressedData.GetSize() && Header.TotalCompressedSize == Header.TotalRawSize + sizeof(BufferHeader)) { return CompressedData.Mid(sizeof(BufferHeader), Header.TotalRawSize).MakeOwned(); } return CompositeBuffer(); } CompositeBuffer DecompressToComposite(DecoderContext& Context, const DecoderSource& Source, const FHeader& Header, const MemoryView HeaderView, const uint64_t RawOffset, const uint64_t RawSize) const final { ZEN_UNUSED(HeaderView, Context); if (Header.Method == CompressionMethod::None && RawOffset <= Header.TotalRawSize && RawSize <= Header.TotalRawSize - RawOffset && Header.TotalCompressedSize == Header.TotalRawSize + sizeof(FHeader)) { return Source.ReadToComposite(sizeof(FHeader) + RawOffset, RawSize); } return CompositeBuffer(); } [[nodiscard]] bool TryDecompressTo(const BufferHeader& Header, const CompositeBuffer& CompressedData, MutableMemoryView RawView, uint64_t RawOffset) const final { if (Header.Method == CompressionMethod::None && RawOffset + RawView.GetSize() <= Header.TotalRawSize && Header.TotalCompressedSize == CompressedData.GetSize() && Header.TotalCompressedSize == Header.TotalRawSize + sizeof(BufferHeader)) { CompressedData.CopyTo(RawView, sizeof(BufferHeader) + RawOffset); return true; } return false; } [[nodiscard]] bool TryDecompressTo(DecoderContext& Context, const DecoderSource& Source, const BufferHeader& Header, MemoryView HeaderView, uint64_t RawOffset, MutableMemoryView RawView) const final { ZEN_UNUSED(HeaderView, Context); if (Header.Method == CompressionMethod::None && RawOffset <= Header.TotalRawSize && RawView.GetSize() <= Header.TotalRawSize - RawOffset && Header.TotalCompressedSize == Header.TotalRawSize + sizeof(BufferHeader)) { return Source.Read(sizeof(BufferHeader) + RawOffset, RawView); } return false; } [[nodiscard]] uint64_t GetHeaderSize(const BufferHeader&) const final { return sizeof(BufferHeader); } virtual bool DecompressToStream( const BufferHeader& Header, const CompositeBuffer& CompressedData, uint64_t RawOffset, uint64_t RawSize, std::function&& Callback) const final { if (Header.Method == CompressionMethod::None && Header.TotalCompressedSize == CompressedData.GetSize() && Header.TotalCompressedSize == Header.TotalRawSize + sizeof(BufferHeader) && RawOffset <= Header.TotalRawSize && (RawOffset + RawSize) <= Header.TotalRawSize) { bool Result = true; IoBufferFileReference FileRef = {nullptr, 0, 0}; if ((CompressedData.GetSegments().size() == 1) && CompressedData.GetSegments()[0].AsIoBuffer().GetFileReference(FileRef)) { ZEN_ASSERT(FileRef.FileHandle != nullptr); uint64_t CallbackOffset = 0; ScanFile(FileRef.FileHandle, FileRef.FileChunkOffset + sizeof(BufferHeader) + RawOffset, RawSize, 512u * 1024u, [&](const void* Data, size_t Size) { if (Result) { CompositeBuffer Tmp(SharedBuffer::MakeView(Data, Size)); Result = Callback(sizeof(BufferHeader) + RawOffset + CallbackOffset, Size, CallbackOffset, Tmp); } CallbackOffset += Size; }); return Result; } else { return Callback(sizeof(BufferHeader) + RawOffset, RawSize, 0, CompressedData.Mid(sizeof(BufferHeader) + RawOffset, RawSize)); } } return false; } }; ////////////////////////////////////////////////////////////////////////// class BlockEncoder : public BaseEncoder { public: virtual CompositeBuffer Compress(const CompositeBuffer& RawData, uint64_t BlockSize) const final; virtual bool CompressToStream( const CompositeBuffer& RawData, std::function&& Callback, uint64_t BlockSize) const final; protected: virtual CompressionMethod GetMethod() const = 0; virtual uint8_t GetCompressor() const = 0; virtual uint8_t GetCompressionLevel() const = 0; virtual uint64_t CompressBlockBound(uint64_t RawSize) const = 0; virtual bool CompressBlock(MutableMemoryView& CompressedData, MemoryView RawData) const = 0; private: uint64_t GetCompressedBlocksBound(uint64_t BlockCount, uint64_t BlockSize, uint64_t RawSize) const { switch (BlockCount) { case 0: return 0; case 1: return CompressBlockBound(RawSize); default: return CompressBlockBound(BlockSize) - BlockSize + RawSize; } } }; CompositeBuffer BlockEncoder::Compress(const CompositeBuffer& RawData, const uint64_t BlockSize) const { ZEN_ASSERT(IsPow2(BlockSize) && (BlockSize <= (1u << 31))); const uint64_t RawSize = RawData.GetSize(); BLAKE3Stream RawHash; const uint64_t BlockCount = RoundUp(RawSize, BlockSize) / BlockSize; ZEN_ASSERT(BlockCount <= ~uint32_t(0)); // Allocate the buffer for the header, metadata, and compressed blocks. const uint64_t MetaSize = BlockCount * sizeof(uint32_t); const uint64_t CompressedDataSize = sizeof(BufferHeader) + MetaSize + GetCompressedBlocksBound(BlockCount, BlockSize, RawSize); UniqueBuffer CompressedData = UniqueBuffer::Alloc(CompressedDataSize); // Compress the raw data in blocks and store the raw data for incompressible blocks. std::vector CompressedBlockSizes; CompressedBlockSizes.reserve(BlockCount); uint64_t CompressedSize = 0; { MutableMemoryView CompressedBlocksView = CompressedData.GetMutableView() + sizeof(BufferHeader) + MetaSize; IoBufferFileReference FileRef = {nullptr, 0, 0}; if ((RawData.GetSegments().size() == 1) && RawData.GetSegments()[0].AsIoBuffer().GetFileReference(FileRef)) { ZEN_ASSERT(FileRef.FileHandle != nullptr); UniqueBuffer RawBlockCopy = UniqueBuffer::Alloc(BlockSize); BasicFile Source; Source.Attach(FileRef.FileHandle); auto _ = MakeGuard([&Source]() { Source.Detach(); }); for (uint64_t RawOffset = 0; RawOffset < RawSize;) { const uint64_t RawBlockSize = zen::Min(RawSize - RawOffset, BlockSize); Source.Read(RawBlockCopy.GetData(), RawBlockSize, FileRef.FileChunkOffset + RawOffset); const MemoryView RawBlock = RawBlockCopy.GetView().Left(RawBlockSize); RawHash.Append(RawBlock); MutableMemoryView CompressedBlock = CompressedBlocksView; if (!CompressBlock(CompressedBlock, RawBlock)) { return CompositeBuffer(); } uint64_t CompressedBlockSize = CompressedBlock.GetSize(); if (RawBlockSize <= CompressedBlockSize) { CompressedBlockSize = RawBlockSize; CompressedBlocksView = CompressedBlocksView.CopyFrom(RawBlock); } else { CompressedBlocksView += CompressedBlockSize; } CompressedBlockSizes.push_back(static_cast(CompressedBlockSize)); CompressedSize += CompressedBlockSize; RawOffset += RawBlockSize; } } else { UniqueBuffer RawBlockCopy; CompositeBuffer::Iterator It = RawData.GetIterator(0); for (uint64_t RawOffset = 0; RawOffset < RawSize;) { const uint64_t RawBlockSize = zen::Min(RawSize - RawOffset, BlockSize); const MemoryView RawBlock = RawData.ViewOrCopyRange(It, RawBlockSize, RawBlockCopy); RawHash.Append(RawBlock); MutableMemoryView CompressedBlock = CompressedBlocksView; if (!CompressBlock(CompressedBlock, RawBlock)) { return CompositeBuffer(); } uint64_t CompressedBlockSize = CompressedBlock.GetSize(); if (RawBlockSize <= CompressedBlockSize) { CompressedBlockSize = RawBlockSize; CompressedBlocksView = CompressedBlocksView.CopyFrom(RawBlock); } else { CompressedBlocksView += CompressedBlockSize; } CompressedBlockSizes.push_back(static_cast(CompressedBlockSize)); CompressedSize += CompressedBlockSize; RawOffset += RawBlockSize; } } } // Return an uncompressed buffer if the compressed data is larger than the raw data. if (RawSize <= MetaSize + CompressedSize) { CompressedData.Reset(); return NoneEncoder().Compress(RawData, BlockSize); } // Write the header and calculate the CRC-32. for (uint32_t& Size : CompressedBlockSizes) { Size = ByteSwap(Size); } CompressedData.GetMutableView().Mid(sizeof(BufferHeader), MetaSize).CopyFrom(MakeMemoryView(CompressedBlockSizes)); BufferHeader Header; Header.Method = GetMethod(); Header.Compressor = GetCompressor(); Header.CompressionLevel = GetCompressionLevel(); Header.BlockSizeExponent = static_cast(zen::FloorLog2_64(BlockSize)); Header.BlockCount = static_cast(BlockCount); Header.TotalRawSize = RawSize; Header.TotalCompressedSize = sizeof(BufferHeader) + MetaSize + CompressedSize; Header.RawHash = RawHash.GetHash(); Header.Write(CompressedData.GetMutableView().Left(sizeof(BufferHeader) + MetaSize)); const MemoryView CompositeView = CompressedData.GetView().Left(Header.TotalCompressedSize); return CompositeBuffer(SharedBuffer::MakeView(CompositeView, CompressedData.MoveToShared())); } bool BlockEncoder::CompressToStream( const CompositeBuffer& RawData, std::function&& Callback, uint64_t BlockSize = DefaultBlockSize) const { ZEN_ASSERT(IsPow2(BlockSize) && (BlockSize <= (1u << 31))); const uint64_t RawSize = RawData.GetSize(); BLAKE3Stream RawHash; const uint64_t BlockCount = RoundUp(RawSize, BlockSize) / BlockSize; ZEN_ASSERT(BlockCount <= ~uint32_t(0)); const uint64_t MetaSize = BlockCount * sizeof(uint32_t); const uint64_t FullHeaderSize = sizeof(BufferHeader) + MetaSize; std::vector CompressedBlockSizes; CompressedBlockSizes.reserve(BlockCount); uint64_t CompressedSize = 0; { UniqueBuffer CompressedBlockBuffer = UniqueBuffer::Alloc(GetCompressedBlocksBound(1, BlockSize, Min(RawSize, BlockSize))); IoBufferFileReference FileRef = {nullptr, 0, 0}; if ((RawData.GetSegments().size() == 1) && RawData.GetSegments()[0].AsIoBuffer().GetFileReference(FileRef)) { ZEN_ASSERT(FileRef.FileHandle != nullptr); UniqueBuffer RawBlockCopy = UniqueBuffer::Alloc(BlockSize); BasicFile Source; Source.Attach(FileRef.FileHandle); auto _ = MakeGuard([&Source]() { Source.Detach(); }); for (uint64_t RawOffset = 0; RawOffset < RawSize;) { const uint64_t RawBlockSize = zen::Min(RawSize - RawOffset, BlockSize); Source.Read(RawBlockCopy.GetData(), RawBlockSize, FileRef.FileChunkOffset + RawOffset); const MemoryView RawBlock = RawBlockCopy.GetView().Left(RawBlockSize); RawHash.Append(RawBlock); MutableMemoryView CompressedBlock = CompressedBlockBuffer.GetMutableView(); if (!CompressBlock(CompressedBlock, RawBlock)) { return false; } uint64_t CompressedBlockSize = CompressedBlock.GetSize(); if (RawBlockSize <= CompressedBlockSize) { Callback(FileRef.FileChunkOffset + RawOffset, RawBlockSize, FullHeaderSize + CompressedSize, CompositeBuffer(IoBuffer(IoBuffer::Wrap, RawBlockCopy.GetView().GetData(), RawBlockSize))); CompressedBlockSize = RawBlockSize; } else { Callback(FileRef.FileChunkOffset + RawOffset, RawBlockSize, FullHeaderSize + CompressedSize, CompositeBuffer(IoBuffer(IoBuffer::Wrap, CompressedBlock.GetData(), CompressedBlockSize))); } CompressedBlockSizes.push_back(static_cast(CompressedBlockSize)); CompressedSize += CompressedBlockSize; RawOffset += RawBlockSize; } } else { UniqueBuffer RawBlockCopy; CompositeBuffer::Iterator It = RawData.GetIterator(0); for (uint64_t RawOffset = 0; RawOffset < RawSize;) { const uint64_t RawBlockSize = zen::Min(RawSize - RawOffset, BlockSize); const MemoryView RawBlock = RawData.ViewOrCopyRange(It, RawBlockSize, RawBlockCopy); RawHash.Append(RawBlock); MutableMemoryView CompressedBlock = CompressedBlockBuffer.GetMutableView(); if (!CompressBlock(CompressedBlock, RawBlock)) { return false; } uint64_t CompressedBlockSize = CompressedBlock.GetSize(); if (RawBlockSize <= CompressedBlockSize) { Callback(RawOffset, RawBlockSize, FullHeaderSize + CompressedSize, CompositeBuffer(IoBuffer(IoBuffer::Wrap, RawBlock.GetData(), RawBlockSize))); CompressedBlockSize = RawBlockSize; } else { Callback(RawOffset, RawBlockSize, FullHeaderSize + CompressedSize, CompositeBuffer(IoBuffer(IoBuffer::Wrap, CompressedBlock.GetData(), CompressedBlockSize))); } CompressedBlockSizes.push_back(static_cast(CompressedBlockSize)); CompressedSize += CompressedBlockSize; RawOffset += RawBlockSize; } } } // Return failure if the compressed data is larger than the raw data. if (RawSize <= MetaSize + CompressedSize) { return false; } // Write the header and calculate the CRC-32. for (uint32_t& Size : CompressedBlockSizes) { Size = ByteSwap(Size); } UniqueBuffer HeaderBuffer = UniqueBuffer::Alloc(sizeof(BufferHeader) + MetaSize); BufferHeader Header; Header.Method = GetMethod(); Header.Compressor = GetCompressor(); Header.CompressionLevel = GetCompressionLevel(); Header.BlockSizeExponent = static_cast(zen::FloorLog2_64(BlockSize)); Header.BlockCount = static_cast(BlockCount); Header.TotalRawSize = RawSize; Header.TotalCompressedSize = sizeof(BufferHeader) + MetaSize + CompressedSize; Header.RawHash = RawHash.GetHash(); HeaderBuffer.GetMutableView().Mid(sizeof(BufferHeader), MetaSize).CopyFrom(MakeMemoryView(CompressedBlockSizes)); Header.Write(HeaderBuffer.GetMutableView()); Callback(0, 0, 0, CompositeBuffer(IoBuffer(IoBuffer::Wrap, HeaderBuffer.GetData(), HeaderBuffer.GetSize()))); return true; } class BlockDecoder : public BaseDecoder { public: [[nodiscard]] uint64_t GetHeaderSize(const BufferHeader& Header) const final { return sizeof(BufferHeader) + sizeof(uint32_t) * uint64_t(Header.BlockCount); } [[nodiscard]] virtual bool TryDecompressTo(DecoderContext& Context, const DecoderSource& Source, const BufferHeader& Header, MemoryView HeaderView, uint64_t RawOffset, MutableMemoryView RawView) const final; CompositeBuffer DecompressToComposite(const BufferHeader& Header, const CompositeBuffer& CompressedData) const final; CompositeBuffer DecompressToComposite(DecoderContext& Context, const DecoderSource& Source, const FHeader& Header, const MemoryView HeaderView, const uint64_t RawOffset, const uint64_t RawSize) const final; [[nodiscard]] bool TryDecompressTo(const BufferHeader& Header, const CompositeBuffer& CompressedData, MutableMemoryView RawView, uint64_t RawOffset) const final; virtual bool DecompressToStream( const BufferHeader& Header, const CompositeBuffer& CompressedData, uint64_t RawOffset, uint64_t RawSize, std::function&& Callback) const final; protected: virtual bool DecompressBlock(MutableMemoryView RawData, MemoryView CompressedData) const = 0; }; CompositeBuffer BlockDecoder::DecompressToComposite(const BufferHeader& Header, const CompositeBuffer& CompressedData) const { if (Header.BlockCount == 0 || Header.TotalCompressedSize != CompressedData.GetSize()) { return CompositeBuffer(); } // The raw data cannot reference the compressed data unless it is owned. // An empty raw buffer requires an empty segment, which this path creates. if (!CompressedData.IsOwned() || Header.TotalRawSize == 0) { UniqueBuffer Buffer = UniqueBuffer::Alloc(Header.TotalRawSize); return TryDecompressTo(Header, CompressedData, Buffer, 0) ? CompositeBuffer(Buffer.MoveToShared()) : CompositeBuffer(); } std::vector CompressedBlockSizes; CompressedBlockSizes.resize(Header.BlockCount); CompressedData.CopyTo(MakeMutableMemoryView(CompressedBlockSizes), sizeof(BufferHeader)); for (uint32_t& Size : CompressedBlockSizes) { Size = ByteSwap(Size); } // Allocate the buffer for the raw blocks that were compressed. SharedBuffer RawData; MutableMemoryView RawDataView; const uint64_t BlockSize = uint64_t(1) << Header.BlockSizeExponent; { uint64_t RawDataSize = 0; uint64_t RemainingRawSize = Header.TotalRawSize; for (const uint32_t CompressedBlockSize : CompressedBlockSizes) { const uint64_t RawBlockSize = zen::Min(RemainingRawSize, BlockSize); if (CompressedBlockSize < BlockSize) { RawDataSize += RawBlockSize; } RemainingRawSize -= RawBlockSize; } UniqueBuffer RawDataBuffer = UniqueBuffer::Alloc(RawDataSize); RawDataView = RawDataBuffer; RawData = RawDataBuffer.MoveToShared(); } // Decompress the compressed data in blocks and reference the uncompressed blocks. uint64_t PendingCompressedSegmentOffset = sizeof(BufferHeader) + uint64_t(Header.BlockCount) * sizeof(uint32_t); uint64_t PendingCompressedSegmentSize = 0; uint64_t PendingRawSegmentOffset = 0; uint64_t PendingRawSegmentSize = 0; std::vector Segments; const auto CommitPendingCompressedSegment = [&PendingCompressedSegmentOffset, &PendingCompressedSegmentSize, &CompressedData, &Segments] { if (PendingCompressedSegmentSize) { CompressedData.IterateRange(PendingCompressedSegmentOffset, PendingCompressedSegmentSize, [&Segments](MemoryView View, const SharedBuffer& ViewOuter) { Segments.push_back(SharedBuffer::MakeView(View, ViewOuter)); }); PendingCompressedSegmentOffset += PendingCompressedSegmentSize; PendingCompressedSegmentSize = 0; } }; const auto CommitPendingRawSegment = [&PendingRawSegmentOffset, &PendingRawSegmentSize, &RawData, &Segments] { if (PendingRawSegmentSize) { const MemoryView PendingSegment = RawData.GetView().Mid(PendingRawSegmentOffset, PendingRawSegmentSize); Segments.push_back(SharedBuffer::MakeView(PendingSegment, RawData)); PendingRawSegmentOffset += PendingRawSegmentSize; PendingRawSegmentSize = 0; } }; UniqueBuffer CompressedBlockCopy; uint64_t RemainingRawSize = Header.TotalRawSize; uint64_t RemainingCompressedSize = CompressedData.GetSize(); for (const uint32_t CompressedBlockSize : CompressedBlockSizes) { if (RemainingCompressedSize < CompressedBlockSize) { return CompositeBuffer(); } const uint64_t RawBlockSize = zen::Min(RemainingRawSize, BlockSize); if (RawBlockSize == CompressedBlockSize) { CommitPendingRawSegment(); PendingCompressedSegmentSize += RawBlockSize; } else { CommitPendingCompressedSegment(); const MemoryView CompressedBlock = CompressedData.ViewOrCopyRange(PendingCompressedSegmentOffset, CompressedBlockSize, CompressedBlockCopy); if (!DecompressBlock(RawDataView.Left(RawBlockSize), CompressedBlock)) { return CompositeBuffer(); } PendingCompressedSegmentOffset += CompressedBlockSize; PendingRawSegmentSize += RawBlockSize; RawDataView += RawBlockSize; } RemainingCompressedSize -= CompressedBlockSize; RemainingRawSize -= RawBlockSize; } CommitPendingCompressedSegment(); CommitPendingRawSegment(); return CompositeBuffer(std::move(Segments)); } bool BlockDecoder::DecompressToStream( const BufferHeader& Header, const CompositeBuffer& CompressedData, uint64_t RawOffset, uint64_t RawSize, std::function&& Callback) const { if (Header.TotalCompressedSize != CompressedData.GetSize()) { return false; } const uint64_t BlockSize = uint64_t(1) << Header.BlockSizeExponent; UniqueBuffer BlockSizeBuffer; MemoryView BlockSizeView = CompressedData.ViewOrCopyRange(sizeof(BufferHeader), Header.BlockCount * sizeof(uint32_t), BlockSizeBuffer); std::span CompressedBlockSizes(reinterpret_cast(BlockSizeView.GetData()), Header.BlockCount); UniqueBuffer CompressedBlockCopy; const size_t FirstBlockIndex = uint64_t(RawOffset / BlockSize); const size_t LastBlockIndex = uint64_t((RawOffset + RawSize - 1) / BlockSize); const uint64_t LastBlockSize = BlockSize - ((Header.BlockCount * BlockSize) - Header.TotalRawSize); uint64_t OffsetInFirstBlock = RawOffset % BlockSize; uint64_t CompressedOffset = sizeof(BufferHeader) + uint64_t(Header.BlockCount) * sizeof(uint32_t); uint64_t RemainingRawSize = RawSize; for (size_t BlockIndex = 0; BlockIndex < FirstBlockIndex; BlockIndex++) { const uint32_t CompressedBlockSize = ByteSwap(CompressedBlockSizes[BlockIndex]); CompressedOffset += CompressedBlockSize; } UniqueBuffer RawDataBuffer; IoBufferFileReference FileRef = {nullptr, 0, 0}; if ((CompressedData.GetSegments().size() == 1) && CompressedData.GetSegments()[0].AsIoBuffer().GetFileReference(FileRef)) { ZEN_ASSERT(FileRef.FileHandle != nullptr); BasicFile Source; Source.Attach(FileRef.FileHandle); auto _ = MakeGuard([&Source]() { Source.Detach(); }); for (size_t BlockIndex = FirstBlockIndex; BlockIndex <= LastBlockIndex; BlockIndex++) { const uint64_t UncompressedBlockSize = BlockIndex == Header.BlockCount - 1 ? LastBlockSize : BlockSize; const uint32_t CompressedBlockSize = ByteSwap(CompressedBlockSizes[BlockIndex]); const bool IsCompressed = CompressedBlockSize < UncompressedBlockSize; const uint64_t BytesToUncompress = OffsetInFirstBlock > 0 ? zen::Min(RawSize, UncompressedBlockSize - OffsetInFirstBlock) : zen::Min(RemainingRawSize, BlockSize); if (CompressedBlockCopy.GetSize() < CompressedBlockSize) { CompressedBlockCopy = UniqueBuffer::Alloc(CompressedBlockSize); } Source.Read(CompressedBlockCopy.GetData(), CompressedBlockSize, FileRef.FileChunkOffset + CompressedOffset); MemoryView CompressedBlock = CompressedBlockCopy.GetView().Left(CompressedBlockSize); if (IsCompressed) { if (RawDataBuffer.IsNull()) { RawDataBuffer = UniqueBuffer::Alloc(zen::Min(RawSize, UncompressedBlockSize)); } else { ZEN_ASSERT(RawDataBuffer.GetSize() >= UncompressedBlockSize); } MutableMemoryView UncompressedBlock = RawDataBuffer.GetMutableView().Left(UncompressedBlockSize); if (!DecompressBlock(UncompressedBlock, CompressedBlock)) { return false; } if (!Callback(CompressedOffset, CompressedBlockSize, BlockIndex * BlockSize + OffsetInFirstBlock, CompositeBuffer(IoBuffer(IoBuffer::Wrap, RawDataBuffer.GetData(), BytesToUncompress)))) { return false; } } else { if (!Callback( CompressedOffset, BytesToUncompress, BlockIndex * BlockSize + OffsetInFirstBlock, CompositeBuffer( IoBuffer(IoBuffer::Wrap, CompressedBlockCopy.GetView().Mid(OffsetInFirstBlock).GetData(), BytesToUncompress)))) { return false; } } OffsetInFirstBlock = 0; RemainingRawSize -= BytesToUncompress; CompressedOffset += CompressedBlockSize; } } else { for (size_t BlockIndex = FirstBlockIndex; BlockIndex <= LastBlockIndex; BlockIndex++) { const uint64_t UncompressedBlockSize = BlockIndex == Header.BlockCount - 1 ? LastBlockSize : BlockSize; const uint32_t CompressedBlockSize = ByteSwap(CompressedBlockSizes[BlockIndex]); const bool IsCompressed = CompressedBlockSize < UncompressedBlockSize; const uint64_t BytesToUncompress = OffsetInFirstBlock > 0 ? zen::Min(RawSize, UncompressedBlockSize - OffsetInFirstBlock) : zen::Min(RemainingRawSize, BlockSize); MemoryView CompressedBlock = CompressedData.ViewOrCopyRange(CompressedOffset, CompressedBlockSize, CompressedBlockCopy); if (IsCompressed) { if (RawDataBuffer.IsNull()) { RawDataBuffer = UniqueBuffer::Alloc(zen::Min(RawSize, UncompressedBlockSize)); } else { ZEN_ASSERT(RawDataBuffer.GetSize() >= UncompressedBlockSize); } MutableMemoryView UncompressedBlock = RawDataBuffer.GetMutableView().Left(UncompressedBlockSize); if (!DecompressBlock(UncompressedBlock, CompressedBlock)) { return false; } if (!Callback(CompressedOffset, UncompressedBlockSize, BlockIndex * BlockSize + OffsetInFirstBlock, CompositeBuffer(IoBuffer(IoBuffer::Wrap, RawDataBuffer.GetData(), BytesToUncompress)))) { return false; } } else { if (!Callback( CompressedOffset, BytesToUncompress, BlockIndex * BlockSize + OffsetInFirstBlock, CompositeBuffer( IoBuffer(IoBuffer::Wrap, CompressedBlockCopy.GetView().Mid(OffsetInFirstBlock).GetData(), BytesToUncompress)))) { return false; } } OffsetInFirstBlock = 0; RemainingRawSize -= BytesToUncompress; CompressedOffset += CompressedBlockSize; } } return true; } bool BlockDecoder::TryDecompressTo(const BufferHeader& Header, const CompositeBuffer& CompressedData, MutableMemoryView RawView, uint64_t RawOffset) const { if (Header.TotalRawSize < RawOffset + RawView.GetSize() || Header.TotalCompressedSize != CompressedData.GetSize()) { return false; } const uint64_t BlockSize = uint64_t(1) << Header.BlockSizeExponent; UniqueBuffer BlockSizeBuffer; MemoryView BlockSizeView = CompressedData.ViewOrCopyRange(sizeof(BufferHeader), Header.BlockCount * sizeof(uint32_t), BlockSizeBuffer); std::span CompressedBlockSizes(reinterpret_cast(BlockSizeView.GetData()), Header.BlockCount); UniqueBuffer CompressedBlockCopy; UniqueBuffer UncompressedBlockCopy; const size_t FirstBlockIndex = uint64_t(RawOffset / BlockSize); const size_t LastBlockIndex = uint64_t((RawOffset + RawView.GetSize() - 1) / BlockSize); const uint64_t LastBlockSize = BlockSize - ((Header.BlockCount * BlockSize) - Header.TotalRawSize); uint64_t OffsetInFirstBlock = RawOffset % BlockSize; uint64_t CompressedOffset = sizeof(BufferHeader) + uint64_t(Header.BlockCount) * sizeof(uint32_t); uint64_t RemainingRawSize = RawView.GetSize(); for (size_t BlockIndex = 0; BlockIndex < FirstBlockIndex; BlockIndex++) { const uint32_t CompressedBlockSize = ByteSwap(CompressedBlockSizes[BlockIndex]); CompressedOffset += CompressedBlockSize; } IoBufferFileReference FileRef = {nullptr, 0, 0}; if ((CompressedData.GetSegments().size() == 1) && CompressedData.GetSegments()[0].AsIoBuffer().GetFileReference(FileRef)) { ZEN_ASSERT(FileRef.FileHandle != nullptr); BasicFile Source; Source.Attach(FileRef.FileHandle); auto _ = MakeGuard([&Source]() { Source.Detach(); }); for (size_t BlockIndex = FirstBlockIndex; BlockIndex <= LastBlockIndex; BlockIndex++) { const uint64_t UncompressedBlockSize = BlockIndex == Header.BlockCount - 1 ? LastBlockSize : BlockSize; const uint32_t CompressedBlockSize = ByteSwap(CompressedBlockSizes[BlockIndex]); const bool IsCompressed = CompressedBlockSize < UncompressedBlockSize; const uint64_t BytesToUncompress = OffsetInFirstBlock > 0 ? zen::Min(RawView.GetSize(), UncompressedBlockSize - OffsetInFirstBlock) : zen::Min(RemainingRawSize, BlockSize); if (CompressedBlockCopy.GetSize() < CompressedBlockSize) { CompressedBlockCopy = UniqueBuffer::Alloc(CompressedBlockSize); } Source.Read(CompressedBlockCopy.GetData(), CompressedBlockSize, FileRef.FileChunkOffset + CompressedOffset); MemoryView CompressedBlock = CompressedBlockCopy.GetView().Left(CompressedBlockSize); if (IsCompressed) { MutableMemoryView UncompressedBlock = RawView.Left(BytesToUncompress); const bool IsAligned = BytesToUncompress == UncompressedBlockSize; if (!IsAligned) { // Decompress to a temporary buffer when the first or the last block reads are not aligned with the block boundaries. if (UncompressedBlockCopy.IsNull()) { UncompressedBlockCopy = UniqueBuffer::Alloc(BlockSize); } UncompressedBlock = UncompressedBlockCopy.GetMutableView().Mid(0, UncompressedBlockSize); } if (!DecompressBlock(UncompressedBlock, CompressedBlock)) { return false; } if (!IsAligned) { RawView.CopyFrom(UncompressedBlock.Mid(OffsetInFirstBlock, BytesToUncompress)); } } else { RawView.CopyFrom(CompressedBlock.Mid(OffsetInFirstBlock, BytesToUncompress)); } OffsetInFirstBlock = 0; RemainingRawSize -= BytesToUncompress; CompressedOffset += CompressedBlockSize; RawView += BytesToUncompress; } } else { for (size_t BlockIndex = FirstBlockIndex; BlockIndex <= LastBlockIndex; BlockIndex++) { const uint64_t UncompressedBlockSize = BlockIndex == Header.BlockCount - 1 ? LastBlockSize : BlockSize; const uint32_t CompressedBlockSize = ByteSwap(CompressedBlockSizes[BlockIndex]); const bool IsCompressed = CompressedBlockSize < UncompressedBlockSize; const uint64_t BytesToUncompress = OffsetInFirstBlock > 0 ? zen::Min(RawView.GetSize(), UncompressedBlockSize - OffsetInFirstBlock) : zen::Min(RemainingRawSize, BlockSize); MemoryView CompressedBlock = CompressedData.ViewOrCopyRange(CompressedOffset, CompressedBlockSize, CompressedBlockCopy); if (IsCompressed) { MutableMemoryView UncompressedBlock = RawView.Left(BytesToUncompress); const bool IsAligned = BytesToUncompress == UncompressedBlockSize; if (!IsAligned) { // Decompress to a temporary buffer when the first or the last block reads are not aligned with the block boundaries. if (UncompressedBlockCopy.IsNull()) { UncompressedBlockCopy = UniqueBuffer::Alloc(BlockSize); } UncompressedBlock = UncompressedBlockCopy.GetMutableView().Mid(0, UncompressedBlockSize); } if (!DecompressBlock(UncompressedBlock, CompressedBlock)) { return false; } if (!IsAligned) { RawView.CopyFrom(UncompressedBlock.Mid(OffsetInFirstBlock, BytesToUncompress)); } } else { RawView.CopyFrom(CompressedBlock.Mid(OffsetInFirstBlock, BytesToUncompress)); } OffsetInFirstBlock = 0; RemainingRawSize -= BytesToUncompress; CompressedOffset += CompressedBlockSize; RawView += BytesToUncompress; } } return RemainingRawSize == 0; } bool BlockDecoder::TryDecompressTo(DecoderContext& Context, const DecoderSource& Source, const FHeader& Header, const MemoryView HeaderView, const uint64_t RawOffset, MutableMemoryView RawView) const { if (Header.TotalRawSize < RawOffset + RawView.GetSize()) { return false; } const uint64_t BlockSize = uint64_t(1) << Header.BlockSizeExponent; const uint64_t LastBlockSize = BlockSize - (BlockSize * Header.BlockCount - Header.TotalRawSize); const uint32_t FirstBlockIndex = uint32_t(RawOffset / BlockSize); const uint32_t LastBlockIndex = uint32_t((RawOffset + RawView.GetSize() - 1) / BlockSize); uint64_t RawBlockOffset = RawOffset % BlockSize; const uint32_t* const CompressedBlockSizes = static_cast(HeaderView.RightChop(sizeof(FHeader)).GetData()); uint64_t CompressedOffset = sizeof(FHeader) + sizeof(uint32_t) * uint32_t(Header.BlockCount); // + Algo::TransformAccumulate(MakeArrayView(CompressedBlockSizes, FirstBlockIndex), // [](uint32_t Size) -> uint64_t { return NETWORK_ORDER32(Size); }, // uint64_t(0) for (uint32_t i = 0; i < FirstBlockIndex; ++i) { CompressedOffset += ByteSwap(CompressedBlockSizes[i]); } for (uint32_t BlockIndex = FirstBlockIndex; BlockIndex <= LastBlockIndex; BlockIndex++) { const uint64_t RawBlockSize = BlockIndex == Header.BlockCount - 1 ? LastBlockSize : BlockSize; const uint64_t RawBlockReadSize = zen::Min(RawView.GetSize(), RawBlockSize - RawBlockOffset); const uint32_t CompressedBlockSize = ByteSwap(CompressedBlockSizes[BlockIndex]); const bool bIsCompressed = CompressedBlockSize < RawBlockSize; if (bIsCompressed) { if (Context.RawBlockIndex == BlockIndex) { RawView.Left(RawBlockReadSize).CopyFrom(Context.RawBlock.GetView().Mid(RawBlockOffset, RawBlockReadSize)); } else { MutableMemoryView RawBlock; if (RawBlockReadSize == RawBlockSize) { RawBlock = RawView.Left(RawBlockSize); } else { if (Context.RawBlock.GetSize() < RawBlockSize) { Context.RawBlock = UniqueBuffer::Alloc(BlockSize); } RawBlock = Context.RawBlock.GetMutableView().Left(RawBlockSize); Context.RawBlockIndex = BlockIndex; } const MemoryView CompressedBlock = Source.ReadOrView(CompressedOffset, CompressedBlockSize, Context); if (CompressedBlock.IsEmpty() || !DecompressBlock(RawBlock, CompressedBlock)) { return false; } if (RawBlockReadSize != RawBlockSize) { RawView.CopyFrom(RawBlock.Mid(RawBlockOffset, RawBlockReadSize)); } } } else { Source.Read(CompressedOffset + RawBlockOffset, RawView.Left(RawBlockReadSize)); } RawBlockOffset = 0; CompressedOffset += CompressedBlockSize; RawView += RawBlockReadSize; } return RawView.GetSize() == 0; } CompositeBuffer BlockDecoder::DecompressToComposite(DecoderContext& Context, const DecoderSource& Source, const FHeader& Header, const MemoryView HeaderView, const uint64_t RawOffset, const uint64_t RawSize) const { if (RawSize > 0) { UniqueBuffer Buffer = UniqueBuffer::Alloc(RawSize); if (TryDecompressTo(Context, Source, Header, HeaderView, RawOffset, Buffer)) { return CompositeBuffer(Buffer.MoveToShared()); } } return CompositeBuffer(); } ////////////////////////////////////////////////////////////////////////// struct OodleInit { OodleInit() { OodleConfigValues Config; Oodle_GetConfigValues(&Config); // Always read/write Oodle v9 binary data. Config.m_OodleLZ_BackwardsCompatible_MajorVersion = 9; Oodle_SetConfigValues(&Config); } }; OodleInit InitOodle; class OodleEncoder final : public BlockEncoder { public: OodleEncoder(OodleCompressor InCompressor, OodleCompressionLevel InCompressionLevel) : Compressor(InCompressor) , CompressionLevel(InCompressionLevel) { } protected: CompressionMethod GetMethod() const final { return CompressionMethod::Oodle; } uint8_t GetCompressor() const final { return static_cast(Compressor); } uint8_t GetCompressionLevel() const final { return static_cast(CompressionLevel); } uint64_t CompressBlockBound(uint64_t RawSize) const final { return static_cast(OodleLZ_GetCompressedBufferSizeNeeded(OodleLZ_Compressor_Kraken, static_cast(RawSize))); } bool CompressBlock(MutableMemoryView& CompressedData, MemoryView RawData) const final { const OodleLZ_Compressor LZCompressor = GetOodleLZCompressor(Compressor); const OodleLZ_CompressionLevel LZCompressionLevel = GetOodleLZCompressionLevel(CompressionLevel); if (LZCompressor == OodleLZ_Compressor_Invalid || LZCompressionLevel == OodleLZ_CompressionLevel_Invalid || LZCompressionLevel == OodleLZ_CompressionLevel_None) { return false; } const OO_SINTa RawSize = static_cast(RawData.GetSize()); if (static_cast(CompressedData.GetSize()) < OodleLZ_GetCompressedBufferSizeNeeded(LZCompressor, RawSize)) { return false; } const OO_SINTa Size = OodleLZ_Compress(LZCompressor, RawData.GetData(), RawSize, CompressedData.GetData(), LZCompressionLevel); CompressedData.LeftInline(static_cast(Size)); return Size > 0; } static OodleLZ_Compressor GetOodleLZCompressor(OodleCompressor Compressor) { switch (Compressor) { case OodleCompressor::Selkie: return OodleLZ_Compressor_Selkie; case OodleCompressor::Mermaid: return OodleLZ_Compressor_Mermaid; case OodleCompressor::Kraken: return OodleLZ_Compressor_Kraken; case OodleCompressor::Leviathan: return OodleLZ_Compressor_Leviathan; case OodleCompressor::NotSet: default: return OodleLZ_Compressor_Invalid; } } static OodleLZ_CompressionLevel GetOodleLZCompressionLevel(OodleCompressionLevel Level) { const int IntLevel = (int)Level; if (IntLevel < (int)OodleLZ_CompressionLevel_Min || IntLevel > (int)OodleLZ_CompressionLevel_Max) { return OodleLZ_CompressionLevel_Invalid; } return OodleLZ_CompressionLevel(IntLevel); } private: const OodleCompressor Compressor; const OodleCompressionLevel CompressionLevel; }; class OodleDecoder final : public BlockDecoder { protected: bool DecompressBlock(MutableMemoryView RawData, MemoryView CompressedData) const final { const OO_SINTa RawSize = static_cast(RawData.GetSize()); const OO_SINTa Size = OodleLZ_Decompress(CompressedData.GetData(), static_cast(CompressedData.GetSize()), RawData.GetData(), RawSize, OodleLZ_FuzzSafe_Yes, OodleLZ_CheckCRC_Yes, OodleLZ_Verbosity_None); return Size == RawSize; } }; /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// class LZ4Decoder final : public BlockDecoder { protected: bool DecompressBlock(MutableMemoryView RawData, MemoryView CompressedData) const final { if (CompressedData.GetSize() <= std::numeric_limits::max()) { const int Size = LZ4_decompress_safe(static_cast(CompressedData.GetData()), static_cast(RawData.GetData()), static_cast(CompressedData.GetSize()), static_cast(zen::Min(RawData.GetSize(), uint64_t(LZ4_MAX_INPUT_SIZE)))); return static_cast(Size) == RawData.GetSize(); } return false; } }; ////////////////////////////////////////////////////////////////////////// static const BaseDecoder* GetDecoder(CompressionMethod Method) { static NoneDecoder None; static OodleDecoder Oodle; static LZ4Decoder LZ4; switch (Method) { default: return nullptr; case CompressionMethod::None: return &None; case CompressionMethod::Oodle: return &Oodle; case CompressionMethod::LZ4: return &LZ4; } } ////////////////////////////////////////////////////////////////////////// bool ReadHeader(const CompositeBuffer& CompressedData, BufferHeader& OutHeader, UniqueBuffer* OutHeaderData) { const uint64_t CompressedDataSize = CompressedData.GetSize(); if (CompressedDataSize < sizeof(BufferHeader)) { return false; } const size_t HeaderBufferSize = 1024; uint8_t HeaderBuffer[HeaderBufferSize]; uint64_t ReadSize = Min(CompressedDataSize, HeaderBufferSize); uint64_t FirstSegmentSize = CompressedData.GetSegments()[0].GetSize(); if (FirstSegmentSize >= sizeof(BufferHeader)) { // Keep first read inside first segment if possible ReadSize = Min(ReadSize, FirstSegmentSize); } MutableMemoryView HeaderMemory(HeaderBuffer, &HeaderBuffer[ReadSize]); CompositeBuffer::Iterator It = CompressedData.GetIterator(0); CompressedData.CopyTo(HeaderMemory, It); OutHeader = *reinterpret_cast(HeaderMemory.GetData()); OutHeader.ByteSwap(); if (OutHeader.Magic != BufferHeader::ExpectedMagic) { return false; } if (OutHeader.TotalCompressedSize > CompressedDataSize) { return false; } const BaseDecoder* const Decoder = GetDecoder(OutHeader.Method); if (!Decoder) { return false; } uint64_t FullHeaderSize = Decoder->GetHeaderSize(OutHeader); if (FullHeaderSize > CompressedDataSize) { return false; } if (OutHeaderData) { *OutHeaderData = UniqueBuffer::Alloc(FullHeaderSize); MutableMemoryView RemainingHeaderView = OutHeaderData->GetMutableView().CopyFrom(HeaderMemory.Mid(0, FullHeaderSize)); if (!RemainingHeaderView.IsEmpty()) { CompressedData.CopyTo(RemainingHeaderView, It); } if (OutHeader.Crc32 != BufferHeader::CalculateCrc32(OutHeaderData->GetView())) { return false; } } else if (FullHeaderSize < ReadSize) { if (OutHeader.Crc32 != BufferHeader::CalculateCrc32(HeaderMemory.Mid(0, FullHeaderSize))) { return false; } } else { UniqueBuffer HeaderData = UniqueBuffer::Alloc(FullHeaderSize); MutableMemoryView RemainingHeaderView = HeaderData.GetMutableView().CopyFrom(HeaderMemory.Mid(0, FullHeaderSize)); if (!RemainingHeaderView.IsEmpty()) { CompressedData.CopyTo(RemainingHeaderView, It); } if (OutHeader.Crc32 != BufferHeader::CalculateCrc32(HeaderData.GetView())) { return false; } } return true; } bool BufferHeader::IsValid(const CompositeBuffer& CompressedData, IoHash& OutRawHash, uint64_t& OutRawSize, uint64_t* OutOptionalTotalCompressedSize) { detail::BufferHeader Header; if (ReadHeader(CompressedData, Header, nullptr)) { OutRawHash = IoHash::FromBLAKE3(Header.RawHash); OutRawSize = Header.TotalRawSize; if (OutOptionalTotalCompressedSize) { *OutOptionalTotalCompressedSize = Header.TotalCompressedSize; } return true; } return false; } /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// static bool TryReadHeader(DecoderContext& Context, Archive& Ar, FHeader& OutHeader, MemoryView& OutHeaderView) { if (Context.HeaderOffset != MAX_uint64) { OutHeaderView = Context.Header.GetView().Left(Context.HeaderSize); MakeMutableMemoryView(&OutHeader, &OutHeader + 1).CopyFrom(OutHeaderView.Left(sizeof(FHeader))); OutHeader.ByteSwap(); return true; } const int64_t Offset = Ar.Tell(); FHeader& Header = OutHeader; Ar.Serialize(&Header, sizeof(FHeader)); Header.ByteSwap(); if (const BaseDecoder* const Decoder = GetDecoder(Header.Method); Decoder && Header.Magic == FHeader::ExpectedMagic) { const uint64_t HeaderSize = Decoder->GetHeaderSize(Header); if (Context.Header.GetSize() < HeaderSize) { Context.Header = UniqueBuffer::Alloc(zen::Max(NextPow2(HeaderSize), DefaultHeaderSize)); } const MutableMemoryView HeaderView = Context.Header.GetMutableView().Left(HeaderSize); const MutableMemoryView HeaderTail = HeaderView.CopyFrom(MakeMemoryView(&Header, &Header + 1)); Ar.Serialize(HeaderTail.GetData(), int64_t(HeaderTail.GetSize())); FHeader* const HeaderCopy = static_cast(HeaderView.GetData()); HeaderCopy->ByteSwap(); if (Header.Crc32 == FHeader::CalculateCrc32(HeaderView)) { Context.HeaderOffset = uint64_t(Offset); Context.HeaderSize = HeaderSize; Context.HeaderCrc32 = Header.Crc32; Context.RawBlockIndex = MAX_uint32; OutHeaderView = HeaderView; return true; } } return false; } static bool TryReadHeader(DecoderContext& Context, const CompositeBuffer& Buffer, FHeader& OutHeader, MemoryView& OutHeaderView) { if (Context.HeaderOffset != MAX_uint64) { OutHeaderView = Buffer.ViewOrCopyRange(Context.HeaderOffset, Context.HeaderSize, Context.Header); MakeMutableMemoryView(&OutHeader, &OutHeader + 1).CopyFrom(OutHeaderView.Left(sizeof(FHeader))); OutHeader.ByteSwap(); return true; } if (Buffer.GetSize() < sizeof(FHeader)) { return false; } FHeader& Header = OutHeader; Buffer.CopyTo(MakeMutableMemoryView(&Header, &Header + 1)); Header.ByteSwap(); if (const BaseDecoder* const Decoder = GetDecoder(Header.Method); Decoder && Header.Magic == FHeader::ExpectedMagic) { const uint64_t HeaderSize = Decoder->GetHeaderSize(Header); const MemoryView HeaderView = Buffer.ViewOrCopyRange(0, HeaderSize, Context.Header, [](uint64_t Size) { return UniqueBuffer::Alloc(zen::Max(NextPow2(Size), DefaultHeaderSize)); }); if (Header.Crc32 == FHeader::CalculateCrc32(HeaderView)) { Context.HeaderOffset = 0; Context.HeaderSize = HeaderSize; if (Context.HeaderCrc32 != Header.Crc32) { Context.HeaderCrc32 = Header.Crc32; Context.RawBlockIndex = MAX_uint32; } OutHeaderView = HeaderView; return true; } } return false; } /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// class ArchiveDecoderSource final : public DecoderSource { public: explicit ArchiveDecoderSource(Archive& InArchive, const uint64_t InBaseOffset) : m_Archive(InArchive), m_BaseOffset(InBaseOffset) {} bool Read(uint64_t Offset, MutableMemoryView Data) const final { m_Archive.Seek(int64_t(m_BaseOffset + Offset)); m_Archive.Serialize(Data.GetData(), int64_t(Data.GetSize())); return !m_Archive.IsError(); } MemoryView ReadOrView(const uint64_t Offset, const uint64_t Size, DecoderContext& Context) const final { if (Context.CompressedBlock.GetSize() < Size) { Context.CompressedBlock = UniqueBuffer::Alloc(zen::Max(NextPow2(Size), DefaultBlockSize)); } const MutableMemoryView View = Context.CompressedBlock.GetMutableView().Left(Size); return Read(Offset, View) ? View : MemoryView(); } CompositeBuffer ReadToComposite(const uint64_t Offset, const uint64_t Size) const final { UniqueBuffer Buffer = UniqueBuffer::Alloc(Size); if (Read(Offset, Buffer)) { return CompositeBuffer(Buffer.MoveToShared()); } return CompositeBuffer(); } private: Archive& m_Archive; const uint64_t m_BaseOffset; }; class BufferDecoderSource final : public DecoderSource { public: explicit BufferDecoderSource(const CompositeBuffer& InBuffer) : m_Buffer(InBuffer) {} bool Read(const uint64_t Offset, const MutableMemoryView Data) const final { if (Offset + Data.GetSize() <= m_Buffer.GetSize()) { m_Buffer.CopyTo(Data, Offset); return true; } return false; } MemoryView ReadOrView(const uint64_t Offset, const uint64_t Size, DecoderContext& Context) const final { return m_Buffer.ViewOrCopyRange(Offset, Size, Context.CompressedBlock, [](uint64_t BufferSize) -> UniqueBuffer { return UniqueBuffer::Alloc(zen::Max(zen::NextPow2(BufferSize), DefaultBlockSize)); }); } CompositeBuffer ReadToComposite(const uint64_t Offset, const uint64_t Size) const final { return m_Buffer.Mid(Offset, Size).MakeOwned(); } private: const CompositeBuffer& m_Buffer; }; ////////////////////////////////////////////////////////////////////////// template inline CompositeBuffer ValidBufferOrEmpty(BufferType&& CompressedData, IoHash& OutRawHash, uint64_t& OutRawSize, uint64_t* OutOptionalTotalCompressedSize) { return BufferHeader::IsValid(CompressedData, OutRawHash, OutRawSize, OutOptionalTotalCompressedSize) ? CompositeBuffer(std::forward(CompressedData)) : CompositeBuffer(); } CompositeBuffer GetCompressedRange(const BufferHeader& Header, MemoryView HeaderRawData, const CompositeBuffer& CompressedData, uint64_t RawOffset, uint64_t RawSize) { if (Header.TotalRawSize < RawOffset + RawSize) { return CompositeBuffer(); } if (Header.Method == CompressionMethod::None) { BufferHeader NewHeader = Header; NewHeader.Crc32 = 0; NewHeader.TotalRawSize = RawSize; NewHeader.TotalCompressedSize = NewHeader.TotalRawSize + sizeof(BufferHeader); NewHeader.RawHash = BLAKE3(); UniqueBuffer HeaderData = UniqueBuffer::Alloc(sizeof(BufferHeader)); NewHeader.Write(HeaderData); return CompositeBuffer(HeaderData.MoveToShared(), CompressedData.Mid(sizeof(BufferHeader) + RawOffset, RawSize).MakeOwned()); } else { MemoryView BlockSizeView = HeaderRawData.Mid(sizeof(Header), Header.BlockCount * sizeof(uint32_t)); std::span CompressedBlockSizes(reinterpret_cast(BlockSizeView.GetData()), Header.BlockCount); const uint64_t BlockSize = uint64_t(1) << Header.BlockSizeExponent; const uint64_t LastBlockSize = BlockSize - ((Header.BlockCount * BlockSize) - Header.TotalRawSize); const size_t FirstBlock = uint64_t(RawOffset / BlockSize); const size_t LastBlock = uint64_t((RawOffset + RawSize - 1) / BlockSize); uint64_t CompressedOffset = sizeof(BufferHeader) + uint64_t(Header.BlockCount) * sizeof(uint32_t); const uint64_t NewBlockCount = LastBlock - FirstBlock + 1; const uint64_t NewMetaSize = NewBlockCount * sizeof(uint32_t); uint64_t NewCompressedSize = 0; uint64_t NewTotalRawSize = 0; std::vector NewCompressedBlockSizes; NewCompressedBlockSizes.reserve(NewBlockCount); for (size_t BlockIndex = FirstBlock; BlockIndex <= LastBlock; ++BlockIndex) { const uint64_t UncompressedBlockSize = (BlockIndex == Header.BlockCount - 1) ? LastBlockSize : BlockSize; NewTotalRawSize += UncompressedBlockSize; const uint32_t CompressedBlockSize = CompressedBlockSizes[BlockIndex]; NewCompressedBlockSizes.push_back(CompressedBlockSize); NewCompressedSize += ByteSwap(CompressedBlockSize); } const uint64_t NewTotalCompressedSize = sizeof(BufferHeader) + NewBlockCount * sizeof(uint32_t) + NewCompressedSize; const uint64_t NewCompressedHeaderSize = sizeof(BufferHeader) + NewBlockCount * sizeof(uint32_t); UniqueBuffer NewCompressedHeaderData = UniqueBuffer::Alloc(NewCompressedHeaderSize); // Seek to first compressed block for (size_t BlockIndex = 0; BlockIndex < FirstBlock; ++BlockIndex) { const uint64_t CompressedBlockSize = ByteSwap(CompressedBlockSizes[BlockIndex]); CompressedOffset += CompressedBlockSize; } CompositeBuffer NewCompressedData = CompressedData.Mid(CompressedOffset, NewCompressedSize).MakeOwned(); // Copy block sizes NewCompressedHeaderData.GetMutableView().Mid(sizeof(BufferHeader), NewMetaSize).CopyFrom(MakeMemoryView(NewCompressedBlockSizes)); BufferHeader NewHeader; NewHeader.Crc32 = 0; NewHeader.Method = Header.Method; NewHeader.Compressor = Header.Compressor; NewHeader.CompressionLevel = Header.CompressionLevel; NewHeader.BlockSizeExponent = Header.BlockSizeExponent; NewHeader.BlockCount = static_cast(NewBlockCount); NewHeader.TotalRawSize = NewTotalRawSize; NewHeader.TotalCompressedSize = NewTotalCompressedSize; NewHeader.RawHash = BLAKE3(); NewHeader.Write(NewCompressedHeaderData.GetMutableView().Left(sizeof(BufferHeader) + NewMetaSize)); return CompositeBuffer(NewCompressedHeaderData.MoveToShared(), NewCompressedData); } } CompositeBuffer CopyCompressedRange(const BufferHeader& Header, MemoryView HeaderRawData, const CompositeBuffer& CompressedData, uint64_t RawOffset, uint64_t RawSize) { if (Header.TotalRawSize < RawOffset + RawSize) { return CompositeBuffer(); } if (Header.Method == CompressionMethod::None) { UniqueBuffer NewCompressedData = UniqueBuffer::Alloc(RawSize); CompressedData.CopyTo(NewCompressedData.GetMutableView(), sizeof(Header) + RawOffset); BufferHeader NewHeader = Header; NewHeader.Crc32 = 0; NewHeader.TotalRawSize = RawSize; NewHeader.TotalCompressedSize = NewHeader.TotalRawSize + sizeof(BufferHeader); NewHeader.RawHash = BLAKE3(); UniqueBuffer HeaderData = UniqueBuffer::Alloc(sizeof(BufferHeader)); NewHeader.Write(HeaderData); return CompositeBuffer(HeaderData.MoveToShared(), NewCompressedData.MoveToShared()); } else { MemoryView BlockSizeView = HeaderRawData.Mid(sizeof(Header), Header.BlockCount * sizeof(uint32_t)); std::span CompressedBlockSizes(reinterpret_cast(BlockSizeView.GetData()), Header.BlockCount); const uint64_t BlockSize = uint64_t(1) << Header.BlockSizeExponent; const uint64_t LastBlockSize = BlockSize - ((Header.BlockCount * BlockSize) - Header.TotalRawSize); const size_t FirstBlock = uint64_t(RawOffset / BlockSize); const size_t LastBlock = uint64_t((RawOffset + RawSize - 1) / BlockSize); uint64_t CompressedOffset = sizeof(BufferHeader) + uint64_t(Header.BlockCount) * sizeof(uint32_t); const uint64_t NewBlockCount = LastBlock - FirstBlock + 1; const uint64_t NewMetaSize = NewBlockCount * sizeof(uint32_t); uint64_t NewCompressedSize = 0; uint64_t NewTotalRawSize = 0; std::vector NewCompressedBlockSizes; NewCompressedBlockSizes.reserve(NewBlockCount); for (size_t BlockIndex = FirstBlock; BlockIndex <= LastBlock; ++BlockIndex) { const uint64_t UncompressedBlockSize = (BlockIndex == Header.BlockCount - 1) ? LastBlockSize : BlockSize; NewTotalRawSize += UncompressedBlockSize; const uint32_t CompressedBlockSize = CompressedBlockSizes[BlockIndex]; NewCompressedBlockSizes.push_back(CompressedBlockSize); NewCompressedSize += ByteSwap(CompressedBlockSize); } const uint64_t NewTotalCompressedSize = sizeof(BufferHeader) + NewBlockCount * sizeof(uint32_t) + NewCompressedSize; UniqueBuffer NewCompressedData = UniqueBuffer::Alloc(NewTotalCompressedSize); MutableMemoryView NewCompressedBlocks = NewCompressedData.GetMutableView() + sizeof(BufferHeader) + NewMetaSize; // Seek to first compressed block for (size_t BlockIndex = 0; BlockIndex < FirstBlock; ++BlockIndex) { const uint64_t CompressedBlockSize = ByteSwap(CompressedBlockSizes[BlockIndex]); CompressedOffset += CompressedBlockSize; } // Copy blocks UniqueBuffer CompressedBlockCopy; const MemoryView CompressedRange = CompressedData.ViewOrCopyRange(CompressedOffset, NewCompressedSize, CompressedBlockCopy); NewCompressedBlocks.CopyFrom(CompressedRange); // Copy block sizes NewCompressedData.GetMutableView().Mid(sizeof(BufferHeader), NewMetaSize).CopyFrom(MakeMemoryView(NewCompressedBlockSizes)); BufferHeader NewHeader; NewHeader.Crc32 = 0; NewHeader.Method = Header.Method; NewHeader.Compressor = Header.Compressor; NewHeader.CompressionLevel = Header.CompressionLevel; NewHeader.BlockSizeExponent = Header.BlockSizeExponent; NewHeader.BlockCount = static_cast(NewBlockCount); NewHeader.TotalRawSize = NewTotalRawSize; NewHeader.TotalCompressedSize = NewTotalCompressedSize; NewHeader.RawHash = BLAKE3(); NewHeader.Write(NewCompressedData.GetMutableView().Left(sizeof(BufferHeader) + NewMetaSize)); return CompositeBuffer(NewCompressedData.MoveToShared()); } } } // namespace zen::detail namespace zen { const CompressedBuffer CompressedBuffer::Null; CompressedBuffer CompressedBuffer::Compress(const CompositeBuffer& RawData, OodleCompressor Compressor, OodleCompressionLevel CompressionLevel, uint64_t BlockSize) { using namespace detail; if (BlockSize == 0) { BlockSize = DefaultBlockSize; } CompressedBuffer Local; if (CompressionLevel == OodleCompressionLevel::None) { Local.CompressedData = NoneEncoder().Compress(RawData, BlockSize); } else { Local.CompressedData = OodleEncoder(Compressor, CompressionLevel).Compress(RawData, BlockSize); } return Local; } CompressedBuffer CompressedBuffer::Compress(const SharedBuffer& RawData, OodleCompressor Compressor, OodleCompressionLevel CompressionLevel, uint64_t BlockSize) { return Compress(CompositeBuffer(RawData), Compressor, CompressionLevel, BlockSize); } bool CompressedBuffer::CompressToStream( const CompositeBuffer& RawData, std::function&& Callback, OodleCompressor Compressor, OodleCompressionLevel CompressionLevel, uint64_t BlockSize) { using namespace detail; if (BlockSize == 0) { BlockSize = DefaultBlockSize; } if (CompressionLevel == OodleCompressionLevel::None) { return NoneEncoder().CompressToStream(RawData, std::move(Callback), BlockSize); } else { return OodleEncoder(Compressor, CompressionLevel).CompressToStream(RawData, std::move(Callback), BlockSize); } } CompressedBuffer CompressedBuffer::FromCompressed(const CompositeBuffer& InCompressedData, IoHash& OutRawHash, uint64_t& OutRawSize) { CompressedBuffer Local; Local.CompressedData = detail::ValidBufferOrEmpty(InCompressedData, OutRawHash, OutRawSize, /*OutOptionalTotalCompressedSize*/ nullptr); return Local; } CompressedBuffer CompressedBuffer::FromCompressed(CompositeBuffer&& InCompressedData, IoHash& OutRawHash, uint64_t& OutRawSize) { CompressedBuffer Local; Local.CompressedData = detail::ValidBufferOrEmpty(std::move(InCompressedData), OutRawHash, OutRawSize, /*OutOptionalTotalCompressedSize*/ nullptr); return Local; } CompressedBuffer CompressedBuffer::FromCompressed(const SharedBuffer& InCompressedData, IoHash& OutRawHash, uint64_t& OutRawSize) { CompressedBuffer Local; Local.CompressedData = detail::ValidBufferOrEmpty(InCompressedData, OutRawHash, OutRawSize, /*OutOptionalTotalCompressedSize*/ nullptr); return Local; } CompressedBuffer CompressedBuffer::FromCompressed(SharedBuffer&& InCompressedData, IoHash& OutRawHash, uint64_t& OutRawSize) { CompressedBuffer Local; Local.CompressedData = detail::ValidBufferOrEmpty(std::move(InCompressedData), OutRawHash, OutRawSize, /*OutOptionalTotalCompressedSize*/ nullptr); return Local; } CompressedBuffer CompressedBuffer::FromCompressedNoValidate(IoBuffer&& InCompressedData) { if (InCompressedData.GetSize() < sizeof(detail::BufferHeader)) { return CompressedBuffer(); } CompressedBuffer Local; Local.CompressedData = CompositeBuffer(SharedBuffer(std::move(InCompressedData))); return Local; } CompressedBuffer CompressedBuffer::FromCompressedNoValidate(CompositeBuffer&& InCompressedData) { if (InCompressedData.GetSize() < sizeof(detail::BufferHeader)) { return CompressedBuffer(); } CompressedBuffer Local; Local.CompressedData = std::move(InCompressedData); return Local; } bool CompressedBuffer::ValidateCompressedHeader(IoBuffer&& CompressedData, IoHash& OutRawHash, uint64_t& OutRawSize, uint64_t* OutOptionalTotalCompressedSize) { return detail::BufferHeader::IsValid(SharedBuffer(std::move(CompressedData)), OutRawHash, OutRawSize, OutOptionalTotalCompressedSize); } bool CompressedBuffer::ValidateCompressedHeader(const IoBuffer& CompressedData, IoHash& OutRawHash, uint64_t& OutRawSize, uint64_t* OutOptionalTotalCompressedSize) { return detail::BufferHeader::IsValid(SharedBuffer(CompressedData), OutRawHash, OutRawSize, OutOptionalTotalCompressedSize); } bool CompressedBuffer::ValidateCompressedHeader(const CompositeBuffer& CompressedData, IoHash& OutRawHash, uint64_t& OutRawSize, uint64_t* OutOptionalTotalCompressedSize) { return detail::BufferHeader::IsValid(CompressedData, OutRawHash, OutRawSize, OutOptionalTotalCompressedSize); } size_t CompressedBuffer::GetHeaderSizeForNoneEncoder() { return sizeof(detail::BufferHeader); } UniqueBuffer CompressedBuffer::CreateHeaderForNoneEncoder(uint64_t RawSize, const BLAKE3& RawHash) { detail::BufferHeader Header; Header.Method = detail::CompressionMethod::None; Header.BlockCount = 1; Header.TotalRawSize = RawSize; Header.TotalCompressedSize = Header.TotalRawSize + sizeof(detail::BufferHeader); Header.RawHash = RawHash; UniqueBuffer HeaderData = UniqueBuffer::Alloc(sizeof(detail::BufferHeader)); Header.Write(HeaderData); return HeaderData; } uint64_t CompressedBuffer::DecodeRawSize() const { return CompressedData ? detail::BufferHeader::Read(CompressedData).TotalRawSize : 0; } IoHash CompressedBuffer::DecodeRawHash() const { return CompressedData ? IoHash::FromBLAKE3(detail::BufferHeader::Read(CompressedData).RawHash) : IoHash(); } CompressedBuffer CompressedBuffer::CopyRange(uint64_t RawOffset, uint64_t RawSize) const { CompressedBuffer Range; if (RawSize > 0) { detail::BufferHeader Header; UniqueBuffer RawHeaderData; if (ReadHeader(CompressedData, Header, &RawHeaderData)) { if (RawOffset < Header.TotalRawSize) { const uint64_t MaxRawSize = RawOffset == ~uint64_t(0) ? Header.TotalRawSize : Header.TotalRawSize - RawOffset; const uint64_t TotalRawSize = RawSize == ~uint64_t(0) ? MaxRawSize : Min(RawSize, MaxRawSize); if (TotalRawSize > 0) { Range.CompressedData = CopyCompressedRange(Header, RawHeaderData.GetView(), CompressedData, RawOffset, TotalRawSize); } } } } return Range; } CompressedBuffer CompressedBuffer::GetRange(uint64_t RawOffset, uint64_t RawSize) const { CompressedBuffer Range; if (RawSize > 0) { detail::BufferHeader Header; UniqueBuffer RawHeaderData; if (ReadHeader(CompressedData, Header, &RawHeaderData)) { if (RawOffset < Header.TotalRawSize) { const uint64_t MaxRawSize = RawOffset == ~uint64_t(0) ? Header.TotalRawSize : Header.TotalRawSize - RawOffset; const uint64_t TotalRawSize = RawSize == ~uint64_t(0) ? MaxRawSize : Min(RawSize, MaxRawSize); if (TotalRawSize > 0) { Range.CompressedData = GetCompressedRange(Header, RawHeaderData.GetView(), CompressedData, RawOffset, TotalRawSize); } } } } return Range; } bool CompressedBuffer::TryDecompressTo(MutableMemoryView RawView, uint64_t RawOffset) const { using namespace detail; if (CompressedData && RawView.GetSize() > 0) { const BufferHeader Header = BufferHeader::Read(CompressedData); if (Header.Magic == BufferHeader::ExpectedMagic) { if (const BaseDecoder* const Decoder = GetDecoder(Header.Method)) { return Decoder->TryDecompressTo(Header, CompressedData, RawView, RawOffset); } } } return false; } SharedBuffer CompressedBuffer::Decompress(uint64_t RawOffset, uint64_t RawSize) const { using namespace detail; if (CompressedData && RawSize > 0) { const BufferHeader Header = BufferHeader::Read(CompressedData); if (Header.Magic == BufferHeader::ExpectedMagic) { if (const BaseDecoder* const Decoder = GetDecoder(Header.Method)) { const uint64_t TotalRawSize = RawSize < ~uint64_t(0) ? RawSize : Header.TotalRawSize - RawOffset; UniqueBuffer RawData = UniqueBuffer::Alloc(TotalRawSize); if (Decoder->TryDecompressTo(Header, CompressedData, RawData, RawOffset)) { return RawData.MoveToShared(); } } } } return SharedBuffer(); } CompositeBuffer CompressedBuffer::DecompressToComposite() const { using namespace detail; if (CompressedData) { const BufferHeader Header = BufferHeader::Read(CompressedData); if (Header.Magic == BufferHeader::ExpectedMagic) { if (const BaseDecoder* const Decoder = GetDecoder(Header.Method)) { return Decoder->DecompressToComposite(Header, CompressedData); } } } return CompositeBuffer(); } bool CompressedBuffer::DecompressToStream( uint64_t RawOffset, uint64_t RawSize, std::function&& Callback) const { using namespace detail; if (CompressedData) { const BufferHeader Header = BufferHeader::Read(CompressedData); if (Header.Magic == BufferHeader::ExpectedMagic) { if (const BaseDecoder* const Decoder = GetDecoder(Header.Method)) { const uint64_t TotalRawSize = RawSize < ~uint64_t(0) ? RawSize : Header.TotalRawSize - RawOffset; return Decoder->DecompressToStream(Header, CompressedData, RawOffset, TotalRawSize, std::move(Callback)); } } } return false; } bool CompressedBuffer::TryGetCompressParameters(OodleCompressor& OutCompressor, OodleCompressionLevel& OutCompressionLevel, uint64_t& OutBlockSize) const { using namespace detail; if (CompressedData) { switch (const BufferHeader Header = BufferHeader::Read(CompressedData); Header.Method) { case CompressionMethod::None: OutCompressor = OodleCompressor::NotSet; OutCompressionLevel = OodleCompressionLevel::None; OutBlockSize = 0; return true; case CompressionMethod::Oodle: OutCompressor = OodleCompressor(Header.Compressor); OutCompressionLevel = OodleCompressionLevel(Header.CompressionLevel); OutBlockSize = uint64_t(1) << Header.BlockSizeExponent; return true; default: break; } } return false; } /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// CompressedBufferReader::CompressedBufferReader(Archive& Archive) { SetSource(Archive); } CompressedBufferReader::CompressedBufferReader(const CompressedBuffer& Buffer) { SetSource(Buffer); } void CompressedBufferReader::ResetBuffers() { using namespace detail; if (SourceArchive && Context.HeaderOffset != MAX_uint64) { SourceArchive->Seek(int64_t(Context.HeaderOffset)); } Context = DecoderContext(); } void CompressedBufferReader::ResetSource() { Context.HeaderOffset = MAX_uint64; SourceArchive = nullptr; SourceBuffer = nullptr; } void CompressedBufferReader::SetSource(Archive& Archive) { if (SourceArchive == &Archive) { return; } Context.HeaderOffset = MAX_uint64; SourceArchive = &Archive; SourceBuffer = nullptr; } void CompressedBufferReader::SetSource(const CompressedBuffer& Buffer) { if (SourceBuffer == &Buffer) { return; } Context.HeaderOffset = MAX_uint64; SourceArchive = nullptr; SourceBuffer = &Buffer; } uint64_t CompressedBufferReader::GetCompressedSize() { using namespace detail; BufferHeader Header; MemoryView HeaderView; if (TryReadHeader(Header, HeaderView)) { return Header.TotalCompressedSize; } return 0; } uint64_t CompressedBufferReader::GetRawSize() { using namespace detail; BufferHeader Header; MemoryView HeaderView; if (TryReadHeader(Header, HeaderView)) { return Header.TotalRawSize; } return 0; } IoHash CompressedBufferReader::GetRawHash() { using namespace detail; BufferHeader Header; MemoryView HeaderView; if (TryReadHeader(Header, HeaderView)) { return IoHash::FromBLAKE3(Header.RawHash); } return {}; } bool CompressedBufferReader::TryGetCompressParameters(OodleCompressor& OutCompressor, OodleCompressionLevel& OutCompressionLevel, uint64_t& OutBlockSize) { using namespace detail; BufferHeader Header; MemoryView HeaderView; if (TryReadHeader(Header, HeaderView)) { return Header.TryGetCompressParameters(OutCompressor, OutCompressionLevel, OutBlockSize); } return false; } bool CompressedBufferReader::TryDecompressTo(const MutableMemoryView RawView, const uint64_t RawOffset) { using namespace detail; BufferHeader Header; MemoryView HeaderView; if (TryReadHeader(Header, HeaderView)) { const uint64_t TotalRawSize = Header.TotalRawSize; if (RawOffset <= TotalRawSize && RawView.GetSize() <= TotalRawSize - RawOffset) { if (const BaseDecoder* const Decoder = GetDecoder(Header.Method)) { if (Decoder->TryDecompressTo( Context, SourceArchive ? static_cast(ArchiveDecoderSource(*SourceArchive, Context.HeaderOffset)) : static_cast(BufferDecoderSource(SourceBuffer->GetCompressed())), Header, HeaderView, RawOffset, RawView)) { return true; } } } } return false; } SharedBuffer CompressedBufferReader::Decompress(const uint64_t RawOffset, const uint64_t RawSize) { if (RawSize > 0) { using namespace detail; BufferHeader Header; MemoryView HeaderView; if (TryReadHeader(Header, HeaderView)) { const uint64_t TotalRawSize = Header.TotalRawSize; const uint64_t RawSizeToCopy = RawSize == MAX_uint64 ? TotalRawSize - RawOffset : RawSize; if (RawOffset <= TotalRawSize && RawSizeToCopy <= TotalRawSize - RawOffset) { if (const BaseDecoder* const Decoder = GetDecoder(Header.Method)) { UniqueBuffer RawData = UniqueBuffer::Alloc(RawSizeToCopy); if (Decoder->TryDecompressTo( Context, SourceArchive ? static_cast(ArchiveDecoderSource(*SourceArchive, Context.HeaderOffset)) : static_cast(BufferDecoderSource(SourceBuffer->GetCompressed())), Header, HeaderView, RawOffset, RawData)) { return RawData.MoveToShared(); } } } } } return {}; } CompositeBuffer CompressedBufferReader::DecompressToComposite(const uint64_t RawOffset, const uint64_t RawSize) { using namespace detail; BufferHeader Header; MemoryView HeaderView; if (TryReadHeader(Header, HeaderView)) { const uint64_t TotalRawSize = Header.TotalRawSize; const uint64_t RawSizeToCopy = RawSize == MAX_uint64 ? TotalRawSize - RawOffset : RawSize; if (RawOffset <= TotalRawSize && RawSizeToCopy <= TotalRawSize - RawOffset) { if (const BaseDecoder* const Decoder = GetDecoder(Header.Method)) { return Decoder->DecompressToComposite( Context, SourceArchive ? static_cast(ArchiveDecoderSource(*SourceArchive, Context.HeaderOffset)) : static_cast(BufferDecoderSource(SourceBuffer->GetCompressed())), Header, HeaderView, RawOffset, RawSizeToCopy); } } } return CompositeBuffer(); } bool CompressedBufferReader::TryReadHeader(detail::BufferHeader& OutHeader, MemoryView& OutHeaderView) { using namespace detail; if (Archive* const Archive = SourceArchive) { return detail::TryReadHeader(Context, *Archive, OutHeader, OutHeaderView); } if (const CompressedBuffer* const Buffer = SourceBuffer) { return detail::TryReadHeader(Context, Buffer->GetCompressed(), OutHeader, OutHeaderView); } return false; } /** A type that sets the source of a reader upon construction and resets it upon destruction. */ class CompressedBufferReaderSourceScope { public: inline CompressedBufferReaderSourceScope(CompressedBufferReader& InReader, Archive& InArchive) : Reader(InReader) { Reader.SetSource(InArchive); } inline CompressedBufferReaderSourceScope(CompressedBufferReader& InReader, const CompressedBuffer& InBuffer) : Reader(InReader) { Reader.SetSource(InBuffer); } inline ~CompressedBufferReaderSourceScope() { Reader.ResetSource(); } private: CompressedBufferReader& Reader; }; /** ______________________ _____________________________ \__ ___/\_ _____// _____/\__ ___/ _____/ | | | __)_ \_____ \ | | \_____ \ | | | \/ \ | | / \ |____| /_______ /_______ / |____| /_______ / \/ \/ \/ */ #if ZEN_WITH_TESTS TEST_CASE("CompressedBuffer") { uint8_t Zeroes[1024]{}; uint8_t Ones[1024]; memset(Ones, 1, sizeof Ones); { CompressedBuffer Buffer = CompressedBuffer::Compress(CompositeBuffer(SharedBuffer::MakeView(MakeMemoryView(Zeroes))), OodleCompressor::NotSet, OodleCompressionLevel::None); CHECK(Buffer.DecodeRawSize() == sizeof(Zeroes)); CHECK(Buffer.GetCompressedSize() == (sizeof(Zeroes) + sizeof(detail::BufferHeader))); CompositeBuffer Compressed = Buffer.GetCompressed(); IoHash DecodedHash; uint64_t DecodedRawSize; CompressedBuffer BufferD = CompressedBuffer::FromCompressed(Compressed, DecodedHash, DecodedRawSize); CHECK(BufferD.IsNull() == false); CompositeBuffer Decomp = BufferD.DecompressToComposite(); CHECK(Decomp.GetSize() == DecodedRawSize); CHECK(IoHash::HashBuffer(Decomp) == DecodedHash); } { CompressedBuffer Buffer = CompressedBuffer::Compress( CompositeBuffer(SharedBuffer::MakeView(MakeMemoryView(Zeroes)), SharedBuffer::MakeView(MakeMemoryView(Ones))), OodleCompressor::NotSet, OodleCompressionLevel::None); CHECK(Buffer.DecodeRawSize() == (sizeof(Zeroes) + sizeof(Ones))); CHECK(Buffer.GetCompressedSize() == (sizeof(Zeroes) + sizeof(Ones) + sizeof(detail::BufferHeader))); CompositeBuffer Compressed = Buffer.GetCompressed(); IoHash DecodedHash; uint64_t DecodedRawSize; CompressedBuffer BufferD = CompressedBuffer::FromCompressed(Compressed, DecodedHash, DecodedRawSize); CHECK(BufferD.IsNull() == false); CompositeBuffer Decomp = BufferD.DecompressToComposite(); CHECK(Decomp.GetSize() == DecodedRawSize); CHECK(IoHash::HashBuffer(Decomp) == DecodedHash); } { CompressedBuffer Buffer = CompressedBuffer::Compress(CompositeBuffer(SharedBuffer::MakeView(MakeMemoryView(Zeroes)))); CHECK(Buffer.DecodeRawSize() == sizeof(Zeroes)); CHECK(Buffer.GetCompressedSize() < sizeof(Zeroes)); CompositeBuffer Compressed = Buffer.GetCompressed(); IoHash DecodedHash; uint64_t DecodedRawSize; CompressedBuffer BufferD = CompressedBuffer::FromCompressed(Compressed, DecodedHash, DecodedRawSize); CHECK(BufferD.IsNull() == false); CompositeBuffer Decomp = BufferD.DecompressToComposite(); CHECK(Decomp.GetSize() == DecodedRawSize); CHECK(IoHash::HashBuffer(Decomp) == DecodedHash); } { CompressedBuffer Buffer = CompressedBuffer::Compress( CompositeBuffer(SharedBuffer::MakeView(MakeMemoryView(Zeroes)), SharedBuffer::MakeView(MakeMemoryView(Ones)))); CHECK(Buffer.DecodeRawSize() == (sizeof(Zeroes) + sizeof(Ones))); CHECK(Buffer.GetCompressedSize() < (sizeof(Zeroes) + sizeof(Ones))); CompositeBuffer Compressed = Buffer.GetCompressed(); IoHash DecodedHash; uint64_t DecodedRawSize; CompressedBuffer BufferD = CompressedBuffer::FromCompressed(Compressed, DecodedHash, DecodedRawSize); CHECK(BufferD.IsNull() == false); CompositeBuffer Decomp = BufferD.DecompressToComposite(); CHECK(Decomp.GetSize() == DecodedRawSize); CHECK(IoHash::HashBuffer(Decomp) == DecodedHash); } auto GenerateData = [](uint64_t N) -> std::vector { std::vector Data; Data.resize(N); for (size_t Idx = 0; Idx < Data.size(); ++Idx) { Data[Idx] = Idx; } return Data; }; auto ValidateData = [](std::span Values, std::span ExpectedValues, uint64_t Offset) { for (size_t Idx = Offset; uint64_t Value : Values) { const uint64_t ExpectedValue = ExpectedValues[Idx++]; CHECK(Value == ExpectedValue); } }; SUBCASE("decompress with offset and size") { auto UncompressAndValidate = [&ValidateData](CompressedBuffer Compressed, uint64_t OffsetCount, uint64_t Count, const std::vector& ExpectedValues) { SharedBuffer Uncompressed = Compressed.Decompress(OffsetCount * sizeof(uint64_t), Count * sizeof(uint64_t)); CHECK(Uncompressed.GetSize() == Count * sizeof(uint64_t)); std::span Values((const uint64_t*)Uncompressed.GetData(), Uncompressed.GetSize() / sizeof(uint64_t)); ValidateData(Values, ExpectedValues, OffsetCount); }; const uint64_t BlockSize = 64 * sizeof(uint64_t); const uint64_t N = 5000; std::vector ExpectedValues = GenerateData(N); CompressedBuffer Compressed = CompressedBuffer::Compress(SharedBuffer::MakeView(MakeMemoryView(ExpectedValues)), OodleCompressor::Mermaid, OodleCompressionLevel::Optimal4, BlockSize); UncompressAndValidate(Compressed, 0, N, ExpectedValues); UncompressAndValidate(Compressed, 1, N - 1, ExpectedValues); UncompressAndValidate(Compressed, N - 1, 1, ExpectedValues); UncompressAndValidate(Compressed, 0, 1, ExpectedValues); UncompressAndValidate(Compressed, 2, 4, ExpectedValues); UncompressAndValidate(Compressed, 0, 512, ExpectedValues); UncompressAndValidate(Compressed, 3, 514, ExpectedValues); UncompressAndValidate(Compressed, 256, 512, ExpectedValues); UncompressAndValidate(Compressed, 512, 512, ExpectedValues); } SUBCASE("decompress with offset only") { const uint64_t BlockSize = 64 * sizeof(uint64_t); const uint64_t N = 1000; std::vector ExpectedValues = GenerateData(N); CompressedBuffer Compressed = CompressedBuffer::Compress(SharedBuffer::MakeView(MakeMemoryView(ExpectedValues)), OodleCompressor::Mermaid, OodleCompressionLevel::Optimal4, BlockSize); const uint64_t OffsetCount = 150; SharedBuffer Uncompressed = Compressed.Decompress(OffsetCount * sizeof(uint64_t)); std::span Values((const uint64_t*)Uncompressed.GetData(), Uncompressed.GetSize() / sizeof(uint64_t)); ValidateData(Values, ExpectedValues, OffsetCount); } SUBCASE("decompress buffer with one block") { const uint64_t BlockSize = 256 * sizeof(uint64_t); const uint64_t N = 100; std::vector ExpectedValues = GenerateData(N); CompressedBuffer Compressed = CompressedBuffer::Compress(SharedBuffer::MakeView(MakeMemoryView(ExpectedValues)), OodleCompressor::Mermaid, OodleCompressionLevel::Optimal4, BlockSize); const uint64_t OffsetCount = 2; const uint64_t Count = 50; SharedBuffer Uncompressed = Compressed.Decompress(OffsetCount * sizeof(uint64_t), Count * sizeof(uint64_t)); std::span Values((const uint64_t*)Uncompressed.GetData(), Uncompressed.GetSize() / sizeof(uint64_t)); ValidateData(Values, ExpectedValues, OffsetCount); } SUBCASE("decompress uncompressed buffer") { const uint64_t N = 4242; std::vector ExpectedValues = GenerateData(N); CompressedBuffer Compressed = CompressedBuffer::Compress(SharedBuffer::MakeView(MakeMemoryView(ExpectedValues)), OodleCompressor::NotSet, OodleCompressionLevel::None); { const uint64_t OffsetCount = 0; const uint64_t Count = N; SharedBuffer Uncompressed = Compressed.Decompress(OffsetCount * sizeof(uint64_t), Count * sizeof(uint64_t)); std::span Values((const uint64_t*)Uncompressed.GetData(), Uncompressed.GetSize() / sizeof(uint64_t)); ValidateData(Values, ExpectedValues, OffsetCount); } { const uint64_t OffsetCount = 21; const uint64_t Count = 999; SharedBuffer Uncompressed = Compressed.Decompress(OffsetCount * sizeof(uint64_t), Count * sizeof(uint64_t)); std::span Values((const uint64_t*)Uncompressed.GetData(), Uncompressed.GetSize() / sizeof(uint64_t)); ValidateData(Values, ExpectedValues, OffsetCount); } } SUBCASE("copy range") { const uint64_t BlockSize = 64 * sizeof(uint64_t); const uint64_t N = 1000; std::vector ExpectedValues = GenerateData(N); CompressedBuffer Compressed = CompressedBuffer::Compress(SharedBuffer::MakeView(MakeMemoryView(ExpectedValues)), OodleCompressor::Mermaid, OodleCompressionLevel::Optimal4, BlockSize); { const uint64_t OffsetCount = 0; const uint64_t Count = N; SharedBuffer Uncompressed = Compressed.CopyRange(OffsetCount * sizeof(uint64_t), Count * sizeof(uint64_t)).Decompress(); std::span Values((const uint64_t*)Uncompressed.GetData(), Uncompressed.GetSize() / sizeof(uint64_t)); CHECK(Values.size() == Count); ValidateData(Values, ExpectedValues, OffsetCount); } { const uint64_t OffsetCount = 64; const uint64_t Count = N - 64; SharedBuffer Uncompressed = Compressed.CopyRange(OffsetCount * sizeof(uint64_t), Count * sizeof(uint64_t)).Decompress(); std::span Values((const uint64_t*)Uncompressed.GetData(), Uncompressed.GetSize() / sizeof(uint64_t)); CHECK(Values.size() == Count); ValidateData(Values, ExpectedValues, OffsetCount); } { const uint64_t OffsetCount = 64 * 2 + 32; const uint64_t Count = N - OffsetCount; const uint64_t RawOffset = OffsetCount * sizeof(uint64_t); const uint64_t RawSize = Count * sizeof(uint64_t); uint64_t FirstBlockOffset = RawOffset % BlockSize; SharedBuffer Uncompressed = Compressed.CopyRange(RawOffset, RawSize).Decompress(); std::span AllValues((const uint64_t*)Uncompressed.GetData(), RawSize / sizeof(uint64_t)); std::span Values((const uint64_t*)(((const uint8_t*)(Uncompressed.GetData()) + FirstBlockOffset)), RawSize / sizeof(uint64_t)); CHECK(Values.size() == Count); ValidateData(Values, ExpectedValues, OffsetCount); } { const uint64_t OffsetCount = 64 * 2 + 63; const uint64_t Count = N - OffsetCount - 5; const uint64_t RawOffset = OffsetCount * sizeof(uint64_t); const uint64_t RawSize = Count * sizeof(uint64_t); uint64_t FirstBlockOffset = RawOffset % BlockSize; SharedBuffer Uncompressed = Compressed.CopyRange(RawOffset, RawSize).Decompress(); std::span AllValues((const uint64_t*)Uncompressed.GetData(), RawSize / sizeof(uint64_t)); std::span Values((const uint64_t*)(((const uint8_t*)(Uncompressed.GetData()) + FirstBlockOffset)), RawSize / sizeof(uint64_t)); CHECK(Values.size() == Count); ValidateData(Values, ExpectedValues, OffsetCount); } } SUBCASE("get range") { const uint64_t BlockSize = 64 * sizeof(uint64_t); const uint64_t N = 1000; std::vector ExpectedValues = GenerateData(N); CompressedBuffer Compressed = CompressedBuffer::Compress(SharedBuffer::MakeView(MakeMemoryView(ExpectedValues)), OodleCompressor::Mermaid, OodleCompressionLevel::Optimal4, BlockSize); { const uint64_t OffsetCount = 0; const uint64_t Count = N; SharedBuffer Uncompressed = Compressed.GetRange(OffsetCount * sizeof(uint64_t), Count * sizeof(uint64_t)).Decompress(); std::span Values((const uint64_t*)Uncompressed.GetData(), Uncompressed.GetSize() / sizeof(uint64_t)); CHECK(Values.size() == Count); ValidateData(Values, ExpectedValues, OffsetCount); } { const uint64_t OffsetCount = 64; const uint64_t Count = N - 64; SharedBuffer Uncompressed = Compressed.GetRange(OffsetCount * sizeof(uint64_t), Count * sizeof(uint64_t)).Decompress(); std::span Values((const uint64_t*)Uncompressed.GetData(), Uncompressed.GetSize() / sizeof(uint64_t)); CHECK(Values.size() == Count); ValidateData(Values, ExpectedValues, OffsetCount); } { const uint64_t OffsetCount = 64 * 2 + 32; const uint64_t Count = N - OffsetCount; const uint64_t RawOffset = OffsetCount * sizeof(uint64_t); const uint64_t RawSize = Count * sizeof(uint64_t); uint64_t FirstBlockOffset = RawOffset % BlockSize; SharedBuffer Uncompressed = Compressed.GetRange(RawOffset, RawSize).Decompress(); std::span AllValues((const uint64_t*)Uncompressed.GetData(), RawSize / sizeof(uint64_t)); std::span Values((const uint64_t*)(((const uint8_t*)(Uncompressed.GetData()) + FirstBlockOffset)), RawSize / sizeof(uint64_t)); CHECK(Values.size() == Count); ValidateData(Values, ExpectedValues, OffsetCount); } { const uint64_t OffsetCount = 64 * 2 + 63; const uint64_t Count = N - OffsetCount - 5; const uint64_t RawOffset = OffsetCount * sizeof(uint64_t); const uint64_t RawSize = Count * sizeof(uint64_t); uint64_t FirstBlockOffset = RawOffset % BlockSize; SharedBuffer Uncompressed = Compressed.GetRange(RawOffset, RawSize).Decompress(); std::span AllValues((const uint64_t*)Uncompressed.GetData(), RawSize / sizeof(uint64_t)); std::span Values((const uint64_t*)(((const uint8_t*)(Uncompressed.GetData()) + FirstBlockOffset)), RawSize / sizeof(uint64_t)); CHECK(Values.size() == Count); ValidateData(Values, ExpectedValues, OffsetCount); } } SUBCASE("copy uncompressed range") { const uint64_t N = 1000; std::vector ExpectedValues = GenerateData(N); CompressedBuffer Compressed = CompressedBuffer::Compress(SharedBuffer::MakeView(MakeMemoryView(ExpectedValues)), OodleCompressor::NotSet, OodleCompressionLevel::None); { const uint64_t OffsetCount = 0; const uint64_t Count = N; SharedBuffer Uncompressed = Compressed.CopyRange(OffsetCount * sizeof(uint64_t), Count * sizeof(uint64_t)).Decompress(); std::span Values((const uint64_t*)Uncompressed.GetData(), Uncompressed.GetSize() / sizeof(uint64_t)); CHECK(Values.size() == Count); ValidateData(Values, ExpectedValues, OffsetCount); } { const uint64_t OffsetCount = 1; const uint64_t Count = N - OffsetCount; SharedBuffer Uncompressed = Compressed.CopyRange(OffsetCount * sizeof(uint64_t), Count * sizeof(uint64_t)).Decompress(); std::span Values((const uint64_t*)Uncompressed.GetData(), Uncompressed.GetSize() / sizeof(uint64_t)); CHECK(Values.size() == Count); ValidateData(Values, ExpectedValues, OffsetCount); } { const uint64_t OffsetCount = 42; const uint64_t Count = 100; SharedBuffer Uncompressed = Compressed.CopyRange(OffsetCount * sizeof(uint64_t), Count * sizeof(uint64_t)).Decompress(); std::span Values((const uint64_t*)Uncompressed.GetData(), Uncompressed.GetSize() / sizeof(uint64_t)); CHECK(Values.size() == Count); ValidateData(Values, ExpectedValues, OffsetCount); } } SUBCASE("get uncompressed range") { const uint64_t N = 1000; std::vector ExpectedValues = GenerateData(N); CompressedBuffer Compressed = CompressedBuffer::Compress(SharedBuffer::MakeView(MakeMemoryView(ExpectedValues)), OodleCompressor::NotSet, OodleCompressionLevel::None); { const uint64_t OffsetCount = 0; const uint64_t Count = N; SharedBuffer Uncompressed = Compressed.GetRange(OffsetCount * sizeof(uint64_t), Count * sizeof(uint64_t)).Decompress(); std::span Values((const uint64_t*)Uncompressed.GetData(), Uncompressed.GetSize() / sizeof(uint64_t)); CHECK(Values.size() == Count); ValidateData(Values, ExpectedValues, OffsetCount); } { const uint64_t OffsetCount = 1; const uint64_t Count = N - OffsetCount; SharedBuffer Uncompressed = Compressed.GetRange(OffsetCount * sizeof(uint64_t), Count * sizeof(uint64_t)).Decompress(); std::span Values((const uint64_t*)Uncompressed.GetData(), Uncompressed.GetSize() / sizeof(uint64_t)); CHECK(Values.size() == Count); ValidateData(Values, ExpectedValues, OffsetCount); } { const uint64_t OffsetCount = 42; const uint64_t Count = 100; SharedBuffer Uncompressed = Compressed.GetRange(OffsetCount * sizeof(uint64_t), Count * sizeof(uint64_t)).Decompress(); std::span Values((const uint64_t*)Uncompressed.GetData(), Uncompressed.GetSize() / sizeof(uint64_t)); CHECK(Values.size() == Count); ValidateData(Values, ExpectedValues, OffsetCount); } } } TEST_CASE("CompressedBufferReader") { const auto GenerateData = [](size_t N) -> std::vector { std::vector Data; Data.resize(N); for (size_t Idx = 0; Idx < Data.size(); ++Idx) { Data[Idx] = uint64_t(Idx); } return Data; }; CompressedBufferReader Reader; SUBCASE("Decompress with offset and size") { const auto UncompressAndValidate = [&](const CompressedBuffer& Compressed, const int32_t OffsetCount, const int32_t Count, const std::vector& ExpectedValues) { Reader.SetSource(Compressed); { const SharedBuffer Uncompressed = Reader.Decompress(OffsetCount * sizeof(uint64_t), Count * sizeof(uint64_t)); CHECK(Uncompressed.GetView().EqualBytes(MakeMemoryView(std::span{ExpectedValues}.subspan(OffsetCount, Count)))); } { UniqueBuffer Uncompressed = UniqueBuffer::Alloc(Count * sizeof(uint64_t)); if (Reader.TryDecompressTo(Uncompressed, OffsetCount * sizeof(uint64_t))) { CHECK(Uncompressed.GetView().EqualBytes(MakeMemoryView(std::span{ExpectedValues}.subspan(OffsetCount, Count)))); } } }; constexpr uint64_t BlockSize = 64 * sizeof(uint64_t); constexpr int32_t N = 5000; const std::vector ExpectedValues = GenerateData(N); const CompressedBuffer Compressed = CompressedBuffer::Compress(SharedBuffer::MakeView(MakeMemoryView(ExpectedValues)), OodleCompressor::Mermaid, OodleCompressionLevel::Fast, BlockSize); UncompressAndValidate(Compressed, 0, N, ExpectedValues); UncompressAndValidate(Compressed, 1, N - 1, ExpectedValues); UncompressAndValidate(Compressed, N - 1, 1, ExpectedValues); UncompressAndValidate(Compressed, 0, 1, ExpectedValues); UncompressAndValidate(Compressed, 2, 4, ExpectedValues); UncompressAndValidate(Compressed, 0, 512, ExpectedValues); UncompressAndValidate(Compressed, 3, 514, ExpectedValues); UncompressAndValidate(Compressed, 256, 512, ExpectedValues); UncompressAndValidate(Compressed, 512, 512, ExpectedValues); UncompressAndValidate(Compressed, 512, 512, ExpectedValues); UncompressAndValidate(Compressed, 4993, 4, ExpectedValues); } SUBCASE("Decompress with offset only") { constexpr uint64_t BlockSize = 64 * sizeof(uint64_t); constexpr int32_t N = 1000; const std::vector ExpectedValues = GenerateData(N); const CompressedBuffer Compressed = CompressedBuffer::Compress(SharedBuffer::MakeView(MakeMemoryView(ExpectedValues)), OodleCompressor::Mermaid, OodleCompressionLevel::Fast, BlockSize); constexpr uint64_t OffsetCount = 150; { SharedBuffer Buffer = Compressed.GetCompressed().ToShared(); BufferReader Ar(Buffer.GetData(), int64_t(Buffer.GetSize())); CompressedBufferReaderSourceScope Source(Reader, Ar); const SharedBuffer Uncompressed = Reader.Decompress(OffsetCount * sizeof(uint64_t)); CHECK(Uncompressed.GetView().EqualBytes(MakeMemoryView(std::span{ExpectedValues}.subspan(OffsetCount)))); } { CompressedBufferReaderSourceScope Source(Reader, Compressed); const SharedBuffer Uncompressed = Reader.Decompress(OffsetCount * sizeof(uint64_t)); CHECK(Uncompressed.GetView().EqualBytes(MakeMemoryView(std::span{ExpectedValues}.subspan(OffsetCount)))); } // Short (malformed) Buffer { IoHash RawHash; uint64_t RawSize = 0; const CompressedBuffer CompressedShort = CompressedBuffer::FromCompressed(Compressed.GetCompressed().Mid(0, Compressed.GetCompressedSize() - 128), /* out */ RawHash, /* out */ RawSize); Reader.SetSource(CompressedShort); const SharedBuffer Uncompressed = Reader.Decompress(); CHECK(Uncompressed.IsNull()); } } SUBCASE("Decompress only block") { constexpr uint64_t BlockSize = 256 * sizeof(uint64_t); constexpr int32_t N = 100; const std::vector ExpectedValues = GenerateData(N); const CompressedBuffer Compressed = CompressedBuffer::Compress(SharedBuffer::MakeView(MakeMemoryView(ExpectedValues)), OodleCompressor::Mermaid, OodleCompressionLevel::Fast, BlockSize); constexpr uint64_t OffsetCount = 2; constexpr uint64_t Count = 50; { SharedBuffer Buffer = Compressed.GetCompressed().ToShared(); BufferReader Ar(Buffer.GetData(), int64_t(Buffer.GetSize())); CompressedBufferReaderSourceScope Source(Reader, Ar); const SharedBuffer Uncompressed = Reader.Decompress(OffsetCount * sizeof(uint64_t), Count * sizeof(uint64_t)); CHECK(Uncompressed.GetView().EqualBytes(MakeMemoryView(std::span{ExpectedValues}.subspan(OffsetCount, Count)))); } { CompressedBufferReaderSourceScope Source(Reader, Compressed); const SharedBuffer Uncompressed = Reader.Decompress(OffsetCount * sizeof(uint64_t), Count * sizeof(uint64_t)); CHECK(Uncompressed.GetView().EqualBytes(MakeMemoryView(std::span{ExpectedValues}.subspan(OffsetCount, Count)))); } } SUBCASE("Decompress from an uncompressed buffer.") { constexpr int32_t N = 4242; const std::vector ExpectedValues = GenerateData(N); const CompressedBuffer Compressed = CompressedBuffer::Compress(SharedBuffer::MakeView(MakeMemoryView(ExpectedValues)), OodleCompressor::NotSet, OodleCompressionLevel::None); Reader.SetSource(Compressed); { constexpr uint64_t OffsetCount = 0; constexpr uint64_t Count = N; const SharedBuffer Uncompressed = Reader.Decompress(OffsetCount * sizeof(uint64_t), Count * sizeof(uint64_t)); CHECK(Uncompressed.GetView().EqualBytes(MakeMemoryView(std::span{ExpectedValues}.subspan(OffsetCount, Count)))); } { constexpr uint64_t OffsetCount = 21; constexpr uint64_t Count = 999; const SharedBuffer Uncompressed = Reader.Decompress(OffsetCount * sizeof(uint64_t), Count * sizeof(uint64_t)); CHECK(Uncompressed.GetView().EqualBytes(MakeMemoryView(std::span{ExpectedValues}.subspan(OffsetCount, Count)))); } // Short (malformed) Buffer { IoHash RawHash; uint64_t RawSize = 0; const CompressedBuffer CompressedShort = CompressedBuffer::FromCompressed(Compressed.GetCompressed().Mid(0, Compressed.GetCompressedSize() - 128), /* out */ RawHash, /* out */ RawSize); Reader.SetSource(CompressedShort); const SharedBuffer Uncompressed = Reader.Decompress(); CHECK(Uncompressed.IsNull()); } } } void compress_forcelink() { } #endif } // namespace zen