diff options
| author | Stefan Boberg <[email protected]> | 2023-05-23 10:13:47 +0200 |
|---|---|---|
| committer | GitHub <[email protected]> | 2023-05-23 10:13:47 +0200 |
| commit | 633dee5f8688c104a04f0ec719b756dbbad7142f (patch) | |
| tree | f0ce192964f6d6b9fd506f4963e261320803e2ac /src/zencore/compress.cpp | |
| parent | MemoryView::RightChop -> const (diff) | |
| download | zen-633dee5f8688c104a04f0ec719b756dbbad7142f.tar.xz zen-633dee5f8688c104a04f0ec719b756dbbad7142f.zip | |
streaming decompression support (#142)
Added CompressedBufferReader support from UE. This provides some streaming decompression support which can be employed to reduce memory and other resource usage.
Diffstat (limited to 'src/zencore/compress.cpp')
| -rw-r--r-- | src/zencore/compress.cpp | 806 |
1 files changed, 786 insertions, 20 deletions
diff --git a/src/zencore/compress.cpp b/src/zencore/compress.cpp index 44d6efc36..2362d8e78 100644 --- a/src/zencore/compress.cpp +++ b/src/zencore/compress.cpp @@ -6,7 +6,9 @@ #include <zencore/compositebuffer.h> #include <zencore/crc32.h> #include <zencore/endian.h> +#include <zencore/intmath.h> #include <zencore/iohash.h> +#include <zencore/stream.h> #include <zencore/testing.h> #include <oodle2.h> @@ -19,7 +21,8 @@ namespace zen::detail { /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -static constexpr uint64_t DefaultBlockSize = 256 * 1024; +static constexpr uint64_t DefaultBlockSize = 256 * 1024; +static constexpr uint64_t DefaultHeaderSize = 4 * 1024; /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// @@ -115,27 +118,72 @@ struct BufferHeader } 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: - virtual CompositeBuffer Compress(const CompositeBuffer& RawData, uint64_t BlockSize = DefaultBlockSize) const = 0; + [[nodiscard]] virtual CompositeBuffer Compress(const CompositeBuffer& RawData, uint64_t BlockSize = DefaultBlockSize) const = 0; }; class BaseDecoder { public: - virtual CompositeBuffer Decompress(const BufferHeader& Header, const CompositeBuffer& CompressedData) const = 0; - virtual bool TryDecompressTo(const BufferHeader& Header, - const CompositeBuffer& CompressedData, - MutableMemoryView RawView, - uint64_t RawOffset) const = 0; - virtual uint64_t GetHeaderSize(const BufferHeader& Header) const = 0; + [[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; }; /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// @@ -161,7 +209,7 @@ public: class NoneDecoder final : public BaseDecoder { public: - [[nodiscard]] CompositeBuffer Decompress(const BufferHeader& Header, const CompositeBuffer& CompressedData) const final + [[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)) @@ -171,6 +219,23 @@ public: 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, @@ -186,6 +251,24 @@ public: 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); } }; @@ -305,22 +388,38 @@ BlockEncoder::Compress(const CompositeBuffer& RawData, const uint64_t BlockSize) class BlockDecoder : public BaseDecoder { public: - CompositeBuffer Decompress(const BufferHeader& Header, const CompositeBuffer& CompressedData) const final; - [[nodiscard]] bool TryDecompressTo(const BufferHeader& Header, - const CompositeBuffer& CompressedData, - MutableMemoryView RawView, - uint64_t RawOffset) const final; [[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; + protected: virtual bool DecompressBlock(MutableMemoryView RawData, MemoryView CompressedData) const = 0; }; CompositeBuffer -BlockDecoder::Decompress(const BufferHeader& Header, const CompositeBuffer& CompressedData) const +BlockDecoder::DecompressToComposite(const BufferHeader& Header, const CompositeBuffer& CompressedData) const { if (Header.BlockCount == 0 || Header.TotalCompressedSize != CompressedData.GetSize()) { @@ -519,6 +618,109 @@ BlockDecoder::TryDecompressTo(const BufferHeader& Header, 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<const uint32_t*>(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 +{ + UniqueBuffer Buffer = UniqueBuffer::Alloc(RawSize); + if (TryDecompressTo(Context, Source, Header, HeaderView, RawOffset, Buffer)) + { + return CompositeBuffer(Buffer.MoveToShared()); + } + return CompositeBuffer(); +} + ////////////////////////////////////////////////////////////////////////// struct OodleInit @@ -671,14 +873,15 @@ GetDecoder(CompressionMethod Method) bool BufferHeader::IsValid(const CompositeBuffer& CompressedData, IoHash& OutRawHash, uint64_t& OutRawSize) { - uint64_t Size = CompressedData.GetSize(); - if (Size < sizeof(BufferHeader)) + const uint64_t CompressedDataSize = CompressedData.GetSize(); + if (CompressedDataSize < sizeof(BufferHeader)) { return false; } + const size_t StackBufferSize = 256; uint8_t StackBuffer[StackBufferSize]; - uint64_t ReadSize = Min(Size, StackBufferSize); + uint64_t ReadSize = Min(CompressedDataSize, StackBufferSize); BufferHeader* Header = reinterpret_cast<BufferHeader*>(StackBuffer); { CompositeBuffer::Iterator It; @@ -689,20 +892,27 @@ BufferHeader::IsValid(const CompositeBuffer& CompressedData, IoHash& OutRawHash, { return false; } + const BaseDecoder* const Decoder = GetDecoder(Header->Method); if (!Decoder) { return false; } + uint32_t Crc32 = Header->Crc32; OutRawHash = IoHash::FromBLAKE3(Header->RawHash); OutRawSize = Header->TotalRawSize; uint64_t HeaderSize = Decoder->GetHeaderSize(*Header); + + if (Header->TotalCompressedSize > CompressedDataSize) + { + return false; + } + Header->ByteSwap(); if (HeaderSize > ReadSize) { - // 0.004% of cases on a Fortnite hot cache cook UniqueBuffer HeaderCopy = UniqueBuffer::Alloc(HeaderSize); CompositeBuffer::Iterator It; CompressedData.CopyTo(HeaderCopy.GetMutableView(), It); @@ -720,9 +930,170 @@ BufferHeader::IsValid(const CompositeBuffer& CompressedData, IoHash& OutRawHash, return false; } } + return true; } +/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +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<FHeader*>(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<typename BufferType> @@ -1007,7 +1378,7 @@ CompressedBuffer::DecompressToComposite() const { if (const BaseDecoder* const Decoder = GetDecoder(Header.Method)) { - return Decoder->Decompress(Header, CompressedData); + return Decoder->DecompressToComposite(Header, CompressedData); } } } @@ -1041,6 +1412,240 @@ CompressedBuffer::TryGetCompressParameters(OodleCompressor& OutCompressor, 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<const DecoderSource&>(ArchiveDecoderSource(*SourceArchive, Context.HeaderOffset)) + : static_cast<const DecoderSource&>(BufferDecoderSource(SourceBuffer->GetCompressed())), + Header, + HeaderView, + RawOffset, + RawView)) + { + return true; + } + } + } + } + return false; +} + +SharedBuffer +CompressedBufferReader::Decompress(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)) + { + UniqueBuffer RawData = UniqueBuffer::Alloc(RawSizeToCopy); + if (Decoder->TryDecompressTo( + Context, + SourceArchive ? static_cast<const DecoderSource&>(ArchiveDecoderSource(*SourceArchive, Context.HeaderOffset)) + : static_cast<const DecoderSource&>(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<const DecoderSource&>(ArchiveDecoderSource(*SourceArchive, Context.HeaderOffset)) + : static_cast<const DecoderSource&>(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; +}; + /** ______________________ _____________________________ \__ ___/\_ _____// _____/\__ ___/ _____/ @@ -1341,6 +1946,167 @@ TEST_CASE("CompressedBuffer") } } +TEST_CASE("CompressedBufferReader") +{ + const auto GenerateData = [](size_t N) -> std::vector<uint64_t> { + std::vector<uint64_t> 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<uint64_t>& 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<uint64_t> 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<uint64_t> 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<uint64_t> 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<uint64_t> 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() { |