From 1c2b373e9ccc40782dcfa3c86f9336d00aeeef59 Mon Sep 17 00:00:00 2001 From: devgianlu Date: Sat, 1 Mar 2025 17:41:22 +0100 Subject: [PATCH] LibCompress: Refactor deflate de/compressor using zlib Also remove two crash tests that are not relevant anymore because the implementation changed substantially. --- Libraries/LibCompress/Deflate.cpp | 815 +----------------- Libraries/LibCompress/Deflate.h | 179 +--- .../ImageFormats/WebPWriterLossless.cpp | 1 + .../LibWeb/Compression/CompressionStream.cpp | 15 +- .../Compression/DecompressionStream.cpp | 2 +- Tests/LibCompress/TestDeflate.cpp | 61 +- ...zzDeflateCompression-6163230961303552.fuzz | Bin 29687 -> 0 bytes ...DeflateDecompression-5523852259360768.fuzz | 1 - 8 files changed, 56 insertions(+), 1018 deletions(-) delete mode 100644 Tests/LibCompress/deflate-test-files/clusterfuzz-testcase-minimized-FuzzDeflateCompression-6163230961303552.fuzz delete mode 100644 Tests/LibCompress/deflate-test-files/clusterfuzz-testcase-minimized-FuzzDeflateDecompression-5523852259360768.fuzz diff --git a/Libraries/LibCompress/Deflate.cpp b/Libraries/LibCompress/Deflate.cpp index c229c58ce9..dece3cc77b 100644 --- a/Libraries/LibCompress/Deflate.cpp +++ b/Libraries/LibCompress/Deflate.cpp @@ -1,25 +1,19 @@ /* * Copyright (c) 2020, the SerenityOS developers. * Copyright (c) 2021, Idan Horowitz + * Copyright (c) 2025, Altomani Gianluca * * SPDX-License-Identifier: BSD-2-Clause */ -#include -#include #include -#include #include -#include +#include + +#include namespace Compress { -static constexpr u8 deflate_special_code_length_copy = 16; -static constexpr u8 deflate_special_code_length_zeros = 17; -static constexpr u8 deflate_special_code_length_long_zeros = 18; - -static constexpr int EndOfBlock = 256; - CanonicalCode const& CanonicalCode::fixed_literal_codes() { static CanonicalCode code; @@ -165,807 +159,28 @@ ErrorOr CanonicalCode::read_symbol(LittleEndianInputBitStream& stream) cons return Error::from_string_literal("Symbol exceeds maximum symbol number"); } -DeflateDecompressor::CompressedBlock::CompressedBlock(DeflateDecompressor& decompressor, CanonicalCode literal_codes, Optional distance_codes) - : m_decompressor(decompressor) - , m_literal_codes(literal_codes) - , m_distance_codes(distance_codes) -{ -} - -ErrorOr DeflateDecompressor::CompressedBlock::try_read_more() -{ - if (m_eof == true) - return false; - - auto const symbol = TRY(m_literal_codes.read_symbol(*m_decompressor.m_input_stream)); - - if (symbol >= 286) - return Error::from_string_literal("Invalid deflate literal/length symbol"); - - if (symbol < EndOfBlock) { - u8 byte_symbol = symbol; - m_decompressor.m_output_buffer.write({ &byte_symbol, sizeof(byte_symbol) }); - return true; - } - - if (symbol == EndOfBlock) { - m_eof = true; - return false; - } - - if (!m_distance_codes.has_value()) - return Error::from_string_literal("Distance codes have not been initialized"); - - auto const length = TRY(m_decompressor.decode_length(symbol)); - auto const distance_symbol = TRY(m_distance_codes.value().read_symbol(*m_decompressor.m_input_stream)); - if (distance_symbol >= 30) - return Error::from_string_literal("Invalid deflate distance symbol"); - - auto const distance = TRY(m_decompressor.decode_distance(distance_symbol)); - - auto copied_length = TRY(m_decompressor.m_output_buffer.copy_from_seekback(distance, length)); - - // TODO: What should we do if the output buffer is full? - VERIFY(copied_length == length); - - return true; -} - -DeflateDecompressor::UncompressedBlock::UncompressedBlock(DeflateDecompressor& decompressor, size_t length) - : m_decompressor(decompressor) - , m_bytes_remaining(length) -{ -} - -ErrorOr DeflateDecompressor::UncompressedBlock::try_read_more() -{ - if (m_bytes_remaining == 0) - return false; - - if (m_decompressor.m_input_stream->is_eof()) - return Error::from_string_literal("Input data ends in the middle of an uncompressed DEFLATE block"); - - Array temporary_buffer; - auto readable_bytes = temporary_buffer.span().trim(min(m_bytes_remaining, m_decompressor.m_output_buffer.empty_space())); - auto read_bytes = TRY(m_decompressor.m_input_stream->read_some(readable_bytes)); - auto written_bytes = m_decompressor.m_output_buffer.write(read_bytes); - VERIFY(read_bytes.size() == written_bytes); - - m_bytes_remaining -= written_bytes; - return true; -} - -ErrorOr> DeflateDecompressor::construct(MaybeOwned stream) -{ - auto output_buffer = TRY(CircularBuffer::create_empty(32 * KiB)); - return TRY(adopt_nonnull_own_or_enomem(new (nothrow) DeflateDecompressor(move(stream), move(output_buffer)))); -} - -DeflateDecompressor::DeflateDecompressor(MaybeOwned stream, CircularBuffer output_buffer) - : m_input_stream(move(stream)) - , m_output_buffer(move(output_buffer)) -{ -} - -DeflateDecompressor::~DeflateDecompressor() -{ - if (m_state == State::ReadingCompressedBlock) - m_compressed_block.~CompressedBlock(); - if (m_state == State::ReadingUncompressedBlock) - m_uncompressed_block.~UncompressedBlock(); -} - -ErrorOr DeflateDecompressor::read_some(Bytes bytes) -{ - size_t total_read = 0; - while (total_read < bytes.size()) { - auto slice = bytes.slice(total_read); - - if (m_state == State::Idle) { - if (m_read_final_block) - break; - - m_read_final_block = TRY(m_input_stream->read_bit()); - auto const block_type = TRY(m_input_stream->read_bits(2)); - - if (block_type == 0b00) { - m_input_stream->align_to_byte_boundary(); - - u16 length = TRY(m_input_stream->read_value>()); - u16 negated_length = TRY(m_input_stream->read_value>()); - - if ((length ^ 0xffff) != negated_length) - return Error::from_string_literal("Calculated negated length does not equal stored negated length"); - - m_state = State::ReadingUncompressedBlock; - new (&m_uncompressed_block) UncompressedBlock(*this, length); - - continue; - } - - if (block_type == 0b01) { - m_state = State::ReadingCompressedBlock; - new (&m_compressed_block) CompressedBlock(*this, CanonicalCode::fixed_literal_codes(), CanonicalCode::fixed_distance_codes()); - - continue; - } - - if (block_type == 0b10) { - CanonicalCode literal_codes; - Optional distance_codes; - TRY(decode_codes(literal_codes, distance_codes)); - - m_state = State::ReadingCompressedBlock; - new (&m_compressed_block) CompressedBlock(*this, literal_codes, distance_codes); - - continue; - } - - return Error::from_string_literal("Unhandled block type for Idle state"); - } - - if (m_state == State::ReadingCompressedBlock) { - auto nread = m_output_buffer.read(slice).size(); - - while (nread < slice.size() && TRY(m_compressed_block.try_read_more())) { - nread += m_output_buffer.read(slice.slice(nread)).size(); - } - - total_read += nread; - if (nread == slice.size()) - break; - - m_compressed_block.~CompressedBlock(); - m_state = State::Idle; - - continue; - } - - if (m_state == State::ReadingUncompressedBlock) { - auto nread = m_output_buffer.read(slice).size(); - - while (nread < slice.size() && TRY(m_uncompressed_block.try_read_more())) { - nread += m_output_buffer.read(slice.slice(nread)).size(); - } - - total_read += nread; - if (nread == slice.size()) - break; - - m_uncompressed_block.~UncompressedBlock(); - m_state = State::Idle; - - continue; - } - - VERIFY_NOT_REACHED(); - } - - return bytes.slice(0, total_read); -} - -bool DeflateDecompressor::is_eof() const { return m_state == State::Idle && m_read_final_block; } - -ErrorOr DeflateDecompressor::write_some(ReadonlyBytes) -{ - return Error::from_errno(EBADF); -} - -bool DeflateDecompressor::is_open() const -{ - return true; -} - -void DeflateDecompressor::close() +ErrorOr> DeflateDecompressor::create(MaybeOwned stream) { + auto buffer = TRY(AK::FixedArray::create(16 * 1024)); + auto zstream = TRY(GenericZlibDecompressor::new_z_stream(-MAX_WBITS)); + return adopt_nonnull_own_or_enomem(new (nothrow) DeflateDecompressor(move(buffer), move(stream), zstream)); } ErrorOr DeflateDecompressor::decompress_all(ReadonlyBytes bytes) { - FixedMemoryStream memory_stream { bytes }; - LittleEndianInputBitStream bit_stream { MaybeOwned(memory_stream) }; - auto deflate_stream = TRY(DeflateDecompressor::construct(MaybeOwned(bit_stream))); - return deflate_stream->read_until_eof(4096); + return ::Compress::decompress_all(bytes); } -ErrorOr DeflateDecompressor::decode_length(u32 symbol) +ErrorOr> DeflateCompressor::create(MaybeOwned stream, GenericZlibCompressionLevel compression_level) { - if (symbol <= 264) - return symbol - 254; - - if (symbol <= 284) { - auto extra_bits = (symbol - 261) / 4; - return (((symbol - 265) % 4 + 4) << extra_bits) + 3 + TRY(m_input_stream->read_bits(extra_bits)); - } - - if (symbol == 285) - return DeflateDecompressor::max_back_reference_length; - - VERIFY_NOT_REACHED(); + auto buffer = TRY(AK::FixedArray::create(16 * 1024)); + auto zstream = TRY(GenericZlibCompressor::new_z_stream(-MAX_WBITS, compression_level)); + return adopt_nonnull_own_or_enomem(new (nothrow) DeflateCompressor(move(buffer), move(stream), zstream)); } -ErrorOr DeflateDecompressor::decode_distance(u32 symbol) +ErrorOr DeflateCompressor::compress_all(ReadonlyBytes bytes, GenericZlibCompressionLevel compression_level) { - if (symbol <= 3) - return symbol + 1; - - if (symbol <= 29) { - auto extra_bits = (symbol / 2) - 1; - return ((symbol % 2 + 2) << extra_bits) + 1 + TRY(m_input_stream->read_bits(extra_bits)); - } - - VERIFY_NOT_REACHED(); -} - -ErrorOr DeflateDecompressor::decode_codes(CanonicalCode& literal_code, Optional& distance_code) -{ - auto literal_code_count = TRY(m_input_stream->read_bits(5)) + 257; - auto distance_code_count = TRY(m_input_stream->read_bits(5)) + 1; - auto code_length_count = TRY(m_input_stream->read_bits(4)) + 4; - - // First we have to extract the code lengths of the code that was used to encode the code lengths of - // the code that was used to encode the block. - - u8 code_lengths_code_lengths[19] = { 0 }; - for (size_t i = 0; i < code_length_count; ++i) { - code_lengths_code_lengths[code_lengths_code_lengths_order[i]] = TRY(m_input_stream->read_bits(3)); - } - - // Now we can extract the code that was used to encode the code lengths of the code that was used to - // encode the block. - auto const code_length_code = TRY(CanonicalCode::from_bytes({ code_lengths_code_lengths, sizeof(code_lengths_code_lengths) })); - - // Next we extract the code lengths of the code that was used to encode the block. - Vector code_lengths; - while (code_lengths.size() < literal_code_count + distance_code_count) { - auto symbol = TRY(code_length_code.read_symbol(*m_input_stream)); - - if (symbol < deflate_special_code_length_copy) { - code_lengths.append(static_cast(symbol)); - } else if (symbol == deflate_special_code_length_copy) { - if (code_lengths.is_empty()) - return Error::from_string_literal("Found no codes to copy before a copy block"); - auto nrepeat = 3 + TRY(m_input_stream->read_bits(2)); - for (size_t j = 0; j < nrepeat; ++j) - code_lengths.append(code_lengths.last()); - } else if (symbol == deflate_special_code_length_zeros) { - auto nrepeat = 3 + TRY(m_input_stream->read_bits(3)); - for (size_t j = 0; j < nrepeat; ++j) - code_lengths.append(0); - } else { - VERIFY(symbol == deflate_special_code_length_long_zeros); - auto nrepeat = 11 + TRY(m_input_stream->read_bits(7)); - for (size_t j = 0; j < nrepeat; ++j) - code_lengths.append(0); - } - } - - if (code_lengths.size() != literal_code_count + distance_code_count) - return Error::from_string_literal("Number of code lengths does not match the sum of codes"); - - // Now we extract the code that was used to encode literals and lengths in the block. - literal_code = TRY(CanonicalCode::from_bytes(code_lengths.span().trim(literal_code_count))); - - // Now we extract the code that was used to encode distances in the block. - - if (distance_code_count == 1) { - auto length = code_lengths[literal_code_count]; - - if (length == 0) - return {}; - else if (length != 1) - return Error::from_string_literal("Length for a single distance code is longer than 1"); - } - - distance_code = TRY(CanonicalCode::from_bytes(code_lengths.span().slice(literal_code_count))); - - return {}; -} - -ErrorOr> DeflateCompressor::construct(MaybeOwned stream, CompressionLevel compression_level) -{ - auto bit_stream = TRY(try_make(move(stream))); - auto deflate_compressor = TRY(adopt_nonnull_own_or_enomem(new (nothrow) DeflateCompressor(move(bit_stream), compression_level))); - return deflate_compressor; -} - -DeflateCompressor::DeflateCompressor(NonnullOwnPtr stream, CompressionLevel compression_level) - : m_compression_level(compression_level) - , m_compression_constants(compression_constants[static_cast(m_compression_level)]) - , m_output_stream(move(stream)) -{ - m_symbol_frequencies.fill(0); - m_distance_frequencies.fill(0); -} - -DeflateCompressor::~DeflateCompressor() = default; - -ErrorOr DeflateCompressor::read_some(Bytes) -{ - return Error::from_errno(EBADF); -} - -ErrorOr DeflateCompressor::write_some(ReadonlyBytes bytes) -{ - VERIFY(!m_finished); - - size_t total_written = 0; - while (!bytes.is_empty()) { - auto n_written = bytes.copy_trimmed_to(pending_block().slice(m_pending_block_size)); - m_pending_block_size += n_written; - - if (m_pending_block_size == block_size) - TRY(flush()); - - bytes = bytes.slice(n_written); - total_written += n_written; - } - return total_written; -} - -bool DeflateCompressor::is_eof() const -{ - return true; -} - -bool DeflateCompressor::is_open() const -{ - return m_output_stream->is_open(); -} - -void DeflateCompressor::close() -{ -} - -// Knuth's multiplicative hash on 4 bytes -u16 DeflateCompressor::hash_sequence(u8 const* bytes) -{ - constexpr u32 const knuth_constant = 2654435761; // shares no common factors with 2^32 - return ((bytes[0] | bytes[1] << 8 | bytes[2] << 16 | bytes[3] << 24) * knuth_constant) >> (32 - hash_bits); -} - -size_t DeflateCompressor::compare_match_candidate(size_t start, size_t candidate, size_t previous_match_length, size_t maximum_match_length) -{ - VERIFY(previous_match_length < maximum_match_length); - - // We firstly check that the match is at least (prev_match_length + 1) long, we check backwards as there's a higher chance the end mismatches - for (ssize_t i = previous_match_length; i >= 0; i--) { - if (m_rolling_window[start + i] != m_rolling_window[candidate + i]) - return 0; - } - - // Find the actual length - auto match_length = previous_match_length + 1; - while (match_length < maximum_match_length && m_rolling_window[start + match_length] == m_rolling_window[candidate + match_length]) { - match_length++; - } - - VERIFY(match_length > previous_match_length); - VERIFY(match_length <= maximum_match_length); - return match_length; -} - -size_t DeflateCompressor::find_back_match(size_t start, u16 hash, size_t previous_match_length, size_t maximum_match_length, size_t& match_position) -{ - auto max_chain_length = m_compression_constants.max_chain; - if (previous_match_length == 0) - previous_match_length = min_match_length - 1; // we only care about matches that are at least min_match_length long - if (previous_match_length >= maximum_match_length) - return 0; // we can't improve a maximum length match - if (previous_match_length >= m_compression_constants.max_lazy_length) - return 0; // the previous match is already pretty, we shouldn't waste another full search - if (previous_match_length >= m_compression_constants.good_match_length) - max_chain_length /= 4; // we already have a pretty good much, so do a shorter search - - auto candidate = m_hash_head[hash]; - auto match_found = false; - while (max_chain_length--) { - if (candidate == empty_slot) - break; // no remaining candidates - - VERIFY(candidate < start); - if (start - candidate > window_size) - break; // outside the window - - auto match_length = compare_match_candidate(start, candidate, previous_match_length, maximum_match_length); - - if (match_length != 0) { - match_found = true; - match_position = candidate; - previous_match_length = match_length; - - if (match_length == maximum_match_length) - return match_length; // bail if we got the maximum possible length - } - - candidate = m_hash_prev[candidate % window_size]; - } - if (!match_found) - return 0; // we didn't find any matches - return previous_match_length; // we found matches, but they were at most previous_match_length long -} - -ALWAYS_INLINE u8 DeflateCompressor::distance_to_base(u16 distance) -{ - return (distance <= 256) ? distance_to_base_lo[distance - 1] : distance_to_base_hi[(distance - 1) >> 7]; -} - -void DeflateCompressor::lz77_compress_block() -{ - for (auto& slot : m_hash_head) { // initialize chained hash table - slot = empty_slot; - } - - auto insert_hash = [&](auto pos, auto hash) { - auto window_pos = pos % window_size; - m_hash_prev[window_pos] = m_hash_head[hash]; - m_hash_head[hash] = window_pos; - }; - - auto emit_literal = [&](auto literal) { - VERIFY(m_pending_symbol_size <= block_size + 1); - auto index = m_pending_symbol_size++; - m_symbol_buffer[index].distance = 0; - m_symbol_buffer[index].literal = literal; - m_symbol_frequencies[literal]++; - }; - - auto emit_back_reference = [&](auto distance, auto length) { - VERIFY(m_pending_symbol_size <= block_size + 1); - auto index = m_pending_symbol_size++; - m_symbol_buffer[index].distance = distance; - m_symbol_buffer[index].length = length; - m_symbol_frequencies[length_to_symbol[length]]++; - m_distance_frequencies[distance_to_base(distance)]++; - }; - - size_t previous_match_length = 0; - size_t previous_match_position = 0; - - VERIFY(m_compression_constants.great_match_length <= max_match_length); - - // our block starts at block_size and is m_pending_block_size in length - auto block_end = block_size + m_pending_block_size; - size_t current_position; - for (current_position = block_size; current_position < block_end - min_match_length + 1; current_position++) { - auto hash = hash_sequence(&m_rolling_window[current_position]); - size_t match_position; - auto match_length = find_back_match(current_position, hash, previous_match_length, - min(m_compression_constants.great_match_length, block_end - current_position), match_position); - - insert_hash(current_position, hash); - - // if the previous match is as good as the new match, just use it - if (previous_match_length != 0 && previous_match_length >= match_length) { - emit_back_reference((current_position - 1) - previous_match_position, previous_match_length); - - // skip all the bytes that are included in this match - for (size_t j = current_position + 1; j < min(current_position - 1 + previous_match_length, block_end - min_match_length + 1); j++) { - insert_hash(j, hash_sequence(&m_rolling_window[j])); - } - current_position = (current_position - 1) + previous_match_length - 1; - previous_match_length = 0; - continue; - } - - if (match_length == 0) { - VERIFY(previous_match_length == 0); - emit_literal(m_rolling_window[current_position]); - continue; - } - - // if this is a lazy match, and the new match is better than the old one, output previous as literal - if (previous_match_length != 0) { - emit_literal(m_rolling_window[current_position - 1]); - } - - previous_match_length = match_length; - previous_match_position = match_position; - } - - // clean up leftover lazy match - if (previous_match_length != 0) { - emit_back_reference((current_position - 1) - previous_match_position, previous_match_length); - current_position = (current_position - 1) + previous_match_length; - } - - // output remaining literals - while (current_position < block_end) { - emit_literal(m_rolling_window[current_position++]); - } -} - -size_t DeflateCompressor::huffman_block_length(Array const& literal_bit_lengths, Array const& distance_bit_lengths) -{ - size_t length = 0; - - for (size_t i = 0; i < 286; i++) { - auto frequency = m_symbol_frequencies[i]; - length += literal_bit_lengths[i] * frequency; - - if (i >= 257) // back reference length symbols - length += packed_length_symbols[i - 257].extra_bits * frequency; - } - - for (size_t i = 0; i < 30; i++) { - auto frequency = m_distance_frequencies[i]; - length += distance_bit_lengths[i] * frequency; - length += packed_distances[i].extra_bits * frequency; - } - - return length; -} - -size_t DeflateCompressor::uncompressed_block_length() -{ - auto padding = 8 - ((m_output_stream->bit_offset() + 3) % 8); - // 3 bit block header + align to byte + 2 * 16 bit length fields + block contents - return 3 + padding + (2 * 16) + m_pending_block_size * 8; -} - -size_t DeflateCompressor::fixed_block_length() -{ - // block header + fixed huffman encoded block contents - return 3 + huffman_block_length(fixed_literal_bit_lengths, fixed_distance_bit_lengths); -} - -size_t DeflateCompressor::dynamic_block_length(Array const& literal_bit_lengths, Array const& distance_bit_lengths, Array const& code_lengths_bit_lengths, Array const& code_lengths_frequencies, size_t code_lengths_count) -{ - // block header + literal code count + distance code count + code length count - auto length = 3 + 5 + 5 + 4; - - // 3 bits per code_length - length += 3 * code_lengths_count; - - for (size_t i = 0; i < code_lengths_frequencies.size(); i++) { - auto frequency = code_lengths_frequencies[i]; - length += code_lengths_bit_lengths[i] * frequency; - - if (i == deflate_special_code_length_copy) { - length += 2 * frequency; - } else if (i == deflate_special_code_length_zeros) { - length += 3 * frequency; - } else if (i == deflate_special_code_length_long_zeros) { - length += 7 * frequency; - } - } - - return length + huffman_block_length(literal_bit_lengths, distance_bit_lengths); -} - -ErrorOr DeflateCompressor::write_huffman(CanonicalCode const& literal_code, Optional const& distance_code) -{ - auto has_distances = distance_code.has_value(); - for (size_t i = 0; i < m_pending_symbol_size; i++) { - if (m_symbol_buffer[i].distance == 0) { - TRY(literal_code.write_symbol(*m_output_stream, m_symbol_buffer[i].literal)); - continue; - } - VERIFY(has_distances); - auto symbol = length_to_symbol[m_symbol_buffer[i].length]; - TRY(literal_code.write_symbol(*m_output_stream, symbol)); - // Emit extra bits if needed - TRY(m_output_stream->write_bits(m_symbol_buffer[i].length - packed_length_symbols[symbol - 257].base_length, packed_length_symbols[symbol - 257].extra_bits)); - - auto base_distance = distance_to_base(m_symbol_buffer[i].distance); - TRY(distance_code.value().write_symbol(*m_output_stream, base_distance)); - // Emit extra bits if needed - TRY(m_output_stream->write_bits(m_symbol_buffer[i].distance - packed_distances[base_distance].base_distance, packed_distances[base_distance].extra_bits)); - } - return {}; -} - -size_t DeflateCompressor::encode_huffman_lengths(ReadonlyBytes lengths, Array& encoded_lengths) -{ - size_t encoded_count = 0; - size_t i = 0; - while (i < lengths.size()) { - if (lengths[i] == 0) { - auto zero_count = 0; - for (size_t j = i; j < min(lengths.size(), i + 138) && lengths[j] == 0; j++) - zero_count++; - - if (zero_count < 3) { // below minimum repeated zero count - encoded_lengths[encoded_count++].symbol = 0; - i++; - continue; - } - - if (zero_count <= 10) { - encoded_lengths[encoded_count].symbol = deflate_special_code_length_zeros; - encoded_lengths[encoded_count++].count = zero_count; - } else { - encoded_lengths[encoded_count].symbol = deflate_special_code_length_long_zeros; - encoded_lengths[encoded_count++].count = zero_count; - } - i += zero_count; - continue; - } - - encoded_lengths[encoded_count++].symbol = lengths[i++]; - - auto copy_count = 0; - for (size_t j = i; j < min(lengths.size(), i + 6) && lengths[j] == lengths[i - 1]; j++) - copy_count++; - - if (copy_count >= 3) { - encoded_lengths[encoded_count].symbol = deflate_special_code_length_copy; - encoded_lengths[encoded_count++].count = copy_count; - i += copy_count; - continue; - } - } - return encoded_count; -} - -size_t DeflateCompressor::encode_block_lengths(Array const& literal_bit_lengths, Array const& distance_bit_lengths, Array& encoded_lengths, size_t& literal_code_count, size_t& distance_code_count) -{ - literal_code_count = max_huffman_literals; - distance_code_count = max_huffman_distances; - - VERIFY(literal_bit_lengths[EndOfBlock] != 0); // Make sure at least the EndOfBlock marker is present - while (literal_bit_lengths[literal_code_count - 1] == 0) - literal_code_count--; - - // Drop trailing zero lengths, keeping at least one - while (distance_bit_lengths[distance_code_count - 1] == 0 && distance_code_count > 1) - distance_code_count--; - - Array all_lengths {}; - for (size_t i = 0; i < literal_code_count; i++) - all_lengths[i] = literal_bit_lengths[i]; - for (size_t i = 0; i < distance_code_count; i++) - all_lengths[literal_code_count + i] = distance_bit_lengths[i]; - - return encode_huffman_lengths(all_lengths.span().trim(literal_code_count + distance_code_count), encoded_lengths); -} - -ErrorOr DeflateCompressor::write_dynamic_huffman(CanonicalCode const& literal_code, size_t literal_code_count, Optional const& distance_code, size_t distance_code_count, Array const& code_lengths_bit_lengths, size_t code_length_count, Array const& encoded_lengths, size_t encoded_lengths_count) -{ - TRY(m_output_stream->write_bits(literal_code_count - 257, 5)); - TRY(m_output_stream->write_bits(distance_code_count - 1, 5)); - TRY(m_output_stream->write_bits(code_length_count - 4, 4)); - - for (size_t i = 0; i < code_length_count; i++) { - TRY(m_output_stream->write_bits(code_lengths_bit_lengths[code_lengths_code_lengths_order[i]], 3)); - } - - auto code_lengths_code = MUST(CanonicalCode::from_bytes(code_lengths_bit_lengths)); - for (size_t i = 0; i < encoded_lengths_count; i++) { - auto encoded_length = encoded_lengths[i]; - TRY(code_lengths_code.write_symbol(*m_output_stream, encoded_length.symbol)); - if (encoded_length.symbol == deflate_special_code_length_copy) { - TRY(m_output_stream->write_bits(encoded_length.count - 3, 2)); - } else if (encoded_length.symbol == deflate_special_code_length_zeros) { - TRY(m_output_stream->write_bits(encoded_length.count - 3, 3)); - } else if (encoded_length.symbol == deflate_special_code_length_long_zeros) { - TRY(m_output_stream->write_bits(encoded_length.count - 11, 7)); - } - } - - TRY(write_huffman(literal_code, distance_code)); - return {}; -} - -ErrorOr DeflateCompressor::flush() -{ - TRY(m_output_stream->write_bits(m_finished, 1)); - - // if this is just an empty block to signify the end of the deflate stream use the smallest block possible (10 bits total) - if (m_pending_block_size == 0) { - VERIFY(m_finished); // we shouldn't be writing empty blocks unless this is the final one - TRY(m_output_stream->write_bits(0b01u, 2)); // fixed huffman codes - TRY(m_output_stream->write_bits(0b0000000u, 7)); // end of block symbol - TRY(m_output_stream->align_to_byte_boundary()); - return {}; - } - - auto write_uncompressed = [&]() -> ErrorOr { - TRY(m_output_stream->write_bits(0b00u, 2)); // no compression - TRY(m_output_stream->align_to_byte_boundary()); - TRY(m_output_stream->write_value>(m_pending_block_size)); - TRY(m_output_stream->write_value>(~m_pending_block_size)); - TRY(m_output_stream->write_until_depleted(pending_block().slice(0, m_pending_block_size))); - return {}; - }; - - if (m_compression_level == CompressionLevel::STORE) { // disabled compression fast path - TRY(write_uncompressed()); - m_pending_block_size = 0; - return {}; - } - - // The following implementation of lz77 compression and huffman encoding is based on the reference implementation by Hans Wennborg https://www.hanshq.net/zip.html - - // this reads from the pending block and writes to m_symbol_buffer - lz77_compress_block(); - - // insert EndOfBlock marker to the symbol buffer - m_symbol_buffer[m_pending_symbol_size].distance = 0; - m_symbol_buffer[m_pending_symbol_size++].literal = EndOfBlock; - m_symbol_frequencies[EndOfBlock]++; - - // generate optimal dynamic huffman code lengths - Array dynamic_literal_bit_lengths {}; - Array dynamic_distance_bit_lengths {}; - generate_huffman_lengths(dynamic_literal_bit_lengths, m_symbol_frequencies, 15); // deflate data huffman can use up to 15 bits per symbol - generate_huffman_lengths(dynamic_distance_bit_lengths, m_distance_frequencies, 15); - - // encode literal and distance lengths together in deflate format - Array encoded_lengths {}; - size_t literal_code_count; - size_t distance_code_count; - auto encoded_lengths_count = encode_block_lengths(dynamic_literal_bit_lengths, dynamic_distance_bit_lengths, encoded_lengths, literal_code_count, distance_code_count); - - // count code length frequencies - Array code_lengths_frequencies { 0 }; - for (size_t i = 0; i < encoded_lengths_count; i++) { - code_lengths_frequencies[encoded_lengths[i].symbol]++; - } - // generate optimal huffman code lengths code lengths - Array code_lengths_bit_lengths {}; - generate_huffman_lengths(code_lengths_bit_lengths, code_lengths_frequencies, 7); // deflate code length huffman can use up to 7 bits per symbol - // calculate actual code length code lengths count (without trailing zeros) - auto code_lengths_count = code_lengths_bit_lengths.size(); - while (code_lengths_bit_lengths[code_lengths_code_lengths_order[code_lengths_count - 1]] == 0) - code_lengths_count--; - - auto uncompressed_size = uncompressed_block_length(); - auto fixed_huffman_size = fixed_block_length(); - auto dynamic_huffman_size = dynamic_block_length(dynamic_literal_bit_lengths, dynamic_distance_bit_lengths, code_lengths_bit_lengths, code_lengths_frequencies, code_lengths_count); - - // If the compression somehow didn't reduce the size enough, just write out the block uncompressed as it allows for much faster decompression - if (uncompressed_size <= min(fixed_huffman_size, dynamic_huffman_size)) { - TRY(write_uncompressed()); - } else if (fixed_huffman_size <= dynamic_huffman_size) { - // If the fixed and dynamic huffman codes come out the same size, prefer the fixed version, as it takes less time to decode fixed huffman codes. - TRY(m_output_stream->write_bits(0b01u, 2)); - TRY(write_huffman(CanonicalCode::fixed_literal_codes(), CanonicalCode::fixed_distance_codes())); - } else { - // dynamic huffman codes - TRY(m_output_stream->write_bits(0b10u, 2)); - auto literal_code = MUST(CanonicalCode::from_bytes(dynamic_literal_bit_lengths)); - auto distance_code_or_error = CanonicalCode::from_bytes(dynamic_distance_bit_lengths); - Optional distance_code; - if (!distance_code_or_error.is_error()) - distance_code = distance_code_or_error.release_value(); - TRY(write_dynamic_huffman(literal_code, literal_code_count, distance_code, distance_code_count, code_lengths_bit_lengths, code_lengths_count, encoded_lengths, encoded_lengths_count)); - } - if (m_finished) - TRY(m_output_stream->align_to_byte_boundary()); - - // reset all block specific members - m_pending_block_size = 0; - m_pending_symbol_size = 0; - m_symbol_frequencies.fill(0); - m_distance_frequencies.fill(0); - // On the final block this copy will potentially produce an invalid search window, but since its the final block we dont care - pending_block().copy_trimmed_to({ m_rolling_window, block_size }); - - return {}; -} - -ErrorOr DeflateCompressor::final_flush() -{ - VERIFY(!m_finished); - m_finished = true; - TRY(flush()); - TRY(m_output_stream->flush_buffer_to_stream()); - return {}; -} - -ErrorOr DeflateCompressor::compress_all(ReadonlyBytes bytes, CompressionLevel compression_level) -{ - auto output_stream = TRY(try_make()); - auto deflate_stream = TRY(DeflateCompressor::construct(MaybeOwned(*output_stream), compression_level)); - - TRY(deflate_stream->write_until_depleted(bytes)); - TRY(deflate_stream->final_flush()); - - auto buffer = TRY(ByteBuffer::create_uninitialized(output_stream->used_buffer_size())); - TRY(output_stream->read_until_filled(buffer)); - - return buffer; + return ::Compress::compress_all(bytes, compression_level); } } diff --git a/Libraries/LibCompress/Deflate.h b/Libraries/LibCompress/Deflate.h index 9e470c6de9..466ff53aff 100644 --- a/Libraries/LibCompress/Deflate.h +++ b/Libraries/LibCompress/Deflate.h @@ -1,6 +1,7 @@ /* * Copyright (c) 2020, the SerenityOS developers. * Copyright (c) 2021, Idan Horowitz + * Copyright (c) 2025, Altomani Gianluca * * SPDX-License-Identifier: BSD-2-Clause */ @@ -8,14 +9,8 @@ #pragma once #include -#include -#include -#include -#include -#include #include -#include -#include +#include namespace Compress { @@ -60,172 +55,28 @@ ALWAYS_INLINE ErrorOr CanonicalCode::write_symbol(LittleEndianOutputBitStr return {}; } -class DeflateDecompressor final : public Stream { -private: - class CompressedBlock { - public: - CompressedBlock(DeflateDecompressor&, CanonicalCode literal_codes, Optional distance_codes); - - ErrorOr try_read_more(); - - private: - bool m_eof { false }; - - DeflateDecompressor& m_decompressor; - CanonicalCode m_literal_codes; - Optional m_distance_codes; - }; - - class UncompressedBlock { - public: - UncompressedBlock(DeflateDecompressor&, size_t); - - ErrorOr try_read_more(); - - private: - DeflateDecompressor& m_decompressor; - size_t m_bytes_remaining; - }; - - enum class State { - Idle, - ReadingCompressedBlock, - ReadingUncompressedBlock - }; - +class DeflateDecompressor final : public GenericZlibDecompressor { public: - friend CompressedBlock; - friend UncompressedBlock; - - static ErrorOr> construct(MaybeOwned stream); - ~DeflateDecompressor(); - - 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; - + static ErrorOr> create(MaybeOwned); static ErrorOr decompress_all(ReadonlyBytes); private: - DeflateDecompressor(MaybeOwned stream, CircularBuffer buffer); - - ErrorOr decode_length(u32); - ErrorOr decode_distance(u32); - ErrorOr decode_codes(CanonicalCode& literal_code, Optional& distance_code); - - static constexpr u16 max_back_reference_length = 258; - - bool m_read_final_block { false }; - - State m_state { State::Idle }; - union { - CompressedBlock m_compressed_block; - UncompressedBlock m_uncompressed_block; - }; - - MaybeOwned m_input_stream; - CircularBuffer m_output_buffer; + DeflateDecompressor(AK::FixedArray buffer, MaybeOwned stream, z_stream* zstream) + : GenericZlibDecompressor(move(buffer), move(stream), zstream) + { + } }; -class DeflateCompressor final : public Stream { +class DeflateCompressor final : public GenericZlibCompressor { public: - static constexpr size_t block_size = 32 * KiB - 1; // TODO: this can theoretically be increased to 64 KiB - 2 - static constexpr size_t window_size = block_size * 2; - static constexpr size_t hash_bits = 15; - static constexpr size_t max_huffman_literals = 288; - static constexpr size_t max_huffman_distances = 32; - static constexpr size_t min_match_length = 4; // matches smaller than these are not worth the size of the back reference - static constexpr size_t max_match_length = 258; // matches longer than these cannot be encoded using huffman codes - static constexpr u16 empty_slot = UINT16_MAX; - - struct CompressionConstants { - size_t good_match_length; // Once we find a match of at least this length (a good enough match) we reduce max_chain to lower processing time - size_t max_lazy_length; // If the match is at least this long we dont defer matching to the next byte (which takes time) as its good enough - size_t great_match_length; // Once we find a match of at least this length (a great match) we can just stop searching for longer ones - size_t max_chain; // We only check the actual length of the max_chain closest matches - }; - - // These constants were shamelessly "borrowed" from zlib - static constexpr CompressionConstants compression_constants[] = { - { 0, 0, 0, 0 }, - { 4, 4, 8, 4 }, - { 8, 16, 128, 128 }, - { 32, 258, 258, 4096 }, - { max_match_length, max_match_length, max_match_length, 1 << hash_bits } // disable all limits - }; - - enum class CompressionLevel : int { - STORE = 0, - FAST, - GOOD, - GREAT, - BEST // WARNING: this one can take an unreasonable amount of time! - }; - - static ErrorOr> construct(MaybeOwned, CompressionLevel = CompressionLevel::GOOD); - ~DeflateCompressor(); - - 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; - ErrorOr final_flush(); - - static ErrorOr compress_all(ReadonlyBytes bytes, CompressionLevel = CompressionLevel::GOOD); + static ErrorOr> create(MaybeOwned, GenericZlibCompressionLevel = GenericZlibCompressionLevel::Default); + static ErrorOr compress_all(ReadonlyBytes, GenericZlibCompressionLevel = GenericZlibCompressionLevel::Default); private: - DeflateCompressor(NonnullOwnPtr, CompressionLevel = CompressionLevel::GOOD); - - Bytes pending_block() { return { m_rolling_window + block_size, block_size }; } - - // LZ77 Compression - static u16 hash_sequence(u8 const* bytes); - size_t compare_match_candidate(size_t start, size_t candidate, size_t prev_match_length, size_t max_match_length); - size_t find_back_match(size_t start, u16 hash, size_t previous_match_length, size_t max_match_length, size_t& match_position); - void lz77_compress_block(); - - // Huffman Coding - struct code_length_symbol { - u8 symbol; - u8 count; // used for special symbols 16-18 - }; - static u8 distance_to_base(u16 distance); - size_t huffman_block_length(Array const& literal_bit_lengths, Array const& distance_bit_lengths); - ErrorOr write_huffman(CanonicalCode const& literal_code, Optional const& distance_code); - static size_t encode_huffman_lengths(ReadonlyBytes lengths, Array& encoded_lengths); - size_t encode_block_lengths(Array const& literal_bit_lengths, Array const& distance_bit_lengths, Array& encoded_lengths, size_t& literal_code_count, size_t& distance_code_count); - ErrorOr write_dynamic_huffman(CanonicalCode const& literal_code, size_t literal_code_count, Optional const& distance_code, size_t distance_code_count, Array const& code_lengths_bit_lengths, size_t code_length_count, Array const& encoded_lengths, size_t encoded_lengths_count); - - size_t uncompressed_block_length(); - size_t fixed_block_length(); - size_t dynamic_block_length(Array const& literal_bit_lengths, Array const& distance_bit_lengths, Array const& code_lengths_bit_lengths, Array const& code_lengths_frequencies, size_t code_lengths_count); - ErrorOr flush(); - - bool m_finished { false }; - CompressionLevel m_compression_level; - CompressionConstants m_compression_constants; - NonnullOwnPtr m_output_stream; - - u8 m_rolling_window[window_size]; - size_t m_pending_block_size { 0 }; - - struct [[gnu::packed]] { - u16 distance; // back reference length - union { - u16 literal; // literal byte or on of block symbol - u16 length; // back reference length (if distance != 0) - }; - } m_symbol_buffer[block_size + 1]; - size_t m_pending_symbol_size { 0 }; - Array m_symbol_frequencies; // there are 286 valid symbol values (symbols 286-287 never occur) - Array m_distance_frequencies; // there are 30 valid distance values (distances 30-31 never occur) - - // LZ77 Chained hash table - u16 m_hash_head[1 << hash_bits]; - u16 m_hash_prev[window_size]; + DeflateCompressor(AK::FixedArray buffer, MaybeOwned stream, z_stream* zstream) + : GenericZlibCompressor(move(buffer), move(stream), zstream) + { + } }; } diff --git a/Libraries/LibGfx/ImageFormats/WebPWriterLossless.cpp b/Libraries/LibGfx/ImageFormats/WebPWriterLossless.cpp index 14d655ed61..617f181d18 100644 --- a/Libraries/LibGfx/ImageFormats/WebPWriterLossless.cpp +++ b/Libraries/LibGfx/ImageFormats/WebPWriterLossless.cpp @@ -8,6 +8,7 @@ #include #include +#include #include #include #include diff --git a/Libraries/LibWeb/Compression/CompressionStream.cpp b/Libraries/LibWeb/Compression/CompressionStream.cpp index 01304a49e0..a18224c36c 100644 --- a/Libraries/LibWeb/Compression/CompressionStream.cpp +++ b/Libraries/LibWeb/Compression/CompressionStream.cpp @@ -33,7 +33,7 @@ WebIDL::ExceptionOr> CompressionStream::construct_imp case Bindings::CompressionFormat::Deflate: return TRY(Compress::ZlibCompressor::create(move(input_stream))); case Bindings::CompressionFormat::DeflateRaw: - return TRY(Compress::DeflateCompressor::construct(make(move(input_stream)))); + return TRY(Compress::DeflateCompressor::create(move(input_stream))); case Bindings::CompressionFormat::Gzip: return TRY(Compress::GzipCompressor::create(move(input_stream))); } @@ -166,16 +166,9 @@ ErrorOr CompressionStream::compress(ReadonlyBytes bytes, Finish fini })); if (finish == Finish::Yes) { - TRY(m_compressor.visit( - [&](NonnullOwnPtr const& compressor) { - return compressor->finish(); - }, - [&](NonnullOwnPtr const& compressor) { - return compressor->final_flush(); - }, - [&](NonnullOwnPtr const& compressor) { - return compressor->finish(); - })); + TRY(m_compressor.visit([](auto const& compressor) { + return compressor->finish(); + })); } auto buffer = TRY(ByteBuffer::create_uninitialized(m_output_stream->used_buffer_size())); diff --git a/Libraries/LibWeb/Compression/DecompressionStream.cpp b/Libraries/LibWeb/Compression/DecompressionStream.cpp index 392a1f4792..8739417afa 100644 --- a/Libraries/LibWeb/Compression/DecompressionStream.cpp +++ b/Libraries/LibWeb/Compression/DecompressionStream.cpp @@ -34,7 +34,7 @@ WebIDL::ExceptionOr> DecompressionStream::construct case Bindings::CompressionFormat::Deflate: return TRY(Compress::ZlibDecompressor::create(move(input_stream))); case Bindings::CompressionFormat::DeflateRaw: - return TRY(Compress::DeflateDecompressor::construct(make(move(input_stream)))); + return TRY(Compress::DeflateDecompressor::create(move(input_stream))); case Bindings::CompressionFormat::Gzip: return TRY(Compress::GzipDecompressor::create((move(input_stream)))); } diff --git a/Tests/LibCompress/TestDeflate.cpp b/Tests/LibCompress/TestDeflate.cpp index c263009b24..9fd6ce77e2 100644 --- a/Tests/LibCompress/TestDeflate.cpp +++ b/Tests/LibCompress/TestDeflate.cpp @@ -4,17 +4,12 @@ * SPDX-License-Identifier: BSD-2-Clause */ -#include - #include #include #include #include #include -#include -#include - -#define TEST_INPUT(x) ("deflate-test-files/" x) +#include TEST_CASE(canonical_code_simple) { @@ -30,7 +25,7 @@ TEST_CASE(canonical_code_simple) 0x00, 0x01, 0x01, 0x02, 0x03, 0x05, 0x08, 0x0d, 0x15 }; - auto const huffman = Compress::CanonicalCode::from_bytes(code).value(); + auto const huffman = TRY_OR_FAIL(Compress::CanonicalCode::from_bytes(code)); auto memory_stream = MUST(try_make(input)); LittleEndianInputBitStream bit_stream { move(memory_stream) }; @@ -50,7 +45,7 @@ TEST_CASE(canonical_code_complex) 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05 }; - auto const huffman = Compress::CanonicalCode::from_bytes(code).value(); + auto const huffman = TRY_OR_FAIL(Compress::CanonicalCode::from_bytes(code)); auto memory_stream = MUST(try_make(input)); LittleEndianInputBitStream bit_stream { move(memory_stream) }; @@ -75,8 +70,8 @@ TEST_CASE(deflate_decompress_compressed_block) u8 const uncompressed[] = "This is a simple text file :)"; - auto const decompressed = Compress::DeflateDecompressor::decompress_all(compressed); - EXPECT(decompressed.value().bytes() == ReadonlyBytes({ uncompressed, sizeof(uncompressed) - 1 })); + auto const decompressed = TRY_OR_FAIL(Compress::DeflateDecompressor::decompress_all(compressed)); + EXPECT(decompressed.bytes() == ReadonlyBytes({ uncompressed, sizeof(uncompressed) - 1 })); } TEST_CASE(deflate_decompress_uncompressed_block) @@ -88,13 +83,13 @@ TEST_CASE(deflate_decompress_uncompressed_block) u8 const uncompressed[] = "Hello, World!"; - auto const decompressed = Compress::DeflateDecompressor::decompress_all(compressed); - EXPECT(decompressed.value().bytes() == (ReadonlyBytes { uncompressed, sizeof(uncompressed) - 1 })); + auto const decompressed = TRY_OR_FAIL(Compress::DeflateDecompressor::decompress_all(compressed)); + EXPECT(decompressed.bytes() == (ReadonlyBytes { uncompressed, sizeof(uncompressed) - 1 })); } TEST_CASE(deflate_decompress_multiple_blocks) { - Array const compressed { + Array const compressed { 0x00, 0x1f, 0x00, 0xe0, 0xff, 0x54, 0x68, 0x65, 0x20, 0x66, 0x69, 0x72, 0x73, 0x74, 0x20, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x20, 0x69, 0x73, 0x20, 0x75, 0x6e, 0x63, 0x6f, 0x6d, 0x70, 0x72, 0x65, 0x73, 0x73, 0x65, 0x64, @@ -105,8 +100,8 @@ TEST_CASE(deflate_decompress_multiple_blocks) u8 const uncompressed[] = "The first block is uncompressed and the second block is compressed."; - auto const decompressed = Compress::DeflateDecompressor::decompress_all(compressed); - EXPECT(decompressed.value().bytes() == (ReadonlyBytes { uncompressed, sizeof(uncompressed) - 1 })); + auto const decompressed = TRY_OR_FAIL(Compress::DeflateDecompressor::decompress_all(compressed)); + EXPECT(decompressed.bytes() == (ReadonlyBytes { uncompressed, sizeof(uncompressed) - 1 })); } TEST_CASE(deflate_decompress_zeroes) @@ -118,15 +113,15 @@ TEST_CASE(deflate_decompress_zeroes) Array const uncompressed { 0 }; - auto const decompressed = Compress::DeflateDecompressor::decompress_all(compressed); - EXPECT(uncompressed == decompressed.value().bytes()); + auto const decompressed = TRY_OR_FAIL(Compress::DeflateDecompressor::decompress_all(compressed)); + EXPECT(uncompressed == decompressed.bytes()); } TEST_CASE(deflate_round_trip_store) { auto original = ByteBuffer::create_uninitialized(1024).release_value(); fill_with_random(original); - auto compressed = TRY_OR_FAIL(Compress::DeflateCompressor::compress_all(original, Compress::DeflateCompressor::CompressionLevel::STORE)); + auto compressed = TRY_OR_FAIL(Compress::DeflateCompressor::compress_all(original)); auto uncompressed = TRY_OR_FAIL(Compress::DeflateDecompressor::decompress_all(compressed)); EXPECT(uncompressed == original); } @@ -136,19 +131,21 @@ TEST_CASE(deflate_round_trip_compress) auto original = ByteBuffer::create_zeroed(2048).release_value(); fill_with_random(original.bytes().trim(1024)); // we pre-filled the second half with 0s to make sure we test back references as well // Since the different levels just change how much time is spent looking for better matches, just use fast here to reduce test time - auto compressed = TRY_OR_FAIL(Compress::DeflateCompressor::compress_all(original, Compress::DeflateCompressor::CompressionLevel::FAST)); + auto compressed = TRY_OR_FAIL(Compress::DeflateCompressor::compress_all(original)); auto uncompressed = TRY_OR_FAIL(Compress::DeflateDecompressor::decompress_all(compressed)); EXPECT(uncompressed == original); } TEST_CASE(deflate_round_trip_compress_large) { - auto size = Compress::DeflateCompressor::block_size * 2; - auto original = ByteBuffer::create_uninitialized(size).release_value(); // Compress a buffer larger than the maximum block size to test the sliding window mechanism + // Compress a buffer larger than the maximum block size to test the sliding window mechanism + auto original = TRY_OR_FAIL(ByteBuffer::create_uninitialized((32 * KiB - 1) * 2)); fill_with_random(original); + // Since the different levels just change how much time is spent looking for better matches, just use fast here to reduce test time - auto compressed = TRY_OR_FAIL(Compress::DeflateCompressor::compress_all(original, Compress::DeflateCompressor::CompressionLevel::FAST)); + auto compressed = TRY_OR_FAIL(Compress::DeflateCompressor::compress_all(original)); auto uncompressed = TRY_OR_FAIL(Compress::DeflateDecompressor::decompress_all(compressed)); + EXPECT(uncompressed.span() == original.span().slice(0, uncompressed.size())); EXPECT(uncompressed == original); } @@ -156,23 +153,5 @@ TEST_CASE(deflate_compress_literals) { // This byte array is known to not produce any back references with our lz77 implementation even at the highest compression settings Array test { 0, 0, 0, 0, 0x72, 0, 0, 0xee, 0, 0, 0, 0x26, 0, 0, 0, 0x28, 0, 0, 0x72 }; - auto compressed = TRY_OR_FAIL(Compress::DeflateCompressor::compress_all(test, Compress::DeflateCompressor::CompressionLevel::GOOD)); -} - -TEST_CASE(ossfuzz_63183) -{ - auto path = TEST_INPUT("clusterfuzz-testcase-minimized-FuzzDeflateCompression-6163230961303552.fuzz"sv); - auto test_file = MUST(Core::File::open(path, Core::File::OpenMode::Read)); - auto test_data = MUST(test_file->read_until_eof()); - auto compressed = TRY_OR_FAIL(Compress::DeflateCompressor::compress_all(test_data, Compress::DeflateCompressor::CompressionLevel::GOOD)); - auto decompressed = TRY_OR_FAIL(Compress::DeflateDecompressor::decompress_all(compressed)); - EXPECT(test_data == decompressed); -} - -TEST_CASE(ossfuzz_58046) -{ - auto path = TEST_INPUT("clusterfuzz-testcase-minimized-FuzzDeflateDecompression-5523852259360768.fuzz"sv); - auto test_file = TRY_OR_FAIL(Core::File::open(path, Core::File::OpenMode::Read)); - auto test_data = TRY_OR_FAIL(test_file->read_until_eof()); - EXPECT(Compress::DeflateDecompressor::decompress_all(test_data).is_error()); + auto compressed = TRY_OR_FAIL(Compress::DeflateCompressor::compress_all(test)); } diff --git a/Tests/LibCompress/deflate-test-files/clusterfuzz-testcase-minimized-FuzzDeflateCompression-6163230961303552.fuzz b/Tests/LibCompress/deflate-test-files/clusterfuzz-testcase-minimized-FuzzDeflateCompression-6163230961303552.fuzz deleted file mode 100644 index 91158016c0355599f2659c385c088d5999742686..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 29687 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 O-~Eny*KXB&^nU;YHxKCm diff --git a/Tests/LibCompress/deflate-test-files/clusterfuzz-testcase-minimized-FuzzDeflateDecompression-5523852259360768.fuzz b/Tests/LibCompress/deflate-test-files/clusterfuzz-testcase-minimized-FuzzDeflateDecompression-5523852259360768.fuzz deleted file mode 100644 index 88cca49cda..0000000000 --- a/Tests/LibCompress/deflate-test-files/clusterfuzz-testcase-minimized-FuzzDeflateDecompression-5523852259360768.fuzz +++ /dev/null @@ -1 +0,0 @@ - Iÿ¿ž Iÿ¿IÀž Iÿ¿ž ¿ž¼ž \ No newline at end of file