aboutsummaryrefslogtreecommitdiff
path: root/src/zencore/compress.cpp
diff options
context:
space:
mode:
authorStefan Boberg <[email protected]>2023-05-23 10:13:47 +0200
committerGitHub <[email protected]>2023-05-23 10:13:47 +0200
commit633dee5f8688c104a04f0ec719b756dbbad7142f (patch)
treef0ce192964f6d6b9fd506f4963e261320803e2ac /src/zencore/compress.cpp
parentMemoryView::RightChop -> const (diff)
downloadzen-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.cpp806
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()
{