LibCompress: Implement the XZ delta filter

This commit is contained in:
Tim Schumacher 2023-10-22 19:49:41 +02:00 committed by Jelle Raaijmakers
parent f0b08e9dea
commit 786e654dfd
Notes: sideshowbarker 2024-07-17 03:14:39 +09:00
3 changed files with 102 additions and 3 deletions

View File

@ -1183,8 +1183,8 @@ TEST_CASE(xz_utils_good_1_3delta_lzma2)
auto stream = MUST(try_make<FixedMemoryStream>(compressed));
auto decompressor = MUST(Compress::XzDecompressor::create(move(stream)));
// TODO: This uses the currently unimplemented delta filter.
(void)decompressor->read_until_eof(PAGE_SIZE);
auto buffer = TRY_OR_FAIL(decompressor->read_until_eof(PAGE_SIZE));
EXPECT_EQ(buffer.span(), xz_utils_lorem_ipsum.bytes());
}
TEST_CASE(xz_utils_good_1_arm64_lzma2_1)
@ -1864,7 +1864,9 @@ TEST_CASE(xz_utils_unsupported_filter_flags_2)
auto stream = MUST(try_make<FixedMemoryStream>(compressed));
auto decompressor = MUST(Compress::XzDecompressor::create(move(stream)));
// TODO: The delta filter is currently unimplemented. However, once it is implemented, we likely want to mirror the recommended behavior of the specification anyways.
// TODO: We don't yet check whether the filter chain satisfies the "can't be the last filter"
// requirement. We just happen to get the result right because we try to uncompress the
// test case and fail.
auto buffer_or_error = decompressor->read_until_eof(PAGE_SIZE);
EXPECT(buffer_or_error.is_error());
}

View File

@ -161,6 +161,66 @@ u32 XzFilterLzma2Properties::dictionary_size() const
return dictionary_size;
}
u32 XzFilterDeltaProperties::distance() const
{
// "The Properties byte indicates the delta distance, which can be
// 1-256 bytes backwards from the current byte: 0x00 indicates
// distance of 1 byte and 0xFF distance of 256 bytes."
return encoded_distance + 1;
}
ErrorOr<NonnullOwnPtr<XzFilterDelta>> XzFilterDelta::create(MaybeOwned<Stream> stream, u32 distance)
{
auto buffer = TRY(CircularBuffer::create_empty(distance));
auto filter = TRY(adopt_nonnull_own_or_enomem(new (nothrow) XzFilterDelta(move(stream), move(buffer))));
return filter;
}
XzFilterDelta::XzFilterDelta(MaybeOwned<Stream> stream, CircularBuffer buffer)
: m_stream(move(stream))
, m_buffer(move(buffer))
{
}
ErrorOr<Bytes> XzFilterDelta::read_some(Bytes bytes)
{
bytes = TRY(m_stream->read_some(bytes));
auto distance = m_buffer.capacity();
for (auto& byte : bytes) {
if (m_buffer.seekback_limit() >= distance) {
u8 byte_at_distance { 0 };
MUST(m_buffer.read_with_seekback({ &byte_at_distance, 1 }, distance));
byte = byte_at_distance + byte;
}
m_buffer.write({ &byte, 1 });
MUST(m_buffer.discard(1));
}
return bytes;
}
ErrorOr<size_t> XzFilterDelta::write_some(ReadonlyBytes)
{
return EBADF;
}
bool XzFilterDelta::is_eof() const
{
return m_stream->is_eof();
}
bool XzFilterDelta::is_open() const
{
return m_stream->is_open();
}
void XzFilterDelta::close()
{
}
ErrorOr<NonnullOwnPtr<XzDecompressor>> XzDecompressor::create(MaybeOwned<Stream> stream)
{
auto counting_stream = TRY(try_make<CountingStream>(move(stream)));
@ -353,6 +413,17 @@ ErrorOr<void> XzDecompressor::load_next_block(u8 encoded_block_header_size)
continue;
}
// 5.3.3. Delta
if (filter.id == 0x03) {
if (filter.properties.size() < sizeof(XzFilterDeltaProperties))
return Error::from_string_literal("XZ Delta filter has a smaller-than-needed properties size");
auto const* properties = reinterpret_cast<XzFilterDeltaProperties*>(filter.properties.data());
new_block_stream = TRY(XzFilterDelta::create(move(new_block_stream), properties->distance()));
continue;
}
return Error::from_string_literal("XZ block header contains unknown filter ID");
}

View File

@ -6,6 +6,7 @@
#pragma once
#include <AK/CircularBuffer.h>
#include <AK/ConstrainedStream.h>
#include <AK/CountingStream.h>
#include <AK/Endian.h>
@ -98,6 +99,31 @@ struct [[gnu::packed]] XzFilterLzma2Properties {
};
static_assert(sizeof(XzFilterLzma2Properties) == 1);
// 5.3.3. Delta
struct [[gnu::packed]] XzFilterDeltaProperties {
u8 encoded_distance;
u32 distance() const;
};
static_assert(sizeof(XzFilterDeltaProperties) == 1);
class XzFilterDelta : public Stream {
public:
static ErrorOr<NonnullOwnPtr<XzFilterDelta>> create(MaybeOwned<Stream>, u32 distance);
virtual ErrorOr<Bytes> read_some(Bytes) override;
virtual ErrorOr<size_t> write_some(ReadonlyBytes) override;
virtual bool is_eof() const override;
virtual bool is_open() const override;
virtual void close() override;
private:
XzFilterDelta(MaybeOwned<Stream>, CircularBuffer);
MaybeOwned<Stream> m_stream;
CircularBuffer m_buffer;
};
class XzDecompressor : public Stream {
public:
static ErrorOr<NonnullOwnPtr<XzDecompressor>> create(MaybeOwned<Stream>);