From 786e654dfdbd478678fa8e1a1e671f6c7910f5ea Mon Sep 17 00:00:00 2001 From: Tim Schumacher Date: Sun, 22 Oct 2023 19:49:41 +0200 Subject: [PATCH] LibCompress: Implement the XZ delta filter --- Tests/LibCompress/TestXz.cpp | 8 +-- Userland/Libraries/LibCompress/Xz.cpp | 71 +++++++++++++++++++++++++++ Userland/Libraries/LibCompress/Xz.h | 26 ++++++++++ 3 files changed, 102 insertions(+), 3 deletions(-) diff --git a/Tests/LibCompress/TestXz.cpp b/Tests/LibCompress/TestXz.cpp index 4f4ca826a4..7a70be3507 100644 --- a/Tests/LibCompress/TestXz.cpp +++ b/Tests/LibCompress/TestXz.cpp @@ -1183,8 +1183,8 @@ TEST_CASE(xz_utils_good_1_3delta_lzma2) auto stream = MUST(try_make(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(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()); } diff --git a/Userland/Libraries/LibCompress/Xz.cpp b/Userland/Libraries/LibCompress/Xz.cpp index 2615de91e1..f928d1ad81 100644 --- a/Userland/Libraries/LibCompress/Xz.cpp +++ b/Userland/Libraries/LibCompress/Xz.cpp @@ -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> XzFilterDelta::create(MaybeOwned 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, CircularBuffer buffer) + : m_stream(move(stream)) + , m_buffer(move(buffer)) +{ +} + +ErrorOr 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 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> XzDecompressor::create(MaybeOwned stream) { auto counting_stream = TRY(try_make(move(stream))); @@ -353,6 +413,17 @@ ErrorOr 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(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"); } diff --git a/Userland/Libraries/LibCompress/Xz.h b/Userland/Libraries/LibCompress/Xz.h index 1d07fa956b..8a054eb0b4 100644 --- a/Userland/Libraries/LibCompress/Xz.h +++ b/Userland/Libraries/LibCompress/Xz.h @@ -6,6 +6,7 @@ #pragma once +#include #include #include #include @@ -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> create(MaybeOwned, u32 distance); + + virtual ErrorOr read_some(Bytes) override; + virtual ErrorOr write_some(ReadonlyBytes) override; + virtual bool is_eof() const override; + virtual bool is_open() const override; + virtual void close() override; + +private: + XzFilterDelta(MaybeOwned, CircularBuffer); + + MaybeOwned m_stream; + CircularBuffer m_buffer; +}; + class XzDecompressor : public Stream { public: static ErrorOr> create(MaybeOwned);